[PATCH] arch/um: Add security sandboxing by enforcing read-only on host system
From: حنان المطيري
Date: Thu Sep 10 2026 - 11:53:44 EST
Hi,
I have developed a security feature for arch/um that protects the host
filesystem. It boots directly from the host system files (making it
fast and lightweight) but enforces a strict Read-Only policy on the
host, while allowing full Read/Write operations only within a specific
sandbox directory (/tmp/sandbox). Any write or modifications outside
this path will be blocked with an -EACCES error.
Since I don't use Git locally, I am sending the configuration, file
path, and explanations directly below. The actual implementation code
is appended at the very bottom of this message.
---
### 1. File Path to Modify:
arch/um/kernel/skas/syscall.c
### 2. How it Works:
- The modified handle_syscall acts as a dynamic firewall for the kernel.
- It intercepts critical file manipulation syscalls (open, openat,
unlink, unlinkat, mkdir, mkdirat, rmdir, rename, renameat, renameat2)
right before execution.
- If a syscall attempts to modify, create, or delete a file, it checks
the path string using strncpy_from_user.
- If the path is absolute and doesn't start with /tmp/sandbox, or if
it contains relative path traversal (..), it rejects the request
instantly by injecting -EACCES into the return register, thus
protecting the underlying host filesystem from any modifications.
### 3. Why the Custom Command and Script are Required:
- **Why the Command is necessary (rootfstype=hostfs):** Traditionally,
UML boots from an isolated disk image (rootfs). By passing this exact
command, we force UML to mount the real host directory (/) as its root
filesystem. This allows the sandbox to boot instantly using the host's
native system binaries and configurations without duplication.
- **Why the Script must be changed:** Since we are booting directly
from the host files, the boot and initialization scripts within the
runtime environment must be adapted to redirect their active system
state, logging, and temporary data writing exclusively into
/tmp/sandbox. If they attempt to write to the regular host paths (like
/var/log or /etc), the kernel security logic will drop the request.
Recommended Kernel Boot Command (Ensure CONFIG_HOSTFS=y is enabled):
./linux root=/dev/root rootfstype=hostfs rootflags=/ init=/sbin/init rw
---
### 4. Implementation Code (Please append this at the end of the file):
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2026 Tamim N. Almutairi (Tamimdevlopment@xxxxxxxxxxx)
*/
#include <linux/kernel.h>
#include <linux/ptrace.h>
#include <linux/seccomp.h>
#include <kern_util.h>
#include <sysdep/ptrace.h>
#include <sysdep/ptrace_user.h>
#include <linux/time-internal.h>
#include <asm/syscall.h>
#include <asm/unistd.h>
#include <asm/delay.h>
#include <linux/fcntl.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
void handle_syscall(struct uml_pt_regs *r)
{
struct pt_regs *regs = container_of(r, struct pt_regs, regs);
int syscall;
UPT_SYSCALL_NR(r) = PT_SYSCALL_NR(r->gp);
PT_REGS_SET_SYSCALL_RETURN(regs, -ENOSYS);
if (syscall_trace_enter(regs))
goto out;
if (!seccomp_permit_syscall())
goto out;
syscall = UPT_SYSCALL_NR(r);
if ((time_travel_mode == TT_MODE_INFCPU ||
time_travel_mode == TT_MODE_EXTERNAL) &&
syscall == __NR_sched_yield)
tt_extra_sched_jiffies += 1;
if (syscall >= 0 && syscall < __NR_syscalls) {
unsigned long ret;
int violate_sandbox = 0;
if (syscall == __NR_openat || syscall == __NR_open ||
syscall == __NR_unlinkat || syscall == __NR_unlink ||
syscall == __NR_mkdirat || syscall == __NR_mkdir ||
syscall == __NR_rmdir ||
syscall == __NR_renameat2 || syscall == __NR_renameat || syscall ==
__NR_rename) {
unsigned long arg_flags = 0;
unsigned long uptr = 0;
unsigned long uptr2 = 0;
int check_path = 0;
if (syscall == __NR_openat) {
uptr = UPT_SYSCALL_ARG2(®s->regs);
arg_flags = UPT_SYSCALL_ARG3(®s->regs);
if ((arg_flags & O_ACCMODE) == O_WRONLY ||
(arg_flags & O_ACCMODE) == O_RDWR ||
(arg_flags & O_CREAT) ||
(arg_flags & O_TRUNC)) {
check_path = 1;
}
} else if (syscall == __NR_open) {
uptr = UPT_SYSCALL_ARG1(®s->regs);
arg_flags = UPT_SYSCALL_ARG2(®s->regs);
if ((arg_flags & O_ACCMODE) == O_WRONLY ||
(arg_flags & O_ACCMODE) == O_RDWR ||
(arg_flags & O_CREAT) ||
(arg_flags & O_TRUNC)) {
check_path = 1;
}
} else if (syscall == __NR_unlinkat || syscall == __NR_mkdirat) {
uptr = UPT_SYSCALL_ARG2(®s->regs);
check_path = 1;
} else if (syscall == __NR_renameat2 || syscall == __NR_renameat) {
uptr = UPT_SYSCALL_ARG2(®s->regs);
uptr2 = UPT_SYSCALL_ARG4(®s->regs);
check_path = 1;
} else if (syscall == __NR_rename) {
uptr = UPT_SYSCALL_ARG1(®s->regs);
uptr2 = UPT_SYSCALL_ARG2(®s->regs);
check_path = 1;
} else {
uptr = UPT_SYSCALL_ARG1(®s->regs);
check_path = 1;
}
if (check_path && uptr && !violate_sandbox) {
char *kname = kmalloc(256, GFP_KERNEL);
if (kname) {
long res = strncpy_from_user(kname, (const char __user *)uptr, 255);
if (res > 0) {
kname[res] = '\0';
if (kname[0] == '/') {
if (strncmp(kname, "/tmp/sandbox", 12) != 0)
violate_sandbox = 1;
} else {
if (strstr(kname, "..") != NULL)
violate_sandbox = 1;
}
}
kfree(kname);
}
}
if (uptr2 && !violate_sandbox) {
char *kname2 = kmalloc(256, GFP_KERNEL);
if (kname2) {
long res2 = strncpy_from_user(kname2, (const char __user *)uptr2, 255);
if (res2 > 0) {
kname2[res2] = '\0';
if (kname2[0] == '/') {
if (strncmp(kname2, "/tmp/sandbox", 12) != 0)
violate_sandbox = 1;
} else {
if (strstr(kname2, "..") != NULL)
violate_sandbox = 1;
}
}
kfree(kname2);
}
}
}
if (violate_sandbox) {
PT_REGS_SET_SYSCALL_RETURN(regs, -EACCES);
goto out;
}
ret = (*sys_call_table[syscall])(UPT_SYSCALL_ARG1(®s->regs),
UPT_SYSCALL_ARG2(®s->regs),
UPT_SYSCALL_ARG3(®s->regs),
UPT_SYSCALL_ARG4(®s->regs),
UPT_SYSCALL_ARG5(®s->regs),
UPT_SYSCALL_ARG6(®s->regs));
PT_REGS_SET_SYSCALL_RETURN(regs, ret);
if (IS_ERR_VALUE(ret) &&
(time_travel_mode == TT_MODE_INFCPU ||
time_travel_mode == TT_MODE_EXTERNAL)) {
um_udelay(1);
schedule();
}
}
out:
syscall_trace_leave(regs);
}
---
Please review this architectural enhancement and let me know your feedback.
Best regards,
Tamim N. Almutairi