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
This commit is contained in:
Ondřej Surý 2026-04-30 11:31:51 +02:00
parent 688e8667a3
commit f1ec5e1809
2 changed files with 40 additions and 9 deletions

View 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

View 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