summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/knot/nameserver/internet.c2
-rw-r--r--src/knot/nameserver/nsec_proofs.c3
-rw-r--r--src/knot/zone/contents.c16
-rw-r--r--src/knot/zone/contents.h4
-rw-r--r--src/knot/zone/node.c6
-rw-r--r--src/knot/zone/node.h4
-rw-r--r--src/knot/zone/semantic-check.c3
-rw-r--r--src/libknot/packet/pkt.c7
-rw-r--r--src/libknot/packet/pkt.h3
-rw-r--r--tests-extra/tests/basic/zerobyte/data/test.zone9
-rw-r--r--tests-extra/tests/basic/zerobyte/test.py34
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()