Skip to content

Commit feef4fd

Browse files
committed
Reject search tree records in separator
1 parent 93d2b6c commit feef4fd

3 files changed

Lines changed: 137 additions & 4 deletions

File tree

Changes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
`\xAB\xCD\xEFMaxMind.com` marker. Such files are now rejected as invalid
99
metadata instead of allowing a zero-length metadata section to reach the
1010
decoder.
11+
- Fixed search-tree validation for records that point into the 16-byte separator
12+
before the data section. These records are now rejected as corrupt instead of
13+
being exposed as apparent data entries with underflowed offsets.
1114

1215
## 1.13.3 - 2026-03-05
1316

src/maxminddb.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,12 +1000,12 @@ static int find_address_in_search_tree(const MMDB_s *const mmdb,
10001000

10011001
result->netmask = current_bit;
10021002

1003-
if (value >= (uint64_t)node_count + mmdb->data_section_size) {
1004-
// The pointer points off the end of the database.
1003+
uint8_t type = record_type(mmdb, value);
1004+
if (type == MMDB_RECORD_TYPE_INVALID) {
10051005
return MMDB_CORRUPT_SEARCH_TREE_ERROR;
10061006
}
10071007

1008-
if (value == node_count) {
1008+
if (type == MMDB_RECORD_TYPE_EMPTY) {
10091009
// record is empty
10101010
result->found_entry = false;
10111011
return MMDB_SUCCESS;
@@ -1093,7 +1093,14 @@ static uint8_t record_type(const MMDB_s *const mmdb, uint64_t record) {
10931093
return MMDB_RECORD_TYPE_EMPTY;
10941094
}
10951095

1096-
if (record - node_count < mmdb->data_section_size) {
1096+
uint64_t data_offset = record - node_count;
1097+
if (data_offset < MMDB_DATA_SECTION_SEPARATOR) {
1098+
DEBUG_MSG("record points into the data section separator");
1099+
return MMDB_RECORD_TYPE_INVALID;
1100+
}
1101+
1102+
data_offset -= MMDB_DATA_SECTION_SEPARATOR;
1103+
if (data_offset < mmdb->data_section_size) {
10971104
return MMDB_RECORD_TYPE_DATA;
10981105
}
10991106

t/bad_search_tree_t.c

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1+
// Required for mkstemp visibility under -D_POSIX_C_SOURCE=200112L on glibc.
2+
#define _XOPEN_SOURCE 500
3+
14
#include "maxminddb_test_helper.h"
25

6+
#include <errno.h>
7+
#include <string.h>
8+
9+
#ifdef _WIN32
10+
#include <io.h>
11+
#include <stdio.h>
12+
#define unlink _unlink
13+
#else
14+
#include <unistd.h>
15+
#endif
16+
317
/*
418
* Test the off-by-one fix in MMDB_read_node: node_number >= node_count
519
* must return MMDB_INVALID_NODE_NUMBER_ERROR. Previously the check used
@@ -41,8 +55,117 @@ void test_read_node_bounds(void) {
4155
free(mmdb);
4256
}
4357

58+
static FILE *open_temp_file(char *path, size_t path_size) {
59+
#ifdef _WIN32
60+
errno_t err = tmpnam_s(path, path_size);
61+
if (err != 0) {
62+
return NULL;
63+
}
64+
return fopen(path, "wb");
65+
#else
66+
snprintf(path, path_size, "./bad-search-tree-XXXXXX");
67+
int fd = mkstemp(path);
68+
if (fd < 0) {
69+
return NULL;
70+
}
71+
return fdopen(fd, "wb");
72+
#endif
73+
}
74+
75+
static int copy_file(FILE *dest, const char *source_path) {
76+
FILE *source = fopen(source_path, "rb");
77+
if (!source) {
78+
return -1;
79+
}
80+
81+
unsigned char buffer[4096];
82+
size_t bytes_read;
83+
while ((bytes_read = fread(buffer, 1, sizeof(buffer), source)) > 0) {
84+
if (fwrite(buffer, 1, bytes_read, dest) != bytes_read) {
85+
fclose(source);
86+
return -1;
87+
}
88+
}
89+
90+
int result = ferror(source) ? -1 : 0;
91+
fclose(source);
92+
return result;
93+
}
94+
95+
void test_separator_record_rejected(void) {
96+
char *db_file = test_database_path("MaxMind-DB-test-ipv4-24.mmdb");
97+
char temp_path[64];
98+
FILE *temp = open_temp_file(temp_path, sizeof(temp_path));
99+
if (!temp) {
100+
free(db_file);
101+
BAIL_OUT("could not create temp file: %s", strerror(errno));
102+
}
103+
104+
if (copy_file(temp, db_file) != 0) {
105+
fclose(temp);
106+
unlink(temp_path);
107+
free(db_file);
108+
BAIL_OUT("could not copy test database: %s", strerror(errno));
109+
}
110+
111+
// Overwrite node 0's left record with node_count + 1, which points into
112+
// the 16-byte separator between the search tree and data section.
113+
if (fseek(temp, 0, SEEK_SET) != 0) {
114+
fclose(temp);
115+
unlink(temp_path);
116+
free(db_file);
117+
BAIL_OUT("fseek failed: %s", strerror(errno));
118+
}
119+
unsigned char bad_record[3] = {0x00, 0x00, 0xA4};
120+
if (fwrite(bad_record, 1, sizeof(bad_record), temp) != sizeof(bad_record)) {
121+
fclose(temp);
122+
unlink(temp_path);
123+
free(db_file);
124+
BAIL_OUT("fwrite failed: %s", strerror(errno));
125+
}
126+
if (fclose(temp) != 0) {
127+
unlink(temp_path);
128+
free(db_file);
129+
BAIL_OUT("fclose failed: %s", strerror(errno));
130+
}
131+
132+
MMDB_s mmdb;
133+
int status = MMDB_open(temp_path, MMDB_MODE_MMAP, &mmdb);
134+
cmp_ok(status, "==", MMDB_SUCCESS, "opened crafted bad search tree MMDB");
135+
if (status != MMDB_SUCCESS) {
136+
unlink(temp_path);
137+
free(db_file);
138+
return;
139+
}
140+
141+
MMDB_search_node_s node;
142+
status = MMDB_read_node(&mmdb, 0, &node);
143+
cmp_ok(
144+
status, "==", MMDB_SUCCESS, "MMDB_read_node succeeds for crafted node");
145+
cmp_ok(node.left_record_type,
146+
"==",
147+
MMDB_RECORD_TYPE_INVALID,
148+
"MMDB_read_node marks separator record as invalid");
149+
150+
int gai_error = 0;
151+
int mmdb_error = 0;
152+
MMDB_lookup_result_s result =
153+
MMDB_lookup_string(&mmdb, "1.1.1.1", &gai_error, &mmdb_error);
154+
cmp_ok(gai_error, "==", 0, "lookup string parse succeeds");
155+
cmp_ok(mmdb_error,
156+
"==",
157+
MMDB_CORRUPT_SEARCH_TREE_ERROR,
158+
"MMDB_lookup_string rejects records pointing into the separator");
159+
ok(!result.found_entry, "lookup does not report an entry for corrupt tree");
160+
161+
MMDB_close(&mmdb);
162+
unlink(temp_path);
163+
free(db_file);
164+
}
165+
44166
int main(void) {
45167
plan(NO_PLAN);
46168
test_read_node_bounds();
169+
test_separator_record_rejected();
47170
done_testing();
48171
}

0 commit comments

Comments
 (0)