libc/resolv: Refactor the option parser

Start the loop by finding the end of the option name, the name-value
separator (if any), and the end of the option.  Use those pointers to
simplify matching the option name and parsing the option value, and
validate option names and values more strictly.  This means that:

* We no longer accept trailing garbage in an option name or value.  For
  instance, we would previously interpret “edns0123” as “edns0” and
  “timeout:3xyz” as “timeout:3”.  This was actually quite lucky because
  we also failed to recognize the newline at the end of the option line
  as a whitespace character.

* For options that take a numerical argument, we would previously accept
  negative values and treat non-numerical arguments as 0, while large
  numerical arguments would be capped to the option's maximum permitted
  value.  Now, any failure to parse the argument, including overflow,
  results in the option being left unchanged.

MFC after:	1 week
Relnotes:	yes
Reviewed by:	markj
Differential Revision:	https://reviews.freebsd.org/D57923
This commit is contained in:
Dag-Erling Smørgrav committed 2026-07-06 14:23:29 +02:00
1 parent aba9fffebf
commit 9bfdfecd27
1 file changed
+106 -90
+106 -90
View File
@@ -3,6 +3,7 @@
*
* Copyright (c) 1985, 1989, 1993
* The Regents of the University of California. All rights reserved.
* Copyright (c) 2026 Dag-Erling Smørgrav
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -80,6 +81,7 @@
#include <arpa/nameser.h>
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -103,9 +105,11 @@ static const char sort_mask[] = "/&";
static u_int32_t net_mask(struct in_addr);
#endif
#if !defined(isascii) /*%< XXX - could be a function */
# define isascii(c) (!(c & 0200))
#endif
#define res_space(ch) \
isspace((unsigned char)(ch))
#define res_match(str, end, word) \
(strncmp((str), (word), (end) - (str)) == 0 && \
(word)[(end) - (str)] == '\0')
/*
* Resolver state default settings.
@@ -520,129 +524,141 @@ __res_vinit(res_state statp, int preinit) {
static void
res_setoptions(res_state statp, const char *options, const char *source)
{
const char *cp = options;
int i;
const char *cp = options, *sp, *ep;
char *numend;
long num;
struct __res_state_ext *ext = statp->_u._ext.ext;
#ifdef DEBUG
if (statp->options & RES_DEBUG)
if (statp->options & RES_DEBUG) {
printf(";; res_setoptions(\"%s\", \"%s\")...\n",
options, source);
}
#endif
while (*cp) {
for (;;) {
/* skip leading and inner runs of spaces */
while (*cp == ' ' || *cp == '\t')
while (res_space(*cp))
cp++;
/* find the separator if there is one */
sp = cp;
while (*sp != '\0' && !res_space(*sp) && *sp != ':')
sp++;
/* find the end of the option */
ep = sp;
while (*ep != '\0' && !res_space(*ep))
ep++;
/* end of option line */
if (ep == cp)
break;
/* search for and process individual options */
if (!strncmp(cp, "ndots:", sizeof("ndots:") - 1)) {
i = atoi(cp + sizeof("ndots:") - 1);
if (i <= RES_MAXNDOTS)
statp->ndots = i;
else
statp->ndots = RES_MAXNDOTS;
/* note: sp < ep if implies *sp == ':' */
if (res_match(cp, sp, "ndots") && sp < ep) {
num = strtol(sp + 1, &numend, 10);
if (numend == ep && num >= 0 && num <= RES_MAXNDOTS) {
statp->ndots = num;
#ifdef DEBUG
if (statp->options & RES_DEBUG)
printf(";;\tndots=%d\n", statp->ndots);
if (statp->options & RES_DEBUG)
printf(";;\tndots=%d\n", statp->ndots);
#endif
} else if (!strncmp(cp, "timeout:", sizeof("timeout:") - 1)) {
i = atoi(cp + sizeof("timeout:") - 1);
if (i <= RES_MAXRETRANS)
statp->retrans = i;
else
statp->retrans = RES_MAXRETRANS;
}
} else if (res_match(cp, sp, "timeout") && sp < ep) {
num = strtol(sp + 1, &numend, 10);
if (numend == ep && num > 0 && num <= RES_MAXRETRANS) {
statp->retrans = num;
#ifdef DEBUG
if (statp->options & RES_DEBUG)
printf(";;\ttimeout=%d\n", statp->retrans);
if (statp->options & RES_DEBUG)
printf(";;\ttimeout=%d\n",
statp->retrans);
#endif
} else if (!strncmp(cp, "attempts:", sizeof("attempts:") - 1)){
i = atoi(cp + sizeof("attempts:") - 1);
if (i <= RES_MAXRETRY)
statp->retry = i;
else
statp->retry = RES_MAXRETRY;
}
} else if (res_match(cp, sp, "attempts") && sp < ep) {
num = strtol(sp + 1, &numend, 10);
if (numend == ep && num > 0 && num <= RES_MAXRETRY) {
statp->retry = num;
#ifdef DEBUG
if (statp->options & RES_DEBUG)
printf(";;\tattempts=%d\n", statp->retry);
if (statp->options & RES_DEBUG)
printf(";;\tattempts=%d\n",
statp->retry);
#endif
} else if (!strncmp(cp, "debug", sizeof("debug") - 1)) {
}
} else if (res_match(cp, ep, "debug")) {
#ifdef DEBUG
if (!(statp->options & RES_DEBUG)) {
printf(";; res_setoptions(\"%s\", \"%s\")..\n",
options, source);
statp->options |= RES_DEBUG;
}
#endif
statp->options |= RES_DEBUG;
#ifdef DEBUG
printf(";;\tdebug\n");
#endif
} else if (!strncmp(cp, "no_tld_query",
sizeof("no_tld_query") - 1) ||
!strncmp(cp, "no-tld-query",
sizeof("no-tld-query") - 1)) {
} else if (res_match(cp, ep, "no-tld-query") ||
res_match(cp, ep, "no_tld_query")) {
statp->options |= RES_NOTLDQUERY;
} else if (!strncmp(cp, "inet6", sizeof("inet6") - 1)) {
} else if (res_match(cp, ep, "inet6")) {
statp->options |= RES_USE_INET6;
} else if (!strncmp(cp, "insecure1", sizeof("insecure1") - 1)) {
statp->options |= RES_INSECURE1;
} else if (!strncmp(cp, "insecure2", sizeof("insecure2") - 1)) {
statp->options |= RES_INSECURE2;
} else if (!strncmp(cp, "rotate", sizeof("rotate") - 1)) {
} else if (res_match(cp, ep, "insecure1")) {
statp->options |= RES_INSECURE1;
} else if (res_match(cp, ep, "insecure2")) {
statp->options |= RES_INSECURE2;
} else if (res_match(cp, ep, "blast")) {
statp->options |= RES_BLAST;
} else if (res_match(cp, ep, "rotate")) {
statp->options |= RES_ROTATE;
} else if (!strncmp(cp, "usevc", sizeof("usevc") - 1)) {
} else if (res_match(cp, ep, "usevc")) {
statp->options |= RES_USEVC;
} else if (!strncmp(cp, "no-check-names",
sizeof("no-check-names") - 1)) {
} else if (res_match(cp, ep, "no-check-names")) {
statp->options |= RES_NOCHECKNAME;
} else if (!strncmp(cp, "reload-period:",
sizeof("reload-period:") - 1)) {
if (ext != NULL) {
ext->reload_period = (u_short)
atoi(cp + sizeof("reload-period:") - 1);
} else if (res_match(cp, sp, "reload-period") && sp < ep) {
num = strtol(sp + 1, &numend, 10);
if (numend == ep && num > 0 && num <= USHRT_MAX) {
if (ext != NULL)
ext->reload_period = (u_short)num;
#ifdef DEBUG
if (statp->options & RES_DEBUG)
printf(";;\treload-period %hu\n",
(u_short)num);
#endif
}
}
#ifdef RES_USE_EDNS0
else if (!strncmp(cp, "edns0", sizeof("edns0") - 1)) {
} else if (res_match(cp, ep, "edns0")) {
statp->options |= RES_USE_EDNS0;
}
#endif
#ifndef _LIBC
else if (!strncmp(cp, "dname", sizeof("dname") - 1)) {
} else if (res_match(cp, ep, "dname")) {
statp->options |= RES_USE_DNAME;
}
else if (!strncmp(cp, "nibble:", sizeof("nibble:") - 1)) {
if (ext == NULL)
goto skip;
cp += sizeof("nibble:") - 1;
i = MIN(strcspn(cp, " \t"), sizeof(ext->nsuffix) - 1);
strncpy(ext->nsuffix, cp, i);
ext->nsuffix[i] = '\0';
}
else if (!strncmp(cp, "nibble2:", sizeof("nibble2:") - 1)) {
if (ext == NULL)
goto skip;
cp += sizeof("nibble2:") - 1;
i = MIN(strcspn(cp, " \t"), sizeof(ext->nsuffix2) - 1);
strncpy(ext->nsuffix2, cp, i);
ext->nsuffix2[i] = '\0';
}
else if (!strncmp(cp, "v6revmode:", sizeof("v6revmode:") - 1)) {
cp += sizeof("v6revmode:") - 1;
/* "nibble" and "bitstring" used to be valid */
if (!strncmp(cp, "single", sizeof("single") - 1)) {
statp->options |= RES_NO_NIBBLE2;
} else if (!strncmp(cp, "both", sizeof("both") - 1)) {
statp->options &=
~RES_NO_NIBBLE2;
} else if (res_match(cp, sp, "nibble") && sp < ep) {
sp++;
if (ext != NULL && ep - sp < sizeof(ext->nsuffix)) {
memcpy(ext->nsuffix, sp, ep - sp);
ext->nsuffix[ep - sp] = '\0';
}
}
} else if (res_match(cp, sp, "nibble2") && sp < ep) {
sp++;
if (ext != NULL && ep - sp < sizeof(ext->nsuffix2)) {
memcpy(ext->nsuffix2, sp, ep - sp);
ext->nsuffix2[ep - sp] = '\0';
}
} else if (res_match(cp, sp, "v6revmode") && sp < ep) {
/* "nibble" and "bitstring" used to be valid */
if (res_match(sp + 1, ep, "single"))
statp->options |= RES_NO_NIBBLE2;
else if (res_match(sp + 1, ep, "both"))
statp->options &= ~RES_NO_NIBBLE2;
#ifdef DEBUG
else
printf(";;\t%.*s: unrecognized value: %.*s\n",
(int)(sp - cp), cp,
(int)(ep - sp - 1), sp + 1);
#endif
else {
/* XXX - print a warning here? */
}
#ifndef _LIBC
skip:
#endif /* !_LIBC */
} else {
#ifdef DEBUG
printf(";;\tunrecognized option: %.*s\n",
(int)(ep - cp), cp);
#endif
/* skip to next run of spaces */
while (*cp && *cp != ' ' && *cp != '\t')
cp++;
}
cp = ep;
}
}