Skip to content

Commit

Permalink
Add semantic checks for DS and ZONEMD digests
Browse files Browse the repository at this point in the history
  • Loading branch information
k0ekk0ek committed Aug 15, 2024
1 parent a411742 commit 42a2e68
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 7 deletions.
90 changes: 84 additions & 6 deletions src/generic/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -1291,8 +1291,26 @@ static int32_t check_ds_rr(
(r = check(&c, check_int8(parser, type, &f[2], o+c, n-c))))
return r;

// FIXME: can implement checking for digest length based on algorithm here.
// e.g. SHA-1 digest is 20 bytes, see RFC3658 section 2.4
const uint8_t digest_algorithm = parser->rdata->octets[3];

if ((digest_algorithm & 0x7) == digest_algorithm) {
// https://www.iana.org/assignments/ds-rr-types
static const uint8_t digest_sizes[8] = {
0, // 0: Reserved
20, // 1: SHA-1
32, // 2: SHA-256
32, // 3: GOST R 34.11-94
48, // 4: SHA-384
48, // 5: GOST R 34.10-2012
48, // 6: SM3
0 // 7: Unassigned
};

const uint8_t digest_size = digest_sizes[ digest_algorithm ];

if (digest_size && n - 4 != digest_size)
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
}

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

const uint8_t digest_algorithm = parser->rdata->octets[3];

if ((digest_algorithm & 0x7) == digest_algorithm) {
// https://www.iana.org/assignments/ds-rr-types
static const uint8_t digest_sizes[8] = {
0, // 0: Reserved
20, // 1: SHA-1
32, // 2: SHA-256
32, // 3: GOST R 34.11-94
48, // 4: SHA-384
48, // 5: GOST R 34.10-2012
48, // 6: SM3
0 // 7: Unassigned
};

const uint8_t digest_size = digest_sizes[ digest_algorithm ];
size_t length = (uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets;

if (digest_size && length - 4 != digest_size)
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
}

return accept_rr(parser, type, rdata);
}

Expand Down Expand Up @@ -2005,10 +2045,32 @@ nonnull_all
static int32_t check_zonemd_rr(
parser_t *parser, const type_info_t *type, const rdata_t *rdata)
{
// FIXME: RDATA contains digests, do extra checks?
assert(rdata->octets >= parser->rdata->octets);
if ((uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets < 6)
SYNTAX_ERROR(parser, "Invalid %s", NAME(type));
int32_t r;
size_t c = 0;
const size_t n = (uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets;
const uint8_t *o = parser->rdata->octets;
const rdata_info_t *f = type->rdata.fields;

if ((r = check(&c, check_int32(parser, type, &f[0], o, n))) ||
(r = check(&c, check_int8(parser, type, &f[1], o+c, n-c))) ||
(r = check(&c, check_int8(parser, type, &f[2], o+c, n-c))))
return r;

const uint8_t digest_algorithm = parser->rdata->octets[5];
if ((digest_algorithm & 0x3) == digest_algorithm) {
// https://www.iana.org/assignments/dns-parameters#zonemd-hash-algorithms
static const uint8_t digest_sizes[4] = {
0, // 0: Reserved
48, // 1: SHA-384
64, // 2: SHA-512
0 // 3: Unassigned
};

const uint8_t digest_size = digest_sizes[ digest_algorithm ];
if (digest_size && n - 6 != digest_size)
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
}

return accept_rr(parser, type, rdata);
}

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

const uint8_t digest_algorithm = parser->rdata->octets[5];
if ((digest_algorithm & 0x3) == digest_algorithm) {
// https://www.iana.org/assignments/dns-parameters#zonemd-hash-algorithms
static const uint8_t digest_sizes[4] = {
0, // 0: Reserved
48, // 1: SHA-384
64, // 2: SHA-512
0 // 3: Unassigned
};

const uint8_t digest_size = digest_sizes[ digest_algorithm ];
size_t length = (uintptr_t)rdata->octets - (uintptr_t)parser->rdata->octets;
if (digest_size && length - 6 != digest_size)
SEMANTIC_ERROR(parser, "Invalid digest in %s", NAME(type));
}

return accept_rr(parser, type, rdata);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ if(HAVE_HASWELL)
set_source_files_properties(haswell/bits.c PROPERTIES COMPILE_FLAGS "-march=haswell")
endif()

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)
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)

set(xbounds ${CMAKE_CURRENT_SOURCE_DIR}/zones/xbounds.zone)
set(xbounds_c "${CMAKE_CURRENT_BINARY_DIR}/xbounds.c")
Expand Down
179 changes: 179 additions & 0 deletions tests/semantics.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* syntax.c -- presentation format syntax test cases
*
* Copyright (c) 2023, NLnet Labs. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <assert.h>
#include <limits.h>
#include <stdarg.h>
#include <setjmp.h>
#include <string.h>
#include <stdlib.h>
#include <cmocka.h>
#if !_WIN32
#include <unistd.h>
#endif

#include "zone.h"
#include "diagnostic.h"
#include "tools.h"

