DF-5607 / cotdfault.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 131 | /* * cotdfault.c -- DF-5607 PoC: fault injection inside a REAL vkernel * cothread, proving the signal-discipline gap owned by * sys/platform/vkernel64/platform/cothread.c. * * Mechanism (no sys/ modification needed): * * - cothread_thread() (cothread.c:138-151) runs cpu_mask_all_signals() * [cothread.c:143] then tls_set_gs(0, ...) which sets the thread's %gs * base to NULL [cothread.c:149]. Every cothread therefore runs with * %gs base == NULL (readable via the get_tls_area(2) syscall, nr 473). * * - cpu_mask_all_signals() (machintr.c:205-211) blocks only * SIGALRM|SIGIO|SIGQUIT|SIGUSR1|SIGTERM|SIGWINCH|SIGUSR2|SIGURG -- * the synchronous fault signals (SIGSEGV/SIGBUS/SIGTRAP/SIGFPE) * stay DELIVERABLE in every cothread. * * - The vkernel installs exc_segfault for those fault signals * process-wide with SA_SIGINFO|SA_NODEFER and an EMPTY sa_mask * (exception.c init_exceptions, exception.c:227-233). kern_trap()'s * first statement is the %gs-relative mycpu load * (thread.h:_get_mycpu "movq %gs:globaldata") -> faults when * %gs==0 -> SA_NODEFER re-enters the handler on the same stack -> * unbounded recursion until the host kills the vkernel: no panic, * no dump, no diagnostic (the DF-5587 dynamic, exercised here on a * real boot instead of a standalone model). * * LD_PRELOAD this shim into the vkernel binary. * * - We interpose sigblock() purely for diagnostics: it lets us report * every caller's %gs base at the exact cpu_mask_all_signals() * calls (cothread.c:109 and cothread.c:143). * * - We interpose nanosleep(): the vktimer cothread calls it via * cothread_sleep() (cothread.c:186-189) immediately after * tls_set_gs(0) -- a %gs base of NULL there is BY CONSTRUCTION a * cothread in its real work loop (vktimer_thread, systimer.c:244). * The first time we see that, we inject the fault a real cothread * bug would take (e.g. if_vke.c's deliberate "*(volatile int *)0=1" * NULL-ring write): pthread_kill(self, SIGSEGV). * * If the discipline were correct (fault signals blocked for cothreads, * or a re-entrancy-safe fault handler), the vkernel would survive or * die loudly. Baseline stock vkernel: silent unbounded recursion death. * * Build (guest, as root): cc -shared -O2 -fPIC -o cotdfault.so cotdfault.c * Run: LD_PRELOAD=/root/cotdfault.so ./kernel.debug ... */ #include <signal.h> #include <stdio.h> #include <string.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include <pthread.h> #include <dlfcn.h> struct tls_info { void *base; long size; }; static int seen_nonnull_gs; /* some thread ran with %gs set (a cpu) */ static int diag_count; /* cap diagnostic lines */ static const char msg_cotd[] = "COTDFAULT: nanosleep() in cothread context (vktimer_thread, %gs base=NULL " "per tls_set_gs(0) cothread.c:149)\n"; static const char msg_fire[] = "COTDFAULT: injecting SIGSEGV -- fault signals are NOT blocked by " "cpu_mask_all_signals (machintr.c:205) in this cothread\n"; static int gs_base_is_null(void) { struct tls_info info; memset(&info, 0, sizeof(info)); if (syscall(473, 1, &info, sizeof(info)) != 0) return (0); /* be conservative */ return (info.base == NULL); } int sigblock(int mask) { static int (*real_sigblock)(int); if (real_sigblock == NULL) real_sigblock = dlsym(RTLD_NEXT, "sigblock"); if (diag_count < 40) { ++diag_count; if (gs_base_is_null()) { write(2, "COTDFATAL-DIAG: sigblock() caller %gs base=NULL " "(cothread_thread cothread.c:143)\n", 71); } else { seen_nonnull_gs = 1; } } else if (!gs_base_is_null()) { seen_nonnull_gs = 1; } return real_sigblock(mask); } int nanosleep(const struct timespec *rqtp, struct timespec *rmtp) { static int (*real_nanosleep)(const struct timespec *, struct timespec *); if (real_nanosleep == NULL) real_nanosleep = dlsym(RTLD_NEXT, "nanosleep"); if (seen_nonnull_gs && gs_base_is_null()) { static int injected; if (!injected) { injected = 1; write(2, msg_cotd, sizeof(msg_cotd) - 1); write(2, msg_fire, sizeof(msg_fire) - 1); pthread_kill(pthread_self(), SIGSEGV); /* if we return, a fixed kernel survived the fault */ } } else if (!seen_nonnull_gs && !gs_base_is_null()) { seen_nonnull_gs = 1; } return real_nanosleep(rqtp, rmtp); } |