Skip to content

Commit dd666f3

Browse files
committed
Add tests to ensure correct behavior on boundaries
1 parent 34f77b1 commit dd666f3

File tree

4 files changed

+627
-1
lines changed

4 files changed

+627
-1
lines changed

tests/CMakeLists.txt

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
find_package(cmocka REQUIRED)
2-
cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c syntax.c eui.c)
2+
cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c syntax.c eui.c bounds.c)
3+
4+
set(xbounds ${CMAKE_CURRENT_SOURCE_DIR}/zones/xbounds.zone)
5+
set(xbounds_c "${CMAKE_CURRENT_BINARY_DIR}/xbounds.c")
6+
set(xxd_cmake ${CMAKE_CURRENT_SOURCE_DIR}/xxd.cmake)
7+
8+
add_custom_command(
9+
OUTPUT "${xbounds_c}"
10+
COMMAND ${CMAKE_COMMAND}
11+
ARGS "-DINPUT_FILE=${xbounds}" "-DOUTPUT_FILE=${xbounds_c}" -P ${xxd_cmake}
12+
DEPENDS "${xbounds}" "${xxd_cmake}")
13+
14+
add_custom_target(generate_xbounds_c DEPENDS "${xbounds_c}")
315

416
target_link_libraries(zone-tests PRIVATE zone)
17+
target_sources(zone-tests PRIVATE "${xbounds_c}")
18+
add_dependencies(zone-tests generate_xbounds_c)
519
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
620
target_compile_options(zone-tests PRIVATE -Wno-missing-prototypes -Wno-deprecated-declarations)
721
elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU")

