]> git.ipfire.org Git - thirdparty/glibc.git/blame - nscd/netgroupcache.c
CVE-2024-33600: nscd: Do not send missing not-found response in addgetnetgrentX ...
[thirdparty/glibc.git] / nscd / netgroupcache.c
CommitLineData
684ae515 1/* Cache handling for netgroup lookup.
dff8da6b 2 Copyright (C) 2011-2024 Free Software Foundation, Inc.
684ae515 3 This file is part of the GNU C Library.
684ae515
UD
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
5a82c748 16 along with this program; if not, see <https://www.gnu.org/licenses/>. */
684ae515
UD
17
18#include <alloca.h>
19#include <assert.h>
20#include <errno.h>
21#include <libintl.h>
22#include <stdbool.h>
c6dfed24 23#include <stdlib.h>
684ae515
UD
24#include <unistd.h>
25#include <sys/mman.h>
26
3f7c7ac8 27#include "../nss/netgroup.h"
684ae515
UD
28#include "nscd.h"
29#include "dbg_log.h"
37233df9
TS
30
31#include <kernel-features.h>
684ae515
UD
32
33
34/* This is the standard reply in case the service is disabled. */
35static const netgroup_response_header disabled =
36{
37 .version = NSCD_VERSION,
38 .found = -1,
39 .nresults = 0,
40 .result_len = 0
41};
42
43/* This is the struct describing how to write this record. */
44const struct iovec netgroup_iov_disabled =
45{
46 .iov_base = (void *) &disabled,
47 .iov_len = sizeof (disabled)
48};
49
50
51/* This is the standard reply in case we haven't found the dataset. */
52static const netgroup_response_header notfound =
53{
54 .version = NSCD_VERSION,
55 .found = 0,
56 .nresults = 0,
57 .result_len = 0
58};
59
60
61struct dataset
62{
63 struct datahead head;
64 netgroup_response_header resp;
65 char strdata[0];
66};
67
9a3c6a6f
SP
68/* Sends a notfound message and prepares a notfound dataset to write to the
69 cache. Returns true if there was enough memory to allocate the dataset and
70 returns the dataset in DATASETP, total bytes to write in TOTALP and the
71 timeout in TIMEOUTP. KEY_COPY is set to point to the copy of the key in the
72 dataset. */
73static bool
74do_notfound (struct database_dyn *db, int fd, request_header *req,
75 const char *key, struct dataset **datasetp, ssize_t *totalp,
76 time_t *timeoutp, char **key_copy)
77{
78 struct dataset *dataset;
79 ssize_t total;
80 time_t timeout;
81 bool cacheable = false;
82
83 total = sizeof (notfound);
84 timeout = time (NULL) + db->negtimeout;
85
86 if (fd != -1)
87 TEMP_FAILURE_RETRY (send (fd, &notfound, total, MSG_NOSIGNAL));
88
89 dataset = mempool_alloc (db, sizeof (struct dataset) + req->key_len, 1);
90 /* If we cannot permanently store the result, so be it. */
91 if (dataset != NULL)
92 {
1cdeb237
SP
93 timeout = datahead_init_neg (&dataset->head,
94 sizeof (struct dataset) + req->key_len,
95 total, db->negtimeout);
9a3c6a6f
SP
96
97 /* This is the reply. */
98 memcpy (&dataset->resp, &notfound, total);
99
100 /* Copy the key data. */
101 memcpy (dataset->strdata, key, req->key_len);
102 *key_copy = dataset->strdata;
103
104 cacheable = true;
105 }
106 *timeoutp = timeout;
107 *totalp = total;
108 *datasetp = dataset;
109 return cacheable;
110}
684ae515
UD
111
112static time_t
113addgetnetgrentX (struct database_dyn *db, int fd, request_header *req,
114 const char *key, uid_t uid, struct hashentry *he,
745664bd
FW
115 struct datahead *dh, struct dataset **resultp,
116 void **tofreep)
684ae515 117{
a1ffb40e 118 if (__glibc_unlikely (debug_level > 0))
684ae515
UD
119 {
120 if (he == NULL)
121 dbg_log (_("Haven't found \"%s\" in netgroup cache!"), key);
122 else
123 dbg_log (_("Reloading \"%s\" in netgroup cache!"), key);
124 }
125
f4f3b091 126 static nss_action_list netgroup_database;
684ae515
UD
127 time_t timeout;
128 struct dataset *dataset;
129 bool cacheable = false;
130 ssize_t total;
9a3c6a6f 131 bool found = false;
684ae515
UD
132
133 char *key_copy = NULL;
134 struct __netgrent data;
135 size_t buflen = MAX (1024, sizeof (*dataset) + req->key_len);
136 size_t buffilled = sizeof (*dataset);
137 char *buffer = NULL;
138 size_t nentries = 0;
684ae515 139 size_t group_len = strlen (key) + 1;
c6dfed24
RM
140 struct name_list *first_needed
141 = alloca (sizeof (struct name_list) + group_len);
745664bd 142 *tofreep = NULL;
684ae515
UD
143
144 if (netgroup_database == NULL
9b456c5d 145 && !__nss_database_get (nss_database_netgroup, &netgroup_database))
684ae515
UD
146 {
147 /* No such service. */
9a3c6a6f
SP
148 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
149 &key_copy);
7835b00d 150 goto maybe_cache_add;
684ae515
UD
151 }
152
153 memset (&data, '\0', sizeof (data));
980cb518 154 buffer = xmalloc (buflen);
745664bd 155 *tofreep = buffer;
c6dfed24
RM
156 first_needed->next = first_needed;
157 memcpy (first_needed->name, key, group_len);
158 data.needed_groups = first_needed;
684ae515
UD
159
160 while (data.needed_groups != NULL)
161 {
162 /* Add the next group to the list of those which are known. */
163 struct name_list *this_group = data.needed_groups->next;
164 if (this_group == data.needed_groups)
165 data.needed_groups = NULL;
166 else
167 data.needed_groups->next = this_group->next;
168 this_group->next = data.known_groups;
169 data.known_groups = this_group;
170
171 union
172 {
173 enum nss_status (*f) (const char *, struct __netgrent *);
174 void *ptr;
175 } setfct;
176
f4f3b091 177 nss_action_list nip = netgroup_database;
684ae515
UD
178 int no_more = __nss_lookup (&nip, "setnetgrent", NULL, &setfct.ptr);
179 while (!no_more)
180 {
181 enum nss_status status
182 = DL_CALL_FCT (*setfct.f, (data.known_groups->name, &data));
183
184 if (status == NSS_STATUS_SUCCESS)
185 {
9a3c6a6f 186 found = true;
684ae515
UD
187 union
188 {
189 enum nss_status (*f) (struct __netgrent *, char *, size_t,
190 int *);
191 void *ptr;
192 } getfct;
193 getfct.ptr = __nss_lookup_function (nip, "getnetgrent_r");
194 if (getfct.f != NULL)
195 while (1)
196 {
197 int e;
198 status = getfct.f (&data, buffer + buffilled,
c44496df 199 buflen - buffilled - req->key_len, &e);
684ae515
UD
200 if (status == NSS_STATUS_SUCCESS)
201 {
202 if (data.type == triple_val)
203 {
204 const char *nhost = data.val.triple.host;
205 const char *nuser = data.val.triple.user;
206 const char *ndomain = data.val.triple.domain;
207
ea7d8b95
SP
208 size_t hostlen = strlen (nhost ?: "") + 1;
209 size_t userlen = strlen (nuser ?: "") + 1;
210 size_t domainlen = strlen (ndomain ?: "") + 1;
211
50fd745b
AS
212 if (nhost == NULL || nuser == NULL || ndomain == NULL
213 || nhost > nuser || nuser > ndomain)
684ae515 214 {
50fd745b
AS
215 const char *last = nhost;
216 if (last == NULL
217 || (nuser != NULL && nuser > last))
218 last = nuser;
219 if (last == NULL
220 || (ndomain != NULL && ndomain > last))
221 last = ndomain;
222
223 size_t bufused
224 = (last == NULL
225 ? buffilled
226 : last + strlen (last) + 1 - buffer);
684ae515
UD
227
228 /* We have to make temporary copies. */
684ae515
UD
229 size_t needed = hostlen + userlen + domainlen;
230
231 if (buflen - req->key_len - bufused < needed)
232 {
980cb518 233 buflen += MAX (buflen, 2 * needed);
af37a8a3
SP
234 /* Save offset in the old buffer. We don't
235 bother with the NULL check here since
236 we'll do that later anyway. */
237 size_t nhostdiff = nhost - buffer;
238 size_t nuserdiff = nuser - buffer;
239 size_t ndomaindiff = ndomain - buffer;
240
5d41dadf 241 char *newbuf = xrealloc (buffer, buflen);
af37a8a3 242 /* Fix up the triplet pointers into the new
5d41dadf 243 buffer. */
af37a8a3 244 nhost = (nhost ? newbuf + nhostdiff
5d41dadf 245 : NULL);
af37a8a3 246 nuser = (nuser ? newbuf + nuserdiff
5d41dadf 247 : NULL);
af37a8a3 248 ndomain = (ndomain ? newbuf + ndomaindiff
5d41dadf 249 : NULL);
dca56588 250 *tofreep = buffer = newbuf;
684ae515
UD
251 }
252
253 nhost = memcpy (buffer + bufused,
50fd745b 254 nhost ?: "", hostlen);
684ae515 255 nuser = memcpy ((char *) nhost + hostlen,
50fd745b 256 nuser ?: "", userlen);
684ae515 257 ndomain = memcpy ((char *) nuser + userlen,
50fd745b 258 ndomain ?: "", domainlen);
684ae515
UD
259 }
260
261 char *wp = buffer + buffilled;
ea7d8b95
SP
262 wp = memmove (wp, nhost ?: "", hostlen);
263 wp += hostlen;
264 wp = memmove (wp, nuser ?: "", userlen);
265 wp += userlen;
266 wp = memmove (wp, ndomain ?: "", domainlen);
267 wp += domainlen;
684ae515
UD
268 buffilled = wp - buffer;
269 ++nentries;
270 }
271 else
272 {
273 /* Check that the group has not been
274 requested before. */
275 struct name_list *runp = data.needed_groups;
276 if (runp != NULL)
277 while (1)
278 {
279 if (strcmp (runp->name, data.val.group) == 0)
280 break;
281
282 runp = runp->next;
283 if (runp == data.needed_groups)
284 {
285 runp = NULL;
286 break;
287 }
288 }
289
290 if (runp == NULL)
291 {
292 runp = data.known_groups;
293 while (runp != NULL)
294 if (strcmp (runp->name, data.val.group) == 0)
295 break;
296 else
297 runp = runp->next;
298 }
299
300 if (runp == NULL)
301 {
302 /* A new group is requested. */
303 size_t namelen = strlen (data.val.group) + 1;
304 struct name_list *newg = alloca (sizeof (*newg)
305 + namelen);
306 memcpy (newg->name, data.val.group, namelen);
307 if (data.needed_groups == NULL)
308 data.needed_groups = newg->next = newg;
309 else
310 {
311 newg->next = data.needed_groups->next;
312 data.needed_groups->next = newg;
313 data.needed_groups = newg;
314 }
315 }
316 }
317 }
c3ec475c 318 else if (status == NSS_STATUS_TRYAGAIN && e == ERANGE)
684ae515 319 {
980cb518 320 buflen *= 2;
dca56588 321 *tofreep = buffer = xrealloc (buffer, buflen);
684ae515 322 }
c3ec475c
SP
323 else if (status == NSS_STATUS_RETURN
324 || status == NSS_STATUS_NOTFOUND
325 || status == NSS_STATUS_UNAVAIL)
326 /* This was either the last one for this group or the
327 group was empty or the NSS module had an internal
328 failure. Look at next group if available. */
329 break;
684ae515
UD
330 }
331
332 enum nss_status (*endfct) (struct __netgrent *);
333 endfct = __nss_lookup_function (nip, "endnetgrent");
334 if (endfct != NULL)
335 (void) DL_CALL_FCT (*endfct, (&data));
336
337 break;
338 }
339
340 no_more = __nss_next2 (&nip, "setnetgrent", NULL, &setfct.ptr,
341 status, 0);
342 }
343 }
344
9a3c6a6f
SP
345 /* No results. Return a failure and write out a notfound record in the
346 cache. */
347 if (!found)
348 {
349 cacheable = do_notfound (db, fd, req, key, &dataset, &total, &timeout,
350 &key_copy);
7835b00d 351 goto maybe_cache_add;
9a3c6a6f
SP
352 }
353
684ae515
UD
354 total = buffilled;
355
356 /* Fill in the dataset. */
357 dataset = (struct dataset *) buffer;
1cdeb237
SP
358 timeout = datahead_init_pos (&dataset->head, total + req->key_len,
359 total - offsetof (struct dataset, resp),
360 he == NULL ? 0 : dh->nreloads + 1,
361 db->postimeout);
684ae515
UD
362
363 dataset->resp.version = NSCD_VERSION;
364 dataset->resp.found = 1;
365 dataset->resp.nresults = nentries;
366 dataset->resp.result_len = buffilled - sizeof (*dataset);
367
368 assert (buflen - buffilled >= req->key_len);
369 key_copy = memcpy (buffer + buffilled, key, req->key_len);
370 buffilled += req->key_len;
371
372 /* Now we can determine whether on refill we have to create a new
373 record or not. */
374 if (he != NULL)
375 {
376 assert (fd == -1);
377
378 if (dataset->head.allocsize == dh->allocsize
379 && dataset->head.recsize == dh->recsize
380 && memcmp (&dataset->resp, dh->data,
381 dh->allocsize - offsetof (struct dataset, resp)) == 0)
382 {
383 /* The data has not changed. We will just bump the timeout
384 value. Note that the new record has been allocated on
385 the stack and need not be freed. */
386 dh->timeout = dataset->head.timeout;
387 dh->ttl = dataset->head.ttl;
388 ++dh->nreloads;
389 dataset = (struct dataset *) dh;
390
391 goto out;
392 }
393 }
394
395 {
396 struct dataset *newp
397 = (struct dataset *) mempool_alloc (db, total + req->key_len, 1);
a1ffb40e 398 if (__glibc_likely (newp != NULL))
684ae515
UD
399 {
400 /* Adjust pointer into the memory block. */
401 key_copy = (char *) newp + (key_copy - buffer);
402
403 dataset = memcpy (newp, dataset, total + req->key_len);
404 cacheable = true;
405
406 if (he != NULL)
407 /* Mark the old record as obsolete. */
408 dh->usable = false;
409 }
410 }
411
412 if (he == NULL && fd != -1)
7835b00d
FW
413 /* We write the dataset before inserting it to the database since
414 while inserting this thread might block and so would
415 unnecessarily let the receiver wait. */
8c78faa9 416 writeall (fd, &dataset->resp, dataset->head.recsize);
684ae515 417
7835b00d 418 maybe_cache_add:
684ae515
UD
419 if (cacheable)
420 {
421 /* If necessary, we also propagate the data to disk. */
422 if (db->persistent)
423 {
424 // XXX async OK?
425 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
426 msync ((void *) pval,
427 ((uintptr_t) dataset & pagesize_m1) + total + req->key_len,
428 MS_ASYNC);
429 }
430
431 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
432 true, db, uid, he == NULL);
433
434 pthread_rwlock_unlock (&db->lock);
435
436 /* Mark the old entry as obsolete. */
437 if (dh != NULL)
438 dh->usable = false;
439 }
440
441 out:
684ae515
UD
442 *resultp = dataset;
443
444 return timeout;
445}
446
447
448static time_t
449addinnetgrX (struct database_dyn *db, int fd, request_header *req,
450 char *key, uid_t uid, struct hashentry *he,
451 struct datahead *dh)
452{
453 const char *group = key;
32c7acd4 454 key = strchr (key, '\0') + 1;
1c81d55f 455 size_t group_len = key - group;
684ae515
UD
456 const char *host = *key++ ? key : NULL;
457 if (host != NULL)
32c7acd4 458 key = strchr (key, '\0') + 1;
684ae515
UD
459 const char *user = *key++ ? key : NULL;
460 if (user != NULL)
32c7acd4 461 key = strchr (key, '\0') + 1;
684ae515
UD
462 const char *domain = *key++ ? key : NULL;
463
a1ffb40e 464 if (__glibc_unlikely (debug_level > 0))
684ae515
UD
465 {
466 if (he == NULL)
467 dbg_log (_("Haven't found \"%s (%s,%s,%s)\" in netgroup cache!"),
468 group, host ?: "", user ?: "", domain ?: "");
469 else
470 dbg_log (_("Reloading \"%s (%s,%s,%s)\" in netgroup cache!"),
471 group, host ?: "", user ?: "", domain ?: "");
472 }
473
474 struct dataset *result = (struct dataset *) cache_search (GETNETGRENT,
475 group, group_len,
476 db, uid);
477 time_t timeout;
745664bd 478 void *tofree;
684ae515 479 if (result != NULL)
745664bd
FW
480 {
481 timeout = result->head.timeout;
482 tofree = NULL;
483 }
684ae515
UD
484 else
485 {
486 request_header req_get =
487 {
488 .type = GETNETGRENT,
489 .key_len = group_len
490 };
491 timeout = addgetnetgrentX (db, -1, &req_get, group, uid, NULL, NULL,
745664bd 492 &result, &tofree);
684ae515
UD
493 }
494
495 struct indataset
496 {
497 struct datahead head;
498 innetgroup_response_header resp;
499 } *dataset
500 = (struct indataset *) mempool_alloc (db,
501 sizeof (*dataset) + req->key_len,
502 1);
684ae515 503 bool cacheable = true;
a1ffb40e 504 if (__glibc_unlikely (dataset == NULL))
684ae515
UD
505 {
506 cacheable = false;
87801a8f
FW
507 /* The alloca is safe because nscd_run_worker verfies that
508 key_len is not larger than MAXKEYLEN. */
509 dataset = alloca (sizeof (*dataset) + req->key_len);
684ae515
UD
510 }
511
1cdeb237
SP
512 datahead_init_pos (&dataset->head, sizeof (*dataset) + req->key_len,
513 sizeof (innetgroup_response_header),
514 he == NULL ? 0 : dh->nreloads + 1, result->head.ttl);
515 /* Set the notfound status and timeout based on the result from
516 getnetgrent. */
684ae515 517 dataset->head.notfound = result->head.notfound;
684ae515
UD
518 dataset->head.timeout = timeout;
519
520 dataset->resp.version = NSCD_VERSION;
521 dataset->resp.found = result->resp.found;
522 /* Until we find a matching entry the result is 0. */
523 dataset->resp.result = 0;
524
525 char *key_copy = memcpy ((char *) (dataset + 1), group, req->key_len);
526
527 if (dataset->resp.found)
528 {
529 const char *triplets = (const char *) (&result->resp + 1);
530
531 for (nscd_ssize_t i = result->resp.nresults; i > 0; --i)
532 {
533 bool success = true;
534
fbd6b5a4
SP
535 /* For the host, user and domain in each triplet, we assume success
536 if the value is blank because that is how the wildcard entry to
537 match anything is stored in the netgroup cache. */
538 if (host != NULL && *triplets != '\0')
684ae515 539 success = strcmp (host, triplets) == 0;
32c7acd4 540 triplets = strchr (triplets, '\0') + 1;
684ae515 541
fbd6b5a4 542 if (success && user != NULL && *triplets != '\0')
684ae515 543 success = strcmp (user, triplets) == 0;
32c7acd4 544 triplets = strchr (triplets, '\0') + 1;
684ae515 545
fbd6b5a4
SP
546 if (success && (domain == NULL || *triplets == '\0'
547 || strcmp (domain, triplets) == 0))
684ae515
UD
548 {
549 dataset->resp.result = 1;
550 break;
551 }
32c7acd4 552 triplets = strchr (triplets, '\0') + 1;
684ae515
UD
553 }
554 }
555
556 if (he != NULL && dh->data[0].innetgroupdata.result == dataset->resp.result)
557 {
558 /* The data has not changed. We will just bump the timeout
559 value. Note that the new record has been allocated on
560 the stack and need not be freed. */
561 dh->timeout = timeout;
562 dh->ttl = dataset->head.ttl;
563 ++dh->nreloads;
3de93d19
DD
564 if (cacheable)
565 pthread_rwlock_unlock (&db->lock);
745664bd 566 goto out;
684ae515
UD
567 }
568
569 if (he == NULL)
570 {
571 /* We write the dataset before inserting it to the database
572 since while inserting this thread might block and so would
573 unnecessarily let the receiver wait. */
8c78faa9 574 assert (fd != -1);
684ae515 575
8c78faa9 576 writeall (fd, &dataset->resp, sizeof (innetgroup_response_header));
684ae515
UD
577 }
578
579 if (cacheable)
580 {
581 /* If necessary, we also propagate the data to disk. */
582 if (db->persistent)
583 {
584 // XXX async OK?
585 uintptr_t pval = (uintptr_t) dataset & ~pagesize_m1;
586 msync ((void *) pval,
587 ((uintptr_t) dataset & pagesize_m1) + sizeof (*dataset)
588 + req->key_len,
589 MS_ASYNC);
590 }
591
592 (void) cache_add (req->type, key_copy, req->key_len, &dataset->head,
593 true, db, uid, he == NULL);
594
595 pthread_rwlock_unlock (&db->lock);
596
597 /* Mark the old entry as obsolete. */
598 if (dh != NULL)
599 dh->usable = false;
600 }
601
745664bd
FW
602 out:
603 free (tofree);
684ae515
UD
604 return timeout;
605}
606
607
745664bd
FW
608static time_t
609addgetnetgrentX_ignore (struct database_dyn *db, int fd, request_header *req,
610 const char *key, uid_t uid, struct hashentry *he,
611 struct datahead *dh)
612{
613 struct dataset *ignore;
614 void *tofree;
615 time_t timeout = addgetnetgrentX (db, fd, req, key, uid, he, dh,
616 &ignore, &tofree);
617 free (tofree);
618 return timeout;
619}
620
684ae515
UD
621void
622addgetnetgrent (struct database_dyn *db, int fd, request_header *req,
623 void *key, uid_t uid)
624{
745664bd 625 addgetnetgrentX_ignore (db, fd, req, key, uid, NULL, NULL);
684ae515
UD
626}
627
628
629time_t
630readdgetnetgrent (struct database_dyn *db, struct hashentry *he,
631 struct datahead *dh)
632{
633 request_header req =
634 {
635 .type = GETNETGRENT,
636 .key_len = he->len
637 };
745664bd
FW
638 return addgetnetgrentX_ignore
639 (db, -1, &req, db->data + he->key, he->owner, he, dh);
684ae515
UD
640}
641
642
643void
644addinnetgr (struct database_dyn *db, int fd, request_header *req,
645 void *key, uid_t uid)
646{
647 addinnetgrX (db, fd, req, key, uid, NULL, NULL);
648}
649
650
651time_t
652readdinnetgr (struct database_dyn *db, struct hashentry *he,
653 struct datahead *dh)
654{
655 request_header req =
656 {
657 .type = INNETGR,
658 .key_len = he->len
659 };
660
661 return addinnetgrX (db, -1, &req, db->data + he->key, he->owner, he, dh);
662}