From ca3a3198e81007f31d8d39138790352a31110987 Mon Sep 17 00:00:00 2001 From: Pierre Warnier Date: Sun, 6 Sep 2026 11:07:41 +0200 Subject: [PATCH] tests: cover gpasswd as a group administrator The reason gpasswd is setuid is that someone who is not root, but is named an administrator of one group, manages that group's membership. Nothing in the suite exercised a non-root caller of it, and that is the only place the omission shows: the multicall binary drops privilege for every applet not on its setuid list, so leaving gpasswd off that list produces a permission error there and nowhere else. It was off the list when the tool arrived. Twelve assertions: an administrator adds a member without being root and the change lands in both group and gshadow; a plain member may not change the membership, may not appoint administrators and may not use --root; and the administrator list is unchanged after all three refusals. Checked that the test bites by taking gpasswd back off the setuid list and rebuilding -- the administrator is then refused and the group file is untouched. Worth saying that the first attempt at that check appeared to show the fix was unnecessary; the build output had been suppressed and a stale binary was installed. --- tests/e2e/deploy-test.sh | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/e2e/deploy-test.sh b/tests/e2e/deploy-test.sh index 0cabc92..d818f05 100755 --- a/tests/e2e/deploy-test.sh +++ b/tests/e2e/deploy-test.sh @@ -824,6 +824,51 @@ test_root_option() { rm -rf "$tree" } +# ── gpasswd as a group administrator ──────────────────────────────── + +test_gpasswd_group_admin() { + section "gpasswd as a group administrator" + + # The reason gpasswd is setuid: someone who is not root, but is named an + # administrator of one group, manages that group's membership. Nothing + # else in this suite exercises a non-root caller of gpasswd, and the + # multicall binary drops privilege for every applet not on its setuid + # list -- so leaving gpasswd off that list fails exactly here, and + # nowhere else. + userdel -r gp_admin 2>/dev/null || true + userdel -r gp_member 2>/dev/null || true + groupdel gp_team 2>/dev/null || true + + assert_ok "useradd -m gp_admin" useradd -m gp_admin + assert_ok "useradd -m gp_member" useradd -m gp_member + assert_ok "groupadd gp_team" groupadd gp_team + assert_ok "gpasswd -A gp_admin gp_team" gpasswd -A gp_admin gp_team + assert_file_contains "gp_admin is recorded as an administrator" \ + /etc/gshadow '^gp_team:[^:]*:gp_admin:' + + assert_ok "an administrator adds a member without being root" \ + su -s /bin/bash gp_admin -c "gpasswd -a gp_member gp_team" + assert_file_contains "the member is in /etc/group" \ + /etc/group '^gp_team:.*:gp_member' + assert_file_contains "and in /etc/gshadow" \ + /etc/gshadow '^gp_team:.*:gp_member' + + assert_fail "a plain member may not change the membership" \ + su -s /bin/bash gp_member -c "gpasswd -a gp_admin gp_team" + assert_fail "a plain member may not appoint administrators" \ + su -s /bin/bash gp_member -c "gpasswd -A gp_member gp_team" + assert_fail "a plain member may not use --root" \ + su -s /bin/bash gp_member -c "gpasswd --root / -a gp_member gp_team" + + # The refusals must change nothing. + assert_file_contains "the administrator list is unchanged" \ + /etc/gshadow '^gp_team:[^:]*:gp_admin:' + + groupdel gp_team 2>/dev/null || true + userdel -r gp_admin 2>/dev/null || true + userdel -r gp_member 2>/dev/null || true +} + # ── nscd cache invalidation ──────────────────────────────────────── test_nscd() { @@ -933,6 +978,7 @@ main() { test_individual_tools test_pam_auth test_self_service + test_gpasswd_group_admin test_aging_and_input test_audit_logging test_root_option