[PATCH] resolv: Add test for gethostbyname_r unaligned buffer [BZ #18287]

Sergey Kolosov skolosov@redhat.com
Tue Jun 16 19:17:04 GMT 2026


Add a test for the buffer overflow in gethostbyname_r (Bug 18287), which
occurs when alignment padding is not subtracted from the remaining buffer
length.

The test uses binary search to find the exact required buffer size
for a mocked DNS response. It then tests all pointer misalignments
(1-7 bytes) using a custom 0xAA guard region. This guarantees
deterministic detection of out-of-bounds writes, avoiding false
negatives caused by malloc chunk rounding.
---
This patch adds a test for the gethostbyname_r buffer overflow (BZ #18287).
---
 resolv/Makefile                               |   3 +
 resolv/tst-resolv-gethostbyname_r-unaligned.c | 158 ++++++++++++++++++
 2 files changed, 161 insertions(+)
 create mode 100644 resolv/tst-resolv-gethostbyname_r-unaligned.c

diff --git a/resolv/Makefile b/resolv/Makefile
index 68b3a4dbf3..ecd4dcbfa0 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -157,6 +157,7 @@ tests += \
   tst-resolv-ai_idn-nolibidn2 \
   tst-resolv-canonname \
   tst-resolv-getaddrinfo-fqdn \
+  tst-resolv-gethostbyname_r-unaligned \
   tst-resolv-trustad \
 
 # Needs resolv_context.
@@ -306,6 +307,8 @@ $(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-gethostbyname_r-unaligned: \
+  $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
 $(objpfx)tst-resolv-res_init: $(objpfx)libresolv.so
 $(objpfx)tst-resolv-res_init-failure: $(objpfx)libresolv.so \
diff --git a/resolv/tst-resolv-gethostbyname_r-unaligned.c b/resolv/tst-resolv-gethostbyname_r-unaligned.c
new file mode 100644
index 0000000000..7298c6f8e6
--- /dev/null
+++ b/resolv/tst-resolv-gethostbyname_r-unaligned.c
@@ -0,0 +1,158 @@
+/* Test for BZ #18287.
+   This test verifies that gethostbyname_r correctly accounts for pointer
+   alignment padding when calculating the remaining buffer size.
+   Copyright (C) 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 <errno.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+#include <string.h>
+#include <netdb.h>
+#include <stdio.h>
+
+/* Prepare big enough answer to trigger buffer overflow.  */
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  if (strcmp (qname, "foo.site.example") == 0 && qtype == T_A)
+    {
+      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);
+
+      for (int i = 0; i <= 60; i++)
+        {
+          char last_ch = (char) i;
+          char addr_ipv4[4] = { 127, 126, 125, last_ch };
+          resolv_response_open_record (b, qname, qclass, T_A, 0x12345678);
+          resolv_response_add_data (b, addr_ipv4, sizeof (addr_ipv4));
+          resolv_response_close_record (b);
+        }
+    }
+}
+
+static int
+query_host (const char *host_name, size_t size, int align)
+{
+  struct hostent ret;
+  struct hostent *result;
+  int err;
+
+  /* Allocate memory for buffer, align and 64 bytes.  */
+  size_t total_alloc = size + align + 64;
+  unsigned char *raw_buf = malloc (total_alloc);
+  TEST_VERIFY_EXIT (raw_buf != NULL);
+
+  /* Fill in buffer with 0xAA to detect buffer overflow.  */
+  memset (raw_buf, 0, size + align);
+  memset (raw_buf + size + align, 0xAA, 64);
+  
+  char *ptr = (char *) raw_buf + align;
+
+  int res = gethostbyname_r (host_name, &ret, ptr, size, &result, &err);
+
+  /* Verify that the overflow guard region remains unchanged.  */
+  int overflow_detected = 0;
+  for (int i = 0; i < 64; i++)
+    {
+      if (raw_buf[size + align + i] != 0xAA)
+        {
+          overflow_detected = 1;
+          break;
+        }
+    }
+
+  free (raw_buf);
+
+  if (overflow_detected)
+    {
+      printf ("Buffer overflow was detected! (align=%d, size=%zu)\n",
+              align, size);
+      /* Return 99 to indicate that a buffer overflow was detected.  */
+      return 99;
+    }
+
+  if (res == 0 && result != NULL)
+    return 0; /* Buffer is enough.  */
+  else if (res == ERANGE || err == NETDB_INTERNAL)
+    return 1; /* Buffer is too small.  */
+  else
+    FAIL_EXIT1 ("gethostbyname_r failed unexpectedly: res=%d, err=%d",
+                res, err);
+}
+
+static int
+do_test (void)
+{
+  struct resolv_test *aux = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response,
+     });
+
+  const char *host_name = "foo.site.example";
+  int lower_bound = 512;
+  int upper_bound = 2048;
+
+  TEST_VERIFY (query_host (host_name, lower_bound, 0) == 1);
+  TEST_VERIFY (query_host (host_name, upper_bound, 0) == 0);
+
+  printf ("info: Finding smallest buffer size that captures DNS response\n");
+
+  while (upper_bound != lower_bound + 1)
+    {
+      int size = (lower_bound + upper_bound) / 2;
+      if (query_host (host_name, size, 0) == 0)
+        upper_bound = size;
+      else
+        lower_bound = size;
+    }
+
+  printf ("info: Boundary found. lower_bound=%d, upper_bound=%d\n",
+          lower_bound, upper_bound);
+
+  TEST_VERIFY (query_host (host_name, lower_bound, 0) == 1);
+  TEST_VERIFY (query_host (host_name, upper_bound, 0) == 0);
+
+  /* Trigger the vulnerability.
+     Test all misalignments (1-7 bytes). A patched glibc safely
+     returns 0 (success) or 1 (ERANGE) depending on padding.
+     A vulnerable glibc will fail to account for alignment padding,
+     overflow the buffer, corrupt the 0xAA canary, and return 99.  */
+  for (int align = 1; align < 8; align++)
+    {
+      int qres = query_host (host_name, upper_bound, align);
+      
+      if (qres == 99)
+        FAIL_EXIT1 ("Buffer overflow detected at align %d\n", align);
+      
+      /* Return value other than 99 means no overflow occurred.  */
+      TEST_VERIFY (qres == 0 || qres == 1);
+    }
+
+  resolv_test_end (aux);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.54.0



More information about the Libc-alpha mailing list