DragonFlyBSD Kernel Audit
DF-2837 / sbmax.c
← back to finding ↓ download raw
/*
 * DF-2837 PoC: sysctl_handle_sb_max() (uipc_socket2.c:686-704) maintains
 * the `u_long sb_max` through sizeof(int) (4-byte) SYSCTL_IN/SYSCTL_OUT
 * operations even though the variable is 8 bytes on amd64:
 *
 *   - an 8-byte write is silently accepted and only its LOW 4 bytes are
 *     stored (the upper half is ignored, no error);
 *   - consequently sb_max can never hold a value >= 4 GiB, and any
 *     attempt wraps to the truncated magnitude silently.
 *
 * Root-only writer; this demonstrates the type confusion, not an
 * unprivileged attack.
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int
main(void)
{
	int oldv = 0, nowv = 0;
	size_t len = sizeof(oldv);
	u_long v8;
	int error;

	error = sysctlbyname("kern.ipc.maxsockbuf", &oldv, &len, NULL, 0);
	printf("current kern.ipc.maxsockbuf = %d (sysctl ok=%d)\n",
	       oldv, error == 0);

	/* 8-byte write whose meaningful value is 0x1_00001000 (4 GiB + 4 KiB) */
	v8 = 0x0000000100001000UL;
	error = sysctlbyname("kern.ipc.maxsockbuf", NULL, NULL, &v8,
			     sizeof(v8));
	printf("write #1: 8-byte value 0x%016llx -> sysctl returned %d (%s)\n",
	       (unsigned long long)v8, error, error ? strerror(errno) : "ok");

	len = sizeof(nowv);
	sysctlbyname("kern.ipc.maxsockbuf", &nowv, &len, NULL, 0);
	printf("after 8-byte write of 0x100001000, sb_max = %d "
	       "(0x%x)  <-- low 32 bits kept, high half silently dropped\n",
	       nowv, nowv);

	/* 8-byte write that is exactly 4 GiB + 524288 */
	v8 = 0x0000000100080000UL;
	error = sysctlbyname("kern.ipc.maxsockbuf", NULL, NULL, &v8,
			     sizeof(v8));
	len = sizeof(nowv);
	sysctlbyname("kern.ipc.maxsockbuf", &nowv, &len, NULL, 0);
	printf("write #2: 8-byte 0x100080000 -> ret=%d, sb_max = %d "
	       "(0x%x)  <-- became 0x80000, magnitude wrong by 4 GiB\n",
	       error, nowv, nowv);

	/* restore */
	v8 = (u_long)oldv;
	error = sysctlbyname("kern.ipc.maxsockbuf", NULL, NULL, &v8,
			     sizeof(v8));
	len = sizeof(nowv);
	sysctlbyname("kern.ipc.maxsockbuf", &nowv, &len, NULL, 0);
	printf("restored: sb_max = %d (ret=%d)\n", nowv, error);
	return (0);
}