]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Reject negative and out-of-range TTLs in dnssec-* tools
authorOndřej Surý <ondrej@isc.org>
Thu, 30 Apr 2026 11:16:12 +0000 (13:16 +0200)
committerOndřej Surý <ondrej@isc.org>
Thu, 30 Apr 2026 15:40:19 +0000 (17:40 +0200)
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
bin/tests/system/dnssectools/tests.sh

index f03d2e8265b6ad3164540a2d0ea2d61bdf2f0356..7dc8db1d5b1b5abef6c2fa2f777489aacb14a636 100644 (file)
  * DNSSEC Support Routines.
  */
 
+#include <ctype.h>
+#include <errno.h>
 #include <inttypes.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stdlib.h>
 #include <unistd.h>
@@ -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++;
+       }
+       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);
        }
-       ttl = time_units(ttl, endp, orig);
-       return ttl;
+       return time_units((dns_ttl_t)val, endp, orig);
 }
 
 dst_key_state_t
index 96e4500b5797fd3b39c7ae1750e5c0ab39e63de7..8b5065b19b43a66c838e676b49dad8a7264b676a 100644 (file)
@@ -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