security: hardening по результатам аудита безопасности
This commit is contained in:
@@ -74,6 +74,11 @@ var (
|
||||
// ErrUnexpectedEOF indicates the chunk stream ended before any chunk
|
||||
// with flags&0x01==1 (logical end-of-stream marker) was observed.
|
||||
ErrUnexpectedEOF = errors.New("composite: unexpected end of stream")
|
||||
|
||||
// ErrPlaintextTooLarge indicates the decrypted payload would exceed the
|
||||
// configured maximum plaintext size; decryption is aborted before the
|
||||
// limit is crossed to prevent unbounded disk consumption.
|
||||
ErrPlaintextTooLarge = errors.New("composite: plaintext size limit exceeded")
|
||||
)
|
||||
|
||||
// Format constants.
|
||||
@@ -89,6 +94,7 @@ const (
|
||||
wrappedCekLen int = 48 // 32-byte CEK + 16-byte GCM tag
|
||||
firstPayloadNonceLen int = 12
|
||||
kekLen int = 32
|
||||
maxPlaintextSize int64 = 1 << 40 // 1 TiB cap on decrypted output
|
||||
infoPq string = "git.tswf.io/infra/go-synapse-backupper/v2/kek/pq"
|
||||
infoComposite string = "git.tswf.io/infra/go-synapse-backupper/v2/kek/composite"
|
||||
)
|
||||
@@ -505,7 +511,24 @@ func (d *decryptor) Decrypt(
|
||||
return err
|
||||
}
|
||||
|
||||
return decryptChunks(src, plaintext, payloadGcm, firstPayloadNonce)
|
||||
plaintextLimiter := &limitedWriter{writer: plaintext, remaining: maxPlaintextSize}
|
||||
return decryptChunks(src, plaintextLimiter, payloadGcm, firstPayloadNonce)
|
||||
}
|
||||
|
||||
// limitedWriter wraps an io.Writer and rejects writes that would exceed a
|
||||
// maximum byte budget.
|
||||
type limitedWriter struct {
|
||||
writer io.Writer
|
||||
remaining int64
|
||||
}
|
||||
|
||||
func (lw *limitedWriter) Write(p []byte) (int, error) {
|
||||
if int64(len(p)) > lw.remaining {
|
||||
return 0, ErrPlaintextTooLarge
|
||||
}
|
||||
n, err := lw.writer.Write(p)
|
||||
lw.remaining -= int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// decryptChunks reads and decrypts chunk records until a final chunk
|
||||
|
||||
@@ -947,3 +947,41 @@ func loadGoldenPrivs(
|
||||
}
|
||||
return newFakePriv(fakePqSchemeID, pqRaw), newFakePriv(fakeClassicalSchemeID, classicalRaw)
|
||||
}
|
||||
|
||||
func TestLimitedWriter_AllowsWritesWithinBudget(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
lw := &limitedWriter{writer: &buf, remaining: 10}
|
||||
|
||||
n, err := lw.Write([]byte("hello"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if n != 5 {
|
||||
t.Errorf("wrote %d bytes, want 5", n)
|
||||
}
|
||||
|
||||
n, err = lw.Write([]byte("world"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if n != 5 {
|
||||
t.Errorf("wrote %d bytes, want 5", n)
|
||||
}
|
||||
|
||||
if buf.String() != "helloworld" {
|
||||
t.Errorf("buffer = %q, want %q", buf.String(), "helloworld")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitedWriter_RejectsOverBudget(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
lw := &limitedWriter{writer: &buf, remaining: 3}
|
||||
|
||||
_, err := lw.Write([]byte("hello"))
|
||||
if !errors.Is(err, ErrPlaintextTooLarge) {
|
||||
t.Errorf("error = %v, want ErrPlaintextTooLarge", err)
|
||||
}
|
||||
if buf.Len() != 0 {
|
||||
t.Errorf("buffer len = %d, want 0", buf.Len())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user