Skip to content

Commit 3b80948

Browse files
committed
Add semantic checks for DS and ZONEMD digests
Fixes NLnetLabs/nsd#205.
1 parent a411742 commit 3b80948

File tree

3 files changed

+264
-7
lines changed

3 files changed

+264
-7
lines changed

src/generic/types.h

+84-6
Original file line numberDiff line numberDiff line change
@@ -1291,8 +1291,26 @@ static int32_t check_ds_rr(
12911291
(r = check(&c, check_int8(parser, type, &f[2], o+c, n-c))))
12921292
return r;
12931293

1294-
// FIXME: can implement checking for digest length based on algorithm here.
1295-
// e.g. SHA-1 digest is 20 bytes, see RFC3658 section 2.4
1294+
const uint8_t digest_algorithm = parser->rdata->octets[3];
1295+
1296+
if ((digest_algorithm & 0x7) == digest_algorithm) {
1297+
// https://www.iana.org/assignments/ds-rr-types
1298+
static const uint8_t digest_sizes[8] = {
1299+
0, // 0: Reserved
1300+
20, // 1: SHA-1
1301+
32, // 2: SHA-256
1302+
32, // 3: GOST R 34.11-94
1303+
48, // 4: SHA-384
1304+
48, // 5: GOST R 34.10-2012
1305+
48, // 6: SM3
1306+
0 // 7: Unassigned
1307+
};
1308+
1309+
const uint8_t digest_size = digest_sizes[ digest_algorithm ];
1310+
1311+
if (digest_size && n - 4 != digest_size)
1312+
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
1313+
}
12961314

