mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-18 19:00:46 +00:00
triedb/pathdb: validate restart count and key offsets in trienode decoder
decodeRestartTrailer computed the minimum trailer size as int(8*nRestarts)+4. nRestarts is a uint32, so the multiplication was evaluated in 32-bit and wrapped for counts >= 1<<29, leaving the guard as len(keySection) < 4, which the caller has already excluded. The subsequent offset read then indexed the section with a negative value and panicked. The decoded key offsets were also only checked for monotonicity, never against the end of the key data, so an offset past the limit panicked when the section was sliced. Perform the size computation in 64-bit, bound each key offset by the key limit, and defer the slice preallocation until after the size check.
This commit is contained in:
parent
06b23b4293
commit
802eb91441
2 changed files with 29 additions and 5 deletions
|
|
@ -397,20 +397,25 @@ func decodeRestartTrailer(keySection []byte) ([]uint32, []uint32, int, error) {
|
|||
}
|
||||
nRestarts := binary.BigEndian.Uint32(keySection[len(keySection)-4:])
|
||||
|
||||
// Decode the trailer
|
||||
// Decode the trailer. The multiplication is performed in 64-bit to prevent
|
||||
// the result from wrapping around for large restart counts.
|
||||
if uint64(len(keySection)) < uint64(nRestarts)*8+4 {
|
||||
return nil, nil, 0, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection))
|
||||
}
|
||||
var (
|
||||
keyOffsets = make([]uint32, 0, int(nRestarts))
|
||||
valOffsets = make([]uint32, 0, int(nRestarts))
|
||||
keyLimit = len(keySection) - 4 - int(nRestarts)*8 // End of key data
|
||||
)
|
||||
if len(keySection) < int(8*nRestarts)+4 {
|
||||
return nil, nil, 0, fmt.Errorf("key section too short, restarts: %d, size: %d", nRestarts, len(keySection))
|
||||
}
|
||||
for i := range int(nRestarts) {
|
||||
o := len(keySection) - 4 - (int(nRestarts)-i)*8
|
||||
keyOffset := binary.BigEndian.Uint32(keySection[o : o+4])
|
||||
if i != 0 && keyOffset <= keyOffsets[i-1] {
|
||||
return nil, nil, 0, fmt.Errorf("key offset is out of order, prev: %v, cur: %v", keyOffsets[i-1], keyOffset)
|
||||
}
|
||||
if int(keyOffset) > keyLimit {
|
||||
return nil, nil, 0, fmt.Errorf("key offset is out of range, offset: %d, limit: %d", keyOffset, keyLimit)
|
||||
}
|
||||
keyOffsets = append(keyOffsets, keyOffset)
|
||||
|
||||
// Same value offset is allowed just in case all the trie nodes in the last
|
||||
|
|
@ -421,7 +426,6 @@ func decodeRestartTrailer(keySection []byte) ([]uint32, []uint32, int, error) {
|
|||
}
|
||||
valOffsets = append(valOffsets, valOffset)
|
||||
}
|
||||
keyLimit := len(keySection) - 4 - int(nRestarts)*8 // End of key data
|
||||
return keyOffsets, valOffsets, keyLimit, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -656,6 +656,26 @@ func TestDecodeSingleCorruptedData(t *testing.T) {
|
|||
if err == nil {
|
||||
t.Fatal("Expected error for invalid restart count")
|
||||
}
|
||||
|
||||
// Test with a restart count large enough to overflow the trailer size
|
||||
// computation (8*nRestarts wraps around for nRestarts >= 1<<29).
|
||||
corrupted = make([]byte, len(keySection))
|
||||
copy(corrupted, keySection)
|
||||
binary.BigEndian.PutUint32(corrupted[len(corrupted)-4:], 1<<29)
|
||||
err = decodeSingle(corrupted, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for overflowing restart count")
|
||||
}
|
||||
|
||||
// Test with a key offset pointing beyond the end of the key data
|
||||
corrupted = make([]byte, 12)
|
||||
binary.BigEndian.PutUint32(corrupted[0:4], 0xffffffff) // key offset
|
||||
binary.BigEndian.PutUint32(corrupted[4:8], 0) // value offset
|
||||
binary.BigEndian.PutUint32(corrupted[8:12], 1) // restart count
|
||||
err = decodeSingle(corrupted, nil)
|
||||
if err == nil {
|
||||
t.Fatal("Expected error for out-of-range key offset")
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to test encode/decode cycle
|
||||
|
|
|
|||
Loading…
Reference in a new issue