From fe701052ea456308171ad038a895ff5f54816583 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Sun, 5 Sep 2021 11:59:38 -0600 Subject: [PATCH] LibC: Expand region for global destructors when it reaches capacity In 553361d we started mprotecting the atexit handlers when they are not being modified or executed. As part of that commit, we unintentionally changed the max number of global destructors from 1024 to 256 (on x86, only 128 on x86_64). This patch expands the initial size of the global destructors page to 2 pages from 1, and allows the pool to be expanded at runtime by mapping a new set of pages and copying the AtExitEntries over. --- Userland/Libraries/LibC/cxxabi.cpp | 43 +++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/Userland/Libraries/LibC/cxxabi.cpp b/Userland/Libraries/LibC/cxxabi.cpp index c7af6660ba..44c0855ab6 100644 --- a/Userland/Libraries/LibC/cxxabi.cpp +++ b/Userland/Libraries/LibC/cxxabi.cpp @@ -1,15 +1,17 @@ /* - * Copyright (c) 2019-2020, Andrew Kaster + * Copyright (c) 2019-2021, Andrew Kaster * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include #include #include #include +#include #include #include #include @@ -23,7 +25,8 @@ struct AtExitEntry { bool has_been_called { false }; }; -static constexpr size_t max_atexit_entry_count = PAGE_SIZE / sizeof(AtExitEntry); +// We'll re-allocate the region if it ends up being too small at runtime +static size_t atexit_entry_region_size = 2 * PAGE_SIZE; static AtExitEntry* atexit_entries; static size_t atexit_entry_count = 0; @@ -31,7 +34,7 @@ static pthread_mutex_t atexit_mutex = __PTHREAD_MUTEX_INITIALIZER; static void lock_atexit_handlers() { - if (mprotect(atexit_entries, PAGE_SIZE, PROT_READ) < 0) { + if (mprotect(atexit_entries, atexit_entry_region_size, PROT_READ) < 0) { perror("lock_atexit_handlers"); _exit(1); } @@ -39,7 +42,7 @@ static void lock_atexit_handlers() static void unlock_atexit_handlers() { - if (mprotect(atexit_entries, PAGE_SIZE, PROT_READ | PROT_WRITE) < 0) { + if (mprotect(atexit_entries, atexit_entry_region_size, PROT_READ | PROT_WRITE) < 0) { perror("unlock_atexit_handlers"); _exit(1); } @@ -49,13 +52,9 @@ int __cxa_atexit(AtExitFunction exit_function, void* parameter, void* dso_handle { __pthread_mutex_lock(&atexit_mutex); - if (atexit_entry_count >= max_atexit_entry_count) { - __pthread_mutex_unlock(&atexit_mutex); - return -1; - } - + // allocate initial atexit region if (!atexit_entries) { - atexit_entries = (AtExitEntry*)mmap(nullptr, PAGE_SIZE, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + atexit_entries = (AtExitEntry*)mmap(nullptr, atexit_entry_region_size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (atexit_entries == MAP_FAILED) { __pthread_mutex_unlock(&atexit_mutex); perror("__cxa_atexit mmap"); @@ -63,6 +62,30 @@ int __cxa_atexit(AtExitFunction exit_function, void* parameter, void* dso_handle } } + // reallocate atexit region, increasing size by PAGE_SIZE + if ((atexit_entry_count) >= (atexit_entry_region_size / sizeof(AtExitEntry))) { + if (Checked::addition_would_overflow(atexit_entry_region_size, PAGE_SIZE)) { + __pthread_mutex_unlock(&atexit_mutex); + return -1; + } + dbgln_if(GLOBAL_DTORS_DEBUG, "__cxa_atexit: Growing exit handler region from {} to {}", atexit_entry_region_size, atexit_entry_region_size + PAGE_SIZE); + size_t new_atexit_region_size = atexit_entry_region_size + PAGE_SIZE; + + auto* new_atexit_entries = (AtExitEntry*)mmap(nullptr, new_atexit_region_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (new_atexit_entries == MAP_FAILED) { + __pthread_mutex_unlock(&atexit_mutex); + perror("__cxa_atexit mmap (new size)"); + return -1; + } + memcpy(new_atexit_entries, atexit_entries, atexit_entry_region_size); + if (munmap(atexit_entries, atexit_entry_region_size) < 0) { + perror("__cxa_atexit munmap old region"); + // leak the old region on failure + } + atexit_entries = new_atexit_entries; + atexit_entry_region_size = new_atexit_region_size; + } + unlock_atexit_handlers(); atexit_entries[atexit_entry_count++] = { exit_function, parameter, dso_handle, false }; lock_atexit_handlers();