DF-4847 / ffmix.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 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | /* * ffmix.c โ adversarial harness for the REAL sys/dev/sound/pcm/feeder_mixer.c * (GLM 5.3 pass-2, FXROUND follow-up; DF-4849). * * M1 :299 entry misround: count = SND_FXROUND(count_in, sz) > count_in for * bad-residue free-space values inside the claimed-exact domain => * children fill count > free bytes into b and the mixer returns * rcnt > count_in โ in-kernel sndbuf_acquire() then overflows the ring * (buffer.c:615-636, only KASSERT-protected in production). * M2 :339-364 return misround + :362 unsigned underflow: a child feeder * that returns slightly more than asked (proven possible by the * fxfmt/ffvol harnesses: feeder_format returns count+delta on entry * misrounds) makes rcnt = SND_FXROUND(ret, sz) > count, so * mcnt = count - rcnt wraps to ~0xFFFFxxxx and :368 issues * memset(b + rcnt, zerodata, ~4GiB) โ catastrophic heap overwrite, * caught here by a memset interceptor (exit code 42 = bomb detected). */ #include "fxshim.h" #include <err.h> #define min(a, b) (((a) < (b)) ? (a) : (b)) #define AFMT_PASSTHROUGH AFMT_AC3 /* ---- fake channel / buffer machinery (channel.h + buffer.h shapes) ---- */ #define PCMDIR_PLAY 1 #define PCMDIR_REC 2 #define CHN_F_MMAP 0x00000001 #define CHN_F_CLOSING 0x00000002 #define CHN_F_DIRTY 0x00000004 #define CHN_F_EXCLUSIVE 0x00000008 #define CHN_F_VIRTUAL 0x00000010 #define CHN_F_HAS_VCHAN 0x00000020 #define CHN_F_PASSTHROUGH 0x00000040 #define CHN_F_STOPPED 0x00000080 #define CHN_F_RUNNING 0x00000100 #define CHN_LOCK(ch) do { } while (0) #define CHN_UNLOCK(ch) do { } while (0) #define CHN_STOPPED(ch) (((ch)->flags & CHN_F_STOPPED) != 0) struct pcm_channel { int direction; int flags; int feedcount; int xruns; uint32_t format; struct pcm_feeder *feeder; struct snd_dbuf *bufsoft, *bufhard; struct { struct { struct pcm_channel *tqh_first; } busy; } children; struct pcm_channel *next_busy; /* fake list link */ }; struct snd_dbuf { uint8_t *buf; uint8_t *tmpbuf; uint32_t size; uint32_t align; int rp, rl; }; static inline uint32_t sndbuf_getsize(struct snd_dbuf *b) { return (b->size); } static inline uint32_t sndbuf_getalign(struct snd_dbuf *b) { return (b->align); } static inline uint8_t * sndbuf_getbuf(struct snd_dbuf *b) { return (b->buf); } static inline int sndbuf_getfree(struct snd_dbuf *b) { return ((int)(b->size)); } static inline int sndbuf_getready(struct snd_dbuf *b) { return ((int)(b->size)); } static inline uint8_t sndbuf_zerodata(uint32_t fmt) { (void)fmt; return (0); } static inline int sndbuf_acquire(struct snd_dbuf *b, uint8_t *from, unsigned int count) { (void)b; (void)from; (void)count; return (0); } static inline int sndbuf_dispose(struct snd_dbuf *b, uint8_t *to, unsigned int count) { (void)b; (void)to; (void)count; return (0); } #define CHN_FOREACH(var, parent, field) \ for ((var) = (parent)->children.busy.tqh_first; (var) != NULL; \ (var) = (var)->next_busy) /* ---- per-child source control ---- */ struct childctl { uint32_t give; /* fixed return: 0 = fill all asked */ uint32_t pad; /* extra bytes past asked (overshoot return) */ }; static struct childctl cctl[8]; static int nchildren; static uint8_t *mixwin; static uint32_t last_count; /* ---- memset bomb interceptor (defined before the file under test) ---- */ #define memset fx_memset static void *fx_memset(void *p, int c, size_t n); #include "feeder_mixer.c" static void * fx_memset(void *p, int c, size_t n) { if (n > (1u << 20)) { /* do NOT execute the multi-gigabyte memset; proof recorded */ printf("MEMSET-BOMB: memset(b + %ld, 0x%02x, %llu) " "[mixer count window was %u => mcnt underflow]\n", (long)((uintptr_t)p - (uintptr_t)mixwin), c, (unsigned long long)n, last_count); fflush(stdout); fflush(stderr); _exit(42); } return (__builtin_memset(p, c, n)); } static struct pcm_feeder mixf; static struct pcm_channel parent, child[8]; static struct pcm_feeder childf[8]; static struct snd_dbuf srcbuf; static uint8_t srcbuf_storage[1 << 17]; static uint32_t child_give(int idx, uint32_t asked) { struct childctl *cc = &cctl[idx]; uint32_t give = (cc->give != 0) ? cc->give : asked; return (give + cc->pad); } /* per-child feed stub: tagged by feeder desc idx */ uint32_t fx_source_feed_impl(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { uint32_t give, i; (void)c; (void)source; fx_audit_asks++; if ((uint64_t)count > fx_audit_max_ask) fx_audit_max_ask = count; give = child_give(f->desc->idx, count); if (fx_in_range(b, give)) { for (i = 0; i < give; i++) b[i] = (uint8_t)(0x5A ^ (i & 0x1f)); if ((uintptr_t)b + give - fx_win_base > fx_audit_max_off) fx_audit_max_off = (uintptr_t)b + give - fx_win_base; } fx_audit_given += give; return (give); } int fx_feeder_set(struct pcm_feeder *fe, int what, int value) { return (feed_mixer_set(fe, what, value)); } int main(void) { uint32_t sz, count_in, ret, found; int t; uint32_t prng = 0x27d4eb2fu; uint64_t fails = 0, checks = 0; __builtin_memset(&mixf, 0, sizeof(mixf)); mixf.desc = &mixf.desc_static; mixf.desc->in = mixf.desc->out = SND_FORMAT(AFMT_S16_LE, 6, 0); if (feed_mixer_init(&mixf) != 0) { printf("mixer init failed\n"); return (1); } sz = feed_mixer_info_tab[FEEDMIXER_INFOIDX(mixf.data)].bps * FEEDMIXER_CHANNELS(mixf.data); printf("mixer: parent fmt S16_LE 6ch, sz=%u\n", sz); srcbuf.buf = srcbuf_storage; srcbuf.size = sizeof(srcbuf_storage); srcbuf.align = sz; __builtin_memset(&parent, 0, sizeof(parent)); parent.direction = PCMDIR_PLAY; parent.flags = CHN_F_HAS_VCHAN; nchildren = 2; for (t = 0; t < nchildren; t++) { __builtin_memset(&child[t], 0, sizeof(child[t])); child[t].direction = PCMDIR_PLAY; child[t].flags = CHN_F_VIRTUAL | CHN_F_RUNNING; child[t].format = SND_FORMAT(AFMT_S16_LE, 6, 0); __builtin_memset(&childf[t], 0, sizeof(childf[t])); childf[t].desc = &childf[t].desc_static; childf[t].desc->idx = t; child[t].feeder = &childf[t]; child[t].next_busy = (t + 1 < nchildren) ? &child[t + 1] : NULL; } parent.children.busy.tqh_first = &child[0]; /* ---- M1: free-space values where FXROUND(count_in, sz) > count_in ---- */ found = 0; for (count_in = 1; count_in <= 65536 && found < 500; count_in++) { if (SND_FXROUND(count_in, sz) <= count_in) continue; found++; mixwin = fx_win_alloc(1 << 17); fx_win_base = (uintptr_t)mixwin; fx_win_limit = (uintptr_t)mixwin + (1 << 17); fx_arena_base = fx_win_base; fx_arena_limit = fx_win_base + (1 << 18); fx_audit_max_ask = 0; for (t = 0; t < nchildren; t++) { cctl[t].give = 0; cctl[t].pad = 0; } last_count = SND_FXROUND(count_in, sz); ret = feed_mixer_feed(&mixf, &parent, mixwin, count_in, &srcbuf); checks++; if (ret > count_in) { if (fails < 10) printf("FAIL[M1 ring overflow] count_in(free)=%u " "FXROUND=%u rcnt=%u (+%u bytes past free)\n", count_in, SND_FXROUND(count_in, sz), ret, ret - count_in); fails++; } fx_win_free(mixwin); } printf("M1 done: %llu checks, %llu fails (rcnt > free, ring overflow)\n", (unsigned long long)checks, (unsigned long long)fails); /* ---- M2: child overshoot return => mcnt underflow => memset bomb ---- */ { /* feeder_format observed deltas: entry FXROUND(count, oalign) * can return count+8 at oalign=12 (e.g. 65548 -> 65556) */ uint32_t overs = 8; count_in = 65536; mixwin = fx_win_alloc(1 << 17); fx_win_base = (uintptr_t)mixwin; fx_win_limit = (uintptr_t)mixwin + (1 << 17); fx_arena_base = fx_win_base; fx_arena_limit = fx_win_base + (1 << 18); for (t = 0; t < nchildren; t++) { cctl[t].give = 0; cctl[t].pad = (t == 0) ? overs : 0; } last_count = SND_FXROUND(count_in, sz); printf("M2: child0 (feeder_format-style) returns count+%u; " "FXROUND(%u+%u, %u) = %u > count %u => mcnt = %u - %u\n", overs, last_count, overs, sz, SND_FXROUND(last_count + overs, sz), last_count, last_count, SND_FXROUND(last_count + overs, sz)); fflush(stdout); ret = feed_mixer_feed(&mixf, &parent, mixwin, count_in, &srcbuf); /* reaching here means NO bomb: rcnt stayed within the window */ printf("M2: no memset bomb (rcnt=%u, window=%u) -> %s\n", ret, last_count, ret <= last_count ? "PASS" : "FAIL"); if (ret > last_count) fails++; fx_win_free(mixwin); } /* soak: random residues with well-behaved children */ for (t = 0; t < 5000; t++) { prng ^= prng << 13; prng ^= prng >> 17; prng ^= prng << 5; count_in = 4096 + (prng % 61441); mixwin = fx_win_alloc(1 << 17); fx_win_base = (uintptr_t)mixwin; fx_win_limit = (uintptr_t)mixwin + (1 << 17); fx_arena_base = fx_win_base; fx_arena_limit = fx_win_base + (1 << 18); for (int u = 0; u < nchildren; u++) { cctl[u].give = 0; cctl[u].pad = 0; } last_count = SND_FXROUND(count_in, sz); ret = feed_mixer_feed(&mixf, &parent, mixwin, count_in, &srcbuf); checks++; if (ret > count_in) { if (fails < 10) printf("FAIL[soak] count_in=%u rcnt=%u\n", count_in, ret); fails++; } fx_win_free(mixwin); } printf("ffmix: checks=%llu failures=%llu -> %s " "(exit 42 == memset bomb)\n", (unsigned long long)checks, (unsigned long long)fails, fails ? "FAIL" : "ALL PASS"); return (fails != 0); } |