Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
311ad21
sandbox2 and deps licenses
valeriy42 Oct 28, 2025
7382f3b
build integration
valeriy42 Oct 28, 2025
2c5775e
Enhance Sandbox2 integration tests and policy handling
valeriy42 Oct 28, 2025
9b8da0f
Refactor Sandbox2 handling in CDetachedProcessSpawner_Linux
valeriy42 Oct 28, 2025
ac67539
Implement Sandbox2 syscall policy for ML processes
valeriy42 Oct 28, 2025
b43e999
formatting
valeriy42 Nov 5, 2025
5cd22f9
fix header
valeriy42 Nov 5, 2025
5c5a2a0
sandbox2 library correctly linked
valeriy42 Nov 6, 2025
0fe86f5
Refactor Sandbox2 policy handling in unit tests
valeriy42 Nov 6, 2025
9dadd42
test 1 passes without sandbox2
valeriy42 Nov 6, 2025
404c6b5
benign model passes. policy very permissive.
valeriy42 Nov 6, 2025
878fadf
evil model generator
valeriy42 Nov 6, 2025
d4adb62
restrict policies.
valeriy42 Nov 6, 2025
d60ba2f
Refactor syscall definitions in CDetachedProcessSpawner_Linux
valeriy42 Nov 6, 2025
fbc3c00
formatting
valeriy42 Nov 6, 2025
9d5d2ba
changelog
valeriy42 Nov 6, 2025
7506aab
patch SAPI cmake file to make Python3 dependency optional
valeriy42 Nov 6, 2025
6f49269
Merge branch 'enhancement/sandbox2' of https://github.com/valeriy42/m…
valeriy42 Nov 6, 2025
ac26ca6
fix python checks on CI
valeriy42 Nov 7, 2025
ec5cde2
Remove Sandbox2 security tests and add CDetachedProcessSpawner tests …
valeriy42 Nov 7, 2025
03b1ee4
Add syscall allowance for prlimit64 in sandbox policy
valeriy42 Nov 20, 2025
b93dfec
formatting
valeriy42 Nov 21, 2025
0594140
adjust for arm64
valeriy42 Nov 21, 2025
c77ebb6
python integration test simulation works
valeriy42 Nov 27, 2025
fa8f99d
java IT works again
valeriy42 Dec 3, 2025
8dc9905
formatting
valeriy42 Dec 9, 2025
d853920
Fix CI build failures
valeriy42 Dec 9, 2025
4f69cd9
Merge remote-tracking branch 'upstream/main' into enhancement/sandbox2
valeriy42 Jul 17, 2026
ec7d3ed
[ML] Allow legacy file-system syscalls in pytorch_inference sandbox p…
valeriy42 Jul 17, 2026
a6a7ecd
Update docs/changelog/2873.yaml
valeriy42 Jul 17, 2026
ee0bcb3
[ML] Disable unity build for vendored Abseil / Sandboxed API
valeriy42 Jul 17, 2026
f5d6fc2
[ML] Fix sandboxed pytorch_inference named-pipe setup on long CI paths
valeriy42 Jul 17, 2026
62ea65b
[ML] Add Sandbox2 diagnostics for pytorch_inference pipe-visibility f…
valeriy42 Jul 18, 2026
cce88ce
[ML] Fix diagnostics: stop redirecting the controller's stderr
valeriy42 Jul 18, 2026
e15a5e1
[ML] Log Sandbox2 AwaitResult for pytorch_inference diagnosis
valeriy42 Jul 18, 2026
27b8d73
[ML] TEMP: narrow multi-node ES ITs to *PyTorchModelIT for faster San…
valeriy42 Jul 18, 2026
e582cd9
[ML] TEMP: force minimal x86_64 PR pipeline for Sandbox2 debugging
valeriy42 Jul 18, 2026
57f00ed
[ML] Allow clone3 by number so pytorch_inference threads on CI
valeriy42 Jul 18, 2026
a190c3e
[ML] Remove Sandbox2 debugging scaffolding and restore full CI
valeriy42 Jul 18, 2026
0a56df5
[ML] Fix Sandbox2 spawner allowlist, PID tracking, and termination lo…
valeriy42 Jul 19, 2026
d9a856d
[ML] Allow glibc futex ops needed for concurrent pytorch_inference
valeriy42 Jul 20, 2026
569f8cc
[ML] Disarm Sandbox2 wall-time/CPU limits for the long-lived pytorch_…
valeriy42 Jul 21, 2026
0d89fc4
[ML] Fix clang-format violations in Sandbox2 spawner and test
valeriy42 Jul 21, 2026
facd362
formatting
valeriy42 Jul 21, 2026
39940cb
[ML] Fix Sandbox2 spawner tracking, seccomp gate, and attack tests
valeriy42 Jul 22, 2026
ff0349c
formatting
valeriy42 Jul 22, 2026
339d3f5
[ML] Remove Sandbox2 attack-defense test from Docker CI
valeriy42 Jul 22, 2026
69dea1b
[ML] Harden Sandbox2 spawner after code review follow-ups
valeriy42 Jul 22, 2026
786cdeb
[ML] Improve Sandbox2 production observability and logging
valeriy42 Jul 23, 2026
ef21959
[ML] Honour --disableSandbox kill switch in pytorch_inference spawner
valeriy42 Jul 23, 2026
63d8850
[ML] Correct Main.cc comment about pytorch_inference sandbox routing
valeriy42 Jul 23, 2026
634182f
[ML] Strip --disableSandbox on non-Linux spawners
valeriy42 Jul 23, 2026
f153159
[ML] Point Sandbox2 startup failures at sandbox_enabled kill switch
valeriy42 Jul 23, 2026
7c9623c
formatting
valeriy42 Jul 23, 2026
6fcdecd
[ML] Fix unit test failures from spawn failure reason and seccomp tes…
valeriy42 Jul 27, 2026
ab46fc8
Merge branch 'main' of https://github.com/elastic/ml-cpp into enhance…
valeriy42 Jul 28, 2026
3dc4a56
[ML] Relocate Sandbox2 to MlSandbox and address PR #2873 review
valeriy42 Aug 19, 2026
3d96e50
formatting
valeriy42 Aug 19, 2026
6f57661
[ML] Address Jan's PR #2873 review nits
valeriy42 Aug 19, 2026
bf83cda
formatting
valeriy42 Aug 19, 2026
f0bdcf6
[ML] Fix Sandbox2 CI: set ML_SANDBOX2_EXPECT, link zlib, add Windows …
valeriy42 Aug 19, 2026
eeb52ab
[ML] Fix Sandbox2 CI regressions from MlSandbox restructure
valeriy42 Aug 19, 2026
45c1dae
[ML] Fix Sandbox2 zlib link without touching sandbox2 ALIAS
valeriy42 Aug 19, 2026
730933d
[ML] Fix Sandbox2 CI: zlib link order, probe mount, test bundle
valeriy42 Aug 20, 2026
f8b0a53
[ML] Fix aarch64 host sandbox re-run missing Boost SONAMEs
valeriy42 Aug 20, 2026
f1c938a
[ML] Fix aarch64 host sandbox re-run missing GCC 13 libstdc++
valeriy42 Aug 20, 2026
92016a5
[ML] Remove dead build_and_test.sh CI script
valeriy42 Aug 20, 2026
90dd9ca
[ML] Run aarch64 enforced Sandbox2 tests inside build image
valeriy42 Aug 20, 2026
fec7e1e
[ML] Escalate aarch64 enforced Sandbox2 docker run to --privileged
valeriy42 Aug 20, 2026
b1dadfe
[ML] Bundle gcc133 sysroot for aarch64 host enforced Sandbox2
valeriy42 Aug 20, 2026
181caa9
[ML] Diagnose Sandbox2 userns denial and dispatch coverage on it
valeriy42 Sep 3, 2026
4439aba
[ML] TEMPORARY: trim PR CI to Linux aarch64 while debugging Sandbox2
valeriy42 Sep 3, 2026
50bacc2
[ML] Fix the userns probe: mount(proc) also needs CLONE_NEWPID
valeriy42 Sep 3, 2026
4e29545
[ML] Pin enforced Sandbox2 coverage to the aarch64 host runner
valeriy42 Sep 3, 2026
781c044
[ML] Set CPP_SRC_HOME for the aarch64 host sandbox re-run
valeriy42 Sep 3, 2026
e88a112
Revert "[ML] TEMPORARY: trim PR CI to Linux aarch64 while debugging S…
valeriy42 Sep 3, 2026
15db7e3
[ML] Keep the userns diagnostic as a regression canary
valeriy42 Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions .buildkite/pipelines/build_linux.json.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,25 @@ def main(args):
],
})
else:
# Regression canary for the enforced Sandbox2 coverage pinned in
# run_tests.sh: reports which user-namespace stage this agent
# denies, if any. Kept because that pin fails the build when the
# agent loses namespace support, and this step is what says which
# stage broke - the alternative is another round of guessing.
# Needs no build artifacts, hence no depends_on: it answers in
# ~1 minute rather than behind the 45 minute compile. soft_fail
# because it is instrumentation, not a gate.
pipeline_steps.append({
"label": f"Diagnose user namespaces for linux-{arch} :linux:",
"timeout_in_minutes": "15",
"agents": test_agents[arch],
"commands": [
".buildkite/scripts/steps/diagnose_userns.sh"
],
"key": f"diagnose_userns_linux-{arch}",
"soft_fail": True,
})

