|
1 | | -// Required for mkstemp visibility under -D_POSIX_C_SOURCE=200112L on glibc. |
2 | | -#define _XOPEN_SOURCE 500 |
3 | | - |
4 | 1 | #include "maxminddb_test_helper.h" |
5 | 2 |
|
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 | | - |
17 | 3 | /* |
18 | 4 | * Test the off-by-one fix in MMDB_read_node: node_number >= node_count |
19 | 5 | * must return MMDB_INVALID_NODE_NUMBER_ERROR. Previously the check used |
@@ -55,114 +41,60 @@ void test_read_node_bounds(void) { |
55 | 41 | free(mmdb); |
56 | 42 | } |
57 | 43 |
|
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 | | - |
| 44 | +// Open a pre-built corruption fixture and assert that all rejection paths |
| 45 | +// fire. `lookup_ip` must traverse the corrupted record from node 0 — high |
| 46 | +// bit 0 for a corrupted left record, 1 for a corrupted right record. |
| 47 | +static void check_corrupt_record(const char *label, |
| 48 | + const char *fixture, |
| 49 | + const char *lookup_ip) { |
| 50 | + char *db_file = bad_database_path(fixture); |
132 | 51 | 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"); |
| 52 | + int status = MMDB_open(db_file, MMDB_MODE_MMAP, &mmdb); |
| 53 | + cmp_ok(status, "==", MMDB_SUCCESS, "%s: opened crafted bad MMDB", label); |
135 | 54 | if (status != MMDB_SUCCESS) { |
136 | | - unlink(temp_path); |
137 | 55 | free(db_file); |
138 | 56 | return; |
139 | 57 | } |
140 | 58 |
|
141 | 59 | MMDB_search_node_s node; |
142 | 60 | 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, |
| 61 | + cmp_ok(status, |
146 | 62 | "==", |
147 | | - MMDB_RECORD_TYPE_INVALID, |
148 | | - "MMDB_read_node marks separator record as invalid"); |
| 63 | + MMDB_CORRUPT_SEARCH_TREE_ERROR, |
| 64 | + "%s: MMDB_read_node rejects record as corrupt", |
| 65 | + label); |
149 | 66 |
|
150 | 67 | int gai_error = 0; |
151 | 68 | int mmdb_error = 0; |
152 | 69 | 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"); |
| 70 | + MMDB_lookup_string(&mmdb, lookup_ip, &gai_error, &mmdb_error); |
| 71 | + cmp_ok(gai_error, "==", 0, "%s: lookup string parse succeeds", label); |
155 | 72 | cmp_ok(mmdb_error, |
156 | 73 | "==", |
157 | 74 | 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"); |
| 75 | + "%s: MMDB_lookup_string rejects record", |
| 76 | + label); |
| 77 | + ok(!result.found_entry, "%s: lookup reports no entry", label); |
160 | 78 |
|
161 | 79 | MMDB_close(&mmdb); |
162 | | - unlink(temp_path); |
163 | 80 | free(db_file); |
164 | 81 | } |
165 | 82 |
|
| 83 | +void test_separator_record_rejected(void) { |
| 84 | + // Records in the half-open range [node_count + 1, node_count + 16) point |
| 85 | + // into the 16-byte separator between the search tree and data section |
| 86 | + // and must be rejected as corrupt. |
| 87 | + check_corrupt_record("left record, first separator byte", |
| 88 | + "libmaxminddb-separator-record-min-left.mmdb", |
| 89 | + "1.1.1.1"); |
| 90 | + check_corrupt_record("right record, first separator byte", |
| 91 | + "libmaxminddb-separator-record-min-right.mmdb", |
| 92 | + "128.0.0.0"); |
| 93 | + check_corrupt_record("left record, last separator byte", |
| 94 | + "libmaxminddb-separator-record-max-left.mmdb", |
| 95 | + "1.1.1.1"); |
| 96 | +} |
| 97 | + |
166 | 98 | int main(void) { |
167 | 99 | plan(NO_PLAN); |
168 | 100 | test_read_node_bounds(); |
|
0 commit comments