diff --git a/cel/canonical_eventlog.go b/cel/canonical_eventlog.go index b0177e6..231d4e8 100644 --- a/cel/canonical_eventlog.go +++ b/cel/canonical_eventlog.go @@ -38,6 +38,12 @@ const ( recnumValueLength uint32 = 8 // support up to 2^64 records regIndexValueLength uint32 = 1 // support up to 256 registers + + // maxTLVValueSize caps the allocation in unmarshalFirstTLV to prevent an + // out-of-memory condition when parsing attacker-controlled CEL data. + // The largest legitimate TLV value in a CEL record is the content field, + // which is bounded well below 1 MiB in practice. + maxTLVValueSize uint32 = 1 << 20 // 1 MiB ) // MRExtender extends an implementation-specific measurement register at the @@ -96,6 +102,9 @@ func unmarshalFirstTLV(buf *bytes.Buffer) (tlv TLV, err error) { return TLV{}, io.EOF } valueLength := binary.BigEndian.Uint32(lengthBytes) + if valueLength > maxTLVValueSize { + return TLV{}, fmt.Errorf("TLV value length %d exceeds maximum %d", valueLength, maxTLVValueSize) + } data = append(data, lengthBytes...) valueBytes := make([]byte, valueLength) diff --git a/cel/canonical_eventlog_test.go b/cel/canonical_eventlog_test.go index ce1bf47..5fd9bf3 100644 --- a/cel/canonical_eventlog_test.go +++ b/cel/canonical_eventlog_test.go @@ -255,3 +255,24 @@ func fakeRotExtender(rot register.FakeROT) MRExtender { }) } } + +// TestOversizeTLVValueLength is a regression test for a pre-fix OOM condition: +// unmarshalFirstTLV allocated make([]byte, valueLength) without any bounds check, +// allowing a 5-byte crafted TLV (1 type byte + 4-byte uint32 length) to trigger +// a multi-gigabyte allocation. The fix adds the maxTLVValueSize guard. +func TestOversizeTLVValueLength(t *testing.T) { + // 5-byte crafted TLV: type=0x00, length=maxTLVValueSize+1, no value bytes. + var buf bytes.Buffer + buf.WriteByte(0x00) // recnum type + oversize := maxTLVValueSize + 1 + buf.WriteByte(byte(oversize >> 24)) + buf.WriteByte(byte(oversize >> 16)) + buf.WriteByte(byte(oversize >> 8)) + buf.WriteByte(byte(oversize)) + + _, err := unmarshalFirstTLV(&buf) + if err == nil { + t.Fatal("unmarshalFirstTLV should return an error for oversize valueLength") + } + t.Logf("correctly rejected oversize TLV: %v", err) +}