Skip to content

Commit 29c907d

Browse files
committed
Add support for limiting $INCLUDE depth
1 parent a43cfc3 commit 29c907d

File tree

5 files changed

+189
-43
lines changed

5 files changed

+189
-43
lines changed

include/zone.h

+2
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ typedef struct {
312312
/** Disable $INCLUDE directive. */
313313
/** Useful in setups where untrusted input may be offered. */
314314
bool no_includes;
315+
/** Maximum $INCLUDE depth. 0 for default. */
316+
uint32_t include_limit;
315317
/** Enable 1h2m3s notations for TTLS. */
316318
bool pretty_ttls;
317319
/** Origin in wire format. */

src/generic/format.h

+12-6
Original file line numberDiff line numberDiff line change
@@ -296,12 +296,18 @@ static really_inline int32_t parse_dollar_include(
296296
}
297297

298298
// check for recursive includes
299-
do {
300-
if (strcmp(includer->path, file->path) != 0)
301-
continue;
302-
zone_close_file(parser, file);
303-
SYNTAX_ERROR(parser, "Circular include in %s", NAME(&include));
304-
} while ((includer = includer->includer));
299+
for (uint32_t depth = 1; includer; depth++, includer = includer->includer) {
300+
if (strcmp(includer->path, file->path) == 0) {
301+
zone_error(parser, "Circular include in %s", file->name);
302+
zone_close_file(parser, file);
303+
return ZONE_SEMANTIC_ERROR;
304+
}
305+
if (depth > parser->options.include_limit) {
306+
zone_error(parser, "Include %s nested too deeply", file->name);
307+
zone_close_file(parser, file);
308+
return ZONE_SEMANTIC_ERROR;
309+
}
310+
}
305311

306312
adjust_line_count(parser->file);
307313
parser->file = file;

src/zone.c

+4
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@ static int32_t initialize_parser(
392392
parser->owner = &parser->buffers.owner.blocks[0];
393393
parser->owner->length = 0;
394394
parser->rdata = &parser->buffers.rdata.blocks[0];
395+
396+
if (!parser->options.no_includes && !parser->options.include_limit)
397+
parser->options.include_limit = 10; // arbitrary, default in NSD
398+
395399
return 0;
396400
}
397401

tests/include.c

+171-10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <string.h>
1313
#include <stdlib.h>
1414
#include <cmocka.h>
15+
#include <limits.h>
1516
#if !_WIN32
1617
#include <unistd.h>
1718
#endif
@@ -125,6 +126,45 @@ int setup(void **state)
125126
return -1;
126127
}
127128

129+
static char *generate_include(const char *text)
130+
{
131+
for (int i=0; i < 100; i++) {
132+
char *path = tempnam(NULL, "zone");
133+
if (path) {
134+
FILE *handle = fopen(path, "wbx");
135+
if (handle) {
136+
int result = fputs(text, handle);
137+
fflush(handle);
138+
(void)fclose(handle);
139+
if (result != EOF)
140+
return path;
141+
}
142+
free(path);
143+
}
144+
}
145+
return NULL;
146+
}
147+
148+
static int32_t parse(
149+
const zone_options_t *options, const char *text, void *user_data)
150+
{
151+
zone_parser_t parser = { 0 };
152+
zone_name_buffer_t name;
153+
zone_rdata_buffer_t rdata;
154+
zone_buffers_t buffers = { 1, &name, &rdata };
155+
156+
int32_t code;
157+
size_t length = strlen(text);
158+
char *string = malloc(length + 1 + ZONE_PADDING_SIZE);
159+
assert_non_null(string);
160+
memcpy(string, text, length);
161+
string[length] = '\0';
162+
163+
code = zone_parse_string(&parser, options, &buffers, string, length, user_data);
164+
free(string);
165+
return code;
166+
}
167+
128168
diagnostic_pop()
129169

