# Security review of all 55 version diffs Every diff in this directory was screened for memory-safety and object-lifetime changes: added or removed bounds checks, changed comparison constants, copy-size changes, refcount and free/release restructuring, and validation added to host-facing entry points. **Method note, and it matters more than any single finding.** Screening the decompiled C produced mostly false positives. Three independent traps were hit and each had to be settled by reading the instruction bytes: * Hex-Rays **elides dead stores** to a local that is never read back, but cannot elide a call. A function that initialised its whole stack frame with inline stores therefore decompiles to a bare `SVC`, and the same function using `bzero_core()` decompiles with a visible call -- which looks exactly like a zero-initialisation fix and is not one. * IDA sometimes **mis-analyses a tail call as a thunk** and silently drops the rest of the function. `merit_cmd03` lost 39 lines that way in one build, inventing an apparent "+3 bounds checks". * **Semantically identical rewrites** are everywhere: `x >> 30 != 0` against `x >= 0x40000000`, `x > 0x1F` against `x >= 0x20`, `len >= 0x2024` against `len > 0x2023`. So each candidate below was verified by extracting the function's bytes from both ELFs and diffing them. A function whose bytes differ only at a handful of isolated offsets with small deltas is a relocation artifact, not a change. --- ## 1. CONFIRMED security fix: HDCP timing side-channel **HDCP 23.0.0.52 -> 23.0.0.57**, `hdcp2_rx_LC_Send_L_prime`. The HDCP 2.x **locality check** verifies L'. The comparison used changed: ```c // 23.0.0.52 if ( memcmp_be(v9, (unsigned int *)a2, v10) != 0 ) // early exit: // if (v3 != v4) return ±1; // 23.0.0.57 if ( ct_memcmp(v9, a2, v10) != 0 ) // no early exit: // v3 |= v4 ^ v6; over all bytes ``` `memcmp_be` returns as soon as a word differs. `ct_memcmp` accumulates every byte difference into one accumulator and only then returns. L' verification is precisely where an early return leaks a byte-at-a-time timing oracle, so this is a real hardening fix and the only confirmed one in the corpus. Note `receiver_id_lookup` still uses `memcmp_be` in 23.0.0.57, which is appropriate -- receiver IDs are not secrets. ## 2. Behaviour change, security relevance unresolved: SYS_DRV service 0x208 A new error-path call was added to exactly the four content-protection TAs, within a narrow window, and to none of the other twelve modules: | module | first build with `sysdrv_rpc_208` | |---|---| | GCP | 22.0.0.62 | | HDCP | 23.0.0.59 | | MERIT | 24.0.0.56 | | OPM | 25.0.0.62 | absent in all builds of asd, auc, csu, dtm, fp, oc, powerplay, rap, ras, tmz, wspe, xgmi. The shape is consistent: on every failure path, call the service with a magic first/second argument pair (`(6, 0xF000)` in MERIT, `(3, 0xF000)` in GCP, `(227, 1, 0xF000)` in ASD's equivalent inline form), then return `0xFFFF0000 | code`. ASD's variant adds `if (rpc_id <= 0xFFFE)` before logging a 16-bit-truncated id, confirming the data path is a reporting channel rather than validation. This is a coordinated rollout of structured error reporting across the DRM TAs. Whether it also tears down protection state on failure cannot be decided from this corpus -- it needs the SYS_DRV image, which is not here. Flagged, not claimed. ## 3. NOT FIXED in any build: unbounded HDCP2 SRM length Not a diff finding but a *non*-diff finding, and the most actionable item here. `asd_srm_header_check` validates an attacker-supplied System Renewability Message: ``` byte0>>4 == 8 (HDCP1 SRM): if (len - 43 > 0x13D5) return 26; // bounded 43..5120 byte0>>4 == 9 (HDCP2 SRM): if (len > 0x186) { verify(buf, len - 379); } // lower bound ONLY -- no upper bound, and len is never // compared against the transport length ``` Checked in **all 18 ASD builds**, from 33.0.16.144 through 33.0.8.239 and the handover reference: the HDCP1 path is bounded `43..5120` and the HDCP2 path carries a lower bound only, in every single one. The asymmetry was never fixed over the whole version range available. ## 4. Verified NOT security fixes Each of these screened as a candidate and was refuted at byte level. | apparent change | verdict | |---|---| | `bzero_core(frame, 0x18)` added to 24 HDCP SVC wrappers (23.0.0.52 -> .57) | **Not a fix.** `.52` already wrote all six frame words via `MOVS`/`STRD`/`STM`. The bzero replaced inline stores; it costs +6 bytes and turns leaf functions non-leaf. Source refactor. | | `merit_cmd03` gains three bounds checks (24.0.0.57 -> .61) | **Not a change at all.** 208 bytes in both, **4 bytes differ**, deltas -1/-32/-32 -- relocations. IDA mis-read `.57`'s tail as a thunk. | | `gcp_cmdFE0D`: `arg10 >> 30 != 0` -> `arg10 >= 0x40000000` | Identical predicate. | | `gcp_cmd05`: `arg10 > 0x1F` -> `arg10 >= 0x20` | Identical predicate. | | `merit_TA_InvokeCommandEntryPoint`: `len >= 0x2024` -> `len > 0x2023` | Identical predicate. | | GCP refcount restructure in `cmd05` / `cmdFE0D` (22.0.0.60 -> .62) | Equivalent. Both release via `rpc_305` when the refcount is already 0 or decrements to 0. | | `csu_TA_InvokeCommandEntryPoint`: min length `0x14` -> `0x24` (42.0.0.2 -> .6) | **Not a fix.** Raised to accommodate the newly added `csu_cmd03`. `cmd01` touches only offsets `0x10..0x13` and `cmd02` only `0xC`, both inside the old 20-byte bound, so no shipped build under-validated. | | `asd_link_call_D49818`: `v6 <= 0x1E` -> `v6 <= 0xF` | **Not a bounds tightening.** It is an ASIC-ID allowlist whose window was rebased: `.195` tests the raw id against mask `dword_D11308` over 0..30, `.239` tests `id - 20` against mask `0xFEF5` over 20..35. The actual index bound, `a1 <= 1`, is unchanged. | | `asd_dispatch` gains/loses ~48 `case` labels | Representation. Hex-Rays alternates between an explicit `switch` and `if (rpc_id - 384 <= 0x40)`; 384 = 0x180 and 0x180+0x40 = 0x1C0, exactly the range that appears to vanish. | | `asd_dispatch` `0xFFFE` guard appears | Guards `asd_log_event(227, 1, rpc_id & 0xFFFF)`. Logging, not validation. | | `xgmi_vsnprintf_setup__h2` constant churn | Format-specifier characters (`'p'`, `'f'`, `'e'`, `'d'`, `'u'`, `'x'`...). Artifact of pairwise comparison inside one switch. | | `gcp_cmdFE08` allowlist 14 -> 28 entries | Functional, not a fix: new ASIC/family IDs admitted to key exchange as silicon ships. `0xd` still absent; `0xe` backfilled at 22.0.0.68. | ## Scope and what was not reachable 2,944 functions change at byte level across the 55 diffs after filtering relocation-only changes. The large majority are display and link programming (`asd_encoder_enable_seq`, `asd_program_link_by_signal*`, `asd_link_sub_C930`), which is hardware sequencing. Screening prioritised command handlers, dispatchers, session and crypto handlers, SRM parsing and the host-facing entry points. Two limits worth stating. ASD's largest changes sit in functions whose cross-version pairing is `FUZZY` tier, meaning the identity itself is a similarity judgement -- a bound change on a FUZZY pair is weak evidence and none is claimed here. And `asd_dispatch` genuinely grows 778 -> 1352 bytes inside branch 33.0.0; that growth is real and has not been fully attributed.