[glibc/release/2.36/master] resolv: Count records correctly (CVE-2026-4437)

Aurelien Jarno via Glibc-cvs <[email protected]>
Newsgroups gmane.comp.lib.glibc.cvs
Message-ID <[email protected]>
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=fe7ce8f57442cbe647f3af1b3e35343e2ef437b3

commit fe7ce8f57442cbe647f3af1b3e35343e2ef437b3
Author: Carlos O'Donell <[email protected]>
Date:   Fri Mar 20 16:43:33 2026 -0400

    resolv: Count records correctly (CVE-2026-4437)
    
    The answer section boundary was previously ignored, and the code in
    getanswer_ptr would iterate past the last resource record, but not
    beyond the end of the returned data.  This could lead to subsequent data
    being interpreted as answer records, thus violating the DNS
    specification.  Such resource records could be maliciously crafted and
    hidden from other tooling, but processed by the glibc stub resolver and
    acted upon by the application.  While we trust the data returned by the
    configured recursive resolvers, we should not trust its format and
    should validate it as required.  It is a security issue to incorrectly
    process the DNS protocol.
    
    A regression test is added for response section crossing.
    
    No regressions on x86_64-linux-gnu.
    
    Reviewed-by: Collin Funk <[email protected]>
    (cherry picked from commit 9f5f18aab40ec6b61fa49a007615e6077e9a979b)

Diff:
---
 resolv/Makefile                 |   4 +
 resolv/nss_dns/dns-host.c       |   2 +-
 resolv/tst-resolv-dns-section.c | 162 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 167 insertions(+), 1 deletion(-)

diff --git a/resolv/Makefile b/resolv/Makefile
index e5165fb34b..a13f75d858 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -97,6 +97,7 @@ tests += \
   tst-resolv-basic \
   tst-resolv-binary \
   tst-resolv-byaddr \
+  tst-resolv-dns-section \
   tst-resolv-edns \
   tst-resolv-invalid-cname \
   tst-resolv-network \
@@ -108,6 +109,7 @@ tests += \
   tst-resolv-semi-failure \
   tst-resolv-short-response \
   tst-resolv-trailing \
+  # tests
 
 # This test calls __res_context_send directly, which is not exported
 # from libresolv.
