Conversation
4cbe85c added six refusals to the offline cache key file -- not a regular file, not root-owned, setuid, setgid, any group bit, any other bit -- and touched no test file. There was no tests/test_cache_key*. It was the only hardening of the 0.7.0 series with no proof, and it also corrected a real branch-nesting bug: the ownership test used to sit INSIDE the loose-permissions branch, so a key file that was mode 0600 and owned by any other uid slipped through entirely. tests/test_cache_key.c now drives each one, 16 assertions, including that 0600-owned-by-someone-else case and the 0644 that root's umask 022 produces from the `dd` recipe SECURITY.md used to document. Reaching them needed one change. As an ordinary user every file a test can create fails the ownership check first, so the four refusals below it were unreachable in any CI job that is not root -- and a suite that skips is a suite that proves nothing. The checks therefore move into key_file_rejected(), which takes the expected owner as an argument; read_key_file() passes 0, as it always did, and one assertion pins that value so the tests cannot quietly become a test of themselves. Writing it turned up a defect. read_key_file() opened with O_RDONLY|O_NOFOLLOW, and opening a fifo read-only blocks until a writer appears: the "not a regular file" refusal directly below it was unreachable, and a fifo at /etc/open-bastion/cache.key parked the caller forever instead of being refused. On the PAM path that is a login that never finishes. Placing one there needs write access to /etc/open-bastion, so this is not a privilege boundary being crossed -- it is a check that could not fire. O_NONBLOCK makes it fire and does nothing to a regular file, which is the only thing that gets past it. Five catalogue entries, one per check plus the non-blocking open, all caught. The wrapper the runner needs imposes a timeout, because the O_NONBLOCK mutant hangs the suite rather than failing it, and the runner has no timeout of its own. ctest 23/23, mutation 5/5 on the new entries, both as an ordinary user and under `unshare -r` so the root path in CI's Rocky containers is exercised too. Closes part 2 of #268.
guimard
force-pushed
the
test/cache-key-refusals
branch
from
September 8, 2026 12:44
1ab0f1b to
a5be0a2
Compare
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.
Item 2 of #268.
The gap
4cbe85cadded six refusals to the offline cache key file — not a regular file,not root-owned, setuid, setgid, any group bit, any other bit — and touched no
test file. There was no
tests/test_cache_key*. It was the only hardening ofthe 0.7.0 series with no proof, and it corrected a real branch-nesting bug: the
ownership test used to sit inside the loose-permissions branch, so a key file
that was mode
0600and owned by any other uid slipped through entirely.The test
tests/test_cache_key.c, 16 assertions, includes the.cto reach its statics(the same thing
tests/test_nss_cache.cdoes). It covers the0600-owned-by-someone-else case, each group and other bit one at a time, the0644root's umask 022 produces from theddrecipeSECURITY.mdused todocument, and — so the rest cannot pass vacuously — the two modes that must be
accepted,
0600and0400.What had to change to reach them
As an ordinary user every file a test can create fails the ownership check
first, so the four refusals below it were unreachable in any CI job that is not
root — and a suite that skips proves nothing. The checks move into
key_file_rejected(), which takes the expected owner as an argument.read_key_file()passes0, as it always did, and one assertion pins thatvalue so the tests cannot quietly become a test of themselves. The suite passes
unprivileged and under
unshare -r, so the root path CI's Rocky containers takeis exercised too.
A defect it turned up
read_key_file()opened withO_RDONLY | O_NOFOLLOW, and opening a fiforead-only blocks until a writer appears. The "not a regular file" refusal
directly below it was therefore unreachable: a fifo at
/etc/open-bastion/cache.keyparked the caller forever instead of beingrefused, and on the PAM path that is a login that never finishes.
Placing one there needs write access to
/etc/open-bastion, so no privilegeboundary is crossed — this is a check that could not fire.
O_NONBLOCKmakes itfire and does nothing to a regular file, which is the only thing that gets past
it.
Proof it can fail
Five entries in
tests/mutation/catalogue, one per check plus the non-blockingopen:
The wrapper the runner needs imposes a
timeout: theO_NONBLOCKmutant hangsthe suite rather than failing it, and the runner has no timeout of its own.
ctest23/23,tests/test_ob_ci_coverage.shandtests/test_ob_changelog.shgreen.