Skip to content

Commit 6d5f7ab

Browse files
committed
Fix pointer-to-pointer infinite recursion
Replace recursive decodeKey implementation with iterative approach and add validation to prevent pointer-to-pointer references which are invalid per MaxMind DB specification.
1 parent 2a7e2ee commit 6d5f7ab

File tree

3 files changed

+44
-4
lines changed

3 files changed

+44
-4
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Added `Offset()` method to `Decoder` to get the current database offset. This
66
enables custom unmarshalers to implement caching for improved performance when
77
loading databases with duplicate data structures.
8+
- Fixed infinite recursion in pointer-to-pointer data structures, which are
9+
invalid per the MaxMind DB specification.
810

911
## 2.0.0-beta.4 - 2025-07-05
1012

internal/decoder/data_decoder.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,14 +380,31 @@ func (d *DataDecoder) decodeKey(offset uint) ([]byte, uint, error) {
380380
if err != nil {
381381
return nil, 0, err
382382
}
383+
384+
// Follow pointer if present (but only once, per spec)
385+
nextOffset := dataOffset + size // default return offset
383386
if kindNum == KindPointer {
384-
pointer, ptrOffset, err := d.decodePointer(size, dataOffset)
387+
pointer, newNextOffset, err := d.decodePointer(size, dataOffset)
388+
if err != nil {
389+
return nil, 0, err
390+
}
391+
nextOffset = newNextOffset
392+
393+
// Decode the pointed-to data
394+
kindNum, size, dataOffset, err = d.decodeCtrlData(pointer)
385395
if err != nil {
386396
return nil, 0, err
387397
}
388-
key, _, err := d.decodeKey(pointer)
389-
return key, ptrOffset, err
398+
399+
// Check for pointer-to-pointer, which is invalid per spec
400+
if kindNum == KindPointer {
401+
return nil, 0, mmdberrors.NewInvalidDatabaseError(
402+
"invalid pointer to pointer at offset %d",
403+
pointer,
404+
)
405+
}
390406
}
407+
391408
if kindNum != KindString {
392409
return nil, 0, mmdberrors.NewInvalidDatabaseError(
393410
"unexpected type when decoding string: %v",
@@ -398,7 +415,7 @@ func (d *DataDecoder) decodeKey(offset uint) ([]byte, uint, error) {
398415
if newOffset > uint(len(d.buffer)) {
399416
return nil, 0, mmdberrors.NewOffsetError()
400417
}
401-
return d.buffer[dataOffset:newOffset], newOffset, nil
418+
return d.buffer[dataOffset:newOffset], nextOffset, nil
402419
}
403420

404421
// NextValueOffset skips ahead to the next value without decoding

internal/decoder/reflection.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,14 @@ PATH:
9898
if err != nil {
9999
return err
100100
}
101+
102+
// Check for pointer-to-pointer after we've already read the data
103+
if typeNum == KindPointer {
104+
return mmdberrors.NewInvalidDatabaseError(
105+
"invalid pointer to pointer at offset %d",
106+
pointer,
107+
)
108+
}
101109
}
102110

103111
switch v := v.(type) {
@@ -512,6 +520,19 @@ func (d *ReflectionDecoder) unmarshalPointer(
512520
if err != nil {
513521
return 0, err
514522
}
523+
524+
// Check for pointer-to-pointer by looking at what we're about to decode
525+
// This is done efficiently by checking the control byte at the pointer location
526+
if len(d.buffer) > int(pointer) {
527+
controlByte := d.buffer[pointer]
528+
if (controlByte >> 5) == 1 { // KindPointer = 1, stored in top 3 bits
529+
return 0, mmdberrors.NewInvalidDatabaseError(
530+
"invalid pointer to pointer at offset %d",
531+
pointer,
532+
)
533+
}
534+
}
535+
515536
_, err = d.decodeValue(pointer, result, depth)
516537
return newOffset, err
517538
}

0 commit comments

Comments
 (0)