DF-2552 / arith_proof.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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | /* * DF-2552 โ Arithmetic proof of the sbuf_extend/sbuf_extendsize int-truncation * defect. * * This program replicates the EXACT arithmetic of: * sys/kern/subr_sbuf.c: sbuf_extendsize(int size) * sys/kern/subr_sbuf.c: sbuf_extend(struct sbuf *s, int addlen) * * It shows that when s->s_size (ssize_t) + addlen (int) exceeds INT_MAX, * the narrowing conversion to `int` (the parameter type of * sbuf_extendsize) produces a negative value. sbuf_extendsize then takes * the `size < 4096` branch and returns SBUF_MINEXTENDSIZE (16). The * subsequent memcpy(newbuf, s->s_buf, s->s_size) in sbuf_extend copies * s->s_size bytes (e.g. 4096) into the 16-byte buffer โ a heap overflow. * * This proves the arithmetic defect WITHOUT needing a kernel. The * companion kernel-module harness (sbuftest_mod/) demonstrates the actual * heap overflow in the running kernel. * * Reachability note: sbuf_bcopyin/sbuf_copyin/sbuf_uionew โ the only * functions that pass user-influenced addlen to sbuf_extend โ have ZERO * in-tree callers (verified via grep). All live sbuf growth happens one * byte at a time via sbuf_put_byte -> sbuf_extend(s, 1), which can never * overflow int. The defect is therefore a LATENT code-level bug, not * reachable from unprivileged userspace on the current kernel. */ #include <stdio.h> #include <stdint.h> #include <limits.h> /* --- Exact replicas from sys/kern/subr_sbuf.c --- */ #define SBUF_MINEXTENDSIZE 16 #define SBUF_MAXEXTENDSIZE 4096 #define SBUF_MAXEXTENDINCR 4096 #define roundup2(x, y) (((x) + ((y) - 1)) & (~((uintptr_t)(y) - 1))) /* Replica of sbuf_extendsize (subr_sbuf.c:131-145) โ takes int, narrows */ static int sbuf_extendsize_buggy(int size) { int newsize; if (size < (int)SBUF_MAXEXTENDSIZE) { newsize = SBUF_MINEXTENDSIZE; while (newsize < size) newsize *= 2; } else { newsize = roundup2(size, SBUF_MAXEXTENDINCR); } /* KASSERT(newsize >= size) โ we check and report */ return (newsize); } /* Replica of the FIXED sbuf_extendsize โ takes ssize_t, no truncation */ static int sbuf_extendsize_fixed(long size) /* ssize_t == long on 64-bit */ { int newsize; if (size < 0) return (-1); /* caller checks for error */ if (size < (long)SBUF_MAXEXTENDSIZE) { newsize = SBUF_MINEXTENDSIZE; while (newsize < size) newsize *= 2; } else { newsize = roundup2(size, SBUF_MAXEXTENDINCR); } return (newsize); } /* * Simulate the extend arithmetic. * s_size: ssize_t (the sbuf's current buffer size) * addlen: int (bytes needed) * Returns the newsize that sbuf_extend would kmalloc. */ static void simulate_extend(long s_size, int addlen, const char *label) { long ssize_sum; /* s->s_size + addlen computed as ssize_t */ int narrowed; /* (int)(s->s_size + addlen) โ what sbuf_extendsize sees */ int newsize_buggy; int newsize_fixed; ssize_sum = s_size + (long)addlen; narrowed = (int)ssize_sum; /* implicit narrowing in the real code */ newsize_buggy = sbuf_extendsize_buggy(narrowed); printf("=== %s ===\n", label); printf(" s_size (ssize_t) = %ld\n", s_size); printf(" addlen (int) = %d\n", addlen); printf(" s_size + addlen = %ld (as ssize_t, no overflow in 64-bit)\n", ssize_sum); printf(" (int)(sum) = %d (narrowed โ what sbuf_extendsize receives)\n", narrowed); printf(" BUGGY newsize = %d (sbuf_extendsize returns this)\n", newsize_buggy); if (newsize_buggy < s_size && newsize_buggy < ssize_sum) { long overflow_bytes = s_size - newsize_buggy; printf(" *** HEAP OVERFLOW: kmalloc(%d) then memcpy(%ld bytes) => %ld-byte overflow! ***\n", newsize_buggy, s_size, overflow_bytes); } else { printf(" (no overflow โ allocation is large enough)\n"); } /* Now show the fixed version */ if (ssize_sum > INT_MAX || ssize_sum < 0) { newsize_fixed = -1; /* fixed code rejects */ printf(" FIXED newsize = ENOMEM (overflow detected, extend refused)\n"); } else { newsize_fixed = sbuf_extendsize_fixed(ssize_sum); printf(" FIXED newsize = %d (correct)\n", newsize_fixed); } printf("\n"); } int main(void) { long s_size; int addlen; long trigger_len; printf("DF-2552: sbuf_extend / sbuf_extendsize int-truncation proof\n"); printf("=============================================================\n\n"); /* Scenario 1: normal operation (no overflow) */ s_size = 16; addlen = 1; simulate_extend(s_size, addlen, "Normal: s_size=16, addlen=1 (sbuf_put_byte path)"); /* Scenario 2: the bug โ s_size=4096, addlen crafted via sbuf_bcopyin */ /* * sbuf_bcopyin(s, uaddr, len) computes addlen = (int)(len - SBUF_FREESPACE(s)). * With s_size=4096, s_len=0: SBUF_FREESPACE = 4095. * Choose len = INT_MAX + 4095 = 2147487742 so addlen = INT_MAX = 2147483647. * Then s_size + addlen = 4096 + 2147483647 = 2147487743 > INT_MAX. * Narrowed to int: -2147479553 (negative). * sbuf_extendsize(-2147479553) returns 16. * kmalloc(16), memcpy(old 4096 bytes) => 4080-byte heap overflow. */ s_size = 4096; trigger_len = (long)INT_MAX + 4095; /* = 2147487742 */ /* In sbuf_bcopyin: addlen = (int)(trigger_len - SBUF_FREESPACE(s)) */ long freespace = s_size - 1; /* SBUF_FREESPACE = s_size - (s_len + 1) = 4095 */ addlen = (int)(trigger_len - freespace); /* should be INT_MAX */ printf("sbuf_bcopyin path: s_size=%ld, len=%ld, SBUF_FREESPACE=%ld\n", s_size, trigger_len, freespace); printf(" addlen = (int)(len - SBUF_FREESPACE) = (int)(%ld) = %d\n\n", trigger_len - freespace, addlen); simulate_extend(s_size, addlen, "BUG: sbuf_bcopyin with crafted len"); /* Scenario 3: larger s_size, same pattern */ s_size = 65536; freespace = s_size - 1; trigger_len = (long)INT_MAX + freespace; addlen = (int)(trigger_len - freespace); printf("sbuf_bcopyin path: s_size=%ld, len=%ld, addlen=%d\n\n", s_size, trigger_len, addlen); simulate_extend(s_size, addlen, "BUG: larger s_size=65536"); printf("=============================================================\n"); printf("CONCLUSION: The int-truncation in sbuf_extendsize(int) is real.\n"); printf("When s_size + addlen > INT_MAX, the narrowed argument is negative,\n"); printf("sbuf_extendsize returns 16, and sbuf_extend's memcpy overflows the\n"); printf("16-byte allocation by s_size - 16 bytes.\n"); printf("REACHABILITY: sbuf_bcopyin/sbuf_copyin/sbuf_uionew (the only callers\n"); printf("that pass large addlen) have ZERO in-tree callers. Not reachable from\n"); printf("unprivileged userspace. Latent code defect.\n"); return 0; } |