Adds --use_core_scheduling proto flag that calls prctl(PR_SCHED_CORE_CREATE) in the child process, giving each jail its own SMT scheduling group. Prevents sharing a physical core with other tenants and mitigates L1TF/MDS side-channel attacks in multi-tenant environments. Requires Linux >= 5.14; guarded with #ifdef PR_SCHED_CORE for compatibility with older kernel headers.
The protobuf API marks PrintToString as nodiscard, so ignoring its
return value broke the build under -Werror=unused-result. Check the
result and bail out with a warning if formatting fails.
setupArgv() unconditionally called set_path(argv[optind]) when positional
arguments were present after --, clobbering any path previously set via
the -x / --exec_file flag. The intended behavior, documented in the help
text ("File to exec (default: argv[0])"), is for --exec_file to win and
fall back to argv[0] only when not given.
Remove the unconditional set_path; the existing fallback a few lines
below already sets the path from argv[0] when --exec_file was not
provided.
ENOENT means the thread has moved on (killed or interrupted).
EINPROGRESS indicates either misuse by sending again after
a successful send (which we're not doing) but we also see it
when a process is exiting, perhaps related to thread shutdown.
Authored-by: rsc@google.com
Tested-by: rsc@google.com
Most of the system calls involved in the unotify poll loop were subtly wrong.
The biggest problem was the reuse of the previous message's req.id with
isTargetAlive to decide whether to exit the loop. The loop can be watching
many threads and many processes, any one of which might exit at any time
(especially if the last observed system call was exit!), so it does not make
sense to focus on a specific thread except within the context of a pending
syscall being evaluated.
SECCOMP_IOCTL_NOTIF_ID_VALID is for one purpose and one purpose only:
checking after an access by pid that the pid was not reused, invalidating
whatever was just read. The only time that purpose applies is between
parseSyscall and addStat, which is now the only time that the loop calls
isTargetAlive.
The loop was misusing isTargetAlive to decide when to exit the loop.
Now it correctly waits until a POLLHUP event.
The loop was misusing isTargetAlive to decide whether to call
SECCOMP_IOCTL_NOTIF_SEND. That's going to do the same check itself,
because otherwise there would be a race between the check and the send.
That redundant check is deleted.
The loop was also misusing isTargetAlive after a failed
SECCOMP_IOCTL_NOTIF_SEND to decide whether to exit the loop.
As before, one target being dead does not imply that all the traced targets
are dead, so that check is deleted. On failure, if the error is EINTR,
the send is tried again. If the error is ENOENT, that means the thread is
no longer blocked in the system call (either due to an interrupt or signal),
so we stop the send attempt but continue the processing loop.
Otherwise we print the error error, stop the send, but continue the processing loop.
The hangs seem to have been caused by a few different failure modes:
1. The loop could have been exiting prematurely, hanging the syscalls
waiting for judgement.
2. In one strace log I read carefully, the loop kept running after POLLHUP,
hanging in SECCOMP_IOCTL_NOTIF_RECV. I believe this may have been
fixed between 6.6 and 6.12.
3. The loop was not reacting well at all to send failing because the syscall
had been interrupted. This manifested as needing to run Go programs
with GODEBUG=asyncpreemptoff=1 to let them run at all.
With these changes, Go programs using signals work just fine.
Authored-by: rsc@google.com
Tested-by: rsc@google.com
initParent registers a defer{nl_cache_free(link_cache)} RAII guard that
runs on all exit paths, but the error returns inside the iface_own loop
and the cloneIface block also called nl_cache_free explicitly, causing
a double-free when moveToNs or cloneIface fails.
Remove the redundant explicit frees; the defer guard is sufficient.
When nsjail creates a new process in a new PID namespace (CLONE_NEWPID) using the direct kernel syscall clone/clone3 (introduced in d1f332b), glibc's internal PID/TID cache is not updated for the child process.
As a result, calling the glibc wrapper `sched_setaffinity(0, ...)` inside the child process causes glibc to inadvertently pass the cached parent's TID to the kernel instead of 0 (current thread). Since the parent's TID does not exist within the new PID namespace, the kernel returns ESRCH (No such process).
This commit fixes the issue by bypassing the glibc wrapper and invoking the `sched_setaffinity` syscall directly via `util::syscall`. This ensures that `0` is passed accurately to the kernel, referring to the current thread.
* Introduces a dedicated, async worker thread that monitors SECCOMP_RET_USER_NOTIF events
* Produces telemetry using protobufs to track resolved namespace paths, and network endpoints
- Migrate TCP/UDP flow management to std::unique_ptr and packet parsing to std::span
- Add mandatory checksum validation for UDP (IPv4 optional, IPv6 per RFC 8200), closing parity with TCP/ICMP
- Handle IPv6 Authentication Header (AH) in extension header parser to prevent firewall rule bypass
- Add defense-in-depth MTU cap in tcp_process_data to prevent int32_t overflow in sequence arithmetic
- Fix uint16_t port loop overflow in HOST_TO_GUEST listener setup (infinite loop when dport_end=65535)
- Block SSRF via forged loopback/v4mapped destinations in both IPv4 and IPv6 TCP/UDP paths
- Extract policy evaluation and proxy encapsulation into standalone policy.cc and encap.cc modules
- Replace all raw inet_ntop+char[] patterns with ip4_to_string/ip6_to_string helpers