DF-1048 / harness.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 132 133 134 135 136 137 138 139 140 141 142 143 144 | /* SPDX-License-Identifier: BSD-2-Clause * DF-1048 harness: proves the divide-by-zero primitive in * umcs7840_calc_baudrate() at the arithmetic level. * * The guest VM has NO USB host controller and no MCS7820/MCS7840 adapter, * so the live tcsetattr(B0) trigger path cannot be exercised at runtime. * This harness extracts the EXACT buggy function and its data table * verbatim from sys/bus/u4b/serial/umcs.c and replays the arithmetic * that the kernel would perform when called with rate==0 (B0). * * On x86-64, integer division by zero raises #DE. In kernel mode that is * a fatal trap (panic, "trap type 17"). In userspace the same #DE fault * is delivered as SIGFPE (Floating point exception / Arithmetic exception). * So a SIGFPE here is the direct userspace analog of the kernel panic the * finding describes. * * `rate` is passed through `volatile` so the optimizer cannot exploit the * undefined-behaviour of "divide by zero" to elide the div instruction * (it does so at -O2 otherwise). The kernel has no such luxury: gcc * emits a real `div`/`idiv` and the CPU raises #DE. * * Build: cc -O0 -o harness harness.c (or -O2, volatile defeats the elision) * Run: ./harness */ #include <stdio.h> #include <stdint.h> #include <signal.h> #include <setjmp.h> #include <stdlib.h> #include <string.h> /* ---- verbatim copy from sys/bus/u4b/serial/umcs.c:1052-1054 ---- */ #define NELEM(a) (sizeof(a) / sizeof(a[0])) static const uint32_t umcs7840_baudrate_divisors[] = {0, 115200, 230400, 403200, 460800, 806400, 921600, 1572864, 3145728,}; static const uint8_t umcs7840_baudrate_divisors_len = NELEM(umcs7840_baudrate_divisors); /* * volatile sink so the optimizer cannot constant-fold / dead-store-eliminate * the outputs (and thereby drop the division). */ static volatile uint16_t sink_div; static volatile uint8_t sink_clk; /* ---- verbatim copy of the BUGGY function, umcs.c:1056-1070 ---- * `rate` is read through a volatile pointer so the compiler must emit a * genuine hardware divide and cannot prove rate!=0. */ static int umcs7840_calc_baudrate_BUGGY(volatile const uint32_t *ratep, uint16_t *divisor, uint8_t *clk) { volatile const uint32_t rate = *ratep; uint8_t i = 0; if (rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1]) return (-1); for (i = 0; i < umcs7840_baudrate_divisors_len - 1 && !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i); *divisor = umcs7840_baudrate_divisors[i + 1] / rate; /* <-- trap when rate==0 */ *clk = i << 4; /* MCS7840_DEV_SPx_CLOCK_SHIFT is 4 */ return (0); } /* ---- the FIXED function (mirrors fix.diff) ---- */ static int umcs7840_calc_baudrate_FIXED(volatile const uint32_t *ratep, uint16_t *divisor, uint8_t *clk) { volatile const uint32_t rate = *ratep; uint8_t i = 0; if (rate == 0 || rate > umcs7840_baudrate_divisors[umcs7840_baudrate_divisors_len - 1]) return (-1); for (i = 0; i < umcs7840_baudrate_divisors_len - 1 && !(rate > umcs7840_baudrate_divisors[i] && rate <= umcs7840_baudrate_divisors[i + 1]); ++i); *divisor = umcs7840_baudrate_divisors[i + 1] / rate; *clk = i << 4; return (0); } static sigjmp_buf jb; static volatile sig_atomic_t got_fpe; static void fpe_handler(int sig) { (void)sig; got_fpe = 1; siglongjmp(jb, 1); } static const char * test(const char *which, int (*fn)(volatile const uint32_t *, uint16_t *, uint8_t *), uint32_t rate_in) { uint32_t rate = rate_in; /* addressable copy */ uint16_t divisor = 0xDEAD; uint8_t clk = 0xFF; got_fpe = 0; struct sigaction sa, old; sa.sa_handler = fpe_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGFPE, &sa, &old); if (sigsetjmp(jb, 1) == 0) { int rc = fn(&rate, &divisor, &clk); sigaction(SIGFPE, &old, NULL); sink_div = divisor; sink_clk = clk; static char buf[160]; snprintf(buf, sizeof(buf), "returned rc=%d divisor=0x%04x clk=0x%02x", rc, divisor, clk); (void)which; return buf; } sigaction(SIGFPE, &old, NULL); return "*** DIVIDE BY ZERO (SIGFPE / #DE trap) ***"; } int main(void) { printf("DF-1048 harness: umcs7840_calc_baudrate divide-by-zero proof\n"); printf("array umcs7840_baudrate_divisors has %u elements (len-1 = %u)\n", umcs7840_baudrate_divisors_len, (uint8_t)(umcs7840_baudrate_divisors_len - 1)); printf("On x86-64, integer /0 raises #DE -> kernel panic (trap 17); " "userspace analog is SIGFPE.\n\n"); printf("== BUGGY version (mirrors umcs.c:1056-1070 as shipped) ==\n"); printf(" rate=%-10u -> %s\n", 0u, test("BUGGY", umcs7840_calc_baudrate_BUGGY, 0u)); printf(" rate=%-10u -> %s\n", 115200u, test("BUGGY", umcs7840_calc_baudrate_BUGGY, 115200u)); printf(" rate=%-10u -> %s\n", 921600u, test("BUGGY", umcs7840_calc_baudrate_BUGGY, 921600u)); printf("\n"); printf("== FIXED version (mirrors fix.diff: rate==0 guard) ==\n"); printf(" rate=%-10u -> %s\n", 0u, test("FIXED", umcs7840_calc_baudrate_FIXED, 0u)); printf(" rate=%-10u -> %s\n", 115200u, test("FIXED", umcs7840_calc_baudrate_FIXED, 115200u)); printf(" rate=%-10u -> %s\n", 921600u, test("FIXED", umcs7840_calc_baudrate_FIXED, 921600u)); printf("\n"); printf("CONCLUSION: buggy version faults on rate==0 (kernel #DE panic, trap 17);\n"); printf(" fixed version returns -1 cleanly (-> EINVAL propagates to tcsetattr).\n"); return 0; } |