Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
39 changes: 36 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# The VoidFrame monolithic kernel 💫
# The VoidFrame monolithic kernel 💫 v0.0.1-beta5.3

## Table of Contents

Expand Down Expand Up @@ -314,7 +314,40 @@ Astra runs in a continuous loop, performing a series of checks:

Before the scheduler context-switches to a process, it performs a final, lightweight security check. This check validates the process's token and verifies its privilege flags one last time, ensuring that a compromised process is never allowed to run on the CPU.

### 7. Dynamic Performance Controller: DynamoX
### 7. Cerberus: Memory Security Agent

While Astra ensures the logical integrity of processes, **Cerberus** acts as the kernel's dedicated memory security guard. Its mission is to detect, classify, and mitigate common and dangerous memory corruption vulnerabilities in real-time. It is enabled via the `VF_CONFIG_USE_CERBERUS` flag and integrates deeply with the process scheduler and memory managers.

Cerberus operates on multiple defensive fronts:

#### Proactive Memory Tracking

1. **Stack Canary Protection**: To protect against stack buffer overflows (a common attack vector), Cerberus automatically installs a "canary"—a known secret value—at the end of a process's stack when it's registered. Before the scheduler switches to a process, Cerberus checks if this canary is intact. If the value has been overwritten, it signifies a buffer overflow, and a `MEM_VIOLATION_CANARY_CORRUPT` violation is logged.

2. **Allocation Monitoring**: Cerberus maintains a record of all active memory allocations for monitored processes (`CerberusTrackAlloc`). When memory is freed (`CerberusTrackFree`), Cerberus validates the operation. If a process attempts to free a memory address that isn't actively allocated to it, Cerberus flags it as a potential "double-free" or an attempt to free invalid memory, logging a `MEM_VIOLATION_DOUBLE_FREE`.

#### Reactive Fault Analysis

When a critical memory fault (e.g., a page fault) occurs, Cerberus intercepts the event before it panics the kernel. It analyzes the context of the fault to determine its likely cause, translating a generic CPU exception into a specific security event.

| Violation Type | Heuristic / Cause |
|:---------------------------------|:--------------------------------------------------------------------------|
| `MEM_VIOLATION_USE_AFTER_FREE` | Accessing a `NULL` or very low memory address (`< 0x1000`). |
| `MEM_VIOLATION_BUFFER_OVERFLOW` | Writing to a page that is not present in memory. |
| `MEM_VIOLATION_STACK_CORRUPTION` | Attempting to execute code from a non-executable page (NX bit violation). |
| `MEM_VIOLATION_BOUNDS_CHECK` | A user-mode process attempting to access kernel space memory. |

#### Integration with the Scheduler and Astra

Cerberus's true strength comes from its integration with the rest of the system:

* **Pre-emptive Threat Mitigation**: The `CerberusPreScheduleCheck` function is called just before a process is scheduled to run. If a process has been marked as `is_compromised` (either due to a canary failure or by exceeding its violation threshold), Cerberus will **block the process from ever running again**. This prevents a corrupted process from doing further damage.
* **Violation Threshold**: Cerberus tracks the number of violations per process. If a process accumulates too many violations (`CERBERUS_VIOLATION_THRESHOLD`), it is automatically flagged as compromised, even if the individual violations were not critical.
* **Threat Escalation**: When a significant threat is confirmed, `CerberusReportThreat` formats a detailed message and sends it to **Astra** via a VFS file. This allows Astra to take higher-level action, such as shutting down related processes or increasing the system-wide threat level.

By combining proactive checks with intelligent fault analysis, Cerberus provides a robust defense-in-depth security layer, making the entire system more resilient to memory-based attacks.

### 8. Dynamic Performance Controller: DynamoX

DynamoX is a system process that intelligently manages the CPU's operational frequency (simulated via the PIT timer) to deliver performance when needed and conserve power when the system is idle.

