From 9c526fd9187e371c4b74ba093909693079642dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adil=20Alperen=20=C3=87iftci?= Date: Thu, 29 Oct 2026 12:39:08 +0300 Subject: [PATCH] compiler: fail closed past sandbox stack capacity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I reproduced a 65th sandbox block running without its grant list because the generated 64-slot stack silently discarded the push. Track logical depth past the physical capacity and refuse checks until matching pops or unwind restoration returns to a stored level. Cover the denied inner operation and the restored outer state with an end-to-end regression. Signed-off-by: Adil Alperen Çiftci --- compiler/codegen/codegen.c | 10 ++++-- ...093650Z-security-sandbox-depth-overflow.md | 5 +++ .../sandbox_depth_overflow/prog.ae | 35 ++++++++++++++++++ .../test_sandbox_depth_overflow.sh | 36 +++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 new_changelogs/20261029T093650Z-security-sandbox-depth-overflow.md create mode 100644 tests/integration/sandbox_depth_overflow/prog.ae create mode 100755 tests/integration/sandbox_depth_overflow/test_sandbox_depth_overflow.sh diff --git a/compiler/codegen/codegen.c b/compiler/codegen/codegen.c index 9c8afa129..b1c48efdf 100644 --- a/compiler/codegen/codegen.c +++ b/compiler/codegen/codegen.c @@ -9536,6 +9536,9 @@ void generate_program(CodeGenerator* gen, ASTNode* program) { print_line(gen, "static AETHER_TLS void* _aether_sandbox_stack[64];"); print_line(gen, "static AETHER_TLS int _aether_sandbox_depth = 0;"); if (has_sandbox) { + /* Depth continues past the physical stack capacity. The checker + * refuses every operation while it is above 64, so a nested enforce + * cannot disappear merely because its grant slot did not fit. */ /* Trusted names in `sandbox.enforce(perms, foo) { ... }`: each level * of the stack records which enforce site pushed it (0 for any other * push). A trusted call runs with the authority of the code that @@ -9561,10 +9564,10 @@ void generate_program(CodeGenerator* gen, ASTNode* program) { print_line(gen, "static AETHER_TLS int _aether_sandbox_site_at[64];"); print_line(gen, "static AETHER_TLS int _aether_sandbox_skip_to[64];"); print_line(gen, "static AETHER_TLS int _aether_sandbox_pending_site = 0;"); - print_line(gen, "static inline void _aether_sandbox_push(void* ctx) { if (_aether_sandbox_depth < 64) { _aether_sandbox_site_at[_aether_sandbox_depth] = _aether_sandbox_pending_site; _aether_sandbox_skip_to[_aether_sandbox_depth] = -1; _aether_sandbox_stack[_aether_sandbox_depth++] = ctx; } _aether_sandbox_pending_site = 0; }"); - print_line(gen, "static inline AETHER_MAYBE_UNUSED int _aether_sandbox_trust_enter(int site) { int saved = _aether_sandbox_depth; for (int l = _aether_sandbox_depth - 1; l >= 0; l--) { if (_aether_sandbox_skip_to[l] >= 0) { l = _aether_sandbox_skip_to[l]; continue; } if (_aether_sandbox_site_at[l] == site) { if (_aether_sandbox_depth < 64) { _aether_sandbox_site_at[_aether_sandbox_depth] = 0; _aether_sandbox_skip_to[_aether_sandbox_depth] = l; _aether_sandbox_stack[_aether_sandbox_depth++] = (void*)0; } break; } } return saved; }"); + print_line(gen, "static inline void _aether_sandbox_push(void* ctx) { if (_aether_sandbox_depth < 64) { _aether_sandbox_site_at[_aether_sandbox_depth] = _aether_sandbox_pending_site; _aether_sandbox_skip_to[_aether_sandbox_depth] = -1; _aether_sandbox_stack[_aether_sandbox_depth] = ctx; } _aether_sandbox_depth++; _aether_sandbox_pending_site = 0; }"); + print_line(gen, "static inline AETHER_MAYBE_UNUSED int _aether_sandbox_trust_enter(int site) { int saved = _aether_sandbox_depth; if (_aether_sandbox_depth > 64) return saved; for (int l = _aether_sandbox_depth - 1; l >= 0; l--) { if (_aether_sandbox_skip_to[l] >= 0) { l = _aether_sandbox_skip_to[l]; continue; } if (_aether_sandbox_site_at[l] == site) { if (_aether_sandbox_depth < 64) { _aether_sandbox_site_at[_aether_sandbox_depth] = 0; _aether_sandbox_skip_to[_aether_sandbox_depth] = l; _aether_sandbox_stack[_aether_sandbox_depth] = (void*)0; } _aether_sandbox_depth++; break; } } return saved; }"); } else { - print_line(gen, "static inline void _aether_sandbox_push(void* ctx) { if (_aether_sandbox_depth < 64) _aether_sandbox_stack[_aether_sandbox_depth++] = ctx; }"); + print_line(gen, "static inline void _aether_sandbox_push(void* ctx) { if (_aether_sandbox_depth < 64) _aether_sandbox_stack[_aether_sandbox_depth] = ctx; _aether_sandbox_depth++; }"); } print_line(gen, "static inline void _aether_sandbox_pop(void) { if (_aether_sandbox_depth > 0) _aether_sandbox_depth--; }"); if (has_sandbox) { @@ -9597,6 +9600,7 @@ void generate_program(CodeGenerator* gen, ASTNode* program) { print_line(gen, "extern void aether_sandbox_audit(const char*, const char*, int);"); print_line(gen, "static int _aether_sandbox_check_impl(const char* category, const char* resource) {"); print_line(gen, " if (_aether_sandbox_depth <= 0) return 1;"); + print_line(gen, " if (_aether_sandbox_depth > 64) { aether_sandbox_audit(category, resource, 0); return 0; }"); print_line(gen, " int _allowed = 1;"); /* From the top down, a trusted call's barrier jumping over the * levels it hides. With none left in force the check is the one an diff --git a/new_changelogs/20261029T093650Z-security-sandbox-depth-overflow.md b/new_changelogs/20261029T093650Z-security-sandbox-depth-overflow.md new file mode 100644 index 000000000..d15adea39 --- /dev/null +++ b/new_changelogs/20261029T093650Z-security-sandbox-depth-overflow.md @@ -0,0 +1,5 @@ +- **Deeply nested sandbox blocks now fail closed.** The generated permission + stack silently dropped pushes beyond its 64-slot capacity, so an inner + policy could run under only its outer grants. The logical depth now + continues past the storage limit and refuses checks until matching pops or + unwind restoration returns it to a representable level. diff --git a/tests/integration/sandbox_depth_overflow/prog.ae b/tests/integration/sandbox_depth_overflow/prog.ae new file mode 100644 index 000000000..fc0a79e3f --- /dev/null +++ b/tests/integration/sandbox_depth_overflow/prog.ae @@ -0,0 +1,35 @@ +import std.os +import std.sandbox + +fn descend(level: int, allow: sandbox.Grants, deny: sandbox.Grants) -> int { + visible = 1 + if level == 0 { + sandbox.enforce(deny) callback { + if os.getenv("AE_SANDBOX_DEPTH_SECRET") == null { visible = 0 } + } + return visible + } + + sandbox.enforce(allow) callback { + visible = descend(level - 1, allow, deny) + } + return visible +} + +main() { + allow = sandbox.new("allow env") { + grant_env("AE_SANDBOX_DEPTH_SECRET") + } + deny = sandbox.new("deny env") {} + + visible = descend(64, allow, deny) + println("overflow-visible=${visible}") + + restored = 0 + if os.getenv("AE_SANDBOX_DEPTH_SECRET") != null { restored = 1 } + println("after-visible=${restored}") + + sandbox.free(deny) + sandbox.free(allow) + return visible + (1 - restored) +} diff --git a/tests/integration/sandbox_depth_overflow/test_sandbox_depth_overflow.sh b/tests/integration/sandbox_depth_overflow/test_sandbox_depth_overflow.sh new file mode 100755 index 000000000..cd25593e0 --- /dev/null +++ b/tests/integration/sandbox_depth_overflow/test_sandbox_depth_overflow.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# A grant pushed past the fixed stack capacity must refuse checks instead of +# letting the block run under only its less restrictive outer grants. + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" +AE="$ROOT/build/ae" + +if [ ! -x "$AE" ]; then + echo " [SKIP] sandbox_depth_overflow: build/ae not built" + exit 0 +fi + +TMP="$(cd "$(mktemp -d)" && pwd -P)" +trap 'rm -rf "$TMP"' EXIT +if ! "$AE" build "$SCRIPT_DIR/prog.ae" -o "$TMP/prog" >"$TMP/build.log" 2>&1; then + echo " [FAIL] sandbox_depth_overflow: probe did not build" + sed 's/^/ /' "$TMP/build.log" | tail -20 + exit 1 +fi + +set +e +out="$(AE_SANDBOX_DEPTH_SECRET=visible "$TMP/prog" 2>&1)" +rc=$? +set -e +if [ "$rc" -eq 0 ] && printf '%s' "$out" | grep -q '^overflow-visible=0$' && + printf '%s' "$out" | grep -q '^after-visible=1$'; then + echo " [PASS] sandbox stack overflow refuses the inner check" + echo "PASS: sandbox_depth_overflow" + exit 0 +fi + +echo " [FAIL] sandbox stack overflow dropped the inner policy" +printf '%s\n' "$out" | sed 's/^/ /' | head -10 +echo "FAIL: sandbox_depth_overflow (rc=$rc)" +exit 1