Objects fetched from the store are untrusted; several decode paths let one expand or print without limit. - blobgen.LimitReader errors past a byte cap (not io.LimitReader silent EOF). DecodeManifest reads through caps on both compressed input and decompressed output, far above any real manifest, so json.Decode cannot buffer a compressible bomb. FetchAndDecryptBlob bounds decompression to the blob recorded uncompressed_size (not the restoring host blob_size_limit). - downloadSnapshotDB streams straight from storage to its temp file with io.Copy, replacing two ReadAll calls that held the whole database twice. - FetchBlob drops the per-blob Stat round-trip, its expectedSize parameter and returned size, all of which only fed a debug log. - TTYHandler and ui.Writer escape control characters in messages, attribute keys/values, and rendered identifiers/paths before colour codes are applied, so a crafted value cannot drive the terminal. Model: opus-4-8
51 lines
1.4 KiB
Go
51 lines
1.4 KiB
Go
package vaultik_test
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"testing"
|
|
|
|
"filippo.io/age"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/vaultik/internal/blobgen"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// TestFetchAndDecryptBlobBoundsPlaintext feeds a small, highly
|
|
// compressible blob (256 KiB of zeros) whose decompressed size far exceeds
|
|
// the plaintext bound passed to FetchAndDecryptBlob. Decompression must
|
|
// stop with blobgen.ErrOutputTooLarge within the bound rather than
|
|
// expanding the whole blob into the restore cache.
|
|
func TestFetchAndDecryptBlobBoundsPlaintext(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
identity, err := age.GenerateX25519Identity()
|
|
require.NoError(t, err)
|
|
|
|
plaintext := make([]byte, 256*1024)
|
|
encryptedData, correctHash := buildHashTestBlob(t, identity, plaintext)
|
|
|
|
mockStorage := NewMockStorer()
|
|
blobPath := "blobs/" + correctHash[:2] + "/" +
|
|
correctHash[2:4] + "/" + correctHash
|
|
|
|
mockStorage.mu.Lock()
|
|
mockStorage.data[blobPath] = encryptedData
|
|
mockStorage.mu.Unlock()
|
|
|
|
tv := vaultik.NewForTesting(mockStorage)
|
|
|
|
const maxPlaintext = 1024
|
|
|
|
rc, err := tv.FetchAndDecryptBlob(
|
|
context.Background(), correctHash, maxPlaintext, identity)
|
|
require.NoError(t, err)
|
|
|
|
n, copyErr := io.Copy(io.Discard, rc)
|
|
_ = rc.Close()
|
|
|
|
require.ErrorIs(t, copyErr, blobgen.ErrOutputTooLarge)
|
|
require.LessOrEqual(t, n, int64(maxPlaintext)+1,
|
|
"decompression must stop within the recorded plaintext bound")
|
|
}
|