aboutsummaryrefslogtreecommitdiffstats
path: root/psp_out/ORPHAN-CODE-SWEEP.md
blob: 556ac5a9536aedcd14f6f3b2f7b1b25fe23f21fc (plain) (blame)
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
# Orphan-code sweep — code that was invisible to every prior scan

## Why this was run

`hdcp_subcmd17` was found late, by accident. It had been invisible to every scan
in this project because `sub_402910` reaches it via `JUMPOUT`, so IDA never made
it a function -- and every tool I used (bug scans, the 54 `handlers/*.c` files,
the xref maps) iterates over *functions*. Code that is not in a function does not
exist to any of them.

That is a systematic blind spot, not a one-off. This sweep enumerates it:
for each binary, compute the set of bytes not covered by any defined function,
drop padding/literal pools, and keep regions that contain real instructions or
have inbound CALL/JMP xrefs.

## Result: 10 functions were missing across 2 binaries

### HDCP (105 -> 108 functions)

| addr | frame | callers | verdict |
|---|---|---|---|
| `hdcp_orphan_403500` | 0x54 | `0x402f0c`, `0x40419c` | clean |
| `hdcp_orphan_403EB0` | 0x29C (668 B) | `0x4036d8` | clean |
| `hdcp_orphan_404118` | 0x438 (1080 B) | `0x402428` | clean |

`hdcp_orphan_403500` looked dangerous:

    if ( a6 >= 0x40 ) { ta_memcpy(a4, dst, 0x40u); *a5 = 64; }   // writes 64 bytes

A 64-byte write whose only guard is a caller-supplied length. Both call sites pass
`a6 = 0x20`, so the branch is never taken and the write never happens. Bounded --
but it is exactly the shape that would be a bug if any caller passed >= 0x40.

`hdcp_orphan_404118` requires `a2 == 522` and copies 522 bytes into a 528-byte
buffer. Fits. `hdcp_orphan_403EB0` copies a constant 0x93 into a 152-byte slot.
Fits. (Hex-Rays renders that slot as `v10[16]`; as usual the declared size is
wrong -- the real gap to the next variable is 152 bytes. Do not trust it.)

### ASD (1553 -> 1560 functions)

`asd_orphan_D0974C`, `D0C0A0`, `D10F70`, `D135C4`, `D17DFC`, `D17EB4`, `D17FFC`.
None contains a variable-size copy.

`asd_orphan_D135C4` is the interesting one: it is called from `0xd13538`, i.e.
**inside the verification chain that gates the ASD 0x308 OOB copy**. It is an
ASIC/firmware gate:

    SVC 0x8D                  -> id in R0, must be <= 0x14
    TST (1 << id), #0x10000C  -> id must be 2, 3 or 20
    then version checks on the returned dword

It does not open the 0x308 gate by itself, but it is a previously-unseen
precondition on that path and it was absent from `ASD-REACHABLE-AUDIT.md`.

## CORRECTION: `hdcp_verify_mac` was three functions merged

The sweep exposed a real error in my annotations. The saved IDB had:

    hdcp_verify_mac = 0x403FF4 - 0x40423C

Inside that range IDA was using **two different frame bases** -- `[SP,#0x44+..]`
at `0x4040fc` but `[SP,#0x42C+..]` at `0x404216` -- and the prologue pushes
`{R4-R11,LR}` while `0x404238` pops `{R4-R8,PC}`. The true layout is:

    hdcp_verify_mac   0x403FF4 - 0x40410E   (282 B)  ADDEQ SP,#0x44 ; POPEQ {R4-R11,PC}
    literal pool      0x404110 - 0x404117
    orphan function   0x404118 - 0x40423C            PUSH {R4-R8,LR} ; SUB.W SP,#0x438

So my earlier statement that "`hdcp_verify_mac` is 217 instructions and contains
no backward branch" was computed over the **wrong range** -- it covered three
functions. The conclusion (no loop) survives re-checking on the true 282-byte
body, but the measurement that produced it was invalid.

## What this does NOT change

The hang analysis is unaffected. The canary check (`0x4040f8`-`0x404106`) and the
crypto calls (`0x404086`, `0x4040dc`, `0x4040e4`) are all inside the true
`hdcp_verify_mac`, and `ADDEQ SP,SP,#0x44` confirms the 68-byte frame with the
canary at `SP+0x40` sitting directly above the 32-byte output buffer at `SP+0x20`.

## Net

No new vulnerability. The value is coverage and two corrections:
a whole class of code was outside every previous scan, and one load-bearing
function boundary was wrong. Both are now fixed in the saved IDBs.