diff --git a/SOURCES/unbound-1.7.3-additional-logging.patch b/SOURCES/unbound-1.7.3-additional-logging.patch new file mode 100644 index 0000000..7630422 --- /dev/null +++ b/SOURCES/unbound-1.7.3-additional-logging.patch @@ -0,0 +1,553 @@ +diff --git a/daemon/worker.c b/daemon/worker.c +index 44a989a..3acecc1 100644 +--- a/daemon/worker.c ++++ b/daemon/worker.c +@@ -1193,7 +1193,7 @@ worker_handle_request(struct comm_point* c, void* arg, int error, + if(worker->env.cfg->log_queries) { + char ip[128]; + addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); +- log_nametypeclass(0, ip, qinfo.qname, qinfo.qtype, qinfo.qclass); ++ log_query_in(ip, qinfo.qname, qinfo.qtype, qinfo.qclass); + } + if(qinfo.qtype == LDNS_RR_TYPE_AXFR || + qinfo.qtype == LDNS_RR_TYPE_IXFR) { +diff --git a/doc/Changelog b/doc/Changelog +index 3d05ae5..bb74461 100644 +--- a/doc/Changelog ++++ b/doc/Changelog +@@ -1,3 +1,15 @@ ++30 November 2018: Wouter ++ - log-tag-queryreply: yes in unbound.conf tags the log-queries and ++ log-replies in the log file for easier log filter maintenance. ++ ++21 August 2018: Wouter ++ - log-local-actions: yes option for unbound.conf that logs all the ++ local zone actions, a patch from Saksham Manchanda (Secure64). ++ ++17 August 2018: Wouter ++ - log-servfail: yes prints log lines that say why queries are ++ returning SERVFAIL to clients. ++ + 19 June 2018: Wouter + - Fix for unbound-control on Windows and set TCP socket parameters + more closely. +diff --git a/doc/example.conf.in b/doc/example.conf.in +index be83bda..aa06cee 100644 +--- a/doc/example.conf.in ++++ b/doc/example.conf.in +@@ -309,6 +309,17 @@ server: + # timetoresolve, fromcache and responsesize. + # log-replies: no + ++ # log with tag 'query' and 'reply' instead of 'info' for ++ # filtering log-queries and log-replies from the log. ++ # log-tag-queryreply: no ++ ++ # log the local-zone actions, like local-zone type inform is enabled ++ # also for the other local zone types. ++ # log-local-actions: no ++ ++ # print log lines that say why queries return SERVFAIL to clients. ++ # log-servfail: no ++ + # the pid file. Can be an absolute path outside of chroot/work dir. + # pidfile: "@UNBOUND_PIDFILE@" + +diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in +index 9167a5a..b73bc70 100644 +--- a/doc/unbound.conf.5.in ++++ b/doc/unbound.conf.5.in +@@ -618,6 +618,21 @@ Default is no. Note that it takes time to print these + lines which makes the server (significantly) slower. Odd (nonprintable) + characters in names are printed as '?'. + .TP ++.B log\-tag\-queryreply: \fI ++Prints the word 'query' and 'reply' with log\-queries and log\-replies. ++This makes filtering logs easier. The default is off (for backwards ++compatibility). ++.TP ++.B log\-local\-actions: \fI ++Print log lines to inform about local zone actions. These lines are like the ++local\-zone type inform prints out, but they are also printed for the other ++types of local zones. ++.TP ++.B log\-servfail: \fI ++Print log lines that say why queries return SERVFAIL to clients. ++This is separate from the verbosity debug logs, much smaller, and printed ++at the error level, not the info level of debug info from verbosity. ++.TP + .B pidfile: \fI + The process id is written to the file. Default is "@UNBOUND_PIDFILE@". + So, +diff --git a/services/localzone.c b/services/localzone.c +index 0f60817..a85619b 100644 +--- a/services/localzone.c ++++ b/services/localzone.c +@@ -1459,7 +1459,7 @@ lz_inform_print(struct local_zone* z, struct query_info* qinfo, + uint16_t port = ntohs(((struct sockaddr_in*)&repinfo->addr)->sin_port); + dname_str(z->name, zname); + addr_to_str(&repinfo->addr, repinfo->addrlen, ip, sizeof(ip)); +- snprintf(txt, sizeof(txt), "%s inform %s@%u", zname, ip, ++ snprintf(txt, sizeof(txt), "%s %s %s@%u", zname, local_zone_type2str(z->type), ip, + (unsigned)port); + log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); + } +@@ -1576,7 +1576,8 @@ local_zones_answer(struct local_zones* zones, struct module_env* env, + z->override_tree, &tag, tagname, num_tags); + lock_rw_unlock(&zones->lock); + } +- if((lzt == local_zone_inform || lzt == local_zone_inform_deny) ++ if((env->cfg->log_local_actions || ++ lzt == local_zone_inform || lzt == local_zone_inform_deny) + && repinfo) + lz_inform_print(z, qinfo, repinfo); + +diff --git a/services/mesh.c b/services/mesh.c +index 41aba74..55c1947 100644 +--- a/services/mesh.c ++++ b/services/mesh.c +@@ -977,7 +977,7 @@ mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, + rcode = LDNS_RCODE_SERVFAIL; + if(!rcode && (rep->security == sec_status_bogus || + rep->security == sec_status_secure_sentinel_fail)) { +- if(!(reason = errinf_to_str(&m->s))) ++ if(!(reason = errinf_to_str_bogus(&m->s))) + rcode = LDNS_RCODE_SERVFAIL; + } + /* send the reply */ +@@ -1148,6 +1148,15 @@ void mesh_query_done(struct mesh_state* mstate) + struct mesh_cb* c; + struct reply_info* rep = (mstate->s.return_msg? + mstate->s.return_msg->rep:NULL); ++ if((mstate->s.return_rcode == LDNS_RCODE_SERVFAIL || ++ (rep && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_SERVFAIL)) ++ && mstate->s.env->cfg->log_servfail ++ && !mstate->s.env->cfg->val_log_squelch) { ++ char* err = errinf_to_str_servfail(&mstate->s); ++ if(err) ++ log_err("%s", err); ++ free(err); ++ } + for(r = mstate->reply_list; r; r = r->next) { + /* if a response-ip address block has been stored the + * information should be logged for each client. */ +diff --git a/util/config_file.c b/util/config_file.c +index b061760..68a0a15 100644 +--- a/util/config_file.c ++++ b/util/config_file.c +@@ -115,6 +115,9 @@ config_create(void) + cfg->log_time_ascii = 0; + cfg->log_queries = 0; + cfg->log_replies = 0; ++ cfg->log_tag_queryreply = 0; ++ cfg->log_local_actions = 0; ++ cfg->log_servfail = 0; + #ifndef USE_WINSOCK + # ifdef USE_MINI_EVENT + /* select max 1024 sockets */ +@@ -540,6 +543,9 @@ int config_set_option(struct config_file* cfg, const char* opt, + else S_YNO("val-log-squelch:", val_log_squelch) + else S_YNO("log-queries:", log_queries) + else S_YNO("log-replies:", log_replies) ++ else S_YNO("log-tag-queryreply:", log_tag_queryreply) ++ else S_YNO("log-local-actions:", log_local_actions) ++ else S_YNO("log-servfail:", log_servfail) + else S_YNO("val-permissive-mode:", val_permissive_mode) + else S_YNO("aggressive-nsec:", aggressive_nsec) + else S_YNO("ignore-cd-flag:", ignore_cd) +@@ -893,6 +899,9 @@ config_get_option(struct config_file* cfg, const char* opt, + else O_STR(opt, "logfile", logfile) + else O_YNO(opt, "log-queries", log_queries) + else O_YNO(opt, "log-replies", log_replies) ++ else O_YNO(opt, "log-tag-queryreply", log_tag_queryreply) ++ else O_YNO(opt, "log-local-actions", log_local_actions) ++ else O_YNO(opt, "log-servfail", log_servfail) + else O_STR(opt, "pidfile", pidfile) + else O_YNO(opt, "hide-identity", hide_identity) + else O_YNO(opt, "hide-version", hide_version) +@@ -1845,6 +1854,7 @@ config_apply(struct config_file* config) + EDNS_ADVERTISED_SIZE = (uint16_t)config->edns_buffer_size; + MINIMAL_RESPONSES = config->minimal_responses; + RRSET_ROUNDROBIN = config->rrset_roundrobin; ++ LOG_TAG_QUERYREPLY = config->log_tag_queryreply; + log_set_time_asc(config->log_time_ascii); + autr_permit_small_holddown = config->permit_small_holddown; + } +@@ -2220,7 +2230,7 @@ void errinf_origin(struct module_qstate* qstate, struct sock_list *origin) + } + } + +-char* errinf_to_str(struct module_qstate* qstate) ++char* errinf_to_str_bogus(struct module_qstate* qstate) + { + char buf[20480]; + char* p = buf; +@@ -2245,6 +2255,31 @@ char* errinf_to_str(struct module_qstate* qstate) + return p; + } + ++char* errinf_to_str_servfail(struct module_qstate* qstate) ++{ ++ char buf[20480]; ++ char* p = buf; ++ size_t left = sizeof(buf); ++ struct config_strlist* s; ++ char dname[LDNS_MAX_DOMAINLEN+1]; ++ char t[16], c[16]; ++ sldns_wire2str_type_buf(qstate->qinfo.qtype, t, sizeof(t)); ++ sldns_wire2str_class_buf(qstate->qinfo.qclass, c, sizeof(c)); ++ dname_str(qstate->qinfo.qname, dname); ++ snprintf(p, left, "SERVFAIL <%s %s %s>:", dname, t, c); ++ left -= strlen(p); p += strlen(p); ++ if(!qstate->errinf) ++ snprintf(p, left, " misc failure"); ++ else for(s=qstate->errinf; s; s=s->next) { ++ snprintf(p, left, " %s", s->str); ++ left -= strlen(p); p += strlen(p); ++ } ++ p = strdup(buf); ++ if(!p) ++ log_err("malloc failure in errinf_to_str"); ++ return p; ++} ++ + void errinf_rrset(struct module_qstate* qstate, struct ub_packed_rrset_key *rr) + { + char buf[1024]; +diff --git a/util/config_file.h b/util/config_file.h +index 4206eb9..1e7f402 100644 +--- a/util/config_file.h ++++ b/util/config_file.h +@@ -268,6 +268,12 @@ struct config_file { + int log_queries; + /** log replies with one line per reply */ + int log_replies; ++ /** tag log_queries and log_replies for filtering */ ++ int log_tag_queryreply; ++ /** log every local-zone hit **/ ++ int log_local_actions; ++ /** log servfails with a reason */ ++ int log_servfail; + /** log identity to report */ + char* log_identity; + +@@ -1070,7 +1076,15 @@ void errinf_dname(struct module_qstate* qstate, const char* str, + * @return string or NULL on malloc failure (already logged). + * This string is malloced and has to be freed by caller. + */ +-char* errinf_to_str(struct module_qstate* qstate); ++char* errinf_to_str_bogus(struct module_qstate* qstate); ++ ++/** ++ * Create error info in string. For other servfails. ++ * @param qstate: query state. ++ * @return string or NULL on malloc failure (already logged). ++ * This string is malloced and has to be freed by caller. ++ */ ++char* errinf_to_str_servfail(struct module_qstate* qstate); + + /** + * Used during options parsing +diff --git a/util/configlexer.lex b/util/configlexer.lex +index 6124e32..9b22db1 100644 +--- a/util/configlexer.lex ++++ b/util/configlexer.lex +@@ -366,6 +366,9 @@ log-identity{COLON} { YDVAR(1, VAR_LOG_IDENTITY) } + log-time-ascii{COLON} { YDVAR(1, VAR_LOG_TIME_ASCII) } + log-queries{COLON} { YDVAR(1, VAR_LOG_QUERIES) } + log-replies{COLON} { YDVAR(1, VAR_LOG_REPLIES) } ++log-tag-queryreply{COLON} { YDVAR(1, VAR_LOG_TAG_QUERYREPLY) } ++log-local-actions{COLON} { YDVAR(1, VAR_LOG_LOCAL_ACTIONS) } ++log-servfail{COLON} { YDVAR(1, VAR_LOG_SERVFAIL) } + local-zone{COLON} { YDVAR(2, VAR_LOCAL_ZONE) } + local-data{COLON} { YDVAR(1, VAR_LOCAL_DATA) } + local-data-ptr{COLON} { YDVAR(1, VAR_LOCAL_DATA_PTR) } +diff --git a/util/configparser.y b/util/configparser.y +index e34665a..4b23a71 100644 +--- a/util/configparser.y ++++ b/util/configparser.y +@@ -106,7 +106,7 @@ extern struct config_parser_state* cfg_parser; + %token VAR_AUTO_TRUST_ANCHOR_FILE VAR_KEEP_MISSING VAR_ADD_HOLDDOWN + %token VAR_DEL_HOLDDOWN VAR_SO_RCVBUF VAR_EDNS_BUFFER_SIZE VAR_PREFETCH + %token VAR_PREFETCH_KEY VAR_SO_SNDBUF VAR_SO_REUSEPORT VAR_HARDEN_BELOW_NXDOMAIN +-%token VAR_IGNORE_CD_FLAG VAR_LOG_QUERIES VAR_LOG_REPLIES ++%token VAR_IGNORE_CD_FLAG VAR_LOG_QUERIES VAR_LOG_REPLIES VAR_LOG_LOCAL_ACTIONS + %token VAR_TCP_UPSTREAM VAR_SSL_UPSTREAM + %token VAR_SSL_SERVICE_KEY VAR_SSL_SERVICE_PEM VAR_SSL_PORT VAR_FORWARD_FIRST + %token VAR_STUB_SSL_UPSTREAM VAR_FORWARD_SSL_UPSTREAM VAR_TLS_CERT_BUNDLE +@@ -158,6 +158,8 @@ extern struct config_parser_state* cfg_parser; + %token VAR_AUTH_ZONE VAR_ZONEFILE VAR_MASTER VAR_URL VAR_FOR_DOWNSTREAM + %token VAR_FALLBACK_ENABLED VAR_TLS_ADDITIONAL_PORT VAR_LOW_RTT VAR_LOW_RTT_PERMIL + %token VAR_ALLOW_NOTIFY VAR_TLS_WIN_CERT ++%token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL ++%token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY + + %% + toplevelvars: /* empty */ | toplevelvars toplevelvar ; +@@ -217,6 +219,7 @@ content_server: server_num_threads | server_verbosity | server_port | + server_edns_buffer_size | server_prefetch | server_prefetch_key | + server_so_sndbuf | server_harden_below_nxdomain | server_ignore_cd_flag | + server_log_queries | server_log_replies | server_tcp_upstream | server_ssl_upstream | ++ server_log_local_actions | + server_ssl_service_key | server_ssl_service_pem | server_ssl_port | + server_minimal_responses | server_rrset_roundrobin | server_max_udp_size | + server_so_reuseport | server_delay_close | +@@ -249,7 +252,9 @@ content_server: server_num_threads | server_verbosity | server_port | + server_ipsecmod_whitelist | server_ipsecmod_strict | + server_udp_upstream_without_downstream | server_aggressive_nsec | + server_tls_cert_bundle | server_tls_additional_port | server_low_rtt | +- server_low_rtt_permil | server_tls_win_cert ++ server_low_rtt_permil | server_tls_win_cert | ++ server_tcp_connection_limit | server_log_servfail | ++ server_unknown_server_time_limit | server_log_tag_queryreply + ; + stubstart: VAR_STUB_ZONE + { +@@ -764,6 +769,33 @@ server_log_replies: VAR_LOG_REPLIES STRING_ARG + free($2); + } + ; ++server_log_tag_queryreply: VAR_LOG_TAG_QUERYREPLY STRING_ARG ++ { ++ OUTYY(("P(server_log_tag_queryreply:%s)\n", $2)); ++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) ++ yyerror("expected yes or no."); ++ else cfg_parser->cfg->log_tag_queryreply = (strcmp($2, "yes")==0); ++ free($2); ++ } ++ ; ++server_log_servfail: VAR_LOG_SERVFAIL STRING_ARG ++ { ++ OUTYY(("P(server_log_servfail:%s)\n", $2)); ++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) ++ yyerror("expected yes or no."); ++ else cfg_parser->cfg->log_servfail = (strcmp($2, "yes")==0); ++ free($2); ++ } ++ ; ++server_log_local_actions: VAR_LOG_LOCAL_ACTIONS STRING_ARG ++ { ++ OUTYY(("P(server_log_local_actions:%s)\n", $2)); ++ if(strcmp($2, "yes") != 0 && strcmp($2, "no") != 0) ++ yyerror("expected yes or no."); ++ else cfg_parser->cfg->log_local_actions = (strcmp($2, "yes")==0); ++ free($2); ++ } ++ ; + server_chroot: VAR_CHROOT STRING_ARG + { + OUTYY(("P(server_chroot:%s)\n", $2)); +diff --git a/util/data/msgreply.c b/util/data/msgreply.c +index 772f5d1..df2131c 100644 +--- a/util/data/msgreply.c ++++ b/util/data/msgreply.c +@@ -844,7 +844,9 @@ log_reply_info(enum verbosity_value v, struct query_info *qinf, + addr_to_str(addr, addrlen, clientip_buf, sizeof(clientip_buf)); + if(rcode == LDNS_RCODE_FORMERR) + { +- log_info("%s - - - %s - - - ", clientip_buf, rcode_buf); ++ if(LOG_TAG_QUERYREPLY) ++ log_reply("%s - - - %s - - - ", clientip_buf, rcode_buf); ++ else log_info("%s - - - %s - - - ", clientip_buf, rcode_buf); + } else { + if(qinf->qname) + dname_str(qinf->qname, qname_buf); +@@ -852,7 +854,11 @@ log_reply_info(enum verbosity_value v, struct query_info *qinf, + pktlen = sldns_buffer_limit(rmsg); + sldns_wire2str_type_buf(qinf->qtype, type_buf, sizeof(type_buf)); + sldns_wire2str_class_buf(qinf->qclass, class_buf, sizeof(class_buf)); +- log_info("%s %s %s %s %s " ARG_LL "d.%6.6d %d %d", ++ if(LOG_TAG_QUERYREPLY) ++ log_reply("%s %s %s %s %s " ARG_LL "d.%6.6d %d %d", ++ clientip_buf, qname_buf, type_buf, class_buf, ++ rcode_buf, (long long)dur.tv_sec, (int)dur.tv_usec, cached, (int)pktlen); ++ else log_info("%s %s %s %s %s " ARG_LL "d.%6.6d %d %d", + clientip_buf, qname_buf, type_buf, class_buf, + rcode_buf, (long long)dur.tv_sec, (int)dur.tv_usec, cached, (int)pktlen); + } +diff --git a/util/log.c b/util/log.c +index 43dd572..fff4319 100644 +--- a/util/log.c ++++ b/util/log.c +@@ -391,6 +391,24 @@ log_hex(const char* msg, void* data, size_t length) + log_hex_f(verbosity, msg, data, length); + } + ++void ++log_query(const char *format, ...) ++{ ++ va_list args; ++ va_start(args, format); ++ log_vmsg(LOG_INFO, "query", format, args); ++ va_end(args); ++} ++ ++void ++log_reply(const char *format, ...) ++{ ++ va_list args; ++ va_start(args, format); ++ log_vmsg(LOG_INFO, "reply", format, args); ++ va_end(args); ++} ++ + void log_buf(enum verbosity_value level, const char* msg, sldns_buffer* buf) + { + if(verbosity < level) +diff --git a/util/log.h b/util/log.h +index 7bc3d9e..172cd01 100644 +--- a/util/log.h ++++ b/util/log.h +@@ -160,6 +160,20 @@ void log_warn(const char* format, ...) ATTR_FORMAT(printf, 1, 2); + */ + void log_hex(const char* msg, void* data, size_t length); + ++/** ++ * Log query. ++ * Pass printf formatted arguments. No trailing newline is needed. ++ * @param format: printf-style format string. Arguments follow. ++ */ ++void log_query(const char* format, ...) ATTR_FORMAT(printf, 1, 2); ++ ++/** ++ * Log reply. ++ * Pass printf formatted arguments. No trailing newline is needed. ++ * @param format: printf-style format string. Arguments follow. ++ */ ++void log_reply(const char* format, ...) ATTR_FORMAT(printf, 1, 2); ++ + /** + * Easy alternative for log_hex, takes a sldns_buffer. + * @param level: verbosity level for this message, compared to global +diff --git a/util/net_help.c b/util/net_help.c +index a193c36..617a896 100644 +--- a/util/net_help.c ++++ b/util/net_help.c +@@ -67,6 +67,9 @@ int MINIMAL_RESPONSES = 0; + /** rrset order roundrobin: default is no */ + int RRSET_ROUNDROBIN = 0; + ++/** log tag queries with name instead of 'info' for filtering */ ++int LOG_TAG_QUERYREPLY = 0; ++ + /* returns true is string addr is an ip6 specced address */ + int + str_is_ip6(const char* str) +@@ -335,7 +338,7 @@ log_nametypeclass(enum verbosity_value v, const char* str, uint8_t* name, + { + char buf[LDNS_MAX_DOMAINLEN+1]; + char t[12], c[12]; +- const char *ts, *cs; ++ const char *ts, *cs; + if(verbosity < v) + return; + dname_str(name, buf); +@@ -361,6 +364,37 @@ log_nametypeclass(enum verbosity_value v, const char* str, uint8_t* name, + log_info("%s %s %s %s", str, buf, ts, cs); + } + ++void ++log_query_in(const char* str, uint8_t* name, uint16_t type, uint16_t dclass) ++{ ++ char buf[LDNS_MAX_DOMAINLEN+1]; ++ char t[12], c[12]; ++ const char *ts, *cs; ++ dname_str(name, buf); ++ if(type == LDNS_RR_TYPE_TSIG) ts = "TSIG"; ++ else if(type == LDNS_RR_TYPE_IXFR) ts = "IXFR"; ++ else if(type == LDNS_RR_TYPE_AXFR) ts = "AXFR"; ++ else if(type == LDNS_RR_TYPE_MAILB) ts = "MAILB"; ++ else if(type == LDNS_RR_TYPE_MAILA) ts = "MAILA"; ++ else if(type == LDNS_RR_TYPE_ANY) ts = "ANY"; ++ else if(sldns_rr_descript(type) && sldns_rr_descript(type)->_name) ++ ts = sldns_rr_descript(type)->_name; ++ else { ++ snprintf(t, sizeof(t), "TYPE%d", (int)type); ++ ts = t; ++ } ++ if(sldns_lookup_by_id(sldns_rr_classes, (int)dclass) && ++ sldns_lookup_by_id(sldns_rr_classes, (int)dclass)->name) ++ cs = sldns_lookup_by_id(sldns_rr_classes, (int)dclass)->name; ++ else { ++ snprintf(c, sizeof(c), "CLASS%d", (int)dclass); ++ cs = c; ++ } ++ if(LOG_TAG_QUERYREPLY) ++ log_query("%s %s %s %s", str, buf, ts, cs); ++ else log_info("%s %s %s %s", str, buf, ts, cs); ++} ++ + void log_name_addr(enum verbosity_value v, const char* str, uint8_t* zone, + struct sockaddr_storage* addr, socklen_t addrlen) + { +diff --git a/util/net_help.h b/util/net_help.h +index de2e1ac..f2e0e43 100644 +--- a/util/net_help.h ++++ b/util/net_help.h +@@ -99,6 +99,9 @@ extern int MINIMAL_RESPONSES; + /** rrset order roundrobin */ + extern int RRSET_ROUNDROBIN; + ++/** log tag queries with name instead of 'info' for filtering */ ++extern int LOG_TAG_QUERYREPLY; ++ + /** + * See if string is ip4 or ip6. + * @param str: IP specification. +@@ -235,6 +238,12 @@ void sockaddr_store_port(struct sockaddr_storage* addr, socklen_t addrlen, + void log_nametypeclass(enum verbosity_value v, const char* str, + uint8_t* name, uint16_t type, uint16_t dclass); + ++/** ++ * Like log_nametypeclass, but logs with log_query for query logging ++ */ ++void log_query_in(const char* str, uint8_t* name, uint16_t type, ++ uint16_t dclass); ++ + /** + * Compare two sockaddrs. Imposes an ordering on the addresses. + * Compares address and port. +diff --git a/validator/val_kcache.c b/validator/val_kcache.c +index 22070cc..e0b88b6 100644 +--- a/validator/val_kcache.c ++++ b/validator/val_kcache.c +@@ -89,7 +89,7 @@ key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey, + if(key_entry_isbad(k) && qstate->errinf && + qstate->env->cfg->val_log_level >= 2) { + /* on malloc failure there is simply no reason string */ +- key_entry_set_reason(k, errinf_to_str(qstate)); ++ key_entry_set_reason(k, errinf_to_str_bogus(qstate)); + } + key_entry_hash(k); + slabhash_insert(kcache->slab, k->entry.hash, &k->entry, +diff --git a/validator/validator.c b/validator/validator.c +index 5777b29..2d9cc17 100644 +--- a/validator/validator.c ++++ b/validator/validator.c +@@ -2227,13 +2227,15 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq, + vq->orig_msg->rep->ttl = ve->bogus_ttl; + vq->orig_msg->rep->prefetch_ttl = + PREFETCH_TTL_CALC(vq->orig_msg->rep->ttl); +- if(qstate->env->cfg->val_log_level >= 1 && ++ if((qstate->env->cfg->val_log_level >= 1 || ++ qstate->env->cfg->log_servfail) && + !qstate->env->cfg->val_log_squelch) { +- if(qstate->env->cfg->val_log_level < 2) ++ if(qstate->env->cfg->val_log_level < 2 && ++ !qstate->env->cfg->log_servfail) + log_query_info(0, "validation failure", + &qstate->qinfo); + else { +- char* err = errinf_to_str(qstate); ++ char* err = errinf_to_str_bogus(qstate); + if(err) log_info("%s", err); + free(err); + } +@@ -2332,6 +2334,7 @@ processDLVLookup(struct module_qstate* qstate, struct val_qstate* vq, + + if(vq->dlv_status == dlv_error) { + verbose(VERB_QUERY, "failed DLV lookup"); ++ errinf(qstate, "failed DLV lookup"); + return val_error(qstate, id); + } else if(vq->dlv_status == dlv_success) { + uint8_t* nm; diff --git a/SOURCES/unbound-1.7.3-crypto-policy-non-compliance-openssl.patch b/SOURCES/unbound-1.7.3-crypto-policy-non-compliance-openssl.patch new file mode 100644 index 0000000..6d4b906 --- /dev/null +++ b/SOURCES/unbound-1.7.3-crypto-policy-non-compliance-openssl.patch @@ -0,0 +1,13 @@ +diff --git a/util/net_help.c b/util/net_help.c +index a5059b0..a193c36 100644 +--- a/util/net_help.c ++++ b/util/net_help.c +@@ -703,7 +703,7 @@ listen_sslctx_setup(void* ctxt) + #endif + #if defined(SHA256_DIGEST_LENGTH) && defined(USE_ECDSA) + /* if we have sha256, set the cipher list to have no known vulns */ +- if(!SSL_CTX_set_cipher_list(ctx, "TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-256-GCM-SHA384:TLS13-AES-128-GCM-SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256")) ++ if(!SSL_CTX_set_cipher_list(ctx, "PROFILE=SYSTEM")) + log_crypto_err("could not set cipher list with SSL_CTX_set_cipher_list"); + #endif + diff --git a/SOURCES/unbound-1.7.3-security-hardening.patch b/SOURCES/unbound-1.7.3-security-hardening.patch new file mode 100644 index 0000000..930bbad --- /dev/null +++ b/SOURCES/unbound-1.7.3-security-hardening.patch @@ -0,0 +1,677 @@ +diff --git a/config.h.in b/config.h.in +index 04356f3..3b06bfa 100644 +--- a/config.h.in ++++ b/config.h.in +@@ -666,6 +666,9 @@ + /* Shared data */ + #undef SHARE_DIR + ++/* The size of `size_t', as computed by sizeof. */ ++#undef SIZEOF_SIZE_T ++ + /* The size of `time_t', as computed by sizeof. */ + #undef SIZEOF_TIME_T + +diff --git a/configure.ac b/configure.ac +index c5e0c7b..1bff4ed 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -371,6 +371,7 @@ AC_INCLUDES_DEFAULT + # endif + #endif + ]) ++AC_CHECK_SIZEOF(size_t) + + # add option to disable the evil rpath + ACX_ARG_RPATH +diff --git a/contrib/create_unbound_ad_servers.sh b/contrib/create_unbound_ad_servers.sh +index d31f078..49fdbff 100644 +--- a/contrib/create_unbound_ad_servers.sh ++++ b/contrib/create_unbound_ad_servers.sh +@@ -9,12 +9,13 @@ + # Variables + dst_dir="/etc/opt/csw/unbound" + work_dir="/tmp" +-list_addr="http://pgl.yoyo.org/adservers/serverlist.php?hostformat=nohtml&showintro=1&startdate%5Bday%5D=&startdate%5Bmonth%5D=&startdate%5Byear%5D=" ++list_addr="https://pgl.yoyo.org/adservers/serverlist.php?hostformat=nohtml&showintro=1&startdate%5Bday%5D=&startdate%5Bmonth%5D=&startdate%5Byear%5D=" + + # OS commands + CAT=`which cat` + ECHO=`which echo` + WGET=`which wget` ++TR=`which tr` + + # Check Wget installed + if [ ! -f $WGET ]; then +@@ -22,8 +23,10 @@ if [ ! -f $WGET ]; then + exit 1 + fi + ++# remove special characters with tr to protect unbound.conf + $WGET -O $work_dir/yoyo_ad_servers "$list_addr" && \ + $CAT $work_dir/yoyo_ad_servers | \ ++$TR -d '";$\\' | \ + while read line ; \ + do \ + $ECHO "local-zone: \"$line\" redirect" ;\ +@@ -36,4 +39,4 @@ echo "Done." + # the unbound_ad_servers file: + # + # include: $dst_dir/unbound_ad_servers +-# +\ No newline at end of file ++# +diff --git a/daemon/daemon.c b/daemon/daemon.c +index 6820e11..1b4f329 100644 +--- a/daemon/daemon.c ++++ b/daemon/daemon.c +@@ -426,9 +426,7 @@ daemon_create_workers(struct daemon* daemon) + int* shufport; + log_assert(daemon && daemon->cfg); + if(!daemon->rand) { +- unsigned int seed = (unsigned int)time(NULL) ^ +- (unsigned int)getpid() ^ 0x438; +- daemon->rand = ub_initstate(seed, NULL); ++ daemon->rand = ub_initstate(NULL); + if(!daemon->rand) + fatal_exit("could not init random generator"); + hash_set_raninit((uint32_t)ub_random(daemon->rand)); +diff --git a/daemon/worker.c b/daemon/worker.c +index 3acecc1..8354010 100644 +--- a/daemon/worker.c ++++ b/daemon/worker.c +@@ -1629,18 +1629,14 @@ worker_create(struct daemon* daemon, int id, int* ports, int n) + return NULL; + } + /* create random state here to avoid locking trouble in RAND_bytes */ +- seed = (unsigned int)time(NULL) ^ (unsigned int)getpid() ^ +- (((unsigned int)worker->thread_num)<<17); +- /* shift thread_num so it does not match out pid bits */ +- if(!(worker->rndstate = ub_initstate(seed, daemon->rand))) { +- seed = 0; ++ if(!(worker->rndstate = ub_initstate(daemon->rand))) { + log_err("could not init random numbers."); + tube_delete(worker->cmd); + free(worker->ports); + free(worker); + return NULL; + } +- seed = 0; ++ explicit_bzero(&seed, sizeof(seed)); + #ifdef USE_DNSTAP + if(daemon->cfg->dnstap) { + log_assert(daemon->dtenv != NULL); +diff --git a/dns64/dns64.c b/dns64/dns64.c +index 7889d72..300202c 100644 +--- a/dns64/dns64.c ++++ b/dns64/dns64.c +@@ -782,6 +782,16 @@ dns64_inform_super(struct module_qstate* qstate, int id, + * Signal that the sub-query is finished, no matter whether we are + * successful or not. This lets the state machine terminate. + */ ++ if(!super->minfo[id]) { ++ super->minfo[id] = (enum dns64_qstate *)regional_alloc(super->region, ++ sizeof(*(super->minfo[id]))); ++ if(!super->minfo[id]) { ++ log_err("out of memory"); ++ super->return_rcode = LDNS_RCODE_SERVFAIL; ++ super->return_msg = NULL; ++ return; ++ } ++ } + super->minfo[id] = (void*)DNS64_SUBQUERY_FINISHED; + + /* If there is no successful answer, we're done. */ +diff --git a/dnscrypt/dnscrypt.c b/dnscrypt/dnscrypt.c +index 3545d3d..7dd2ce5 100644 +--- a/dnscrypt/dnscrypt.c ++++ b/dnscrypt/dnscrypt.c +@@ -732,6 +732,11 @@ dnsc_load_local_data(struct dnsc_env* dnscenv, struct config_file *cfg) + ); + continue; + } ++ if((unsigned)strlen(dnscenv->provider_name) >= (unsigned)0xffff0000) { ++ /* guard against integer overflow in rrlen calculation */ ++ verbose(VERB_OPS, "cert #%" PRIu32 " is too long", serial); ++ continue; ++ } + rrlen = strlen(dnscenv->provider_name) + + strlen(ttl_class_type) + + 4 * sizeof(struct SignedCert) + // worst case scenario +diff --git a/doc/Changelog b/doc/Changelog +index bb74461..4cb080e 100644 +--- a/doc/Changelog ++++ b/doc/Changelog +@@ -1,3 +1,55 @@ ++3 December 2019: Wouter ++ - Fix Assert Causing DoS in synth_cname(), ++ reported by X41 D-Sec. ++ - Fix Assert Causing DoS in dname_pkt_copy(), ++ reported by X41 D-Sec. ++ - Fix OOB Read in sldns_wire2str_dname_scan(), ++ reported by X41 D-Sec. ++ - Fix Out of Bounds Write in sldns_str2wire_str_buf(), ++ reported by X41 D-Sec. ++ - Fix Out of Bounds Write in sldns_b64_pton(), ++ fixed by check in sldns_str2wire_int16_data_buf(), ++ reported by X41 D-Sec. ++ - Fix Insufficient Handling of Compressed Names in dname_pkt_copy(), ++ reported by X41 D-Sec. ++ - Fix Out of Bound Write Compressed Names in rdata_copy(), ++ reported by X41 D-Sec. ++ - Fix Hang in sldns_wire2str_pkt_scan(), ++ reported by X41 D-Sec. ++ ++20 November 2019: Wouter ++ - Fix Out of Bounds Read in rrinternal_get_owner(), ++ reported by X41 D-Sec. ++ - Fix Race Condition in autr_tp_create(), ++ reported by X41 D-Sec. ++ - Fix Shared Memory World Writeable, ++ reported by X41 D-Sec. ++ - Adjust unbound-control to make stats_shm a read only operation. ++ - Fix Weak Entropy Used For Nettle, ++ reported by X41 D-Sec. ++ - Fix Randomness Error not Handled Properly, ++ reported by X41 D-Sec. ++ - Fix Out-of-Bounds Read in dname_valid(), ++ reported by X41 D-Sec. ++ - Fix Config Injection in create_unbound_ad_servers.sh, ++ reported by X41 D-Sec. ++ ++19 November 2019: Wouter ++ - Fix Integer Overflow in Regional Allocator, ++ reported by X41 D-Sec. ++ - Fix Unchecked NULL Pointer in dns64_inform_super() ++ and ipsecmod_new(), reported by X41 D-Sec. ++ - Fix Out-of-bounds Read in rr_comment_dnskey(), ++ reported by X41 D-Sec. ++ - Fix Integer Overflows in Size Calculations, ++ reported by X41 D-Sec. ++ - Fix Integer Overflow to Buffer Overflow in ++ sldns_str2wire_dname_buf_origin(), reported by X41 D-Sec. ++ - Fix Out of Bounds Read in sldns_str2wire_dname(), ++ reported by X41 D-Sec. ++ - Fix Out of Bounds Write in sldns_bget_token_par(), ++ reported by X41 D-Sec. ++ + 30 November 2018: Wouter + - log-tag-queryreply: yes in unbound.conf tags the log-queries and + log-replies in the log file for easier log filter maintenance. +diff --git a/ipsecmod/ipsecmod.c b/ipsecmod/ipsecmod.c +index 3572f12..1422a62 100644 +--- a/ipsecmod/ipsecmod.c ++++ b/ipsecmod/ipsecmod.c +@@ -103,11 +103,11 @@ ipsecmod_new(struct module_qstate* qstate, int id) + { + struct ipsecmod_qstate* iq = (struct ipsecmod_qstate*)regional_alloc( + qstate->region, sizeof(struct ipsecmod_qstate)); +- memset(iq, 0, sizeof(*iq)); + qstate->minfo[id] = iq; + if(!iq) + return 0; + /* Initialise it. */ ++ memset(iq, 0, sizeof(*iq)); + iq->enabled = qstate->env->cfg->ipsecmod_enabled; + iq->is_whitelisted = ipsecmod_domain_is_whitelisted( + (struct ipsecmod_env*)qstate->env->modinfo[id], qstate->qinfo.qname, +diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c +index 8230d17..942c3d5 100644 +--- a/iterator/iter_scrub.c ++++ b/iterator/iter_scrub.c +@@ -231,6 +231,10 @@ synth_cname(uint8_t* qname, size_t qnamelen, struct rrset_parse* dname_rrset, + size_t dtarglen; + if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen, pkt)) + return 0; ++ if(qnamelen <= dname_rrset->dname_len) ++ return 0; ++ if(qnamelen == 0) ++ return 0; + log_assert(qnamelen > dname_rrset->dname_len); + /* DNAME from com. to net. with qname example.com. -> example.net. */ + /* so: \3com\0 to \3net\0 and qname \7example\3com\0 */ +diff --git a/libunbound/libunbound.c b/libunbound/libunbound.c +index 275e8d2..a8979c2 100644 +--- a/libunbound/libunbound.c ++++ b/libunbound/libunbound.c +@@ -83,7 +83,6 @@ + static struct ub_ctx* ub_ctx_create_nopipe(void) + { + struct ub_ctx* ctx; +- unsigned int seed; + #ifdef USE_WINSOCK + int r; + WSADATA wsa_data; +@@ -107,15 +106,12 @@ static struct ub_ctx* ub_ctx_create_nopipe(void) + return NULL; + } + alloc_init(&ctx->superalloc, NULL, 0); +- seed = (unsigned int)time(NULL) ^ (unsigned int)getpid(); +- if(!(ctx->seed_rnd = ub_initstate(seed, NULL))) { +- seed = 0; ++ if(!(ctx->seed_rnd = ub_initstate(NULL))) { + ub_randfree(ctx->seed_rnd); + free(ctx); + errno = ENOMEM; + return NULL; + } +- seed = 0; + lock_basic_init(&ctx->qqpipe_lock); + lock_basic_init(&ctx->rrpipe_lock); + lock_basic_init(&ctx->cfglock); +diff --git a/libunbound/libworker.c b/libunbound/libworker.c +index 3dcaa78..07a08c6 100644 +--- a/libunbound/libworker.c ++++ b/libunbound/libworker.c +@@ -122,7 +122,6 @@ libworker_delete_event(struct libworker* w) + static struct libworker* + libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb) + { +- unsigned int seed; + struct libworker* w = (struct libworker*)calloc(1, sizeof(*w)); + struct config_file* cfg = ctx->env->cfg; + int* ports; +@@ -177,17 +176,13 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb) + } + w->env->worker = (struct worker*)w; + w->env->probe_timer = NULL; +- seed = (unsigned int)time(NULL) ^ (unsigned int)getpid() ^ +- (((unsigned int)w->thread_num)<<17); +- seed ^= (unsigned int)w->env->alloc->next_id; + if(!w->is_bg || w->is_bg_thread) { + lock_basic_lock(&ctx->cfglock); + } +- if(!(w->env->rnd = ub_initstate(seed, ctx->seed_rnd))) { ++ if(!(w->env->rnd = ub_initstate(ctx->seed_rnd))) { + if(!w->is_bg || w->is_bg_thread) { + lock_basic_unlock(&ctx->cfglock); + } +- seed = 0; + libworker_delete(w); + return NULL; + } +@@ -207,7 +202,6 @@ libworker_setup(struct ub_ctx* ctx, int is_bg, struct ub_event_base* eb) + hash_set_raninit((uint32_t)ub_random(w->env->rnd)); + } + } +- seed = 0; + + if(eb) + w->base = comm_base_create_event(eb); +diff --git a/respip/respip.c b/respip/respip.c +index 2e9313f..7d2a588 100644 +--- a/respip/respip.c ++++ b/respip/respip.c +@@ -475,10 +475,16 @@ copy_rrset(const struct ub_packed_rrset_key* key, struct regional* region) + if(!ck->rk.dname) + return NULL; + ++ if((unsigned)data->count >= 0xffff00U) ++ return NULL; /* guard against integer overflow in dsize */ + dsize = sizeof(struct packed_rrset_data) + data->count * + (sizeof(size_t)+sizeof(uint8_t*)+sizeof(time_t)); +- for(i=0; icount; i++) ++ for(i=0; icount; i++) { ++ if((unsigned)dsize >= 0x0fffffffU || ++ (unsigned)data->rr_len[i] >= 0x0fffffffU) ++ return NULL; /* guard against integer overflow */ + dsize += data->rr_len[i]; ++ } + d = regional_alloc(region, dsize); + if(!d) + return NULL; +diff --git a/sldns/parse.c b/sldns/parse.c +index b62c405..b30264e 100644 +--- a/sldns/parse.c ++++ b/sldns/parse.c +@@ -325,8 +325,14 @@ sldns_bget_token_par(sldns_buffer *b, char *token, const char *delim, + if (c == '\n' && p != 0) { + /* in parentheses */ + /* do not write ' ' if we want to skip spaces */ +- if(!(skipw && (strchr(skipw, c)||strchr(skipw, ' ')))) ++ if(!(skipw && (strchr(skipw, c)||strchr(skipw, ' ')))) { ++ /* check for space for the space character */ ++ if (limit > 0 && (i >= limit || (size_t)(t-token) >= limit)) { ++ *t = '\0'; ++ return -1; ++ } + *t++ = ' '; ++ } + lc = c; + continue; + } +diff --git a/sldns/str2wire.c b/sldns/str2wire.c +index 1a51bb6..414b7b8 100644 +--- a/sldns/str2wire.c ++++ b/sldns/str2wire.c +@@ -150,6 +150,10 @@ int sldns_str2wire_dname_buf_origin(const char* str, uint8_t* buf, size_t* len, + if(s) return s; + + if(rel && origin && dlen > 0) { ++ if((unsigned)dlen >= 0x00ffffffU || ++ (unsigned)origin_len >= 0x00ffffffU) ++ /* guard against integer overflow in addition */ ++ return RET_ERR(LDNS_WIREPARSE_ERR_GENERAL, *len); + if(dlen + origin_len - 1 > LDNS_MAX_DOMAINLEN) + return RET_ERR(LDNS_WIREPARSE_ERR_DOMAINNAME_OVERFLOW, + LDNS_MAX_DOMAINLEN); +@@ -168,7 +172,9 @@ uint8_t* sldns_str2wire_dname(const char* str, size_t* len) + uint8_t dname[LDNS_MAX_DOMAINLEN+1]; + *len = sizeof(dname); + if(sldns_str2wire_dname_buf(str, dname, len) == 0) { +- uint8_t* r = (uint8_t*)malloc(*len); ++ uint8_t* r; ++ if(*len > sizeof(dname)) return NULL; ++ r = (uint8_t*)malloc(*len); + if(r) return memcpy(r, dname, *len); + } + *len = 0; +@@ -187,6 +193,9 @@ rrinternal_get_owner(sldns_buffer* strbuf, uint8_t* rr, size_t* len, + sldns_buffer_position(strbuf)); + } + ++ if(token_len < 2) /* make sure there is space to read "@" or "" */ ++ return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL, ++ sldns_buffer_position(strbuf)); + if(strcmp(token, "@") == 0) { + uint8_t* tocopy; + if (origin) { +@@ -1094,7 +1103,7 @@ int sldns_str2wire_str_buf(const char* str, uint8_t* rd, size_t* len) + while(sldns_parse_char(&ch, &s)) { + if(sl >= 255) + return RET_ERR(LDNS_WIREPARSE_ERR_INVALID_STR, s-str); +- if(*len < sl+1) ++ if(*len < sl+2) + return RET_ERR(LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL, + s-str); + rd[++sl] = ch; +@@ -2095,6 +2104,8 @@ int sldns_str2wire_int16_data_buf(const char* str, uint8_t* rd, size_t* len) + char* s; + int n; + n = strtol(str, &s, 10); ++ if(n < 0) /* negative number not allowed */ ++ return LDNS_WIREPARSE_ERR_SYNTAX; + if(*len < ((size_t)n)+2) + return LDNS_WIREPARSE_ERR_BUFFER_TOO_SMALL; + if(n > 65535) +diff --git a/sldns/wire2str.c b/sldns/wire2str.c +index 832239f..a95c9b3 100644 +--- a/sldns/wire2str.c ++++ b/sldns/wire2str.c +@@ -585,6 +585,7 @@ static int rr_comment_dnskey(char** s, size_t* slen, uint8_t* rr, + if(rrlen < dname_off + 10) return 0; + rdlen = sldns_read_uint16(rr+dname_off+8); + if(rrlen < dname_off + 10 + rdlen) return 0; ++ if(rdlen < 2) return 0; + rdata = rr + dname_off + 10; + flags = (int)sldns_read_uint16(rdata); + w += sldns_str_print(s, slen, " ;{"); +@@ -781,7 +782,7 @@ int sldns_wire2str_dname_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen, + /* spool labels onto the string, use compression if its there */ + uint8_t* pos = *d; + unsigned i, counter=0; +- const unsigned maxcompr = 1000; /* loop detection, max compr ptrs */ ++ const unsigned maxcompr = 256; /* loop detection, max compr ptrs */ + int in_buf = 1; + if(*dlen == 0) return sldns_str_print(s, slen, "ErrorMissingDname"); + if(*pos == 0) { +@@ -789,7 +790,7 @@ int sldns_wire2str_dname_scan(uint8_t** d, size_t* dlen, char** s, size_t* slen, + (*dlen)--; + return sldns_str_print(s, slen, "."); + } +- while(*pos) { ++ while((!pkt || pos < pkt+pktlen) && *pos) { + /* read label length */ + uint8_t labellen = *pos++; + if(in_buf) { (*d)++; (*dlen)--; } +diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c +index d165417..2884309 100644 +--- a/smallapp/unbound-control.c ++++ b/smallapp/unbound-control.c +@@ -407,19 +407,19 @@ static void print_stats_shm(const char* cfgfile) + if(!config_read(cfg, cfgfile, NULL)) + fatal_exit("could not read config file"); + /* get shm segments */ +- id_ctl = shmget(cfg->shm_key, sizeof(int), SHM_R|SHM_W); ++ id_ctl = shmget(cfg->shm_key, sizeof(int), SHM_R); + if(id_ctl == -1) { + fatal_exit("shmget(%d): %s", cfg->shm_key, strerror(errno)); + } +- id_arr = shmget(cfg->shm_key+1, sizeof(int), SHM_R|SHM_W); ++ id_arr = shmget(cfg->shm_key+1, sizeof(int), SHM_R); + if(id_arr == -1) { + fatal_exit("shmget(%d): %s", cfg->shm_key+1, strerror(errno)); + } +- shm_stat = (struct ub_shm_stat_info*)shmat(id_ctl, NULL, 0); ++ shm_stat = (struct ub_shm_stat_info*)shmat(id_ctl, NULL, SHM_RDONLY); + if(shm_stat == (void*)-1) { + fatal_exit("shmat(%d): %s", id_ctl, strerror(errno)); + } +- stats = (struct ub_stats_info*)shmat(id_arr, NULL, 0); ++ stats = (struct ub_stats_info*)shmat(id_arr, NULL, SHM_RDONLY); + if(stats == (void*)-1) { + fatal_exit("shmat(%d): %s", id_arr, strerror(errno)); + } +diff --git a/testcode/unitmain.c b/testcode/unitmain.c +index fecde80..96a6654 100644 +--- a/testcode/unitmain.c ++++ b/testcode/unitmain.c +@@ -537,10 +537,8 @@ rnd_test(void) + struct ub_randstate* r; + int num = 1000, i; + long int a[1000]; +- unsigned int seed = (unsigned)time(NULL); + unit_show_feature("ub_random"); +- printf("ub_random seed is %u\n", seed); +- unit_assert( (r = ub_initstate(seed, NULL)) ); ++ unit_assert( (r = ub_initstate(NULL)) ); + for(i=0; i= 0); +diff --git a/util/data/dname.c b/util/data/dname.c +index b744f06..923be02 100644 +--- a/util/data/dname.c ++++ b/util/data/dname.c +@@ -75,6 +75,8 @@ dname_valid(uint8_t* dname, size_t maxlen) + { + size_t len = 0; + size_t labellen; ++ if(maxlen == 0) ++ return 0; /* too short, shortest is '0' root label */ + labellen = *dname++; + while(labellen) { + if(labellen&0xc0) +@@ -345,11 +347,17 @@ dname_pkt_hash(sldns_buffer* pkt, uint8_t* dname, hashvalue_type h) + void dname_pkt_copy(sldns_buffer* pkt, uint8_t* to, uint8_t* dname) + { + /* copy over the dname and decompress it at the same time */ ++ size_t comprcount = 0; + size_t len = 0; + uint8_t lablen; + lablen = *dname++; + while(lablen) { + if(LABEL_IS_PTR(lablen)) { ++ if(comprcount++ > MAX_COMPRESS_PTRS) { ++ /* too many compression pointers */ ++ *to = 0; /* end the result prematurely */ ++ return; ++ } + /* follow pointer */ + if((size_t)PTR_OFFSET(lablen, *dname) + >= sldns_buffer_limit(pkt)) +@@ -358,6 +366,10 @@ void dname_pkt_copy(sldns_buffer* pkt, uint8_t* to, uint8_t* dname) + lablen = *dname++; + continue; + } ++ if(lablen > LDNS_MAX_LABELLEN) { ++ *to = 0; /* end the result prematurely */ ++ return; ++ } + log_assert(lablen <= LDNS_MAX_LABELLEN); + len += (size_t)lablen+1; + if(len >= LDNS_MAX_DOMAINLEN) { +diff --git a/util/data/msgreply.c b/util/data/msgreply.c +index df2131c..dbae34d 100644 +--- a/util/data/msgreply.c ++++ b/util/data/msgreply.c +@@ -238,10 +238,10 @@ rdata_copy(sldns_buffer* pkt, struct packed_rrset_data* data, uint8_t* to, + break; + } + if(len) { ++ log_assert(len <= pkt_len); + memmove(to, sldns_buffer_current(pkt), len); + to += len; + sldns_buffer_skip(pkt, (ssize_t)len); +- log_assert(len <= pkt_len); + pkt_len -= len; + } + rdf++; +diff --git a/util/random.c b/util/random.c +index 8332960..9380502 100644 +--- a/util/random.c ++++ b/util/random.c +@@ -86,8 +86,7 @@ ub_systemseed(unsigned int ATTR_UNUSED(seed)) + } + + struct ub_randstate* +-ub_initstate(unsigned int ATTR_UNUSED(seed), +- struct ub_randstate* ATTR_UNUSED(from)) ++ub_initstate(struct ub_randstate* ATTR_UNUSED(from)) + { + struct ub_randstate* s = (struct ub_randstate*)malloc(1); + if(!s) { +@@ -123,8 +122,8 @@ void ub_systemseed(unsigned int ATTR_UNUSED(seed)) + { + } + +-struct ub_randstate* ub_initstate(unsigned int ATTR_UNUSED(seed), +- struct ub_randstate* ATTR_UNUSED(from)) ++struct ub_randstate* ++ub_initstate(struct ub_randstate* ATTR_UNUSED(from)) + { + struct ub_randstate* s = (struct ub_randstate*)calloc(1, sizeof(*s)); + if(!s) { +@@ -140,7 +139,9 @@ long int ub_random(struct ub_randstate* ATTR_UNUSED(state)) + /* random 31 bit value. */ + SECStatus s = PK11_GenerateRandom((unsigned char*)&x, (int)sizeof(x)); + if(s != SECSuccess) { +- log_err("PK11_GenerateRandom error: %s", ++ /* unbound needs secure randomness for randomized ++ * ID bits and port numbers in packets to upstream servers */ ++ fatal_exit("PK11_GenerateRandom error: %s", + PORT_ErrorToString(PORT_GetError())); + } + return x & MAX_VALUE; +@@ -166,8 +167,7 @@ void ub_systemseed(unsigned int ATTR_UNUSED(seed)) + log_err("Re-seeding not supported, generator untouched"); + } + +-struct ub_randstate* ub_initstate(unsigned int seed, +- struct ub_randstate* ATTR_UNUSED(from)) ++struct ub_randstate* ub_initstate(struct ub_randstate* ATTR_UNUSED(from)) + { + struct ub_randstate* s = (struct ub_randstate*)calloc(1, sizeof(*s)); + uint8_t buf[YARROW256_SEED_FILE_SIZE]; +@@ -183,15 +183,10 @@ struct ub_randstate* ub_initstate(unsigned int seed, + yarrow256_seed(&s->ctx, YARROW256_SEED_FILE_SIZE, buf); + s->seeded = yarrow256_is_seeded(&s->ctx); + } else { +- /* Stretch the uint32 input seed and feed it to Yarrow */ +- uint32_t v = seed; +- size_t i; +- for(i=0; i < (YARROW256_SEED_FILE_SIZE/sizeof(seed)); i++) { +- memmove(buf+i*sizeof(seed), &v, sizeof(seed)); +- v = v*seed + (uint32_t)i; +- } +- yarrow256_seed(&s->ctx, YARROW256_SEED_FILE_SIZE, buf); +- s->seeded = yarrow256_is_seeded(&s->ctx); ++ log_err("nettle random(yarrow) cannot initialize, " ++ "getentropy failed: %s", strerror(errno)); ++ free(s); ++ return NULL; + } + + return s; +diff --git a/util/random.h b/util/random.h +index a05a994..e75157d 100644 +--- a/util/random.h ++++ b/util/random.h +@@ -57,15 +57,12 @@ void ub_systemseed(unsigned int seed); + + /** + * Initialize a random generator state for use +- * @param seed: seed value to create state contents. +- * (ignored for arc4random). + * @param from: if not NULL, the seed is taken from this random structure. + * can be used to seed random states via a parent-random-state that + * is itself seeded with entropy. + * @return new state or NULL alloc failure. + */ +-struct ub_randstate* ub_initstate(unsigned int seed, +- struct ub_randstate* from); ++struct ub_randstate* ub_initstate(struct ub_randstate* from); + + /** + * Generate next random number from the state passed along. +diff --git a/util/regional.c b/util/regional.c +index 899a54e..5be09eb 100644 +--- a/util/regional.c ++++ b/util/regional.c +@@ -120,8 +120,18 @@ regional_destroy(struct regional *r) + void * + regional_alloc(struct regional *r, size_t size) + { +- size_t a = ALIGN_UP(size, ALIGNMENT); ++ size_t a; + void *s; ++ if( ++#if SIZEOF_SIZE_T == 8 ++ (unsigned long long)size >= 0xffffffffffffff00ULL ++#else ++ (unsigned)size >= (unsigned)0xffffff00UL ++#endif ++ ) ++ return NULL; /* protect against integer overflow in ++ malloc and ALIGN_UP */ ++ a = ALIGN_UP(size, ALIGNMENT); + /* large objects */ + if(a > REGIONAL_LARGE_OBJECT_SIZE) { + s = malloc(ALIGNMENT + size); +diff --git a/util/shm_side/shm_main.c b/util/shm_side/shm_main.c +index a783c09..69bee4d 100644 +--- a/util/shm_side/shm_main.c ++++ b/util/shm_side/shm_main.c +@@ -121,7 +121,7 @@ int shm_main_init(struct daemon* daemon) + shmctl(daemon->shm_info->id_arr, IPC_RMID, NULL); + + /* SHM: Create the segment */ +- daemon->shm_info->id_ctl = shmget(daemon->shm_info->key, sizeof(struct ub_shm_stat_info), IPC_CREAT | 0666); ++ daemon->shm_info->id_ctl = shmget(daemon->shm_info->key, sizeof(struct ub_shm_stat_info), IPC_CREAT | 0644); + + if (daemon->shm_info->id_ctl < 0) + { +@@ -134,7 +134,7 @@ int shm_main_init(struct daemon* daemon) + return 0; + } + +- daemon->shm_info->id_arr = shmget(daemon->shm_info->key + 1, shm_size, IPC_CREAT | 0666); ++ daemon->shm_info->id_arr = shmget(daemon->shm_info->key + 1, shm_size, IPC_CREAT | 0644); + + if (daemon->shm_info->id_arr < 0) + { +diff --git a/validator/autotrust.c b/validator/autotrust.c +index 7bc5577..e19bd7b 100644 +--- a/validator/autotrust.c ++++ b/validator/autotrust.c +@@ -370,10 +370,10 @@ autr_tp_create(struct val_anchors* anchors, uint8_t* own, size_t own_len, + free(tp); + return NULL; + } +- lock_basic_unlock(&anchors->lock); + lock_basic_init(&tp->lock); + lock_protect(&tp->lock, tp, sizeof(*tp)); + lock_protect(&tp->lock, tp->autr, sizeof(*tp->autr)); ++ lock_basic_unlock(&anchors->lock); + return tp; + } + diff --git a/SOURCES/unbound-1.7.3-symlink-traversal.patch b/SOURCES/unbound-1.7.3-symlink-traversal.patch new file mode 100644 index 0000000..0c06794 --- /dev/null +++ b/SOURCES/unbound-1.7.3-symlink-traversal.patch @@ -0,0 +1,43 @@ +diff --git a/unbound-1.7.3/daemon/unbound.c b/unbound-1.7.3/daemon/unbound.c +index 1383110..66ed61d 100644 +--- a/daemon/unbound.c ++++ b/daemon/unbound.c +@@ -327,18 +327,32 @@ readpid (const char* file) + static void + writepid (const char* pidfile, pid_t pid) + { +- FILE* f; ++ int fd; ++ char pidbuf[32]; ++ size_t count = 0; ++ snprintf(pidbuf, sizeof(pidbuf), "%lu\n", (unsigned long)pid); + +- if ((f = fopen(pidfile, "w")) == NULL ) { ++ if((fd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC ++#ifdef O_NOFOLLOW ++ | O_NOFOLLOW ++#endif ++ , 0644)) == -1) { + log_err("cannot open pidfile %s: %s", + pidfile, strerror(errno)); + return; + } +- if(fprintf(f, "%lu\n", (unsigned long)pid) < 0) { +- log_err("cannot write to pidfile %s: %s", +- pidfile, strerror(errno)); ++ while(count < strlen(pidbuf)) { ++ ssize_t r = write(fd, pidbuf+count, strlen(pidbuf)-count); ++ if(r == -1) { ++ if(errno == EAGAIN || errno == EINTR) ++ continue; ++ log_err("cannot write to pidfile %s: %s", ++ pidfile, strerror(errno)); ++ break; ++ } ++ count += r; + } +- fclose(f); ++ close(fd); + } + + /** diff --git a/SPECS/unbound.spec b/SPECS/unbound.spec index 9a37167..f67eb73 100644 --- a/SPECS/unbound.spec +++ b/SPECS/unbound.spec @@ -34,7 +34,7 @@ Summary: Validating, recursive, and caching DNS(SEC) resolver Name: unbound Version: 1.7.3 -Release: 14%{?extra_version:.%{extra_version}}%{?dist} +Release: 15%{?extra_version:.%{extra_version}}%{?dist} License: BSD Url: https://www.unbound.net/ Source: https://www.unbound.net/downloads/%{name}-%{version}%{?extra_version}.tar.gz @@ -65,9 +65,14 @@ Patch8: unbound-1.7.3-auth-callback.patch Patch9: unbound-1.7.3-ksk-2010-revoked.patch Patch10: unbound-1.7.3-DNS-over-TLS-memory-leak.patch Patch11: unbound-1.7.3-amplifying-an-incoming-query.patch +Patch12: unbound-1.7.3-crypto-policy-non-compliance-openssl.patch +Patch13: unbound-1.7.3-additional-logging.patch +Patch14: unbound-1.7.3-security-hardening.patch +Patch15: unbound-1.7.3-symlink-traversal.patch +BuildRequires: gdb BuildRequires: gcc, make -BuildRequires: flex, openssl-devel +BuildRequires: byacc, flex, openssl-devel BuildRequires: libevent-devel expat-devel BuildRequires: pkgconfig %if 0%{with_python2} @@ -170,6 +175,10 @@ pushd %{pkgname} %patch9 -p1 -b .ksk-2010-revoked %patch10 -p1 -b .DNS-over-TLS-memory-leak %patch11 -p1 -b .amplifying-an-incoming-query +%patch12 -p1 -b .crypto-policy +%patch13 -p1 -b .additional-logging +%patch14 -p1 -b .security-hardening +%patch15 -p1 -b .symlink-traversal # only for snapshots # autoreconf -iv @@ -439,8 +448,22 @@ popd %attr(0644,unbound,unbound) %config %{_sharedstatedir}/%{name}/root.key # just left for backwards compat with user changed unbound.conf files - format is different! %attr(0644,root,root) %config %{_sysconfdir}/%{name}/root.key +# modification of root.key is maintained by unbound-achor.service and is intentional, so let rpm know +%verify(not md5 size mtime) %{_sharedstatedir}/%{name}/root.key %changelog +* Tue Sep 01 2020 Anna Khaitovich - 1.7.3-15 +- Fix SPEC file to not check md5 mtime and size of /var/lib/unbound/root.key +- Resolves: rhbz#1714175 +- Use system-wide crypto policy setting (PROFILE=SYSTEM) instead of custom setting +- Resolves: rhbz#1842837 +- Enable additional logging in unbound +- Resolves: rhbz#1850460 +- security hardening from x41 report +- Resolves: rhbz#1859933 +- symbolic link traversal when writing PID file +- Resolves: rhbz#1899058 + * Thu May 28 2020 Anna Khaitovich - 1.7.3-14 - Fix unbound-1.7.3-amplifying-an-incoming-query.patch patch - Resolves: rhbz#1839178 (CVE-2020-12662)