-
Notifications
You must be signed in to change notification settings - Fork 246
Harden libmaxminddb against crafted/malformed databases #416
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
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
89ffaca
Fix off-by-one in MMDB_read_node() bounds check
oschwald f296429
Add recursion depth limit to skip_map_or_array()
oschwald 0128127
Fix alloca off-by-one in mmdblookup on Windows
oschwald 467b085
Fix MMDB_lookup_string leaving *mmdb_error uninitialized on GAI failure
oschwald ffa29c1
Fix print_indentation stack overflow on negative indent
oschwald 6b2a64b
NULL file_content after munmap to prevent double-unmap
oschwald 233e312
Fix gmtime NULL dereference in mmdblookup dump_meta
oschwald 13cb45b
Fix integer overflow in find_address_in_search_tree bounds check
oschwald c3bccc0
Fix printf format specifiers in mmdblookup dump_meta
oschwald 4f418a9
Fix integer overflow in MMDB_read_node/find_ipv4_start_node pointer a…
oschwald c185bb8
Use GetFileSizeEx for Windows file size
oschwald c1d353a
Validate array/map size in get_entry_data_list against remaining data
oschwald dbb6558
Extract shared MMDB test writer helpers into mmdb_test_writer.h
oschwald 04bf5ef
Add new test programs to t/CMakeLists.txt
oschwald ebdb1ec
NULL data_section and metadata_section after munmap
oschwald cc91dc6
Add record_pointer bounds check to MMDB_read_node
oschwald db24787
Add fwrite error checking in test files
oschwald 1e3a552
Fix mixed tab/space indentation in Makefile.am
oschwald 91e17e2
Clarify mmdb_error behavior on GAI failure in docs
oschwald e1635ba
Extend bad_databases_t to test data extraction after successful lookup
oschwald a99acaf
Update MaxMind-DB submodule with new bad-data files
oschwald d4841c5
Remove mmdb_test_writer.h; rewrite tests to use submodule bad-data files
oschwald 51764c8
Zero data/metadata section sizes in MMDB_close
oschwald 0e470e1
Improve gmtime fallback text to "out of range"
oschwald d3fff39
Add test for MMDB_read_node off-by-one bounds check
oschwald 88af1d6
Add comment for array size validation
oschwald 5b71ac4
Add deep nested array test to max_depth_t
oschwald File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -178,7 +178,8 @@ static int lookup_path_in_map(const char *path_elem, | |
| const MMDB_s *const mmdb, | ||
| MMDB_entry_data_s *entry_data); | ||
| static int skip_map_or_array(const MMDB_s *const mmdb, | ||
| MMDB_entry_data_s *entry_data); | ||
| MMDB_entry_data_s *entry_data, | ||
| int depth); | ||
oschwald marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| static int decode_one_follow(const MMDB_s *const mmdb, | ||
| uint32_t offset, | ||
| MMDB_entry_data_s *entry_data); | ||
|
|
@@ -366,7 +367,7 @@ static LPWSTR utf8_to_utf16(const char *utf8_str) { | |
| } | ||
|
|
||
| static int map_file(MMDB_s *const mmdb) { | ||
| DWORD size; | ||
| ssize_t size; | ||
| int status = MMDB_SUCCESS; | ||
| HANDLE mmh = NULL; | ||
| HANDLE fd = INVALID_HANDLE_VALUE; | ||
|
|
@@ -386,12 +387,17 @@ static int map_file(MMDB_s *const mmdb) { | |
| status = MMDB_FILE_OPEN_ERROR; | ||
| goto cleanup; | ||
| } | ||
| size = GetFileSize(fd, NULL); | ||
| if (size == INVALID_FILE_SIZE) { | ||
| status = MMDB_FILE_OPEN_ERROR; | ||
| LARGE_INTEGER file_size; | ||
| if (!GetFileSizeEx(fd, &file_size)) { | ||
| status = MMDB_IO_ERROR; | ||
| goto cleanup; | ||
| } | ||
| if (file_size.QuadPart < 0 || file_size.QuadPart > SSIZE_MAX) { | ||
| status = MMDB_IO_ERROR; | ||
| goto cleanup; | ||
| } | ||
| mmh = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, size, NULL); | ||
| size = (ssize_t)file_size.QuadPart; | ||
| mmh = CreateFileMapping(fd, NULL, PAGE_READONLY, 0, 0, NULL); | ||
| /* Microsoft documentation for CreateFileMapping indicates this returns | ||
| NULL not INVALID_HANDLE_VALUE on error */ | ||
| if (NULL == mmh) { | ||
|
|
@@ -882,6 +888,11 @@ MMDB_lookup_result_s MMDB_lookup_string(const MMDB_s *const mmdb, | |
|
|
||
| if (!*gai_error) { | ||
| result = MMDB_lookup_sockaddr(mmdb, addresses->ai_addr, mmdb_error); | ||
| } else { | ||
| /* No MMDB error occurred; the GAI failure is reported via | ||
| * *gai_error. Set *mmdb_error to a defined value so callers | ||
| * don't read indeterminate memory. */ | ||
| *mmdb_error = MMDB_SUCCESS; | ||
horgh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| if (NULL != addresses) { | ||
|
|
@@ -979,7 +990,7 @@ static int find_address_in_search_tree(const MMDB_s *const mmdb, | |
|
|
||
| result->netmask = current_bit; | ||
|
|
||
| if (value >= node_count + mmdb->data_section_size) { | ||
| if (value >= (uint64_t)node_count + mmdb->data_section_size) { | ||
horgh marked this conversation as resolved.
Show resolved
Hide resolved
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. Not sure if we want to change anything, but it has a minor point. |
||
| // The pointer points off the end of the database. | ||
| return MMDB_CORRUPT_SEARCH_TREE_ERROR; | ||
| } | ||
|
|
@@ -1039,7 +1050,8 @@ static int find_ipv4_start_node(MMDB_s *const mmdb) { | |
| uint32_t node_count = mmdb->metadata.node_count; | ||
|
|
||
| for (netmask = 0; netmask < 96 && node_value < node_count; netmask++) { | ||
| record_pointer = &search_tree[node_value * record_info.record_length]; | ||
| record_pointer = | ||
| &search_tree[(uint64_t)node_value * record_info.record_length]; | ||
| if (record_pointer + record_info.record_length > mmdb->data_section) { | ||
| return MMDB_CORRUPT_SEARCH_TREE_ERROR; | ||
| } | ||
|
|
@@ -1097,13 +1109,16 @@ int MMDB_read_node(const MMDB_s *const mmdb, | |
| return MMDB_UNKNOWN_DATABASE_FORMAT_ERROR; | ||
| } | ||
|
|
||
| if (node_number > mmdb->metadata.node_count) { | ||
| if (node_number >= mmdb->metadata.node_count) { | ||
| return MMDB_INVALID_NODE_NUMBER_ERROR; | ||
| } | ||
|
|
||
| const uint8_t *search_tree = mmdb->file_content; | ||
| const uint8_t *record_pointer = | ||
| &search_tree[node_number * record_info.record_length]; | ||
| &search_tree[(uint64_t)node_number * record_info.record_length]; | ||
horgh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (record_pointer + record_info.record_length > mmdb->data_section) { | ||
| return MMDB_CORRUPT_SEARCH_TREE_ERROR; | ||
| } | ||
| node->left_record = record_info.left_record_getter(record_pointer); | ||
| record_pointer += record_info.right_record_offset; | ||
| node->right_record = record_info.right_record_getter(record_pointer); | ||
|
|
@@ -1272,7 +1287,7 @@ static int lookup_path_in_array(const char *path_elem, | |
| /* We don't want to follow a pointer here. If the next element is a | ||
| * pointer we simply skip it and keep going */ | ||
| CHECKED_DECODE_ONE(mmdb, entry_data->offset_to_next, entry_data); | ||
| int status = skip_map_or_array(mmdb, entry_data); | ||
| int status = skip_map_or_array(mmdb, entry_data, 0); | ||
| if (MMDB_SUCCESS != status) { | ||
| return status; | ||
| } | ||
|
|
@@ -1314,7 +1329,7 @@ static int lookup_path_in_map(const char *path_elem, | |
| /* We don't want to follow a pointer here. If the next element is | ||
| * a pointer we simply skip it and keep going */ | ||
| CHECKED_DECODE_ONE(mmdb, offset_to_value, &value); | ||
| int status = skip_map_or_array(mmdb, &value); | ||
| int status = skip_map_or_array(mmdb, &value, 0); | ||
| if (MMDB_SUCCESS != status) { | ||
| return status; | ||
| } | ||
|
|
@@ -1327,15 +1342,21 @@ static int lookup_path_in_map(const char *path_elem, | |
| } | ||
|
|
||
| static int skip_map_or_array(const MMDB_s *const mmdb, | ||
| MMDB_entry_data_s *entry_data) { | ||
| MMDB_entry_data_s *entry_data, | ||
| int depth) { | ||
| if (depth >= MAXIMUM_DATA_STRUCTURE_DEPTH) { | ||
| DEBUG_MSG("reached the maximum data structure depth"); | ||
| return MMDB_INVALID_DATA_ERROR; | ||
| } | ||
|
|
||
| if (entry_data->type == MMDB_DATA_TYPE_MAP) { | ||
| uint32_t size = entry_data->data_size; | ||
| while (size-- > 0) { | ||
| CHECKED_DECODE_ONE( | ||
| mmdb, entry_data->offset_to_next, entry_data); // key | ||
| CHECKED_DECODE_ONE( | ||
| mmdb, entry_data->offset_to_next, entry_data); // value | ||
| int status = skip_map_or_array(mmdb, entry_data); | ||
| int status = skip_map_or_array(mmdb, entry_data, depth + 1); | ||
| if (MMDB_SUCCESS != status) { | ||
| return status; | ||
| } | ||
|
|
@@ -1345,7 +1366,7 @@ static int skip_map_or_array(const MMDB_s *const mmdb, | |
| while (size-- > 0) { | ||
| CHECKED_DECODE_ONE( | ||
| mmdb, entry_data->offset_to_next, entry_data); // value | ||
| int status = skip_map_or_array(mmdb, entry_data); | ||
| int status = skip_map_or_array(mmdb, entry_data, depth + 1); | ||
| if (MMDB_SUCCESS != status) { | ||
| return status; | ||
| } | ||
|
|
@@ -1707,6 +1728,12 @@ static int get_entry_data_list(const MMDB_s *const mmdb, | |
| case MMDB_DATA_TYPE_ARRAY: { | ||
| uint32_t array_size = entry_data_list->entry_data.data_size; | ||
| uint32_t array_offset = entry_data_list->entry_data.offset_to_next; | ||
| /* Each array element needs at least 1 byte. */ | ||
| if (array_offset >= mmdb->data_section_size || | ||
| array_size > mmdb->data_section_size - array_offset) { | ||
| DEBUG_MSG("array size exceeds remaining data section"); | ||
| return MMDB_INVALID_DATA_ERROR; | ||
| } | ||
| while (array_size-- > 0) { | ||
| MMDB_entry_data_list_s *entry_data_list_to = | ||
| data_pool_alloc(pool); | ||
|
|
@@ -1730,6 +1757,12 @@ static int get_entry_data_list(const MMDB_s *const mmdb, | |
| uint32_t size = entry_data_list->entry_data.data_size; | ||
|
|
||
| offset = entry_data_list->entry_data.offset_to_next; | ||
| /* Each map entry needs at least a key and a value (1 byte each). */ | ||
| if (offset >= mmdb->data_section_size || | ||
| size > (mmdb->data_section_size - offset) / 2) { | ||
| DEBUG_MSG("map size exceeds remaining data section"); | ||
| return MMDB_INVALID_DATA_ERROR; | ||
| } | ||
| while (size-- > 0) { | ||
| MMDB_entry_data_list_s *list_key = data_pool_alloc(pool); | ||
| if (!list_key) { | ||
|
|
@@ -1894,6 +1927,12 @@ static void free_mmdb_struct(MMDB_s *const mmdb) { | |
| #pragma clang diagnostic pop | ||
| #endif | ||
| #endif | ||
| mmdb->file_content = NULL; | ||
| mmdb->file_size = 0; | ||
horgh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| mmdb->data_section = NULL; | ||
| mmdb->data_section_size = 0; | ||
| mmdb->metadata_section = NULL; | ||
| mmdb->metadata_section_size = 0; | ||
| } | ||
|
|
||
| if (NULL != mmdb->metadata.database_type) { | ||
|
|
@@ -1931,6 +1970,7 @@ static void free_languages_metadata(MMDB_s *mmdb) { | |
| #endif | ||
| } | ||
| FREE_AND_SET_NULL(mmdb->metadata.languages.names); | ||
| mmdb->metadata.languages.count = 0; | ||
| } | ||
|
|
||
| static void free_descriptions_metadata(MMDB_s *mmdb) { | ||
|
|
@@ -1973,6 +2013,7 @@ static void free_descriptions_metadata(MMDB_s *mmdb) { | |
| } | ||
|
|
||
| FREE_AND_SET_NULL(mmdb->metadata.description.descriptions); | ||
| mmdb->metadata.description.count = 0; | ||
| } | ||
|
|
||
| const char *MMDB_lib_version(void) { return PACKAGE_VERSION; } | ||
|
|
@@ -2158,7 +2199,7 @@ dump_entry_data_list(FILE *stream, | |
|
|
||
| static void print_indentation(FILE *stream, int i) { | ||
| char buffer[1024]; | ||
| int size = i >= 1024 ? 1023 : i; | ||
| int size = i < 0 ? 0 : (i >= 1024 ? 1023 : i); | ||
| memset(buffer, 32, (size_t)size); | ||
| buffer[size] = '\0'; | ||
| fputs(buffer, stream); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.