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

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Wed Jun 17 20:17:46 GMT 2026



On 16/06/26 16:17, Sergey Kolosov wrote:
> 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).

Thanks for working on this, some comments below.

> ---
>  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);

Use support_next_to_fault_allocate (support/next_to_fault.h) instead of the 
hand-rolled 0xAA guard as localedata/tst-bz12701-lc.c, resolv/tst-inet_pton.c, 
and posix/wordexp-test.c.  Something like:

  struct support_next_to_fault nf = 
    support_next_to_fault_allocate (size + align);
  memset (nf.buffer, 0, size + align);
  char *ptr = nf.buffer + align;          /* ptr + size lands exactly on the fault page */
  int res = gethostbyname_r (host_name, &ret, ptr, size, &result, &err);
  [...]
  support_next_to_fault_free (&nf);

On a vulnerable glibc it produces a clean SIGSEGV instead of relying on the 
corruption landing within 64 bytes.

> +  
> +  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>



More information about the Libc-alpha mailing list