]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Process dig -x reverse octets iteratively 11928/head
authorOndřej Surý <ondrej@isc.org>
Thu, 30 Apr 2026 09:31:51 +0000 (11:31 +0200)
committerOndřej Surý <ondrej@isc.org>
Thu, 30 Apr 2026 12:02:02 +0000 (14:02 +0200)
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
bin/dig/dighost.c
bin/tests/system/digdelv/tests.sh

index 4c0af64d64b54c875b402d792104133aab81611f..145f9c930655efd711a0721d8fc38e0b99601309 100644 (file)
@@ -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
index 977812ab002fad72cc0a20f72a92895ab59da2d4..7cccfeb73ab4a5b3f076ce76a269d5119bf8a622 100644 (file)
@@ -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