T3MP3ST v1 — hardened launch (AGPL, verify-claims 24/24, leak-scrubbed)

This commit is contained in:
faber committed 2026-07-04 21:11:05 -04:00
1 parent c7a356bde0
commit 6c57e09023
242 files changed
+1761 -51038

No files matched your search

@@ -1 +1 @@
{"id": "GHSA-hm2w-vr2p-hq7w", "cve": null, "repo": "theopolis/uefi-firmware-parser", "cwes": ["CWE-787"], "vuln_files": ["uefi_firmware/compression/Tiano/Decompress.c"], "duration_sec": 135, "error": null, "strict": true, "lenient": true, "findings_scored": [{"fileMatch": true, "lineProx": true, "cweMatch": true, "hit": true, "title": "Out-of-bounds write in ReadCLen: attacker-controlled run-length fills mCLen[] past NC with no bounds check", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 525, "cwe": "CWE-787"}, {"fileMatch": true, "lineProx": true, "cweMatch": false, "hit": true, "title": "GetInfo reports DstSize purely from attacker-controlled header (size mismatch / undersized output buffer risk)", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 717, "cwe": "CWE-130"}], "n_findings": 2, "findings": [{"title": "Out-of-bounds write in ReadCLen: attacker-controlled run-length fills mCLen[] past NC with no bounds check", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 525, "cwe": "CWE-787", "vuln_class": "out-of-bounds-write", "severity": "critical", "description": "In ReadCLen(), the symbol count `Number` is read from 9 attacker-controlled bits (GetBits(Sd, CBIT), CBIT=9 => up to 511) and the outer loop runs `while (Index < Number)`. Inside, when the decoded CharC<=2 it performs a run-length zero-fill: for CharC==2 the run length is `GetBits(Sd, CBIT) + 20` (up to 531) and the inner loop `while ((INT16)CharC >= 0) Sd->mCLen[Index++] = 0;` advances Index by up to ~531 in a single outer iteration. There is NO `Index >= NC` bound check anywhere in ReadCLen (NC=510, mCLen is UINT8 mCLen[510]). Because the outer bound is only re-checked between iterations and Index can jump by hundreds at a time, Index can be driven far beyond NC, writing zero bytes well past mCLen[] into the adjacent struct members (mPTLen, mCTable, mPTTable) and past the end of the SCRATCH_DATA scratch allocation. All inputs flow unvalidated from the compressed source buffer (Decompress -> Decode -> DecodeC -> ReadCLen) which is attacker-controlled firmware. Note the maintainer added a guard in MakeTable (Index>=TableSize) and a DataIdx check in Decode, but the analogous Index>=NC guard required in ReadCLen is missing (it exists in upstream/fixed EDK2). The line 531 single-byte write path is also unguarded.", "poc": "Provide a crafted EFI/Tiano compressed blob to EfiDecompress()/TianoDecompress(): 8-byte header CompSize/OrigSize consistent with SrcSize, then a block whose ReadPTLen tables decode the Char&Len set such that ReadCLen reads Number (9 bits) = large value and repeatedly decodes the code that maps to CharC==2, supplying GetBits(9)=large run lengths so the inner `Sd->mCLen[Index++] = 0;` loop advances Index past 510 and keeps overrunning across many outer iterations, corrupting memory beyond mCLen[510] within/after the SCRATCH_DATA buffer.", "confidence": 0.9}, {"title": "GetInfo reports DstSize purely from attacker-controlled header (size mismatch / undersized output buffer risk)", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 717, "cwe": "CWE-130", "vuln_class": "info-disclosure", "severity": "low", "description": "GetInfo() derives *DstSize entirely from the 4-byte OrigSize field in the attacker-controlled source header with no sanity bound vs SrcSize. Decompress() later enforces DstSize==OrigSize, so a mismatch is rejected, but a caller that allocates based on this value and then runs the decoder is exposed to the ReadCLen/Decode write primitives above. Secondary to the primary finding.", "poc": "Header bytes 4..7 set to an arbitrary 32-bit OrigSize control the size returned to the caller for buffer allocation.", "confidence": 0.25}], "transcript": ""}
{"id": "GHSA-hm2w-vr2p-hq7w", "cve": null, "repo": "theopolis/uefi-firmware-parser", "cwes": ["CWE-787"], "vuln_files": ["uefi_firmware/compression/Tiano/Decompress.c"], "duration_sec": 139.8, "error": null, "strict": true, "lenient": true, "findings_scored": [{"fileMatch": true, "lineProx": true, "cweMatch": true, "hit": true, "title": "Out-of-bounds write in ReadCLen via unbounded code-length run expansion (missing Index >= NC guard)", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 525, "cwe": "CWE-787"}, {"fileMatch": true, "lineProx": true, "cweMatch": true, "hit": true, "title": "Out-of-bounds read of destination buffer when DataIdx == mOrigSize (off-by-one in copy bound check)", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 662, "cwe": "CWE-125"}], "n_findings": 2, "findings": [{"title": "Out-of-bounds write in ReadCLen via unbounded code-length run expansion (missing Index >= NC guard)", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 525, "cwe": "CWE-787", "vuln_class": "buffer-overflow (out-of-bounds write)", "severity": "critical", "description": "ReadCLen() parses the Char&Len code-length table from the attacker-controlled compressed stream. Number = GetBits(Sd, CBIT=9) yields up to 511, while mCLen is only NC = (0xff + MAXMATCH + 2 - THRESHOLD) = 510 bytes. The loop `while (Index < Number)` writes Sd->mCLen[Index++] with NO check that Index < NC (the upstream EDK2 fix adds such a guard; it is absent here). Worse, the run-length branch reads CharC = GetBits(Sd, CBIT) + 20 (up to 531) and then executes `while ((INT16)CharC >= 0) { Sd->mCLen[Index++] = 0; CharC--; }`, so a single symbol can advance Index by up to ~531 with no bound. Index (UINT16) can therefore run far past 510, overflowing mCLen and clobbering the adjacent fields of SCRATCH_DATA (mPTLen[31], then mCTable[4096], mPTTable[256]). All of these values (Number and each CharC) are pulled directly from the compressed bitstream via GetBits with no validation or sanitizer between source and sink. Reachable unauthenticated by simply feeding a crafted Tiano/EFI-compressed section to TianoDecompress()/EfiDecompress()/Decompress(), which the parser invokes on untrusted firmware images.", "poc": "Build an EFI/Tiano compressed blob whose 8-byte header sets CompSize>=8 and any OrigSize (DstSize must equal OrigSize). In the first block: BlockSize (16 bits) nonzero; encode the Extra Set (ReadPTLen NT) as a trivial valid table; then in ReadCLen set Number (9 bits) = 511, and repeatedly emit the CharC==2 symbol whose run count GetBits(9)+20 = 531 zeros. On the first such symbol Index jumps from 0 to 531, writing Sd->mCLen[0..530]=0 \u2014 21 bytes past the 510-byte array \u2014 and subsequent iterations (Index<511) push writes hundreds of bytes further, corrupting mPTLen and mCTable in the SCRATCH_DATA buffer.", "confidence": 0.85}, {"title": "Out-of-bounds read of destination buffer when DataIdx == mOrigSize (off-by-one in copy bound check)", "file": "uefi_firmware/compression/Tiano/Decompress.c", "line": 662, "cwe": "CWE-125", "vuln_class": "info-disclosure (out-of-bounds read)", "severity": "low", "description": "In Decode(), the back-reference source index is validated with `if (DataIdx > Sd->mOrigSize)` using '>' rather than '>='. When DecodeP() yields a value making DataIdx exactly equal to mOrigSize, the guard passes and the copy loop reads Sd->mDstBase[DataIdx] one byte past the end of the destination buffer. Both DataIdx (derived from DecodeP over attacker bits) and the copy are attacker-influenced.", "poc": "Craft compressed data whose pointer decode produces DecodeP(Sd) such that mOutBuf - DecodeP - 1 == mOrigSize, passing the '>' check and reading mDstBase[mOrigSize].", "confidence": 0.3}], "transcript": ""}