CVE-2023-4527: Stack read overflow with large TCP responses in no-aaaa mode

Without passing alt_dns_packet_buffer, __res_context_search can only
store 2048 bytes (what fits into dns_packet_buffer).  However,
the function returns the total packet size, and the subsequent
DNS parsing code in _nss_dns_gethostbyname4_r reads beyond the end
of the stack-allocated buffer.

Fixes commit f282cdbe7f ("resolv: Implement no-aaaa
stub resolver option") and bug 30842.
This commit is contained in:
Florian Weimer 2023-09-13 14:10:56 +02:00
parent c8fa383f4c
commit bd77dd7e73
4 changed files with 137 additions and 2 deletions

6
NEWS
View File

@ -42,7 +42,11 @@ Changes to build and runtime requirements:
Security related changes:
[Add security related changes here]
CVE-2023-4527: If the system is configured in no-aaaa mode via
/etc/resolv.conf, getaddrinfo is called for the AF_UNSPEC address
family, and a DNS response is received over TCP that is larger than
2048 bytes, getaddrinfo may potentially disclose stack contents via
the returned address data, or crash.
The following bugs are resolved with this release:

View File

@ -102,6 +102,7 @@ tests += \
tst-resolv-invalid-cname \
tst-resolv-network \
tst-resolv-noaaaa \
tst-resolv-noaaaa-vc \
tst-resolv-nondecimal \
tst-resolv-res_init-multi \
tst-resolv-search \
@ -293,6 +294,7 @@ $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \
$(objpfx)tst-resolv-invalid-cname: $(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)
$(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library)

View File

@ -427,7 +427,7 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
{
n = __res_context_search (ctx, name, C_IN, T_A,
dns_packet_buffer, sizeof (dns_packet_buffer),
NULL, NULL, NULL, NULL, NULL);
&alt_dns_packet_buffer, NULL, NULL, NULL, NULL);
if (n >= 0)
status = gaih_getanswer_noaaaa (alt_dns_packet_buffer, n,
&abuf, pat, errnop, herrnop, ttlp);

View File

@ -0,0 +1,129 @@
/* Test the RES_NOAAAA resolver option with a large response.
Copyright (C) 2022-2023 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 <netdb.h>
#include <resolv.h>
#include <stdbool.h>
#include <stdlib.h>
#include <support/check.h>
#include <support/check_nss.h>
#include <support/resolv_test.h>
#include <support/support.h>
#include <support/xmemstream.h>
/* Used to keep track of the number of queries. */
static volatile unsigned int queries;
/* If true, add a large TXT record at the start of the answer section. */
static volatile bool stuff_txt;
static void
response (const struct resolv_response_context *ctx,
struct resolv_response_builder *b,
const char *qname, uint16_t qclass, uint16_t qtype)
{
/* If not using TCP, just force its use. */
if (!ctx->tcp)
{
struct resolv_response_flags flags = {.tc = true};
resolv_response_init (b, flags);
resolv_response_add_question (b, qname, qclass, qtype);
return;
}
/* The test needs to send four queries, the first three are used to
grow the NSS buffer via the ERANGE handshake. */
++queries;
TEST_VERIFY (queries <= 4);
/* AAAA queries are supposed to be disabled. */
TEST_COMPARE (qtype, T_A);
TEST_COMPARE (qclass, C_IN);
TEST_COMPARE_STRING (qname, "example.com");
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 (stuff_txt)
{
resolv_response_open_record (b, qname, qclass, T_TXT, 60);
int zero = 0;
for (int i = 0; i <= 15000; ++i)
resolv_response_add_data (b, &zero, sizeof (zero));
resolv_response_close_record (b);
}
for (int i = 0; i < 200; ++i)
{
resolv_response_open_record (b, qname, qclass, qtype, 60);
char ipv4[4] = {192, 0, 2, i + 1};
resolv_response_add_data (b, &ipv4, sizeof (ipv4));
resolv_response_close_record (b);
}
}
static int
do_test (void)
{
struct resolv_test *obj = resolv_test_start
((struct resolv_redirect_config)
{
.response_callback = response
});
_res.options |= RES_NOAAAA;
for (int do_stuff_txt = 0; do_stuff_txt < 2; ++do_stuff_txt)
{
queries = 0;
stuff_txt = do_stuff_txt;
struct addrinfo *ai = NULL;
int ret;
ret = getaddrinfo ("example.com", "80",
&(struct addrinfo)
{
.ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
}, &ai);
char *expected_result;
{
struct xmemstream mem;
xopen_memstream (&mem);
for (int i = 0; i < 200; ++i)
fprintf (mem.out, "address: STREAM/TCP 192.0.2.%d 80\n", i + 1);
xfclose_memstream (&mem);
expected_result = mem.buffer;
}
check_addrinfo ("example.com", ai, ret, expected_result);
free (expected_result);
freeaddrinfo (ai);
}
resolv_test_end (obj);
return 0;
}
#include <support/test-driver.c>