pebble/tests/fakes/fake_kernel_malloc.h
2025-01-27 11:38:16 -08:00

192 lines
4.9 KiB
C

/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <inttypes.h>
#include <string.h>
#include "util/list.h"
#include "clar_asserts.h"
// Simple pass-through implementation of kernel_malloc/free that attempts to
// protect against buffer overruns in tested code by adding a canary value to
// the beginning and end of the allocated block, and verifying the value on
// freeing of the block. It won't catch *all* memory errors of course, like
// writing way outside of your bounds, or use-after-free, or neglecting to free.
// But it should catch some of the simpler cases.
static const uint32_t s_malloc_canary = 0x54761F34;
static uint64_t s_largest_free_block_bytes = ~0;
static uint64_t s_heap_mark;
static bool s_stats_enabled = false;
typedef struct {
ListNode node;
size_t size;
void *ptr;
} Allocation;
Allocation *s_head;
void* kernel_malloc(size_t bytes) {
if (bytes > s_largest_free_block_bytes) {
return NULL;
}
char* memory = malloc(bytes + 12);
memcpy(memory, &bytes, 4);
memcpy(memory + 4, &s_malloc_canary, 4);
memcpy(memory + bytes + 8, &s_malloc_canary, 4);
void* ptr = memory + 8;
if (s_stats_enabled) {
Allocation *a = (Allocation *) malloc(sizeof(Allocation));
*a = (const Allocation) {
.size = bytes,
.ptr = ptr,
};
s_head = (Allocation *) list_prepend((ListNode *) s_head, &a->node);
}
return ptr;
}
void* kernel_zalloc(size_t bytes) {
void *ptr = kernel_malloc(bytes);
if (ptr) {
memset(ptr, 0, bytes);
}
return ptr;
}
void* kernel_zalloc_check(size_t bytes) {
return kernel_zalloc(bytes);
}
void* kernel_malloc_check(size_t bytes) {
return kernel_malloc(bytes);
}
char* kernel_strdup(const char* s) {
char *r = kernel_malloc_check(strlen(s) + 1);
if (!r) {
return NULL;
}
strcpy(r, s);
return r;
}
char* kernel_strdup_check(const char* s) {
return kernel_strdup(s);
}
static bool prv_find_allocation_filter_cb(ListNode *found_node, void *data) {
Allocation *a = (Allocation *) found_node;
return (a->ptr == data);
}
// Split into its own function to make it easy to set a breakpoint on it when debugging
// using `./waf test --debug_test`
static void prv_double_free_assert(Allocation *a) {
cl_assert_(a != NULL, "Couldn't find allocation! Double free?");
}
void kernel_free(void* ptr) {
if (ptr == NULL) {
return;
}
if (s_stats_enabled) {
Allocation *a = (Allocation *) list_find((ListNode *) s_head,
prv_find_allocation_filter_cb, ptr);
prv_double_free_assert(a);
list_remove(&a->node, (ListNode **) &s_head, NULL);
free(a);
}
char* memory = (char*)ptr - 8;
uint32_t canary_start = -1;
uint32_t canary_end = -1;
uint32_t canary_length = -1;
memcpy(&canary_length, memory, 4);
memcpy(&canary_start, memory + 4, 4);
memcpy(&canary_end, memory + canary_length + 8, 4);
cl_assert(canary_start == s_malloc_canary);
cl_assert(canary_length != -1);
cl_assert(canary_end == s_malloc_canary);
free(memory);
}
//! Enables or disables the tracking of allocations
void fake_kernel_malloc_enable_stats(bool enable) {
s_stats_enabled = enable;
}
//! Returns the number of bytes allocated on the kernel heap.
//! @note Call fake_kernel_malloc_enable_stats(true) before using this.
uint64_t fake_kernel_malloc_get_total_bytes_allocated(void) {
uint64_t bytes_allocated = 0;
Allocation *a = s_head;
while (a) {
bytes_allocated += a->size;
a = (Allocation *) a->node.next;
}
return bytes_allocated;
}
//! Makes successive kernel_malloc() fail for sizes above the number of bytes specified.
void fake_kernel_malloc_set_largest_free_block(uint64_t bytes) {
s_largest_free_block_bytes = bytes;
}
//! Marks the current, total bytes allocated.
//! @see fake_kernel_malloc_mark_assert_equal
void fake_kernel_malloc_mark(void) {
s_heap_mark = fake_kernel_malloc_get_total_bytes_allocated();
}
//! Asserts that the total bytes allocated is the same as the last time fake_kernel_malloc_mark()
//! was called.
void fake_kernel_malloc_mark_assert_equal(void) {
cl_assert_equal_i(s_heap_mark, fake_kernel_malloc_get_total_bytes_allocated());
}
void fake_kernel_malloc_init(void) {
s_largest_free_block_bytes = ~0;
s_heap_mark = 0;
s_head = NULL;
}
void fake_kernel_malloc_deinit(void) {
Allocation *a = s_head;
while (a) {
Allocation *next = (Allocation *) a->node.next;
free(a);
a = next;
}
s_head = NULL;
}