Skip to content

Commit 62de7e8

Browse files
committed
Add support for limiting $INCLUDE depth
1 parent a43cfc3 commit 62de7e8

File tree

5 files changed

+184
-43
lines changed

5 files changed

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

+166-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,42 @@ 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+
(void)fclose(handle);
137+
if (result != EOF)
138+
return path;
139+
}
140+
free(path);
141+
}
142+
return NULL;
143+
}
144+
145+
static int32_t parse(
146+
const zone_options_t *options, const char *text, void *user_data)
147+
{
148+
zone_parser_t parser = { 0 };
149+
zone_name_buffer_t name;
150+
zone_rdata_buffer_t rdata;
151+
zone_buffers_t buffers = { 1, &name, &rdata };
152+
153+
int32_t code;
154+
size_t length = strlen(text);
155+
char *string = malloc(length + 1 + ZONE_PADDING_SIZE);
156+
assert_non_null(string);
157+
memcpy(string, text, length);
158+
string[length] = '\0';
159+
160+
code = zone_parse_string(&parser, options, &buffers, string, length, user_data);
161+
free(string);
162+
return code;
163+
}
164+
128165
diagnostic_pop()
129166

130167
static int32_t add_rr(
@@ -216,12 +253,12 @@ static int32_t no_such_file_accept(
216253

217254
static void no_such_file_log(
218255
zone_parser_t *parser,
219-
uint32_t category,
256+
uint32_t priority,
220257
const char *message,
221258
void *user_data)
222259
{
223260
(void)parser;
224-
(void)category;
261+
(void)priority;
225262
if (!strstr(message, "no such file"))
226263
return;
227264
no_file_test_t *test = (no_file_test_t*)user_data;
@@ -232,10 +269,6 @@ static void no_such_file_log(
232269
void the_include_that_wasnt(void **state)
233270
{
234271
// 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 };
239272
zone_options_t options = { 0 };
240273
no_file_test_t test = { 0 };
241274
int32_t code;
@@ -245,7 +278,7 @@ void the_include_that_wasnt(void **state)
245278
options.origin.octets = origin;
246279
options.origin.length = sizeof(origin);
247280
options.default_ttl = 3600;
248-
options.default_class = ZONE_IN;
281+
options.default_class = 1;
249282

250283
(void)state;
251284

@@ -256,16 +289,139 @@ void the_include_that_wasnt(void **state)
256289
int length = snprintf(buffer, sizeof(buffer), "$INCLUDE %s", non_include);
257290
assert_true(length >= 0 && (size_t)length < SIZE_MAX - (ZONE_PADDING_SIZE + 1));
258291

259-
char *include = malloc((size_t)length + 1 + ZONE_PADDING_SIZE);
292+
char *include = malloc((size_t)length + 1);
260293
assert_non_null(include);
261294
(void)snprintf(include, (size_t)length + 1, "$INCLUDE %s", non_include);
262295

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

271427
//

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)