static int32_t digest_test_accept_rr(
zone_parser_t *parser,
const zone_name_t *owner,
uint16_t type,
uint16_t class,
uint32_t ttl,
uint16_t rdlength,
const uint8_t *rdata,
void *user_data)
{
(void)parser;
(void)owner;
(void)type;
(void)class;
(void)ttl;
(void)rdlength;
(void)rdata;
(void)user_data;
return 0;
}

static int32_t parse_digest(const char *input)
{
const uint8_t origin[] = { 0 };
zone_parser_t parser;
zone_name_buffer_t name;
zone_rdata_buffer_t rdata;
zone_buffers_t buffers = { 1, &name, &rdata };
zone_options_t options;

memset(&options, 0, sizeof(options));
options.accept.callback = digest_test_accept_rr;
options.origin.octets = origin;
options.origin.length = sizeof(origin);
options.default_ttl = 3600;
options.default_class = 1;

fprintf(stderr, "INPUT: \"%s\"\n", input);
return zone_parse_string(
&parser, &options, &buffers, input, strlen(input), NULL);
}

/*!cmocka */
void ds_digest_lengths(void **state)
{
static const char fmt[] =
"dskey.example.com. 86400 IN DS 60485 5 %c ( %.*s )";
static const char hex_fmt[] =
"dskey.example.com. 86400 CLASS1 TYPE43 \\# %d EC45 05 0%c ( %.*s )";
static const char hex[] =
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef";

static const struct {
int algorithm;
int digest_length;
int32_t code;
} tests[] = {
// 0: Reserved
{ 0, 10, ZONE_SUCCESS },
// 1: SHA-1
{ 1, 20, ZONE_SUCCESS },
{ 1, 19, ZONE_SEMANTIC_ERROR },
{ 1, 21, ZONE_SEMANTIC_ERROR },
// 2: SHA-256
{ 2, 32, ZONE_SUCCESS },
{ 2, 31, ZONE_SEMANTIC_ERROR },
{ 2, 33, ZONE_SEMANTIC_ERROR },
// 3: GOST R 34.11-94
{ 3, 32, ZONE_SUCCESS },
{ 3, 31, ZONE_SEMANTIC_ERROR },
{ 3, 33, ZONE_SEMANTIC_ERROR },
// 4: SHA-384
{ 4, 48, ZONE_SUCCESS },
{ 4, 47, ZONE_SEMANTIC_ERROR },
{ 4, 49, ZONE_SEMANTIC_ERROR },
// 5: GOST R 34.10-2012
{ 5, 48, ZONE_SUCCESS },
{ 5, 47, ZONE_SEMANTIC_ERROR },
{ 5, 49, ZONE_SEMANTIC_ERROR },
// 6: SM3
{ 6, 48, ZONE_SUCCESS },
{ 6, 47, ZONE_SEMANTIC_ERROR },
{ 6, 49, ZONE_SEMANTIC_ERROR }
};

(void)state;

int32_t code;
for (size_t i=0, n = sizeof(tests)/sizeof(tests[0]); i < n; i++) {
char buf[512];
const int algo = tests[i].algorithm;
const int len = tests[i].digest_length;

snprintf(buf, sizeof(buf), fmt, algo + 0x30, len * 2, hex);
code = parse_digest(buf);
assert_int_equal(code, tests[i].code);

snprintf(buf, sizeof(buf), hex_fmt, 4 + len, algo + 0x30, len * 2, hex);
code = parse_digest(buf);
assert_int_equal(code, tests[i].code);
}
}

/*!cmocka */
void zonemd_digest_lengths(void **state)
{
static const char fmt[] =
"example.com. 86400 IN ZONEMD 2018031500 1 %c ( %.*s )";
static const char hex_fmt[] =
"example.com. 86400 CLASS1 TYPE63 \\# %d 7848B78C 01 0%c ( %.*s )";
static const char hex[] =
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef";

static const struct {
int algorithm;
int digest_length;
int32_t code;
} tests[] = {
// 0: Reserved
{ 0, 10, ZONE_SUCCESS },
// 1: SHA-384
{ 1, 48, ZONE_SUCCESS },
{ 1, 47, ZONE_SEMANTIC_ERROR },
{ 1, 49, ZONE_SEMANTIC_ERROR },
// 2: SHA-512
{ 2, 64, ZONE_SUCCESS },
{ 2, 63, ZONE_SEMANTIC_ERROR },
{ 2, 65, ZONE_SEMANTIC_ERROR }
};

(void)state;

int32_t code;

for (size_t i=0, n = sizeof(tests)/sizeof(tests[0]); i < n; i++) {
char buf[512];
const int algo = tests[i].algorithm;
const int len = tests[i].digest_length;

snprintf(buf, sizeof(buf), fmt, algo + 0x30, len * 2, hex);
code = parse_digest(buf);
assert_int_equal(code, tests[i].code);

snprintf(buf, sizeof(buf), hex_fmt, 6 + len, algo + 0x30, len * 2, hex);
code = parse_digest(buf);
assert_int_equal(code, tests[i].code);
}
}

0 comments on commit 42a2e68

Please sign in to comment.