mirror of
https://github.com/isc-projects/bind9.git
synced 2026-07-11 10:18:51 -04:00
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)
203 lines
5 KiB
C
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(®ion, 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;
|
|
}
|