Merge remote-tracking branch 'upstream/main' into add-nested-archive-cataloging

This commit is contained in:
Keith Zantow
2026-10-12 09:25:49 -04:00
10 changed files with 254 additions and 61 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ runs:
using: "composite"
steps:
- name: Setup go + go-make tooling
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
uses: anchore/go-make/.github/actions/setup@27a9f35f41b5fe86d68815844efd2ba816e9ba6b # v0.9.1
with:
go-version: ${{ inputs.go-version }}
cache-key-prefix: ${{ inputs.cache-key-prefix }}
+1 -1
View File
@@ -118,7 +118,7 @@ jobs:
# for updating brew formula in anchore/homebrew-syft
GITHUB_BREW_TOKEN: ${{ secrets.ANCHOREOPS_GITHUB_OSS_WRITE_TOKEN }}
- uses: anchore/sbom-action@3ad7283483fc7af8ff2b4ea19663c2d5ca935e26 #v0.24.2
- uses: anchore/sbom-action@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c #v0.24.3
continue-on-error: true
with:
file: go.mod
+3 -3
View File
@@ -99,7 +99,7 @@ jobs:
# go-make's setup only (not ./.github/actions/bootstrap): binny ships no windows release, so binny tools
# are unavailable here (`make unit` skips the fixture refresh on windows)
- name: Setup go + go-make tooling
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
uses: anchore/go-make/.github/actions/setup@27a9f35f41b5fe86d68815844efd2ba816e9ba6b # v0.9.1
with:
# keep in sync with the go-version default in ./.github/actions/bootstrap
go-version: "1.26.2"
@@ -195,7 +195,7 @@ jobs:
# go-make's setup only (not ./.github/actions/bootstrap): the test only needs make + the snapshot binary
- name: Setup go + go-make tooling
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
uses: anchore/go-make/.github/actions/setup@27a9f35f41b5fe86d68815844efd2ba816e9ba6b # v0.9.1
with:
# keep in sync with the go-version default in ./.github/actions/bootstrap
go-version: "1.26.2"
@@ -228,7 +228,7 @@ jobs:
# go-make's setup only (not ./.github/actions/bootstrap): the test only needs make + the snapshot binary
- name: Setup go + go-make tooling
uses: anchore/go-make/.github/actions/setup@fa5421b3bf24c9d18f11a736c5eb4172f2b7d683 # v0.8.1
uses: anchore/go-make/.github/actions/setup@27a9f35f41b5fe86d68815844efd2ba816e9ba6b # v0.9.1
with:
# keep in sync with the go-version default in ./.github/actions/bootstrap
go-version: "1.26.2"
+4 -4
View File
@@ -1,12 +1,12 @@
module github.com/anchore/syft/.make
go 1.25.8
go 1.26.0
require github.com/anchore/go-make v0.8.1
require github.com/anchore/go-make v0.9.1
require (
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
golang.org/x/mod v0.40.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/mod v0.41.0 // indirect
golang.org/x/sys v0.48.0 // indirect
)
+6 -6
View File
@@ -1,10 +1,10 @@
github.com/anchore/go-make v0.8.1 h1:GAhPXR+9qVQhFB1pfVlHFOzxAt3dTDTUIA7Tp7nx9tg=
github.com/anchore/go-make v0.8.1/go.mod h1:xALbVsPsDoJnJxdkjPlxOLZV7GbAdT5Pv3gd0kP1Lz0=
github.com/anchore/go-make v0.9.1 h1:OnC830XJEhdHUPhR8jLpnjmLTco9UThNZfHpLefqd94=
github.com/anchore/go-make v0.9.1/go.mod h1:1KhqPt0STAHLVEngwrXxYsuFftD62Nx497rNujwo8IY=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
+6 -6
View File
@@ -41,7 +41,7 @@ require (
github.com/facebookincubator/nvdtools v0.1.5
github.com/github/go-spdx/v2 v2.7.0
github.com/gkampitakis/go-snaps v0.5.23
github.com/go-git/go-billy/v5 v5.9.1
github.com/go-git/go-billy/v5 v5.9.2
github.com/go-git/go-git/v5 v5.19.2
github.com/go-test/deep v1.1.1
github.com/go-viper/mapstructure/v2 v2.5.0
@@ -52,7 +52,7 @@ require (
github.com/google/licensecheck v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/color v1.6.1
github.com/gpustack/gguf-parser-go v0.26.3
github.com/gpustack/gguf-parser-go v0.26.4
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-getter v1.8.9
github.com/hashicorp/go-multierror v1.1.1
@@ -97,11 +97,11 @@ require (
golang.org/x/net v0.59.0
golang.org/x/time v0.16.0
golang.org/x/tools v0.50.0
modernc.org/sqlite v1.59.0
modernc.org/sqlite v1.60.1
)
require (
github.com/pb33f/ordered-map/v2 v2.3.1
github.com/pb33f/ordered-map/v2 v2.3.2
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd
howett.net/plist v1.0.1
)
@@ -265,6 +265,7 @@ require (
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/opencontainers/selinux v1.15.1 // indirect
github.com/pb33f/go-yaml v0.1.0 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/pierrec/lz4/v4 v4.1.26 // indirect
@@ -328,7 +329,6 @@ require (
go.opentelemetry.io/otel/trace v1.46.0 // indirect
go.podman.io/image/v5 v5.41.2 // indirect
go.podman.io/storage v1.64.1 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
golang.org/x/crypto v0.57.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
@@ -345,7 +345,7 @@ require (
google.golang.org/grpc v1.83.2 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
modernc.org/libc v1.75.7 // indirect
modernc.org/libc v1.77.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.12.1 // indirect
)
+16 -16
View File
@@ -434,8 +434,8 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.9.1 h1:8U73XiOTfINdItHVa6z4Gv7ToObcZ6grkqQbLryLCdA=
github.com/go-git/go-billy/v5 v5.9.1/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
github.com/go-git/go-billy/v5 v5.9.2 h1:OXFSRyz4g20upsGDJgQG9Bak1l/ZEv8GHVYB52O71sE=
github.com/go-git/go-billy/v5 v5.9.2/go.mod h1:ExsU+jcGwXTBOnyilvAnEM1wug1IxHr4yP2ZXsNRtV0=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.19.2 h1:wkfn7vOlUBu8ivAWKBWisTiwJK4jYHzTF8Ndv1LyGqY=
@@ -579,8 +579,8 @@ github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gpustack/gguf-parser-go v0.26.3 h1:F63PlUPIW56HGU5k4Crv0JAG9ojtz/TuwzgMYlb82Ec=
github.com/gpustack/gguf-parser-go v0.26.3/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
github.com/gpustack/gguf-parser-go v0.26.4 h1:B9CRrQBcIB7I/MJFZvzPZ+e+cbc1xBzQJcmpi9l/mKU=
github.com/gpustack/gguf-parser-go v0.26.4/go.mod h1:y4TwTtDqFWTK+xvprOjRUh+dowgU2TKCX37vRKvGiZ0=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74 h1:mymLUKThnV9wFvogOK8NnsMP9/vlhnjXY98gr2QIGW8=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.74/go.mod h1:Bh9qYL8ehmDxSg14Tk8oxFCP90XHfs6NxV1D84884xA=
@@ -808,8 +808,10 @@ github.com/opencontainers/selinux v1.15.1/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZ
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pb33f/ordered-map/v2 v2.3.1 h1:5319HDO0aw4DA4gzi+zv4FXU9UlSs3xGZ40wcP1nBjY=
github.com/pb33f/ordered-map/v2 v2.3.1/go.mod h1:qxFQgd0PkVUtOMCkTapqotNgzRhMPL7VvaHKbd1HnmQ=
github.com/pb33f/go-yaml v0.1.0 h1:zZ6Gg+ATbumXlXZNLMHIe7+zTkVqe20lx+TSYRrXEuY=
github.com/pb33f/go-yaml v0.1.0/go.mod h1:QvWdkHP5VjFYAeHMQUkaPQqREaQnBNOXmWzAjNrnSdo=
github.com/pb33f/ordered-map/v2 v2.3.2 h1:wDyaZ2Pv9QLh64X4utCeD5Zoi9nIv2aW3lwv172O5PQ=
github.com/pb33f/ordered-map/v2 v2.3.2/go.mod h1:1OhFrXu3OYw3kM+FXF+Ug3ugmmZ9LE8z0JfQMLvtV0w=
github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM=
github.com/pborman/indent v1.2.1/go.mod h1:FitS+t35kIYtB5xWTZAPhnmrxcciEEOdbyrrpz5K6Vw=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
@@ -1084,8 +1086,6 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
go.yaml.in/yaml/v4 v4.0.0-rc.2 h1:/FrI8D64VSr4HtGIlUtlFMGsm7H7pWTbj6vOLVZcA6s=
go.yaml.in/yaml/v4 v4.0.0-rc.2/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1610,10 +1610,10 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
modernc.org/cc/v4 v4.29.7 h1:q+NXGJ0bK3b4TXFYQQVr9pYETGnmwFWkrUzJnMya/Tg=
modernc.org/cc/v4 v4.29.7/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
modernc.org/ccgo/v4 v4.36.1 h1:ZNIUZAryN0UgnJwtyxrdEzcFc3yD4Cu4AzjfPXsLsIE=
modernc.org/ccgo/v4 v4.36.1/go.mod h1:rrtGc2QkS239nYb/mQNuBMyjq3/y3ZXWbBjPoV3wqzA=
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
@@ -1622,8 +1622,8 @@ modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.75.7 h1:o3DTP9/0p9pKmY2WCKQaySW6wIiZhNM7wc2lUoyhfew=
modernc.org/libc v1.75.7/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
modernc.org/libc v1.77.1 h1:Ct8j47QtiZ1Enj2DtFXQtUqrPCAjdCmPjtCuvrYQ0Hs=
modernc.org/libc v1.77.1/go.mod h1:87/pZ4L6nD1zqW4nItuS12YO7hN1igAah34xjnQo/W0=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
@@ -1632,8 +1632,8 @@ modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A=
modernc.org/sqlite v1.59.0/go.mod h1:+paeT2A3iPRHkQDwG7oA6Tk0zQd5woMEI8q7orfry8k=
modernc.org/sqlite v1.60.1 h1:/blz53O951KWFOso4QQvEs/Fq6cDBKLtMVrYNSeJVKw=
modernc.org/sqlite v1.60.1/go.mod h1:1dIoEagfDE72QytD5scH1lxARtaUgKgHC/NuApA27r0=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
@@ -2439,12 +2439,18 @@
"@backstage/plugin-catalog-backend": [
"cpe:2.3:a:linuxfoundation:backstage\\/plugin-catalog-backend-module-unprocessed:*:*:*:*:*:node.js:*:*"
],
"@backstage/plugin-catalog-unprocessed-entities": [
"cpe:2.3:a:linuxfoundation:backstage\\/plugin-catalog-unprocessed-entities:*:*:*:*:*:node.js:*:*"
],
"@backstage/plugin-catalog-unprocessed-entities-common": [
"cpe:2.3:a:linuxfoundation:backstage\\/plugin-catalog-unprocessed-entities-common:*:*:*:*:*:node.js:*:*"
],
"@backstage/plugin-scaffolder-backend": [
"cpe:2.3:a:linuxfoundation:backstage\\/plugin-scaffolder-backend:*:*:*:*:*:*:*:*"
],
"@backstage/plugin-scaffolder-backend-module-sentry": [
"cpe:2.3:a:linuxfoundation:backstage\\/plugin-scaffolder-backend-module-sentry:*:*:*:*:*:node.js:*:*"
],
"@backstage/plugin-techdocs": [
"cpe:2.3:a:linuxfoundation:\\@backstage\\/plugin-techdocs:*:*:*:*:*:node.js:*:*"
],
@@ -12,18 +12,25 @@ import (
)
const (
// maxBundleSearchSize bounds the bytes findSignatureOffset will hold at once while looking for the
// bundle marker.
// maxBundleSearchSize bounds how far into a file findSignatureOffset looks for the bundle marker.
//
// The marker sits inside the executable structure, so the search legitimately covers a whole
// single-file bundle, which routinely runs past 100MB and can reach a few hundred for an app that
// embeds sizable assets. Clamping to the file length alone is not enough: a mostly empty file costs
// almost nothing inside a compressed layer, so a small artifact can still authorize a multi-gigabyte
// allocation. The trade-off is that a bundle larger than this loses its deps.json rather than being
// cataloged, which is the correct direction to fail when the alternative is OOM-killing the scan.
// This mirrors maxDeclaredSectionSize in syft/internal/elfutil.
// almost nothing inside a compressed layer, so a small artifact can still authorize an unbounded
// amount of reading. The trade-off is that a bundle larger than this loses its deps.json rather than
// being cataloged, which is the correct direction to fail. This mirrors maxDeclaredSectionSize in
// syft/internal/elfutil. What the search holds in memory is bounded separately, by
// markerSearchWindowSize, so raising this costs time rather than resident bytes.
maxBundleSearchSize = 512 * intFile.MB
// markerSearchWindowSize is how much of a file findSignatureOffset holds at once.
markerSearchWindowSize = 256 * intFile.KB
// bundleHeaderOffsetSize is the width of the little-endian header offset stored immediately before
// the marker.
bundleHeaderOffsetSize = 8
// maxDepsJSONSize bounds an embedded deps.json. These are dependency manifests, so real ones are
// measured in KB even for large applications.
maxDepsJSONSize = 3 * intFile.MB
@@ -37,13 +44,26 @@ const (
)
// dotNetBundleSignature is the SHA-256 hash of ".net core bundle" used to identify single-file bundles.
var dotNetBundleSignature = []byte{
var dotNetBundleSignature = [32]byte{
0x8b, 0x12, 0x02, 0xb9, 0x6a, 0x61, 0x20, 0x38,
0x72, 0x7b, 0x93, 0x02, 0x14, 0xd7, 0xa0, 0x32,
0x13, 0xf5, 0xb9, 0xe6, 0xef, 0xae, 0x33, 0x18,
0xee, 0x3b, 0x2d, 0xce, 0x24, 0xb3, 0x6a, 0xae,
}
// markerSearchOverlap is how much of each search window carries over into the next: the bytes a marker
// could straddle the boundary with, plus the offset that precedes it. Retaining both is what lets a
// windowed search give the same answer as reading the file whole - a marker split across two reads is
// still matched, and the offset in front of it is always in the same window as the match.
const markerSearchOverlap = bundleHeaderOffsetSize + len(dotNetBundleSignature) - 1
// the slide at the end of findSignatureOffset carries markerSearchOverlap bytes into the next window and
// advances by the rest, so a window no wider than the overlap would take a negative slice index and never
// move. The window is five orders of magnitude clear of that today; this is what refuses to compile if
// anyone shrinks it to the marker's own scale, which is the tempting thing to do to make a test cheaper.
// The -1 is what makes equality fail too: the window has to exceed the overlap, not merely match it.
const _ = uint(markerSearchWindowSize - markerSearchOverlap - 1)
// ExtractDepsJSON returns the deps.json embedded in the .NET single-file bundle in r, or "" if r carries no
// bundle marker.
//
@@ -94,29 +114,17 @@ func findBundleHeaderOffset(r unionreader.UnionReader, searchLimit int64) (int64
clamped = true
}
// this scans a whole executable, routinely over 100MB for a single-file bundle, so the buffer is sized
// exactly once. An append-growing read holds both arrays at its final growth and would cost well over
// twice the file's own size for the same result.
searchData := make([]byte, limit)
// a short read is not fatal here: the marker may well be in what we did get, so search the bytes we
// actually hold. ReadAt reports a short read as io.EOF, and may report a full one that way too, so the
// count is what says how much there is to search.
n, err := r.ReadAt(searchData, 0)
if err != nil && !errors.Is(err, io.EOF) {
headerOffset, found, err := findSignatureOffset(r, limit)
if err != nil {
return 0, err
}
idx := bytes.Index(searchData[:n], dotNetBundleSignature)
if idx == -1 || idx < 8 {
if !found {
if clamped {
return 0, fmt.Errorf("no bundle marker in the first %d bytes and the rest of the %d byte file was not searched", maxBundleSearchSize, size)
}
return 0, nil
}
headerOffset := int64(binary.LittleEndian.Uint64(searchData[idx-8 : idx]))
if headerOffset == 0 {
// the marker is compiled into every apphost; only publishing as a single file fills in the offset
return 0, nil
@@ -131,6 +139,52 @@ func findBundleHeaderOffset(r unionreader.UnionReader, searchLimit int64) (int64
return headerOffset, nil
}
// findSignatureOffset searches the first limit bytes of r for the bundle marker and returns the header
// offset stored in the 8 bytes immediately before it, or false if the marker is not there.
func findSignatureOffset(r unionreader.UnionReader, limit int64) (int64, bool, error) {
// nothing shorter than the marker plus the offset in front of it can hold a usable match
if limit < int64(bundleHeaderOffsetSize+len(dotNetBundleSignature)) {
return 0, false, nil
}
buf := make([]byte, min(int64(markerSearchWindowSize), limit))
var (
windowStart int64 // offset in the file that buf[0] holds
filled int // bytes of buf that hold file content
)
for {
// a short read is not fatal: the marker may well be in what we did get. ReadAt reports a short
// read as io.EOF, and may report a full one that way too, so the count is what says how much
// there is to search.
want := min(int64(len(buf)-filled), limit-windowStart-int64(filled))
n, err := r.ReadAt(buf[filled:int64(filled)+want], windowStart+int64(filled))
filled += n
if err != nil && !errors.Is(err, io.EOF) {
return 0, false, err
}
if idx := bytes.Index(buf[:filled], dotNetBundleSignature[:]); idx >= 0 {
// only reachable in the first window: after a slide the overlap guarantees that a match not
// already seen begins at least bundleHeaderOffsetSize into the buffer
if idx < bundleHeaderOffsetSize {
return 0, false, nil
}
return int64(binary.LittleEndian.Uint64(buf[idx-bundleHeaderOffsetSize : idx])), true, nil
}
// a buffer the read could not fill means the reader ran out before the limit did
if filled < len(buf) || windowStart+int64(filled) >= limit {
return 0, false, nil
}
copy(buf, buf[filled-markerSearchOverlap:filled])
windowStart += int64(filled - markerSearchOverlap)
filled = markerSearchOverlap
}
}
// dotNetBundleHeader represents the fixed portion of the bundle header (version 1+)
type dotNetBundleHeader struct {
MajorVersion uint32
@@ -16,7 +16,7 @@ import (
func fileWithSignatureAt(size, sigStart int, headerOffset uint64) []byte {
data := make([]byte, size)
binary.LittleEndian.PutUint64(data[sigStart-8:sigStart], headerOffset)
copy(data[sigStart:], dotNetBundleSignature)
copy(data[sigStart:], dotNetBundleSignature[:])
return data
}
@@ -74,7 +74,7 @@ func TestFindBundleHeaderOffset(t *testing.T) {
{
// there is no room for the 8-byte header offset before the signature
name: "signature too close to the start to carry an offset",
data: append(append([]byte{0, 0}, dotNetBundleSignature...), make([]byte, 32)...),
data: append(append([]byte{0, 0}, dotNetBundleSignature[:]...), make([]byte, 32)...),
searchLimit: 128,
},
{
@@ -295,3 +295,136 @@ func TestFindDepsJSONInManifest_PlausibleEntryCountIsWalked(t *testing.T) {
require.NoError(t, err)
assert.Empty(t, got, "no deps.json entry in this manifest")
}
// A windowed search only gives the same answer as reading the file whole if a marker lying across a
// window boundary - or one whose header offset lies across it - is still matched. These are the offsets
// where an overlap that is absent, too small, or applied to the wrong end of the buffer goes wrong.
func TestFindBundleHeaderOffset_MarkerAcrossWindowBoundary(t *testing.T) {
const window = markerSearchWindowSize
const headerOffset = 0x1234
size := window + 4096
for _, tt := range []struct {
name string
sigStart int
}{
{"whole marker inside the first window", window - 4096},
{"header offset split across the boundary", window - 4},
{"header offset ends exactly at the boundary", window},
{"signature split one byte into the second window", window - len(dotNetBundleSignature) + 1},
{"signature split down the middle", window - len(dotNetBundleSignature)/2},
{"signature starts one byte before the boundary", window - 1},
{"whole marker inside the second window", window + 2048},
} {
t.Run(tt.name, func(t *testing.T) {
data := fileWithSignatureAt(size, tt.sigStart, headerOffset)
got, err := findBundleHeaderOffset(readSeekCloser{bytes.NewReader(data)}, int64(size))
require.NoError(t, err)
assert.Equal(t, int64(headerOffset), got)
})
}
}
// recordingReader records the largest single read it is asked for. The search buffer is what bounds
// that length, so this is what fails if the implementation ever sizes a buffer against the file again.
type recordingReader struct {
*bytes.Reader
largestRead int
}
func (r *recordingReader) ReadAt(p []byte, off int64) (int, error) {
if len(p) > r.largestRead {
r.largestRead = len(p)
}
return r.Reader.ReadAt(p, off)
}
func (*recordingReader) Close() error { return nil }
// Holding an executable at its full size to look for 32 bytes is what made this the largest single
// consumer of memory in a scan of a Windows volume, so the bound matters more than the result here.
func TestFindBundleHeaderOffset_ReadsAreBoundedByTheWindow(t *testing.T) {
size := 5 * markerSearchWindowSize
data := fileWithSignatureAt(size, size-1024, 0x1234)
r := &recordingReader{Reader: bytes.NewReader(data)}
got, err := findBundleHeaderOffset(r, int64(size))
require.NoError(t, err)
require.Equal(t, int64(0x1234), got, "the marker must still be found at the far end of the file")
assert.LessOrEqual(t, r.largestRead, markerSearchWindowSize,
"no single read may exceed the search window, whatever the file's size")
}
// Every window after the first begins with bytes already searched. Re-reporting a match from that
// overlap would be harmless, but failing to advance past it would spin forever.
func TestFindBundleHeaderOffset_UnmarkedFileTerminates(t *testing.T) {
for _, size := range []int{
markerSearchWindowSize - 1,
markerSearchWindowSize,
markerSearchWindowSize + 1,
markerSearchWindowSize + markerSearchOverlap,
3 * markerSearchWindowSize,
} {
got, err := findBundleHeaderOffset(readSeekCloser{bytes.NewReader(make([]byte, size))}, int64(size))
require.NoError(t, err)
assert.Zero(t, got)
}
}
// lateShortReader reads cleanly until shortFrom and only then hands back fewer bytes than were asked for.
// shortReader above comes up short on its very first read, which the search answers before it has slid at
// all; the reader running out partway through a windowed search is a different branch.
type lateShortReader struct {
*bytes.Reader
shortFrom int64 // a read starting here or beyond stops after shortLen bytes
shortLen int
}
func (r *lateShortReader) ReadAt(p []byte, off int64) (int, error) {
if off < r.shortFrom || len(p) <= r.shortLen {
return r.Reader.ReadAt(p, off)
}
n, _ := r.Reader.ReadAt(p[:r.shortLen], off)
return n, io.EOF
}
func (*lateShortReader) Close() error { return nil }
// A squashfs block that decompresses short makes unionreader return fewer bytes than were asked for, and
// nothing says that lands in the first window. Whatever the search did get still has to be searched, and the
// bytes the reader never produced have to end it rather than being read as a file with no bundle in it.
func TestFindBundleHeaderOffset_ShortReadAfterASlideSearchesWhatWasRead(t *testing.T) {
const window = markerSearchWindowSize
const shortLen = 4096 // what the second window gets before the reader gives up
const headerOffset = 0x1234
size := 2*window + 8192
for _, tt := range []struct {
name string
sigStart int
want int64
}{
{"marker inside the bytes the second window did get", window + 1024, headerOffset},
{"marker straddling the boundary into those bytes", window - 4, headerOffset},
{"marker beyond where the reader gave up", window + shortLen + 1024, 0},
} {
t.Run(tt.name, func(t *testing.T) {
// honest about its length: only the read past the first window comes up short
r := &lateShortReader{
Reader: bytes.NewReader(fileWithSignatureAt(size, tt.sigStart, headerOffset)),
shortFrom: window,
shortLen: shortLen,
}
got, err := findBundleHeaderOffset(r, int64(size))
require.NoError(t, err, "a reader that runs out is not a parse failure")
assert.Equal(t, tt.want, got)
})
}
}