maxmind/libmaxminddb

Out-of-bounds write caused by incorrect error handling of calloc in get_options (mmdblookup.c:298)

cve-reporting opened this issue · 0 comments

mmdblookup tool is vulnerable to OOB write attack via execution with maliciously crafted parameter list.

Incorrect handling of the value returned by calloc in get_options may lead to:

  • out-of-bound write attempt and segmentation fault error in case of restrictive memory protection,
  • near NULL out-of-bound overwrite in case of limited memory restrictions (e.g. in embedded environments).

Memory allocation is performed to handle input parameters, which are controlled by the attacker.
Number of input parameters is limited by an operating system restrictions - e.g. in Linux by ARG_MAX:
https://unix.stackexchange.com/questions/120642/what-defines-the-maximum-size-for-a-command-single-argument
mmdblookup allocates nr_params * sizeof(const char * ) bytes - e.g. on 64-bit Linux system following command allocated 1.6 MB block:

./bin/mmdblookup --file x --ip 1 $(python3 -c "print(209000*'0 ')")

If the allocation be unsuccessful (which may happen on systems with limited resources or strict limits) and calloc returns NULL, mmdblookup will fill the memory block starting from 0 with addresses of input parameters (argv).

Vulnerable code (mmdblookup.c):

297:    const char **lookup_path =
298:        calloc((argc - optind) + 1, sizeof(const char *));
299:    int i;
300:    for (i = 0; i < argc - optind; i++) {
301:        lookup_path[i] = argv[i + optind];
302:        (*lookup_path_length)++;
303:    }
304:    lookup_path[i] = NULL;

See following recommendations for details (especially the calloc example):
https://wiki.sei.cmu.edu/confluence/display/c/ERR33-C.+Detect+and+handle+standard+library+errors

The issue can be reproduced and tested using ErrorSanitizer (https://gitlab.com/ErrorSanitizer/ErrorSanitizer).

Reproduction steps:

  1. Install gdb

  2. Download and unpack code of ErrorSanitizer (https://gitlab.com/ErrorSanitizer/ErrorSanitizer)

  3. Perform compilation of ErrorSanitizer according to the manual (https://gitlab.com/ErrorSanitizer/ErrorSanitizer#compilation)

    cd ErrorSanitizer; make

  4. Set ESAN to the path of ErrorSanitizer directory

    export ESAN=/opt/...

  5. Download and unzip attached map temp_0.cur_input
    temp_0.cur_input.zip

  6. Run mmdblookup with ErrorSanitizer in gdb using:

    gdb -batch -ex='run' -ex='backtrace' --args env LD_PRELOAD="$ESAN/error_sanitizer_preload.so" ./bin/.libs/mmdblookup --file tmp --ip 0 temp_0.cur_input

You should receive similar output:

process 31222 is executing new program: libmaxminddb/bin/.libs/mmdblookup
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Program received signal SIGSEGV, Segmentation fault.
0x0000555555555414 in get_options (ip_file=<synthetic pointer>, thread_count=<synthetic pointer>, lookup_path_length=<synthetic pointer>, iterations=<synthetic pointer>, verbose=<synthetic pointer>, ip_address=<synthetic pointer>, mmdb_file=<synthetic pointer>, argv=0x7fffffffdae8, argc=<optimized out>) at mmdblookup.c:301
301	        lookup_path[i] = argv[i + optind];
#0  0x0000555555555414 in get_options (ip_file=<synthetic pointer>, thread_count=<synthetic pointer>, lookup_path_length=<synthetic pointer>, iterations=<synthetic pointer>, verbose=<synthetic pointer>, ip_address=<synthetic pointer>, mmdb_file=<synthetic pointer>, argv=0x7fffffffdae8, argc=<optimized out>) at mmdblookup.c:301
#1  main (argc=<optimized out>, argv=0x7fffffffdae8) at mmdblookup.c:103
#0  0x0000555555555414 in get_options (ip_file=<synthetic pointer>, thread_count=<synthetic pointer>, lookup_path_length=<synthetic pointer>, iterations=<synthetic pointer>, verbose=<synthetic pointer>, ip_address=<synthetic pointer>, mmdb_file=<synthetic pointer>, argv=0x7fffffffdae8, argc=<optimized out>) at mmdblookup.c:301
	program = <optimized out>
	lookup_path = 0x0
	i = <optimized out>
	help = 0
	version = 0
	program = <optimized out>
	lookup_path = <optimized out>
	i = <optimized out>
	options = {{name = 0x5555555565fe "file", has_arg = 1, flag = 0x0, val = 102}, {name = 0x5555555565d6 "ip", has_arg = 1, flag = 0x0, val = 105}, {name = 0x5555555565d9 "verbose", has_arg = 0, flag = 0x0, val = 118}, {name = 0x5555555565e1 "version", has_arg = 0, flag = 0x0, val = 110}, {name = 0x5555555565e9 "benchmark", has_arg = 1, flag = 0x0, val = 98}, {name = 0x5555555565f3 "threads", has_arg = 1, flag = 0x0, val = 116}, {name = 0x5555555565fb "ip-file", has_arg = 1, flag = 0x0, val = 73}, {name = 0x555555556603 "help", has_arg = 0, flag = 0x0, val = 104}, {name = 0x5555555564d8 "?", has_arg = 0, flag = 0x0, val = 1}, {name = 0x0, has_arg = 0, flag = 0x0, val = 0}}
	opt_index = <optimized out>
	optstring = <optimized out>
	opt_char = <optimized out>
#1  main (argc=<optimized out>, argv=0x7fffffffdae8) at mmdblookup.c:103
	mmdb_file = 0x7fffffffdf0f "tmp"
	ip_address = 0x7fffffffdf18 "0"
	verbose = 0
	iterations = 0
	lookup_path_length = <optimized out>
	thread_count = 0
	ip_file = 0x0
	lookup_path = <optimized out>
	mmdb = {flags = 25638685, filename = 0x7ffff7bacae6 "esan_enable_map_based_failure", file_size = 140737349601192, file_content = 0x7ffff7bac1dc "\253\064\306KG\261\202>\222~\307֕\364ec", data_section = 0x7ffff7bacbd4 "strtoul", data_section_size = 0, metadata_section = 0x7fffffffd5d0 "\250Ǻ\367\377\177", metadata_section_size = 3, full_record_byte_size = 32767, depth = 0, ipv4_start_node = {netmask = 54720, node_value = 32767}, metadata = {node_count = 0, record_size = 0, ip_version = 0, database_type = 0x7ffff7ff7a40 "p\341\377\367\377\177", languages = {count = 0, names = 0x7}, binary_format_major_version = 1, binary_format_minor_version = 0, build_epoch = 140737341165800, description = {count = 1640875860, descriptions = 0x7ffff7ff6358}}}