Repository: c-ares/c-ares (2026-07-03T17:41:58Z)

Issue:
Multiple OPT records accepted in a single DNS response (RFC 6891 violation)
Currently, c-ares accepts multiple OPT records in a single DNS response without rejecting the message. 

According to RFC 6891 Section 6.1.1: *"There MUST be at most one OPT pseudo-RR in the Additional Data section of a message. If a query message with more than one OPT RR is received, a FORMERR (Format Error) MUST be returned."*

Allowing a second OPT record could potentially overwrite extended RCODE bits or inject unexpected EDNS options. For comparison, resolvers like unbound reject a second OPT record with `FORMERR` during message parsing.

**To Reproduce**
Parse a crafted DNS response containing more than one OPT record in the Additional Data section using `ares_dns_parse`. c-ares will successfully parse the response instead of throwing a format error.

**Expected behavior**
c-ares should reject the response and return a format error (e.g., `ARES_EFORMERR`) when a second OPT record is encountered during parsing.

**Version info**
* **c-ares version:** `main` (Tested against commit `0752823`)

**Additional context**
This was originally submitted to the c-ares security list, but it was agreed to be tracked as a standard protocol compliance bug rather than a security vulnerability.


Discussion:

Patch:
diff --git a/src/lib/record/ares_dns_parse.c b/src/lib/record/ares_dns_parse.c
index 18c254221b..113438ffb3 100644
--- a/src/lib/record/ares_dns_parse.c
+++ b/src/lib/record/ares_dns_parse.c
@@ -1690,6 +1690,22 @@ static ares_status_t ares_dns_parse_buf(ares_buf_t *buf, unsigned int flags,
     }
   }
 
+  /* RFC 6891 6.1.1: a message MUST NOT contain more than one OPT RR.  Reject
+   * any response carrying multiple OPT records in the additional section. */
+  {
+    size_t rr_cnt  = ares_dns_record_rr_cnt(*dnsrec, ARES_SECTION_ADDITIONAL);
+    size_t opt_cnt = 0;
+    size_t j;
+    for (j = 0; j < rr_cnt; j++) {
+      const ares_dns_rr_t *rr =
+        ares_dns_record_rr_get_const(*dnsrec, ARES_SECTION_ADDITIONAL, j);
+      if (ares_dns_rr_get_type(rr) == ARES_REC_TYPE_OPT && ++opt_cnt > 1) {
+        status = ARES_EBADRESP;
+        goto fail;
+      }
+    }
+  }
+
   /* Finalize rcode now that if we have OPT it is processed */
   if (!ares_dns_rcode_isvalid((*dnsrec)->raw_rcode)) {
     (*dnsrec)->rcode = ARES_RCODE_SERVFAIL;


Test patch:
diff --git a/test/ares-test-parse.cc b/test/ares-test-parse.cc
index 399c94dd56..b00fe62f0c 100644
--- a/test/ares-test-parse.cc
+++ b/test/ares-test-parse.cc
@@ -395,5 +395,33 @@ TEST_F(LibraryTest, ParseRejectsName256) {
   EXPECT_EQ(nullptr, dnsrec);
 }
 
+// A single OPT record in the additional section is valid (EDNS0).
+TEST_F(LibraryTest, ParseSingleOpt) {
+  DNSPacket pkt;
+  pkt.set_qid(0x1234).set_response()
+    .add_question(new DNSQuestion("example.com", T_A))
+    .add_additional(new DNSOptRR(0, 0, 0, 1280, {}, {}, false));
+  std::vector<byte> data = pkt.data();
+
+  ares_dns_record_t *dnsrec = NULL;
+  EXPECT_EQ(ARES_SUCCESS, ares_dns_parse(data.data(), data.size(), 0, &dnsrec));
+  EXPECT_NE(nullptr, dnsrec);
+  ares_dns_record_destroy(dnsrec);
+}
+
+// RFC 6891 6.1.1: more than one OPT record in a message is a format error.
+TEST_F(LibraryTest, ParseMultipleOptRejected) {
+  DNSPacket pkt;
+  pkt.set_qid(0x1234).set_response()
+    .add_question(new DNSQuestion("example.com", T_A))
+    .add_additional(new DNSOptRR(0, 0, 0, 1280, {}, {}, false))
+    .add_additional(new DNSOptRR(0, 0, 0, 1280, {}, {}, false));
+  std::vector<byte> data = pkt.data();
+
+  ares_dns_record_t *dnsrec = NULL;
+  EXPECT_EQ(ARES_EBADRESP, ares_dns_parse(data.data(), data.size(), 0, &dnsrec));
+  EXPECT_EQ(nullptr, dnsrec);
+}
+
 }  // namespace test
 }  // namespace ares