diff options
| author | user <user@clank> | 2026-09-29 23:32:02 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-09-29 23:32:02 +0200 |
| commit | bb2c2e466e991f15ff278a54fdfbca17b59be18b (patch) | |
| tree | 4e00575502cb2d98877be9dcb033051d05c12099 | |
| parent | twenty-seventh pass: resolve the lead's gate and move it one level down (diff) | |
| download | amd-psp-re-bb2c2e466e991f15ff278a54fdfbca17b59be18b.tar.gz amd-psp-re-bb2c2e466e991f15ff278a54fdfbca17b59be18b.tar.xz amd-psp-re-bb2c2e466e991f15ff278a54fdfbca17b59be18b.zip | |
twenty-eighth pass: gate resolves into a split verdict, one writer bounded and one not
Pass twenty-seven reduced the lead to whether a single count field can exceed 1188. It
cannot: its only writer sets it from a five-arm dispatch to one of five constants with a
maximum of 64, and the consumer calls that writer immediately before reading the field, so
the two are paired and that path is safe by a wide margin.
The length field has a second writer that is not bounded. After a properly bounded copy of
message payload into the slot's buffer, a dword is read back out of the just-copied
content and stored into the length field with no validation between load and store. The
two writers also disagree about units, since the first stores a count pre-scaled by eight
so the consumer's shift recovers it while the second stores a raw dword, meaning the
consumer's shift is only correct for the first.
Through the second writer a message dword becomes the shifted length at the copy site,
where the only test is a lower bound and the write ends past a frame that has the link
register pushed.
This is deliberately not called a finding. Four things remain unestablished and are
recorded in the lead file: whether the payload offset is host-supplied, whether the
relevant arm is reachable and under which opcode, whether a poisoned field survives to the
consumer, and whether the consumer is reachable from a host RPC. Any one failing closes
it, which is what happened to both previous leads of this shape.
| -rw-r--r-- | fw/AUDIT-TA-MEMSAFETY-HUNT.md | 39 | ||||
| -rw-r--r-- | fw/LEAD-SYSDRV-E17334-STACKED-LENGTH-NO-UPPER-BOUND.md | 54 |
2 files changed, 88 insertions, 5 deletions
diff --git a/fw/AUDIT-TA-MEMSAFETY-HUNT.md b/fw/AUDIT-TA-MEMSAFETY-HUNT.md index 4280ab5..bd45f7c 100644 --- a/fw/AUDIT-TA-MEMSAFETY-HUNT.md +++ b/fw/AUDIT-TA-MEMSAFETY-HUNT.md @@ -1488,3 +1488,42 @@ count. The lead now turns on a single sharper question: **can `[r6+0x144]` excee Still **no confirmed primitive**. The open chain is now three links shorter and its gate is one concrete field, recorded in `fw/LEAD-SYSDRV-E17334-STACKED-LENGTH-NO-UPPER-BOUND.md`. + +================================================================================ +# Twenty-eighth pass: the gate resolves into a split verdict — one writer safe, one not + +Pass twenty-seven reduced the lead to a single question: can `[r6+0x144]` exceed `0x4a4`? + +**Answer: no.** That field's only writer in SYS_DRV is `sub_E18A74 @0xe18b06`, and the value +comes from a 5-arm `tbb` setting it to one of `0x14, 0x1c, 0x20, 0x30, 0x40` — **max 64** +against the 1188 needed. Through that writer the chain is safe by a wide margin, and notably +`sub_E1D6BC` calls `sub_E18A74` immediately before reading the field, so the two are paired. + +**But `slot[0x14]` has a second writer, and it is not bounded.** `sub_E1D6BC @0xe1d8f6`: + +```asm +0xe1d8e8 cmp r2, #0xd0 ; bhi bail ; the copy length IS bounded +0xe1d8f0 blx #0xe0192c ; memcpy(slot->buf, msg+0x70, <= 0xd0) +0xe1d8f4 ldr r0, [r6, #4] ; a dword of the just-copied CONTENT +0xe1d8f6 str r0, [r4, #0x14] ; stored unvalidated +``` + +The copy is fine; the **value** read back out of it is not checked. And the two writers disagree +about the field's units — writer 1 stores a count pre-scaled by 8 so the consumer's `>> 3` +recovers it, writer 2 stores a raw dword. The consumer's shift is correct only for writer 1. + +Via writer 2, a message dword `V` produces `r8 = V >> 3` at `sub_E17334`, where the only test is +`r8 >= 0xb` and the write ends at `sp+0x210+r8` in a `0x6b4` frame that has `lr` pushed. + +## Deliberately not called a finding yet + +Four things remain unestablished, recorded in the lead file: whether `msg+0x70` is host-supplied; +whether the `0xe1d8e6` arm is reachable and under which opcode; whether a poisoned `slot[0x14]` +survives to the consumer; and whether `sub_E1CDEC` is reachable from a host RPC at all. Any one +failing closes it — which is what happened to both previous leads of this shape. + +## Objective status after twenty-eight passes + +No confirmed primitive, but for the first time in this hunt there is a **specific unvalidated +attacker-influenced value** with a concrete path to an out-of-frame write, rather than a bound +that turned out to be present. diff --git a/fw/LEAD-SYSDRV-E17334-STACKED-LENGTH-NO-UPPER-BOUND.md b/fw/LEAD-SYSDRV-E17334-STACKED-LENGTH-NO-UPPER-BOUND.md index 6e95618..7daa9be 100644 --- a/fw/LEAD-SYSDRV-E17334-STACKED-LENGTH-NO-UPPER-BOUND.md +++ b/fw/LEAD-SYSDRV-E17334-STACKED-LENGTH-NO-UPPER-BOUND.md @@ -102,13 +102,57 @@ The first writer is decisive for this chain: `sub_E1CDEC` computes `r5 = slot[0x exactly undoes the `<< 3`. **So `r8` is literally `[r6+0x144]`** — an element count, stored scaled and recovered unscaled. -**The remaining question is therefore: what is the maximum of `[r6+0x144]`?** +**Writer 1 (`0xe1d8d0`) is BOUNDED.** `[r6+0x144]`'s only writer anywhere in SYS_DRV is +`sub_E18A74 @0xe18b06` (`strd r8, r6, [sb, #0x144]`, with `sb` = the same object), and the +stored value `r8 = r4` is set in a 5-arm `tbb` to one of the constants +**`0x14, 0x1c, 0x20, 0x30, 0x40`**. Maximum **64**, versus the 1188 needed to overflow. Via +this writer the chain is safe by a factor of ~18. -* If it can exceed `0x4a4` (1188), `sub_E17334` overflows its frame and the saved `lr`. -* If it is bounded at or below that, the lead closes. +## Writer 2 is the problem: `slot[0x14]` takes a dword of message content -This is a strictly sharper question than the one this file opened with, and it is one level from -the bottom: `r6` in `sub_E1D6BC` is the object whose `+0x144` holds the count. +`slot[+0x14]`'s other writer is `sub_E1D6BC @0xe1d8f6`: + +```asm +0xe1d8e6 ldr r6, [r4, #0x4c] ; r6 = the slot's buffer pointer +0xe1d8e8 cmp r2, #0xd0 +0xe1d8ea bhi #0xe1d82c ; copy length bounded to 0xd0 -- the COPY is fine +0xe1d8ec adds r1, #0x70 ; source = message payload + 0x70 +0xe1d8f0 blx #0xe0192c ; memcpy(r6, msg+0x70, <=0xd0) +0xe1d8f4 ldr r0, [r6, #4] ; <-- a dword of the JUST-COPIED CONTENT +0xe1d8f6 str r0, [r4, #0x14] ; slot[0x14] = that dword, UNVALIDATED +``` + +The copy itself is properly bounded. What is not validated is the **value** subsequently read +out of the copied buffer and stored into `slot[0x14]`. There is no range test between the load +and the store, and no `<< 3` scaling here — unlike writer 1, which stores a count pre-scaled so +that the consumer's `>> 3` recovers it. + +Combined with the consumer: + +``` +sub_E1CDEC: r5 = slot[0x14] >> 3 ; then used as a CAPACITY, and passed on +sub_E17334: r8 = r5, only r8 >= 0xb checked, write ends at sp+0x210+r8 +``` + +a message dword `V` yields `r8 = V >> 3`, which exceeds the `0x4a4` limit for any +`V > 0x2527`. The two writers disagree about the field's units (writer 1 scaled, writer 2 +raw), which is itself the smell: the consumer's `>> 3` is correct only for writer 1. + +## What must still be established before this is a finding + +1. **Is `msg+0x70` host-supplied?** The source derives from `sub_E0B0F4(...)`'s object at `+4`, + offset `0x70` — consistent with an RPC payload, but its provenance was not traced to a + host-writable buffer. +2. **Is the `0xe1d8e6` arm reachable?** `sub_E1D6BC` selects arms through a chain of comparisons + against `dword_E1D900`/`dword_E1D918`/`dword_E1D91C`; which opcode reaches this arm is not + yet decoded. +3. **Can the same slot then reach `sub_E1CDEC` -> `sub_E17334`?** Both use the accessor, but no + ordering/lifetime argument has been made that the poisoned `slot[0x14]` survives to the + consumer. +4. **`sub_E1CDEC`'s own callers** remain untraced, so host reachability of the consumer is + equally unestablished. + +Any one of these failing closes the lead, exactly as happened to the XGMI and `0x109a` leads. ## Honest caveats |
