From 51774decd222dba52b4f6e88f73f99947d6b06d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= Date: Thu, 30 Apr 2026 13:16:12 +0200 Subject: [PATCH] Reject negative and out-of-range TTLs in dnssec-* tools strtottl() parsed the operator's TTL string with strtol() and assigned the long directly to dns_ttl_t (uint32_t) with no sign or ERANGE check. The only validation was the "no digits parsed" branch, so a fully-consumed "-1" became UINT32_MAX (~136 years) and was silently written into DNSKEY/key files by dnssec-keygen -L, dnssec-signzone -t, dnssec-settime -L, etc. Any signing pipeline interpolating the TTL from a variable could mint a key with a multi-decade TTL and never see an error. Switch to strtoul(), reject a leading '-' explicitly (strtoul silently negates), check errno == ERANGE, and reject values exceeding UINT32_MAX before handing the result to time_units(). The pre-existing multiplication wrap inside time_units() is tracked separately. Assisted-by: Claude:claude-opus-4-7 --- bin/dnssec/dnssectool.c | 31 +++++++++++++++++++++------ bin/tests/system/dnssectools/tests.sh | 27 +++++++++++++++++++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index f03d2e8265..7dc8db1d5b 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -17,7 +17,10 @@ * DNSSEC Support Routines. */ +#include +#include #include +#include #include #include #include @@ -223,19 +226,35 @@ isnone(const char *str) { dns_ttl_t strtottl(const char *str) { const char *orig = str; - dns_ttl_t ttl; + const char *p = str; + unsigned long val; char *endp; if (isnone(str)) { return (dns_ttl_t)0; } - ttl = strtol(str, &endp, 0); - if (ttl == 0 && endp == str) { - fatal("TTL must be numeric"); + /* + * strtoul() silently negates a leading '-', producing + * ULONG_MAX-class values that then truncate to a near-UINT32_MAX + * TTL. Reject the sign explicitly before parsing. + */ + while (isspace((unsigned char)*p)) { + p++; } - ttl = time_units(ttl, endp, orig); - return ttl; + if (*p == '-') { + fatal("TTL must be non-negative: %s", orig); + } + + errno = 0; + val = strtoul(str, &endp, 0); + if (endp == str) { + fatal("TTL must be numeric: %s", orig); + } + if (errno == ERANGE || val > UINT32_MAX) { + fatal("TTL %s out of range (max %u)", orig, UINT32_MAX); + } + return time_units((dns_ttl_t)val, endp, orig); } dst_key_state_t diff --git a/bin/tests/system/dnssectools/tests.sh b/bin/tests/system/dnssectools/tests.sh index 96e4500b57..8b5065b19b 100644 --- a/bin/tests/system/dnssectools/tests.sh +++ b/bin/tests/system/dnssectools/tests.sh @@ -342,6 +342,33 @@ n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) +echo_i "checking that a negative DNSKEY TTL is rejected ($n)" +ret=0 +zone=example +$KEYGEN -L -1 -a $DEFAULT_ALGORITHM $zone >dnssectools.out.test$n 2>&1 && ret=1 +grep -q "TTL must be non-negative" dnssectools.out.test$n || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +echo_i "checking that an out-of-range DNSKEY TTL is rejected ($n)" +ret=0 +zone=example +$KEYGEN -L 9999999999 -a $DEFAULT_ALGORITHM $zone >dnssectools.out.test$n 2>&1 && ret=1 +grep -q "out of range" dnssectools.out.test$n || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +echo_i "checking that a negative DNSKEY TTL with a unit suffix is rejected ($n)" +ret=0 +zone=example +$KEYGEN -L -1d -a $DEFAULT_ALGORITHM $zone >dnssectools.out.test$n 2>&1 && ret=1 +grep -q "TTL must be non-negative" dnssectools.out.test$n || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + echo_i "checking that a DS record cannot be generated for a key using an unsupported algorithm ($n)" ret=0 zone=example