]> git.ipfire.org Git - people/mlorenz/ipfire-2.x.git/blame - src/patches/glibc-2.38/0020-getaddrinfo-Fix-use-after-free-in-getcanonname-CVE-2.patch
glibc: Import patches from upstream
[people/mlorenz/ipfire-2.x.git] / src / patches / glibc-2.38 / 0020-getaddrinfo-Fix-use-after-free-in-getcanonname-CVE-2.patch
CommitLineData
b9215da1
MT
1From 00ae4f10b504bc4564e9f22f00907093f1ab9338 Mon Sep 17 00:00:00 2001
2From: Siddhesh Poyarekar <siddhesh@sourceware.org>
3Date: Fri, 15 Sep 2023 13:51:12 -0400
4Subject: [PATCH 20/27] getaddrinfo: Fix use after free in getcanonname
5 (CVE-2023-4806)
6
7When an NSS plugin only implements the _gethostbyname2_r and
8_getcanonname_r callbacks, getaddrinfo could use memory that was freed
9during tmpbuf resizing, through h_name in a previous query response.
10
11The backing store for res->at->name when doing a query with
12gethostbyname3_r or gethostbyname2_r is tmpbuf, which is reallocated in
13gethosts during the query. For AF_INET6 lookup with AI_ALL |
14AI_V4MAPPED, gethosts gets called twice, once for a v6 lookup and second
15for a v4 lookup. In this case, if the first call reallocates tmpbuf
16enough number of times, resulting in a malloc, th->h_name (that
17res->at->name refers to) ends up on a heap allocated storage in tmpbuf.
18Now if the second call to gethosts also causes the plugin callback to
19return NSS_STATUS_TRYAGAIN, tmpbuf will get freed, resulting in a UAF
20reference in res->at->name. This then gets dereferenced in the
21getcanonname_r plugin call, resulting in the use after free.
22
23Fix this by copying h_name over and freeing it at the end. This
24resolves BZ #30843, which is assigned CVE-2023-4806.
25
26Signed-off-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
27(cherry picked from commit 973fe93a5675c42798b2161c6f29c01b0e243994)
28---
29 nss/Makefile | 15 ++++-
30 nss/nss_test_gai_hv2_canonname.c | 56 +++++++++++++++++
31 nss/tst-nss-gai-hv2-canonname.c | 63 +++++++++++++++++++
32 nss/tst-nss-gai-hv2-canonname.h | 1 +
33 .../postclean.req | 0
34 .../tst-nss-gai-hv2-canonname.script | 2 +
35 sysdeps/posix/getaddrinfo.c | 25 +++++---
36 7 files changed, 152 insertions(+), 10 deletions(-)
37 create mode 100644 nss/nss_test_gai_hv2_canonname.c
38 create mode 100644 nss/tst-nss-gai-hv2-canonname.c
39 create mode 100644 nss/tst-nss-gai-hv2-canonname.h
40 create mode 100644 nss/tst-nss-gai-hv2-canonname.root/postclean.req
41 create mode 100644 nss/tst-nss-gai-hv2-canonname.root/tst-nss-gai-hv2-canonname.script
42
43diff --git a/nss/Makefile b/nss/Makefile
44index 06fcdc450f..8a5126ecf3 100644
45--- a/nss/Makefile
46+++ b/nss/Makefile
47@@ -82,6 +82,7 @@ tests-container := \
48 tst-nss-test3 \
49 tst-reload1 \
50 tst-reload2 \
51+ tst-nss-gai-hv2-canonname \
52 # tests-container
53
54 # Tests which need libdl
55@@ -145,7 +146,8 @@ libnss_compat-inhibit-o = $(filter-out .os,$(object-suffixes))
56 ifeq ($(build-static-nss),yes)
57 tests-static += tst-nss-static
58 endif
59-extra-test-objs += nss_test1.os nss_test2.os nss_test_errno.os
60+extra-test-objs += nss_test1.os nss_test2.os nss_test_errno.os \
61+ nss_test_gai_hv2_canonname.os
62
63 include ../Rules
64
65@@ -180,12 +182,16 @@ rtld-tests-LDFLAGS += -Wl,--dynamic-list=nss_test.ver
66 libof-nss_test1 = extramodules
67 libof-nss_test2 = extramodules
68 libof-nss_test_errno = extramodules
69+libof-nss_test_gai_hv2_canonname = extramodules
70 $(objpfx)/libnss_test1.so: $(objpfx)nss_test1.os $(link-libc-deps)
71 $(build-module)
72 $(objpfx)/libnss_test2.so: $(objpfx)nss_test2.os $(link-libc-deps)
73 $(build-module)
74 $(objpfx)/libnss_test_errno.so: $(objpfx)nss_test_errno.os $(link-libc-deps)
75 $(build-module)
76+$(objpfx)/libnss_test_gai_hv2_canonname.so: \
77+ $(objpfx)nss_test_gai_hv2_canonname.os $(link-libc-deps)
78+ $(build-module)
79 $(objpfx)nss_test2.os : nss_test1.c
80 # Use the nss_files suffix for these objects as well.
81 $(objpfx)/libnss_test1.so$(libnss_files.so-version): $(objpfx)/libnss_test1.so
82@@ -195,10 +201,14 @@ $(objpfx)/libnss_test2.so$(libnss_files.so-version): $(objpfx)/libnss_test2.so
83 $(objpfx)/libnss_test_errno.so$(libnss_files.so-version): \
84 $(objpfx)/libnss_test_errno.so
85 $(make-link)
86+$(objpfx)/libnss_test_gai_hv2_canonname.so$(libnss_files.so-version): \
87+ $(objpfx)/libnss_test_gai_hv2_canonname.so
88+ $(make-link)
89 $(patsubst %,$(objpfx)%.out,$(tests) $(tests-container)) : \
90 $(objpfx)/libnss_test1.so$(libnss_files.so-version) \
91 $(objpfx)/libnss_test2.so$(libnss_files.so-version) \
92- $(objpfx)/libnss_test_errno.so$(libnss_files.so-version)
93+ $(objpfx)/libnss_test_errno.so$(libnss_files.so-version) \
94+ $(objpfx)/libnss_test_gai_hv2_canonname.so$(libnss_files.so-version)
95
96 ifeq (yes,$(have-thread-library))
97 $(objpfx)tst-cancel-getpwuid_r: $(shared-thread-library)
98@@ -215,3 +225,4 @@ LDFLAGS-tst-nss-test3 = -Wl,--disable-new-dtags
99 LDFLAGS-tst-nss-test4 = -Wl,--disable-new-dtags
100 LDFLAGS-tst-nss-test5 = -Wl,--disable-new-dtags
101 LDFLAGS-tst-nss-test_errno = -Wl,--disable-new-dtags
102+LDFLAGS-tst-nss-test_gai_hv2_canonname = -Wl,--disable-new-dtags
103diff --git a/nss/nss_test_gai_hv2_canonname.c b/nss/nss_test_gai_hv2_canonname.c
104new file mode 100644
105index 0000000000..4439c83c9f
106--- /dev/null
107+++ b/nss/nss_test_gai_hv2_canonname.c
108@@ -0,0 +1,56 @@
109+/* NSS service provider that only provides gethostbyname2_r.
110+ Copyright The GNU Toolchain Authors.
111+ This file is part of the GNU C Library.
112+
113+ The GNU C Library is free software; you can redistribute it and/or
114+ modify it under the terms of the GNU Lesser General Public
115+ License as published by the Free Software Foundation; either
116+ version 2.1 of the License, or (at your option) any later version.
117+
118+ The GNU C Library is distributed in the hope that it will be useful,
119+ but WITHOUT ANY WARRANTY; without even the implied warranty of
120+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
121+ Lesser General Public License for more details.
122+
123+ You should have received a copy of the GNU Lesser General Public
124+ License along with the GNU C Library; if not, see
125+ <https://www.gnu.org/licenses/>. */
126+
127+#include <nss.h>
128+#include <stdlib.h>
129+#include <string.h>
130+#include "nss/tst-nss-gai-hv2-canonname.h"
131+
132+/* Catch misnamed and functions. */
133+#pragma GCC diagnostic error "-Wmissing-prototypes"
134+NSS_DECLARE_MODULE_FUNCTIONS (test_gai_hv2_canonname)
135+
136+extern enum nss_status _nss_files_gethostbyname2_r (const char *, int,
137+ struct hostent *, char *,
138+ size_t, int *, int *);
139+
140+enum nss_status
141+_nss_test_gai_hv2_canonname_gethostbyname2_r (const char *name, int af,
142+ struct hostent *result,
143+ char *buffer, size_t buflen,
144+ int *errnop, int *herrnop)
145+{
146+ return _nss_files_gethostbyname2_r (name, af, result, buffer, buflen, errnop,
147+ herrnop);
148+}
149+
150+enum nss_status
151+_nss_test_gai_hv2_canonname_getcanonname_r (const char *name, char *buffer,
152+ size_t buflen, char **result,
153+ int *errnop, int *h_errnop)
154+{
155+ /* We expect QUERYNAME, which is a small enough string that it shouldn't fail
156+ the test. */
157+ if (memcmp (QUERYNAME, name, sizeof (QUERYNAME))
158+ || buflen < sizeof (QUERYNAME))
159+ abort ();
160+
161+ strncpy (buffer, name, buflen);
162+ *result = buffer;
163+ return NSS_STATUS_SUCCESS;
164+}
165diff --git a/nss/tst-nss-gai-hv2-canonname.c b/nss/tst-nss-gai-hv2-canonname.c
166new file mode 100644
167index 0000000000..d5f10c07d6
168--- /dev/null
169+++ b/nss/tst-nss-gai-hv2-canonname.c
170@@ -0,0 +1,63 @@
171+/* Test NSS query path for plugins that only implement gethostbyname2
172+ (#30843).
173+ Copyright The GNU Toolchain Authors.
174+ This file is part of the GNU C Library.
175+
176+ The GNU C Library is free software; you can redistribute it and/or
177+ modify it under the terms of the GNU Lesser General Public
178+ License as published by the Free Software Foundation; either
179+ version 2.1 of the License, or (at your option) any later version.
180+
181+ The GNU C Library is distributed in the hope that it will be useful,
182+ but WITHOUT ANY WARRANTY; without even the implied warranty of
183+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
184+ Lesser General Public License for more details.
185+
186+ You should have received a copy of the GNU Lesser General Public
187+ License along with the GNU C Library; if not, see
188+ <https://www.gnu.org/licenses/>. */
189+
190+#include <nss.h>
191+#include <netdb.h>
192+#include <stdlib.h>
193+#include <string.h>
194+#include <support/check.h>
195+#include <support/xstdio.h>
196+#include "nss/tst-nss-gai-hv2-canonname.h"
197+
198+#define PREPARE do_prepare
199+
200+static void do_prepare (int a, char **av)
201+{
202+ FILE *hosts = xfopen ("/etc/hosts", "w");
203+ for (unsigned i = 2; i < 255; i++)
204+ {
205+ fprintf (hosts, "ff01::ff02:ff03:%u:2\ttest.example.com\n", i);
206+ fprintf (hosts, "192.168.0.%u\ttest.example.com\n", i);
207+ }
208+ xfclose (hosts);
209+}
210+
211+static int
212+do_test (void)
213+{
214+ __nss_configure_lookup ("hosts", "test_gai_hv2_canonname");
215+
216+ struct addrinfo hints = {};
217+ struct addrinfo *result = NULL;
218+
219+ hints.ai_family = AF_INET6;
220+ hints.ai_flags = AI_ALL | AI_V4MAPPED | AI_CANONNAME;
221+
222+ int ret = getaddrinfo (QUERYNAME, NULL, &hints, &result);
223+
224+ if (ret != 0)
225+ FAIL_EXIT1 ("getaddrinfo failed: %s\n", gai_strerror (ret));
226+
227+ TEST_COMPARE_STRING (result->ai_canonname, QUERYNAME);
228+
229+ freeaddrinfo(result);
230+ return 0;
231+}
232+
233+#include <support/test-driver.c>
234diff --git a/nss/tst-nss-gai-hv2-canonname.h b/nss/tst-nss-gai-hv2-canonname.h
235new file mode 100644
236index 0000000000..14f2a9cb08
237--- /dev/null
238+++ b/nss/tst-nss-gai-hv2-canonname.h
239@@ -0,0 +1 @@
240+#define QUERYNAME "test.example.com"
241diff --git a/nss/tst-nss-gai-hv2-canonname.root/postclean.req b/nss/tst-nss-gai-hv2-canonname.root/postclean.req
242new file mode 100644
243index 0000000000..e69de29bb2
244diff --git a/nss/tst-nss-gai-hv2-canonname.root/tst-nss-gai-hv2-canonname.script b/nss/tst-nss-gai-hv2-canonname.root/tst-nss-gai-hv2-canonname.script
245new file mode 100644
246index 0000000000..31848b4a28
247--- /dev/null
248+++ b/nss/tst-nss-gai-hv2-canonname.root/tst-nss-gai-hv2-canonname.script
249@@ -0,0 +1,2 @@
250+cp $B/nss/libnss_test_gai_hv2_canonname.so $L/libnss_test_gai_hv2_canonname.so.2
251+su
252diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c
253index 0356b622be..b2236b105c 100644
254--- a/sysdeps/posix/getaddrinfo.c
255+++ b/sysdeps/posix/getaddrinfo.c
256@@ -120,6 +120,7 @@ struct gaih_result
257 {
258 struct gaih_addrtuple *at;
259 char *canon;
260+ char *h_name;
261 bool free_at;
262 bool got_ipv6;
263 };
264@@ -165,6 +166,7 @@ gaih_result_reset (struct gaih_result *res)
265 if (res->free_at)
266 free (res->at);
267 free (res->canon);
268+ free (res->h_name);
269 memset (res, 0, sizeof (*res));
270 }
271
272@@ -203,9 +205,8 @@ gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp,
273 return 0;
274 }
275
276-/* Convert struct hostent to a list of struct gaih_addrtuple objects. h_name
277- is not copied, and the struct hostent object must not be deallocated
278- prematurely. The new addresses are appended to the tuple array in RES. */
279+/* Convert struct hostent to a list of struct gaih_addrtuple objects. The new
280+ addresses are appended to the tuple array in RES. */
281 static bool
282 convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, int family,
283 struct hostent *h, struct gaih_result *res)
284@@ -238,6 +239,15 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, int family,
285 res->at = array;
286 res->free_at = true;
287
288+ /* Duplicate h_name because it may get reclaimed when the underlying storage
289+ is freed. */
290+ if (res->h_name == NULL)
291+ {
292+ res->h_name = __strdup (h->h_name);
293+ if (res->h_name == NULL)
294+ return false;
295+ }
296+
297 /* Update the next pointers on reallocation. */
298 for (size_t i = 0; i < old; i++)
299 array[i].next = array + i + 1;
300@@ -262,7 +272,6 @@ convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, int family,
301 }
302 array[i].next = array + i + 1;
303 }
304- array[0].name = h->h_name;
305 array[count - 1].next = NULL;
306
307 return true;
308@@ -324,15 +333,15 @@ gethosts (nss_gethostbyname3_r fct, int family, const char *name,
309 memory allocation failure. The returned string is allocated on the
310 heap; the caller has to free it. */
311 static char *
312-getcanonname (nss_action_list nip, struct gaih_addrtuple *at, const char *name)
313+getcanonname (nss_action_list nip, const char *hname, const char *name)
314 {
315 nss_getcanonname_r *cfct = __nss_lookup_function (nip, "getcanonname_r");
316 char *s = (char *) name;
317 if (cfct != NULL)
318 {
319 char buf[256];
320- if (DL_CALL_FCT (cfct, (at->name ?: name, buf, sizeof (buf),
321- &s, &errno, &h_errno)) != NSS_STATUS_SUCCESS)
322+ if (DL_CALL_FCT (cfct, (hname ?: name, buf, sizeof (buf), &s, &errno,
323+ &h_errno)) != NSS_STATUS_SUCCESS)
324 /* If the canonical name cannot be determined, use the passed
325 string. */
326 s = (char *) name;
327@@ -771,7 +780,7 @@ get_nss_addresses (const char *name, const struct addrinfo *req,
328 if ((req->ai_flags & AI_CANONNAME) != 0
329 && res->canon == NULL)
330 {
331- char *canonbuf = getcanonname (nip, res->at, name);
332+ char *canonbuf = getcanonname (nip, res->h_name, name);
333 if (canonbuf == NULL)
334 {
335 __resolv_context_put (res_ctx);
336--
3372.39.2
338