tools/sandbox: update with new features, variants, and tests - #3566
tools/sandbox: update with new features, variants, and tests#3566gzvolsky-tm wants to merge 6 commits into
Conversation
This commit brings several enhancements, reliability improvements, and tests to the sandbox: - Add support for `SANDBOX_UID_MAP` and `SANDBOX_GID_MAP` environment variables. This allows mapping multiple UID/GID ranges into the user namespace utilizing `newuidmap` and `newgidmap`. - Add `noproc_sandbox` and `nonetproc_sandbox` which skip remounting `/proc`. This provides a workaround for environments (like newer systemd or certain container runtimes) that prevent remounting a partially masked `/proc`. - Introduce `SANDBOX_FILE_MOUNTS` to allow bind-mounting a comma-separated list of specific files into the sandbox. - The loopback IP address can now be specified via `SANDBOX_LOCAL_IP`. - Introduce a pipe (`sync_fd`) to synchronize the parent and child processes. This prevents the child from hanging indefinitely if the parent dies before the sandbox setup is complete. - Add `SIGTERM` forwarding from the parent to the sandboxed child process. - Add a README documenting the sandbox, its variants, and configurable environment variables. - Add a `sandbox_test.go` test suite to test the network/mount isolation, ID mapping, and process lifecycle across all sandbox variants.
|
Maybe let's think about what the structure of these binaries should look like. I'm not sure that it's very optimal to ship multiple different ones with Please for the different combinations of things that it could sandbox - maybe that should be some flag / env var thing instead on a single binary. |
…ailures Backward compatibility: - add_local_ip: add 10.1.1.1 to the loopback interface by default again, for backward compatibility with the previous upstream behaviour (#3310). SANDBOX_LOCAL_IP now overrides the address, and setting it to empty string disables it. Build fixes: - Fix test name clash by renaming sandbox_test to sandbox_go_test - Use the name `please_sandbox` consistently - Fix Darwin and FreeBSD builds by updating the contain() stub and moving <string.h> back out of the #ifdef __linux__ block. - Restore static = (CONFIG.get("STATIC_SANDBOX") is not None) so the alpine release links the sandboxes statically (defined in .plzconfig.alpine). - Drop inline from perror_sock: the inline definition without static emits no external symbol, breaking -O0 links. Tests: - Keep SANDBOX_UID_MAP/SANDBOX_GID_MAP outside ids below 65536 so the mappings work in constrained container uid spaces. - Assert network isolation via curl's exit code (7, couldn't connect) instead of error text (wording changed in curl 8). - Install uidmap (shadow-uidmap on alpine) in the CI images and delegate root:0:65536 in /etc/subuid and /etc/subgid, since the sandbox execs newuidmap/newgidmap, which check subordinate id delegation even for root.
Move the sandbox Go test into its own directory to avoid including adjacent C files in the Go package. Fixes ``` level=error msg="[linters_context] typechecking error: C source files not allowed when not using cgo or SWIG: main.c nonet_main.c nonetproc_main.c noproc_main.c sandbox.c" ```
- Restore SIGKILL as the parent-death signal. The sandboxed command is PID 1 in the new namespace, so the kernel discards SIGTERM and the command leaks as an orphan process when the sandbox crashes. - map_ids: return an error when waitpid fails instead of reading an uninitialised status. - Tolerate an absent /dev/shm. - Require SANDBOX_UID_MAP and SANDBOX_GID_MAP to be set together. The other options, defaulting to a trivial mapping or mirroring the other mapping, made an assumption that isn't always correct. - Fix the TMP_DIR-under-/tmp check to exclude `/tmp[^/]+`. - Exec new*idmap via PATH search. This is safe because the sandbox binary itself runs with neither setuid bits nor file capabilities. - Fix fmt.Sprintf lint errors.
f6d40cf to
151a097
Compare
- Remove `nonet_sandbox`, `noproc_sandbox`, and `nonetproc_sandbox`. Under local execution Please sets `SHARE_NETWORK` and `SHARE_MOUNT` explicitly on every sandboxed invocation, so the variants' flipped defaults only took effect where something else invoked them, e.g. remote execution workers. - Add `MOUNT_PROC=0` option to `please_sandbox` to allow disabling `/proc` remounting. - Add README section explaining how to use the environment variables with Please. - Refactor per-binary test matrices whose premises were per-variant defaults and multiple binaries. BREAKING CHANGE: Removing `nonet_sandbox` is a breaking change for downstream repositories that referenced it, and for remote execution workers that invoked a variant binary and relied on its defaults. The migration path is to use a wrapper that defines the environment (see tools/sandbox/README.md).
88fcb15 to
8807a2a
Compare
| @@ -0,0 +1,15 @@ | |||
| go_test( | |||
There was a problem hiding this comment.
Why put the tests in a subdirectory?
There was a problem hiding this comment.
We got this lint error when the C and Go files were colocated:
level=error msg="[linters_context] typechecking error: C source files not allowed when not using cgo or SWIG: main.c nonet_main.c nonetproc_main.c noproc_main.c sandbox.c"
|
|
||
| static int cloned_pid; | ||
|
|
||
| int perror_sock(char *errmsg, const int sock) { |
There was a problem hiding this comment.
| int perror_sock(char *errmsg, const int sock) { | |
| inline int perror_sock(char *errmsg, const int sock) { |
There was a problem hiding this comment.
The inline was removed here: 5b8d710 . I assume it was removed because the keyword has different meanings C/C++ and based on whether it's just inline, inline static, or inline extern, and removing it made the code more readable than changing it to inline static.
| labels = [ | ||
| "localonly", | ||
| "manual", | ||
| ], |
There was a problem hiding this comment.
Presumably these tests aren't currently being run by CircleCI? Can we remove these labels? Do we need to make changes to the CircleCI config?
There was a problem hiding this comment.
If I removed manual right now, it would be executed in this step: https://app.circleci.com/pipelines/github/thought-machine/please/7814/workflows/d70458df-9432-4518-bcf0-06c5bde6eec3/jobs/63145/parallel-runs/0/steps/0-105 .
I would like to remove it and see whether it passes in CI once we update the CI image references to a version that includes the dependencies added in this PR. It does pass when run locally in the images defined here.
There was a problem hiding this comment.
Let's split out the image changes to a separate PR and get that landed first?
There was a problem hiding this comment.
Happy to split the image changes if you'd prefer, but while testing them I found that they're no longer a pre-requisite: the sandbox tests can't run with the docker executor that is used by the current jobs (mount syscall is blocked and CircleCI doesn't expose configuration to allow it). To work around this, I updated this PR with new jobs that run the tests using machine executors and added them to the release requirements. See example run from the latest commit. The jobs currently apply the image changes at container startup. Once the images are updated, these steps can be removed (tracked in #3571), but the rest of this setup will stay.
peterebden
left a comment
There was a problem hiding this comment.
This is good by me, although I like Sam's idea of splitting out the image changes so the tests will work.
| In addition to the above, when enabling the mount namespace, the sandbox will remount /proc, so any | ||
| process that use it will have access to accurate information of the PID namespace (otherwise they'd | ||
| still have access to /proc from the parent namespace). There's however a specific edge case in the | ||
| Linux kernel, that prevents /proc from being remounted in a mount namespace, when the parent /proc | ||
| is not fully visible, [see this commit](https://github.com/torvalds/linux/commit/1b852bceb0d1). | ||
|
|
||
| This is an issue with most container runtimes (and therefore Kubernetes), as by default, they will | ||
| hide some part of /proc in a container to reduce attack surface, eg | ||
| [see this docker PR](https://github.com/docker/cli/pull/1808). |
There was a problem hiding this comment.
I'm a little unclear what the conclusion of this section is? I remember the issue (remember bashing my head against it for a while) but is this something the sandbox works around, is it just a limitation of cases where it doesn't work, or do we intend to try to fix it in the future?
There was a problem hiding this comment.
The conclusion is that users of the sandbox have two options: Either unmask proc (k8s pod procMount: Unmasked securityContext option, which is less of an issue when combined with hostUsers: false), or disable the /proc isolation, in which case the sandboxed process will be able to see (but not send signals to) processes outside the sandbox. I'll update the README to mention this.
- Run the sandbox tests on machine executors via rootless podman. The
docker executor was not sufficient ("remount: Permission denied"),
likely due to apparmor settings that are not configurable.
//tools/sandbox/test:sandbox_test therefore remains manual.
- Make release-gs conditional on the new sandbox tests
- Update README
- TODO: Remove container setup steps once the new images are tagged: #3571
This PR brings several enhancements, reliability improvements, and tests to the sandbox:
SANDBOX_UID_MAPandSANDBOX_GID_MAPenvironment variables. This allows mapping multiple UID/GID ranges into the user namespace utilizingnewuidmapandnewgidmap. Both must be set together.MOUNT_PROC=0to skip remounting/proc. This provides a workaround for environments (like newer systemd or certain container runtimes) that prevent remounting a partially masked/proc.SANDBOX_FILE_MOUNTSto allow bind-mounting a comma-separated list of specific files into the sandbox.10.1.1.1, added in Add local IP and default gateway to network for sandbox tool #3310) can now be overridden viaSANDBOX_LOCAL_IP, or disabled by setting it to the empty string./dev/shmso nothing can be inadvertently shared through it.sync_fd) to synchronize the parent and child processes. This prevents the child from hanging indefinitely if the parent dies before the sandbox setup is complete.SIGTERMforwarding from the parent to the sandboxed child process.sandbox_test.gotest suite to test the network/mount isolation,/procremounting, ID mapping, and process lifecycle across all combinations of the environment knobs.manual/localonlyuntil the images are updated.Behaviour changes
/procis now mounted read-only. This may affect tests that write to it, e.g. to/proc/self/oom_score_adj.TMP_DIRbind mount is now recursive (MS_REC), so submounts ofTMP_DIRare visible inside the sandbox.TMP_DIRis/tmpor lives under it, the sandbox no longer returns early: it still bind-mounts it to/tmp/plz_sandbox, remounts the root read-only, and changes into it.nosuid/nodev/noexec/noatimemount flags instead of silently dropping them.Breaking change
nonet_sandboxis a breaking change for downstream repositories that referenced it, and for remote execution workers that invoked a variant binary and relied on its defaults. The migration path is to use a wrapper that defines the environment (see tools/sandbox/README.md).Resolves #3541