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
This commit is contained in:
Ondřej Surý 2026-04-30 13:16:12 +02:00
parent ffb5b76dbf
commit 51774decd2
2 changed files with 52 additions and 6 deletions

View file

@ -17,7 +17,10 @@
* 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++;
}
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

View 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