geom: fix panic in UnmarshalTWKB on huge element counts - #712
Conversation
TWKB element counts (point counts, ring point counts, and ID list lengths) are read as untrusted varints and used directly as make() lengths. A crafted count such as 2^64-1 casts to a negative int and panics with "makeslice: len out of range" (and smaller-but-still-huge counts trigger excessive allocation). Validate each count against the number of remaining bytes before allocating, since every coordinate and ID is encoded as a varint of at least one byte.
peterstace
left a comment
There was a problem hiding this comment.
Thanks for contributing @ChrisJr404 !
I've got a few small requests to clean some things up before merging.
| func (p *twkbParser) parsePointArray(numPoints int) ([]float64, error) { | ||
| // Guard against corrupt or malicious inputs that specify a huge point | ||
| // count. Each coordinate is encoded as a varint of at least one byte, so a | ||
| // valid encoding of numPoints points needs at least numPoints*dimensions | ||
| // remaining bytes. Checking this before allocating avoids a make() panic | ||
| // (or excessive memory allocation) driven by an untrusted count. | ||
| if numPoints < 0 || numPoints > (len(p.twkb)-p.pos)/p.dimensions { | ||
| return nil, fmt.Errorf("number of points %d exceeds remaining buffer size", numPoints) | ||
| } |
There was a problem hiding this comment.
parsePointCountAndArray narrows the count before this check, passing int(numPoints), so 2^64-1 arrives here as -1. That's what the < 0 arm is for, and the error then reads number of points -1 exceeds remaining buffer size. wkb_parser.go:231 compares in uint64 before narrowing and so needs no negative case:
| func (p *twkbParser) parsePointArray(numPoints int) ([]float64, error) { | |
| // Guard against corrupt or malicious inputs that specify a huge point | |
| // count. Each coordinate is encoded as a varint of at least one byte, so a | |
| // valid encoding of numPoints points needs at least numPoints*dimensions | |
| // remaining bytes. Checking this before allocating avoids a make() panic | |
| // (or excessive memory allocation) driven by an untrusted count. | |
| if numPoints < 0 || numPoints > (len(p.twkb)-p.pos)/p.dimensions { | |
| return nil, fmt.Errorf("number of points %d exceeds remaining buffer size", numPoints) | |
| } | |
| func (p *twkbParser) parsePointArray(count uint64) ([]float64, error) { | |
| // Guard against corrupt or malicious inputs that specify a huge point | |
| // count. Each coordinate is encoded as a varint of at least one byte, so a | |
| // valid encoding of count points needs at least count*dimensions remaining | |
| // bytes. Checking the count before narrowing it to an int keeps an | |
| // untrusted value out of make(). | |
| remaining := len(p.twkb) - p.pos | |
| if count > uint64(remaining/p.dimensions) { | |
| return nil, fmt.Errorf("number of points %d exceeds remaining buffer size of %d bytes", count, remaining) | |
| } | |
| numPoints := int(count) |
parsePointCountAndArray then drops its int(...) cast.
| if _, err := geom.UnmarshalTWKB(tc.twkb); err == nil { | ||
| t.Fatal("expected an error, but got nil") | ||
| } |
There was a problem hiding this comment.
The file already imports internal/test and uses it throughout, and test.Err does exactly this check.
| if _, err := geom.UnmarshalTWKB(tc.twkb); err == nil { | |
| t.Fatal("expected an error, but got nil") | |
| } | |
| _, err := geom.UnmarshalTWKB(tc.twkb) | |
| test.Err(t, err) |
|
Thanks for the review. I made both cleanups: parsePointArray now takes the count as a uint64 and checks it before narrowing to int (so the negative arm is gone and the caller drops its cast), and the huge-count test uses test.Err now. |
peterstace
left a comment
There was a problem hiding this comment.
LGTM, thanks for the contribution!
Description
UnmarshalTWKBpanics on certain malformed inputs. TWKB element counts (aLineString/ring point count, or a MultiPoint/collection ID list length) are
read as untrusted
uvarints and passed straight intomake()as the slicelength. A crafted count of
2^64-1casts to a negativeintand panics withruntime error: makeslice: len out of range; smaller-but-still-huge countsinstead trigger an oversized allocation. Both are reachable directly from the
documented
UnmarshalTWKBentry point with a handful of bytes, so any callerdecoding untrusted TWKB can be crashed.
Minimal repro (before this change):
The fix validates each count against the number of remaining bytes before
allocating, in
parsePointArrayandparseIDList. Every coordinate and everyID is encoded as a varint of at least one byte, so a count larger than the
remaining buffer (or negative, from the overflow cast) cannot describe a valid
TWKB and is now rejected with an error. This mirrors the length checks the WKB
parser already performs. Valid inputs are unaffected — the full
geomtestsuite passes.
Check List
Have you:
Added unit tests? Yes —
TestUnmarshalTWKBHugeCountcovers the LineString,Polygon-ring, and MultiPoint-ID-list paths. It panics before this change and
passes after.
Add cmprefimpl tests? Not appropriate (this is a malformed-input rejection,
not a value-semantics change).
Updated release notes? Yes — added a bullet to the Unreleased section of
CHANGELOG.md.Updated the README.md? Not applicable (no new functionality).
Related Issue
Benchmark Results
path.