Skip to content
Permalink
Browse files
[libpas] Refactor PGM to align with libpas allocation norms and split…
… pas_get_random

https://bugs.webkit.org/show_bug.cgi?id=240331

Reviewed by Yusuke Suzuki.

This patch touches a few major areas.

PGM did not properly align with how we returned allocation results with the rest of the code base.
We now use pas_allocation_result instead. This resulted in touching a lot of the PGM testing code.

Added numerous helper utilities to make it easier to check whether we should call into PGM.

Added config option for each heap whether PGM will be enabled or not.

Cleaned up documentation.

I split the pas_get_random into two functions (pas_get_fast_random and pas_get_secure_random).

* Source/bmalloc/libpas/Documentation.md:
* Source/bmalloc/libpas/ReadMe.md:
* Source/bmalloc/libpas/src/libpas/bmalloc_heap_config.h:
* Source/bmalloc/libpas/src/libpas/hotbit_heap_config.h:
* Source/bmalloc/libpas/src/libpas/iso_heap_config.h:
* Source/bmalloc/libpas/src/libpas/iso_test_heap_config.h:
* Source/bmalloc/libpas/src/libpas/minalign32_heap_config.h:
* Source/bmalloc/libpas/src/libpas/pagesize64k_heap_config.h:
* Source/bmalloc/libpas/src/libpas/pas_baseline_allocator_table.c:
(pas_baseline_allocator_table_get_random_index):
* Source/bmalloc/libpas/src/libpas/pas_dynamic_primitive_heap_map.c:
(pas_dynamic_primitive_heap_map_find_slow):
* Source/bmalloc/libpas/src/libpas/pas_heap_config.h:
* Source/bmalloc/libpas/src/libpas/pas_heap_config_utils.h:
* Source/bmalloc/libpas/src/libpas/pas_large_heap.c:
(pas_large_heap_try_allocate_pgm):
* Source/bmalloc/libpas/src/libpas/pas_large_heap.h:
* Source/bmalloc/libpas/src/libpas/pas_probabilistic_guard_malloc_allocator.c:
(pas_probabilistic_guard_malloc_allocate):
(pas_probabilistic_guard_malloc_deallocate):
(pas_probabilistic_guard_malloc_check_exists):
(pas_probabilistic_guard_malloc_get_free_virtual_memory):
(pas_probabilistic_guard_malloc_get_free_wasted_memory):
(pas_probabilistic_guard_malloc_debug_info):
(pas_probabilistic_guard_malloc_trigger): Deleted.
(pas_probabilistic_guard_malloc_can_use): Deleted.
(pas_probabilistic_guard_malloc_should_use): Deleted.
* Source/bmalloc/libpas/src/libpas/pas_probabilistic_guard_malloc_allocator.h:
* Source/bmalloc/libpas/src/libpas/pas_random.h:
(pas_get_fast_random):
(pas_get_secure_random):
(pas_get_random): Deleted.
* Source/bmalloc/libpas/src/libpas/pas_segregated_shared_page_directory.c:
(find_first_eligible_consider_view):
* Source/bmalloc/libpas/src/libpas/thingy_heap_config.h:
* Source/bmalloc/libpas/src/test/IsoHeapPartialAndBaselineTests.cpp:
* Source/bmalloc/libpas/src/test/PGMTests.cpp:
(std::testPGMSingleAlloc):
(std::testPGMMultipleAlloc):
(std::testPGMErrors):

