Add or extend syscall: $target
You are adding or extending Linux syscall support in gvisor. The target is: $target
Phase 1: Understand the current state
Check the syscall table — search
pkg/sentry/syscalls/linux/linux64.gofor the syscall name. Determine:- Is it registered? (has a table entry)
- What is its support level? (
Supported,PartiallySupported,ErrorWithEvent,Error) - What handler function is it mapped to?
Read the existing implementation (if any) — find the handler in
pkg/sentry/syscalls/linux/sys_*.go. Look for:- Which flags/options are already handled
- Which flags fall through to
default:/ returnEINVAL/ENOSYS - The function signature pattern:
func Name(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error)
Check existing ABI constants — look in
pkg/abi/linux/for the relevant constants file (e.g.,prctl.go,ioctl.go,socket.go)Read the Linux man page / kernel source — use WebSearch or WebFetch to look up the exact Linux behavior for the syscall or flag. This is critical for correctness. Search for the man page (e.g.,
man 2 prctl) and/or relevant kernel source.
Phase 2: Plan the changes
Present the user a summary before writing code:
- What the target syscall/flag does in Linux
- What changes are needed in gvisor (list files)
- Any kernel subsystems that need new support (e.g., new task fields, VFS operations)
- Whether this can be a simple implementation or needs deeper infrastructure
Wait for user confirmation before proceeding.
Phase 3: Implement
Follow this checklist — not all steps apply to every change:
A. ABI Constants (pkg/abi/linux/)
- Add any missing constants to the appropriate file
- Follow the existing naming convention (e.g.,
PR_prefix for prctl,CLONE_for clone flags) - Add a one-line comment matching the Linux kernel comment style:
// PR_FOO does bar. - If adding a new struct, use
pkg/marshalfor user-space copying
B. Syscall Implementation (pkg/sentry/syscalls/linux/sys_*.go)
New syscall: Create
sys_<name>.gowith the handler function. Follow the standard signature:func SyscallName(t *kernel.Task, sysno uintptr, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {New flag on existing syscall: Add a
caseto the existing switch statementUse
linuxerrpackage for errors (e.g.,linuxerr.EINVAL,linuxerr.EPERM)Use
t.CopyIn*/t.CopyOut*orprimitive.Copy*for userspace memory accessUse
hostarch.Addrfor user pointers:args[N].Pointer()Use
args[N].Int(),args[N].Uint(),args[N].Uint64()for scalar arguments
C. Syscall Table (pkg/sentry/syscalls/linux/linux64.go)
- New syscall: Change from
syscalls.ErrorWithEvent(...)orsyscalls.Error(...)tosyscalls.Supported("name", HandlerFunc)orsyscalls.PartiallySupported("name", HandlerFunc, "note about limitations", nil) - Improved existing: Update
PartiallySupportednote to reflect newly supported options, or upgrade toSupportedif fully implemented - The table has entries for both AMD64 and ARM64 — update both if the syscall applies to both architectures
D. BUILD files
- Add new
.gofiles tosrcsinpkg/sentry/syscalls/linux/BUILD - Add any new package dependencies to
deps - Add new ABI files to
pkg/abi/linux/BUILDif created
E. Kernel/Sentry Support (if needed)
- New task-level state →
pkg/sentry/kernel/task.goor related - New filesystem operations →
pkg/sentry/vfs/orpkg/sentry/fsimpl/ - New network features →
pkg/tcpip/
F. Tests (test/syscalls/linux/)
- Add or extend C++ tests in
test/syscalls/linux/<syscall>.cc - Use the gtest framework with gvisor's test utilities:
SyscallSucceeds(),SyscallFailsWithErrno(EINVAL), etc. fromtest/util/test_util.hASSERT_NO_ERRNO_AND_VALUE()for operations that return PosixErrorOr
- Add test to
test/syscalls/linux/BUILDif it's a new file - Test both the happy path and error cases
Phase 4: Test-driven verification against native Linux
The test MUST pass on native Linux first. This ensures the test itself is correct before running it inside gvisor. Native test targets run the C++ test binary directly on the host kernel.
Step 1: Run the native test
The native test target naming convention is:
bazel test //test/syscalls:<syscall>_test_native
For example:
bazel test //test/syscalls:access_test_native
bazel test //test/syscalls:prctl_test_native
bazel test //test/syscalls:eventfd_test_native
The native target is auto-generated by the syscall_test() macro in
test/syscalls/BUILD from the cc_binary in test/syscalls/linux/BUILD. It
runs with --platform=native (directly on the host kernel, no gvisor sandbox).
Run the native test FIRST and iterate until it passes. If the native test fails, the test itself is buggy — fix the test before touching the gvisor implementation.
bazel test //test/syscalls:<syscall>_test_native --test_output=errors
To run a specific test case:
bazel test //test/syscalls:<syscall>_test_native --test_output=errors --test_arg=--gtest_filter='TestSuite.TestCase'
Step 2: Build the gvisor implementation
After the native test passes, verify the gvisor code compiles:
bazel build //pkg/sentry/syscalls/linux/...
If ABI constants changed:
bazel build //pkg/abi/linux/...
Step 3: Run the gvisor (runsc) test
After the native test passes and the gvisor implementation compiles, run the test under gvisor to verify the implementation is correct:
bazel test //test/syscalls:<syscall>_test_runsc_ptrace_shared --test_output=errors
This test MUST pass. If it fails, the gvisor implementation has a bug — fix the implementation and re-run until it passes.
Iterative workflow
The expected loop is:
- Write/update the C++ test in
test/syscalls/linux/<syscall>.cc - Run
bazel test //test/syscalls:<syscall>_test_native --test_output=errors - If it fails → fix the test (it's a bug in the test, not Linux)
- Once native passes → implement/update the gvisor handler
- Build gvisor:
bazel build //pkg/sentry/syscalls/linux/... - Run
bazel test //test/syscalls:<syscall>_test_runsc_ptrace_shared --test_output=errors - If it fails → fix the gvisor implementation and repeat from step 5
Key patterns to follow
Error handling
// Return Linux error codes from linuxerr package
return 0, nil, linuxerr.EINVAL
return 0, nil, linuxerr.EPERM
return 0, nil, linuxerr.ENOSYS
Copying data to/from userspace
// Copy a single int32 to userspace
_, err := primitive.CopyInt32Out(t, args[1].Pointer(), value)
// Copy a struct from userspace
var s SomeStruct
_, err := s.CopyIn(t, args[0].Pointer())
// Copy a string from userspace
name, err := t.CopyInString(addr, maxLen)
Unimplemented options pattern
// For options you deliberately don't implement, emit an event:
t.Kernel().EmitUnimplementedEvent(t, sysno)
return 0, nil, linuxerr.ENOSYS
Credential checks
creds := t.Credentials()
if !creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, creds.UserNamespace) {
return 0, nil, linuxerr.EPERM
}
Important notes
- Match Linux behavior exactly — check the man page and kernel source for edge cases
- gvisor does NOT have a real kernel, so some things (hardware access, kernel modules) cannot be implemented — stub them with appropriate errors
- Always handle the
default:case in switch statements - Use
t.Kernel().EmitUnimplementedEvent(t, sysno)before returning ENOSYS for deliberate non-implementation — this enables tracking of missing features - Prefer returning errors over panicking
- Consider both amd64 and arm64 when the syscall exists on both architectures
Source: google/gvisor — distributed by TomeVault.