tests/bounds.c

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
* bounds.c -- test for correct indexer operation on boundaries
3+
*
4+
* Copyright (c) 2024, NLnet Labs. All rights reserved.
5+
*
6+
* SPDX-License-Identifier: BSD-3-Clause
7+
*
8+
*/
9+
#include <stdarg.h>
10+
#include <setjmp.h>
11+
#include <string.h>
12+
#include <stdlib.h>
13+
#include <cmocka.h>
14+
15+
#include "zone.h"
16+
17+
// indexer(s) scans in 64-byte chunks, use white space for positioning
18+
19+
// terminate contiguous on last byte of block
20+
const char contiguous_end_last[] =
21+
"foo. TXT bar\nfoo. TXT baz";
22+
23+
// terminate contiguous on first byte of next block
24+
const char contiguous_end_first[] =
25+
"foo. TXT bar\nfoo. TXT baz";
26+
27+
// terminate quoted on last byte of the block
28+
const char quoted_end_last[] =
29+
"foo. TXT \"bar\"\nfoo. TXT baz";
30+
31+
// terminate quoted on first byte of next block
32+
const char quoted_end_first[] =
33+
"foo. TXT \"bar\"\nfoo. TXT baz";
34+
35+
// terminate comment on last byte of block
36+
const char comment_end_last[] =
37+
"foo. TXT bar ; comment\nfoo. TXT baz";
38+
39+
// terminate comment on first byte of next block
40+
const char comment_end_first[] =
41+
"foo. TXT bar ; comment\nfoo. TXT baz";
42+
43+
// start contiguous on last byte of block
44+
const char contiguous_start_last[] =
45+
"foo. TXT bar"
46+
"\nfoo. TXT baz";
47+
48+
// start quoted on last byte of block
49+
const char quoted_start_last[] =
50+
"foo. TXT \""
51+
"bar\"\nfoo. TXT baz";
52+
53+
// start quoted on last byte of block, end on first byte of next block
54+
const char quoted_start_last_end_first[] =
55+
"foo. TXT \""
56+
"\"\nfoo. TXT baz";
57+
58+
// start quoted on last byte of block, end of first byte of next next block
59+
const char quoted_start_last_end_next_first[] =
60+
"foo. TXT \""
61+
"bar "
62+
"\"\nfoo. TXT baz";
63+
64+
// start comment on last byte of block
65+
const char comment_start_last[] =
66+
"foo. TXT bar;"
67+
" foobar\nfoo. TXT baz";
68+
69+
// start comment on last byte of block, end on first byte of next block
70+
const char comment_start_last_end_first[] =
71+
"foo. TXT bar;"
72+
"\nfoo. TXT baz";
73+
74+
// start comment on last byte of block, end on first byte of next next block
75+
const char comment_start_last_end_next_first[] =
76+
"foo. TXT bar;"
77+
" "
78+
"\nfoo. TXT baz";
79+
80+
// FIXME: the above can be testen on buffer boundaries too
81+
// FIXME: add a maximum buffer size test
82+
// FIXME: test buffer is not resized when processing a comment
83+
84+
static int32_t accept_bar_baz(
85+
zone_parser_t *parser,
86+
const zone_name_t *owner,
87+
uint16_t type,
88+
uint16_t class,
89+
uint32_t ttl,
90+
uint16_t rdlength,
91+
const uint8_t *rdata,
92+
void *user_data)
93+
{
94+
(void)parser;
95+
(void)class;
96+
(void)ttl;
97+
98+
static const uint8_t foo[5] = { 3, 'f', 'o', 'o', 0 };
99+
100+
if (owner->length != 5 || memcmp(owner->octets, foo, 5) != 0)
101+
return ZONE_SYNTAX_ERROR;
102+
if (type != ZONE_TYPE_TXT)
103+
return ZONE_SYNTAX_ERROR;
104+
105+
if (rdlength == 1 && rdata[0] == 0) {
106+
*((size_t *)user_data) += 1;
107+
return 0;
108+
} else if (rdlength > 3 && rdata[0] >= 3) {
109+
switch (*((size_t *)user_data)) {
110+
case 0: // expect bar
111+
if (memcmp(rdata+1, "bar", 3) != 0)
112+
return ZONE_SYNTAX_ERROR;
113+
break;
114+
case 1: // expect baz
115+
if (memcmp(rdata+1, "baz", 3) != 0)
116+
return ZONE_SYNTAX_ERROR;
117+
break;
118+
default:
119+
return ZONE_SYNTAX_ERROR;
120+
}
121+
122+
*((size_t *)user_data) += 1;
123+
return 0;
124+
}
125+
126+
return ZONE_SYNTAX_ERROR;
127+
}
128+
129+
/*!cmocka */
130+
void block_boundary(void **state)
131+
{
132+
(void)state;
133+
134+
static const uint8_t root[1] = { 0 };
135+
static const struct {
136+
const char *input; size_t length;
137+
} tests[] = {
138+
{ contiguous_end_last, sizeof(contiguous_end_last) },
139+
{ contiguous_end_first, sizeof(contiguous_end_first) },
140+
{ quoted_end_last, sizeof(quoted_end_last) },
141+
{ quoted_end_first, sizeof(quoted_end_first) },
142+
{ comment_end_last, sizeof(comment_end_last) },
143+
{ comment_end_first, sizeof(comment_end_first) },
144+
{ contiguous_start_last, sizeof(contiguous_start_last) },
145+
{ quoted_start_last, sizeof(quoted_start_last) },
146+
{ quoted_start_last_end_first, sizeof(quoted_start_last_end_first) },
147+
{ quoted_start_last_end_next_first, sizeof(quoted_start_last_end_next_first) },
148+
{ comment_start_last, sizeof(comment_start_last) },
149+
{ comment_start_last_end_first, sizeof(comment_start_last_end_first) },
150+
{ comment_start_last_end_next_first, sizeof(comment_start_last_end_next_first) }
151+
};
152+
153+
zone_parser_t parser;
154+
zone_options_t options;
155+
zone_name_buffer_t owner;
156+
zone_rdata_buffer_t rdata;
157+
zone_buffers_t buffers = { 1, &owner, &rdata };
158+
159+
for (int i=0, n=sizeof(tests)/sizeof(tests[0]); i < n; i++) {
160+
// allocate memory instead of using a static buffer for asan
161+
char *input = malloc(tests[i].length + 64);
162+
assert_non_null(input);
163+
memcpy(input, tests[i].input, tests[i].length);
164+
memset(&parser, 0, sizeof(parser));
165+
memset(&options, 0, sizeof(options));
166+
options.origin.octets = root;
167+
options.origin.length = 1;
168+
options.accept.callback = &accept_bar_baz;
169+
options.default_ttl = 3600;
170+
options.default_class = 1;
171+
172+
fprintf(stderr, "INPUT:\n%s\n", input);
173+
174+
size_t count = 0;
175+
int32_t code = zone_parse_string(
176+
&parser, &options, &buffers, input, tests[i].length - 1, &count);
177+
assert_int_equal(code, ZONE_SUCCESS);
178+
assert_int_equal(count, 2);
179+
free(input);
180+
}
181+
}
182+
183+
static int32_t count_openpgp(
184+
zone_parser_t *parser,
185+
const zone_name_t *owner,
186+
uint16_t type,
187+
uint16_t class,
188+
uint32_t ttl,
189+
uint16_t rdlength,
190+
const uint8_t *rdata,
191+
void *user_data)
192+
{
193+
(void)parser;
194+
(void)owner;
195+
(void)class;
196+
(void)ttl;
197+
(void)rdlength;
198+
(void)rdata;
199+
if (type == ZONE_TYPE_OPENPGPKEY)
200+
*((size_t *)user_data) += 1;
201+
return 0;
202+
}
203+
204+
extern unsigned char xbounds_zone[];
205+
extern unsigned int xbounds_zone_len;
206+
207+
/*!cmocka */
208+
void contiguous_on_buffer_boundary(void **state)
209+
{
210+
// test if buffer is properly resized if token crosses boundary
211+
212+
(void)state;
213+
214+
static const uint8_t root[1] = { 0 };
215+
216+
zone_parser_t parser;
217+
memset(&parser, 0, sizeof(parser));
218+
zone_options_t options;
219+
memset(&options, 0, sizeof(options));
220+
options.origin.octets = root;
221+
options.origin.length = 1;
222+
options.accept.callback = &count_openpgp;
223+
options.default_ttl = 3600;
224+
options.default_class = 1;
225+
226+
zone_name_buffer_t owner;
227+
zone_rdata_buffer_t rdata;
228+
zone_buffers_t buffers = { 1, &owner, &rdata };
229+
230+
// generate zone file to parse
231+
char *path = tempnam(NULL, "xbounds");
232+
assert_non_null(path);
233+
FILE *handle = fopen(path, "wb");
234+
assert_non_null(handle);
235+
size_t written = fwrite(xbounds_zone, 1, xbounds_zone_len, handle);
236+
assert_int_equal((int)written, xbounds_zone_len);
237+
(void)fclose(handle);
238+
size_t count = 0;
239+
int32_t code = zone_parse(&parser, &options, &buffers, path, &count);
240+
assert_int_equal(code, ZONE_SUCCESS);
241+
assert_int_equal(count, 3);
242+
free(path);
243+
}

