# OPEN OBSERVATION: session-table redzone write appears once real TA init is used **Status: not a finding.** Reproducible and attributed, but not root-caused and not shown to be reachable by anything but the fuzzer. Written down so it is not lost or quietly upgraded. ## What changed `harness/fuzz_hdcp.py` used to allocate its own session table and patch `dword_40237C` to point at it, then write `state=2`/`active=1` into slots by hand. It now runs `TA_CreateEntryPoint`, uses the TA's own table at the image default `0x407000`, and opens sessions with real cmd 0x0E invokes. The 16-byte redzone sentinel still sits immediately after 8 slots, now at `0x408460`. ## The observation | config | seeds with `REDZONE_session_table` | | --- | --- | | old: patched table, hand-written `active`/`state` | **0 / 5** | | new: real table, real cmd 0x0E opens | **5 / 5** (exactly 1 hit each) | 3000 iterations per seed, seeds 1-5. One hit per seed in every seed is systematic, not a lucky RNG draw. ## Attribution The writes are `ta_memcpy` (0x401058) calls landing at and just past the end of the 8-slot table, e.g.: ``` memcpy dst=0x004084b1 len=0x10 (table_end + 0x51) memcpy dst=0x004084c1 len=0x10 (table_end + 0x61) ``` plus 4-byte and 1-byte stores at `table_end + 0..15` from PCs `0x401078`, `0x401084`, `0x401088`, `0x4010a8`, `0x4010b8`, `0x4010d8`. The command being fuzzed is 0x0F. ## What has been ruled out * **Not the open path.** Running only `reset()` + `open_sessions()` for 12 iterations, with no fuzz command at all, produces **zero** redzone writes, and the TA's allocator restarts cleanly at slot 1 every iteration. An earlier hypothesis that the TA's allocator state drifts across iterations (because `reset()` zeroes the table but not TA globals) was tested and is **wrong**. * **Not legitimate BSS use.** A purely benign sequence -- 8 real cmd 0x0E opens followed by cmd 0x0F, 1, 2, 3 -- writes the redzone address **zero** times, so `0x408460` is not simply TA data that happens to live there. The oracle is sound at that address. * **Not an out-of-range slot index.** An earlier reading of mine said "slot index 8". That was wrong: the figure came from a script labelling which slot the *destination address* falls in, not the index the TA used. The fuzzer's slot field is `rng.randrange(NSESS)` = 0..7 and is never overwritten by payload mutation (payload starts at request +0x1C). So this is an overrun out of the **last** slot, whose end coincides with the table end, not a bad index. ## What has NOT been established * Why it appears under the real table and not the patched one. The most likely reason is layout: the old table was a fresh `alloc` and the last slot's contents/neighbours differ, so the same overrun either did not occur or landed somewhere unmonitored. **This is a hypothesis, untested.** * Whether the overrun is reachable by anything a host can send. This is a component-level harness that calls handlers directly; nothing here speaks to host reachability, and the surrounding work in `FINDING-HOST-MAILBOX-AND-RING-IN-EMULATOR.md` shows the HDCP command surface is **not** host-reachable in the emulated configuration at all. * Whether it is one of the four latent unbounded-index / uncapped-counter defects already recorded in `FINDINGS.md`, or something separate. ## Why it is recorded rather than fixed or claimed The harness change it came from is correct and wanted: it replaces a harness fiction with the TA's real table and real open path. The new oracle hit is a *consequence of measuring the real thing*, which is the point of the change. But an unexplained oracle hit is not a vulnerability, and this project's rule is that a result needs a validated oracle and a demonstrated path. It has the first and not the second. Next step for anyone picking this up: capture the full `ta_memcpy` call chain for the failing iteration (dst, src, len, and the calling handler) and determine the length that drives the overrun, then ask whether that length is attacker-chosen or a fuzzer artefact of the mutated descriptor lengths. --- # RESOLVED: it was my redzone sitting on top of a real TA object Triaged. **Not a firmware defect — an artefact of this harness's own sentinel placement**, introduced by the same commit that switched to the TA's real table. ## What the copies actually are Capturing every `ta_memcpy` in the failing iteration (seed 1, iteration 3, cmd 0x0F), the request targets **slot 3** — active and entirely legitimate — and the copies that trip the sentinel look like this: ``` dst=0x00408460 src=0x004077f3 len=0x0005 LR=0x004032fd dst=0x00408465 src=0x00407906 len=0x0010 LR=0x00403309 dst=0x00408475 src=0x004078f6 len=0x0010 LR=0x00403317 dst=0x00408485 src=0x004079c9 len=0x0010 LR=0x00403325 dst=0x0040849c ... LR=0x004032fd <- +0x3C ``` They start at **exactly** `table_end` and march in steps of **0x3C**. An overflow does not begin precisely at an object boundary and advance by a fixed stride; a loop over an array does. The stride is visible in the code — `adds r4, #0x3c` at `0x4032e8`, driving the serialisation loop at `0x4032f0..0x403325`. Over 400 iterations each of the four call sites produces 16 distinct destinations, all on the same 0x3C grid. So the firmware keeps a **60-byte record array immediately after the session table**, beginning at `0x408460`. The session table really is 8 slots, and the next global starts where it ends. ## Why the old configuration never saw it The old harness allocated a private table with `c.alloc(NSESS*SESS_SZ + 64)`, so `table + NSESS*SESS_SZ` landed in genuine slack. Against the TA's real table at `0x407000` the same arithmetic lands on live TA data, and any command that walks the record array trips the sentinel. 5/5 seeds, one hit each — systematic, because it was structural. ## The earlier "ruled out" list was sound but incomplete The checks recorded above were all correct as far as they went: it is not the open path, and a *benign* sequence never writes that address. That last one is exactly why this took another pass to see — the record array is only walked by particular cmd 0x0F inputs, so a benign probe leaves it untouched and the address looks unused. "Nothing benign writes here" does not establish "nothing legitimate writes here". ## Fix The session-table redzone is **removed**, not re-sited. "Writes past the last session slot" is not expressible when a real object begins at that address, and moving the sentinel past the record array would test a different, arbitrary boundary while pretending to test the table. Overruns remain covered by the stack-canary check, the argument-buffer redzones and fault classification. After removal, 3000 iterations at each of seeds 1-5: ``` seed 1 {'ok': 2954, 'CANARY_SMASH': 46} seed 2 {'ok': 2963, 'CANARY_SMASH': 37} seed 3 {'ok': 2964, 'CANARY_SMASH': 36} seed 4 {'ok': 2971, 'CANARY_SMASH': 29} seed 5 {'ok': 2965, 'CANARY_SMASH': 35} ``` No `REDZONE_session_table` in any seed; the canary oracle is unaffected. **Net effect on the security picture: none.** No defect was found and none was withdrawn — the hit never was one.