1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-14 05:34:58 +00:00
serenity/Userland/Libraries/LibJS/Heap/CellAllocator.h
Andreas Kling b6d4eea7ac LibJS: Never give back virtual memory once it belongs to a cell type
Instead of returning HeapBlock memory to the kernel (or a non-type
specific shared cache), we now keep a BlockAllocator per CellAllocator
and implement "deallocation" by basically informing the kernel that we
don't need the physical memory right now.

This is done with MADV_FREE or MADV_DONTNEED if available, but for other
platforms (including SerenityOS) we munmap and then re-mmap the memory
to achieve the same effect. It's definitely clunky, so I've added a
FIXME about implementing the madvise options on SerenityOS too.

The important outcome of this change is that GC types that use a
type-specific allocator become immune to use-after-free type confusion
attacks, since their virtual addresses will only ever be re-used for
the same exact type again and again.

Fixes #22274
2023-12-31 15:35:56 +01:00

73 lines
1.9 KiB
C++

/*
* Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/IntrusiveList.h>
#include <AK/NeverDestroyed.h>
#include <AK/NonnullOwnPtr.h>
#include <LibJS/Forward.h>
#include <LibJS/Heap/BlockAllocator.h>
#include <LibJS/Heap/HeapBlock.h>
#define JS_DECLARE_ALLOCATOR(ClassName) \
static JS::TypeIsolatingCellAllocator<ClassName> cell_allocator;
#define JS_DEFINE_ALLOCATOR(ClassName) \
JS::TypeIsolatingCellAllocator<ClassName> ClassName::cell_allocator;
namespace JS {
class CellAllocator {
public:
explicit CellAllocator(size_t cell_size);
~CellAllocator() = default;
size_t cell_size() const { return m_cell_size; }
Cell* allocate_cell(Heap&);
template<typename Callback>
IterationDecision for_each_block(Callback callback)
{
for (auto& block : m_full_blocks) {
if (callback(block) == IterationDecision::Break)
return IterationDecision::Break;
}
for (auto& block : m_usable_blocks) {
if (callback(block) == IterationDecision::Break)
return IterationDecision::Break;
}
return IterationDecision::Continue;
}
void block_did_become_empty(Badge<Heap>, HeapBlock&);
void block_did_become_usable(Badge<Heap>, HeapBlock&);
IntrusiveListNode<CellAllocator> m_list_node;
using List = IntrusiveList<&CellAllocator::m_list_node>;
BlockAllocator& block_allocator() { return m_block_allocator; }
private:
size_t const m_cell_size;
BlockAllocator m_block_allocator;
using BlockList = IntrusiveList<&HeapBlock::m_list_node>;
BlockList m_full_blocks;
BlockList m_usable_blocks;
};
template<typename T>
class TypeIsolatingCellAllocator {
public:
using CellType = T;
NeverDestroyed<CellAllocator> allocator { sizeof(T) };
};
}