@@ -284,6 +286,8 @@ $(objpfx)tst-resolv-aliases: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-binary: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-byaddr: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-dns-section: $(objpfx)libresolv.so \
+  $(shared-thread-library)
 $(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-res_init: $(objpfx)libresolv.so
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
index 227734da5c..75368f0fea 100644
--- a/resolv/nss_dns/dns-host.c
+++ b/resolv/nss_dns/dns-host.c
@@ -820,7 +820,7 @@ getanswer_ptr (unsigned char *packet, size_t packetlen,
   /* expected_name may be updated to point into this buffer.  */
   unsigned char name_buffer[NS_MAXCDNAME];
 
-  while (ancount > 0)
+  for (; ancount > 0; --ancount)
     {
       struct ns_rr_wire rr;
       if (!__ns_rr_cursor_next (&c, &rr))
diff --git a/resolv/tst-resolv-dns-section.c b/resolv/tst-resolv-dns-section.c
new file mode 100644
index 0000000000..1171baef51
--- /dev/null
+++ b/resolv/tst-resolv-dns-section.c
@@ -0,0 +1,162 @@
+/* Test handling of invalid section transitions (bug 34014).
+   Copyright (C) 2022-2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <array_length.h>
+#include <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/format_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+/* Name of test, and the second section type.  */
+struct item {
+  const char *test;
+  int ns_section;
+};
+
+static const struct item test_items[] =
+  {
+    { "Test crossing from ns_s_an to ns_s_ar.", ns_s_ar },
+    { "Test crossing from ns_s_an to ns_s_an.", ns_s_ns },
+
+    { NULL, 0 },
+  };
+
+/* The response is designed to contain the following:
+   - An Answer section with one T_PTR record that is skipped.
+   - A second section with a semantically invalid T_PTR record.
+   The original defect is that the response parsing would cross
+   section boundaries and handle the additional section T_PTR
+   as if it were an answer.  A conforming implementation would
+   stop as soon as it reaches the end of the section.  */
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  TEST_COMPARE (qclass, C_IN);
+
+  /* We only test PTR.  */
+  TEST_COMPARE (qtype, T_PTR);
+
+  unsigned int count;
+  char *tail = NULL;
+
+  if (strstr (qname, "in-addr.arpa") != NULL
+      && sscanf (qname, "%u.%ms", &count, &tail) == 2)
+    TEST_COMPARE_STRING (tail, "0.168.192.in-addr.arpa");
+  else if (sscanf (qname, "%x.%ms", &count, &tail) == 2)
+    {
+    TEST_COMPARE_STRING (tail, "\
+0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa");
+    }
+  else
+    FAIL_EXIT1 ("invalid QNAME: %s\n", qname);
+  free (tail);
+
+  /* We have a bounded number of possible tests.  */
+  TEST_VERIFY (count >= 0);
+  TEST_VERIFY (count <= 15);
+
+  struct resolv_response_flags flags = {};
+  resolv_response_init (b, flags);
+  resolv_response_add_question (b, qname, qclass, qtype);
+  resolv_response_section (b, ns_s_an);
+
+  /* Actual answer record, but the wrong name (skipped).  */
+  resolv_response_open_record (b, "1.0.0.10.in-addr.arpa", qclass, qtype, 60);
+
+  /* Record the answer.  */
+  resolv_response_add_name (b, "test.ptr.example.net");
+  resolv_response_close_record (b);
+
+  /* Add a second section to test section boundary crossing.  */
+  resolv_response_section (b, test_items[count].ns_section);
+  /* Semantically incorrect, but hide a T_PTR entry.  */
+  resolv_response_open_record (b, qname, qclass, qtype, 60);
+  resolv_response_add_name (b, "wrong.ptr.example.net");
+  resolv_response_close_record (b);
+}
+
+
+/* Perform one check using a reverse lookup.  */
+static void
+check_reverse (int af, int count)
+{
+  TEST_VERIFY (af == AF_INET || af == AF_INET6);
+  TEST_VERIFY (count < array_length (test_items));
+
+  char addr[sizeof (struct in6_addr)] = { 0 };
+  socklen_t addrlen;
+  if (af == AF_INET)
+    {
+      addr[0] = (char) 192;
+      addr[1] = (char) 168;
+      addr[2] = (char) 0;
+      addr[3] = (char) count;
+      addrlen = 4;
+    }
+  else
+    {
+      addr[0] = 0x20;
+      addr[1] = 0x01;
+      addr[2] = 0x0d;
+      addr[3] = 0xb8;
+      addr[4] = addr[5] = addr[6] = addr[7] = 0x0;
+      addr[8] = addr[9] = addr[10] = addr[11] = 0x0;
+      addr[12] = 0x0;
+      addr[13] = 0x0;
+      addr[14] = 0x0;
+      addr[15] = count;
+      addrlen = 16;
+    }
+
+  h_errno = 0;
+  struct hostent *answer = gethostbyaddr (addr, addrlen, af);
+  TEST_VERIFY (answer == NULL);
+  TEST_VERIFY (h_errno == NO_RECOVERY);
+  if (answer != NULL)
+    printf ("error: unexpected success: %s\n",
+	    support_format_hostent (answer));
+}
+
+static int
+do_test (void)
+{
+  struct resolv_test *obj = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response
+     });
+
+  for (int i = 0; test_items[i].test != NULL; i++)
+    {
+      check_reverse (AF_INET, i);
+      check_reverse (AF_INET6, i);
+    }
+
+  resolv_test_end (obj);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.