From: Ondřej Surý Date: Thu, 30 Apr 2026 09:31:51 +0000 (+0200) Subject: Process dig -x reverse octets iteratively X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f1ec5e18098cd13e9e6b3a1a6579ff2ff5ab5e1c;p=thirdparty%2Fbind9.git Process dig -x reverse octets iteratively reverse_octets() recursed once per dot, with depth bounded only by ARG_MAX (~2 MiB on Linux), so feeding dig -x a deep input like '1.1.1.…1' busted the call stack and crashed the tool with SIGSEGV instead of a structured error. The transformation it performs is purely textual (split on '.', emit components in reverse), so the recursion was never load-bearing. Walk the input once into a fixed-size array of label slices, capped at DNS_NAME_MAXLABELS (which is the most we could ever fit into the result buffer anyway), then iterate the array in reverse to write the output. Inputs with more than DNS_NAME_MAXLABELS labels now return DNS_R_NAMETOOLONG, which dig.c surfaces as 'Invalid IP address' and exit 1. Drop the unnecessary (int) casts on ptrdiff_t/size_t lengths while at it. Assisted-by: Claude:claude-opus-4-7 --- diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index 4c0af64d64b..145f9c93065 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -288,16 +288,33 @@ append(const char *text, size_t len, char **p, char *end) { static isc_result_t reverse_octets(const char *in, char **p, char *end) { - const char *dot = strchr(in, '.'); - size_t len; - if (dot != NULL) { - RETERR(reverse_octets(dot + 1, p, end)); - RETERR(append(".", 1, p, end)); - len = (int)(dot - in); - } else { - len = (int)strlen(in); + const char *parts[DNS_NAME_MAXLABELS]; + size_t lens[DNS_NAME_MAXLABELS]; + size_t n = 0; + const char *cursor = in; + + while (true) { + const char *dot = strchr(cursor, '.'); + if (n >= DNS_NAME_MAXLABELS) { + return DNS_R_NAMETOOLONG; + } + parts[n] = cursor; + lens[n] = (dot != NULL) ? (size_t)(dot - cursor) + : strlen(cursor); + n++; + if (dot == NULL) { + break; + } + cursor = dot + 1; } - return append(in, len, p, end); + + for (size_t i = n; i-- > 0;) { + if (i + 1 < n) { + RETERR(append(".", 1, p, end)); + } + RETERR(append(parts[i], lens[i], p, end)); + } + return ISC_R_SUCCESS; } isc_result_t diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh index 977812ab002..7cccfeb73ab 100644 --- a/bin/tests/system/digdelv/tests.sh +++ b/bin/tests/system/digdelv/tests.sh @@ -148,6 +148,20 @@ if [ -x "$DIG" ]; then if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) + n=$((n + 1)) + echo_i "checking dig -x rejects deeply nested input cleanly ($n)" + ret=0 + longinput="$(printf '1.%.0s' $(seq 1 6400))1" + # Pre-fix: SIGSEGV (139) or ASan abort (134) from unbounded recursion in + # reverse_octets() on the dots. Post-fix: structured rejection via + # DNS_R_NAMETOOLONG -> "Invalid IP address" -> exit 1. + rc=0 + dig_with_opts -x "$longinput" >dig.out.test$n 2>&1 || rc=$? + [ $rc -ge 128 ] && ret=1 + grep "Invalid IP address" dig.out.test$n >/dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + n=$((n + 1)) echo_i "checking dig over TCP works ($n)" ret=0