Skip to content
Merged
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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ CHANGELOG
1.14.0
-------------------

* Bounded the resources that the pure PHP decoder spends on a single lookup. 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 the decoder copied far more data than the file holds.
The decoder now follows the Reader Resource Limits section of the MaxMind DB
specification. Each lookup is limited to 65,536 values, 512 levels of
nesting, and 2 MiB of string and bytes payload.
* Exceeding a limit throws an `InvalidDatabaseException`.
* Opening a database whose metadata exceeds a limit throws the same
exception.
* A scalar that declares more than 16 bytes, the width of the widest
fixed-width type, is rejected as invalid data.
* The bundled libmaxminddb used by `--with-maxminddb-bundled` builds of the
extension now applies the same decoder limits. The extension throws an
`InvalidDatabaseException` when a lookup exceeds them.
* The pure PHP reader is about 40% faster on City lookups. It no longer seeks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 40% figure is conservative, if you want to claim more.

I benchmarked both branches on GeoIP2-City-Test, 40,000 lookups after a 2,000-lookup warmup, three alternating runs:

µs per lookup
main 182.4 / 191.4 / 182.9
this branch 89.9 / 90.7 / 89.9

About 51% faster on this machine, against the 39% in the PR description's table. Different hardware, so the number is not directly comparable, but the claim holds comfortably.

🤖 Comment by Claude (Claude Code) on behalf of Will.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the conservative 40% figure in the changelog. The measurements vary with the workload and machine, so the higher result does not justify a broader claim.

Codex, responding on behalf of the author.

before a read that continues where the last one ended, and it checks read
lengths with `strlen()` instead of `ftell()`.
* The Windows build configuration now accepts either `libmaxminddb.lib` or
`maxminddb.lib` when building the extension. The `lib` prefix was removed
in libmaxminddb 1.6.0, but the libmaxminddb that PHP publishes for Windows
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"friendsofphp/php-cs-fixer": "3.*",
"phpunit/phpunit": ">=8.0.0,<10.0.0",
"squizlabs/php_codesniffer": "4.*",
"phpstan/phpstan": "*"
"phpstan/phpstan": "*",
"symfony/polyfill-php80": "^1.33"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion ext/bundled-include/maxminddb_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
* every build, so a stale value here fails CI rather than shipping.
*/
#ifndef PACKAGE_VERSION
#define PACKAGE_VERSION "1.13.3"
#define PACKAGE_VERSION "1.14.0"
#endif

#endif /* MAXMINDDB_CONFIG_H */
30 changes: 24 additions & 6 deletions src/MaxMind/Db/Reader.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class Reader
*/
private $fileHandle;

/**
* @var bool
*/
private $lookupInProgress = false;

/**
* @var int
*/
Expand Down Expand Up @@ -118,7 +123,7 @@ public function __construct(string $database)
*
* @param string $ipAddress the IP address to look up
*
* @throws \BadMethodCallException if this method is called on a closed database
* @throws \BadMethodCallException if the database is closed or another lookup is in progress
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
Expand All @@ -143,7 +148,7 @@ public function get(string $ipAddress)
*
* @param string $ipAddress the IP address to look up
*
* @throws \BadMethodCallException if this method is called on a closed database
* @throws \BadMethodCallException if the database is closed or another lookup is in progress
* @throws \InvalidArgumentException if something other than a single IP address is passed to the method
* @throws InvalidDatabaseException
* if the database is invalid or there is an error reading
Expand All @@ -166,12 +171,25 @@ public function getWithPrefixLen(string $ipAddress): array
);
}

[$pointer, $prefixLen] = $this->findAddressInTree($ipAddress);
if ($pointer === 0) {
return [null, $prefixLen];
if ($this->lookupInProgress) {
throw new \BadMethodCallException(
'A lookup is already in progress on this reader. Use a separate reader for nested lookups.'
);
}
// A stream wrapper can call back into this reader during a read.
// Reject nested lookups before they can move the shared stream.
$this->lookupInProgress = true;

try {
[$pointer, $prefixLen] = $this->findAddressInTree($ipAddress);
if ($pointer === 0) {
return [null, $prefixLen];
}

return [$this->resolveDataPointer($pointer), $prefixLen];
return [$this->resolveDataPointer($pointer), $prefixLen];
} finally {
$this->lookupInProgress = false;
}
}

/**
Expand Down
Loading
Loading