-
Notifications
You must be signed in to change notification settings - Fork 243
Bound decoded values to prevent a pointer fan-out DoS (STF-1568) #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
70d3862
3d8b5ec
044c3c2
b7dc5ab
27d6997
12537f3
1ca2612
8c2a60a
f64beda
5fb2ce5
ea62fc2
5509ed3
5017610
3bb02c0
ed6a893
f5fd27d
5741fff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,28 +9,37 @@ | |
| #include <stddef.h> | ||
| #include <stdlib.h> | ||
|
|
||
| // Allocate an MMDB_data_pool_s. It initially has space for size | ||
| // MMDB_entry_data_list_s structs. | ||
| MMDB_data_pool_s *data_pool_new(size_t const size) { | ||
| // Allocate an MMDB_data_pool_s. It initially has space for up to size | ||
| // MMDB_entry_data_list_s structs. Its total capacity will not exceed max_size. | ||
| MMDB_data_pool_s *data_pool_new(size_t const size, size_t const max_size) { | ||
| MMDB_data_pool_s *const pool = calloc(1, sizeof(MMDB_data_pool_s)); | ||
| if (!pool) { | ||
| return NULL; | ||
| } | ||
|
|
||
| if (size == 0 || | ||
| !can_multiply(SIZE_MAX, size, sizeof(MMDB_entry_data_list_s))) { | ||
| if (size == 0 || max_size == 0) { | ||
| data_pool_destroy(pool); | ||
| return NULL; | ||
| } | ||
| pool->size = size; | ||
| size_t initial_size = size; | ||
| if (initial_size > max_size) { | ||
| initial_size = max_size; | ||
| } | ||
| if (!can_multiply(SIZE_MAX, initial_size, sizeof(MMDB_entry_data_list_s))) { | ||
| data_pool_destroy(pool); | ||
| return NULL; | ||
| } | ||
| pool->size = initial_size; | ||
| pool->blocks[0] = calloc(pool->size, sizeof(MMDB_entry_data_list_s)); | ||
| if (!pool->blocks[0]) { | ||
| data_pool_destroy(pool); | ||
| return NULL; | ||
| } | ||
| pool->blocks[0]->pool = pool; | ||
|
|
||
| pool->sizes[0] = size; | ||
| pool->sizes[0] = initial_size; | ||
| pool->capacity = initial_size; | ||
| pool->max_size = max_size; | ||
|
|
||
| pool->block = pool->blocks[0]; | ||
|
|
||
|
|
@@ -62,8 +71,9 @@ void data_pool_destroy(MMDB_data_pool_s *const pool) { | |
| free(pool); | ||
| } | ||
|
|
||
| // Claim a new struct from the pool. Doing this may cause the pool's size to | ||
| // grow. | ||
| // Claim a new struct from the pool. Doing this may grow the pool. NULL means an | ||
| // allocation failed or the pool reached its maximum capacity. Decoder callers | ||
| // check their logical limit before reaching the capacity limit. | ||
| MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool) { | ||
| if (!pool) { | ||
| return NULL; | ||
|
|
@@ -75,6 +85,10 @@ MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool) { | |
| return element; | ||
| } | ||
|
|
||
| if (pool->capacity == pool->max_size) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This adds a second Callers cannot distinguish "pool reached So this is defense-in-depth rather than a live path, which is fine. But that reasoning lives in another file, and if the guard in 🤖 Comment by Claude (Claude Code) on behalf of Will.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarified in 5017610. The comment now records both possible — Codex, on behalf of Greg. |
||
| return NULL; | ||
| } | ||
|
|
||
| // Take it from a new block of memory. | ||
|
|
||
| size_t const new_index = pool->index + 1; | ||
|
|
@@ -83,10 +97,11 @@ MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool) { | |
| return NULL; | ||
| } | ||
|
|
||
| if (!can_multiply(SIZE_MAX, pool->size, 2)) { | ||
| return NULL; | ||
| size_t const remaining = pool->max_size - pool->capacity; | ||
| size_t new_size = remaining; | ||
| if (pool->size <= remaining / 2) { | ||
| new_size = pool->size * 2; | ||
| } | ||
| size_t const new_size = pool->size * 2; | ||
|
|
||
| if (!can_multiply(SIZE_MAX, new_size, sizeof(MMDB_entry_data_list_s))) { | ||
| return NULL; | ||
|
|
@@ -104,6 +119,7 @@ MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const pool) { | |
|
|
||
| pool->size = new_size; | ||
| pool->sizes[pool->index] = pool->size; | ||
| pool->capacity += new_size; | ||
|
|
||
| MMDB_entry_data_list_s *const element = pool->block; | ||
| pool->used = 1; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,10 @@ | |
| #include <stdbool.h> | ||
| #include <stddef.h> | ||
|
|
||
| // This should be large enough that we never need to grow the array of pointers | ||
| // to blocks. 32 is enough. Even starting out of with size 1 (1 struct), the | ||
| // 32nd element alone will provide 2**32 structs as we exponentially increase | ||
| // the number in each block. Being confident that we do not have to grow the | ||
| // array lets us avoid writing code to do that. That code would be risky as it | ||
| // would rarely be hit and likely not be well tested. | ||
| // Keep the block array fixed so that its own growth does not need a rarely used | ||
| // reallocation path. Even starting with one struct, 32 geometrically growing | ||
| // blocks cover every practical allocation; the last block may be clamped to the | ||
| // configured capacity. | ||
| #define DATA_POOL_NUM_BLOCKS 32 | ||
|
|
||
| // A pool of memory for MMDB_entry_data_list_s structs. This is so we can | ||
|
|
@@ -33,6 +31,12 @@ typedef struct MMDB_data_pool_s { | |
| // How many used in the current block, counting by structs. | ||
| size_t used; | ||
|
|
||
| // Total number of structs reserved across all blocks. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The It reasons purely from exponential growth: "the 32nd element alone will provide 2**32 structs as we exponentially increase the number in each block." Growth is no longer purely exponential — The 32-block conclusion still holds (the clamp happens at most once, at the end, so the block count is at most one more than under pure doubling; the library's own 64/65536 configuration uses about 11 blocks). But the stated reasoning no longer describes the code and does not mention Also worth stating the relations the new fields introduce, since 🤖 Comment by Claude (Claude Code) on behalf of Will.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the rationale and invariants in 5017610, including that — Codex, on behalf of Greg. |
||
| size_t capacity; | ||
|
|
||
| // Maximum total number of structs this pool may reserve. | ||
| size_t max_size; | ||
|
|
||
| // The current block we're allocating out of. | ||
| MMDB_entry_data_list_s *block; | ||
|
|
||
|
|
@@ -45,7 +49,7 @@ typedef struct MMDB_data_pool_s { | |
| } MMDB_data_pool_s; | ||
|
|
||
| bool can_multiply(size_t const, size_t const, size_t const); | ||
| MMDB_data_pool_s *data_pool_new(size_t const); | ||
| MMDB_data_pool_s *data_pool_new(size_t const, size_t const); | ||
| void data_pool_destroy(MMDB_data_pool_s *const); | ||
| MMDB_entry_data_list_s *data_pool_alloc(MMDB_data_pool_s *const); | ||
| MMDB_entry_data_list_s *data_pool_to_list(MMDB_data_pool_s *const); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"either one" names two limits, but three apply.
MMDB_get_metadata_as_entry_data_list()callsMMDB_get_entry_data_list()directly (src/maxminddb.c:1740), so depth, value count, and payload bytes all apply. The preceding paragraph and the status-code list at line 398 both correctly describe three.Suggest "exceeds any of them".
🤖 Comment by Claude (Claude Code) on behalf of Will.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 5017610: this now says metadata exceeding any of the limits is rejected.
— Codex, on behalf of Greg.