Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <errno.h>
4 : #include <stdlib.h>
5 : #include <sys/mman.h>
6 :
7 : #include "alloc-util.h"
8 : #include "errno-util.h"
9 : #include "fd-util.h"
10 : #include "hashmap.h"
11 : #include "list.h"
12 : #include "log.h"
13 : #include "macro.h"
14 : #include "memory-util.h"
15 : #include "mmap-cache.h"
16 : #include "sigbus.h"
17 :
18 : typedef struct Window Window;
19 : typedef struct Context Context;
20 :
21 : struct Window {
22 : MMapCache *cache;
23 :
24 : bool invalidated:1;
25 : bool keep_always:1;
26 : bool in_unused:1;
27 :
28 : int prot;
29 : void *ptr;
30 : uint64_t offset;
31 : size_t size;
32 :
33 : MMapFileDescriptor *fd;
34 :
35 : LIST_FIELDS(Window, by_fd);
36 : LIST_FIELDS(Window, unused);
37 :
38 : LIST_HEAD(Context, contexts);
39 : };
40 :
41 : struct Context {
42 : MMapCache *cache;
43 : unsigned id;
44 : Window *window;
45 :
46 : LIST_FIELDS(Context, by_window);
47 : };
48 :
49 : struct MMapFileDescriptor {
50 : MMapCache *cache;
51 : int fd;
52 : bool sigbus;
53 : LIST_HEAD(Window, windows);
54 : };
55 :
56 : struct MMapCache {
57 : unsigned n_ref;
58 : unsigned n_windows;
59 :
60 : unsigned n_hit, n_missed;
61 :
62 : Hashmap *fds;
63 : Context *contexts[MMAP_CACHE_MAX_CONTEXTS];
64 :
65 : LIST_HEAD(Window, unused);
66 : Window *last_unused;
67 : };
68 :
69 : #define WINDOWS_MIN 64
70 :
71 : #if ENABLE_DEBUG_MMAP_CACHE
72 : /* Tiny windows increase mmap activity and the chance of exposing unsafe use. */
73 : # define WINDOW_SIZE (page_size())
74 : #else
75 : # define WINDOW_SIZE (8ULL*1024ULL*1024ULL)
76 : #endif
77 :
78 238 : MMapCache* mmap_cache_new(void) {
79 : MMapCache *m;
80 :
81 238 : m = new0(MMapCache, 1);
82 238 : if (!m)
83 0 : return NULL;
84 :
85 238 : m->n_ref = 1;
86 238 : return m;
87 : }
88 :
89 10359 : static void window_unlink(Window *w) {
90 : Context *c;
91 :
92 10359 : assert(w);
93 :
94 10359 : if (w->ptr)
95 10359 : munmap(w->ptr, w->size);
96 :
97 10359 : if (w->fd)
98 10359 : LIST_REMOVE(by_fd, w->fd->windows, w);
99 :
100 10359 : if (w->in_unused) {
101 485 : if (w->cache->last_unused == w)
102 483 : w->cache->last_unused = w->unused_prev;
103 :
104 485 : LIST_REMOVE(unused, w->cache->unused, w);
105 : }
106 :
107 10690 : LIST_FOREACH(by_window, c, w->contexts) {
108 331 : assert(c->window == w);
109 331 : c->window = NULL;
110 : }
111 10359 : }
112 :
113 0 : static void window_invalidate(Window *w) {
114 0 : assert(w);
115 :
116 0 : if (w->invalidated)
117 0 : return;
118 :
119 : /* Replace the window with anonymous pages. This is useful
120 : * when we hit a SIGBUS and want to make sure the file cannot
121 : * trigger any further SIGBUS, possibly overrunning the sigbus
122 : * queue. */
123 :
124 0 : assert_se(mmap(w->ptr, w->size, w->prot, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == w->ptr);
125 0 : w->invalidated = true;
126 : }
127 :
128 9881 : static void window_free(Window *w) {
129 9881 : assert(w);
130 :
131 9881 : window_unlink(w);
132 9881 : w->cache->n_windows--;
133 9881 : free(w);
134 9881 : }
135 :
136 3071711 : _pure_ static bool window_matches(Window *w, int prot, uint64_t offset, size_t size) {
137 3071711 : assert(w);
138 3071711 : assert(size > 0);
139 :
140 : return
141 6143422 : prot == w->prot &&
142 6077838 : offset >= w->offset &&
143 3006127 : offset + size <= w->offset + w->size;
144 : }
145 :
146 2983393 : _pure_ static bool window_matches_fd(Window *w, MMapFileDescriptor *f, int prot, uint64_t offset, size_t size) {
147 2983393 : assert(w);
148 2983393 : assert(f);
149 :
150 : return
151 5966786 : w->fd &&
152 5942527 : f->fd == w->fd->fd &&
153 2959134 : window_matches(w, prot, offset, size);
154 : }
155 :
156 10359 : static Window *window_add(MMapCache *m, MMapFileDescriptor *f, int prot, bool keep_always, uint64_t offset, size_t size, void *ptr) {
157 : Window *w;
158 :
159 10359 : assert(m);
160 10359 : assert(f);
161 :
162 10359 : if (!m->last_unused || m->n_windows <= WINDOWS_MIN) {
163 :
164 : /* Allocate a new window */
165 9881 : w = new0(Window, 1);
166 9881 : if (!w)
167 0 : return NULL;
168 9881 : m->n_windows++;
169 : } else {
170 :
171 : /* Reuse an existing one */
172 478 : w = m->last_unused;
173 478 : window_unlink(w);
174 478 : zero(*w);
175 : }
176 :
177 10359 : w->cache = m;
178 10359 : w->fd = f;
179 10359 : w->prot = prot;
180 10359 : w->keep_always = keep_always;
181 10359 : w->offset = offset;
182 10359 : w->size = size;
183 10359 : w->ptr = ptr;
184 :
185 10359 : LIST_PREPEND(by_fd, f->windows, w);
186 :
187 10359 : return w;
188 : }
189 :
190 176364 : static void context_detach_window(Context *c) {
191 : Window *w;
192 :
193 176364 : assert(c);
194 :
195 176364 : if (!c->window)
196 88513 : return;
197 :
198 87851 : w = TAKE_PTR(c->window);
199 87851 : LIST_REMOVE(by_window, w->contexts, c);
200 :
201 87851 : if (!w->contexts && !w->keep_always) {
202 : /* Not used anymore? */
203 : #if ENABLE_DEBUG_MMAP_CACHE
204 : /* Unmap unused windows immediately to expose use-after-unmap
205 : * by SIGSEGV. */
206 : window_free(w);
207 : #else
208 13300 : LIST_PREPEND(unused, c->cache->unused, w);
209 13300 : if (!c->cache->last_unused)
210 809 : c->cache->last_unused = w;
211 :
212 13300 : w->in_unused = true;
213 : #endif
214 : }
215 : }
216 :
217 88182 : static void context_attach_window(Context *c, Window *w) {
218 88182 : assert(c);
219 88182 : assert(w);
220 :
221 88182 : if (c->window == w)
222 0 : return;
223 :
224 88182 : context_detach_window(c);
225 :
226 88182 : if (w->in_unused) {
227 : /* Used again? */
228 12815 : LIST_REMOVE(unused, c->cache->unused, w);
229 12815 : if (c->cache->last_unused == w)
230 347 : c->cache->last_unused = w->unused_prev;
231 :
232 12815 : w->in_unused = false;
233 : }
234 :
235 88182 : c->window = w;
236 88182 : LIST_PREPEND(by_window, w->contexts, c);
237 : }
238 :
239 88182 : static Context *context_add(MMapCache *m, unsigned id) {
240 : Context *c;
241 :
242 88182 : assert(m);
243 :
244 88182 : c = m->contexts[id];
245 88182 : if (c)
246 87851 : return c;
247 :
248 331 : c = new0(Context, 1);
249 331 : if (!c)
250 0 : return NULL;
251 :
252 331 : c->cache = m;
253 331 : c->id = id;
254 :
255 331 : assert(!m->contexts[id]);
256 331 : m->contexts[id] = c;
257 :
258 331 : return c;
259 : }
260 :
261 331 : static void context_free(Context *c) {
262 331 : assert(c);
263 :
264 331 : context_detach_window(c);
265 :
266 331 : if (c->cache) {
267 331 : assert(c->cache->contexts[c->id] == c);
268 331 : c->cache->contexts[c->id] = NULL;
269 : }
270 :
271 331 : free(c);
272 331 : }
273 :
274 238 : static MMapCache *mmap_cache_free(MMapCache *m) {
275 : int i;
276 :
277 238 : assert(m);
278 :
279 2380 : for (i = 0; i < MMAP_CACHE_MAX_CONTEXTS; i++)
280 2142 : if (m->contexts[i])
281 331 : context_free(m->contexts[i]);
282 :
283 238 : hashmap_free(m->fds);
284 :
285 238 : while (m->unused)
286 0 : window_free(m->unused);
287 :
288 238 : return mfree(m);
289 : }
290 :
291 19874 : DEFINE_TRIVIAL_REF_UNREF_FUNC(MMapCache, mmap_cache, mmap_cache_free);
292 :
293 0 : static int make_room(MMapCache *m) {
294 0 : assert(m);
295 :
296 0 : if (!m->last_unused)
297 0 : return 0;
298 :
299 0 : window_free(m->last_unused);
300 0 : return 1;
301 : }
302 :
303 2983724 : static int try_context(
304 : MMapCache *m,
305 : MMapFileDescriptor *f,
306 : int prot,
307 : unsigned context,
308 : bool keep_always,
309 : uint64_t offset,
310 : size_t size,
311 : void **ret,
312 : size_t *ret_size) {
313 :
314 : Context *c;
315 :
316 2983724 : assert(m);
317 2983724 : assert(m->n_ref > 0);
318 2983724 : assert(f);
319 2983724 : assert(size > 0);
320 2983724 : assert(ret);
321 :
322 2983724 : c = m->contexts[context];
323 2983724 : if (!c)
324 331 : return 0;
325 :
326 2983393 : assert(c->id == context);
327 :
328 2983393 : if (!c->window)
329 0 : return 0;
330 :
331 2983393 : if (!window_matches_fd(c->window, f, prot, offset, size)) {
332 :
333 : /* Drop the reference to the window, since it's unnecessary now */
334 87851 : context_detach_window(c);
335 87851 : return 0;
336 : }
337 :
338 2895542 : if (c->window->fd->sigbus)
339 0 : return -EIO;
340 :
341 2895542 : c->window->keep_always = c->window->keep_always || keep_always;
342 :
343 2895542 : *ret = (uint8_t*) c->window->ptr + (offset - c->window->offset);
344 2895542 : if (ret_size)
345 2684603 : *ret_size = c->window->size - (offset - c->window->offset);
346 :
347 2895542 : return 1;
348 : }
349 :
350 88182 : static int find_mmap(
351 : MMapCache *m,
352 : MMapFileDescriptor *f,
353 : int prot,
354 : unsigned context,
355 : bool keep_always,
356 : uint64_t offset,
357 : size_t size,
358 : void **ret,
359 : size_t *ret_size) {
360 :
361 : Window *w;
362 : Context *c;
363 :
364 88182 : assert(m);
365 88182 : assert(m->n_ref > 0);
366 88182 : assert(f);
367 88182 : assert(size > 0);
368 :
369 88182 : if (f->sigbus)
370 0 : return -EIO;
371 :
372 122936 : LIST_FOREACH(by_fd, w, f->windows)
373 112577 : if (window_matches(w, prot, offset, size))
374 77823 : break;
375 :
376 88182 : if (!w)
377 10359 : return 0;
378 :
379 77823 : c = context_add(m, context);
380 77823 : if (!c)
381 0 : return -ENOMEM;
382 :
383 77823 : context_attach_window(c, w);
384 77823 : w->keep_always = w->keep_always || keep_always;
385 :
386 77823 : *ret = (uint8_t*) w->ptr + (offset - w->offset);
387 77823 : if (ret_size)
388 62462 : *ret_size = w->size - (offset - w->offset);
389 :
390 77823 : return 1;
391 : }
392 :
393 10359 : static int mmap_try_harder(MMapCache *m, void *addr, MMapFileDescriptor *f, int prot, int flags, uint64_t offset, size_t size, void **res) {
394 : void *ptr;
395 :
396 10359 : assert(m);
397 10359 : assert(f);
398 10359 : assert(res);
399 :
400 0 : for (;;) {
401 : int r;
402 :
403 10359 : ptr = mmap(addr, size, prot, flags, f->fd, offset);
404 10359 : if (ptr != MAP_FAILED)
405 10359 : break;
406 0 : if (errno != ENOMEM)
407 0 : return negative_errno();
408 :
409 0 : r = make_room(m);
410 0 : if (r < 0)
411 0 : return r;
412 0 : if (r == 0)
413 0 : return -ENOMEM;
414 : }
415 :
416 10359 : *res = ptr;
417 10359 : return 0;
418 : }
419 :
420 10359 : static int add_mmap(
421 : MMapCache *m,
422 : MMapFileDescriptor *f,
423 : int prot,
424 : unsigned context,
425 : bool keep_always,
426 : uint64_t offset,
427 : size_t size,
428 : struct stat *st,
429 : void **ret,
430 : size_t *ret_size) {
431 :
432 : uint64_t woffset, wsize;
433 : Context *c;
434 : Window *w;
435 : void *d;
436 : int r;
437 :
438 10359 : assert(m);
439 10359 : assert(m->n_ref > 0);
440 10359 : assert(f);
441 10359 : assert(size > 0);
442 10359 : assert(ret);
443 :
444 10359 : woffset = offset & ~((uint64_t) page_size() - 1ULL);
445 10359 : wsize = size + (offset - woffset);
446 10359 : wsize = PAGE_ALIGN(wsize);
447 :
448 10359 : if (wsize < WINDOW_SIZE) {
449 : uint64_t delta;
450 :
451 10359 : delta = PAGE_ALIGN((WINDOW_SIZE - wsize) / 2);
452 :
453 10359 : if (delta > offset)
454 9872 : woffset = 0;
455 : else
456 487 : woffset -= delta;
457 :
458 10359 : wsize = WINDOW_SIZE;
459 : }
460 :
461 10359 : if (st) {
462 : /* Memory maps that are larger then the files
463 : underneath have undefined behavior. Hence, clamp
464 : things to the file size if we know it */
465 :
466 10354 : if (woffset >= (uint64_t) st->st_size)
467 0 : return -EADDRNOTAVAIL;
468 :
469 10354 : if (woffset + wsize > (uint64_t) st->st_size)
470 51 : wsize = PAGE_ALIGN(st->st_size - woffset);
471 : }
472 :
473 10359 : r = mmap_try_harder(m, NULL, f, prot, MAP_SHARED, woffset, wsize, &d);
474 10359 : if (r < 0)
475 0 : return r;
476 :
477 10359 : c = context_add(m, context);
478 10359 : if (!c)
479 0 : goto outofmem;
480 :
481 10359 : w = window_add(m, f, prot, keep_always, woffset, wsize, d);
482 10359 : if (!w)
483 0 : goto outofmem;
484 :
485 10359 : context_attach_window(c, w);
486 :
487 10359 : *ret = (uint8_t*) w->ptr + (offset - w->offset);
488 10359 : if (ret_size)
489 484 : *ret_size = w->size - (offset - w->offset);
490 :
491 10359 : return 1;
492 :
493 0 : outofmem:
494 0 : (void) munmap(d, wsize);
495 0 : return -ENOMEM;
496 : }
497 :
498 2983724 : int mmap_cache_get(
499 : MMapCache *m,
500 : MMapFileDescriptor *f,
501 : int prot,
502 : unsigned context,
503 : bool keep_always,
504 : uint64_t offset,
505 : size_t size,
506 : struct stat *st,
507 : void **ret,
508 : size_t *ret_size) {
509 :
510 : int r;
511 :
512 2983724 : assert(m);
513 2983724 : assert(m->n_ref > 0);
514 2983724 : assert(f);
515 2983724 : assert(size > 0);
516 2983724 : assert(ret);
517 2983724 : assert(context < MMAP_CACHE_MAX_CONTEXTS);
518 :
519 : /* Check whether the current context is the right one already */
520 2983724 : r = try_context(m, f, prot, context, keep_always, offset, size, ret, ret_size);
521 2983724 : if (r != 0) {
522 2895542 : m->n_hit++;
523 2895542 : return r;
524 : }
525 :
526 : /* Search for a matching mmap */
527 88182 : r = find_mmap(m, f, prot, context, keep_always, offset, size, ret, ret_size);
528 88182 : if (r != 0) {
529 77823 : m->n_hit++;
530 77823 : return r;
531 : }
532 :
533 10359 : m->n_missed++;
534 :
535 : /* Create a new mmap */
536 10359 : return add_mmap(m, f, prot, context, keep_always, offset, size, st, ret, ret_size);
537 : }
538 :
539 211 : unsigned mmap_cache_get_hit(MMapCache *m) {
540 211 : assert(m);
541 :
542 211 : return m->n_hit;
543 : }
544 :
545 211 : unsigned mmap_cache_get_missed(MMapCache *m) {
546 211 : assert(m);
547 :
548 211 : return m->n_missed;
549 : }
550 :
551 138211 : static void mmap_cache_process_sigbus(MMapCache *m) {
552 138211 : bool found = false;
553 : MMapFileDescriptor *f;
554 : Iterator i;
555 : int r;
556 :
557 138211 : assert(m);
558 :
559 : /* Iterate through all triggered pages and mark their files as
560 : * invalidated */
561 0 : for (;;) {
562 : bool ours;
563 : void *addr;
564 :
565 138211 : r = sigbus_pop(&addr);
566 138211 : if (_likely_(r == 0))
567 138211 : break;
568 0 : if (r < 0) {
569 0 : log_error_errno(r, "SIGBUS handling failed: %m");
570 0 : abort();
571 : }
572 :
573 0 : ours = false;
574 0 : HASHMAP_FOREACH(f, m->fds, i) {
575 : Window *w;
576 :
577 0 : LIST_FOREACH(by_fd, w, f->windows) {
578 0 : if ((uint8_t*) addr >= (uint8_t*) w->ptr &&
579 0 : (uint8_t*) addr < (uint8_t*) w->ptr + w->size) {
580 0 : found = ours = f->sigbus = true;
581 0 : break;
582 : }
583 : }
584 :
585 0 : if (ours)
586 0 : break;
587 : }
588 :
589 : /* Didn't find a matching window, give up */
590 0 : if (!ours) {
591 0 : log_error("Unknown SIGBUS page, aborting.");
592 0 : abort();
593 : }
594 : }
595 :
596 : /* The list of triggered pages is now empty. Now, let's remap
597 : * all windows of the triggered file to anonymous maps, so
598 : * that no page of the file in question is triggered again, so
599 : * that we can be sure not to hit the queue size limit. */
600 138211 : if (_likely_(!found))
601 138211 : return;
602 :
603 0 : HASHMAP_FOREACH(f, m->fds, i) {
604 : Window *w;
605 :
606 0 : if (!f->sigbus)
607 0 : continue;
608 :
609 0 : LIST_FOREACH(by_fd, w, f->windows)
610 0 : window_invalidate(w);
611 : }
612 : }
613 :
614 128364 : bool mmap_cache_got_sigbus(MMapCache *m, MMapFileDescriptor *f) {
615 128364 : assert(m);
616 128364 : assert(f);
617 :
618 128364 : mmap_cache_process_sigbus(m);
619 :
620 128364 : return f->sigbus;
621 : }
622 :
623 9847 : MMapFileDescriptor* mmap_cache_add_fd(MMapCache *m, int fd) {
624 : MMapFileDescriptor *f;
625 : int r;
626 :
627 9847 : assert(m);
628 9847 : assert(fd >= 0);
629 :
630 9847 : f = hashmap_get(m->fds, FD_TO_PTR(fd));
631 9847 : if (f)
632 0 : return f;
633 :
634 9847 : r = hashmap_ensure_allocated(&m->fds, NULL);
635 9847 : if (r < 0)
636 0 : return NULL;
637 :
638 9847 : f = new0(MMapFileDescriptor, 1);
639 9847 : if (!f)
640 0 : return NULL;
641 :
642 9847 : f->cache = m;
643 9847 : f->fd = fd;
644 :
645 9847 : r = hashmap_put(m->fds, FD_TO_PTR(fd), f);
646 9847 : if (r < 0)
647 0 : return mfree(f);
648 :
649 9847 : return f;
650 : }
651 :
652 9847 : void mmap_cache_free_fd(MMapCache *m, MMapFileDescriptor *f) {
653 9847 : assert(m);
654 9847 : assert(f);
655 :
656 : /* Make sure that any queued SIGBUS are first dispatched, so
657 : * that we don't end up with a SIGBUS entry we cannot relate
658 : * to any existing memory map */
659 :
660 9847 : mmap_cache_process_sigbus(m);
661 :
662 19728 : while (f->windows)
663 9881 : window_free(f->windows);
664 :
665 9847 : if (f->cache)
666 9847 : assert_se(hashmap_remove(f->cache->fds, FD_TO_PTR(f->fd)));
667 :
668 9847 : free(f);
669 9847 : }
|