DF-5710 / harness_smbfs.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 | /* Harness: smbfs_vfsops.c caller-side strncpy misuse (DF-5710). * * Replicates VERBATIM the code from sys/vfs/smbfs/smbfs_vfsops.c:165-178 * (smbfs_mount), with the real constants (MNAMELEN=80, struct statfs field * embedded in a mount-like struct) and the real kernel strncpy compiled * unmodified from /usr/src/sys/libkern/strncpy.c. * * index() == strchr() (both BSD legacy); mapped to strchr for userland cc. * * Guard-page rig: the mount-struct analogue is placed at the end of a * readable page followed by a PROT_NONE guard page (kernel-heap analogue), * or given a large runway to visualize the unbounded zero-fill. * * Cases (each in a forked child so SIGSEGV is observable, not fatal): * 0 benign : user="joe" srv="fs01" share="pub" -> must exit 0 * 1 hostile-A: user = 76*'u' (fills budget1) -> 2nd strncpy * budget = pe-pc-2 == -1 -> SIZE_MAX copy -> SIGSEGV * 2 hostile-B: user = 75*'u' -> '@' at pe-2, '/' path -> 3rd strncpy * (share name) budget == -1 -> SIZE_MAX copy -> SIGSEGV * 3 runway : user = 76*'u' with 1 MB runway: shows the copy/pad runs * unbounded (zeroes everything past f_mntfromname) and * still dies at the far guard page. * 4 trace : prints the exact budget arithmetic for both hostile routes. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <sys/mman.h> #include <sys/wait.h> #include <unistd.h> char *kstrncpy(char *dst, const char *src, size_t n); /* kernel strncpy */ #define MNAMELEN 80 struct mount_shim { char heap_before[64]; /* analogue: heap before mnt_stat */ struct { char f_mntfromname[MNAMELEN]; } mnt_stat; char heap_after[64]; /* analogue: heap after mnt_stat */ }; static const char *tag; static void run_mount(const char *vc_username, const char *vc_srvname, const char *ss_name, size_t runway, int verbose) { char *base, *pc, *pe; size_t pagesz = (size_t)sysconf(_SC_PAGESIZE); size_t regionsz = runway + 2 * pagesz; base = mmap(NULL, regionsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (base == MAP_FAILED) { perror("mmap"); _exit(90); } /* guard page at the end of the region */ if (mprotect(base + regionsz - pagesz, pagesz, PROT_NONE) != 0) { perror("mprotect"); _exit(91); } /* place the mount analogue just before the guard page, * or at the start if we were given a runway */ { char *p = base + (runway ? 0 : (regionsz - pagesz - sizeof(struct mount_shim))); struct mount_shim *mp = (struct mount_shim *)p; long long b1, b2, b3; memset(mp, 0xA5, sizeof(*mp)); pc = mp->mnt_stat.f_mntfromname; pe = pc + sizeof(mp->mnt_stat.f_mntfromname); bzero(pc, MNAMELEN); *pc++ = '/'; *pc++ = '/'; b1 = (long long)(pe - pc - 2); if (verbose) printf("[%s] budget1=%lld user='%s'\n", tag, b1, vc_username); pc = strchr(kstrncpy(pc, vc_username, (size_t)b1), 0); /* <-- line 170 */ if (pc < pe - 1) { *(pc++) = '@'; b2 = (long long)(pe - pc - 2); /* AT the call, as in-tree */ if (verbose) printf("[%s] after user+'@': pc offset=%td budget2=%lld\n", tag, pc - (char *)mp, b2); pc = strchr(kstrncpy(pc, vc_srvname, (size_t)b2), 0); /* <-- line 173 */ if (pc < pe - 1) { *(pc++) = '/'; b3 = (long long)(pe - pc - 2); /* AT the call */ if (verbose) printf("[%s] after srv+'/': pc offset=%td budget3=%lld " "(share='%s')\n", tag, pc - (char *)mp, b3, ss_name); kstrncpy(pc, ss_name, (size_t)b3); /* <-- line 176 */ } } if (verbose) printf("[%s] survived; f_mntfromname='%.79s'\n", tag, mp->mnt_stat.f_mntfromname); } _exit(0); } static int child_case(void (*fn)(const char *, const char *, const char *, size_t, int), const char *u, const char *s, const char *h, size_t runway, int verbose, char *desc) { pid_t pid = fork(); int st; if (pid == 0) { fn(u, s, h, runway, verbose); _exit(0); /* not reached */ } waitpid(pid, &st, 0); if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) { printf(" -> result: SIGSEGV (write crossed guard page) [%s]\n", desc); return 1; } else if (WIFEXITED(st)) { printf(" -> result: clean exit(%d) [%s]\n", WEXITSTATUS(st), desc); return 0; } printf(" -> result: other status %d [%s]\n", st, desc); return -1; } int main(void) { char u76[77], u75[76]; memset(u76, 'u', sizeof(u76)); u76[76] = 0; memset(u75, 'u', sizeof(u75)); u75[75] = 0; setvbuf(stdout, NULL, _IONBF, 0); printf("== DF-5710 smbfs_vfsops.c:170/173/176 strncpy budget can be -1 ==\n"); printf("MNAMELEN=%d sizeof(f_mntfromname)=%zu budget1(78-2)=%lld\n\n", MNAMELEN, sizeof(((struct mount_shim *)0)->mnt_stat.f_mntfromname), (long long)(MNAMELEN - 4)); tag = "case0 benign"; printf("case0 benign user='joe' srv='fs01' share='pub' (no guard, adjacent heap intact)\n"); child_case(run_mount, "joe", "fs01", "pub", 0, 1, "baseline"); tag = "case1 user-route"; printf("\ncase1 user=76*'u' -> budget2==-1 -> strncpy(dst,src,SIZE_MAX)\n"); child_case(run_mount, u76, "SRV", "pub", 0, 1, "user-route guard"); tag = "case2 share-route"; printf("\ncase2 user=75*'u' -> budget3==-1 on the SHARE-name strncpy\n"); child_case(run_mount, u75, "SRV", "pub", 0, 1, "share-route guard"); tag = "case3 runway"; printf("\ncase3 user=76*'u' with 1MB runway: unbounded pad visualization\n"); child_case(run_mount, u76, "SRV", "pub", 1024 * 1024, 0, "runway"); printf(" (child zeroed everything from f_mntfromname+79 to the 1MB guard:\n" " kernel analogue = whole struct mount and adjacent heap zeroed)\n"); printf("\nVERDICT: any case1/case2/case3 SIGSEGV == unbounded copy reproduced\n"); return 0; } |