From a8699cb8ece7a05dd5856d6cbdb3c221f3568535 Mon Sep 17 00:00:00 2001 From: Paul Vixie Date: Sat, 9 May 2026 14:39:21 -0700 Subject: [PATCH 1/2] fix several latent bugs across parsing, OOM handling, and option validation asinfo.c: inet_pton() returns 0 (not -1) on malformed addresses; the old check let bogus rdata through with an uninitialized stack buffer. Also use the canonical h_errno for the existence probe and propagate res_ninit() failure instead of charging ahead. dnsdbq.c: -O case in qparam_option() referred to optarg instead of its own arg parameter; harmless today but a footgun. wordexp() return is now checked before dereferencing we_wordv. Reject -p minimal -J at parse time -- ruminate_json() builds a query without a mode, which previously tripped abort() inside present_minimal_lookup(). netio.c: realloc() / asprintf() failure no longer slides into a NULL deref. The earlier asprintf cast turned a -1 return into SIZE_MAX and the realloc results were unchecked. Also handle fdopen() failure on the sort pipes. pdns.c: tuple_make() now requires rrname/rrtype/rdata up front, instead of leaving NULL pointers for sortable_rdatum() and the sort-key fprintf() to dereference on malformed records. time.c: avoid imaxabs(INTMAX_MIN) UB in the relative-time path. tokstr.h: drop a duplicate prototype. Co-Authored-By: Claude Opus 4.7 (1M context) --- asinfo.c | 7 ++-- dnsdbq.c | 8 ++-- netio.c | 24 +++++++++--- pdns.c | 109 +++++++++++++++++++++++++++++-------------------------- time.c | 3 +- tokstr.h | 3 -- 6 files changed, 86 insertions(+), 68 deletions(-) diff --git a/asinfo.c b/asinfo.c index 573a662..495a99b 100644 --- a/asinfo.c +++ b/asinfo.c @@ -84,7 +84,7 @@ asinfo_domain_exists(const char *domain) { u_char buf[NS_PACKETSZ]; return res_query(domain, ns_c_in, ns_t_txt, buf, sizeof buf) > 0 || - _res.res_h_errno != HOST_NOT_FOUND; + h_errno != HOST_NOT_FOUND; } /* asinfo_shutdown() -- deallocate underlying library's heap resources @@ -108,7 +108,7 @@ asinfo_from_ipv4(const char *addr, char **asnum, char **cidr) { u_char a4[32/8]; char *dname; - if (inet_pton(AF_INET, addr, a4) < 0) + if (inet_pton(AF_INET, addr, a4) == 0) return strdup(strerror(errno)); int n = asprintf(&dname, "%d.%d.%d.%d.%s", a4[3], a4[2], a4[1], a4[0], asinfo_domain); @@ -175,7 +175,8 @@ asinfo_from_dns(const char *dname, char **asnum, char **cidr) { DEBUG(1, true, "asinfo_from_dns(%s)\n", dname); if ((res.options & RES_INIT) == 0) - res_ninit(&res); + if (res_ninit(&res) < 0) + return strdup("res_ninit failed"); n = res_nquery(&res, dname, ns_c_in, ns_t_txt, buf, sizeof buf); if (n < 0) { if (res.res_h_errno == HOST_NOT_FOUND) diff --git a/dnsdbq.c b/dnsdbq.c index e3fcee7..5b304ac 100644 --- a/dnsdbq.c +++ b/dnsdbq.c @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -621,6 +620,8 @@ main(int argc, char *argv[]) { usage("can't mix -g with -J"); if (qp.offset != 0) usage("can't mix -O with -J"); + if (presentation == pres_minimal) + usage("can't mix -p minimal with -J"); ruminate_json(json_fd, &qp); close(json_fd); } else if (batching != batch_none) { @@ -978,7 +979,7 @@ qparam_option(int opt, const char *arg, qparam_t qpp) { qpp->explicit_output_limit = qpp->output_limit; break; case 'O': - if (!parse_long(optarg, &qpp->offset) || + if (!parse_long(arg, &qpp->offset) || (qpp->offset < 0)) return "-O must be zero or positive"; break; @@ -1029,7 +1030,8 @@ select_config(void) { for (conf = conf_files; *conf != NULL; conf++) { wordexp_t we; - wordexp(*conf, &we, WRDE_NOCMD); + if (wordexp(*conf, &we, WRDE_NOCMD) != 0) + continue; cf = strdup(we.we_wordv[0]); wordfree(&we); if (access(cf, R_OK) == 0) { diff --git a/netio.c b/netio.c index ab86266..9f22e72 100644 --- a/netio.c +++ b/netio.c @@ -234,7 +234,11 @@ writer_init(long output_limit, ps_user_t ps_user, bool meta_query) { exec_sort(p1, p2); close(p1[0]); writer->sort_stdin = fdopen(p1[1], "w"); + if (writer->sort_stdin == NULL) + my_panic(true, "fdopen"); writer->sort_stdout = fdopen(p2[0], "r"); + if (writer->sort_stdout == NULL) + my_panic(true, "fdopen"); close(p2[1]); } @@ -278,6 +282,7 @@ writer_func(char *ptr, size_t size, size_t nmemb, void *blob) { writer_t writer = query->writer; qparam_ct qp = &query->qp; size_t bytes = size * nmemb; + char *x; char *nl; DEBUG(3, true, "writer_func(%d, %d): %d\n", @@ -306,7 +311,11 @@ writer_func(char *ptr, size_t size, size_t nmemb, void *blob) { } } - fetch->buf = realloc(fetch->buf, fetch->len + bytes); + x = realloc(fetch->buf, fetch->len + bytes); + if (x == NULL) + my_panic(true, "realloc"); + fetch->buf = x; + x = NULL; memcpy(fetch->buf + fetch->len, ptr, bytes); fetch->len += bytes; @@ -365,8 +374,11 @@ writer_func(char *ptr, size_t size, size_t nmemb, void *blob) { fetch->stopped = true; } else if (writer->meta_query) { /* concatenate this fragment (incl \n) to ps_buf. */ - writer->ps_buf = realloc(writer->ps_buf, - writer->ps_len + pre_len + 1); + x = realloc(writer->ps_buf, writer->ps_len + pre_len + 1); + if (x == NULL) + my_panic(true, "realloc"); + writer->ps_buf = x; + x = NULL; memcpy(writer->ps_buf + writer->ps_len, fetch->buf, pre_len + 1); writer->ps_len += pre_len + 1; @@ -431,11 +443,13 @@ last_fetch(fetch_t fetch) { writer->active = NULL; } assert(writer->ps_buf == NULL && writer->ps_len == 0); - writer->ps_len = (size_t) - asprintf(&writer->ps_buf, "-- %s (%s)\n", + int x = asprintf(&writer->ps_buf, "-- %s (%s)\n", or_else(query->status, status_noerror), or_else(query->message, or_else(fetch->saf_msg, "no error"))); + if (x < 0) + my_panic(true, "asprintf"); + writer->ps_len = (size_t) x; if (npaused > 0) { query_t unpause; fetch_t ufetch; diff --git a/pdns.c b/pdns.c index 344baf3..2cdaba2 100644 --- a/pdns.c +++ b/pdns.c @@ -762,58 +762,63 @@ tuple_make(pdns_tuple_t tup, const char *buf, size_t len) { /* Records. */ tup->obj.rrname = json_object_get(tup->obj.cof_obj, "rrname"); - if (tup->obj.rrname != NULL) { - if (!json_is_string(tup->obj.rrname)) { - msg = "rrname must be a string"; - goto ouch; - } - - char *r = strdup(json_string_value(tup->obj.rrname)); - int dot = 0; - - if ((transforms & TRANS_REVERSE) != 0) { - char *t = reverse(r); - DESTROY(r); - r = t; - t = NULL; - /* leading dot comes from reverse() */ - if ((transforms & TRANS_CHOMP) != 0) - dot = 1; - } else if ((transforms & TRANS_CHOMP) != 0) { - /* unescaped trailing dot? */ - size_t l = strlen(r); - if (l > 0 && r[l-1] == '.' && - (l == 1 || r[l-2] != '\\')) - r[l-1] = '\0'; - } - - if (dot) { - /* in chomp+reverse, the dot to chomp is now leading. */ - tup->rrname = strdup(r + dot); - DESTROY(r); - } else { - tup->rrname = r; - } + if (tup->obj.rrname == NULL) { + msg = "rrname is required"; + goto ouch; } - tup->obj.rrtype = json_object_get(tup->obj.cof_obj, "rrtype"); - if (tup->obj.rrtype != NULL) { - if (!json_is_string(tup->obj.rrtype)) { - msg = "rrtype must be a string"; - goto ouch; - } - tup->rrtype = json_string_value(tup->obj.rrtype); - } - tup->obj.rdata = json_object_get(tup->obj.cof_obj, "rdata"); - if (tup->obj.rdata != NULL) { - if (json_is_string(tup->obj.rdata)) { - tup->rdata = json_string_value(tup->obj.rdata); - } else if (!json_is_array(tup->obj.rdata)) { - msg = "rdata must be a string or array"; - goto ouch; - } - /* N.b., the array case is for the consumer to iterate over. */ + if (!json_is_string(tup->obj.rrname)) { + msg = "rrname must be a string"; + goto ouch; } + char *r = strdup(json_string_value(tup->obj.rrname)); + int dot = 0; + + if ((transforms & TRANS_REVERSE) != 0) { + char *t = reverse(r); + DESTROY(r); + r = t; + t = NULL; + /* leading dot comes from reverse() */ + if ((transforms & TRANS_CHOMP) != 0) + dot = 1; + } else if ((transforms & TRANS_CHOMP) != 0) { + /* unescaped trailing dot? */ + size_t l = strlen(r); + if (l > 0 && r[l - 1] == '.' && (l == 1 || r[l - 2] != '\\')) + r[l - 1] = '\0'; + } + + if (dot) { + /* in chomp+reverse, the dot to chomp is now leading. */ + tup->rrname = strdup(r + dot); + DESTROY(r); + } else { + tup->rrname = r; + } + tup->obj.rrtype = json_object_get(tup->obj.cof_obj, "rrtype"); + if (tup->obj.rrtype == NULL) { + msg = "rrtype is required"; + goto ouch; + } + if (!json_is_string(tup->obj.rrtype)) { + msg = "rrtype must be a string"; + goto ouch; + } + tup->rrtype = json_string_value(tup->obj.rrtype); + tup->obj.rdata = json_object_get(tup->obj.cof_obj, "rdata"); + if (tup->obj.rdata == NULL) { + msg = "rdata is required"; + goto ouch; + } + if (json_is_string(tup->obj.rdata)) { + tup->rdata = json_string_value(tup->obj.rdata); + } else if (!json_is_array(tup->obj.rdata)) { + msg = "rdata must be a string or array"; + goto ouch; + } + /* N.b., the array case is for the consumer to iterate over. */ + assert(msg == NULL); return NULL; @@ -1089,7 +1094,7 @@ pick_system(const char *name, const char *context) { } if (msg != NULL) { - my_logf("%s (in %s)\n", msg, context); + my_logf("%s (in %s)", msg, context); DESTROY(msg); my_exit(1); } @@ -1145,7 +1150,7 @@ read_config(void) { l++; if (strchr(line, '\n') == NULL) { - my_logf("conf line #%d: too long", l); + my_logf("conf line #%d: indecipherable", l); my_exit(1); } tok1 = strtok_r(line, "\040\012", &saveptr); @@ -1166,7 +1171,7 @@ read_config(void) { if (strcmp(tok2, "system") == 0 && !psys_specified) { pick_system(tok3, config_file); if (psys == NULL) { - my_logf("unknown %s %s\n", + my_logf("unknown %s %s", DNSDBQ_SYSTEM, tok3); my_exit(1); diff --git a/time.c b/time.c index 9dcdd28..c537b01 100644 --- a/time.c +++ b/time.c @@ -112,8 +112,7 @@ time_get(const char *src, u_long *dst) { ll = strtoll(src, &ep, 10); if (*src != '\0' && *ep == '\0') { if (ll < 0) - *dst = (u_long)startup_time.tv_sec - - (u_long)imaxabs(ll); + *dst = (u_long)(startup_time.tv_sec + ll); else *dst = (u_long)ll; return 1; diff --git a/tokstr.h b/tokstr.h index 23eb652..1c97f74 100644 --- a/tokstr.h +++ b/tokstr.h @@ -46,9 +46,6 @@ struct tokstr *tokstr_region(struct tokstr_reg); // tokstr_string -- create an iterator for a nul-terminated string struct tokstr *tokstr_string(const char *); -// tokstr_string -- create an iterator for a nul-terminated string -struct tokstr *tokstr_string(const char *); - // tokstr_next -- return next token from an iterator (which must be free()'d) // (NULL means no more tokens are available.) char *tokstr_next(struct tokstr *, const char *); From 70ca31cbed72275cbb25795288102ecaa72f1e0c Mon Sep 17 00:00:00 2001 From: Paul Vixie Date: Sun, 10 May 2026 15:33:02 -0700 Subject: [PATCH 2/2] more from claude code + opus 3.7 --- Makefile | 5 ++++- defs.h | 1 + netio.c | 4 ++++ pdns.c | 3 ++- pdns_dnsdb.c | 3 ++- 5 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 0e40d6c..c56db59 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,10 @@ CWARN +=-Werror CDEFS = -DWANT_PDNS_DNSDB=1 -DWANT_PDNS_CIRCL=1 CGPROF = CDEBUG = -g -O3 -CFLAGS += $(CGPROF) $(CDEBUG) $(CWARN) $(CDEFS) +CPROT = -fstack-protector-strong \ + -D_FORTIFY_SOURCE=2 \ + -fPIE -pie -Wl,-z,relro,-z,now +CFLAGS += $(CGPROF) $(CDEBUG) $(CWARN) $(CDEFS) $(CPROT) INCL= $(CURLINCL) $(JANSINCL) LIBS= $(CURLLIBS) $(JANSLIBS) -lresolv # For freebsd, it requires that -lresolv _not_ be used here, use this instead of the above line: diff --git a/defs.h b/defs.h index e545333..181499f 100644 --- a/defs.h +++ b/defs.h @@ -47,6 +47,7 @@ * must not be greater than any pDNS system's concurrent connection limit. */ #define MAX_FETCHES 8 +#define MAX_FETCH_BUF (16*1024*1024) #define DNSDBQ_SYSTEM "DNSDBQ_SYSTEM" diff --git a/netio.c b/netio.c index 9f22e72..94f9bbd 100644 --- a/netio.c +++ b/netio.c @@ -311,6 +311,10 @@ writer_func(char *ptr, size_t size, size_t nmemb, void *blob) { } } + if (fetch->len + bytes > MAX_FETCH_BUF) { + printf("?? very large response\n"); + return 0; + } x = realloc(fetch->buf, fetch->len + bytes); if (x == NULL) my_panic(true, "realloc"); diff --git a/pdns.c b/pdns.c index 2cdaba2..b5f723f 100644 --- a/pdns.c +++ b/pdns.c @@ -644,7 +644,8 @@ tuple_make(pdns_tuple_t tup, const char *buf, size_t len) { my_logf("warning: json_loadb: %d:%d: %s %s", error.line, error.column, error.text, error.source); - abort(); + msg = "json_loadb failed"; + goto ouch; } if (debug_level >= 4) { char *pretty = json_dumps(tup->obj.main, JSON_INDENT(2)); diff --git a/pdns_dnsdb.c b/pdns_dnsdb.c index 0e0f79f..15e5bab 100644 --- a/pdns_dnsdb.c +++ b/pdns_dnsdb.c @@ -534,7 +534,8 @@ rate_tuple_make(rate_tuple_t tup, const char *buf, size_t len) { my_logf("warning: json_loadb: %d:%d: %s %s", error.line, error.column, error.text, error.source); - abort(); + msg = "json_loadb failed"; + goto ouch; } if (debug_level >= 4) { char *pretty = json_dumps(tup->obj.main, JSON_INDENT(2));