Commit f08c72f
committed
libretro-common/formats/png/rpng: integer overflow hardening + test
Audit of rpng.c (1859 lines; the PNG decoder used for
thumbnails, cores' asset bundles, RetroAchievement badges,
and every screenshot displayed by the menu). Three integer-
overflow fixes plus a regression test.
rpng: replace pointer-arithmetic chunk bound check
The chunk-size guard in rpng_iterate_image was:
if (buf + 8 + chunk_size > rpng->buff_end)
return false;
where chunk_size is a 32-bit attacker-controlled value from
the PNG stream. On 32-bit builds a value near UINT32_MAX
wraps the pointer address, the compare is defeated, and the
subsequent IDAT handler's
memcpy(rpng->idat_buf.data + rpng->idat_buf.size,
buf, chunk_size);
can read up to ~4 GiB past the end of the input buffer. On
64-bit the pointer has enough headroom that the compare
still happens to give the right answer in practice, but the
arithmetic is still UB per C99 (pointer values outside the
bounds of the object, and not one-past).
Fix: compute bytes-remaining as a size_t and compare sizes
rather than pointers. The new check also requires all 12
bytes of chunk framing (length + type + CRC) -- pre-patch
the pointer compare happened to only enforce 8 bytes'
presence, letting a chunk whose declared length consumed
all but 1..3 bytes of the input pass (not exploitable on
its own because the advance at the end of the function
then tripped the next-iteration check, but a real
correctness bug that the size compare fixes for free).
rpng: cap IDAT accumulation at 256 MiB
rpng_realloc_idat grew an internal buffer on each IDAT
chunk via:
size_t required = buf->size + chunk_size;
if (required > buf->capacity) {
while (new_cap < required) new_cap *= 2;
realloc(...);
}
On 32-bit size_t the addition could wrap after many chunks
accumulated, making the compare falsely false and the
memcpy overrun the existing allocation. The doubling loop
could similarly overflow new_cap on its own for a single
oversized chunk. On 64-bit the arithmetic holds, but there
was nothing stopping a hostile PNG from streaming in
arbitrarily many IDAT chunks to drive the allocation up.
Fix: introduce RPNG_IDAT_MAX = 256 MiB (dwarfs any
realistic libretro asset) and reject both the per-chunk
addition and the doubling loop if it would exceed that
cap. Accumulated total cannot grow past RPNG_IDAT_MAX.
rpng: widen buff_data advance to size_t
At the end of rpng_iterate_image:
rpng->buff_data += chunk_size + 12;
chunk_size + 12 is computed in uint32_t (uint32_t + int
promotes to uint32_t), so chunk_size near UINT32_MAX wraps.
The per-chunk-size overflow guard above now rejects any
value large enough to trigger this, but keep the
arithmetic explicit in size_t so that a future relaxation
of the guard upstream doesn't silently reintroduce the
wrap.
=== Regression test ===
libretro-common/samples/formats/png/rpng_chunk_overflow_test.c
Three subtests exercising the chunk-header guard:
1. chunk_size = 0xFFFFFFF8 must be rejected
2. chunk_size larger than remaining input rejected
3. Valid minimal IHDR chunk iterates cleanly (smoke)
Honest note on discriminator status: the headline bug is a
pointer-arithmetic overflow only genuinely reachable on
32-bit builds -- on a 64-bit host the pointer has enough
headroom that the compare still happens to give the right
answer (the arithmetic itself is UB, but no sanitizer
flags it in practice). These tests therefore exercise the
NEW size_t-based guard and serve as regression protection
against anyone reintroducing unguarded pointer arithmetic;
they are not pre/post discriminators on a 64-bit CI host.
Compiled with -m32 or run on a 32-bit host, subtest #1
becomes a true discriminator.
The second fix (RPNG_IDAT_MAX cap) is not directly
testable in a CI sample -- exercising the accumulator
overflow needs either a truly 32-bit build environment or
a >256 MiB crafted PNG, neither of which belong in a unit
test. The fix is localised, the cap is explicit, and the
code path leading to it is already exercised by the
existing rpng encode+decode smoke test.
CI: libretro-common samples workflow updated.
rpng_chunk_overflow_test added to RUN_TARGETS. Full local
dry-run under the GHA shell contract:
Built: 17 Ran: 18 Failed: 0
(Ran>Built because the PNG directory's Makefile now builds
both the existing `rpng` interactive sample and the new
`rpng_chunk_overflow_test` in one invocation; the build
count is per-directory.)
VC6 compatibility: rpng.c is compiled on all platforms. The
fix uses only size_t / uint32_t and standard comparisons;
no new headers.1 parent a7ac926 commit f08c72f
3 files changed
Lines changed: 77 additions & 8 deletions
File tree
- .github/workflows
- libretro-common
- formats/png
- samples/formats/png
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1397 | 1397 | | |
1398 | 1398 | | |
1399 | 1399 | | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
1400 | 1410 | | |
1401 | 1411 | | |
1402 | | - | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
1403 | 1423 | | |
1404 | 1424 | | |
1405 | 1425 | | |
1406 | 1426 | | |
1407 | 1427 | | |
1408 | 1428 | | |
| 1429 | + | |
| 1430 | + | |
1409 | 1431 | | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
1410 | 1438 | | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
1411 | 1442 | | |
1412 | 1443 | | |
1413 | 1444 | | |
| |||
1516 | 1547 | | |
1517 | 1548 | | |
1518 | 1549 | | |
| 1550 | + | |
1519 | 1551 | | |
1520 | 1552 | | |
1521 | 1553 | | |
1522 | 1554 | | |
1523 | 1555 | | |
1524 | 1556 | | |
1525 | | - | |
1526 | | - | |
| 1557 | + | |
| 1558 | + | |
| 1559 | + | |
1527 | 1560 | | |
1528 | 1561 | | |
1529 | 1562 | | |
1530 | 1563 | | |
1531 | | - | |
1532 | | - | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
1533 | 1578 | | |
1534 | 1579 | | |
1535 | 1580 | | |
| |||
1702 | 1747 | | |
1703 | 1748 | | |
1704 | 1749 | | |
1705 | | - | |
| 1750 | + | |
| 1751 | + | |
| 1752 | + | |
| 1753 | + | |
| 1754 | + | |
| 1755 | + | |
| 1756 | + | |
1706 | 1757 | | |
1707 | 1758 | | |
1708 | 1759 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
4 | 5 | | |
| |||
43 | 44 | | |
44 | 45 | | |
45 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
46 | 59 | | |
| 60 | + | |
47 | 61 | | |
48 | 62 | | |
49 | 63 | | |
50 | | - | |
| 64 | + | |
51 | 65 | | |
52 | 66 | | |
53 | 67 | | |
54 | 68 | | |
55 | 69 | | |
56 | 70 | | |
57 | 71 | | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
58 | 75 | | |
59 | | - | |
| 76 | + | |
60 | 77 | | |
61 | 78 | | |
0 commit comments