DF-0819 / hammer_blkmap_trigger.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 | /* * DF-0819 runtime trigger โ attempt to deadlock a mounted HAMMER filesystem * by hitting the hammer_bnew error path under memory pressure / ENOSPC. * * This must be run as root (HAMMER mount requires root). It: * 1. Creates a small hammer image (mdconfig) * 2. newfs_hammer on it * 3. Mounts it * 4. Fills it to near-ENOSPC with small files * 5. Concurrently mmaps+memsets large anonymous regions (memory pressure) * while writing 16K-aligned blocks to the hammer fs * 6. Tries to trigger the hammer_bnew failure -> blkmap_lock leak * * If the filesystem DEADLOCKS (all further writes hang in D-state), * the bug is triggered. If writes return ENOSPC, the error path was * not reached (expected โ hammer_io_new always returns 0). * * Usage: ./hammer_blkmap_trigger <mountpoint> * (root must set up the hammer image and mount it first) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <sys/mman.h> #include <sys/stat.h> #include <sys/wait.h> #include <signal.h> static volatile int g_running = 1; static void alarm_handler(int sig) { g_running = 0; } int main(int argc, char **argv) { const char *mp; char path[512]; int fd, i, written = 0, errors = 0; char buf[16384]; /* 16K โ HAMMER_BUFSIZE, to hit buffer-boundary alloc */ if (argc < 2) { fprintf(stderr, "usage: %s <hammer_mountpoint>\n", argv[0]); return 2; } mp = argv[1]; memset(buf, 'A', sizeof(buf)); /* Child: memory pressure via mmap+memset */ pid_t memchild = fork(); if (memchild == 0) { size_t total = 0; for (;;) { void *p = mmap(NULL, 64*1024*1024, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); if (p == MAP_FAILED) { /* system out of memory โ keep trying to maintain pressure */ usleep(1000); continue; } memset(p, 0xAB, 64*1024*1024); total += 64*1024*1024; /* don't unmap โ keep memory consumed */ if (total > (size_t)3*1024*1024*1024) break; /* 3 GB consumed */ } /* spin to keep pressure on */ for (;;) pause(); _exit(0); } /* Parent: hammer writes to near-full filesystem */ signal(SIGALRM, alarm_handler); alarm(15); /* 15 second budget */ for (i = 0; g_running && i < 100000; i++) { snprintf(path, sizeof(path), "%s/stress_%06d", mp, i); fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644); if (fd < 0) { if (errno == ENOSPC) { errors++; continue; /* expected when fs is full */ } errors++; if (i > 100 && errors > 1000) { /* If errors are not ENOSPC, something else is wrong */ break; } continue; } /* write a few 16K blocks to trigger buffer-boundary allocation */ int j; for (j = 0; j < 4; j++) { ssize_t n = write(fd, buf, sizeof(buf)); if (n < 0) { if (errno != ENOSPC) errors++; break; } written++; } close(fd); /* unlink periodically to avoid filling with too many inodes */ if (i % 100 == 0) unlink(path); } /* Check if filesystem is still responsive */ snprintf(path, sizeof(path), "%s/.responsive_check", mp); alarm(5); fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, 0644); if (fd >= 0) { write(fd, "ok", 2); close(fd); unlink(path); printf("RESULT: filesystem still responsive after %d writes, %d errors\n", written, errors); printf("(hammer_bnew error path NOT hit โ lock leak NOT triggered)\n"); printf("This confirms the error path is effectively dead code.\n"); } else { printf("RESULT: filesystem UNRESPONSIVE (open returned %d: %s)\n", errno, strerror(errno)); printf("Possible deadlock โ blkmap_lock may be leaked!\n"); } kill(memchild, SIGTERM); waitpid(memchild, NULL, 0); return 0; } |