Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions tests/unit-tests/memory_tests/deterministic/cross_cage_fork_no_leak.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#undef _GNU_SOURCE
#define _GNU_SOURCE

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <unistd.h>

// Test: fork memory isolation
// Verifies that parent and child cages don't share writable memory

#define PARENT_SENTINEL 0xA1
#define CHILD_SENTINEL 0xB2

#define BUF_SIZE (256 * 1024)

static void wait_child(pid_t pid)
{
int status;
pid_t ret = waitpid(pid, &status, 0);

assert(ret >= 0);
assert(WIFEXITED(status));
assert(WEXITSTATUS(status) == 0);
}

static void assert_all(const unsigned char *buf, size_t size,
unsigned char value)
{
for (size_t i = 0; i < size; i++)
assert(buf[i] == value);
}

int main(void)
{
pid_t pid;

// Test 1: Heap memory
unsigned char *heap = malloc(BUF_SIZE);
assert(heap != NULL);

memset(heap, PARENT_SENTINEL, BUF_SIZE);

pid = fork();
assert(pid >= 0);

if (pid == 0) {
// Child sees initial contents
assert_all(heap, BUF_SIZE, PARENT_SENTINEL);

// Child writes should not affect parent memory
memset(heap, CHILD_SENTINEL, BUF_SIZE);
assert_all(heap, BUF_SIZE, CHILD_SENTINEL);

exit(0);
}

wait_child(pid);

// Parent should still see its original data
assert_all(heap, BUF_SIZE, PARENT_SENTINEL);

free(heap);


// Test 2: anonymous mmap
unsigned char *mapped = mmap(NULL, BUF_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
assert(mapped != MAP_FAILED);

memset(mapped, PARENT_SENTINEL, BUF_SIZE);

pid = fork();
assert(pid >= 0);

if (pid == 0) {
assert_all(mapped, BUF_SIZE, PARENT_SENTINEL);

memset(mapped, CHILD_SENTINEL, BUF_SIZE);
assert_all(mapped, BUF_SIZE, CHILD_SENTINEL);

exit(0);
}

wait_child(pid);

assert_all(mapped, BUF_SIZE, PARENT_SENTINEL);

assert(munmap(mapped, BUF_SIZE) == 0);


// Test 3: allocation after fork
unsigned char *before = malloc(BUF_SIZE);
assert(before != NULL);

memset(before, PARENT_SENTINEL, BUF_SIZE);

pid = fork();
assert(pid >= 0);

if (pid == 0) {
unsigned char *child_buf = malloc(BUF_SIZE);
assert(child_buf != NULL);

memset(child_buf, CHILD_SENTINEL, BUF_SIZE);
assert_all(child_buf, BUF_SIZE, CHILD_SENTINEL);

free(child_buf);
exit(0);
}

wait_child(pid);

assert_all(before, BUF_SIZE, PARENT_SENTINEL);

free(before);


// Test 4: same virtual address mapped separately
void *slot = mmap(NULL, BUF_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
assert(slot != MAP_FAILED);

assert(munmap(slot, BUF_SIZE) == 0);

pid = fork();
assert(pid >= 0);

if (pid == 0) {
unsigned char *child_map = mmap(slot, BUF_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS |
MAP_FIXED,
-1, 0);

assert(child_map == slot);

memset(child_map, CHILD_SENTINEL, BUF_SIZE);
assert_all(child_map, BUF_SIZE, CHILD_SENTINEL);

exit(0);
}

unsigned char *parent_map = mmap(slot, BUF_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS |
MAP_FIXED,
-1, 0);

assert(parent_map == slot);

memset(parent_map, PARENT_SENTINEL, BUF_SIZE);

wait_child(pid);

// Same address, different cages, no shared data
assert_all(parent_map, BUF_SIZE, PARENT_SENTINEL);

assert(munmap(parent_map, BUF_SIZE) == 0);


printf("cross_cage_fork_no_leak test: PASS\n");
return 0;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add new line

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#undef _GNU_SOURCE
#define _GNU_SOURCE

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>

// Test: syscall pointer validation
// Verifies syscalls reject buffers outside the current cage

#define REGION_SIZE (64 * 1024)

int main(void)
{
// Create an address that is no longer mapped
unsigned char *invalid = mmap(NULL, REGION_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);

assert(invalid != MAP_FAILED);
assert(munmap(invalid, REGION_SIZE) == 0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe using sth like:

unsigned char *invalid = mmap(NULL, REGION_SIZE, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);


int fds[2];
assert(pipe(fds) == 0);


// write() should fail because the buffer is unmapped
errno = 0;

ssize_t ret = write(fds[1], invalid, REGION_SIZE);

assert(ret == -1);
assert(errno == EFAULT);


// read() should also fail when writing into an invalid buffer
unsigned char value = 0x5A;

ret = write(fds[1], &value, 1);
assert(ret == 1);

errno = 0;

ret = read(fds[0], invalid, REGION_SIZE);

assert(ret == -1);
assert(errno == EFAULT);


close(fds[0]);
close(fds[1]);

printf("cross_cage_syscall_efault test: PASS\n");
return 0;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add new line

42 changes: 42 additions & 0 deletions tests/unit-tests/memory_tests/fail/cross_cage_wild_pointer.c

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems we can merge this test into invalid_access_direct.c

Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

// Test: wild pointer isolation
// Verifies accesses outside cage memory are trapped

#define BEYOND_CAGE (256u * 1024 * 1024)

int main(void)
{
/*
Allocate a valid pointer inside this cage
Move it outside the cage memory range and verify the access traps
*/
volatile unsigned char *ptr =
(volatile unsigned char *)malloc(64);

if (ptr == NULL)
return 1;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setup failure should return 0 to indicate this error is not intended



volatile unsigned char *wild =
(volatile unsigned char *)((uintptr_t)ptr + BEYOND_CAGE);


// This access should trap
*wild = 0x41;


/*
Reaching here means the invalid write wasn't blocked
The read is included to detect possible data leakage
*/
unsigned char value = *wild;

printf("LEAK: read 0x%02x outside cage memory\n", value);

free((void *)ptr);

return 0;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add new line