internal/as_user: drop supplementary groups - #2301
JasonColapietro wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthrough
ChangesPrivilege-drop hardening
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix · Severity of issue fixed: Low Suggested reviewers: Merge Risk: ⚪ Minimal · up to The privilege-drop path now removes supplementary groups before the requested operation. No actionable merge-blocking risk remains. 🚥 Pre-merge checks | ✅ 5 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Binary size report (
|
| Size | |
|---|---|
Base (main) |
33MiB |
| PR (#2301) | 33MiB |
| Delta | +16B (0.00%) |
yasminvalim
left a comment
There was a problem hiding this comment.
Hey, @JasonColapietro! Nice work!
I guess you just need to rebase and handle the conflicts, then we can run the workflow again :)
c6e73c9 to
27bbd35
Compare
set_eids() switched the effective gid and uid to the target user but never touched the supplementary group list, so the privilege-dropped thread kept the caller's group memberships while acting as that user. A directory reachable only through one of root's supplementary groups stayed reachable for the duration of the switch. Drop the list with setgroups(0, NULL) before relinquishing the gid and uid, while the thread still holds the privilege required to make that call, following the revocation order described in CERT POS36-C. Fixes coreos#2242 Signed-off-by: Jason Colapietro <55137770+JasonColapietro@users.noreply.github.com>
27bbd35 to
838740f
Compare
prestist
left a comment
There was a problem hiding this comment.
@JasonColapietro Nice fix,lgtm.
One thought; we already have root-gated tests in internal/exec/util/user_group_lookup_test.go using the t.Skip("test requires root") pattern. Might be worth adding something similar here in internal/as_user/ to verify the groups actually get dropped. Like creating a dir that's only group-accessible (0070), then calling OpenFile as a user not in that group and checking it gets EACCES.
Not blocking, just wanted to know your thoughts?
Summary
set_eids()ininternal/as_user/as_user.cswitches the effective gid and uidto the target user, but never touches the supplementary group list. The
privilege-dropped thread therefore keeps the caller's group memberships — in
practice root's — while acting as that user.
This drops the list with
setgroups(0, NULL)before the gid and uid arerelinquished, while the thread still holds the privilege required to make that
call, following the revocation order described in
CERT POS36-C.
Reproducer
I built a probe against
as_user.cbefore and after the change. It makes thecaller (root) a member of gid 4242, creates a directory owned
root:4242withmode
0770— so "other" has no access — and then asksau_open()to create afile inside it as
uid=65534 gid=65534, which is not a member of 4242:au_opensucceeded (fd=3): the thread kept root's membership of gid 4242au_openfailed withEACCES, as it shouldSo the retained groups do grant real access during the switch, not just a
theoretical capability.
Impact
Low, and I don't want to oversell it. As #2242 notes, Ignition already runs as
root and
as_useris defense-in-depth rather than a security boundary; the onlycaller is
writeAuthKeysFile()ininternal/exec/util/passwd.go, writing SSHauthorized keys during first boot, where root's supplementary groups are
typically just
{0}. This is a correctness fix that brings the privilege dropin line with POSIX practice, and matters more for any future reuse of this code.
Notes
<grp.h>is added for thesetgroups()declaration; the cgo build uses-Werror=implicit-function-declaration, so a missing declaration would befatal rather than silent.
setgroups()fails,set_eids()returns an errorrather than continuing with a partial privilege drop.
setgroups(0, NULL)clears the list rather than installing the target user'sown groups. Installing the real list would mean either an NSS lookup inside
the cloned thread or widening
au_ids_tto carry the group list from Go.Happy to do the latter if you'd prefer it — clearing seemed like the right
minimal fix for the reported issue.
Testing
./test— Success, exit 0 (Fedora 44, Go 1.24, cgo, run as a non-rootuser)
./build ignition— exit 0git diff --check— cleanas_user.cRunning
./testas root instead failsTestTranslateTree/translate_7in theseven
butane/base/*packages, because that test asserts a permission-deniederror which root bypasses. That failure reproduces identically on an unmodified
origin/main, so it is unrelated to this change.No new in-tree test:
internal/as_useris Linux-, cgo- and root-only, has noexisting unit tests, and a regression test for this would have to manipulate the
test process's own credentials. I'm glad to add a root-gated one if you want it.
Fixes #2242.