Canonical link: https://commits.webkit.org/250997@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@294866 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
stwrt committed May 26, 2022
1 parent 994751c commit ff11be4caf3453823d29e8bead2d770a5852675f
Showing 21 changed files with 267 additions and 256 deletions.
@@ -1178,7 +1178,8 @@ The header file usually looks like this:
.medium_bitfit_min_align_shift = PAS_MIN_MEDIUM_ALIGN_SHIFT, \
.use_marge_bitfit = true, \
.marge_bitfit_min_align_shift = PAS_MIN_MARGE_ALIGN_SHIFT, \
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE)
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE, \
.pgm_enabled = false)
PAS_API extern pas_heap_config iso_heap_config;
@@ -1236,6 +1237,28 @@ and can be made to support the first requirement if we use page header tables fo
medium or marge. So, the JIT heap config focuses on just using bitfit and large and it forces bitfit to use
page header tables even for the small bitfit page config.
## Security Considerations
### Probabilistic Guard Malloc
Probabilistic Guard Malloc (PGM) is a new allocator designed to catch use after free attempts and out of bounds accesses.
It behaves similarly to AddressSanitizer (ASAN), but aims to have minimal runtime overhead.
The design of PGM is quite simple. Each time an allocation is performed an additional guard page is added above and below the newly
allocated page(s). An allocation may span multiple pages. When a deallocation is performed, the page(s) allocated will be protected
using mprotect to ensure that any use after frees will trigger a crash. Virtual memory addresses are never reused, so we will never run
into a case where object 1 is freed, object 2 is allocated over the same address space, and object 1 then accesses the memory address
space of now object 2.
PGM does add notable memory overhead. Each allocation, no matter the size, adds an additional 2 guard pages (8KB for X86_64 and 32KB
for ARM64). In addition, there may be free memory left over in the page(s) allocated for the user. This memory may not be used by any
other allocation.
We added limits on virtual memory and wasted memory to help limit the memory impact on the overall system. Virtual memory for this
allocator is limited to 1GB. Wasted memory, which is the unused memory in the page(s) allocated by the user, is limited to 1MB.
These overall limits should ensure that the memory impact on the system is minimal, while helping to tackle the problems of catching
use after frees and out of bounds accesses.
## The Fast Paths
All of the discussion in the previous sections is about the innards of libpas. But ultimately, clients want to
@@ -1,20 +1,20 @@
# libpas - Phil's Awesome System

Libpas is a configurable memory allocator toolkit designed to enable adoption
of isoheaps. Currently, libpas's main client is WebKit's bmalloc project, where
of isoheaps. Currently, libpas' main client is WebKit's bmalloc project, where
it's used as a replacement for all of bmalloc's functionality (bmalloc::api,
IsoHeap<>, and Gigacage). Libpas's jit_heap API is also used by WebKit's
IsoHeap<>, and Gigacage). Libpas' jit_heap API is also used by WebKit's
ExecutableAllocator.


# How To Build And Test

This section describes how to build libpas standalone. You'll be doing this a
lot when making changes to libpas. It's wise to run libpas's tests before
lot when making changes to libpas. It's wise to run libpas' tests before
trying out your change in any larger system (like WebKit) since libpas tests
are great at catching bugs. If libpas passes its own tests then basic browsing
will seem to work. In production, libpas gets built as part of some other
project (like bmalloc), which just pulls all of libpas's files into that
project (like bmalloc), which just pulls all of libpas' files into that
project's build system.

Build and test:
@@ -64,7 +64,7 @@ production use, libpas is meant to be built as part of some larger malloc
project; for example, when libpas sees the PAS_BMALLOC macro, it will provide
everything that WebKit's bmalloc library needs to create an allocator.

Libpas's toolkit of allocators has three building blocks:
Libpas' toolkit of allocators has three building blocks:

- The segregated heap. This implements something like simple segregated
storage. Roughly speaking, size classes hold onto collections of pages, and
@@ -99,7 +99,7 @@ trade-offs, and so on.

All of the heaps are able to participate in physical page sharing. This means
that anytime any system page of memory managed by the heap becomes totally
empty, it becomes eligible for being returned to the OS via decommit. Libpas's
empty, it becomes eligible for being returned to the OS via decommit. Libpas'
decommit strategy is particularly well tuned so as to compensate for the
inherent memory overheads of isoheaping. Libpas achieves much better memory
usage than bmalloc because it returns pages sooner than bmalloc would have, and
@@ -109,7 +109,7 @@ configured to return a page to the OS anytime it has been free for 300ms.
Libpas is a heavy user of fine-grained locking and intricate lock dancing. Some
data structures will be protected by any of a collection of different locks,
and lock acquisition involves getting the lock, checking if you got the right
lock, and possibly relooping. Libpas's algorithms are designed around:
lock, and possibly relooping. Libpas' algorithms are designed around:

- Reducing the likelihood that any long-running operation would want to hold a
lock that any frequently-running operation would ever need. For example,
@@ -74,7 +74,8 @@ PAS_API void bmalloc_heap_config_activate(void);
.medium_bitfit_min_align_shift = PAS_MIN_MEDIUM_ALIGN_SHIFT, \
.use_marge_bitfit = true, \
.marge_bitfit_min_align_shift = PAS_MIN_MARGE_ALIGN_SHIFT, \
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE)
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE, \
.pgm_enabled = false)

PAS_API extern pas_heap_config bmalloc_heap_config;

@@ -75,7 +75,8 @@ PAS_API void hotbit_heap_config_activate(void);
.medium_bitfit_min_align_shift = PAS_MIN_MEDIUM_ALIGN_SHIFT, \
.use_marge_bitfit = true, \
.marge_bitfit_min_align_shift = PAS_MIN_MARGE_ALIGN_SHIFT, \
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE)
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE, \
.pgm_enabled = false)

PAS_API extern pas_heap_config hotbit_heap_config;

@@ -73,7 +73,8 @@ PAS_BEGIN_EXTERN_C;
.medium_bitfit_min_align_shift = PAS_MIN_MEDIUM_ALIGN_SHIFT, \
.use_marge_bitfit = true, \
.marge_bitfit_min_align_shift = PAS_MIN_MARGE_ALIGN_SHIFT, \
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE)
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE, \
.pgm_enabled = false)

PAS_API extern pas_heap_config iso_heap_config;

@@ -73,7 +73,8 @@ PAS_BEGIN_EXTERN_C;
.medium_bitfit_min_align_shift = PAS_MIN_MEDIUM_ALIGN_SHIFT, \
.use_marge_bitfit = true, \
.marge_bitfit_min_align_shift = PAS_MIN_MARGE_ALIGN_SHIFT, \
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE)
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE, \
.pgm_enabled = false)

PAS_API extern pas_heap_config iso_test_heap_config;

@@ -77,7 +77,8 @@ PAS_API void minalign32_heap_config_activate(void);
.medium_bitfit_min_align_shift = PAS_MIN_MEDIUM_ALIGN_SHIFT, \
.use_marge_bitfit = true, \
.marge_bitfit_min_align_shift = PAS_MIN_MARGE_ALIGN_SHIFT, \
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE)
.marge_bitfit_page_size = PAS_MARGE_PAGE_DEFAULT_SIZE, \
.pgm_enabled = false)

PAS_API extern pas_heap_config minalign32_heap_config;

@@ -76,7 +76,8 @@ PAS_API void pagesize64k_heap_config_activate(void);
.medium_shared_segregated_logging_mode = pas_segregated_deallocation_size_aware_logging_mode, \
.use_medium_bitfit = true, \
.medium_bitfit_min_align_shift = PAS_MIN_MEDIUM_ALIGN_SHIFT, \
.use_marge_bitfit = false)
.use_marge_bitfit = false, \
.pgm_enabled = false)

PAS_API extern pas_heap_config pagesize64k_heap_config;

@@ -63,8 +63,7 @@ void pas_baseline_allocator_table_initialize_if_necessary(void)

unsigned pas_baseline_allocator_table_get_random_index(void)
{
return pas_get_random(pas_fast_random, PAS_MIN(PAS_NUM_BASELINE_ALLOCATORS,
pas_baseline_allocator_table_bound));
return pas_get_fast_random(PAS_MIN(PAS_NUM_BASELINE_ALLOCATORS, pas_baseline_allocator_table_bound));
}

