Commit Graph
12 Commits
Author SHA1 Message Date
Alex Goodman 7717baa43d fix: refuse archive entry names that forge paths or lines
`entryName` only rejected NUL and over-long names, so whatever an archive called an entry flowed
straight into `FullPath`, the file tree and every encoder.

- a name containing `!/` could pose as a nested archive boundary and collide with a real nested entry
- control characters and unicode line separators could add lines to SPDX tag-value output

Both are now refused, and refused names (over-long ones included) mark the resolver truncated with
the quoted name instead of being dropped silently.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-10-21 11:20:30 -04:00
Keith Zantow a1f7c52d89 fix: unknowns, better full vs local path handling
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-10-20 11:15:17 -04:00
Keith Zantow 3e4b036e22 fix: keep RealPath as top-level, use FullPath and utility functions FullNestedPath, LocalPath, Path
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-10-19 15:44:37 -04:00
Keith Zantow 17d109e8b5 chore: remove custom index in favor of existing filetree.Index
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2026-10-13 16:22:32 -04:00
Keith Zantow d95abbf6b5 chore: truncation reason to error type
Signed-off-by: Keith Zantow <keith.zantow@anchore.com>
2026-09-30 15:52:45 -04:00
Alex Goodman 0152e5b59c fix: a damaged entry truncates the archive instead of failing it
a read error while copying one entry (a zip CRC mismatch, a corrupt deflate stream) was returned from `add`. That aborted the walk, and `Extract` threw away every entry already stored. So a large war with one damaged, irrelevant entry produced zero packages with the feature on, while java's legacy path (which reads only the entries it needs) still cataloged it.

- a read error on one entry now drops that entry and marks the resolver truncated (`entry "x" could not be read: ...`), and the walk continues
- if the stream itself breaks partway (a truncated `.tar.gz`), whatever was read before the break is kept and the archive is truncated. With nothing read it is still an error, as before
- disk limit and decompression budget errors still stop the walk

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-24 13:28:53 -04:00
Alex Goodman 81ee189bf4 fix: bound total decompression under each top-level archive
the limiter bounds what's held *at once*, and each archive gives its bytes back when it's released. So a recursive bomb (42.zip shape) never trips a limit: every level fits on its own, and the scan just runs for effectively forever. Capping each archive at N× its own size wouldn't help either, since every level of such a bomb is high-ratio, so the work still grows about N-fold per level.

- each top-level archive gets one decompression budget of `max(100 × its size, 1 GiB)`, spent by every archive nested under it at any depth
- the budget is attached to the context when the walk enters a top-level archive, so `Extract` picks it up without a signature change
- once it's spent, the archive being extracted is truncated with a "possible decompression bomb" unknown and the walk moves on. The next top-level archive starts fresh

real trees (a war of jars, a layer of tarballs) expand a few times over, well under the ratio.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-24 13:23:32 -04:00
Alex Goodman d27b6ebb1b fix: cap each extracted archive entry at 2 GB
`put` had no per-entry cap, unlike the legacy java path which capped every file at 2 GB through `intFile.SafeCopy`. At depth 1, one entry of an overlapping-entry zip bomb could decompress all the way to the 100 GB disk limit before anything stopped it.

- entries are now read through the same 2 GB cap (`intFile.PerFileReadLimit`, now exported). An entry over it is dropped after cap+1 bytes and the rest of the archive is still extracted
- `Resolver` gains `TruncatedReason`, so the truncation unknown says what happened ("an entry larger than 2.0 GiB was skipped", or the existing disk-limit text) instead of always blaming the limits

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-24 13:21:08 -04:00
Alex Goodman 73397f1e8b fix: only extract zip and tar family archives
`identifyFormat` accepted any extractor mholt identified, which put the 7z and rar decoders (`bodgit/sevenzip`, `rardecode`) on untrusted input with no test coverage. Only zip, tar and compressed tar are opened now, until other formats are deliberately supported.

This also fixes java resource adapters: a `.rar` that is really a zip used to be identified by name as RAR and fail to extract. An unsupported identification now falls through to the zip end-of-central-directory check, so it's read as the zip it is.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-24 11:29:34 -04:00
Alex Goodman c9b4f9c085 fix: recover from panics while extracting an archive
sub-pipeline catalogers were already run under `runTaskSafely`, but extraction wasn't: the zip, tar and compressor decoders run on untrusted bytes, and a panic in one unwound the whole walk and failed `CreateSBOM` with "failed to run tasks".

- `processArchive` recovers and returns the panic (with its stack) as an error at the archive's coordinates, so it becomes an unknown and the sibling archives are still cataloged
- `Extract` now defers `Cleanup` on failure instead of calling it on each error path, so a panic no longer leaks the limiter charge or the spill file

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-24 11:28:26 -04:00
Keith Zantow b36234cfe7 feat: simplify+improve resolver implementation / glob search
Signed-off-by: Keith Zantow <keith.zantow@anchore.com>
2026-09-22 11:32:30 -04:00
Keith Zantow 47cafae01b chore: simplify and cleanup
Signed-off-by: Keith Zantow <keith.zantow@anchore.com>
2026-09-22 00:23:36 -04:00