Add a Go SDK (cgo bindings over libsandlock_ffi)#83
Conversation
A Linux-only Go SDK that binds the sandlock C ABI via cgo, mirroring the Python SDK's Sandbox surface. Covers the static policy configuration plus in-process Confine (which the CLI cannot do): - Sandbox config -> native policy for every current builder field - Run / RunInteractive / DryRun with captured Result and Changes - Spawn + Process lifecycle (Pid/Wait/Pause/Resume/Kill/Ports/Close) - Confine, LandlockABIVersion, MinLandlockABI, SyscallNr - pure, cross-platform parsing helpers (internal/policy) with unit tests - linux integration tests that skip when the kernel ABI is too old - a CI job that builds libsandlock_ffi and runs go vet + go test Dynamic policy_fn callbacks, custom seccomp handlers, pipeline/gather, fork/reduce, and checkpoint are intentionally left as follow-ups. policy_fn in particular needs a void* user_data parameter added to sandlock_sandbox_builder_policy_fn before Go can route the callback to a per-Sandbox closure.
congwang-mk
left a comment
There was a problem hiding this comment.
Nice work! Just two issues below:
| if p.h == nil { | ||
| return nil, ErrNotRunning | ||
| } | ||
| r := C.sandlock_handle_wait(p.h) |
There was a problem hiding this comment.
Process mutex is held across the blocking native wait, so Kill can't interrupt Wait?
| mu sync.Mutex | ||
| h unsafe.Pointer | ||
| pid int | ||
| } |
There was a problem hiding this comment.
No finalizer on Process, which leaks handle + orphaned child if dropped without Close/Wait?
|
For the open questions, the current code is fine as it is. |
…lizer Address review feedback on the Go SDK Process lifecycle: - Wait reserved the mutex for the whole native wait, so Pause/Resume/Kill (and Close) blocked behind it and could not interrupt a blocked Wait. Wait now takes ownership via a 'waiting' flag, releases the mutex across the blocking sandlock_handle_wait, and reacquires it to free the handle. The signal helpers act on the process group by PID and touch no handle state, so Kill now interrupts Wait. The FFI handle is not safe for concurrent access, so Ports defers (reports empty) and Close defers the free to Wait while a wait is in flight. - A Process dropped without Wait/Close leaked the handle and orphaned the child. Spawn now installs a runtime finalizer that kills the group and frees the handle; Wait and Close clear it once they have done so. Adds TestProcessKillInterruptsWait. Verified green in a Linux container (Landlock ABI v8): cargo build, go vet/build/test, gofmt.
|
Thanks, both are real. Fixed in 036fd89:
Verified green in a Linux container (Landlock ABI v8): cargo build, go |
| int sandlock_landlock_abi_version(void); | ||
| int sandlock_min_landlock_abi(void); | ||
| int64_t sandlock_syscall_nr(const char* name); | ||
| */ |
There was a problem hiding this comment.
I fixed the sandlock.h generation (PR #87), you can just use the existing sandlock.h:
/*
#cgo CFLAGS: -I${SRCDIR}/../crates/sandlock-ffi/include
#include "sandlock.h"
*/
import "C"PR #87 added a cbindgen-generated header. Include it from the cgo preamble (-I crates/sandlock-ffi/include) and drop the ~110 hand-maintained prototypes, so the bindings track the FFI automatically and can't drift. The generated header types replace the void*/unsigned char placeholders: the builder, policy, handle and result become their named opaque structs, bool setters/getters use C.bool, and the byte-length and change-index params use uintptr_t. Internal pointers are typed accordingly; the public API is unchanged. Verified green in a Linux container against the merged main: cargo build, go vet/build/test, gofmt.
|
Done in e3c7e58 — merged latest #cgo CFLAGS: -I${SRCDIR}/../crates/sandlock-ffi/include
#include "sandlock.h"That drops the ~110 hand-written prototypes, so the bindings can't drift from Verified green against the merged |
Closes the Go-support question in #76.
Per @congwang-mk's guidance there (a real cgo/FFI SDK, placed under
sandlock/go,following the existing FFI + Python binding), this adds a Linux-only Go SDK that
binds the sandlock C ABI (
libsandlock_ffi) via cgo and mirrors the PythonSDK's
Sandboxsurface.What's included
Sandboxmaps every current builder field (filesystem,network, HTTP ACL, resource limits, syscall filtering, determinism,
environment, COW branch handling, uid, …) to the FFI builder.
fs_isolationis intentionally absent, tracking its removal in the core.
Run(captured),RunInteractive(inherited stdio),DryRun(with the filesystem
Changeslist), all taking acontext.Context(adeadline maps to the FFI wait timeout).
Spawnreturns a*ProcesswithPid/Wait/Pause/Resume/Kill/Ports/Close.Confineapplies Landlock rules to the currentprocess — something the CLI fundamentally cannot do.
LandlockABIVersion,MinLandlockABI,SyscallNr.internal/policywith unittests, plus Linux integration tests that
t.Skipwhen the kernel LandlockABI is below the minimum.
gojob that buildslibsandlock_ffiand runsgo vet+go teston the
ubuntu-latestandubuntu-24.04-armrunners.Verification
Built and tested in a Linux container (Landlock ABI v8):
cargo build,go build ./...,go vet ./..., andgo test ./...all pass, exercising areal sandboxed
Run/DryRun. Readable paths in the tests are filtered throughos.Statso the arm64 runner (no/lib64) stays green.Scope / follow-ups
This first PR covers the static policy surface plus
Confine. Deliberately leftfor follow-ups: dynamic
policy_fncallbacks, custom seccomp handlers,pipeline/gather, COW
fork/reduce, andcheckpoint.One upstream note for
policy_fn: the callback typesandlock_policy_fn_tcarries nouser_data. Python works because ctypessynthesises a distinct C function pointer per closure; Go's
//exportyields asingle fixed pointer, so it cannot route the callback to a per-
Sandboxclosurewithout a
void *user_dataparameter onsandlock_sandbox_builder_policy_fn(passed back into the callback). Happy to send that as a small separate PR to
unblock the Go
policy_fnbinding if you're open to it.Open questions
github.com/multikernel/sandlock/go(subdir +go/vX.Y.Ztags). Happy to switch to a separate
sandlock-gorepo if you prefer.../target/release; let meknow if you'd rather standardise on an installed-library path.