bind9/bin/tools/nsec3hash.c
Michal Nowak bb8321c637 Validate nsec3hash arguments instead of relying on atoi()
The nsec3hash tool parsed its algorithm, flags, and iterations
arguments with atoi(), then range-checked the result. For values
that overflow int during digit-by-digit accumulation, atoi() is
undefined; in practice on musl libc the modular wrap leaves
n == 0, which silently passes the "iterations > 0xffffU" check.
On Alpine Linux this made nsec3hash succeed with iterations
treated as 0 for inputs like 4294967296 (2^32).

The latent bug only surfaced when the recent image rebuild pulled
in Hypothesis 6.152.9 (2026-05-19), which unified the distribution
used for bounded and unbounded integers() strategies. The new
smoother distribution explores the 2^32 boundary on unbounded
ranges like integers(min_value=65536); earlier versions did not
reach there, so test_nsec3hash_too_many_iterations only started
failing on Alpine after the image refresh.

Replace the three atoi() calls with isc_parse_uint8 /
isc_parse_uint16, which uniformly reject overflow, trailing
garbage, leading sign, and non-numeric input across libc
implementations. As a side effect, error messages now include
the offending argument and a specific reason ("out of range" vs
"not a valid number").

Assisted-by: Claude:claude-opus-4-7
(cherry picked from commit e13302a6bc)
2026-05-21 11:41:29 +00:00

203 lines
5 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <isc/attributes.h>
#include <isc/base32.h>
#include <isc/buffer.h>
#include <isc/commandline.h>
#include <isc/file.h>
#include <isc/hex.h>
#include <isc/iterated_hash.h>
#include <isc/parseint.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/tls.h>
#include <isc/types.h>
#include <isc/util.h>
#include <dns/fixedname.h>
#include <dns/name.h>
#include <dns/nsec3.h>
#include <dns/types.h>
const char *program = "nsec3hash";
noreturn static void
fatal(const char *format, ...);
static void
fatal(const char *format, ...) {
va_list args;
fprintf(stderr, "%s: ", program);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fprintf(stderr, "\n");
_exit(EXIT_FAILURE);
}
static void
check_result(isc_result_t result, const char *message) {
if (result != ISC_R_SUCCESS) {
fatal("%s: %s", message, isc_result_totext(result));
}
}
static void
usage(void) {
fprintf(stderr, "Usage: %s salt algorithm iterations domain\n",
program);
fprintf(stderr, " %s -r algorithm flags iterations salt domain\n",
program);
exit(EXIT_FAILURE);
}
typedef void
nsec3printer(unsigned int algo, unsigned int flags, unsigned int iters,
const char *saltstr, const char *domain, const char *digest);
static void
nsec3hash(nsec3printer *nsec3print, const char *algostr, const char *flagstr,
const char *iterstr, const char *saltstr, const char *domain) {
dns_fixedname_t fixed;
dns_name_t *name;
isc_buffer_t buffer;
isc_region_t region;
isc_result_t result;
unsigned char hash[NSEC3_MAX_HASH_LENGTH];
unsigned char salt[DNS_NSEC3_SALTSIZE];
unsigned char text[1024];
uint8_t hash_alg;
uint8_t flags;
unsigned int length;
uint16_t iterations;
unsigned int salt_length;
const char dash[] = "-";
if (strcmp(saltstr, "-") == 0) {
salt_length = 0;
salt[0] = 0;
} else {
isc_buffer_init(&buffer, salt, sizeof(salt));
result = isc_hex_decodestring(saltstr, &buffer);
check_result(result, "isc_hex_decodestring(salt)");
salt_length = isc_buffer_usedlength(&buffer);
if (salt_length > DNS_NSEC3_SALTSIZE) {
fatal("salt too long");
}
if (salt_length == 0) {
saltstr = dash;
}
}
result = isc_parse_uint8(&hash_alg, algostr, 10);
if (result != ISC_R_SUCCESS) {
fatal("invalid hash algorithm '%s': %s", algostr,
isc_result_totext(result));
}
if (flagstr == NULL) {
flags = 0;
} else {
result = isc_parse_uint8(&flags, flagstr, 10);
if (result != ISC_R_SUCCESS) {
fatal("invalid flags '%s': %s", flagstr,
isc_result_totext(result));
}
}
result = isc_parse_uint16(&iterations, iterstr, 10);
if (result != ISC_R_SUCCESS) {
fatal("invalid iterations '%s': %s", iterstr,
isc_result_totext(result));
}
name = dns_fixedname_initname(&fixed);
isc_buffer_constinit(&buffer, domain, strlen(domain));
isc_buffer_add(&buffer, strlen(domain));
result = dns_name_fromtext(name, &buffer, dns_rootname, 0, NULL);
check_result(result, "dns_name_fromtext() failed");
dns_name_downcase(name, name, NULL);
length = isc_iterated_hash(hash, hash_alg, iterations, salt,
salt_length, name->ndata, name->length);
if (length == 0) {
fatal("isc_iterated_hash failed");
}
region.base = hash;
region.length = length;
isc_buffer_init(&buffer, text, sizeof(text));
isc_base32hexnp_totext(&region, 1, "", &buffer);
isc_buffer_putuint8(&buffer, '\0');
nsec3print(hash_alg, flags, iterations, saltstr, domain, (char *)text);
}
static void
nsec3hash_print(unsigned int algo, unsigned int flags, unsigned int iters,
const char *saltstr, const char *domain, const char *digest) {
UNUSED(flags);
UNUSED(domain);
fprintf(stdout, "%s (salt=%s, hash=%u, iterations=%u)\n", digest,
saltstr, algo, iters);
}
static void
nsec3hash_rdata_print(unsigned int algo, unsigned int flags, unsigned int iters,
const char *saltstr, const char *domain,
const char *digest) {
fprintf(stdout, "%s NSEC3 %u %u %u %s %s\n", domain, algo, flags, iters,
saltstr, digest);
}
int
main(int argc, char *argv[]) {
bool rdata_format = false;
int ch;
while ((ch = isc_commandline_parse(argc, argv, "-r")) != -1) {
switch (ch) {
case 'r':
rdata_format = true;
break;
case '-':
isc_commandline_index -= 1;
goto skip;
default:
break;
}
}
skip:
argc -= isc_commandline_index;
argv += isc_commandline_index;
if (rdata_format) {
if (argc != 5) {
usage();
}
nsec3hash(nsec3hash_rdata_print, argv[0], argv[1], argv[2],
argv[3], argv[4]);
} else {
if (argc != 4) {
usage();
}
nsec3hash(nsec3hash_print, argv[1], NULL, argv[2], argv[0],
argv[3]);
}
return 0;
}