130170
static int32_t add_rr(
@@ -216,12 +256,12 @@ static int32_t no_such_file_accept(
216256

217257
static void no_such_file_log(
218258
zone_parser_t *parser,
219-
uint32_t category,
259+
uint32_t priority,
220260
const char *message,
221261
void *user_data)
222262
{
223263
(void)parser;
224-
(void)category;
264+
(void)priority;
225265
if (!strstr(message, "no such file"))
226266
return;
227267
no_file_test_t *test = (no_file_test_t*)user_data;
@@ -232,10 +272,6 @@ static void no_such_file_log(
232272
void the_include_that_wasnt(void **state)
233273
{
234274
// test $INCLUDE of nonexistent file is handled gracefully
235-
zone_parser_t parser = { 0 };
236-
zone_name_buffer_t name;
237-
zone_rdata_buffer_t rdata;
238-
zone_buffers_t buffers = { 1, &name, &rdata };
239275
zone_options_t options = { 0 };
240276
no_file_test_t test = { 0 };
241277
int32_t code;
@@ -245,7 +281,7 @@ void the_include_that_wasnt(void **state)
245281
options.origin.octets = origin;
246282
options.origin.length = sizeof(origin);
247283
options.default_ttl = 3600;
248-
options.default_class = ZONE_IN;
284+
options.default_class = 1;
249285

250286
(void)state;
251287

@@ -256,16 +292,141 @@ void the_include_that_wasnt(void **state)
256292
int length = snprintf(buffer, sizeof(buffer), "$INCLUDE %s", non_include);
257293
assert_true(length >= 0 && (size_t)length < SIZE_MAX - (ZONE_PADDING_SIZE + 1));
258294

259-
char *include = malloc((size_t)length + 1 + ZONE_PADDING_SIZE);
295+
char *include = malloc((size_t)length + 1);
260296
assert_non_null(include);
261297
(void)snprintf(include, (size_t)length + 1, "$INCLUDE %s", non_include);
262298

263-
code = zone_parse_string(&parser, &options, &buffers, include, (size_t)length, &test);
299+
code = parse(&options, include, &test);
300+
free(include);
301+
free(non_include);
264302
assert_int_equal(code, ZONE_NOT_A_FILE);
265303
assert_true(test.log_count == 1);
266304
assert_true(test.accept_count == 0);
305+
}
306+
307+
static int32_t in_too_deep_accept(
308+
zone_parser_t *parser,
309+
const zone_name_t *owner,
310+
uint16_t type,
311+
uint16_t class,
312+
uint32_t ttl,
313+
uint16_t rdlength,
314+
const uint8_t *rdata,
315+
void *user_data)
316+
{
317+
(void)parser;
318+
(void)owner;
319+
(void)type;
320+
(void)class;
321+
(void)ttl;
322+
(void)rdlength;
323+
(void)rdata;
324+
(*(size_t *)user_data)++;
325+
return 0;
326+
}
327+
328+
static void in_too_deep_log(
329+
zone_parser_t *parser,
330+
uint32_t priority,
331+
const char *message,
332+
void *user_data)
333+
{
334+
(void)parser;
335+
(void)priority;
336+
337+
if (strstr(message, "nested too deeply"))
338+
*(size_t *)user_data |= 1u << 7;
339+
}
340+
341+
/*!cmocka */
342+
void in_too_deep(void **state)
343+
{
344+
(void)state;
345+
346+
int32_t code;
347+
size_t records;
348+
zone_options_t options = { 0 };
349+
350+
options.accept.callback = &in_too_deep_accept;
351+
options.log.callback = &in_too_deep_log;
352+
options.origin.octets = origin;
353+
options.origin.length = sizeof(origin);
354+
options.default_ttl = 3600;
355+
options.default_class = 1;
356+
options.include_limit = 1;
357+
358+
#define INCLUDE "$INCLUDE %s\n"
359+
360+
char *deeper = generate_include("foo. TXT \"bar\"");
361+
assert_non_null(deeper);
362+
char buffer[16];
363+
int length = snprintf(buffer, sizeof(buffer), INCLUDE, deeper);
364+
assert_true(length > 0);
365+
char *inception = malloc((size_t)length + 1);
366+
assert_non_null(inception);
367+
(void)snprintf(inception, (size_t)length + 1, INCLUDE, deeper);
368+
char *deep = generate_include(inception);
369+
assert_non_null(deep);
370+
free(inception);
371+
length = snprintf(buffer, sizeof(buffer), INCLUDE, deep);
372+
assert_true(length > 0);
373+
inception = malloc((size_t)length + 1);
374+
(void)snprintf(inception, (size_t)length + 1, INCLUDE, deep);
375+
376+
#undef INCLUDE
377+
378+
fprintf(stderr, "INPUT: %s\n", inception);
379+
380+
records = 0;
381+
code = parse(&options, inception, &records);
382+
assert_int_equal(code, ZONE_SEMANTIC_ERROR);
383+
assert_int_equal(records, (1u << 7));
384+
385+
options.include_limit = 0;
386+
records = 0;
387+
code = parse(&options, inception, &records);
388+
assert_int_equal(code, ZONE_SUCCESS);
389+
assert_int_equal(records, 1u);
390+
391+
free(inception);
392+
free(deep);
393+
free(deeper);
394+
}
395+
396+
/*!cmocka */
397+
void been_there_done_that(void **state)
398+
{
399+
(void)state;
400+
401+
zone_options_t options = { 0 };
402+
options.accept.callback = &in_too_deep_accept;
403+
options.log.callback = &in_too_deep_log;
404+
options.origin.octets = origin;
405+
options.origin.length = sizeof(origin);
406+
options.default_ttl = 3600;
407+
options.default_class = 1;
408+
options.include_limit = 1;
409+
410+
int32_t code;
411+
size_t count = 0;
412+
413+
char *path = generate_include(" ");
414+
assert_non_null(path);
415+
FILE* handle = fopen(path, "wb");
416+
assert_non_null(handle);
417+
char dummy[16];
418+
int length = snprintf(dummy, sizeof(dummy), "$INCLUDE \"%s\"\n", path);
419+
assert_true(length > 0 && length < INT_MAX - ZONE_PADDING_SIZE);
420+
char *include = malloc((size_t)length + 1 + ZONE_PADDING_SIZE);
421+
assert_non_null(include);
422+
(void)snprintf(include, (size_t)length + 1, "$INCLUDE \"%s\"\n", path);
423+
int result = fputs(include, handle);
424+
assert_true(result >= 0);
425+
(void)fclose(handle);
426+
free(path);
427+
code = parse(&options, include, &count);
267428
free(include);
268-
free(non_include);
429+
assert_int_equal(code, ZONE_SEMANTIC_ERROR);
269430
}
270431

271432
//

tests/syntax.c

-27
Original file line numberDiff line numberDiff line change
@@ -634,33 +634,6 @@ void no_famous_last_words(void **state)
634634
assert_true(count == 0);
635635
}
636636

637-
/*!cmocka */
638-
void been_there_done_that(void **state)
639-
{
640-
(void)state;
641-
642-
int32_t code;
643-
size_t count = 0;
644-
645-
char *path = generate_include(" ");
646-
assert_non_null(path);
647-
FILE* handle = fopen(path, "wb");
648-
assert_non_null(handle);
649-
char dummy[16];
650-
int length = snprintf(dummy, sizeof(dummy), "$INCLUDE \"%s\"\n", path);
651-
assert_true(length > 0 && length < INT_MAX - ZONE_PADDING_SIZE);
652-
char *include = malloc((size_t)length + 1 + ZONE_PADDING_SIZE);
653-
assert_non_null(include);
654-
(void)snprintf(include, (size_t)length + 1, "$INCLUDE \"%s\"\n", path);
655-
int result = fputs(include, handle);
656-
assert_true(result >= 0);
657-
(void)fclose(handle);
658-
free(path);
659-
code = parse(include, &count);
660-
free(include);
661-
assert_int_equal(code, ZONE_SYNTAX_ERROR);
662-
}
663-
664637
/*!cmocka */
665638
void bad_a_rrs(void **state)
666639
{

0 commit comments

Comments
 (0)