Skip to content

Commit 07ab6d6

Browse files
committed
fixup! Add regression tests for the decoder resource limits
1 parent cca8b12 commit 07ab6d6

6 files changed

Lines changed: 472 additions & 127 deletions

File tree

README.fuzzing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ $ cmake --build . -j$(nproc)
3434

3535
```shell
3636
$ mkdir -p fuzz_mmdb_seed fuzz_mmdb_seed_corpus
37-
$ find ../t/maxmind-db/test-data/ -type f -size -4k -exec cp {} ./fuzz_mmdb_seed_corpus/ \;
37+
$ find ../t/maxmind-db/test-data/ -type f -size -256k -exec cp {} ./fuzz_mmdb_seed_corpus/ \;
3838
$ ./t/fuzz_mmdb fuzz_mmdb_seed/ fuzz_mmdb_seed_corpus/
3939
```
4040

t/Makefile.am

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ CFLAGS += -I$(top_srcdir)/src
1111
noinst_LTLIBRARIES = libmmdbtest.la
1212
libmmdbtest_la_SOURCES = maxminddb_test_helper.c maxminddb_test_helper.h
1313

14-
EXTRA_DIST = compile_c++_t.pl external_symbols_t.pl mmdblookup_t.pl \
14+
EXTRA_DIST = compile_c++_t.pl decoder_limits_t.pl external_symbols_t.pl \
15+
mmdblookup_t.pl \
1516
libtap/COPYING libtap/INSTALL libtap/Makefile libtap/README.md \
1617
libtap/tap.c libtap/tap.h maxmind-db
1718

@@ -32,6 +33,7 @@ data_pool_t_SOURCES = data-pool-t.c ../src/data-pool.c
3233

3334
threads_t_CFLAGS = $(CFLAGS) -pthread
3435

35-
TESTS = $(check_PROGRAMS) compile_c++_t.pl external_symbols_t.pl mmdblookup_t.pl
36+
TESTS = $(check_PROGRAMS) compile_c++_t.pl decoder_limits_t.pl \
37+
external_symbols_t.pl mmdblookup_t.pl
3638

3739
LDADD = libmmdbtest.la libtap/libtap.a

t/data-pool-t.c

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,20 +24,31 @@ int main(void) {
2424

2525
static void test_data_pool_new(void) {
2626
{
27-
MMDB_data_pool_s *const pool = data_pool_new(0);
27+
MMDB_data_pool_s *const pool = data_pool_new(0, 512);
2828
ok(!pool, "size 0 is not valid");
2929
}
3030

3131
{
32-
MMDB_data_pool_s *const pool = data_pool_new(SIZE_MAX - 10);
32+
MMDB_data_pool_s *const pool =
33+
data_pool_new(SIZE_MAX - 10, SIZE_MAX);
3334
ok(!pool, "very large size is not valid");
3435
}
3536

3637
{
37-
MMDB_data_pool_s *const pool = data_pool_new(512);
38+
MMDB_data_pool_s *const pool = data_pool_new(512, 1024);
3839
ok(pool != NULL, "size 512 is valid");
3940
cmp_ok(pool->size, "==", 512, "size is 512");
4041
cmp_ok(pool->used, "==", 0, "used size is 0");
42+
cmp_ok(pool->capacity, "==", 512, "capacity is 512");
43+
cmp_ok(pool->max_size, "==", 1024, "maximum size is 1024");
44+
data_pool_destroy(pool);
45+
}
46+
47+
{
48+
MMDB_data_pool_s *const pool = data_pool_new(512, 10);
49+
ok(pool != NULL, "maximum smaller than initial size is valid");
50+
cmp_ok(pool->size, "==", 10, "initial size is clamped to maximum");
51+
cmp_ok(pool->capacity, "==", 10, "capacity is clamped to maximum");
4152
data_pool_destroy(pool);
4253
}
4354
}
@@ -48,15 +59,15 @@ static void test_data_pool_destroy(void) {
4859
}
4960

5061
{
51-
MMDB_data_pool_s *const pool = data_pool_new(512);
62+
MMDB_data_pool_s *const pool = data_pool_new(512, 512);
5263
ok(pool != NULL, "created pool");
5364
data_pool_destroy(pool);
5465
}
5566
}
5667

