diff --git a/CHANGELOG.md b/CHANGELOG.md index 50aff6c8..a64da97e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,16 @@ CHANGELOG ========= +4.1.1 +------------------ + +* Fixed decoding of data pointers with offsets of 2 GiB or greater. The + pointer payload was decoded into an `int`, so such offsets were + sign-extended to a negative value and rejected by `Buffer.position()` + with an `IllegalArgumentException`. Every record past the 2 GiB + boundary was unreachable in databases larger than 2 GiB, which have + been supported since 4.0.0. + 4.1.0 (2026-05-12) ------------------ diff --git a/src/main/java/com/maxmind/db/Decoder.java b/src/main/java/com/maxmind/db/Decoder.java index f76b5851..73a337e5 100644 --- a/src/main/java/com/maxmind/db/Decoder.java +++ b/src/main/java/com/maxmind/db/Decoder.java @@ -133,7 +133,7 @@ private DecodedValue decode(Class cls, java.lang.reflect.Type genericType if (type.equals(Type.POINTER)) { var pointerSize = ((ctrlByte >>> 3) & 0x3) + 1; var base = pointerSize == 4 ? (byte) 0 : (byte) (ctrlByte & 0x7); - var packed = this.decodeInteger(base, pointerSize); + var packed = Decoder.decodeLong(this.buffer, base, pointerSize); var pointer = packed + this.pointerBase + POINTER_VALUE_OFFSETS[pointerSize]; return decodePointer(pointer, cls, genericType); diff --git a/src/test/java/com/maxmind/db/PointerTest.java b/src/test/java/com/maxmind/db/PointerTest.java index 1c48d0de..23ab6c19 100644 --- a/src/test/java/com/maxmind/db/PointerTest.java +++ b/src/test/java/com/maxmind/db/PointerTest.java @@ -5,8 +5,10 @@ import com.maxmind.db.Reader.FileMode; import java.io.File; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.Test; public class PointerTest { @@ -41,4 +43,22 @@ public void testWithPointers() throws IOException { map.put("long_key2", "long_value2"); assertEquals(map, decoder.decode(57, Map.class)); } + + @SuppressWarnings("static-method") + @Test + public void testPointerBeyond2GiBIsNotSignExtended() throws IOException { + // Control byte for a four-byte pointer, followed by the offset 3473557240. + var buffer = new SingleBuffer(ByteBuffer.wrap(new byte[]{ + (byte) 0x38, (byte) 0xCF, (byte) 0x0A, (byte) 0x46, (byte) 0xF8})); + + var observed = new AtomicLong(Long.MIN_VALUE); + NodeCache cache = (key, loader) -> { + observed.set(key.offset()); + return new DecodedValue(null); + }; + + new Decoder(cache, buffer, 0).decode(0, Object.class); + + assertEquals(3473557240L, observed.get()); + } }