# aarch64: split into build and test steps
build_key = f"build_test_linux-{arch}-{build_type}"

Expand Down Expand Up @@ -218,8 +237,12 @@ def main(args):
],
})

# Add debug build/test steps for PR builds to detect compilation errors with optimization disabled
if os.environ.get("BUILDKITE_PIPELINE_SLUG", "ml-cpp-pr-builds") != "ml-cpp-debug-build" and \
# Add debug build/test steps for PR builds to detect compilation errors with
# optimization disabled. These are x86_64 steps, so they only belong in the
# pipeline when x86_64 was actually requested - an aarch64-only build must
# not drag an x86_64 pair along with it.
if args.build_x86_64 and \
os.environ.get("BUILDKITE_PIPELINE_SLUG", "ml-cpp-pr-builds") != "ml-cpp-debug-build" and \
os.environ.get("BUILDKITE_PULL_REQUEST", "false") != "false" and \
not should_skip_version_bump_pr_ci():
debug_build_key = "build_test_linux-x86_64-RelWithDebInfo-debug"
Expand Down
29 changes: 27 additions & 2 deletions .buildkite/scripts/steps/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,31 @@ if [[ "$HARDWARE_ARCH" = aarch64 && -z "${CPP_CROSS_COMPILE:-}" && "$(uname)" =
' | tar xf -

docker run --rm --workdir=/ml-cpp $TEMP_TAG bash -c '
set -e
# Complete GCC133/Boost toolchain runtime for the *host* enforced
# sandbox re-run. Do not enumerate individual SONAMEs — copying the
# whole gcc133 lib{,64} tree is the point (libstdc++, libgcc_s,
# libgomp, all Boost SONAMEs, and anything else the toolchain ships).
# Flatten into one sysroot so a single LD_LIBRARY_PATH entry beats
# the AlmaLinux 8 agent /lib64.
mkdir -p cmake-build-docker/lib/sysroot
if [ -d /usr/local/gcc133/lib64 ]; then
cp -a /usr/local/gcc133/lib64/. cmake-build-docker/lib/sysroot/
fi
if [ -d /usr/local/gcc133/lib ]; then
cp -a /usr/local/gcc133/lib/. cmake-build-docker/lib/sysroot/
fi
if [ ! -d /usr/local/gcc133/lib64 ] && [ ! -d /usr/local/gcc133/lib ]; then
echo "error: /usr/local/gcc133/lib{,64} missing from build image" >&2
exit 1
fi
{
find cmake-build-docker/test -name "ml_test_*" -type f -executable 2>/dev/null
find cmake-build-docker/lib -name "*.so" 2>/dev/null
find build/distribution -name "*.so" -not -path "*.debug*" 2>/dev/null
find cmake-build-docker/lib \( -name "*.so" -o -name "*.so.*" \) 2>/dev/null
find build/distribution \( -name "*.so" -o -name "*.so.*" \) -not -path "*.debug*" 2>/dev/null
# Sandbox2 spawn tests need the installed pytorch_inference binary.
find build/distribution -type f -path "*/bin/pytorch_inference" 2>/dev/null
find cmake-build-docker/lib/sysroot 2>/dev/null
} | sort -u > /tmp/bundle-files.txt
echo "Files in bundle: $(wc -l < /tmp/bundle-files.txt)" >&2
tar czf - -T /tmp/bundle-files.txt
Expand Down Expand Up @@ -109,6 +130,8 @@ elif [[ "$(uname)" = "Linux" ]]; then
find ${BUILD_DIR}/lib -name "*.so" -o -name "*.dylib" 2>/dev/null
if [ -d "build/distribution" ]; then
find build/distribution -type f \( -name "*.so" -o -name "*.dylib" \) -not -path "*.dSYM*" 2>/dev/null
# Sandbox2 spawn tests need the installed pytorch_inference binary.
find build/distribution -type f -path "*/bin/pytorch_inference" 2>/dev/null
fi
} | sort -u > /tmp/test-bundle-files.txt

Expand Down Expand Up @@ -151,6 +174,8 @@ else
find ${BUILD_DIR}/lib -name "*.so" -o -name "*.dylib" 2>/dev/null
if [ -d "build/distribution" ]; then
find build/distribution -type f \( -name "*.so" -o -name "*.dylib" \) -not -path "*.dSYM*" 2>/dev/null
# Sandbox2 spawn tests need the installed pytorch_inference binary.
find build/distribution -type f -path "*/bin/pytorch_inference" 2>/dev/null
fi
} | sort -u > /tmp/test-bundle-files.txt