bool pas_baseline_allocator_table_for_all(pas_allocator_scavenge_action action)
@@ -87,13 +87,13 @@ pas_dynamic_primitive_heap_map_find_slow(pas_dynamic_primitive_heap_map* map,
being dynamically changed. We try to allow that. */

result = heaps_for_size->heaps[
pas_get_random(pas_fast_random, heaps_for_size->num_heaps)];
pas_get_fast_random(heaps_for_size->num_heaps)];
} else {
if (map->num_heaps >= map->max_heaps) {
if (verbose)
pas_log("Returning existing heap globally.\n");

result = map->heaps[pas_get_random(pas_fast_random, map->num_heaps)];
result = map->heaps[pas_get_fast_random(map->num_heaps)];
} else {
pas_simple_type_with_key_data* key_data;

@@ -174,6 +174,9 @@ struct pas_heap_config {
bool aligned_allocator_talks_to_sharing_pool;
pas_deallocator deallocator;

/* Configure whether probabilistic guard malloc may be called or not during allocation. */
bool pgm_enabled;

/* Tells if it's OK to call mmap on memory managed by this heap. */
pas_mmap_capability mmap_capability;

@@ -92,6 +92,7 @@ typedef struct {
bool use_marge_bitfit;
uint8_t marge_bitfit_min_align_shift;
size_t marge_bitfit_page_size;
bool pgm_enabled;
} pas_basic_heap_config_arguments;

#define PAS_BASIC_HEAP_CONFIG_SEGREGATED_HEAP_FIELDS(name, ...) \
@@ -340,7 +341,8 @@ typedef struct {
.for_each_shared_page_directory_remote = \
pas_heap_config_utils_for_each_shared_page_directory_remote, \
.dump_shared_page_directory_arg = pas_shared_page_directory_by_size_dump_directory_arg, \
PAS_HEAP_CONFIG_SPECIALIZATIONS(name ## _heap_config) \
PAS_HEAP_CONFIG_SPECIALIZATIONS(name ## _heap_config), \
.pgm_enabled = false \
})

#define PAS_BASIC_HEAP_CONFIG_SEGREGATED_HEAP_DECLARATIONS(name, upcase_name) \
@@ -38,6 +38,7 @@
#include "pas_large_sharing_pool.h"
#include "pas_large_map.h"
#include "pas_page_malloc.h"
#include "pas_probabilistic_guard_malloc_allocator.h"
#include <stdio.h>

void pas_large_heap_construct(pas_large_heap* heap)
@@ -184,6 +185,24 @@ pas_large_heap_try_allocate(pas_large_heap* heap,
return result;
}

pas_allocation_result
pas_large_heap_try_allocate_pgm(pas_large_heap* heap,
size_t size,
size_t alignment,
pas_heap_config* heap_config,
pas_physical_memory_transaction* transaction)
{
pas_allocation_result result;
result = pas_probabilistic_guard_malloc_allocate(heap, size, heap_config, transaction);

/* PGM may not succeed for a variety of reasons. We will give it a last ditch effort to try to do a
regular allocation instead. */
if (!result.did_succeed)
result = pas_large_heap_try_allocate(heap, size, alignment, heap_config, transaction);

return result;
}

bool pas_large_heap_try_deallocate(uintptr_t begin,
pas_heap_config* heap_config)
{
@@ -62,6 +62,12 @@ pas_large_heap_try_allocate(pas_large_heap* heap,
pas_heap_config* config,
pas_physical_memory_transaction* transaction);

PAS_API pas_allocation_result
pas_large_heap_try_allocate_pgm(pas_large_heap* heap,
size_t size, size_t alignment,
pas_heap_config* config,
pas_physical_memory_transaction* transaction);

/* Returns true if an object was found and deallocated. */
PAS_API bool pas_large_heap_try_deallocate(uintptr_t base,
pas_heap_config* config);

0 comments on commit ff11be4

Please sign in to comment.