check / check (push) Canceled after 0s
mfer ran the gpg binary to sign, export keys and verify, so it failed wherever gpg is missing. It now uses github.com/ProtonMail/go-crypto/openpgp. --sign-key and MFER_SIGN_KEY name a file holding one version 4 OpenPGP secret key; a protected key's passphrase comes from MFER_SIGN_KEY_PASSPHRASE or a terminal prompt. gen and freshen check that the key can sign before they read any file. Verification keeps the rules of the --require-signature fix: one primary key in the embedded block, counted from its packets, exactly one signature, made by that key or a subkey, and signer equal to its fingerprint. The embedded block may hold no DSA key and no secret key, and an armored field must be one well-formed block. Model: opus-5-5
83 lines
3.2 KiB
Go
83 lines
3.2 KiB
Go
//nolint:testpackage // white-box tests exercise unexported internals
|
|
package mfer
|
|
|
|
import (
|
|
"bytes"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// FuzzNewManifestFromReader feeds arbitrary bytes to the manifest parser.
|
|
// `make test` runs it on the seed corpus in
|
|
// testdata/fuzz/FuzzNewManifestFromReader; `make fuzz` searches for new
|
|
// inputs.
|
|
//
|
|
// For every input the parser must return a manifest or an error, not both
|
|
// and not neither, and must not allocate more than a fixed multiple of its
|
|
// input and of the decompressed data it may read, plus room for the
|
|
// decoder's window buffers. A panic or a hang fails the test on its own.
|
|
func FuzzNewManifestFromReader(f *testing.F) {
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
var before, after runtime.MemStats
|
|
|
|
runtime.ReadMemStats(&before)
|
|
|
|
m, err := NewManifestFromReader(bytes.NewReader(data))
|
|
|
|
runtime.ReadMemStats(&after)
|
|
|
|
if (m == nil) == (err == nil) {
|
|
t.Fatalf("got manifest %p and error %v, want exactly one", m, err)
|
|
}
|
|
|
|
// The parser reads at most the declared size plus one byte of
|
|
// decompressed data, and never more than MaxDecompressedSize.
|
|
decompressed := uint64(MaxDecompressedSize)
|
|
|
|
outer := new(MFFileOuter)
|
|
if validateMagic(data) &&
|
|
proto.Unmarshal(data[len(MAGIC):], outer) == nil {
|
|
size := outer.GetSize()
|
|
if size > 0 && size < MaxDecompressedSize {
|
|
decompressed = uint64(size) + 1
|
|
}
|
|
}
|
|
|
|
// It also keeps a few copies of its input. Buffers grow by
|
|
// copying, so reaching those sizes allocates up to about six times
|
|
// them in total. Decoding the decompressed data takes up to
|
|
// maxDecodedGrowth times its size for file entries, hashes,
|
|
// timestamps and MIME types, and drops fields it does not know.
|
|
// The strings and bytes it copies out of it, such as many one-byte
|
|
// values in one hash, take up to about five times more under the
|
|
// race detector, which pads every small copy to 16 bytes, and about
|
|
// half that without it. Twenty times the input and the
|
|
// decompressed data leaves room for all of that.
|
|
//
|
|
// The decoder also sets aside a new buffer of one to two times the
|
|
// window for each frame that asks for a larger window than the
|
|
// frames before it, and refuses windows above zstdWindowSize. A
|
|
// frame that gives its content size instead of a window has that
|
|
// size as its window, refused above zstdWindowSize like any other.
|
|
// Frames asking for every window size up to that make it set aside
|
|
// about 16 times zstdWindowSize in total; 24 times leaves room.
|
|
//
|
|
// The seed whose frame claims 8 GiB fails if the decoder sets that
|
|
// size aside; the seed whose frames ask for ever larger windows
|
|
// fails if the decoder accepts windows of twice zstdWindowSize; the
|
|
// seed whose two frames together exceed MaxDecompressedSize fails
|
|
// if the decoder decodes them in full instead of stopping at the
|
|
// declared size; the seeds of empty file entries and of a file
|
|
// entry of empty hashes fail if the parser decodes them.
|
|
limit := 20*(uint64(len(data))+decompressed) + 24*zstdWindowSize
|
|
|
|
allocated := after.TotalAlloc - before.TotalAlloc
|
|
if allocated > limit {
|
|
t.Fatalf("allocated %d bytes for %d bytes of input, limit %d",
|
|
allocated, len(data), limit)
|
|
}
|
|
})
|
|
}
|