Limit how much fetch and check read for a manifest or a file (closes #168)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
NewManifestFromReader reads at most one byte past MaxManifestSize, a new constant of 258 MiB: the 256 MiB decompressed limit grown by zstd's worst case of 1/256, plus 1 MiB for the signature, the signing key and the other outer fields. It refuses a larger manifest. fetch, and check given a URL, stop downloading a manifest one byte past the same size and report it as too large; tests lower that size to keep their memory small. fetch stops reading a file one byte past its listed size, so a longer body ends in the size mismatch at once instead of filling the disk. docs/FORMAT.md states the limit and gives the decompressed limit as 256 MiB, the size the code uses. Model: opus-5-5
This commit was merged in pull request #172.
This commit is contained in:
+19
-2
@@ -24,6 +24,7 @@ var (
|
||||
errCompressedHashWrong = errors.New("compressed data hash mismatch")
|
||||
errSignatureNoPubKey = errors.New("signature present but no public key")
|
||||
errDecompressedTooLarge = errors.New("decompressed data exceeds maximum allowed size")
|
||||
errManifestTooLarge = errors.New("manifest exceeds maximum allowed size")
|
||||
errUUIDMismatch = errors.New("outer and inner UUID mismatch")
|
||||
errInvalidFileFormat = errors.New("invalid file format")
|
||||
errInvalidManifestPath = errors.New("manifest contains invalid path")
|
||||
@@ -318,13 +319,14 @@ func validateMagic(dat []byte) bool {
|
||||
return bytes.Equal(got, expected)
|
||||
}
|
||||
|
||||
// NewManifestFromReader reads a manifest from an io.Reader.
|
||||
// NewManifestFromReader reads a manifest from an io.Reader. It refuses a
|
||||
// manifest larger than MaxManifestSize, reading at most one byte past it.
|
||||
//
|
||||
//nolint:revive // unexported-return: exporting manifest is owner question 13
|
||||
func NewManifestFromReader(input io.Reader) (*manifest, error) {
|
||||
m := &manifest{}
|
||||
|
||||
dat, err := io.ReadAll(input)
|
||||
dat, err := readAtMost(input, MaxManifestSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -356,6 +358,21 @@ func NewManifestFromReader(input io.Reader) (*manifest, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// readAtMost reads all of input, or refuses it with errManifestTooLarge
|
||||
// once it passes maxSize bytes, after reading one byte past maxSize.
|
||||
func readAtMost(input io.Reader, maxSize int64) ([]byte, error) {
|
||||
dat, err := io.ReadAll(io.LimitReader(input, maxSize+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if int64(len(dat)) > maxSize {
|
||||
return nil, fmt.Errorf("%w of %d bytes", errManifestTooLarge, maxSize)
|
||||
}
|
||||
|
||||
return dat, nil
|
||||
}
|
||||
|
||||
// ManifestFromFileOptions configures NewManifestFromFile.
|
||||
type ManifestFromFileOptions struct {
|
||||
// Path is the manifest file to read (required).
|
||||
|
||||
Reference in New Issue
Block a user