|
| 1 | +// Required for mkstemp visibility under -D_POSIX_C_SOURCE=200112L on glibc. |
| 2 | +#define _XOPEN_SOURCE 500 |
| 3 | + |
1 | 4 | #include "maxminddb_test_helper.h" |
2 | 5 |
|
| 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 | + |
3 | 17 | /* |
4 | 18 | * Test the off-by-one fix in MMDB_read_node: node_number >= node_count |
5 | 19 | * must return MMDB_INVALID_NODE_NUMBER_ERROR. Previously the check used |
@@ -41,8 +55,117 @@ void test_read_node_bounds(void) { |
41 | 55 | free(mmdb); |
42 | 56 | } |
43 | 57 |
|
| 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 | + |
44 | 166 | int main(void) { |
45 | 167 | plan(NO_PLAN); |
46 | 168 | test_read_node_bounds(); |
| 169 | + test_separator_record_rejected(); |
47 | 170 | done_testing(); |
48 | 171 | } |
0 commit comments