Skip to content

[Bubblewrap] Fail the run when the sandbox loses its network provider - #1250

Merged
Soham Das (SohamDas2021) merged 2 commits into
mainfrom
sohamdas2021-bwrap-slirp-liveness
Sep 23, 2026
Merged

Soham Das (SohamDas2021) merged 2 commits into
mainfrom
sohamdas2021-bwrap-slirp-liveness

Conversation

@SohamDas2021

@SohamDas2021 Soham Das (SohamDas2021) commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

📖 Description

Bubblewrap's private-namespace modes give the workload a network namespace whose only route is slirp4netns. Its liveness was checked once during startup and never again, so a slirp that died under a running workload left the sandbox running against a dead network. Every connection failed with a generic transport error, and the run was attributed to whatever the workload reported — the real cause reached the log only at teardown, as a WARNING:.

Losing the connectivity provider now fails the run. Two checks:

Before the workload starts. Readiness is latched rather than revoked, so it can already be stale by the time the startup gate opens. The supervisor is re-checked immediately before release. Any exit fails the run, including a successful one — slirp's exit code says nothing about whether the sandbox still has a route.

For the lifetime of the workload. The supervisor holds the write end of a pipe nothing ever writes to, and slirp inherits it. The runner keeps the read end and watches it from a monitor thread. It reaches EOF when both have exited, which is what separates a dead network from an orphaned slirp still carrying traffic after its supervisor was killed. On EOF the sandbox is terminated and the run fails naming the supervisor's exit status and a bounded tail of its stderr:

wait failed: Bubblewrap: the sandbox lost its network provider while the workload was running; the proxy network supervisor exited with exit status: 137 (stderr: sent tapfd=7 for tap0 received tapfd=7 Killed)

The monitor is disarmed before teardown stops the supervisor, so an ordinary shutdown — which closes the same descriptor — is never reported as a loss.

This is a diagnosability and reliability fix, not a containment one: a dead slirp removes connectivity rather than granting it, and the egress rules live in the sandbox's own namespace, so they stay in force. It also covers enforcementMode: "firewall", which stands up the same supervisor and the same slirp.

Limitations. Detection is exit-based. A slirp that is alive but wedged still holds its descriptor open and reaches the workload as an unreachable network. The monitor costs ~0.1% of one core (measured: 0.45 µs per poll; the rest is timer wakeup).

🔗 References

Resolves #935

🔍 Validation

New E2E test run_bwrap_provider_loss_test.sh kills the run's own slirp4netns under a live workload and asserts the run fails naming the provider, and that the workload does not outlive it.

The test was confirmed to catch the bug. Reverted against the same test, the sandbox ran the full 90-second timeout against a dead network and reported only script timed out after 90000ms — the behavior described in the issue.

Run on WSL2 Ubuntu 24.04:

  • Unit: 349 pass (3 new). Two pre-existing failures in bwrap_version / probe_exec also fail on unmodified main — a DrvFs artifact where spawning a missing path under /mnt/c returns EACCES instead of ENOENT.
  • E2E, all sudo-free suites: provider_loss, teardown, network_proxy, firewall, directional, localnet, network, basic, readonly_denial, version_gate, linux_process_default — 10 pass, 1 skip (inbound_deny, root-only).
  • cargo clippy -p bwrap_common --all-targets -- -D warnings, cargo fmt --check, and validate-configs.js (375 configs) all clean.
  • Downstream mxc_engine / mxc-sdk / wxc_common suites pass.

✅ Checklist

📋 Issue Type

  • Bug fix
  • Feature
  • Task

…he workload

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6a53974f-949a-4934-9997-9982c4eddd90
… mid-run

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 6a53974f-949a-4934-9997-9982c4eddd90
Copilot AI balanced review requested due to automatic review settings September 23, 2026 21:05
@SohamDas2021
Soham Das (SohamDas2021) requested a review from a team as a code owner September 23, 2026 21:05
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Terminal-state races and the polling API can still misreport provider loss, while the test may kill an unrelated concurrent provider.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

Adds Bubblewrap network-provider liveness monitoring so provider loss terminates and fails active workloads.

Changes:

  • Adds pre-start and runtime provider checks.
  • Integrates provider-loss handling into Bubblewrap lifecycle management.
  • Adds documentation and an end-to-end test.
File Description
src/​backends/​bubblewrap/​common/​src/​bwrap_runner.rs Integrates monitoring and provider-loss outcomes.
src/​backends/​bubblewrap/​common/​src/​provider_monitor.rs Implements the liveness monitor thread.
src/​backends/​bubblewrap/​common/​src/​proxy_network.rs Adds the liveness pipe and diagnostics.
src/​backends/​bubblewrap/​common/​src/​lib.rs Registers the monitor module.
tests/​configs/​bubblewrap_provider_loss.json Defines the provider-loss workload.
tests/​scripts/​run_bwrap_provider_loss_test.sh Exercises mid-run provider termination.
tests/​scripts/​run_bwrap_all_tests.sh Adds the new test to the suite.
docs/​bwrap-support/​bubblewrap-backend.md Documents behavior and limitations.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +68 to +69
after=$(pgrep -x slirp4netns 2>/dev/null | sort || true)
victim=$(comm -13 <(echo "$before") <(echo "$after") | head -1)
Comment on lines +817 to +819
if let Some(status) = exited {
return BwrapOutcome::Exited(status);
}
Comment on lines +892 to 894
.lock_child()
.try_wait()?
.map(|status| status.code().unwrap_or(-1)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@SohamDas2021
Soham Das (SohamDas2021) merged commit 354cf16 into main Sep 23, 2026
31 checks passed
@SohamDas2021
Soham Das (SohamDas2021) deleted the sohamdas2021-bwrap-slirp-liveness branch September 23, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bubblewrap] Proxy-only egress: slirp4netns dying mid-run leaves the sandbox with a broken network and no error

3 participants