diff --git a/internal/backup/verify.go b/internal/backup/verify.go index f2bf52a..6884745 100644 --- a/internal/backup/verify.go +++ b/internal/backup/verify.go @@ -53,6 +53,10 @@ func Verify(ctx context.Context, input io.Reader, maxBytes int64) (*Manifest, er // Bound Next itself: it otherwise hides and allocates extension records. decoded.limit = before + padding + 3*512 h, err := tr.Next() + if errors.Is(err, ErrTooLarge) { + // The 3-block header budget is a structure check, not the size limit. + return nil, verificationError(ctx, nil, "tar header") + } if err == io.EOF { // tar.Reader also accepts EOF without both closing zero blocks. if decoded.n-before != padding+2*512 { @@ -106,6 +110,9 @@ func Verify(ctx context.Context, input io.Reader, maxBytes int64) (*Manifest, er decoded.limit = decoded.n var tail [1]byte if _, err := decoded.Read(tail[:]); err != io.EOF { + if errors.Is(err, ErrTooLarge) { + err = nil // bytes after the tar terminator are corruption, not oversize + } return nil, verificationError(ctx, err, "gzip completion or trailing tar data") } if _, err := buffered.ReadByte(); err != io.EOF { diff --git a/internal/backup/verify_test.go b/internal/backup/verify_test.go index fb0e9f1..06eb123 100644 --- a/internal/backup/verify_test.go +++ b/internal/backup/verify_test.go @@ -170,9 +170,14 @@ func TestVerifyRejectsInvalidMembersAndManifest(t *testing.T) { } { t.Run(tc.name, func(t *testing.T) { data := verifyGzip(t, verifyTar(t, tc.change(verifyFixture(t)))) - if _, err := Verify(context.Background(), bytes.NewReader(data), int64(len(testSQL))); err == nil || strings.Contains(err.Error(), "PRIVATE_CANARY") { + _, err := Verify(context.Background(), bytes.NewReader(data), int64(len(testSQL))) + if err == nil || strings.Contains(err.Error(), "PRIVATE_CANARY") { t.Fatalf("expected a sanitized rejection, got %v", err) } + // PAX header size errors should be corruption, not size limit. + if tc.name == "hidden oversized metadata" && errors.Is(err, ErrTooLarge) { + t.Fatalf("oversized header reported as size limit: %v", err) + } }) } } @@ -190,9 +195,13 @@ func TestVerifyFraming(t *testing.T) { } for name, data := range cases { t.Run(name, func(t *testing.T) { - if _, err := Verify(context.Background(), bytes.NewReader(data), DefaultMaxBytes); err == nil || strings.Contains(err.Error(), "PRIVATE_CANARY") { + _, err := Verify(context.Background(), bytes.NewReader(data), DefaultMaxBytes) + if err == nil || strings.Contains(err.Error(), "PRIVATE_CANARY") { t.Fatalf("expected sanitized framing error, got %v", err) } + if errors.Is(err, ErrTooLarge) { + t.Fatalf("framing corruption reported as size limit: %v", err) + } }) } }