diff --git a/daemon/worker.c b/daemon/worker.c index 5cc586442..bbd91e05f 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -1706,6 +1706,8 @@ worker_handle_request(struct comm_point* c, void* arg, int error, } memset(&reply_edns, 0, sizeof(reply_edns)); reply_edns.edns_present = 1; + reply_edns.edns_version = EDNS_ADVERTISED_VERSION; + reply_edns.udp_size = EDNS_ADVERTISED_SIZE; error_encode(c->buffer, ret, &qinfo, *(uint16_t*)(void *)sldns_buffer_begin(c->buffer), sldns_buffer_read_u16_at(c->buffer, 2), &reply_edns); diff --git a/doc/Changelog b/doc/Changelog index 5448f8d3a..818302ce7 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -13,6 +13,11 @@ udpsize, the reply is truncated with TC and fix for the rcode in that short reply to be a sensible rcode. Thanks to Qifan Zhang, Palo Alto Networks, for the report. + - Fix that when a partial EDNS option is in a query, the + response is a more RFC conformant FORMERR, since the EDNS + option is malformed. Also fix to have an EDNS size for + the reply error encoding for failed EDNS parse of the query. + Thanks to Qifan Zhang, Palo Alto Networks, for the report. 2 September 2026: Wouter - Fix to not commit to have_zone at apply AXFR time for auth diff --git a/testdata/edns_downstream_cookie_short.rpl b/testdata/edns_downstream_cookie_short.rpl index dedd7f174..22fd6e6fa 100644 --- a/testdata/edns_downstream_cookie_short.rpl +++ b/testdata/edns_downstream_cookie_short.rpl @@ -53,4 +53,22 @@ SECTION QUESTION test. IN TXT ENTRY_END +; Check an EDNS option with a too short content +; The EDNS OPT rdata is too short, for the option opt_len. +STEP 30 QUERY +ENTRY_BEGIN +REPLY RD +SECTION QUESTION +test. IN TXT +SECTION ADDITIONAL +. 32768 CLASS1230 OPT \# 04 00 0a 00 08 +ENTRY_END +STEP 31 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA DO FORMERR +SECTION QUESTION +test. IN TXT +ENTRY_END + SCENARIO_END diff --git a/util/data/msgparse.c b/util/data/msgparse.c index f9beb605c..e00f570c7 100644 --- a/util/data/msgparse.c +++ b/util/data/msgparse.c @@ -974,7 +974,6 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, } /* while still more options, and have code+len to read */ - /* ignores partial content (i.e. rdata len 3) */ while(rdata_len >= 4 && i < MAX_PARSED_EDNS_OPTIONS) { uint16_t opt_code = sldns_read_uint16(rdata_ptr); uint16_t opt_len = sldns_read_uint16(rdata_ptr+2); @@ -984,8 +983,10 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, rdata_ptr += 4; rdata_len -= 4; - if(opt_len > rdata_len) - break; /* option code partial */ + if(opt_len > rdata_len) { + /* option code partial */ + return LDNS_RCODE_FORMERR; + } /* handle parse time edns options here */ switch(opt_code) { @@ -1160,6 +1161,9 @@ parse_edns_options_from_query(uint8_t* rdata_ptr, size_t rdata_len, rdata_len -= opt_len; i++; } + /* partial content (i.e. rdata len 3) is FORMERR */ + if(rdata_len > 0 && rdata_len <= 3) + return LDNS_RCODE_FORMERR; return LDNS_RCODE_NOERROR; } @@ -1240,6 +1244,8 @@ parse_extract_edns_from_response_msg(struct msg_parse* msg, rdata_len -= 4; if(opt_len > rdata_len) break; /* option code partial */ + /* This is lenient for partially broken messages + * from upstream. With a partial EDNS option. */ if(!edns_opt_list_append(&edns->opt_list_in, opt_code, opt_len, rdata_ptr, region)) {