diff options
-rw-r--r-- | src/knot/nameserver/internet.c | 2 | ||||
-rw-r--r-- | src/knot/nameserver/nsec_proofs.c | 3 | ||||
-rw-r--r-- | src/knot/zone/contents.c | 16 | ||||
-rw-r--r-- | src/knot/zone/contents.h | 4 | ||||
-rw-r--r-- | src/knot/zone/node.c | 6 | ||||
-rw-r--r-- | src/knot/zone/node.h | 4 | ||||
-rw-r--r-- | src/knot/zone/semantic-check.c | 3 | ||||
-rw-r--r-- | src/libknot/packet/pkt.c | 7 | ||||
-rw-r--r-- | src/libknot/packet/pkt.h | 3 | ||||
-rw-r--r-- | tests-extra/tests/basic/zerobyte/data/test.zone | 9 | ||||
-rw-r--r-- | tests-extra/tests/basic/zerobyte/test.py | 34 |
11 files changed, 80 insertions, 11 deletions
diff --git a/src/knot/nameserver/internet.c b/src/knot/nameserver/internet.c index 034fd268c..949642050 100644 --- a/src/knot/nameserver/internet.c +++ b/src/knot/nameserver/internet.c @@ -461,7 +461,7 @@ static knotd_in_state_t solve_name(knotd_in_state_t state, knot_pkt_t *pkt, { int ret = zone_contents_find_dname(qdata->extra->contents, qdata->name, &qdata->extra->node, &qdata->extra->encloser, - &qdata->extra->previous); + &qdata->extra->previous, qdata->query->flags & KNOT_PF_NULLBYTE); switch (ret) { case ZONE_NAME_FOUND: diff --git a/src/knot/nameserver/nsec_proofs.c b/src/knot/nameserver/nsec_proofs.c index c99fd1fcb..9e7494645 100644 --- a/src/knot/nameserver/nsec_proofs.c +++ b/src/knot/nameserver/nsec_proofs.c @@ -190,7 +190,8 @@ static int put_covering_nsec(const zone_contents_t *zone, const zone_node_t *proof = NULL; - int ret = zone_contents_find_dname(zone, name, &match, &closest, &prev); + int ret = zone_contents_find_dname(zone, name, &match, &closest, &prev, + qdata->query->flags & KNOT_PF_NULLBYTE); if (ret == ZONE_NAME_FOUND) { proof = match; } else if (ret == ZONE_NAME_NOT_FOUND) { diff --git a/src/knot/zone/contents.c b/src/knot/zone/contents.c index e0f4e581c..760a0c5f8 100644 --- a/src/knot/zone/contents.c +++ b/src/knot/zone/contents.c @@ -280,7 +280,8 @@ int zone_contents_find_dname(const zone_contents_t *zone, const knot_dname_t *name, const zone_node_t **match, const zone_node_t **closest, - const zone_node_t **previous) + const zone_node_t **previous, + bool name_nullbyte) { if (name == NULL || match == NULL || closest == NULL) { return KNOT_EINVAL; @@ -306,6 +307,15 @@ int zone_contents_find_dname(const zone_contents_t *zone, // if previous==NULL, zone not adjusted yet assert(node); + + // WARNING: for the sake of efficiency, Knot does not handle \0 byte in qname correctly + // which can lead to disasters here and there. This should cover most of the cases. + bool node_nullbyte = (node->flags & NODE_FLAGS_NULLBYTE); + if (node_nullbyte != name_nullbyte || + (node_nullbyte && !knot_dname_is_equal(node->owner, name))) { + goto nxd; + } + *match = node; *closest = node; if (previous != NULL) { @@ -318,7 +328,7 @@ int zone_contents_find_dname(const zone_contents_t *zone, // closest match assert(!node && prev); - +nxd: node = prev; size_t matched_labels = knot_dname_matched_labels(node->owner, name); while (matched_labels < knot_dname_labels(node->owner, NULL)) { @@ -431,7 +441,7 @@ bool zone_contents_find_node_or_wildcard(const zone_contents_t *contents, const zone_node_t **found) { const zone_node_t *encloser = NULL; - zone_contents_find_dname(contents, find, found, &encloser, NULL); + zone_contents_find_dname(contents, find, found, &encloser, NULL, knot_dname_with_null(find)); if (*found == NULL && encloser != NULL && (encloser->flags & NODE_FLAGS_WILDCARD_CHILD)) { *found = zone_contents_find_wildcard_child(contents, encloser); assert(*found != NULL); diff --git a/src/knot/zone/contents.h b/src/knot/zone/contents.h index e92bf61d4..0acde8319 100644 --- a/src/knot/zone/contents.h +++ b/src/knot/zone/contents.h @@ -119,6 +119,7 @@ zone_node_t *zone_contents_find_node_for_rr(zone_contents_t *contents, const kno * May match \a match if found exactly. * \param[out] previous Previous domain name in canonical order. * Always previous, won't match \a match. + * \param[in] name_nullbyte The \a name parameter contains \0 byte. * * \note The encloser and previous mustn't be used directly for DNSSEC proofs. * These nodes may be empty non-terminals or not authoritative. @@ -133,7 +134,8 @@ int zone_contents_find_dname(const zone_contents_t *contents, const knot_dname_t *name, const zone_node_t **match, const zone_node_t **closest, - const zone_node_t **previous); + const zone_node_t **previous, + bool name_nullbyte); /*! * \brief Tries to find a node with the specified name among the NSEC3 nodes diff --git a/src/knot/zone/node.c b/src/knot/zone/node.c index 291454bd7..82d930d8f 100644 --- a/src/knot/zone/node.c +++ b/src/knot/zone/node.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -124,6 +124,10 @@ zone_node_t *node_new(const knot_dname_t *owner, bool binode, bool second, knot_ // Node is authoritative by default. ret->flags = NODE_FLAGS_AUTH; + if (knot_dname_with_null(owner)) { + ret->flags |= NODE_FLAGS_NULLBYTE; + } + if (binode) { ret->flags |= NODE_FLAGS_BINODE; if (second) { diff --git a/src/knot/zone/node.h b/src/knot/zone/node.h index d30cc6e1c..cab0604f9 100644 --- a/src/knot/zone/node.h +++ b/src/knot/zone/node.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -104,6 +104,8 @@ enum node_flags { NODE_FLAGS_SUBTREE_AUTH = 1 << 11, /*! \brief The node or some node in subtree has any data in it, possibly just insec deleg. */ NODE_FLAGS_SUBTREE_DATA = 1 << 12, + /*! \brief Node owner name contains \0 byte in some label. */ + NODE_FLAGS_NULLBYTE = 1 << 13, }; typedef void (*node_addrem_cb)(zone_node_t *, void *); diff --git a/src/knot/zone/semantic-check.c b/src/knot/zone/semantic-check.c index 3d085d875..d449c5f77 100644 --- a/src/knot/zone/semantic-check.c +++ b/src/knot/zone/semantic-check.c @@ -173,7 +173,8 @@ static int check_delegation(const zone_node_t *node, semchecks_data_t *data) const knot_dname_t *ns_dname = knot_ns_name(ns_rr); const zone_node_t *glue_node = NULL, *glue_encloser = NULL; int ret = zone_contents_find_dname(data->zone, ns_dname, &glue_node, - &glue_encloser, NULL); + &glue_encloser, NULL, + knot_dname_with_null(ns_dname)); switch (ret) { case KNOT_EOUTOFZONE: continue; // NS is out of bailiwick diff --git a/src/libknot/packet/pkt.c b/src/libknot/packet/pkt.c index 728bb3ee2..94bb310db 100644 --- a/src/libknot/packet/pkt.c +++ b/src/libknot/packet/pkt.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -616,6 +616,11 @@ int knot_pkt_parse_question(knot_pkt_t *pkt) /* Copy QNAME and canonicalize to lowercase. */ knot_dname_copy_lower(pkt->lower_qname, pkt->wire + KNOT_WIRE_HEADER_SIZE); + size_t str_len = strnlen((char *)(pkt->wire + KNOT_WIRE_HEADER_SIZE), pkt->qname_size) + 1; + if (pkt->qname_size != str_len) { + pkt->flags |= KNOT_PF_NULLBYTE; + } + return KNOT_EOK; } diff --git a/src/libknot/packet/pkt.h b/src/libknot/packet/pkt.h index da69c8cfc..ac7ac1851 100644 --- a/src/libknot/packet/pkt.h +++ b/src/libknot/packet/pkt.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> +/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,6 +52,7 @@ enum { KNOT_PF_NOCANON = 1 << 5, /*!< Don't canonicalize rrsets during parsing. */ KNOT_PF_ORIGTTL = 1 << 6, /*!< Write RRSIGs with their original TTL. */ KNOT_PF_SOAMINTTL = 1 << 7, /*!< Write SOA with its minimum-ttl as TTL. */ + KNOT_PF_NULLBYTE = 1 << 8, /*!< At lest one \0 byte is present in some qname label. */ }; typedef struct knot_pkt knot_pkt_t; diff --git a/tests-extra/tests/basic/zerobyte/data/test.zone b/tests-extra/tests/basic/zerobyte/data/test.zone new file mode 100644 index 000000000..01c5592dc --- /dev/null +++ b/tests-extra/tests/basic/zerobyte/data/test.zone @@ -0,0 +1,9 @@ +$ORIGIN test. + +@ SOA dns hostmaster 2010111201 10800 3600 1209600 7200 + NS dns +dns A 192.0.2.1 + +psy TXT text +cho.psy NS example.com. +exis\000ing A 1.2.3.4 diff --git a/tests-extra/tests/basic/zerobyte/test.py b/tests-extra/tests/basic/zerobyte/test.py new file mode 100644 index 000000000..9dc736178 --- /dev/null +++ b/tests-extra/tests/basic/zerobyte/test.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 + +'''Test for zero byte in a QNAME label.''' + +from dnstest.test import Test + +t = Test() + +master = t.server("knot") +zone = t.zone("test.", storage=".") + +t.link(zone, master) + +master.dnssec(zone).enable = True +master.dnssec(zone).nsec3 = True +master.dnssec(zone).nsec3_opt_out = True + +t.start() + +master.zone_wait(zone) + +resp = master.dig("psy\\000cho.test.", "A", dnssec=True) +resp.check(rcode="NXDOMAIN") + +resp = master.dig("psy\\000cho\\000nxd.test.", "A", dnssec=True) +resp.check(rcode="NXDOMAIN") + +resp = master.dig("exis\\000ing.test.", "A", dnssec=True) +resp.check(rcode="NOERROR", rdata="1.2.3.4") + +resp = master.dig("ing.exis.test.", "A", dnssec=True) +resp.check(rcode="NXDOMAIN", nordata="1.2.3.4") + +t.end() |