5768
static void test_data_pool_alloc(void) {
5869
{
59-
MMDB_data_pool_s *const pool = data_pool_new(1);
70+
MMDB_data_pool_s *const pool = data_pool_new(1, 3);
6071
ok(pool != NULL, "created pool");
6172
cmp_ok(pool->used, "==", 0, "used size starts at 0");
6273

@@ -75,6 +86,12 @@ static void test_data_pool_alloc(void) {
7586
cmp_ok(pool->size, "==", 2, "size is 2 (new block)");
7687
cmp_ok(pool->used, "==", 1, "used size is 1 in current block");
7788

89+
MMDB_entry_data_list_s *const entry3 = data_pool_alloc(pool);
90+
ok(entry3 != NULL, "got the final allowed entry");
91+
ok(data_pool_alloc(pool) == NULL,
92+
"allocation past maximum capacity is rejected");
93+
cmp_ok(pool->capacity, "==", 3, "capacity does not exceed maximum");
94+
7895
ok(entry1->entry_data.offset == 123,
7996
"accessing the original entry's memory is ok");
8097

@@ -83,7 +100,8 @@ static void test_data_pool_alloc(void) {
83100

84101
{
85102
size_t const initial_size = 10;
86-
MMDB_data_pool_s *const pool = data_pool_new(initial_size);
103+
MMDB_data_pool_s *const pool =
104+
data_pool_new(initial_size, initial_size * 3);
87105
ok(pool != NULL, "created pool");
88106

89107
MMDB_entry_data_list_s *entry1 = NULL;
@@ -124,12 +142,32 @@ static void test_data_pool_alloc(void) {
124142

125143
data_pool_destroy(pool);
126144
}
145+
146+
{
147+
size_t const maximum_size = 65536;
148+
MMDB_data_pool_s *const pool = data_pool_new(64, maximum_size);
149+
ok(pool != NULL, "created a decoder-sized pool");
150+
for (size_t i = 0; i < maximum_size; i++) {
151+
assert(data_pool_alloc(pool) != NULL);
152+
}
153+
cmp_ok(pool->capacity,
154+
"==",
155+
maximum_size,
156+
"final block is clamped to the remaining capacity");
157+
cmp_ok(pool->sizes[pool->index],
158+
"==",
159+
64,
160+
"the clamped final block reserves only 64 entries");
161+
ok(data_pool_alloc(pool) == NULL,
162+
"decoder-sized pool refuses a 65,537th entry");
163+
data_pool_destroy(pool);
164+
}
127165
}
128166

129167
static void test_data_pool_to_list(void) {
130168
{
131169
size_t const initial_size = 16;
132-
MMDB_data_pool_s *const pool = data_pool_new(initial_size);
170+
MMDB_data_pool_s *const pool = data_pool_new(initial_size, initial_size);
133171
ok(pool != NULL, "created pool");
134172

135173
MMDB_entry_data_list_s *const entry1 = data_pool_alloc(pool);
@@ -162,7 +200,7 @@ static void test_data_pool_to_list(void) {
162200

163201
{
164202
size_t const initial_size = 1;
165-
MMDB_data_pool_s *const pool = data_pool_new(initial_size);
203+
MMDB_data_pool_s *const pool = data_pool_new(initial_size, initial_size);
166204
ok(pool != NULL, "created pool");
167205

168206
MMDB_entry_data_list_s *const entry1 = data_pool_alloc(pool);
@@ -180,7 +218,7 @@ static void test_data_pool_to_list(void) {
180218

181219
{
182220
size_t const initial_size = 2;
183-
MMDB_data_pool_s *const pool = data_pool_new(initial_size);
221+
MMDB_data_pool_s *const pool = data_pool_new(initial_size, initial_size);
184222
ok(pool != NULL, "created pool");
185223

186224
MMDB_entry_data_list_s *const entry1 = data_pool_alloc(pool);
@@ -271,7 +309,9 @@ static void test_data_pool_to_list(void) {
271309
// this frequently.
272310
static bool create_and_check_list(size_t const initial_size,
273311
size_t const element_count) {
274-
MMDB_data_pool_s *const pool = data_pool_new(initial_size);
312+
size_t const max_size =
313+
element_count > initial_size ? element_count : initial_size;
314+
MMDB_data_pool_s *const pool = data_pool_new(initial_size, max_size);
275315
assert(pool != NULL);
276316

277317
assert(pool->used == 0);

t/decoder_limits_t.pl

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/env perl
2+
3+
use strict;
4+
use warnings;
5+
6+
use Cwd qw( abs_path );
7+
use FindBin qw( $Bin );
8+
9+
eval <<'EOF';
10+
use Test::More 0.88;
11+
use File::Temp qw( tempdir );
12+
use IPC::Run3 qw( run3 );
13+
EOF
14+
15+
if ($@) {
16+
print
17+
"1..0 # skip decoder limit override tests need Test::More 0.88, File::Temp, and IPC::Run3\n";
18+
exit 0;
19+
}
20+
21+
my $root = abs_path("$Bin/..");
22+
my $include_dir = "$root/include";
23+
my $src_dir = "$root/src";
24+
my $cc = $ENV{CC} || 'cc';
25+
my @cflags = $ENV{CFLAGS} ? ( split ' ', $ENV{CFLAGS} ) : ();
26+
my @base = (
27+
$cc,
28+
@cflags,
29+
'-std=c99',
30+
'-Wall',
31+
'-Wextra',
32+
'-Werror',
33+
'-Wno-unused-function',
34+
'-Wno-unused-parameter',
35+
'-DPACKAGE_VERSION="test"',
36+
"-I$include_dir",
37+
"-I$src_dir",
38+
);
39+
40+
for my $definition (
41+
'-DMAXIMUM_DATA_STRUCTURE_VALUES=1000000',
42+
'-DMAXIMUM_DATA_STRUCTURE_BYTES=1<<31',
43+
'-DMAXIMUM_DATA_STRUCTURE_BYTES=2*1024*1024*1024',
44+
) {
45+
my ( $status, $stderr ) = _run(
46+
@base,
47+
$definition,
48+
'-fsyntax-only',
49+
"$src_dir/maxminddb.c",
50+
);
51+
is( $status, 0, "$definition compiles without warnings" )
52+
or diag($stderr);
53+
}
54+
55+
for my $definition (
56+
'-DMAXIMUM_DATA_STRUCTURE_VALUES=0',
57+
'-DMAXIMUM_DATA_STRUCTURE_VALUES=-1',
58+
'-DMAXIMUM_DATA_STRUCTURE_VALUES=SIZE_MAX+1',
59+
'-DMAXIMUM_DATA_STRUCTURE_BYTES=0',
60+
'-DMAXIMUM_DATA_STRUCTURE_BYTES=-1',
61+
'-DMAXIMUM_DATA_STRUCTURE_BYTES=UINT64_MAX+1',
62+
) {
63+
my ( $status, $stderr ) = _run(
64+
@base,
65+
$definition,
66+
'-fsyntax-only',
67+
"$src_dir/maxminddb.c",
68+
);
69+
isnt( $status, 0, "$definition is rejected" );
70+
like( $stderr, qr/must be between 1 and/, "$definition explains its range" );
71+
}
72+
73+
my $tempdir = tempdir( CLEANUP => 1 );
74+
my $source = "$tempdir/override.c";
75+
open my $fh, '>', $source or die $!;
76+
print {$fh} <<'EOF' or die $!;
77+
#include <maxminddb.h>
78+
#include <stddef.h>
79+
80+
static int decode(const char *path, size_t expected_count) {
81+
MMDB_s mmdb;
82+
if (MMDB_open(path, MMDB_MODE_MMAP, &mmdb) != MMDB_SUCCESS) {
83+
return 1;
84+
}
85+
int gai_error, mmdb_error;
86+
MMDB_lookup_result_s result =
87+
MMDB_lookup_string(&mmdb, "1.1.1.1", &gai_error, &mmdb_error);
88+
if (gai_error != 0 || mmdb_error != MMDB_SUCCESS || !result.found_entry) {
89+
MMDB_close(&mmdb);
90+
return 2;
91+
}
92+
MMDB_entry_data_list_s *list = NULL;
93+
if (MMDB_get_entry_data_list(&result.entry, &list) != MMDB_SUCCESS) {
94+
MMDB_close(&mmdb);
95+
return 3;
96+
}
97+
size_t count = 0;
98+
for (MMDB_entry_data_list_s *node = list; node; node = node->next) {
99+
count++;
100+
}
101+
MMDB_free_entry_data_list(list);
102+
MMDB_close(&mmdb);
103+
return count == expected_count ? 0 : 4;
104+
}
105+
106+
static int reject(const char *path) {
107+
MMDB_s mmdb;
108+
if (MMDB_open(path, MMDB_MODE_MMAP, &mmdb) != MMDB_SUCCESS) {
109+
return 6;
110+
}
111+
int gai_error, mmdb_error;
112+
MMDB_lookup_result_s result =
113+
MMDB_lookup_string(&mmdb, "1.1.1.1", &gai_error, &mmdb_error);
114+
MMDB_entry_data_list_s *list = NULL;
115+
int status = MMDB_get_entry_data_list(&result.entry, &list);
116+
MMDB_free_entry_data_list(list);
117+
MMDB_close(&mmdb);
118+
return gai_error == 0 && mmdb_error == MMDB_SUCCESS &&
119+
result.found_entry && status == MMDB_DECODER_LIMIT_ERROR
120+
? 0
121+
: 7;
122+
}
123+
124+
int main(int argc, char **argv) {
125+
if (argc == 2) {
126+
return reject(argv[1]);
127+
}
128+
if (argc != 3) {
129+
return 5;
130+
}
131+
int status = decode(argv[1], 65537);
132+
return status == 0 ? decode(argv[2], 34) : status;
133+
}
134+
EOF
135+
close $fh or die $!;
136+
137+
my $executable = "$tempdir/override";
138+
my ( $compile_status, $compile_stderr ) = _run(
139+
@base,
140+
'-DMAXIMUM_DATA_STRUCTURE_VALUES=65537',
141+
'-DMAXIMUM_DATA_STRUCTURE_BYTES=2097153',
142+
"$src_dir/maxminddb.c",
143+
"$src_dir/data-pool.c",
144+
$source,
145+
'-lm',
146+
'-o',
147+
$executable,
148+
);
149+
is( $compile_status, 0, 'custom decoder limits compile and link' )
150+
or diag($compile_stderr);
151+
152+
if ( $compile_status == 0 ) {
153+
my ( $status, $stderr ) = _run(
154+
$executable,
155+
"$Bin/maxmind-db/test-data/MaxMind-DB-test-decoder-value-limit-over.mmdb",
156+
"$Bin/maxmind-db/test-data/MaxMind-DB-test-decoder-payload-limit-over.mmdb",
157+
);
158+
is( $status, 0, 'custom decoder limits take effect at runtime' )
159+
or diag($stderr);
160+
}
161+
162+
my $large_executable = "$tempdir/large-override";
163+
( $compile_status, $compile_stderr ) = _run(
164+
@base,
165+
'-DMAXIMUM_DATA_STRUCTURE_BYTES=1<<31',
166+
"$src_dir/maxminddb.c",
167+
"$src_dir/data-pool.c",
168+
$source,
169+
'-lm',
170+
'-o',
171+
$large_executable,
172+
);
173+
is( $compile_status, 0, '2 GiB expression override compiles and links' )
174+
or diag($compile_stderr);
175+
176+
if ( $compile_status == 0 ) {
177+
my ( $status, $stderr ) = _run(
178+
$large_executable,
179+
"$Bin/maxmind-db/test-data/MaxMind-DB-test-payload-amplification-dos-worst-case.mmdb",
180+
);
181+
is( $status, 0, '2 GiB expression remains an enforced runtime limit' )
182+
or diag($stderr);
183+
}
184+
185+
done_testing();
186+
187+
sub _run {
188+
my @command = @_;
189+
my ( $stdout, $stderr );
190+
run3( \@command, \undef, \$stdout, \$stderr );
191+
return ( $? >> 8, $stderr );
192+
}

0 commit comments

Comments
 (0)