Keep the network rules unchangeable inside the sandbox and read them only as a regular file - #30
Merged
Merged
Conversation
added 2 commits
September 13, 2026 13:27
…e it is Code scanning reported three findings. The test probe for the network filter wrote its rules file at a path taken from the environment, with a mode that could leave it writable by anyone. It now creates a private directory with mkdtemp, writes the rules exclusively with mode 0600 and without following a link, and hands the directory to its second phase, which removes it. The filter opened the rules file NETBLOCKER_CONF names with fopen: it followed a symbolic link, blocked every process on a FIFO, and reread the file on SIGHUP. It now opens it without following a link and without blocking, accepts only a regular file, and no longer installs a SIGHUP handler, so a process keeps the disposition it inherited. In phobos.sh the rules file lay in a directory under /tmp, a write path in both shipped policies, so a submission could rewrite the rules every later process reads. The directory now lives under PHOBOS_SPEC_PARENT, /var/tmp by default, and append_netblocker_rules gives both entry points a read rule for the rules file and the library while refusing a policy under which either lies beneath a write path. The legacy wrapper granted neither before. Every layer removes the directory phobos.sh created, and only that one, when the run ends. Both committed libnetblocker.so copies are rebuilt with the pinned toolchain.
The comment beside the prune_sandbox.sh step claimed those checks skip in CI for want of bubblewrap. The run logs show all fifteen passing there.
6 of 12 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The network filter now reads its rules only from the regular file it is given, never through a link or from a FIFO, and no longer rereads them when a process receives a hang-up signal. The rules file lives in a private directory the sandbox can read but not change, and that directory is removed after each run. The test helper that wrote rules files is made safe too.
Linked issues
No linked issues
1. Problem
Code scanning reports three findings. The test probe for the network filter created its rules file with a mode that could leave it writable by anyone, at a path taken from the environment. The filter opens the rules file
NETBLOCKER_CONFnames: it followed a symbolic link, blocked on a FIFO, and reread the file whenever a process received SIGHUP.In
phobos.shthat file lay in a directory under/tmp, which both shipped policies make writable, so a submission could rewrite the network rules of every process it started afterwards. The legacyphobos_wrapper.shdid not grant the library or its rules file at all, so the loader skipped the filter there. The directory was never removed.The layers at fault are the LD_PRELOAD network filter and the policy the filesystem layer hands to Landlock. Phobos let a submission change what it may reach.
2. Improvement from the user's perspective
The network rules a run starts with are the rules it keeps: a submission can read them but can no longer change them. A policy under which the rules file or the filter itself could be changed is refused with exit code 11 and a message naming the write path. No policy directory is left behind after a run, whether it succeeds, fails, is refused or times out.
3. Improvement from the maintainer's perspective
CodeQL alerts #1 and #3, both in the test probe, are fixed at their cause: the probe creates a private directory with
mkdtemp, writes the rules exclusively with mode 0600, and hands the directory to its second phase, which removes it. Alert #2, the filter readingNETBLOCKER_CONF, stays by design: the variable is the interface, and a process can step around a preload library anyway, asSECURITY.mdsays. At its new location it is alert #4, dismissed with that reason.One function,
append_netblocker_rules, gives both entry points the same rules for the library and its rules file, and one,finish_owned_spec_dir, removes a policy directory Phobos created and nothing else. A separate commit corrects a comment #29 put intest.yml: the pruner's Bubblewrap checks do run on the hosted runner.4. Testing manual
Prerequisites
--privileged,--cap-addor--security-opt.Steps
docker run --rm -v "$PWD:/repo:ro" ubuntu:26.04 bash -c 'apt-get update -qq && apt-get install -y -qq gcc-14 libc6-dev binutils >/dev/null && cp -a /repo /w && bash /w/tests/network_cache_ports.sh'main, to see that they catch the old behaviour:git show origin/main:ld_preloader/netblocker.c > /tmp/old-netblocker.c, then step 1 with-v /tmp/old-netblocker.c:/old.c:roadded andcp /old.c /w/ld_preloader/netblocker.c &&placed beforebash.tests/landlock-acceptance/README.mdgives them, on a machine that runs the image natively.Expected result
ok, among them the sectionsthe rules file,SIGHUP,the library and its rules file stay out of the sandbox's reachanda run leaves no specification behind, and the suite ends with51 passed, 0 failed, 0 skipped.FAILon the symbolic link, the FIFO, both SIGHUP dispositions,rewriting the rules and sending SIGHUP widens nothing, and the probe's cleanup, because the old filter blocks on the FIFO until the run is killed. The directory check passes against the old filter too, since reading a directory yields no rules; it guards the behaviour rather than telling the two apart.bestanden: 11,bestanden: 27andbestanden: 13, each withfehlgeschlagen: 0. SectionI.shows threePASSlines: the rules file readable but not writable inside the sandbox, a policy directory beneath a write path refused, and no policy directory left.Negative case (what must still be rejected)
Inside the sandbox the rules file cannot be written (step 3, section
I.). A write path equal to or above the rules file's directory or the library, directly or through a symbolic link, is refused with exit code 11 and the command does not run (step 1). A rules file reached through a link, a directory or a FIFO grants no port. Port 18081 stays denied while 18080 works (step 3,run-tests.sh).Layers exercised
5. Test case coverage regarding this PR
tests/network_cache_ports.sh, Ubuntu 26.04 container, arm64 and amd64netblocker.cfrommaintests/timeout_units.sh, Ubuntu 26.04 container, amd64tests/landlock-acceptance/run-tests.sh, arm64 image,--network nonetests/landlock-acceptance/extra-tests.sh, sametests/landlock-acceptance/phase-test.sh, sameAll runs used kernel 7.0 under Docker Desktop.
Alerts #1 and #3 no longer appear for this pull request. The remaining finding, the filter opening the file
NETBLOCKER_CONFnames, is the one reported as #2 onmain; moving theopeninto its own function gave it a new location, which code scanning counted as the new alert #4. It stays by design, as section 3 says, and #4 is dismissed as "won't fix" with that reason. The netblocker refactor will move this code again, and the alert it then reports gets the same treatment.Breaking changes and migration
/tmpto/var/tmp, or toPHOBOS_SPEC_PARENT. A policy whose write paths cover it, or the directory holdinglibnetblocker.so, now stops with exit code 11.libnetblocker.soandallowedList.cfginphobos_wrapper.sh, which it did not permit before, and readingnet.rulesat its new place, which it did not permit before. Both are needed to load the filter; neither can be changed.Checklist
--privileged,--cap-addor--security-opt, or the manual says why that was not possible.README.md, the comments incore/) was updated where the change is user-facing.Review progress