Skip to content

Commit 3139eb3

Browse files
committed
Add support for limiting $INCLUDE depth
1 parent a43cfc3 commit 3139eb3

File tree

5 files changed

+187
-43
lines changed

5 files changed

+187
-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

+169-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,43 @@ int setup(void **state)
125126
return -1;
126127
}
127128

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

130168
static int32_t add_rr(
@@ -216,12 +254,12 @@ static int32_t no_such_file_accept(
216254

217255
static void no_such_file_log(
218256
zone_parser_t *parser,
219-
uint32_t category,
257+
uint32_t priority,
220258
const char *message,
221259
void *user_data)
222260
{
223261
(void)parser;
224-
(void)category;
262+
(void)priority;
225263
if (!strstr(message, "no such file"))
226264
return;
227265
no_file_test_t *test = (no_file_test_t*)user_data;
@@ -232,10 +270,6 @@ static void no_such_file_log(
232270
void the_include_that_wasnt(void **state)
233271
{
234272
// 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 };
239273
zone_options_t options = { 0 };
240274
no_file_test_t test = { 0 };
241275
int32_t code;
@@ -245,7 +279,7 @@ void the_include_that_wasnt(void **state)
245279
options.origin.octets = origin;
246280
options.origin.length = sizeof(origin);
247281
options.default_ttl = 3600;
248-
options.default_class = ZONE_IN;
282+
options.default_class = 1;
249283

250284
(void)state;
251285

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

259-
char *include = malloc((size_t)length + 1 + ZONE_PADDING_SIZE);
293+
char *include = malloc((size_t)length + 1);
260294
assert_non_null(include);
261295
(void)snprintf(include, (size_t)length + 1, "$INCLUDE %s", non_include);
262296

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

271430
//

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)