12971315
if (c >= n)
12981316
SYNTAX_ERROR(parser, "Invalid %s", NAME(type));
@@ -1322,6 +1340,28 @@ static int32_t parse_ds_rdata(
13221340
if ((code = parse_base16_sequence(parser, type, &fields[3], rdata, token)) < 0)
13231341
return code;
13241342

1343+
const uint8_t digest_algorithm = parser->rdata->octets[3];
1344+
1345+
if ((digest_algorithm & 0x7) == digest_algorithm) {
1346+
// https://www.iana.org/assignments/ds-rr-types
1347+
static const uint8_t digest_sizes[8] = {
1348+
0, // 0: Reserved
1349+
20, // 1: SHA-1
1350+
32, // 2: SHA-256
1351+
32, // 3: GOST R 34.11-94
1352+
48, // 4: SHA-384
1353+
48, // 5: GOST R 34.10-2012
1354+
48, // 6: SM3
1355+
0 // 7: Unassigned
1356+
};
1357+
1358+
const uint8_t digest_size = digest_sizes[ digest_algorithm ];
1359+
size_t length = (uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets;
1360+
1361+
if (digest_size && length - 4 != digest_size)
1362+
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
1363+
}
1364+
13251365
return accept_rr(parser, type, rdata);
13261366
}
13271367

@@ -2005,10 +2045,32 @@ nonnull_all
20052045
static int32_t check_zonemd_rr(
20062046
parser_t *parser, const type_info_t *type, const rdata_t *rdata)
20072047
{
2008-
// FIXME: RDATA contains digests, do extra checks?
2009-
assert(rdata->octets >= parser->rdata->octets);
2010-
if ((uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets < 6)
2011-
SYNTAX_ERROR(parser, "Invalid %s", NAME(type));
2048+
int32_t r;
2049+
size_t c = 0;
2050+
const size_t n = (uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets;
2051+
const uint8_t *o = parser->rdata->octets;
2052+
const rdata_info_t *f = type->rdata.fields;
2053+
2054+
if ((r = check(&c, check_int32(parser, type, &f[0], o, n))) ||
2055+
(r = check(&c, check_int8(parser, type, &f[1], o+c, n-c))) ||
2056+
(r = check(&c, check_int8(parser, type, &f[2], o+c, n-c))))
2057+
return r;
2058+
2059+
const uint8_t digest_algorithm = parser->rdata->octets[5];
2060+
if ((digest_algorithm & 0x3) == digest_algorithm) {
2061+
// https://www.iana.org/assignments/dns-parameters#zonemd-hash-algorithms
2062+
static const uint8_t digest_sizes[4] = {
2063+
0, // 0: Reserved
2064+
48, // 1: SHA-384
2065+
64, // 2: SHA-512
2066+
0 // 3: Unassigned
2067+
};
2068+
2069+
const uint8_t digest_size = digest_sizes[ digest_algorithm ];
2070+
if (digest_size && n - 6 != digest_size)
2071+
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
2072+
}
2073+
20122074
return accept_rr(parser, type, rdata);
20132075
}
20142076

@@ -2035,6 +2097,22 @@ static int32_t parse_zonemd_rdata(
20352097
if ((code = parse_base16_sequence(parser, type, &fields[3], rdata, token)) < 0)
20362098
return code;
20372099

2100+
const uint8_t digest_algorithm = parser->rdata->octets[5];
2101+
if ((digest_algorithm & 0x3) == digest_algorithm) {
2102+
// https://www.iana.org/assignments/dns-parameters#zonemd-hash-algorithms
2103+
static const uint8_t digest_sizes[4] = {
2104+
0, // 0: Reserved
2105+
48, // 1: SHA-384
2106+
64, // 2: SHA-512
2107+
0 // 3: Unassigned
2108+
};
2109+
2110+
const uint8_t digest_size = digest_sizes[ digest_algorithm ];
2111+
size_t length = (uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets;
2112+
if (digest_size && length - 6 != digest_size)
2113+
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
2114+
}
2115+
20382116
return accept_rr(parser, type, rdata);
20392117
}
20402118

tests/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ if(HAVE_HASWELL)
99
set_source_files_properties(haswell/bits.c PROPERTIES COMPILE_FLAGS "-march=haswell")
1010
endif()
1111

12-
cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c syntax.c eui.c bounds.c bits.c)
12+
cmocka_add_tests(zone-tests types.c include.c ip4.c time.c base32.c svcb.c syntax.c semantics.c eui.c bounds.c bits.c)
1313

1414
set(xbounds ${CMAKE_CURRENT_SOURCE_DIR}/zones/xbounds.zone)
1515
set(xbounds_c "${CMAKE_CURRENT_BINARY_DIR}/xbounds.c")

tests/semantics.c

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* syntax.c -- presentation format syntax test cases
3+
*
4+
* Copyright (c) 2023, NLnet Labs. All rights reserved.
5+
*
6+
* SPDX-License-Identifier: BSD-3-Clause
7+
*
8+
*/
9+
#include <assert.h>
10+
#include <limits.h>
11+
#include <stdarg.h>
12+
#include <setjmp.h>
13+
#include <string.h>
14+
#include <stdlib.h>
15+
#include <cmocka.h>
16+
#if !_WIN32
17+
#include <unistd.h>
18+
#endif
19+
20+
#include "zone.h"
21+
#include "diagnostic.h"
22+
#include "tools.h"
23+
24+
static int32_t digest_test_accept_rr(
25+
zone_parser_t *parser,
26+
const zone_name_t *owner,
27+
uint16_t type,
28+
uint16_t class,
29+
uint32_t ttl,
30+
uint16_t rdlength,
31+
const uint8_t *rdata,
32+
void *user_data)
33+
{
34+
(void)parser;
35+
(void)owner;
36+
(void)type;
37+
(void)class;
38+
(void)ttl;
39+
(void)rdlength;
40+
(void)rdata;
41+
(void)user_data;
42+
return 0;
43+
}
44+
45+
static int32_t parse_digest(const char *input)
46+
{
47+
const uint8_t origin[] = { 0 };
48+
zone_parser_t parser;
49+
zone_name_buffer_t name;
50+
zone_rdata_buffer_t rdata;
51+
zone_buffers_t buffers = { 1, &name, &rdata };
52+
zone_options_t options;
53+
54+
memset(&options, 0, sizeof(options));
55+
options.accept.callback = digest_test_accept_rr;
56+
options.origin.octets = origin;
57+
options.origin.length = sizeof(origin);
58+
options.default_ttl = 3600;
59+
options.default_class = 1;
60+
61+
fprintf(stderr, "INPUT: \"%s\"\n", input);
62+
return zone_parse_string(
63+
&parser, &options, &buffers, input, strlen(input), NULL);
64+
}
65+
66+
/*!cmocka */
67+
void ds_digest_lengths(void **state)
68+
{
69+
static const char fmt[] =
70+
"dskey.example.com. 86400 IN DS 60485 5 %c ( %.*s )";
71+
static const char hex_fmt[] =
72+
"dskey.example.com. 86400 CLASS1 TYPE43 \\# %d EC45 05 0%c ( %.*s )";
73+
static const char hex[] =
74+
"0123456789abcdef0123456789abcdef"
75+
"0123456789abcdef0123456789abcdef"
76+
"0123456789abcdef0123456789abcdef"
77+
"0123456789abcdef0123456789abcdef";
78+
79+
static const struct {
80+
int algorithm;
81+
int digest_length;
82+
int32_t code;
83+
} tests[] = {
84+
// 0: Reserved
85+
{ 0, 10, ZONE_SUCCESS },
86+
// 1: SHA-1
87+
{ 1, 20, ZONE_SUCCESS },
88+
{ 1, 19, ZONE_SEMANTIC_ERROR },
89+
{ 1, 21, ZONE_SEMANTIC_ERROR },
90+
// 2: SHA-256
91+
{ 2, 32, ZONE_SUCCESS },
92+
{ 2, 31, ZONE_SEMANTIC_ERROR },
93+
{ 2, 33, ZONE_SEMANTIC_ERROR },
94+
// 3: GOST R 34.11-94
95+
{ 3, 32, ZONE_SUCCESS },
96+
{ 3, 31, ZONE_SEMANTIC_ERROR },
97+
{ 3, 33, ZONE_SEMANTIC_ERROR },
98+
// 4: SHA-384
99+
{ 4, 48, ZONE_SUCCESS },
100+
{ 4, 47, ZONE_SEMANTIC_ERROR },
101+
{ 4, 49, ZONE_SEMANTIC_ERROR },
102+
// 5: GOST R 34.10-2012
103+
{ 5, 48, ZONE_SUCCESS },
104+
{ 5, 47, ZONE_SEMANTIC_ERROR },
105+
{ 5, 49, ZONE_SEMANTIC_ERROR },
106+
// 6: SM3
107+
{ 6, 48, ZONE_SUCCESS },
108+
{ 6, 47, ZONE_SEMANTIC_ERROR },
109+
{ 6, 49, ZONE_SEMANTIC_ERROR }
110+
};
111+
112+
(void)state;
113+
114+
int32_t code;
115+
for (size_t i=0, n = sizeof(tests)/sizeof(tests[0]); i < n; i++) {
116+
char buf[512];
117+
const int algo = tests[i].algorithm;
118+
const int len = tests[i].digest_length;
119+
120+
snprintf(buf, sizeof(buf), fmt, algo + 0x30, len * 2, hex);
121+
code = parse_digest(buf);
122+
assert_int_equal(code, tests[i].code);
123+
124+
snprintf(buf, sizeof(buf), hex_fmt, 4 + len, algo + 0x30, len * 2, hex);
125+
code = parse_digest(buf);
126+
assert_int_equal(code, tests[i].code);
127+
}
128+
}
129+
130+
/*!cmocka */
131+
void zonemd_digest_lengths(void **state)
132+
{
133+
static const char fmt[] =
134+
"example.com. 86400 IN ZONEMD 2018031500 1 %c ( %.*s )";
135+
static const char hex_fmt[] =
136+
"example.com. 86400 CLASS1 TYPE63 \\# %d 7848B78C 01 0%c ( %.*s )";
137+
static const char hex[] =
138+
"0123456789abcdef0123456789abcdef"
139+
"0123456789abcdef0123456789abcdef"
140+
"0123456789abcdef0123456789abcdef"
141+
"0123456789abcdef0123456789abcdef"
142+
"0123456789abcdef0123456789abcdef"
143+
"0123456789abcdef0123456789abcdef";
144+
145+
static const struct {
146+
int algorithm;
147+
int digest_length;
148+
int32_t code;
149+
} tests[] = {
150+
// 0: Reserved
151+
{ 0, 10, ZONE_SUCCESS },
152+
// 1: SHA-384
153+
{ 1, 48, ZONE_SUCCESS },
154+
{ 1, 47, ZONE_SEMANTIC_ERROR },
155+
{ 1, 49, ZONE_SEMANTIC_ERROR },
156+
// 2: SHA-512
157+
{ 2, 64, ZONE_SUCCESS },
158+
{ 2, 63, ZONE_SEMANTIC_ERROR },
159+
{ 2, 65, ZONE_SEMANTIC_ERROR }
160+
};
161+
162+
(void)state;
163+
164+
int32_t code;
165+
166+
for (size_t i=0, n = sizeof(tests)/sizeof(tests[0]); i < n; i++) {
167+
char buf[512];
168+
const int algo = tests[i].algorithm;
169+
const int len = tests[i].digest_length;
170+
171+
snprintf(buf, sizeof(buf), fmt, algo + 0x30, len * 2, hex);
172+
code = parse_digest(buf);
173+
assert_int_equal(code, tests[i].code);
174+
175+
snprintf(buf, sizeof(buf), hex_fmt, 6 + len, algo + 0x30, len * 2, hex);
176+
code = parse_digest(buf);
177+
assert_int_equal(code, tests[i].code);
178+
}
179+
}

0 commit comments

Comments
 (0)