]>
Commit | Line | Data |
---|---|---|
b9215da1 MT |
1 | From 00ae4f10b504bc4564e9f22f00907093f1ab9338 Mon Sep 17 00:00:00 2001 |
2 | From: Siddhesh Poyarekar <siddhesh@sourceware.org> | |
3 | Date: Fri, 15 Sep 2023 13:51:12 -0400 | |
4 | Subject: [PATCH 20/27] getaddrinfo: Fix use after free in getcanonname | |
5 | (CVE-2023-4806) | |
6 | ||
7 | When an NSS plugin only implements the _gethostbyname2_r and | |
8 | _getcanonname_r callbacks, getaddrinfo could use memory that was freed | |
9 | during tmpbuf resizing, through h_name in a previous query response. | |
10 | ||
11 | The backing store for res->at->name when doing a query with | |
12 | gethostbyname3_r or gethostbyname2_r is tmpbuf, which is reallocated in | |
13 | gethosts during the query. For AF_INET6 lookup with AI_ALL | | |
14 | AI_V4MAPPED, gethosts gets called twice, once for a v6 lookup and second | |
15 | for a v4 lookup. In this case, if the first call reallocates tmpbuf | |
16 | enough number of times, resulting in a malloc, th->h_name (that | |
17 | res->at->name refers to) ends up on a heap allocated storage in tmpbuf. | |
18 | Now if the second call to gethosts also causes the plugin callback to | |
19 | return NSS_STATUS_TRYAGAIN, tmpbuf will get freed, resulting in a UAF | |
20 | reference in res->at->name. This then gets dereferenced in the | |
21 | getcanonname_r plugin call, resulting in the use after free. | |
22 | ||
23 | Fix this by copying h_name over and freeing it at the end. This | |
24 | resolves BZ #30843, which is assigned CVE-2023-4806. | |
25 | ||
26 | Signed-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 | ||
43 | diff --git a/nss/Makefile b/nss/Makefile | |
44 | index 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 | |
103 | diff --git a/nss/nss_test_gai_hv2_canonname.c b/nss/nss_test_gai_hv2_canonname.c | |
104 | new file mode 100644 | |
105 | index 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 | +} | |
165 | diff --git a/nss/tst-nss-gai-hv2-canonname.c b/nss/tst-nss-gai-hv2-canonname.c | |
166 | new file mode 100644 | |
167 | index 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> | |
234 | diff --git a/nss/tst-nss-gai-hv2-canonname.h b/nss/tst-nss-gai-hv2-canonname.h | |
235 | new file mode 100644 | |
236 | index 0000000000..14f2a9cb08 | |
237 | --- /dev/null | |
238 | +++ b/nss/tst-nss-gai-hv2-canonname.h | |
239 | @@ -0,0 +1 @@ | |
240 | +#define QUERYNAME "test.example.com" | |
241 | diff --git a/nss/tst-nss-gai-hv2-canonname.root/postclean.req b/nss/tst-nss-gai-hv2-canonname.root/postclean.req | |
242 | new file mode 100644 | |
243 | index 0000000000..e69de29bb2 | |
244 | diff --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 | |
245 | new file mode 100644 | |
246 | index 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 | |
252 | diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c | |
253 | index 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 | -- | |
337 | 2.39.2 | |
338 |