From e3e5ce2e4acd4d45773cb36e3260c7e071d9be8a Mon Sep 17 00:00:00 2001 From: Abhishek Sharma Date: Thu, 19 Feb 2026 00:01:36 -0600 Subject: [PATCH 1/3] Change single sandbox option/define to be non-segue specific --- core/arch/x64/callback.S | 2 +- core/arch/x64/runtime.S | 2 +- core/arch/x64/trampoline.S | 2 +- meson.build | 4 ++-- meson_options.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/arch/x64/callback.S b/core/arch/x64/callback.S index 2fd3ef1..6ec1b61 100644 --- a/core/arch/x64/callback.S +++ b/core/arch/x64/callback.S @@ -87,7 +87,7 @@ 1: #ifdef ENABLE_SEGUE -# ifndef SEGUE_SINGLE_SANDBOX +# ifndef SINGLE_SANDBOX wrgsbase %REG_BASE # endif #endif diff --git a/core/arch/x64/runtime.S b/core/arch/x64/runtime.S index 11237a5..272bbe3 100644 --- a/core/arch/x64/runtime.S +++ b/core/arch/x64/runtime.S @@ -166,7 +166,7 @@ lfi_syscall_entry: // NOTE: consider removing this entirely if we can assume that syscall // execution never modifies %gs (i.e., never calls into another sandbox). #ifdef ENABLE_SEGUE -# ifndef SEGUE_SINGLE_SANDBOX +# ifndef SINGLE_SANDBOX wrgsbase %REG_BASE # endif #endif diff --git a/core/arch/x64/trampoline.S b/core/arch/x64/trampoline.S index 5dd5ebd..34dbefe 100644 --- a/core/arch/x64/trampoline.S +++ b/core/arch/x64/trampoline.S @@ -210,7 +210,7 @@ lfi_trampoline_direct: movq REGS_BASE(%r11), %REG_BASE // also write base to %gs #ifdef ENABLE_SEGUE -# ifndef SEGUE_SINGLE_SANDBOX +# ifndef SINGLE_SANDBOX wrgsbase %REG_BASE # endif #endif diff --git a/meson.build b/meson.build index 9a653c0..b363316 100644 --- a/meson.build +++ b/meson.build @@ -120,8 +120,8 @@ endif if get_option('enable_segue') defines += '-DENABLE_SEGUE' endif -if get_option('segue_single_sandbox') - defines += '-DSEGUE_SINGLE_SANDBOX' +if get_option('single_sandbox') + defines += '-DSINGLE_SANDBOX' endif if get_option('sys_minimal') defines += '-DSYS_MINIMAL' diff --git a/meson_options.txt b/meson_options.txt index bb17a70..93b72b6 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -42,7 +42,7 @@ option('enable_segue', value: true, description: 'use %gs for the sandbox base (x86-64 only)') -option('segue_single_sandbox', +option('single_sandbox', type: 'boolean', value: false, description: 'assume that %gs is never modified by the host, and there is only one sandbox (x86-64 only)') From ce54ee12202bf54cdc82a05b66f18fdccf565867 Mon Sep 17 00:00:00 2001 From: Abhishek Sharma Date: Thu, 19 Feb 2026 12:23:54 -0600 Subject: [PATCH 2/3] Add LARGE_SANDBOX mode for x64 sandbox --- core/arch/arm64/regs.c | 5 ++ core/arch/riscv64/regs.c | 5 ++ core/arch/x64/arch_callback.h | 4 ++ core/arch/x64/callback.S | 6 ++ core/arch/x64/regs.c | 13 ++++ core/arch/x64/trampoline.S | 122 ++++++++++++++++++++++++++++++++++ core/arch_asm.h | 6 +- core/include/lfi_core.h | 5 ++ linux/linux.h | 3 + linux/sys.c | 2 +- linux/sys/sys_arch_prctl.c | 1 + linux/sys/sys_clone.c | 8 +++ linux/thread.c | 2 - linux/trampoline.c | 3 + meson.build | 3 + meson_options.txt | 5 ++ 16 files changed, 189 insertions(+), 4 deletions(-) diff --git a/core/arch/arm64/regs.c b/core/arch/arm64/regs.c index 08dc94a..8324678 100644 --- a/core/arch/arm64/regs.c +++ b/core/arch/arm64/regs.c @@ -14,3 +14,8 @@ lfi_ctx_regs_init(struct LFIContext *ctx) ctx->ctxreg[CTXREG_CTX_OFFSET / 8] = (uint64_t) ctx; #endif } + +EXPORT void +lfi_ctx_thread_regs_init(struct LFIContext* ctx) +{ +} diff --git a/core/arch/riscv64/regs.c b/core/arch/riscv64/regs.c index 54bce6f..abd5ce2 100644 --- a/core/arch/riscv64/regs.c +++ b/core/arch/riscv64/regs.c @@ -10,3 +10,8 @@ lfi_ctx_regs_init(struct LFIContext *ctx) ctx->regs.ra = ctx->box->base; ctx->regs.retaddr = ctx->box->retaddr; } + +EXPORT void +lfi_ctx_thread_regs_init(struct LFIContext* ctx) +{ +} diff --git a/core/arch/x64/arch_callback.h b/core/arch/x64/arch_callback.h index ca58127..51dd289 100644 --- a/core/arch/x64/arch_callback.h +++ b/core/arch/x64/arch_callback.h @@ -2,7 +2,11 @@ #include +#ifdef LARGE_SANDBOX +#define MAXCALLBACKS 40960 +#else #define MAXCALLBACKS 4096 +#endif // A CallbackEntry is what gets inserted into the sandbox when a new callback // is registered. It consists of 16 bytes of code, which performs the callback diff --git a/core/arch/x64/callback.S b/core/arch/x64/callback.S index 6ec1b61..5ce4df1 100644 --- a/core/arch/x64/callback.S +++ b/core/arch/x64/callback.S @@ -132,8 +132,14 @@ // NOTE: This pop instruction could fault. popq %r11 +#ifdef LARGE_SANDBOX + andq %REG_MASK, %r11 + andq $0xffffffffffffffe0, %r11 + addq %REG_BASE, %r11 +#else andl $0xffffffe0, %r11d addq %REG_BASE, %r11 +#endif jmpq *%r11 .endm diff --git a/core/arch/x64/regs.c b/core/arch/x64/regs.c index 1f1ca40..dd01441 100644 --- a/core/arch/x64/regs.c +++ b/core/arch/x64/regs.c @@ -10,10 +10,23 @@ lfi_ctx_regs_init(struct LFIContext *ctx) ctx->regs.pkey = ctx->box->pkey; ctx->regs.fcw = 0x37f; ctx->regs.mxcsr = 0x1f80; +#ifdef LARGE_SANDBOX + ctx->regs.REG_MASK = ctx->box->size - 1; + ctx->regs._tp = 0; +#else #ifdef CTXREG ctx->regs.r15 = (uint64_t) ctx->ctxreg; ctx->ctxreg[CTXREG_CTX_OFFSET / 8] = (uint64_t) ctx; #else ctx->regs.r15 = 0x00000000ffffffff; #endif +#endif +} + +EXPORT void +lfi_ctx_thread_regs_init(struct LFIContext* ctx) +{ +#if defined(LARGE_SANDBOX) && defined(SINGLE_SANDBOX) + __asm__ __volatile__("wrgsbase %0" : : "r"(ctx->regs._tp)); +#endif } diff --git a/core/arch/x64/trampoline.S b/core/arch/x64/trampoline.S index 34dbefe..f8c9b9d 100644 --- a/core/arch/x64/trampoline.S +++ b/core/arch/x64/trampoline.S @@ -215,6 +215,11 @@ lfi_trampoline_direct: # endif #endif +#ifdef LARGE_SANDBOX + // load sandbox mask + movq REGS_MASK(%r11), %REG_MASK +#endif + // load address of the lfi_retfn function that will make the return rtcall movq REGS_RETADDR(%r11), %r11 // align stack @@ -225,7 +230,124 @@ lfi_trampoline_direct: // load address of the target function movq %r12, %r11 // apply mask just to be safe +#ifdef LARGE_SANDBOX + andq %r15, %r11 + andq $0xffffffffffffffe0, %r11 +#else andl $0xffffffe0, %r11d +#endif + addq %REG_BASE, %r11 + + // set the sandbox thread control block by saving the old value of + // %fs:TLS_SLOT_LFI into the context and setting it to point to the context + // itself + get_ctx %r12 + movq (%r13), %r13 + movq %r12, REGS_HOST_TP(%r13) + write_ctx %r13 + +#ifndef STORES_ONLY + fldcw REGS_FCW(%r13) + ldmxcsr REGS_MXCSR(%r13) +#endif + +#ifdef HAVE_PKU + movq %rcx, %rbx + movq %rdx, %rbp + pku_box_access REGS_PKEY(%r13) + movq %rbp, %rdx + movq %rbx, %rcx +#endif + +#ifndef STORES_ONLY + // Zero non-argument registers for full LFI. + zero_regs +#endif + + // this function should return via a runtime call + jmpq *%r11 + int3 + +// Direct trampoline that doesn't involve a thread-local lookup. Instead the +// info usually passed via the lfi_invoke_info thread-local are passed via +// the stack: +// (%rsp+24): n_stack (unused) +// (%rsp+16): lfiptr target +// (%rsp+8): struct LFIContext *box +// (%rsp): struct LFIContext **ctxp +.global lfi_trampoline_stack_args +.p2align 4 +lfi_trampoline_stack_args: + // ctxp + popq %r11 + // box + popq %r10 + + // save callee-saved registers + xchgq %r15, (%rsp) + // xchgq %r14, -8(%rsp) not included yet + pushq %r14 + pushq %r13 + pushq %r12 + pushq %rbx + pushq %rbp + + // Put arguments where we expect them. + movq %r15, %r12 // target + movq %r11, %r13 // ctxp + movq %r14, %r15 // n_stack + movq %r10, %r14 // box + + lazy_ctx_clone + + // dummy push to keep the stack aligned + pushq %rbp + // push sandbox rsp + pushq REGS_RSP(%r11) + + // save current host stack + movq %rsp, REGS_HOST_SP(%r11) + // load sandbox rsp + movq REGS_RSP(%r11), %rsp + // load sandbox base + movq REGS_BASE(%r11), %REG_BASE + // also write base to %gs +#ifdef ENABLE_SEGUE +# ifndef SINGLE_SANDBOX + wrgsbase %REG_BASE +# endif +#endif + +#ifdef LARGE_SANDBOX + // load sandbox mask + movq REGS_MASK(%r11), %REG_MASK +#endif + + // save application stack pointer to copy arguments below + movq REGS_HOST_SP(%r11), %r10 + // load address of the lfi_retfn function that will make the return rtcall + movq REGS_RETADDR(%r11), %r11 + // align stack + andq $0xfffffffffffffff0, %rsp + + // copy stack arguments + pushq 96(%r10) + pushq 88(%r10) + pushq 80(%r10) + pushq 72(%r10) + + // push this as the return address onto the sandbox stack + pushq %r11 + + // load address of the target function + movq %r12, %r11 + // apply mask just to be safe +#ifdef LARGE_SANDBOX + andq %r15, %r11 + andq $0xffffffffffffffe0, %r11 +#else + andl $0xffffffe0, %r11d +#endif addq %REG_BASE, %r11 // set the sandbox thread control block by saving the old value of diff --git a/core/arch_asm.h b/core/arch_asm.h index e31c644..8873859 100644 --- a/core/arch_asm.h +++ b/core/arch_asm.h @@ -104,9 +104,13 @@ #define REGS_XMM(n) (REGS_XMM0 + 16 * n) #define REGS_BASE REGS_R14 - #define REG_BASE r14 +#ifdef LARGE_SANDBOX +#define REGS_MASK REGS_R15 +#define REG_MASK r15 +#endif + #define CTX_ABORT_CALLBACK 448 #define CTX_ABORT_STATUS 456 diff --git a/core/include/lfi_core.h b/core/include/lfi_core.h index c6b189b..f1705e6 100644 --- a/core/include/lfi_core.h +++ b/core/include/lfi_core.h @@ -299,6 +299,11 @@ lfi_ctx_set_tp(struct LFIContext *ctx, uint64_t tp); void lfi_ctx_regs_init(struct LFIContext *ctx); +// Used to set sandbox context registers that can be set once per-thread +// rather than having to be set on each transition. +void +lfi_ctx_thread_regs_init(struct LFIContext* ctx); + // Causes the sandbox context to exit with a given exit code. void lfi_ctx_exit(struct LFIContext *ctx, int code); diff --git a/linux/linux.h b/linux/linux.h index 4ac2eca..0832bc2 100644 --- a/linux/linux.h +++ b/linux/linux.h @@ -38,6 +38,9 @@ struct SysInfo { typedef int linux_clockid_t; typedef int64_t linux_time_t; +// First TID, to avoid using low TID numbers. +#define BASE_TID 10000 + #define LINUX_EPERM 1 #define LINUX_ENOENT 2 #define LINUX_ENXIO 6 diff --git a/linux/sys.c b/linux/sys.c index 8bade50..877b335 100644 --- a/linux/sys.c +++ b/linux/sys.c @@ -69,7 +69,7 @@ syshandle(struct LFILinuxThread *t, uintptr_t sysno, uintptr_t a0, uintptr_t a1, // clang-format off switch (sysno) { SYS(getpid, - 0) + BASE_TID) SYS(write, sys_write(t, a0, a1, a2)) SYS(writev, diff --git a/linux/sys/sys_arch_prctl.c b/linux/sys/sys_arch_prctl.c index c7ce7e1..a101407 100644 --- a/linux/sys/sys_arch_prctl.c +++ b/linux/sys/sys_arch_prctl.c @@ -13,6 +13,7 @@ sys_arch_prctl(struct LFILinuxThread *t, int code, lfiptr addr) return -LINUX_ENOSYS; #else lfi_ctx_set_tp(t->ctx, addr); + lfi_ctx_thread_regs_init(t->ctx); return 0; #endif default: diff --git a/linux/sys/sys_clone.c b/linux/sys/sys_clone.c index 08d22d1..e462e27 100644 --- a/linux/sys/sys_clone.c +++ b/linux/sys/sys_clone.c @@ -41,6 +41,7 @@ threadspawn_fake(struct LFILinuxThread *t) #error "invalid arch" #endif + lfi_ctx_thread_regs_init(t->ctx); int code = lfi_ctx_run(t->ctx, entry); assert(code == 0); } @@ -90,6 +91,7 @@ threadspawn(void *arg) pthread_cond_signal(&t->cond_ready); unlock(&t->lk_ready); + lfi_ctx_thread_regs_init(t->ctx); lfi_ctx_run(t->ctx, entry); end: @@ -152,6 +154,8 @@ spawn(struct LFILinuxThread *p, uint64_t flags, uint64_t stack, uint64_t ptidp, struct LFIRegs *regs = lfi_ctx_regs(p2->ctx); if (flags & LINUX_CLONE_SETTLS) { lfi_ctx_set_tp(p2->ctx, tls); + } else { + lfi_ctx_set_tp(p2->ctx, 0); } if (flags & LINUX_CLONE_CHILD_CLEARTID) { p2->ctidp = ctidp; @@ -184,6 +188,10 @@ spawn(struct LFILinuxThread *p, uint64_t flags, uint64_t stack, uint64_t ptidp, threadspawn_fake(p2); lfi_invoke_info = old; new_ctx = p2->ctx; + + // threadspawn_fake may have clobbered context specific per-thread + // registers. + lfi_ctx_thread_regs_init(p->ctx); } else { pthread_t *thread = malloc(sizeof(pthread_t)); pthread_attr_t attr; diff --git a/linux/thread.c b/linux/thread.c index 4bb40e1..3beed70 100644 --- a/linux/thread.c +++ b/linux/thread.c @@ -33,8 +33,6 @@ host_getauxval(unsigned long type) } #endif -// First TID, to avoid using low TID numbers. -#define BASE_TID 10000 // Maximum number of argv arguments. #define ARGV_MAX 1024 // Maxmimum number of envp arguments. diff --git a/linux/trampoline.c b/linux/trampoline.c index 97d18f2..d860119 100644 --- a/linux/trampoline.c +++ b/linux/trampoline.c @@ -75,6 +75,7 @@ lfi_linux_clone_cb(struct LFIBox *box) // Invoke thread_create in clone_ctx and return the resulting new_ctx. LOCK_WITH_DEFER(&proc->lk_clone, lk_clone); + lfi_ctx_thread_regs_init(proc->clone_ctx); lfiptr pt = LFI_INVOKE(box, &proc->clone_ctx, proc->libsyms.thread_create, lfiptr, (void) ); @@ -82,6 +83,7 @@ lfi_linux_clone_cb(struct LFIBox *box) thread->box_pthread = pt; pthread_setspecific(thread_key, new_ctx); + lfi_ctx_thread_regs_init(new_ctx); return new_ctx; } @@ -102,6 +104,7 @@ thread_destructor(void *p) thread->box_pthread); LOCK_WITH_DEFER(&proc->lk_clone, lk_clone); + lfi_ctx_thread_regs_init(proc->clone_ctx); LFI_INVOKE(proc->box, &proc->clone_ctx, proc->libsyms.free, void, (lfiptr), thread->box_pthread); diff --git a/meson.build b/meson.build index b363316..390d131 100644 --- a/meson.build +++ b/meson.build @@ -123,6 +123,9 @@ endif if get_option('single_sandbox') defines += '-DSINGLE_SANDBOX' endif +if get_option('large_sandbox') + defines += '-DLARGE_SANDBOX' +endif if get_option('sys_minimal') defines += '-DSYS_MINIMAL' endif diff --git a/meson_options.txt b/meson_options.txt index 93b72b6..6bdca5a 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -47,6 +47,11 @@ option('single_sandbox', value: false, description: 'assume that %gs is never modified by the host, and there is only one sandbox (x86-64 only)') +option('large_sandbox', + type: 'boolean', + value: false, + description: 'allow creation of sandboxes with sizes larger than 4GB (x86-64 only)') + option('enable_pku', type: 'boolean', value: false, From ef3ff4406f5422607c31218f542fe6906f17e056 Mon Sep 17 00:00:00 2001 From: Taehyun Noh Date: Tue, 3 Mar 2026 12:45:58 -0600 Subject: [PATCH 3/3] add `--boxsize` flag --- core/core.c | 2 +- tools/lfi-run/main.c | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/core/core.c b/core/core.c index eacd621..4ebeb95 100644 --- a/core/core.c +++ b/core/core.c @@ -93,7 +93,7 @@ lfi_new(struct LFIOptions opts, size_t nsandboxes) } struct BoxMapOptions bm_opts = (struct BoxMapOptions) { - .chunksize = gb(4), + .chunksize = opts.boxsize, .guardsize = REGION_GUARD, }; struct BoxMap *bm = boxmap_new(bm_opts); diff --git a/tools/lfi-run/main.c b/tools/lfi-run/main.c index 9d9722a..90faef4 100644 --- a/tools/lfi-run/main.c +++ b/tools/lfi-run/main.c @@ -25,6 +25,20 @@ struct Buf { void *data; size_t size; }; + static size_t + parse_size(const char *s) + { + char *end; + size_t n = strtoull(s, &end, 0); + switch (*end) { + case 'T': case 't': return n * 1024 * 1024 * 1024 * 1024ULL; + case 'G': case 'g': return n * 1024 * 1024 * 1024ULL; + case 'M': case 'm': return n * 1024 * 1024ULL; + case 'K': case 'k': return n * 1024ULL; + } + return n; + } + static struct Buf readfile(const char *path) @@ -60,6 +74,7 @@ usage(const char *prog_name) printf(" --env= set environment variable\n"); printf(" --dir= map sandbox path to host directory\n"); printf(" --wd= working directory within sandbox\n"); + printf(" --boxsize= large sandbox size (e.g. 4G, 1T); default=4G\n"); printf(" -r, --restricted apply --dir and --wd flags (default is --dir /=/ --wd $PWD for testing)\n"); printf(" input command\n"); } @@ -79,6 +94,7 @@ main(int argc, char **argv) const char **dirs = NULL; int dirs_count = 0; const char *wd = NULL; + size_t boxsize = gb(4); bool restricted = false; static struct option long_options[] = { @@ -92,6 +108,7 @@ main(int argc, char **argv) { "env", required_argument, 0, 4 }, { "dir", required_argument, 0, 5 }, { "wd", required_argument, 0, 6 }, + { "boxsize", required_argument, 0, 7 }, { "restricted", no_argument, 0, 'r' }, { 0, 0, 0, 0 } }; @@ -135,6 +152,9 @@ main(int argc, char **argv) case 6: wd = optarg; break; + case 7: + boxsize = parse_size(optarg); + break; case 'r': restricted = true; break; @@ -152,7 +172,7 @@ main(int argc, char **argv) struct LFIEngine *engine = lfi_new( (struct LFIOptions) { - .boxsize = gb(4), + .boxsize = boxsize, .pagesize = pagesize > 0 ? pagesize : getpagesize(), .no_verify = !verify, .verbose = verbose,