Expand Down Expand Up @@ -389,4 +422,4 @@ The following functions and commands provide interfaces to the process managemen
]
```
Quite simple, isn't it?
> assembler-0 @ voidframe-kernel - 7:05 31/08/2025
> assembler-0 @ voidframe-kernel - 11:54 01/09/2025
24 changes: 12 additions & 12 deletions kernel/process/Process.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "Process.h"
#include "Atomics.h"
#include "KernelHeap.h"
#ifdef VF_CONFIG_USE_CERBERUS
#include "Cerberus.h"
#endif
Expand Down Expand Up @@ -284,7 +285,7 @@ static void __attribute__((visibility("hidden"))) TerminateProcess(uint32_t pid,
#ifdef VF_CONFIG_USE_CERBERUS
CerberusUnregisterProcess(proc->pid);
#endif
if (proc->ProcINFOPath && VfsIsDir(proc->ProcINFOPath)) VfsDelete(proc->ProcINFOPath, true);
if (proc->ProcessRuntimePath && VfsIsDir(proc->ProcessRuntimePath)) VfsDelete(proc->ProcessRuntimePath, true);
else PrintKernelWarning("ProcINFOPath invalid during termination\n");
}

Expand Down Expand Up @@ -329,7 +330,7 @@ static void __attribute__((visibility("hidden"))) ASTerminate(uint32_t pid, cons

SpinUnlockIrqRestore(&scheduler_lock, flags);

if (proc->ProcINFOPath && VfsIsDir(proc->ProcINFOPath)) VfsDelete(proc->ProcINFOPath, true);
if (proc->ProcessRuntimePath && VfsIsDir(proc->ProcessRuntimePath)) VfsDelete(proc->ProcessRuntimePath, true);
else PrintKernelWarning("ProcINFOPath invalid during termination");
}

Expand Down Expand Up @@ -686,7 +687,7 @@ static void SmartAging(void) {
}

static inline __attribute__((visibility("hidden"))) __attribute__((always_inline)) int ProcINFOPathValidation(const ProcessControlBlock * proc) {
if (FastStrCmp(proc->ProcINFOPath, FormatS("%s/%d", RuntimeProcesses, proc->pid)) != 0) return 0;
if (FastStrCmp(proc->ProcessRuntimePath, FormatS("%s/%d", RuntimeProcesses, proc->pid)) != 0) return 0;
return 1;
}
Comment on lines +690 to 692
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

ProcINFO path validation uses FormatS static buffer (race-prone, false tamper alerts).

Use a stack buffer with FormatA to avoid static-buffer reuse.

-static inline __attribute__((visibility("hidden"))) __attribute__((always_inline)) int ProcINFOPathValidation(const ProcessControlBlock * proc) {
-    if (FastStrCmp(proc->ProcessRuntimePath, FormatS("%s/%d", RuntimeProcesses, proc->pid)) != 0) return 0;
+static inline __attribute__((visibility("hidden"))) __attribute__((always_inline)) int ProcINFOPathValidation(const ProcessControlBlock * proc) {
+    char expected[128];
+    FormatA(expected, sizeof(expected), "%s/%d", RuntimeProcesses, proc->pid);
+    if (FastStrCmp(proc->ProcessRuntimePath, expected) != 0) return 0;
     return 1;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (FastStrCmp(proc->ProcessRuntimePath, FormatS("%s/%d", RuntimeProcesses, proc->pid)) != 0) return 0;
return 1;
}
static inline __attribute__((visibility("hidden"))) __attribute__((always_inline)) int ProcINFOPathValidation(const ProcessControlBlock * proc) {
char expected[128];
FormatA(expected, sizeof(expected), "%s/%d", RuntimeProcesses, proc->pid);
if (FastStrCmp(proc->ProcessRuntimePath, expected) != 0) return 0;
return 1;
}
🤖 Prompt for AI Agents
In kernel/process/Process.c around lines 689 to 691, the code builds the
expected ProcessRuntimePath using FormatS (a static buffer) which can be reused
across threads and cause races/false tamper alerts; replace this with a stack
buffer and FormatA: allocate a local char buffer sized with a safe constant
(e.g. PATH_MAX + 32), call FormatA(buf, sizeof buf, "%s/%d", RuntimeProcesses,
proc->pid) to format into the stack buffer, then use
FastStrCmp(proc->ProcessRuntimePath, buf) for comparison and return accordingly;
ensure the buffer size is sufficient and remove reliance on FormatS.


Expand Down Expand Up @@ -716,7 +717,7 @@ static inline __attribute__((visibility("hidden"))) __attribute__((always_inline
}

if (ProcINFOPathValidation(proc) != 1) {
PrintKernelErrorF("[AS-PREFLIGHT] ProcINFOPath tampering detected for PID: %d (%s)\n", proc->pid, proc->ProcINFOPath);
PrintKernelErrorF("[AS-PREFLIGHT] ProcINFOPath tampering detected for PID: %d (%s)\n", proc->pid, proc->ProcessRuntimePath);
ASTerminate(proc->pid, "ProcINFOPath tampering detected");
return 0; // Do not schedule this process.
}
Expand Down Expand Up @@ -1071,16 +1072,15 @@ static __attribute__((visibility("hidden"))) uint32_t CreateSecureProcess(void (
processes[slot].io_operations = 0;
processes[slot].preemption_count = 0;
processes[slot].wait_time = 0;
processes[slot].ProcINFOPath = FormatS("%s/%d", RuntimeProcesses, new_pid);

processes[slot].ProcessRuntimePath = FormatS("%s/%d", RuntimeProcesses, new_pid);
#ifdef VF_CONFIG_USE_CERBERUS
CerberusRegisterProcess(new_pid, (uint64_t)stack, STACK_SIZE);
#endif

#ifdef VF_CONFIG_PROCINFO_CREATE_DEFAULT
if (!VfsIsDir(processes[slot].ProcINFOPath)) {
int rc = VfsCreateDir(processes[slot].ProcINFOPath);
if (rc != 0 && !VfsIsDir(processes[slot].ProcINFOPath)) {
if (!VfsIsDir(processes[slot].ProcessRuntimePath)) {
int rc = VfsCreateDir(processes[slot].ProcessRuntimePath);
if (rc != 0 && !VfsIsDir(processes[slot].ProcessRuntimePath)) {
PrintKernelError("ProcINFO: failed to create dir for PID ");
PrintKernelInt(processes[slot].pid);
PrintKernel("\n");
Expand Down Expand Up @@ -1472,7 +1472,7 @@ static void Astra(void) {
// register
security_manager_pid = current->pid;

FormatA(astra_path, sizeof(astra_path), "%s/astra", current->ProcINFOPath);
FormatA(astra_path, sizeof(astra_path), "%s/astra", current->ProcessRuntimePath);
if (VfsCreateFile(astra_path) != 0) PANIC("Failed to create Astra process info file");

PrintKernelSuccess("Astra: Astra active.\n");
Expand Down Expand Up @@ -1664,8 +1664,8 @@ int ProcessInit(void) {
idle_proc->privilege_level = PROC_PRIV_SYSTEM;
idle_proc->scheduler_node = NULL;
idle_proc->creation_time = GetSystemTicks();
idle_proc->ProcINFOPath = FormatS("%s/%d", RuntimeServices, idle_proc->pid);
if (VfsCreateDir(idle_proc->ProcINFOPath) != 0) PANIC("Failed to create ProcINFO directory");
idle_proc->ProcessRuntimePath = FormatS("%s/%d", RuntimeServices, idle_proc->pid);
if (VfsCreateDir(idle_proc->ProcessRuntimePath) != 0) PANIC("Failed to create ProcINFO directory");
// Securely initialize the token for the Idle Process
Comment on lines +1667 to 1669
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Same persistent path bug for the idle process.

Mirror the fix here.

-    idle_proc->ProcessRuntimePath = FormatS("%s/%d", RuntimeServices, idle_proc->pid);
-    if (VfsCreateDir(idle_proc->ProcessRuntimePath) != 0) PANIC("Failed to create ProcINFO directory");
+    FormatA(idle_proc->ProcessRuntimePath,
+            sizeof(idle_proc->ProcessRuntimePath),
+            "%s/%d", RuntimeServices, idle_proc->pid);
+    if (VfsCreateDir(idle_proc->ProcessRuntimePath) != 0)
+        PANIC("Failed to create ProcINFO directory");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
idle_proc->ProcessRuntimePath = FormatS("%s/%d", RuntimeServices, idle_proc->pid);
if (VfsCreateDir(idle_proc->ProcessRuntimePath) != 0) PANIC("Failed to create ProcINFO directory");
// Securely initialize the token for the Idle Process
FormatA(idle_proc->ProcessRuntimePath,
sizeof(idle_proc->ProcessRuntimePath),
"%s/%d", RuntimeServices, idle_proc->pid);
if (VfsCreateDir(idle_proc->ProcessRuntimePath) != 0)
PANIC("Failed to create ProcINFO directory");
// Securely initialize the token for the Idle Process
🤖 Prompt for AI Agents
In kernel/process/Process.c around lines 1667-1669, the idle process is being
given a persistent RuntimeServices path; mirror the earlier fix by constructing
its runtime path under the ephemeral runtime directory (use the same
RuntimeDir/temporary runtime base used for other processes), e.g. set
idle_proc->ProcessRuntimePath = FormatS("%s/%d", RuntimeDir, idle_proc->pid) and
call VfsCreateDir(idle_proc->ProcessRuntimePath) (keeping the same error
handling) so the idle process directory lives in the non-persistent runtime area
like other processes.

SecurityToken* token = &idle_proc->token;
token->magic = SECURITY_MAGIC;
Expand Down
2 changes: 1 addition & 1 deletion kernel/process/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ typedef struct {
ProcessContext context;
SchedulerNode* scheduler_node;
uint64_t creation_time;
char* ProcINFOPath;
char* ProcessRuntimePath;
} ProcessControlBlock;
Comment on lines +169 to 170
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Make ProcessRuntimePath storage persistent (avoid static-buffer aliasing).

Right now, callers store FormatS() results in this field, which returns a pointer to a single static buffer. Every subsequent FormatS() call overwrites all previously stored paths, breaking termination cleanup and tamper checks. Store the path inline to the PCB (or ensure heap-backed storage and free it on cleanup).

Option A (recommended: inline storage, zero-copy, no lifetime issues):

-    char* ProcessRuntimePath;
+    char  ProcessRuntimePath[128]; // or define PROC_PATH_MAX and use it

Run a quick scan to catch any remaining code assuming a pointer allocation:


🏁 Script executed:

#!/bin/bash
rg -nP "ProcessRuntimePath\s*=\s*FormatS" kernel | cat

Length of output: 289


Make ProcessRuntimePath inline storage to avoid static-buffer aliasing
ProcessRuntimePath is declared as a char* and populated with FormatS(), which returns a pointer to a single static buffer. Each new call to FormatS() overwrites the previous path, breaking termination cleanup and tamper checks. Change the field to inline storage (or heap-allocate and free on cleanup), for example:

--- a/kernel/process/Process.h
+++ b/kernel/process/Process.h
@@ struct ProcessControlBlock {
-    char *ProcessRuntimePath;
+    char  ProcessRuntimePath[PROC_PATH_MAX];  // define PROC_PATH_MAX (e.g. 128)
} ProcessControlBlock;

Then update the two assignment sites in Process.c (currently at lines 1074 and 1667) from:

processes[slot].ProcessRuntimePath = FormatS("%s/%d", RuntimeProcesses, new_pid);
...
idle_proc->ProcessRuntimePath  = FormatS("%s/%d", RuntimeServices, idle_proc->pid);

to:

snprintf(processes[slot].ProcessRuntimePath, PROC_PATH_MAX, "%s/%d", RuntimeProcesses, new_pid);
...
snprintf(idle_proc->ProcessRuntimePath,  PROC_PATH_MAX, "%s/%d", RuntimeServices, idle_proc->pid);
🤖 Prompt for AI Agents
In kernel/process/Process.h around lines 169-170, change the ProcessControlBlock
field from a char* to inline storage (e.g., char
ProcessRuntimePath[PROC_PATH_MAX];) so it does not point at FormatS()'s single
static buffer; then update the two assignment sites in Process.c (around lines
1074 and 1667) to use snprintf(processes[slot].ProcessRuntimePath,
PROC_PATH_MAX, "%s/%d", RuntimeProcesses, new_pid); and
snprintf(idle_proc->ProcessRuntimePath, PROC_PATH_MAX, "%s/%d", RuntimeServices,
idle_proc->pid); respectively, and ensure PROC_PATH_MAX is defined large enough
(and included where needed) or alternatively allocate/free heap storage
consistently if you prefer dynamic allocation.


typedef struct {
Expand Down
4 changes: 2 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
project('voidframe', 'c',
version : '0.0.1-beta5',
version : '0.0.1-beta5.3',
default_options : [
'c_std=c11',
'optimization=2',
Expand Down Expand Up @@ -140,7 +140,7 @@ vf_config_flags = [
'-DVF_CONFIG_USE_VFSHELL',
'-DVF_CONFIG_USE_DYNAMOX',
'-DVF_CONFIG_USE_ASTRA',
# '-DVF_CONFIG_USE_CERBERUS',
'-DVF_CONFIG_USE_CERBERUS',
# '-DVF_CONFIG_PANIC_OVERRIDE',
]

Expand Down