From 7cd0eca44e7095ac0d7b8cd6cde06ee72c0ec255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20B=C3=A9rat?= Date: Tue, 7 Jul 2026 15:05:38 +0200 Subject: [PATCH] Automated backport (missing data for AI changelog generation) (RHEL-33536) Resolves: RHEL-33536 --- glibc-RHEL-33536-1.patch | 185 +++++++++++++++++++++++++++++++++++ glibc-RHEL-33536-2.patch | 196 +++++++++++++++++++++++++++++++++++++ glibc-RHEL-33536-3.patch | 206 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 587 insertions(+) create mode 100644 glibc-RHEL-33536-1.patch create mode 100644 glibc-RHEL-33536-2.patch create mode 100644 glibc-RHEL-33536-3.patch diff --git a/glibc-RHEL-33536-1.patch b/glibc-RHEL-33536-1.patch new file mode 100644 index 0000000..ab30c75 --- /dev/null +++ b/glibc-RHEL-33536-1.patch @@ -0,0 +1,185 @@ +commit daab2a6d19f8360248289840408e7d2e5c203c2e +Author: Sergey Kolosov +Date: Tue Jun 3 22:10:20 2025 +0200 + + resolv: Add test for getaddrinfo returning FQDN in ai_canonname + + Test for BZ #15218. This test verifies that getaddrinfo returns a + fully-qualified domain name in the ai_canonname field then + AI_CANONNAME is set and search domains apply. + + Reviewed-by: Florian Weimer + +diff --git a/resolv/Makefile b/resolv/Makefile +index 7659862ab257f7d3..9927676edac49e63 100644 +--- a/resolv/Makefile ++++ b/resolv/Makefile +@@ -148,6 +148,7 @@ tests += \ + tst-resolv-ai_idn-latin1 \ + tst-resolv-ai_idn-nolibidn2 \ + tst-resolv-canonname \ ++ tst-resolv-getaddrinfo-fqdn \ + tst-resolv-trustad \ + + # Needs resolv_context. +@@ -331,6 +332,7 @@ $(objpfx)tst-resolv-threads: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-txnid-collision: $(objpfx)libresolv.a \ + $(static-thread-library) + $(objpfx)tst-resolv-canonname: $(objpfx)libresolv.so $(shared-thread-library) ++$(objpfx)tst-resolv-getaddrinfo-fqdn: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-trustad: $(objpfx)libresolv.so $(shared-thread-library) + + $(objpfx)tst-ns_name: $(objpfx)libresolv.so +diff --git a/resolv/tst-resolv-getaddrinfo-fqdn.c b/resolv/tst-resolv-getaddrinfo-fqdn.c +new file mode 100644 +index 0000000000000000..75bfe7e42a3e7023 +--- /dev/null ++++ b/resolv/tst-resolv-getaddrinfo-fqdn.c +@@ -0,0 +1,147 @@ ++/* Test for BZ #15218. Verify that getaddrinfo returns FQDN in ++ ai_canonname, when AI_CANONNAME is requested and search domain apply. ++ Copyright (C) 2025 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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++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 ++ || strcmp (qname, "bar.foo.site.example") == 0 ++ || strcmp (qname, "site.example") == 0) ++ { ++ 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); ++ if (qtype == T_A) ++ { ++ char addr_ipv4[4] = { 127, 126, 125, 124 }; ++ 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); ++ } ++ else if (qtype == T_AAAA) ++ { ++ char addr_ipv6[16] = ++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; ++ resolv_response_open_record (b, qname, qclass, T_AAAA, 0x12345678); ++ resolv_response_add_data (b, addr_ipv6, sizeof (addr_ipv6)); ++ resolv_response_close_record (b); ++ } ++ else ++ FAIL_EXIT1 ("qtype must be one of A, AAAA"); ++ } ++ else ++ { ++ struct resolv_response_flags flags = {.rcode = ns_r_nxdomain, }; ++ resolv_response_init (b, flags); ++ resolv_response_add_question (b, qname, qclass, qtype); ++ } ++} ++ ++void ++query_host (const char *host_name, const char *expected_name) ++{ ++ int family[] = { AF_INET, AF_INET6, AF_UNSPEC }; ++ const char *family_names[] = { "AF_INET", "AF_INET6", "AF_UNSPEC" }; ++ ++ for (int i = 0; i < 3; i++) ++ { ++ struct addrinfo hints = { ++ .ai_socktype = 0, ++ .ai_protocol = 0, ++ .ai_family = family[i], ++ .ai_flags = AI_CANONNAME, ++ }; ++ struct addrinfo *result, *current; ++ int res = getaddrinfo (host_name, NULL, &hints, &result); ++ if (res != 0) ++ FAIL_EXIT1 ("getaddrinfo (%s, %s): %s\n", host_name, family_names[i], ++ gai_strerror (res)); ++ else ++ { ++ int count = 0; ++ for (current = result; ++ current != NULL && current->ai_canonname != NULL; ++ current = current->ai_next) ++ { ++ TEST_COMPARE_STRING (current->ai_canonname, expected_name); ++ count++; ++ } ++ freeaddrinfo (result); ++ if (count > 1) ++ FAIL_EXIT1 ("Expected exactly one canonname, but got %d\n", count); ++ } ++ } ++} ++ ++/* test with site.example domain. */ ++void ++test_search_with_site_example_domain (void) ++{ ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response, ++ .search = { "site.example" }, ++ }); ++ ++ query_host ("foo", "foo.site.example"); ++ query_host ("bar.foo", "bar.foo.site.example"); ++ ++ resolv_test_end (aux); ++} ++ ++/* test with example domain. */ ++void ++test_search_with_example_domain (void) ++{ ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response, ++ .search = { "example" }, ++ }); ++ ++ query_host ("foo.site", "foo.site.example"); ++ query_host ("bar.foo.site", "bar.foo.site.example"); ++ query_host ("site", "site.example"); ++ ++ resolv_test_end (aux); ++} ++ ++static int ++do_test (void) ++{ ++ test_search_with_site_example_domain (); ++ test_search_with_example_domain (); ++ ++ return 0; ++} ++ ++#include diff --git a/glibc-RHEL-33536-2.patch b/glibc-RHEL-33536-2.patch new file mode 100644 index 0000000..e85e633 --- /dev/null +++ b/glibc-RHEL-33536-2.patch @@ -0,0 +1,196 @@ +commit 81763a4f7ea1c0ac0e1f475bf92440b908624301 +Author: Sergey Kolosov +Date: Mon Dec 15 13:00:01 2025 +0100 + + resolv: Add test for NOERROR/NODATA handling [BZ #14308] + + Add a test which verifies that getaddrinfo does not fail if one of A/AAAA + responses is NOERROR/NODATA reply with recursion unavailable and the other + response provides an address. + + Reviewed-by: Florian Weimer + +diff --git a/resolv/Makefile b/resolv/Makefile +index 9927676edac49e63..37d899835ea2da80 100644 +--- a/resolv/Makefile ++++ b/resolv/Makefile +@@ -144,6 +144,7 @@ tests-static += tst-ns_rr_cursor + ifeq (yes,$(build-shared)) + tests += \ + tst-getaddrinfo-eai-again-timeout \ ++ tst-resolv-af-unspec-noerror-nodata \ + tst-resolv-ai_idn \ + tst-resolv-ai_idn-latin1 \ + tst-resolv-ai_idn-nolibidn2 \ +@@ -288,6 +289,8 @@ $(objpfx)mtrace-tst-resolv-res_ninit.out: $(objpfx)tst-resolv-res_ninit.out + + $(objpfx)tst-bug18665-tcp: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-bug18665: $(objpfx)libresolv.so $(shared-thread-library) ++$(objpfx)tst-resolv-af-unspec-noerror-nodata: \ ++ $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-getaddrinfo-eai-again-timeout: \ + $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-ai_idn: $(objpfx)libresolv.so $(shared-thread-library) +diff --git a/resolv/tst-resolv-af-unspec-noerror-nodata.c b/resolv/tst-resolv-af-unspec-noerror-nodata.c +new file mode 100644 +index 0000000000000000..be925c5f724f9301 +--- /dev/null ++++ b/resolv/tst-resolv-af-unspec-noerror-nodata.c +@@ -0,0 +1,157 @@ ++/* Test for BZ #14308. ++ Verify that getaddrinfo (AF_UNSPEC) succeeds if one of the A/AAAA ++ responses is a NOERROR/NODATA reply with recursion unavailable (RA=0), ++ but the other response contains a usable address. ++ ++ Copyright (C) 2025 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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++enum scenario ++{ ++ /* A contains data, AAAA is NOERROR/NODATA with RA cleared. */ ++ A_AUTH_AAAA_FAKE, ++ ++ /* AAAA contains data, A is NOERROR/NODATA with RA cleared. */ ++ A_FAKE_AAAA_AUTH ++}; ++ ++static enum scenario current_scenario; ++ ++/* Create a NOERROR reply without answers and with RA cleared. */ ++static void ++add_fake (struct resolv_response_builder *b, ++ const char *qname, uint16_t qclass, uint16_t qtype) ++{ ++ struct resolv_response_flags flags = ++ { ++ .rcode = ns_r_noerror, ++ .clear_ra = true ++ }; ++ resolv_response_init (b, flags); ++ resolv_response_add_question (b, qname, qclass, qtype); ++} ++ ++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) ++ FAIL_EXIT1 ("Unexpected qname: %s", qname); ++ ++ if (qtype == T_A) ++ { ++ if (current_scenario == A_AUTH_AAAA_FAKE) ++ { ++ struct resolv_response_flags flags = { .rcode = ns_r_noerror }; ++ resolv_response_init (b, flags); ++ resolv_response_add_question (b, qname, qclass, qtype); ++ ++ resolv_response_section (b, ns_s_an); ++ resolv_response_open_record (b, qname, qclass, T_A, 100); ++ char addr_ipv4[4] = { 127, 128, 129, 130 }; ++ resolv_response_add_data (b, addr_ipv4, sizeof (addr_ipv4)); ++ resolv_response_close_record (b); ++ } ++ else if (current_scenario == A_FAKE_AAAA_AUTH) ++ add_fake (b, qname, qclass, qtype); ++ else ++ FAIL_EXIT1 ("Unknown scenario: %d", current_scenario); ++ ++ return; ++ } ++ ++ if (qtype == T_AAAA) ++ { ++ if (current_scenario == A_AUTH_AAAA_FAKE) ++ add_fake (b, qname, qclass, qtype); ++ else if (current_scenario == A_FAKE_AAAA_AUTH) ++ { ++ struct resolv_response_flags flags = { .rcode = ns_r_noerror }; ++ resolv_response_init (b, flags); ++ resolv_response_add_question (b, qname, qclass, qtype); ++ ++ resolv_response_section (b, ns_s_an); ++ resolv_response_open_record (b, qname, qclass, T_AAAA, 100); ++ char addr_ipv6[16] = ++ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; ++ resolv_response_add_data (b, addr_ipv6, sizeof (addr_ipv6)); ++ resolv_response_close_record (b); ++ } ++ else ++ FAIL_EXIT1 ("Unknown scenario: %d", current_scenario); ++ ++ return; ++ } ++ ++ FAIL_EXIT1 ("qtype must be one of A, AAAA"); ++} ++ ++static void ++query_host (const char *host_name) ++{ ++ struct addrinfo hints = ++ { ++ .ai_socktype = SOCK_STREAM, ++ .ai_family = AF_UNSPEC, ++ }; ++ ++ struct addrinfo *result = NULL; ++ int res = getaddrinfo (host_name, "80", &hints, &result); ++ ++ if (current_scenario == A_AUTH_AAAA_FAKE) ++ check_addrinfo (host_name, result, res, ++ "address: STREAM/TCP 127.128.129.130 80\n"); ++ else if (current_scenario == A_FAKE_AAAA_AUTH) ++ check_addrinfo (host_name, result, res, ++ "address: STREAM/TCP ::1 80\n"); ++ else ++ FAIL_EXIT1 ("Unexpected scenario: %d", current_scenario); ++ ++ if (res == 0) ++ freeaddrinfo (result); ++} ++ ++static int ++do_test (void) ++{ ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response, ++ }); ++ ++ current_scenario = A_AUTH_AAAA_FAKE; ++ query_host ("foo.site.example"); ++ ++ current_scenario = A_FAKE_AAAA_AUTH; ++ query_host ("foo.site.example"); ++ ++ resolv_test_end (aux); ++ return 0; ++} ++ ++#include diff --git a/glibc-RHEL-33536-3.patch b/glibc-RHEL-33536-3.patch new file mode 100644 index 0000000..9983182 --- /dev/null +++ b/glibc-RHEL-33536-3.patch @@ -0,0 +1,206 @@ +commit b61b98e3d0c81c85200e2082407708c7b0e821a3 +Author: Sergey Kolosov +Date: Mon Jun 22 20:15:44 2026 +0200 + + resolv: Add test for gethostbyname_r unaligned buffer [BZ #18287] + + 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. + + Reviewed-by: Florian Weimer + +diff --git a/resolv/Makefile b/resolv/Makefile +index 37d899835ea2da80..7d10d8f87252cf6a 100644 +--- a/resolv/Makefile ++++ b/resolv/Makefile +@@ -150,6 +150,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. +@@ -309,6 +310,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-multi: $(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 0000000000000000..dc1535c8dbb7c9d6 +--- /dev/null ++++ b/resolv/tst-resolv-gethostbyname_r-unaligned.c +@@ -0,0 +1,161 @@ ++/* 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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static const char host_name[] = "foo.site.example"; ++ ++/* 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, host_name) == 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); ++ } ++ } ++} ++ ++/* Test gethostbyname_r with a specified buffer size and alignment. ++ Returns true if the buffer is sufficient, false if it's too small. */ ++static bool ++query_host (size_t size, size_t align) ++{ ++ struct hostent ret; ++ struct hostent *result = NULL; ++ int err; ++ ++ /* Allocate memory for buffer, alignment padding, and a 64-byte checking ++ area. */ ++ size_t total_alloc = size + align + 64; ++ unsigned char *raw_buf = xmalloc (total_alloc); ++ ++ /* Fill the tail of the buffer with 0xAA to detect overflows below. */ ++ 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. */ ++ for (int i = 0; i < 64; i++) ++ if (raw_buf[size + align + i] != 0xAA) ++ FAIL_EXIT1 ("Buffer overflow was detected! (align=%zu, size=%zu)", ++ align, size); ++ ++ bool is_sufficient = false; ++ ++ if (res == 0 && result != NULL) ++ { ++ /* Generate the expected response to satisfy check_hostent. */ ++ struct xmemstream expected; ++ xopen_memstream (&expected); ++ ++ fprintf (expected.out, "name: %s\n", host_name); ++ for (int i = 0; i <= 60; i++) ++ fprintf (expected.out, "address: 127.126.125.%d\n", i); ++ ++ xfclose_memstream (&expected); ++ check_hostent (host_name, &ret, expected.buffer); ++ free (expected.buffer); ++ is_sufficient = true; /* Buffer is sufficient. */ ++ } ++ else if (res == ERANGE && err == NETDB_INTERNAL) ++ is_sufficient = false; /* Buffer is too small. */ ++ else ++ FAIL_EXIT1 ("gethostbyname_r failed unexpectedly: res=%d, err=%d", ++ res, err); ++ ++ free (raw_buf); ++ return is_sufficient; ++} ++ ++static int ++do_test (void) ++{ ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response, ++ }); ++ ++ int lower_bound = 512; ++ int upper_bound = 2048; ++ ++ TEST_COMPARE (query_host (lower_bound, 0), false); ++ TEST_COMPARE (query_host (upper_bound, 0), true); ++ ++ /* Finding the smallest hostent buffer size. */ ++ while (upper_bound != lower_bound + 1) ++ { ++ int size = (lower_bound + upper_bound) / 2; ++ if (query_host (size, 0)) ++ upper_bound = size; ++ else ++ lower_bound = size; ++ } ++ ++ printf ("info: Boundary found. lower_bound=%d, upper_bound=%d\n", ++ lower_bound, upper_bound); ++ ++ TEST_COMPARE (query_host (lower_bound, 0), false); ++ TEST_COMPARE (query_host (upper_bound, 0), true); ++ ++ /* Trigger the vulnerability. ++ Test all misalignments (1-7 bytes). Note that it is expected that ++ for certain alignments, the required buffer size increases. This ++ happens because gethostbyname_r applies internal padding to align ++ pointers, which consumes available space. Therefore, a patched glibc ++ safely returns true (success) or false (ERANGE) depending on this ++ padding. A vulnerable glibc will fail to account for alignment ++ padding, overflow the buffer, and cause a test failure. */ ++ for (size_t align = 1; align < 8; align++) ++ query_host (upper_bound, align); ++ ++ resolv_test_end (aux); ++ ++ return 0; ++} ++ ++#include