DF-5587 / coresig.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 | /* * coresig.c โ DF-5587 reproducer (cothread fault -> unbounded handler * recursion under the exception.c signal installs, on the DragonFly host). * * Models the vkernel cothread condition: * * - cothread threads run with %gs = 0 (tls_set_gs(0, ...), * cothread.c:143-148) โ "mycpu" is ILLEGAL in cothreads; * - cpu_mask_all_signals() masks only the 8 interrupt signals * (machintr.c:205-211) โ SIGSEGV/SIGBUS/SIGTRAP/SIGFPE stay * deliverable in cothreads; * - those fault signals are handled process-wide by exc_segfault, * installed SA_SIGINFO|SA_NODEFER with an EMPTY sa_mask * (exception.c:222-229); * - exc_segfault's first real work is kern_trap(), whose first statement * is `struct globaldata *gd = mycpu;` (trap.c:585) โ a %gs-relative * load that FAULTS when %gs==0. * * Consequence: any fault in a cothread -> exc_segfault -> kern_trap -> * mycpu fault -> SIGSEGV re-delivered (SA_NODEFER, same stack) -> same * fault -> unbounded recursion -> host cannot push the signal frame -> * process death with NO vkernel panic, NO dump, NO diagnostic. * * This reproducer installs the exact signal configuration of * init_exceptions() and a handler whose first dereference is of a NULL * "TLS" base (the %gs==0 condition), then reports the recursion depth * reached before the host killed the child. * * Expected: child dies of SIGSEGV (recursion), parent prints * RECURSION_DEPTH=<large> and SILENT_DEATH_CONFIRMED. */ #include <sys/mman.h> #include <sys/types.h> #include <sys/wait.h> #include <errno.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> static volatile long *depthp; /* shared with parent: SHARED anon */ static volatile long tls_base; /* the "%gs base": 0, like cothread */ /* * ~ exc_segfault() [exception.c:262-272] -> kern_trap() [trap.c:583-585]: * struct globaldata *gd = mycpu; <- %gs-relative load, %gs==0 -> fault */ static void fault_handler(int sig, siginfo_t *si, void *ctx) { int save = errno; (*depthp)++; /* count re-entries (mapped SHARED) */ /* kern_trap's first statement with a cothread's %gs==0: */ { volatile char *p = (volatile char *)(uintptr_t)tls_base; *p; /* fault at "mycpu" -> re-enter */ } errno = save; } int main(void) { struct sigaction sa; pid_t pid; int st; depthp = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0); if (depthp == MAP_FAILED) { perror("mmap"); return (2); } *depthp = 0; tls_base = 0; /* cothread %gs base */ pid = fork(); if (pid < 0) { perror("fork"); return (2); } if (pid == 0) { /* ~ init_exceptions() [exception.c:222-229] โ exact */ memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = fault_handler; sa.sa_flags = SA_SIGINFO | SA_NODEFER; sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, NULL); /* "the cothread takes a fault" (driver bug, bad DMA pointer, * stack overrun โ anything): */ { volatile char *p = (volatile char *)(uintptr_t)0x10; *p; /* first SIGSEGV */ } _exit(0); /* not reached */ } if (waitpid(pid, &st, 0) < 0) { perror("waitpid"); return (2); } printf("child wait status: exited=%d exit=%d signaled=%d termsig=%d " "core=%d\n", WIFEXITED(st), WIFEXITED(st) ? WEXITSTATUS(st) : -1, WIFSIGNALED(st), WIFSIGNALED(st) ? WTERMSIG(st) : -1, WCOREDUMP(st)); printf("RECURSION_DEPTH=%ld\n", *depthp); if (WIFSIGNALED(st) && *depthp > 100) { printf("SILENT_DEATH_CONFIRMED: handler re-entered %ld times " "before the host killed the process โ no panic, no " "dump path, no diagnostic\n", *depthp); return (0); } printf("UNEXPECTED_OUTCOME\n"); return (1); } |