Expand Down
131 changes: 0 additions & 131 deletions .buildkite/scripts/steps/build_and_test.sh

This file was deleted.

72 changes: 72 additions & 0 deletions .buildkite/scripts/steps/diagnose_userns.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
# or more contributor license agreements. Licensed under the Elastic License
# 2.0 and the following additional limitation. Functionality enabled by the
# files subject to the Elastic License 2.0 may only be used in production when
# invoked by an Elasticsearch process with a license key installed that permits
# use of machine learning features. You may not use this file except in
# compliance with the Elastic License 2.0 and the foregoing additional
# limitation.

# Reports which runner on this agent, if any, can create the user namespace
# Sandbox2 needs - and for the ones that cannot, the exact stage the kernel
# denies.
#
# Sandbox2 requires all of: unshare(CLONE_NEWUSER), a uid/gid mapping,
# unshare(CLONE_NEWNS), a private "/", and a fresh mount of procfs. Any one can
# be denied independently and the failures are indistinguishable from outside,
# which is why userns_probe.sh reports them stage by stage in each runner.
#
# Runs the same probe on the host and under several container configurations,
# because the blocker differs between them: Docker's default seccomp profile
# denies unshare(CLONE_NEWUSER) outright, while a mount covering /proc blocks
# only the final stage. The point is to find one runner where every stage passes,
# which is where ML_SANDBOX2_REQUIRE=enforced can then be set.
#
# Needs no build artifacts, so it runs with no depends_on and answers in about a
# minute instead of waiting behind a 45 minute compile. Never fails the step:
# this is instrumentation, not a gate.