tests/xxd.cmake

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#
2+
# xxd.cmake -- create hex dump in c include file style
3+
#
4+
# Copyright (c) 2024, NLnet Labs. All rights reserved.
5+
#
6+
# SPDX-License-Identifier: BSD-3-Clause
7+
#
8+
cmake_minimum_required(VERSION 3.10)
9+
10+
# equivalent of "xxd -i input.file > output.file"
11+
12+
if(NOT INPUT_FILE)
13+
message(FATAL_ERROR "No input file specified")
14+
endif()
15+
16+
if(NOT OUTPUT_FILE)
17+
message(FATAL_ERROR "No output file specified")
18+
endif()
19+
20+
# normalize paths
21+
file(TO_CMAKE_PATH "${INPUT_FILE}" input_path)
22+
file(TO_CMAKE_PATH "${OUTPUT_FILE}" output_path)
23+
24+
get_filename_component(output_directory "${output_path}" DIRECTORY)
25+
get_filename_component(input_file "${input_path}" NAME)
26+
27+
# make sure input file exists
28+
if(NOT EXISTS "${input_path}")
29+
message(FATAL_ERROR "Input file does not exist")
30+
endif()
31+
# make sure output directory exists
32+
if(output_directory AND NOT EXISTS "${output_directory}")
33+
message(FATAL_ERROR "Output directory (${output_directory}) does not exist")
34+
endif()
35+
36+
# read contents in hexadecimal representation
37+
file(READ "${input_path}" input HEX)
38+
39+
string(LENGTH "${input}" length)
40+
41+
# file contents should be:
42+
#
43+
# unsigned char file_name[] = {
44+
# 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
45+
# ...
46+
# };
47+
# unsigned int file_name_len = xxx;
48+
49+
# write to output
50+
string(APPEND output "// generated by xxd.cmake, do not edit\n")
51+
string(MAKE_C_IDENTIFIER "${input_file}" array_name)
52+
string(APPEND output "unsigned char ${array_name}[] = {\n")
53+
54+
set(xx "[0123456789abcdef]") # a-f are guaranteed to be in lower case
55+
set(xx2 "${xx}${xx}")
56+
set(xx20 "${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}${xx2}")
57+
string(REGEX REPLACE "(${xx20})" "\\1;" lines "${input}")
58+
string(REGEX REPLACE "(${xx2})" "0x\\1, " lines "${lines}")
59+
string(REGEX REPLACE ", $" "" lines "${lines}")
60+
61+
foreach(line ${lines})
62+
string(APPEND output " ${line}\n")
63+
endforeach()
64+
65+
string(APPEND output "};\n")
66+
67+
math(EXPR array_length "${length} / 2")
68+
69+
string(APPEND output "unsigned int ${array_name}_len = ${array_length};\n")
70+
71+
file(WRITE "${output_path}" "${output}")

0 commit comments

Comments
 (0)