From patchwork Fri Mar 20 21:51:59 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Carlos O'Donell X-Patchwork-Id: 132130 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 3BAA94C900E9 for ; Fri, 20 Mar 2026 21:53:21 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3BAA94C900E9 Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=VND3EUvu X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 57BEB4C31885 for ; Fri, 20 Mar 2026 21:52:41 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 57BEB4C31885 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 57BEB4C31885 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1774043561; cv=none; b=nUrmErRspWGPkGir+OrQGKNxJ137ICEv1iEgAOceEMeNvG5nmx+Q7H0fpyQNQucHTgJj4QsvxlKx26t1ImxQtpOXUHHSbhk1OLA4wA0mhaphzP7SoxZcix1zM4jOV+AybzjtvxRNC1CvM8MTdaKK1v0D9q2+QrhlajSB1ARsPV0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1774043561; c=relaxed/simple; bh=k2yZGdv2AJvA25/yUtWXxbC/5R+V0AgNFFhhGEG/ALw=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=LDA/KYIHowTzxttDI2/bS//BmDE78loIMdxak1diZEmnTjhXflb3q6GHiN0FcCYasbUMQ7PW2h9kQPpo6ZM3cj1foalsrYWEI0hpQAHqWQBVTjLglauNmUkZIcRHK2coTPUsvucZ4RxeMPtLQ92c13nlVUUSGw4G05mSTlvnQkw= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 57BEB4C31885 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1774043560; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=qCFk6y61V5iNcDIabZkXGyZDVmImKJDhyXBdA8Nur/k=; b=VND3EUvu2Qpw5FaorjIPXLt/je3HniEMbA5Zvwh0tmJ/vxMVVuIiqAYUtO1U+ABocMjfz0 5pBu8EM6MQJ/84uF6pPDqtGOKeW3fA436GztGL+nbFQ8ep60r/u9R5v06OgiSR2rXGTj0M ViwraNx37m2+qpAoMztiG56vjWfYh+k= Received: from mail-qk1-f200.google.com (mail-qk1-f200.google.com [209.85.222.200]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-149-p0snX0AqPCWH3eGHu_0x6w-1; Fri, 20 Mar 2026 17:52:39 -0400 X-MC-Unique: p0snX0AqPCWH3eGHu_0x6w-1 X-Mimecast-MFC-AGG-ID: p0snX0AqPCWH3eGHu_0x6w_1774043559 Received: by mail-qk1-f200.google.com with SMTP id af79cd13be357-8cd773dd409so352760885a.1 for ; Fri, 20 Mar 2026 14:52:39 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774043558; x=1774648358; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=qCFk6y61V5iNcDIabZkXGyZDVmImKJDhyXBdA8Nur/k=; b=iTM6PRtVM06BtvypaMPdk0vYEjIISY5F4r24Dee3IVY0+WbVIHhe+8PobkxLgylCTB CvjGdz9qUwnhA0WM2tOJeX//bCvLOmwL5AhyOrNn04goytZGrNFA23ZO7dGN3f2oCNxF AYkplVLhGCl/FXMYxLWqx/JQvo18kK4rh42LRd0QnmxfyV+tmPA2ZFPLBx0fj5edQh1R wkhhB1Dw1MWcxvNKmvwzYj6Oi44q49MeL8zf0jC33w4WixU8EjoqImxu5EtkwLtr2WHG FZaAiSCqIasedcJSfw719qyMvr1XVGvnDgASGuDMtXPgj1hnxve3YFAt6dtCO4xDQVhI +nFA== X-Gm-Message-State: AOJu0YwUN3S3WjhlOFWmlUAILUaV9jw2SUKc33WGpC3+ktyc288S4YU7 GaCsHc6urlcUPLLY/HMuTQvXy4hfTmHrAC08b9v1YmnDkFCV+z7o0UHMvW1ijUMfLJUMoAQzyX3 r9/zptQbo5HvkHnowxiAqACScKNewPxZibAmegkeUWy04C73ZE/hT/wyLrn1M2V1sjDOCPtoezU jeiQqbNnKHpT5wtokqWs78pKIhww5G02M91jA2zMG7yKg= X-Gm-Gg: ATEYQzxjexEtgQcztyyMdotum4gVGaeYzANPZ3sRGztNtziLkptjc1XgT6LK2rr7QvN 2eArqmr/edbN9hvTB6eismXZ3/DPkhTA7vpWEky01x3VmG92u6bL+QohAbvIYRqiUpg73Ej+lMd OX8anhqGygoEcUV/FQO0BgNFTK9Fxe5OkiaT7iKyDZ2aDHikbUWUox7zKHvvQM6ULqV65Rm9qlN AnlVrurNLtjQubBgKn2xRp6m2drqYSRVT4ENQFFZICwvIDi187UPNVRpitSvyw1FkQBoBAkaF2v 4FsZe6unL17mkA7xMNQotWwSkfVEo2mshm3/kQcabTfZgqIMEnPQlRjCE2MJ0byp/7x1S3ctXVU 17ldOJVACoLe7I0cEYGSuoj7dUd+Vb8Yfc0s7am42+CE/3raTIKULI+rMiing+pb9HVicj1aA3q lK14MA3DeiLVSJXdbcd78+dlD/46NGbhBW8x4T9nwbe4514vA0RpU= X-Received: by 2002:a05:620a:4009:b0:8cd:b7ff:abb0 with SMTP id af79cd13be357-8cfc7f7fd8bmr675675285a.52.1774043558106; Fri, 20 Mar 2026 14:52:38 -0700 (PDT) X-Received: by 2002:a05:620a:4009:b0:8cd:b7ff:abb0 with SMTP id af79cd13be357-8cfc7f7fd8bmr675670085a.52.1774043557273; Fri, 20 Mar 2026 14:52:37 -0700 (PDT) Received: from codonell-thinkpadp16vgen1.rmtcaon.csb ([198.48.244.52]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-89c85335402sm36649326d6.25.2026.03.20.14.52.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 14:52:36 -0700 (PDT) From: Carlos O'Donell To: libc-alpha@sourceware.org, fweimer@redhat.com, collin.funk1@gmail.com Cc: Carlos O'Donell Subject: [PATCH v2 1/2] resolv: Count records correctly (CVE-2026-4437) Date: Fri, 20 Mar 2026 17:51:59 -0400 Message-ID: <20260320215226.2426367-1-carlos@redhat.com> X-Mailer: git-send-email 2.53.0 MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: uuWRL32G4IuKuCD2aTub-D-D_esAmOvCLYey8h6nHJ4_1774043559 X-Mimecast-Originator: redhat.com content-type: text/plain; charset="US-ASCII"; x-default=true X-Spam-Status: No, score=-11.0 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org 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 --- v1 -> v2 - Split out changes for CVE-2026-4437 - Incorporate Florian's suggestions. - Remove not-needed xmemstream.h included. - Fix IPv6 test to properly scan count via sscanf. - A/B tested changes again to review error messages on failure. resolv/Makefile | 4 + resolv/nss_dns/dns-host.c | 2 +- resolv/tst-resolv-dns-section.c | 162 ++++++++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 resolv/tst-resolv-dns-section.c diff --git a/resolv/Makefile b/resolv/Makefile index 34916a90cc..95bad5df27 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -114,6 +114,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 \ @@ -125,6 +126,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. @@ -299,6 +301,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 6a60c87532..893137027e 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 From patchwork Fri Mar 20 21:52:00 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Carlos O'Donell X-Patchwork-Id: 132131 X-Patchwork-Delegate: azanella@linux.vnet.ibm.com Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id 34A3C4C900DF for ; Fri, 20 Mar 2026 21:54:06 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 34A3C4C900DF Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=Ddo2PvAO X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 301184BB5926 for ; Fri, 20 Mar 2026 21:52:43 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 301184BB5926 Authentication-Results: sourceware.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 301184BB5926 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1774043563; cv=none; b=Gpk4y1iySd7qqtsGfemu+DsNu4viNiK2oCawPPcPMaIHsI2qIG/yF9HTZ7inWuT0l57L6pinFo10NjqNCShvkn9NTmPYL4NHWufnoASBanfQfDlLTPp4vAjFiCX18Kqwgm7lSYQHOpFG5vT0k0LR+eCWtkFUOBtGXC6LDcEfrFs= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1774043563; c=relaxed/simple; bh=gJtBEufAuB4cUQ7xefOaWxektAYXfe5sulH+eV+LDWU=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=JB7YtYv5l+rhSKjQXjVO6o0ZcHr5qWlneRJJnXDNhbPq2GuLILltr6UCZB5e8AnO7BFXL4FSqB1+h9eRv95R8TJMleAh3ZDbviD0BRPI0S0BVtUanD/RTZCoVfI4WxbnPkkshcfj0/F9qSSoDchX5ycdy0Ujgqk2mIrfWGvGDmc= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 301184BB5926 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1774043562; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=NDMkfeHKaTrKRfcE5OjMadObyaISj5wVxHPgO4Bl5vs=; b=Ddo2PvAOa+HFslM186ouQBeaIGsAMSjoeXkpvQ+lORO8bxvrHdAcSWfGC7A88e1QSUatKr ec6JDHP8hivHC0WdOLPXT5t+7L+ox4QEoqzPWbuWGFbqN8MsSjhervUXaT3KP0o2Ta8SaK rWPgEDBxfTGfOogn7R8M1ycMcGizbCg= Received: from mail-qk1-f198.google.com (mail-qk1-f198.google.com [209.85.222.198]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-283-viC8i9zBNcqMIM7dpy5oYA-1; Fri, 20 Mar 2026 17:52:41 -0400 X-MC-Unique: viC8i9zBNcqMIM7dpy5oYA-1 X-Mimecast-MFC-AGG-ID: viC8i9zBNcqMIM7dpy5oYA_1774043561 Received: by mail-qk1-f198.google.com with SMTP id af79cd13be357-8cfc5df1dccso660922485a.1 for ; Fri, 20 Mar 2026 14:52:41 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774043560; x=1774648360; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=NDMkfeHKaTrKRfcE5OjMadObyaISj5wVxHPgO4Bl5vs=; b=g1nDBC7YWlVZyXKhLbskNU0+avQKweZX1I+KEKF1jm/9ZR216qrgHMI9mipLOfBAqr 2aj+CLjr27O5+em2Qh7V38vcEluqp20FP44VEkvUBEkpPgCEYqeMi1+48XtMPwK+F8DD c9mamzoGjWhOQD+Q4d0HeEIt13gXSZRNtR80P9/w8eWLDRssd83jKXSBvc20Vt1ox9C+ +NL1k5ES6vA830tVbdZbU+jlSzSijewzKCyjKX3g1HfaMr3JBgzLBpBJr2FCYcDLmOYw BJxqg8+qKN8e/uE0ZCjf44iQfY9Tu4eC5wZtEK1+y2cU0JU2L4vE1HxHehs2QZAr2pYF 4CEA== X-Gm-Message-State: AOJu0Yy4RG8Q791F70uj6DcYIXVK1Qj5OTkuqRLjMpVK5iKXm52N9zQJ fNAMsX1YDUfkkH1RVwtqQldCPgvSNP5R3Oc6CGhyggplhhl9ALFphvUdYvbo8V5lVRsYJ87X/TO e6gN7RL7+OZz84iFDnmxML7Zs6dizVuTdOA+FfglF8uuOCdfR2cAP3eJkX/gItHCCSKdGdKWTY4 0+h06Zn/2VuBLQ6rciFyRHOlxNtvAXo4CL/5dgPiNhl9Q= X-Gm-Gg: ATEYQzwKgo2PfP3uBimbpGc0kot3oStpww70dUAonItPzvyXyQ1ramZ1liurZYor7ba HVo+sBkYXuTmEMq5hahUCVdtQ7t31y6PxoAc4MQRPch910lOKB1EM3CDMn45fHA+dWFZQQPw0Rp qPGAdhSV+iRlvlu93fEZMBIODUcJ37m5ToSAhbqncN+IYjyQEMq4oHsUO8CpRC4NnM15Q+E85jG CfyBp0Hi6Fe5IGsCgkHD+M4qO7UMCHWYVXV2idJb2JhexnE/Mw47a0rBBuu8tKYbNJFZsNOnFB6 mT1GtKr/hexTaHeTNpZrz3jqms3qilnNnD5xbeRw15uosQxJ0eVLaPA6l4l090X8TnyjD4K6EVr n0yVoha1dgYiAQiYrvvayOAP/3Rskt8peQtskUHjc1gieflus9IjbHYPJ6A63NvvnbxPa1d0GXW cWLWgKn/wcDPxsHo5hFjG8aj7nVPsmK12ZVJCQgWJFBl0t9fZA5A0= X-Received: by 2002:a05:620a:28c2:b0:8cd:8d74:cae6 with SMTP id af79cd13be357-8cfc7f51423mr690571785a.38.1774043560412; Fri, 20 Mar 2026 14:52:40 -0700 (PDT) X-Received: by 2002:a05:620a:28c2:b0:8cd:8d74:cae6 with SMTP id af79cd13be357-8cfc7f51423mr690568885a.38.1774043559886; Fri, 20 Mar 2026 14:52:39 -0700 (PDT) Received: from codonell-thinkpadp16vgen1.rmtcaon.csb ([198.48.244.52]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-89c85335402sm36649326d6.25.2026.03.20.14.52.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 20 Mar 2026 14:52:38 -0700 (PDT) From: Carlos O'Donell To: libc-alpha@sourceware.org, fweimer@redhat.com, collin.funk1@gmail.com Cc: Carlos O'Donell Subject: [PATCH v2 2/2] resolv: Check hostname for validity (CVE-2026-4438) Date: Fri, 20 Mar 2026 17:52:00 -0400 Message-ID: <20260320215226.2426367-2-carlos@redhat.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260320215226.2426367-1-carlos@redhat.com> References: <20260320215226.2426367-1-carlos@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-MFC-PROC-ID: MfOyZNm9oIEz3tyjaGVgvZy1mkoPEOH4FLC6lXTva34_1774043561 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-9.5 required=5.0 tests=BAYES_00, BODY_8BITS, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_PASS, SPF_NONE, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org The processed hostname in getanswer_ptr should be correctly checked to avoid invalid characters from being allowed, including shell metacharacters. It is a security issue to fail to check the returned hostname for validity. A regression test is added for invalid metacharacters and other cases of invalid or valid characters. No regressions on x86_64-linux-gnu. Reviewed-by: Adhemerval Zanella --- v1 -> v2 - Split out changes for CVE-2026-4438 - Incorporate Florian's suggestions. - Remove not-needed xmemstream.h included. - Drop superflous printf in test loop (too verbose). - A/B tested changes again to review error messages on failure. resolv/Makefile | 3 + resolv/nss_dns/dns-host.c | 2 +- resolv/tst-resolv-invalid-ptr.c | 254 ++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 resolv/tst-resolv-invalid-ptr.c diff --git a/resolv/Makefile b/resolv/Makefile index 95bad5df27..971608eff5 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -117,6 +117,7 @@ tests += \ tst-resolv-dns-section \ tst-resolv-edns \ tst-resolv-invalid-cname \ + tst-resolv-invalid-ptr \ tst-resolv-network \ tst-resolv-noaaaa \ tst-resolv-noaaaa-vc \ @@ -312,6 +313,8 @@ $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \ $(shared-thread-library) $(objpfx)tst-resolv-invalid-cname: $(objpfx)libresolv.so \ $(shared-thread-library) +$(objpfx)tst-resolv-invalid-ptr: $(objpfx)libresolv.so \ + $(shared-thread-library) $(objpfx)tst-resolv-no-search: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library) $(objpfx)tst-resolv-noaaaa-vc: $(objpfx)libresolv.so $(shared-thread-library) diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c index 893137027e..728dae615d 100644 --- a/resolv/nss_dns/dns-host.c +++ b/resolv/nss_dns/dns-host.c @@ -866,7 +866,7 @@ getanswer_ptr (unsigned char *packet, size_t packetlen, char hname[MAXHOSTNAMELEN + 1]; if (__ns_name_unpack (c.begin, c.end, rr.rdata, name_buffer, sizeof (name_buffer)) < 0 - || !__res_binary_hnok (expected_name) + || !__res_binary_hnok (name_buffer) || __ns_name_ntop (name_buffer, hname, sizeof (hname)) < 0) { *h_errnop = NO_RECOVERY; diff --git a/resolv/tst-resolv-invalid-ptr.c b/resolv/tst-resolv-invalid-ptr.c new file mode 100644 index 0000000000..4671935795 --- /dev/null +++ b/resolv/tst-resolv-invalid-ptr.c @@ -0,0 +1,254 @@ +/* Test handling of invalid T_PTR results (bug 34015). + 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Name of test, the answer, the expected error return, and if we + expect the call to fail. */ +struct item { + const char *test; + const char *answer; + int expected; + bool fail; +}; + +static const struct item test_items[] = + { + /* Test for invalid characters. */ + { "Invalid use of \"|\"", + "test.|.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"&\"", + "test.&.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \";\"", + "test.;.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"<\"", + "test.<.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \">\"", + "test.>.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"(\"", + "test.(.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \")\"", + "test.).ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"$\"", + "test.$.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"`\"", + "test.`.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"\\\"", + "test.\\.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"\'\"", + "test.'.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"\"\"", + "test.\".ptr.example", NO_RECOVERY, true }, + { "Invalid use of \" \"", + "test. .ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"\\t\"", + "test.\t.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"\\n\"", + "test.\n.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"\\r\"", + "test.\r.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"*\"", + "test.*.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"?\"", + "test.?.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"[\"", + "test.[.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"]\"", + "test.].ptr.example", NO_RECOVERY, true }, + { "Invalid use of \",\"", + "test.,.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"~\"", + "test.~.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \":\"", + "test.:.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"!\"", + "test.!.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"@\"", + "test.@.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"#\"", + "test.#.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"%\"", + "test.%%.ptr.example", NO_RECOVERY, true }, + { "Invalid use of \"^\"", + "test.^.ptr.example", NO_RECOVERY, true }, + + /* Test for invalid UTF-8 characters (2-byte, 4-byte, 6-byte). */ + { "Invalid use of UTF-8 (2-byte, U+00C0-U+00C2)", + "ÁÂÃ.test.ptr.example", NO_RECOVERY, true }, + { "Invalid use of UTF-8 (4-byte, U+0750-U+0752)", + "ݐݑݒ.test.ptr.example", NO_RECOVERY, true }, + { "Invalid use of UTF-8 (6-byte, U+0904-U+0906)", + "ऄअआ.test.ptr.example", NO_RECOVERY, true }, + + /* Test for "-" which may be valid depending on position. */ + { "Invalid leading \"-\"", + "-test.ptr.example", NO_RECOVERY, true }, + { "Valid trailing \"-\"", + "test-.ptr.example", 0, false }, + { "Valid mid-label use of \"-\"", + "te-st.ptr.example", 0, false }, + + /* Test for "_" which is always valid in any position. */ + { "Valid leading use of \"_\"", + "_test.ptr.example", 0, false }, + { "Valid mid-label use of \"_\"", + "te_st.ptr.example", 0, false }, + { "Valid trailing use of \"_\"", + "test_.ptr.example", 0, false }, + + /* Sanity test the broader set [A-Za-z0-9_-] of valid characters. */ + { "Valid \"[A-Z]\"", + "test.ABCDEFGHIJKLMNOPQRSTUVWXYZ.ptr.example", 0, false }, + { "Valid \"[a-z]\"", + "test.abcdefghijklmnopqrstuvwxyz.ptr.example", 0, false }, + { "Valid \"[0-9]\"", + "test.0123456789.ptr.example", 0, false }, + { "Valid mixed use of \"[A-Za-z0-9_-]\"", + "test.012abcABZ_-.ptr.example", 0, false }, + + { NULL, 0, 0 }, + }; + +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, count1; + 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.%x.%ms", &count, &count1, &tail) == 3) + { + 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.8.b.d.0.1.0.0.2.ip6.arpa"); + count |= count1 << 4; + } + 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 <= 255); + + 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. */ + resolv_response_open_record (b, qname, qclass, qtype, 60); + + /* Record the answer. */ + resolv_response_add_name (b, test_items[count].answer); + 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)); + + /* Generate an address to query for each test. */ + 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] = (char) count; + addrlen = 16; + } + + h_errno = 0; + struct hostent *answer = gethostbyaddr (addr, addrlen, af); + + /* Verify h_errno is as expected. */ + TEST_COMPARE (h_errno, test_items[count].expected); + if (h_errno != test_items[count].expected) + /* And print more information if it's not. */ + printf ("INFO: %s\n", test_items[count].test); + + if (test_items[count].fail) + { + /* We expected a failure so verify answer is NULL. */ + TEST_VERIFY (answer == NULL); + /* If it's not NULL we should print out what we received. */ + if (answer != NULL) + printf ("error: unexpected success: %s\n", + support_format_hostent (answer)); + } + else + /* We don't expect a failure so answer must be valid. */ + TEST_COMPARE_STRING (answer->h_name, test_items[count].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