set -uo pipefail

MY_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
PROBE="${MY_DIR}/userns_probe.sh"
BASE_IMAGE="${SANDBOX2_DIAGNOSE_IMAGE:-docker.elastic.co/ml-dev/ml-linux-aarch64-native-build:17}"

echo "--- Sandbox2 userns diagnosis: host"
bash "$PROBE" host

if ! command -v docker >/dev/null 2>&1; then
echo "warning: docker not on PATH; skipping container probes"
echo "--- Sandbox2 userns diagnosis: done"
exit 0
fi

if ! docker pull "$BASE_IMAGE" >/dev/null 2>&1; then
echo "warning: could not pull ${BASE_IMAGE}; skipping container probes"
echo "--- Sandbox2 userns diagnosis: done"
exit 0
fi

# label@@docker flags. Ordered least to most privileged: the cheapest
# configuration that clears every stage is the one to adopt, since each
# escalation widens what a compromised test process could reach.
RUNNERS=(
"docker@@"
"docker+seccomp-unconfined@@--security-opt seccomp=unconfined"
"docker+seccomp+systempaths@@--security-opt seccomp=unconfined --security-opt systempaths=unconfined"
"docker-privileged@@--privileged"
"docker-privileged+systempaths@@--privileged --security-opt systempaths=unconfined"
)

