Alex Goodman c55e1cf2fd Fix: graalvm unbounded allocation (#5291)
* fix(java): bound user-controlled reads in the graalvm native-image cataloger

Sizes, offsets and lengths in a native-image binary all come from the file
being parsed, and several were acted on directly.

The PE export directory sized a `make([]byte, Size)` from a uint32 in the
optional header, so an 8KB file claiming 4GB reserved 4GB before reading a
byte. It now reads what the file actually holds, bounded by the declared size,
and still requires the whole directory to be present. This also fixes a latent
bug: `unionreader.readerAtAdapter.ReadAt` can return a short read with a nil
error, and the old code discarded the count, so squashfs-sourced binaries could
parse zero padding as export data.

The bounds checks in `decompressSbom` and the PE export walk validated only the
end of a range, computed by adding to a value out of the file. Those sums wrap,
and a wrapped sum compares as in range while the slice that follows it panics.
They now subtract from the known-good length instead. The three
`address - sectionBase` subtractions are unsigned and underflowed on an address
below the base; they share one guarded helper now.

The embedded SBOM decompresses through an `io.LimitedReader`, since the
compressed bytes are bounded by the file but what they expand to is not. The
limit is checked before the decoder's own error, which would otherwise report a
size problem as "not a cyclonedx json document". Hitting it is logged, since
the caller reports parse failures at trace level and a dropped SBOM would
otherwise go unnoticed.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(java): stop at the first format that parses a native image

The format loop tried ELF, Mach-O and PE against every reader and kept going
after one of them parsed, so a file that parses as more than one format
contributes its packages once per format.

No input parses as two formats today, so this is not a behavior change in
practice: `debug/pe` rejects an ELF on the optional-header magic, and both
`debug/elf` and `debug/macho` reject anything without their own magic. It
removes the possibility rather than a live bug.

Note this does not deduplicate across readers, which is the outer loop: a
universal Mach-O carrying the same native image for two architectures still
yields its packages twice.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(java): bound the graalvm export directory read against the file

Follow-up to the hardening earlier in this branch, from review.

The export directory read was still bounded only by file size, so a large
mostly-empty file (cheap to ship inside a compressed layer) was read into
memory in full and then discarded. It now weighs the declared size against an
absolute cap and against the bytes really present before allocating anything,
matching `readDataFromRVA` in `syft/pkg/cataloger/internal/dotnet/pe`. A 64MB
file declaring a 4GB export directory went from ~140MB allocated to ~3.6KB.

Also from the same review:

- dropped the decompressed-SBOM ceiling from 100MB to 16MB. Decoding costs
  roughly 48x the JSON size in allocation and 6x retained, so the old ceiling
  was worth several GB of churn per file, and catalogers run concurrently.

- `symbolOffsets` is now single-address `symbolOffset`. Two same-typed address
  arguments in a row could be transposed at a call site and would produce a
  silently wrong SBOM rather than an error.

- `newMachO` now returns an error on a real read failure. It was falling
  through and dropping the file with no error and no log at all.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(java): reject a short read of the graalvm export directory

Second round of review on this branch.

The export directory read introduced earlier in the branch only consulted the
byte count when `ReadAt` also returned an error, so a reader handing back fewer
bytes with a nil error was accepted and the tail of the buffer stayed zeroed,
then got parsed as a valid export directory. The count decides now, whatever
the error says. `io.ReadFull` would also cover it but spins forever on a reader
that keeps answering `(0, nil)`, which is the same hostile input this is meant
to reject, so the single read stays.

Also in here:

- the decompressed SBOM is buffered and size-checked before the decoder is
  invoked, instead of inferring the overrun from `io.LimitedReader.N` after the
  fact. That check was only correct because the cyclonedx decoder happens to
  drain its reader before parsing, and nothing would have caught it if that
  changed.

- four bounds guards, the `newMachO` error path and the SBOM ceiling had no
  test that failed when they were removed. They do now, verified by deleting
  each one and watching a named test go red. The ceiling in particular could be
  raised to 2GB with the suite still green, since every case derived its sizes
  from the constant.

- `nativeImagePE.reader` is dead now that the read takes the reader as an
  argument.

- three comments claimed guarantees the code does not make: that `symbolOffset`
  cannot be called with transposed arguments, that a rejected file cannot go
  unrecorded when it is only logged at trace level, and a set of measured
  allocation multipliers that nothing pins.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* chore(java): trim the graalvm cataloger comments

Comment pass over everything this branch added. Cut about a quarter of the
comment lines, mostly restatement and detail that was never going to stay true.

Dropped: a production comment naming a test, the measured allocation multiples
behind the two size ceilings, cross-references to the equivalent constants in
`dotnet/pe` and `elfutil`, and test docs that restated their own test name
before saying anything.

Kept the parts that fail silently if someone tidies them away: why the read is
a single `ReadAt` rather than `io.ReadFull`, the note that `readExportDirectory`
uses an RVA directly as a file offset, and the fixture notes explaining why a
`VirtualAddress` is deliberately huge or why no symbol sits at index 0.

Three that were stale rather than verbose:

- `nativeImagePE`'s doc still said it holds the underlying reader, which this
  branch deleted.

- `TestFetchSbomSymbols_FindsAllThreeSymbols`' doc had ended up above
  `TestFetchSbomSymbols_AddressBaseOutOfRangeGuard`, documenting the wrong
  function.

- a comment referenced a commit sha that no longer exists on this branch.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-14 13:49:19 -04:00
2025-12-17 18:00:38 +00:00
2020-06-24 14:37:00 -04:00
2026-05-18 11:59:55 -04:00
2024-10-11 05:17:01 -04:00

Cute pink owl syft logo

Syft

A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems. Exceptional for vulnerability detection when used with a scanner like Grype.

 Validations   GitHub release   GitHub go.mod Go version   License: Apache-2.0   Join our Discourse   Follow on Mastodon 

syft-demo

Features

Tip

New to Syft? Check out the Getting Started guide for a walkthrough!

Installation

The quickest way to get up and going:

curl -sSfL https://get.anchore.io/syft | sudo sh -s -- -b /usr/local/bin

Tip

See Installation docs for more ways to get Syft, including Homebrew, Docker, Scoop, Chocolatey, Nix, and more!

The basics

See the packages within a container image or directory:

# container image
syft alpine:latest

# directory
syft ./my-project

To get an SBOM, specify one or more output formats:

# SBOM to stdout
syft <image> -o cyclonedx-json

# Multiple SBOMs to files
syft <image> -o spdx-json=./spdx.json -o cyclonedx-json=./cdx.json

Tip

Check out the Getting Started guide to explore all of the capabilities and features.

Want to know all of the ins-and-outs of Syft? Check out the CLI docs, configuration docs, and JSON schema.

Contributing

We encourage users to help make these tools better by submitting issues when you find a bug or want a new feature. Check out our contributing overview and developer-specific documentation if you are interested in providing code contributions.

Syft development is sponsored by Anchore, and is released under the Apache-2.0 License. The Syft logo by Anchore is licensed under CC BY 4.0

For commercial support options with Syft or Grype, please contact Anchore.

Come talk to us!

The Syft Team holds regular community meetings online. All are welcome to join to bring topics for discussion.

S
Description
A CLI tool and Go library for generating a Software Bill of Materials (SBOM) from container images and filesystems.
Readme Apache-2.0
190 MiB
Languages
Go 98.5%
Shell 1%
Python 0.4%