kernel/slab: Use FIFO instead of LIFO queueing for the object cache.

This should make use-after-frees more likely to be caught by the
standard paranoia facilities (and make them less likely to be
exploitable as memory will take longer to be reused.)
This commit is contained in:
Augustin Cavalier
2025-09-03 15:41:58 -04:00
parent 0937305b84
commit bc50ada648
9 changed files with 147 additions and 96 deletions
+41
View File
@@ -422,6 +422,28 @@ private:
#endif // SLAB_MEMORY_MANAGER_TRACING #endif // SLAB_MEMORY_MANAGER_TRACING
// #pragma mark - utility methods
template<typename Type>
static inline Type*
_pop(Type*& head)
{
Type* oldHead = head;
head = head->next;
return oldHead;
}
template<typename Type>
static inline void
_push(Type*& head, Type* object)
{
object->next = head;
head = object;
}
// #pragma mark - MemoryManager // #pragma mark - MemoryManager
@@ -1441,6 +1463,25 @@ MemoryManager::_FreeArea(Area* area, bool areaRemoved, uint32 flags)
} }
/*static*/ inline void
MemoryManager::_PushFreeArea(Area* area)
{
_push(sFreeAreas, area);
sFreeAreaCount++;
}
/*static*/ inline MemoryManager::Area*
MemoryManager::_PopFreeArea()
{
if (sFreeAreaCount == 0)
return NULL;
sFreeAreaCount--;
return _pop(sFreeAreas);
}
/*static*/ status_t /*static*/ status_t
MemoryManager::_MapChunk(VMArea* vmArea, addr_t address, size_t size, MemoryManager::_MapChunk(VMArea* vmArea, addr_t address, size_t size,
size_t reserveAdditionalMemory, uint32 flags) size_t reserveAdditionalMemory, uint32 flags)
-19
View File
@@ -239,25 +239,6 @@ MemoryManager::MaintenanceNeeded()
} }
/*static*/ inline void
MemoryManager::_PushFreeArea(Area* area)
{
_push(sFreeAreas, area);
sFreeAreaCount++;
}
/*static*/ inline MemoryManager::Area*
MemoryManager::_PopFreeArea()
{
if (sFreeAreaCount == 0)
return NULL;
sFreeAreaCount--;
return _pop(sFreeAreas);
}
/*static*/ inline addr_t /*static*/ inline addr_t
MemoryManager::_AreaBaseAddressForAddress(addr_t address) MemoryManager::_AreaBaseAddressForAddress(addr_t address)
{ {
+8 -10
View File
@@ -51,8 +51,8 @@ ObjectCache::Init(const char* name, size_t objectSize, size_t alignment,
mutex_init(&lock, this->name); mutex_init(&lock, this->name);
if (objectSize < sizeof(object_link)) if (objectSize < sizeof(slab_queue_link))
objectSize = sizeof(object_link); objectSize = sizeof(slab_queue_link);
if (alignment < kMinObjectAlignment) if (alignment < kMinObjectAlignment)
alignment = kMinObjectAlignment; alignment = kMinObjectAlignment;
@@ -125,7 +125,7 @@ ObjectCache::InitSlab(slab* slab, void* pages, size_t byteCount, uint32 flags)
slab->pages = pages; slab->pages = pages;
slab->count = slab->size = byteCount / object_size; slab->count = slab->size = byteCount / object_size;
slab->free = NULL; slab->free.Init();
size_t spareBytes = byteCount - (slab->size * object_size); size_t spareBytes = byteCount - (slab->size * object_size);
@@ -142,7 +142,6 @@ ObjectCache::InitSlab(slab* slab, void* pages, size_t byteCount, uint32 flags)
CREATE_PARANOIA_CHECK_SET(slab, "slab"); CREATE_PARANOIA_CHECK_SET(slab, "slab");
for (size_t i = 0; i < slab->size; i++) { for (size_t i = 0; i < slab->size; i++) {
status_t status = B_OK; status_t status = B_OK;
if (constructor) if (constructor)
@@ -157,11 +156,10 @@ ObjectCache::InitSlab(slab* slab, void* pages, size_t byteCount, uint32 flags)
} }
DELETE_PARANOIA_CHECK_SET(slab); DELETE_PARANOIA_CHECK_SET(slab);
return NULL; return NULL;
} }
_push(slab->free, object_to_link(data, object_size)); slab->free.Push(object_to_link(data, object_size));
ADD_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, slab, ADD_PARANOIA_CHECK(PARANOIA_SUSPICIOUS, slab,
&object_to_link(data, object_size)->next, sizeof(void*)); &object_to_link(data, object_size)->next, sizeof(void*));
@@ -216,13 +214,13 @@ ObjectCache::ReturnObjectToSlab(slab* source, void* object, uint32 flags)
} }
#endif // KDEBUG #endif // KDEBUG
object_link* link = object_to_link(object, object_size); slab_queue_link* link = object_to_link(object, object_size);
TRACE_CACHE(this, "returning %p (%p) to %p, %lu used (%lu empty slabs).", TRACE_CACHE(this, "returning %p (%p) to %p, %lu used (%lu empty slabs).",
object, link, source, source->size - source->count, object, link, source, source->size - source->count,
empty_count); empty_count);
_push(source->free, link); source->free.Push(link);
source->count++; source->count++;
used_count--; used_count--;
@@ -267,8 +265,8 @@ ObjectCache::AssertObjectNotFreed(void* object)
return false; return false;
} }
object_link* link = object_to_link(object, object_size); slab_queue_link* link = object_to_link(object, object_size);
for (object_link* freeLink = source->free; freeLink != NULL; for (slab_queue_link* freeLink = source->free.head; freeLink != NULL;
freeLink = freeLink->next) { freeLink = freeLink->next) {
if (freeLink == link) { if (freeLink == link) {
panic("object_cache: double free of %p (slab %p, cache %p)", panic("object_cache: double free of %p (slab %p, cache %p)",
+9 -11
View File
@@ -10,10 +10,12 @@
#include <condition_variable.h> #include <condition_variable.h>
#include <lock.h> #include <lock.h>
#include <slab/ObjectDepot.h>
#include <slab/Slab.h> #include <slab/Slab.h>
#include <util/DoublyLinkedList.h> #include <util/DoublyLinkedList.h>
#include "ObjectDepot.h"
#include "slab_queue.h"
#include "kernel_debug_config.h" #include "kernel_debug_config.h"
#include "slab_debug.h" #include "slab_debug.h"
@@ -21,16 +23,12 @@
struct ResizeRequest; struct ResizeRequest;
struct object_link {
struct object_link* next;
};
struct slab : DoublyLinkedListLinkImpl<slab> { struct slab : DoublyLinkedListLinkImpl<slab> {
void* pages; void* pages;
size_t size; // total number of objects size_t size; // total number of objects
size_t count; // free objects size_t count; // free objects
size_t offset; size_t offset;
object_link* free; slab_queue free;
#if SLAB_OBJECT_CACHE_ALLOCATION_TRACKING #if SLAB_OBJECT_CACHE_ALLOCATION_TRACKING
AllocationTrackingInfo* tracking; AllocationTrackingInfo* tracking;
#endif #endif
@@ -131,17 +129,17 @@ public:
static inline void* static inline void*
link_to_object(object_link* link, size_t objectSize) link_to_object(slab_queue_link* link, size_t objectSize)
{ {
return ((uint8*)link) - (objectSize - sizeof(object_link)); return ((uint8*)link) - (objectSize - sizeof(slab_queue_link));
} }
static inline object_link* static inline slab_queue_link*
object_to_link(void* object, size_t objectSize) object_to_link(void* object, size_t objectSize)
{ {
return (object_link*)(((uint8*)object) return (slab_queue_link*)(((uint8*)object)
+ (objectSize - sizeof(object_link))); + (objectSize - sizeof(slab_queue_link)));
} }
+32 -31
View File
@@ -7,7 +7,7 @@
*/ */
#include <slab/ObjectDepot.h> #include "ObjectDepot.h"
#include <algorithm> #include <algorithm>
@@ -18,10 +18,10 @@
#include "slab_debug.h" #include "slab_debug.h"
#include "slab_private.h" #include "slab_private.h"
#include "slab_queue.h"
struct DepotMagazine { struct DepotMagazine : public slab_queue_link {
DepotMagazine* next;
uint16 current_round; uint16 current_round;
uint16 round_count; uint16 round_count;
void* rounds[0]; void* rounds[0];
@@ -138,14 +138,14 @@ exchange_with_full(object_depot* depot, DepotMagazine*& magazine)
SpinLocker _(depot->inner_lock); SpinLocker _(depot->inner_lock);
if (depot->full == NULL) if (depot->full.head == NULL)
return false; return false;
depot->full_count--; depot->full_count--;
depot->empty_count++; depot->empty_count++;
_push(depot->empty, magazine); depot->empty.Push(magazine);
magazine = _pop(depot->full); magazine = (DepotMagazine*)depot->full.Pop();
return true; return true;
} }
@@ -158,21 +158,21 @@ exchange_with_empty(object_depot* depot, DepotMagazine*& magazine,
SpinLocker _(depot->inner_lock); SpinLocker _(depot->inner_lock);
if (depot->empty == NULL) if (depot->empty.head == NULL)
return false; return false;
depot->empty_count--; depot->empty_count--;
if (magazine != NULL) { if (magazine != NULL) {
if (depot->full_count < depot->max_count) { if (depot->full_count < depot->max_count) {
_push(depot->full, magazine); depot->full.Push(magazine);
depot->full_count++; depot->full_count++;
freeMagazine = NULL; freeMagazine = NULL;
} else } else
freeMagazine = magazine; freeMagazine = magazine;
} }
magazine = _pop(depot->empty); magazine = (DepotMagazine*)depot->empty.Pop();
return true; return true;
} }
@@ -182,7 +182,7 @@ push_empty_magazine(object_depot* depot, DepotMagazine* magazine)
{ {
SpinLocker _(depot->inner_lock); SpinLocker _(depot->inner_lock);
_push(depot->empty, magazine); depot->empty.Push(magazine);
depot->empty_count++; depot->empty_count++;
} }
@@ -202,8 +202,8 @@ object_depot_init(object_depot* depot, size_t capacity, size_t maxCount,
uint32 flags, void* cookie, void (*return_object)(object_depot* depot, uint32 flags, void* cookie, void (*return_object)(object_depot* depot,
void* cookie, void* object, uint32 flags)) void* cookie, void* object, uint32 flags))
{ {
depot->full = NULL; depot->full.Init();
depot->empty = NULL; depot->empty.Init();
depot->full_count = depot->empty_count = 0; depot->full_count = depot->empty_count = 0;
depot->max_count = maxCount; depot->max_count = maxCount;
depot->magazine_capacity = capacity; depot->magazine_capacity = capacity;
@@ -336,43 +336,44 @@ object_depot_make_empty(object_depot* depot, uint32 flags)
// collect the store magazines // collect the store magazines
DepotMagazine* storeMagazines = NULL; slab_queue storeMagazines;
storeMagazines.Init();
int cpuCount = smp_get_num_cpus(); int cpuCount = smp_get_num_cpus();
for (int i = 0; i < cpuCount; i++) { for (int i = 0; i < cpuCount; i++) {
depot_cpu_store& store = depot->stores[i]; depot_cpu_store& store = depot->stores[i];
if (store.loaded) { if (store.loaded != NULL) {
_push(storeMagazines, store.loaded); storeMagazines.Push(store.loaded);
store.loaded = NULL; store.loaded = NULL;
} }
if (store.previous) { if (store.previous != NULL) {
_push(storeMagazines, store.previous); storeMagazines.Push(store.previous);
store.previous = NULL; store.previous = NULL;
} }
} }
// detach the depot's full and empty magazines // detach the depot's full and empty magazines
DepotMagazine* fullMagazines = depot->full; slab_queue fullMagazines = depot->full;
depot->full = NULL; depot->full.head = depot->full.tail = NULL;
DepotMagazine* emptyMagazines = depot->empty; slab_queue emptyMagazines = depot->empty;
depot->empty = NULL; depot->empty.head = depot->empty.tail = NULL;
writeLocker.Unlock(); writeLocker.Unlock();
// free all magazines // free all magazines
while (storeMagazines != NULL) while (storeMagazines.head != NULL)
empty_magazine(depot, _pop(storeMagazines), flags); empty_magazine(depot, (DepotMagazine*)storeMagazines.Pop(), flags);
while (fullMagazines != NULL) while (fullMagazines.head != NULL)
empty_magazine(depot, _pop(fullMagazines), flags); empty_magazine(depot, (DepotMagazine*)fullMagazines.Pop(), flags);
while (emptyMagazines) while (emptyMagazines.head != NULL)
free_magazine(_pop(emptyMagazines), flags); free_magazine((DepotMagazine*)emptyMagazines.Pop(), flags);
} }
@@ -398,8 +399,8 @@ object_depot_contains_object(object_depot* depot, void* object)
} }
} }
for (DepotMagazine* magazine = depot->full; magazine != NULL; for (DepotMagazine* magazine = (DepotMagazine*)depot->full.head; magazine != NULL;
magazine = magazine->next) { magazine = (DepotMagazine*)magazine->next) {
if (magazine->ContainsObject(object)) if (magazine->ContainsObject(object))
return true; return true;
} }
@@ -416,8 +417,8 @@ object_depot_contains_object(object_depot* depot, void* object)
void void
dump_object_depot(object_depot* depot) dump_object_depot(object_depot* depot)
{ {
kprintf(" full: %p, count %lu\n", depot->full, depot->full_count); kprintf(" full: %p, count %lu\n", depot->full.head, depot->full_count);
kprintf(" empty: %p, count %lu\n", depot->empty, depot->empty_count); kprintf(" empty: %p, count %lu\n", depot->empty.head, depot->empty_count);
kprintf(" max full: %lu\n", depot->max_count); kprintf(" max full: %lu\n", depot->max_count);
kprintf(" capacity: %lu\n", depot->magazine_capacity); kprintf(" capacity: %lu\n", depot->magazine_capacity);
kprintf(" stores:\n"); kprintf(" stores:\n");
@@ -10,14 +10,16 @@
#include <lock.h> #include <lock.h>
#include <KernelExport.h> #include <KernelExport.h>
#include "slab_queue.h"
struct DepotMagazine; struct DepotMagazine;
typedef struct object_depot { typedef struct object_depot {
rw_lock outer_lock; rw_lock outer_lock;
spinlock inner_lock; spinlock inner_lock;
DepotMagazine* full; slab_queue full;
DepotMagazine* empty; slab_queue empty;
size_t full_count; size_t full_count;
size_t empty_count; size_t empty_count;
size_t max_count; size_t max_count;
+4 -4
View File
@@ -20,7 +20,6 @@
#include <elf.h> #include <elf.h>
#include <kernel.h> #include <kernel.h>
#include <low_resource_manager.h> #include <low_resource_manager.h>
#include <slab/ObjectDepot.h>
#include <smp.h> #include <smp.h>
#include <tracing.h> #include <tracing.h>
#include <util/AutoLock.h> #include <util/AutoLock.h>
@@ -28,11 +27,12 @@
#include <vm/vm.h> #include <vm/vm.h>
#include <vm/VMAddressSpace.h> #include <vm/VMAddressSpace.h>
#include "SmallObjectCache.h"
#include "HashedObjectCache.h" #include "HashedObjectCache.h"
#include "ObjectDepot.h"
#include "MemoryManager.h" #include "MemoryManager.h"
#include "slab_debug.h" #include "slab_debug.h"
#include "slab_private.h" #include "slab_private.h"
#include "SmallObjectCache.h"
#if !USE_GUARDED_HEAP_FOR_OBJECT_CACHE #if !USE_GUARDED_HEAP_FOR_OBJECT_CACHE
@@ -251,7 +251,7 @@ static void
dump_slab(::slab* slab) dump_slab(::slab* slab)
{ {
kprintf(" %p %p %6" B_PRIuSIZE " %6" B_PRIuSIZE " %6" B_PRIuSIZE " %p\n", kprintf(" %p %p %6" B_PRIuSIZE " %6" B_PRIuSIZE " %6" B_PRIuSIZE " %p\n",
slab, slab->pages, slab->size, slab->count, slab->offset, slab->free); slab, slab->pages, slab->size, slab->count, slab->offset, slab->free.head);
} }
@@ -1273,7 +1273,7 @@ object_cache_alloc(object_cache* cache, uint32 flags)
ParanoiaChecker _2(source); ParanoiaChecker _2(source);
object_link* link = _pop(source->free); slab_queue_link* link = source->free.Pop();
source->count--; source->count--;
cache->used_count++; cache->used_count++;
-19
View File
@@ -24,25 +24,6 @@ void block_allocator_init_boot();
void block_allocator_init_rest(); void block_allocator_init_rest();
template<typename Type>
static inline Type*
_pop(Type*& head)
{
Type* oldHead = head;
head = head->next;
return oldHead;
}
template<typename Type>
static inline void
_push(Type*& head, Type* object)
{
object->next = head;
head = object;
}
static inline void* static inline void*
slab_internal_alloc(size_t size, uint32 flags) slab_internal_alloc(size_t size, uint32 flags)
{ {
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright 2025, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef SLAB_QUEUE_H
#define SLAB_QUEUE_H
#include <stddef.h>
struct slab_queue_link {
slab_queue_link* next;
};
struct slab_queue {
slab_queue_link* head;
slab_queue_link* tail;
void Init()
{
head = tail = NULL;
}
void Push(slab_queue_link* item)
{
item->next = NULL;
if (tail == NULL) {
head = tail = item;
return;
}
tail->next = item;
tail = item;
}
slab_queue_link* Pop()
{
slab_queue_link* item = head;
head = item->next;
if (head == NULL)
tail = NULL;
return item;
}
};
#endif // SLAB_QUEUE_H