for runner in "${RUNNERS[@]}"; do
label="${runner%%@@*}"
flags="${runner#*@@}"
echo "--- Sandbox2 userns diagnosis: ${label}"
# shellcheck disable=SC2086 - flags must word-split into separate arguments.
docker run --rm -i $flags "$BASE_IMAGE" bash -s "$label" < "$PROBE" ||
echo "[${label}] container probe could not run (exit $?)"
done

echo "--- Sandbox2 userns diagnosis: done"
exit 0
46 changes: 46 additions & 0 deletions .buildkite/scripts/steps/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,46 @@ if [[ "$HARDWARE_ARCH" = aarch64 && -z "${CPP_CROSS_COMPILE:-}" && "$(uname)" =
# Seccomp tests run inside the Docker container which shares the host
# kernel, so the kernel's seccomp filters are exercised without needing
# a separate outside-Docker run.
#
# The container above is left unpinned deliberately. Docker's default seccomp
# profile denies unshare(CLONE_NEWUSER), so the sandbox tests select
# fail-closed coverage there - which is coverage we want, and which no other
# runner on this agent provides.

# The host is the runner that can do the other half. Measured by
# diagnose_userns.sh on this agent (core-almalinux-8-aarch64, kernel 4.18):
#
# host all stages OK
# docker (default) denied at unshare(CLONE_NEWUSER)
# docker + seccomp=unconfined denied at mount(proc), masked /proc paths
# docker + seccomp + systempaths all stages OK
# docker --privileged all stages OK
#
# The host needs no privilege escalation at all, so it is where the enforced
# coverage belongs. Pinned, because these two test cases - a real sandboxed
# spawn and the filesystem-policy differential - are the security-critical
# ones, and a silent downgrade to fail-closed coverage must fail the build
# rather than pass quietly. Runs against the bundled gcc133 sysroot so the
# binary does not resolve against AlmaLinux 8 /lib64.
if [[ $TEST_OUTCOME -eq 0 ]]; then
echo "--- Re-running sandbox unit tests on host (enforced)"
REPO_ROOT_ABS="$(pwd)"
SYSROOT="$(pwd)/${BUILD_DIR}/lib/sysroot"
LIB_DIRS=$(find "$(pwd)/${BUILD_DIR}/lib" "$(pwd)/build/distribution" \
\( -name "*.so" -o -name "*.so.*" \) \
-exec dirname {} \; 2>/dev/null | sort -u | tr '\n' ':')
# CPP_SRC_HOME must be set: CResourceLocator::cppRootDir() otherwise
# falls back to "../../.." on the assumption that the cwd is a source
# unittest directory, whereas this runs from the build tree - so the
# spawn test looked for pytorch_inference under cmake-build-docker/ and
# did not find it. set_env.sh exports it inside the container; nothing
# does on the host.
(cd "${REPO_ROOT_ABS}/${BUILD_DIR}/test/lib/sandbox/unittest" && \
CPP_SRC_HOME="${REPO_ROOT_ABS}" \
ML_SANDBOX2_REQUIRE="${ML_SANDBOX2_HOST_REQUIRE:-enforced}" \
LD_LIBRARY_PATH="${SYSROOT}:${LIB_DIRS}" \
./ml_test_sandbox) || TEST_OUTCOME=$?
fi

else
# --- Linux x86_64 / macOS: run tests directly ---
Expand All @@ -98,6 +138,12 @@ else
export DYLD_LIBRARY_PATH="${LIB_DIRS}${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
fi

# Linux x86_64 PR agents are Buildkite k8s pods: user namespaces often work
# but Sandbox2's mount("proc", "/proc", "proc", ...) returns EPERM, so the
# sandbox tests self-select fail-closed coverage here. Nothing is pinned -
# these pods' namespace support is a property of the k8s runtime, not of this
# repository. macOS has no CSandboxedProcessSpawnerTest_Linux.cc at all.

echo "--- Running tests"
cmake \
-DSOURCE_DIR="$(pwd)" \
Expand Down
Loading