mirror of
https://github.com/anchore/syft.git
synced 2026-08-19 16:48:27 +02:00
chore(lint): catch mholt/archives decompressors too
The decompression rule only matched the stdlib constructors, so the two cataloger sites that decompress through mholt/archives went unreported while being exactly as unbounded. Matches OpenReader on an archives.Decompressor, which picks up both. Also notes in the report text that a byte limit bounds the input and not what the consumer retains. Measured on the mtree parser: 16MB of newlines, well inside a 64MB byte cap, cost 8GB of peak heap, because the parser keeps an object per line. A site can satisfy this rule and still OOM. Worth knowing for the next rule: a type filter that fails to resolve takes the whole rule set down with "used Run() with an empty rule set" rather than just skipping that rule. Type.Is on an interface does that; Type.Implements with an m.Import is what works. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
cd57b0704e
commit
6f512803d2
@ -55,6 +55,8 @@ func noUnboundedAllocations(m dsl.Matcher) {
|
|||||||
|
|
||||||
// nolint:unused
|
// nolint:unused
|
||||||
func noUnboundedDecompression(m dsl.Matcher) {
|
func noUnboundedDecompression(m dsl.Matcher) {
|
||||||
|
m.Import("github.com/mholt/archives")
|
||||||
|
|
||||||
// compression ratios are unbounded (gzip does ~1032:1), so a decompressed stream must be capped
|
// compression ratios are unbounded (gzip does ~1032:1), so a decompressed stream must be capped
|
||||||
// independently of the compressed input, which is already bounded by the file it came from.
|
// independently of the compressed input, which is already bounded by the file it came from.
|
||||||
// The constructor is flagged rather than the consumer because the consumer is often a
|
// The constructor is flagged rather than the consumer because the consumer is often a
|
||||||
@ -68,7 +70,14 @@ func noUnboundedDecompression(m dsl.Matcher) {
|
|||||||
`xz.NewReader($_)`,
|
`xz.NewReader($_)`,
|
||||||
).
|
).
|
||||||
Where(m.File().PkgPath.Matches(`/cataloger/`)).
|
Where(m.File().PkgPath.Matches(`/cataloger/`)).
|
||||||
Report("unbounded decompression in a cataloger: wrap the decompressed stream in io.LimitReader before anything consumes it, or nolint with the bound that already applies")
|
Report("unbounded decompression in a cataloger: wrap the decompressed stream in io.LimitReader before anything consumes it, or nolint with the bound that already applies. Note that a byte limit bounds the input, not what the consumer retains: a parser that keeps an object per line still needs a count bound")
|
||||||
|
|
||||||
|
// the mholt/archives decompressors reach the same stdlib readers a layer down, so a site using them
|
||||||
|
// is exactly as unbounded while matching none of the constructors above
|
||||||
|
m.Match(`$d.OpenReader($_)`).
|
||||||
|
Where(m.File().PkgPath.Matches(`/cataloger/`) &&
|
||||||
|
m["d"].Type.Implements(`archives.Decompressor`)).
|
||||||
|
Report("unbounded decompression in a cataloger: $d.OpenReader returns a stream with no ceiling. bound it before anything consumes it, or nolint with the bound that already applies")
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:unused
|
// nolint:unused
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user