Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CHANGELOG
=========

4.1.1
4.2.0
------------------

* Fixed decoding of data pointers with offsets of 2 GiB or greater. The
Expand All @@ -10,6 +10,43 @@ CHANGELOG
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.
* Fixed typed decoding when an unknown field contains a four-byte pointer whose
low three control bits are nonzero. The pointer's control bits were
incorrectly interpreted as a payload size, which could make decoding resume
in the wrong place. The decoder also rejects a skipped value whose complete
header declares a payload that extends past the data section. Databases written
by MaxMind tooling were not affected.
* Fixed UTF-8 decoding for strings whose multibyte characters cross a buffer
chunk boundary. The decoder also rejects an incomplete multibyte character at
the end of a string.
* Bounded the resources that the decoder spends on a single decode operation. A
crafted database could nest data-section pointers to shared targets so that
decoding one record cost exponential time and memory, or point many times at
one large value so that decoding materialized far more data than the file
holds. Each decode is now limited to 65,536 decoded or skipped values under
this reader's work accounting, 128 levels of container nesting, and 2 MiB of
encoded string and bytes payload. The value limit follows the MaxMind DB
specification's resource guidance. The lower depth limit, payload limit, and
exact value accounting are specific to this reader.
* Exceeding a limit throws an `InvalidDatabaseException`.
* The decoder rejects a data-section pointer whose target is another pointer,
as required by the MaxMind DB format.
* Metadata decoding uses all limits. Skipped fields use the value and depth
limits without materializing their payload.
* Cached pointer targets retain their logical value, depth, and payload cost.
Reusing a target charges that recorded cost without decoding or materializing
it again.
* The decoder rejects integer encodings wider than the format permits before
reading their payload.
* The decoder reports truncated string and bytes values, and malformed UTF-8
strings, as invalid database data.
* The decoder checks declared map and array sizes before decoding their
children. It caps collection preallocation so nested crafted sizes cannot
exhaust the heap before a decoder limit rejects them.
* Improved decoder performance and reduced per-lookup allocation. The decoder
now avoids transient value wrappers, short-circuits common collection targets,
and avoids temporary character buffers for most UTF-8 strings while preserving
strict validation.

4.1.0 (2026-05-12)
------------------
Expand Down
29 changes: 29 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
# Upgrading to 4.2.0

## Decoder Resource Limits

Version 4.2.0 limits the work and memory used by one record or metadata decode.
The decoder rejects an operation that exceeds any of these limits:

- 65,536 decoded or skipped values under the Java reader's work accounting
- 128 nested maps or arrays
- 2 MiB of encoded string and bytes payload materialized by the decoder

Each cached pointer target retains its logical value, depth, and payload cost.
Every pointer occurrence consumes that recorded cost. The cache still avoids
decoding or materializing the target again, but cache state does not determine
whether an operation exceeds a limit.

These limits leave a wide margin above MaxMind-produced records. A custom
database containing an unusually large record that decoded in an earlier
release may now throw `InvalidDatabaseException`. The limits are not
configurable in this release.

When the decoder constructs a custom `List` or `Map` type through an `int`
constructor, it passes an initial-capacity hint capped at 128 rather than the
full declared collection size.

The decoder also rejects a data-section pointer whose target is another pointer,
which the MaxMind DB format does not permit. It rejects integer payloads wider
than their format type permits before reading the payload.

# Upgrading to 4.0.0

This guide covers the breaking changes introduced in version 4.0.0 and how to
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/com/maxmind/db/DecodedValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
* {@code DecodedValue} is a wrapper for the decoded value.
*/
public final class DecodedValue {
private static final int PAYLOAD_SHIFT = 8;
private static final int VALUES_SHIFT = 30;
private static final long PAYLOAD_MASK = (1L << 22) - 1;

final Object value;
private long costs;

DecodedValue(Object value) {
this.value = value;
Expand All @@ -13,4 +18,39 @@ public final class DecodedValue {
Object value() {
return value;
}

int values() {
return values(costs());
}

static int values(long costs) {
return (int) (costs >>> VALUES_SHIFT);
}

long payloadBytes() {
return payloadBytes(costs());
}

static long payloadBytes(long costs) {
return (costs >>> PAYLOAD_SHIFT) & PAYLOAD_MASK;
}

int depth() {
return depth(costs());
}

static int depth(long costs) {
return (int) (costs & 0xFF);
}

DecodedValue costs(int values, long payloadBytes, int depth) {
this.costs = ((long) values << VALUES_SHIFT)
| (payloadBytes << PAYLOAD_SHIFT)
| depth;
return this;
}

long costs() {
return this.costs;
}
}
Loading
Loading