Fix OOM crash from corrupt compact event data
Add sanity bounds to prevent memory exhaustion when decoding corrupt events with garbage varint values. Previously, corrupt data could cause massive allocations (e.g., make([]byte, 2^60)) leading to OOM crashes. - Add MaxTagsPerEvent (10000), MaxTagElements (100), MaxContentLength (10MB), MaxTagElementLength (1MB) limits - Return sentinel errors for corrupt data instead of logging - Silently skip corrupt events (caller handles gracefully) This fixes crash loops on archive.orly.dev where OOM during writes left corrupt events in bbolt database. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,20 @@ const (
|
|||||||
TagElementPubkeySerial = 0x01 // Pubkey serial reference (5 bytes)
|
TagElementPubkeySerial = 0x01 // Pubkey serial reference (5 bytes)
|
||||||
TagElementEventSerial = 0x02 // Event ID serial reference (5 bytes)
|
TagElementEventSerial = 0x02 // Event ID serial reference (5 bytes)
|
||||||
TagElementEventIdFull = 0x03 // Full event ID (32 bytes) - for unknown refs
|
TagElementEventIdFull = 0x03 // Full event ID (32 bytes) - for unknown refs
|
||||||
|
|
||||||
|
// Sanity limits to prevent OOM from corrupt data
|
||||||
|
MaxTagsPerEvent = 10000 // Maximum number of tags in an event
|
||||||
|
MaxTagElements = 100 // Maximum elements in a single tag
|
||||||
|
MaxContentLength = 10 << 20 // 10MB max content
|
||||||
|
MaxTagElementLength = 1 << 20 // 1MB max for a single tag element
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrTooManyTags = errors.New("corrupt data: too many tags")
|
||||||
|
ErrTooManyTagElems = errors.New("corrupt data: too many tag elements")
|
||||||
|
ErrContentTooLarge = errors.New("corrupt data: content too large")
|
||||||
|
ErrTagElementTooLong = errors.New("corrupt data: tag element too long")
|
||||||
|
ErrUnknownTagElemType = errors.New("corrupt data: unknown tag element type")
|
||||||
)
|
)
|
||||||
|
|
||||||
// SerialResolver is an interface for resolving serials during compact encoding/decoding.
|
// SerialResolver is an interface for resolving serials during compact encoding/decoding.
|
||||||
@@ -287,12 +301,15 @@ func UnmarshalCompactEvent(data []byte, eventId []byte, resolver SerialResolver)
|
|||||||
if nTags, err = varint.Decode(r); chk.E(err) {
|
if nTags, err = varint.Decode(r); chk.E(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if nTags > MaxTagsPerEvent {
|
||||||
|
return nil, ErrTooManyTags // Don't log - caller handles gracefully
|
||||||
|
}
|
||||||
if nTags > 0 {
|
if nTags > 0 {
|
||||||
ev.Tags = tag.NewSWithCap(int(nTags))
|
ev.Tags = tag.NewSWithCap(int(nTags))
|
||||||
for i := uint64(0); i < nTags; i++ {
|
for i := uint64(0); i < nTags; i++ {
|
||||||
var t *tag.T
|
var t *tag.T
|
||||||
if t, err = decodeCompactTag(r, resolver); chk.E(err) {
|
if t, err = decodeCompactTag(r, resolver); err != nil {
|
||||||
return nil, err
|
return nil, err // Don't log corruption errors
|
||||||
}
|
}
|
||||||
*ev.Tags = append(*ev.Tags, t)
|
*ev.Tags = append(*ev.Tags, t)
|
||||||
}
|
}
|
||||||
@@ -303,6 +320,9 @@ func UnmarshalCompactEvent(data []byte, eventId []byte, resolver SerialResolver)
|
|||||||
if contentLen, err = varint.Decode(r); chk.E(err) {
|
if contentLen, err = varint.Decode(r); chk.E(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if contentLen > MaxContentLength {
|
||||||
|
return nil, ErrContentTooLarge
|
||||||
|
}
|
||||||
ev.Content = make([]byte, contentLen)
|
ev.Content = make([]byte, contentLen)
|
||||||
if _, err = io.ReadFull(r, ev.Content); chk.E(err) {
|
if _, err = io.ReadFull(r, ev.Content); chk.E(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -320,16 +340,19 @@ func UnmarshalCompactEvent(data []byte, eventId []byte, resolver SerialResolver)
|
|||||||
// decodeCompactTag decodes a single tag from compact format.
|
// decodeCompactTag decodes a single tag from compact format.
|
||||||
func decodeCompactTag(r io.Reader, resolver SerialResolver) (t *tag.T, err error) {
|
func decodeCompactTag(r io.Reader, resolver SerialResolver) (t *tag.T, err error) {
|
||||||
var nElems uint64
|
var nElems uint64
|
||||||
if nElems, err = varint.Decode(r); chk.E(err) {
|
if nElems, err = varint.Decode(r); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if nElems > MaxTagElements {
|
||||||
|
return nil, ErrTooManyTagElems
|
||||||
|
}
|
||||||
|
|
||||||
t = tag.NewWithCap(int(nElems))
|
t = tag.NewWithCap(int(nElems))
|
||||||
|
|
||||||
for i := uint64(0); i < nElems; i++ {
|
for i := uint64(0); i < nElems; i++ {
|
||||||
var elem []byte
|
var elem []byte
|
||||||
if elem, err = decodeTagElement(r, resolver); chk.E(err) {
|
if elem, err = decodeTagElement(r, resolver); err != nil {
|
||||||
return nil, err
|
return nil, err // Don't log corruption errors
|
||||||
}
|
}
|
||||||
t.T = append(t.T, elem)
|
t.T = append(t.T, elem)
|
||||||
}
|
}
|
||||||
@@ -350,9 +373,12 @@ func decodeTagElement(r io.Reader, resolver SerialResolver) (elem []byte, err er
|
|||||||
case TagElementRaw:
|
case TagElementRaw:
|
||||||
// Raw bytes: varint length + data
|
// Raw bytes: varint length + data
|
||||||
var length uint64
|
var length uint64
|
||||||
if length, err = varint.Decode(r); chk.E(err) {
|
if length, err = varint.Decode(r); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if length > MaxTagElementLength {
|
||||||
|
return nil, ErrTagElementTooLong
|
||||||
|
}
|
||||||
elem = make([]byte, length)
|
elem = make([]byte, length)
|
||||||
if _, err = io.ReadFull(r, elem); err != nil {
|
if _, err = io.ReadFull(r, elem); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -402,7 +428,7 @@ func decodeTagElement(r io.Reader, resolver SerialResolver) (elem []byte, err er
|
|||||||
return elem, nil
|
return elem, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unknown tag element type flag")
|
return nil, ErrUnknownTagElemType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user