fix: usr: Do not assert on synthrecord reverse mode with huge prefix

When using the `synthrecord` plugin in reverse mode, if a very long
prefix is configured by the operator such that there is no room to fit
the reversed IP address into a DNS name, `named` could assert. This has
now been fixed. In such situations, an error is logged so the operator
is aware of the problem, and `NXDOMAIN` is answered.

Closes #6115

Merge branch '6115-synthrecord-prefix' into 'main'

See merge request isc-projects/bind9!12173
This commit is contained in:
Colin Vidal 2026-06-04 13:53:39 +02:00
commit fe8f3d9e81
3 changed files with 35 additions and 2 deletions

View file

@ -67,7 +67,7 @@ synthrecord_reverseanswer(synthrecord_t *inst, isc_netaddr_t *na,
REQUIRE(na->family == AF_INET || na->family == AF_INET6);
isc_buffer_init(&b, bdata, sizeof(bdata));
isc_buffer_copyregion(&b, &inst->prefix);
RETERR(isc_buffer_copyregion(&b, &inst->prefix));
isc_buffer_init(&addrb, addrbdata, sizeof(addrbdata));
RETERR(isc_netaddr_totext(na, &addrb));
@ -86,6 +86,9 @@ synthrecord_reverseanswer(synthrecord_t *inst, isc_netaddr_t *na,
*/
isc_buffer_peekuint8(&addrb, &c);
if (c == ':') {
if (isc_buffer_availablelength(&b) == 0) {
return ISC_R_NOSPACE;
}
isc_buffer_putuint8(&b, '0');
}
@ -96,12 +99,15 @@ synthrecord_reverseanswer(synthrecord_t *inst, isc_netaddr_t *na,
isc_buffer_forward(&addrb, isc_buffer_usedlength(&addrb) - 1);
isc_buffer_peekuint8(&addrb, &c);
if (c == ':') {
if (isc_buffer_availablelength(&b) == 0) {
return ISC_R_NOSPACE;
}
isc_buffer_putuint8(&addrb, '0');
}
}
isc_buffer_usedregion(&addrb, &addrr);
isc_buffer_copyregion(&b, &addrr);
RETERR(isc_buffer_copyregion(&b, &addrr));
/*
* Do not attempt to replace anything in the prefix
@ -214,6 +220,13 @@ synthrecord_parseforward(synthrecord_t *inst, const dns_name_t *name,
isc_buffer_init(&b, bdata, sizeof(bdata));
dns_name_totext(&label, DNS_NAME_OMITFINALDOT, &b);
/*
* Buffer is `DNS_NAME_FORMATSIZE` which is the maximum length of
* `dns_name_totext()` can put in there, plus one byte which we're
* setting here. So we know there is at least one remaining byte in the
* buffer.
*/
isc_buffer_putuint8(&b, 0);
if (strncmp((const char *)inst->prefix.base, isc_buffer_base(&b),
inst->prefix.length) != 0)

View file

@ -1,4 +1,5 @@
{% set inview = inview | default(False) %}
{% set toolongprefix = toolongprefix | default(False) %}
options {
query-source address 10.53.0.1;
@ -56,7 +57,11 @@ zone "0.53.10.in-addr.arpa." {
type primary;
file "0.53.10.in-addr.arpa.db";
plugin query "@TOP_BUILDDIR@/synthrecord" {
{% if toolongprefix %}
prefix "waytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefixwaytoolongprefix";
{% else %}
prefix "dynamic-";
{% endif %}
allow-synth {
10.53.0.0/24;
};

View file

@ -508,3 +508,18 @@ def test_synthrecord_inview(ns1, templates):
cmd = ns1.rndc("reconfig", raise_on_exception=False)
assert cmd.rc != 0
watcher.wait_for_line("'synthrecord' must be configured as a zone plugin")
def test_synthrecord_toolongprefix(ns1, templates):
templates.render("ns1/named.conf", {"toolongprefix": True})
with ns1.watch_log_from_here() as watcher:
ns1.rndc("reconfig")
watcher.wait_for_line("running")
ip = IPv4Address("10.53.0.8")
with ns1.watch_log_from_here() as watcher:
query = dns.message.make_query(ip.reverse_pointer, "PTR")
res = isctest.query.udp(query, SERVER)
assert res.rcode() == dns.rcode.NXDOMAIN
watcher.wait_for_line(
"synthrecord cannot create reverse answer name: ran out of space"
)