package accounts import ( "context" "encoding/binary" "errors" "fmt" "os/exec" "strings" "testing" "time" ) // The ways beyond uid, groups and sudo (novox/hq ADR 0266, the review of 2026-10-08): doas, polkit, a // runtime's socket, an ACL on a secret, a setuid program no package owns — each said; each unread question // unknown; and the judge's commands in the C locale. func TestEachFurtherWayToRootIsSaid(t *testing.T) { const broker = "/var/lib/mesh/node-tools/broker" for _, c := range []struct { name string set func(*agentMachine) said string }{ {"doas by name", func(m *agentMachine) { m.texts = map[string]string{"/etc/doas.conf": "permit persist operator\npermit nopass agent as root\n"} }, "doas permits it: permit nopass agent as root"}, {"doas by group", func(m *agentMachine) { m.groups = "agent builders" m.texts = map[string]string{"/etc/opendoas.conf": "permit :builders # the builders\n"} }, "doas permits it: permit :builders"}, {"polkit by name", func(m *agentMachine) { m.dirs = map[string][]string{"/etc/polkit-1/rules.d": {"10-agent.rules"}} m.texts = map[string]string{"/etc/polkit-1/rules.d/10-agent.rules": `polkit.addRule(function(a, s) { if (s.user == "agent") return polkit.Result.YES; });`} }, "a polkit rule names it or a group of it: /etc/polkit-1/rules.d/10-agent.rules"}, {"polkit by group", func(m *agentMachine) { m.groups = "agent network" m.dirs = map[string][]string{"/usr/share/polkit-1/rules.d": {"50-nm.rules"}} m.texts = map[string]string{"/usr/share/polkit-1/rules.d/50-nm.rules": `if (subject.isInGroup("network")) return polkit.Result.YES;`} }, "a polkit rule names it or a group of it: /usr/share/polkit-1/rules.d/50-nm.rules"}, {"docker socket by group bits", func(m *agentMachine) { m.gids = "1600 970" m.files["/run/docker.sock"] = FileMode{UID: 0, GID: 970, Perm: 0o660} }, "it can write the container runtime's socket /run/docker.sock"}, {"docker socket by ACL", func(m *agentMachine) { m.files["/run/docker.sock"] = FileMode{UID: 0, GID: 970, Perm: 0o660} m.acls = map[string][]ACLEntry{"/run/docker.sock": {{User: true, ID: 1600, Read: true, Write: true}}} }, "it can write the container runtime's socket /run/docker.sock"}, {"secret by ACL", func(m *agentMachine) { m.acls = map[string][]ACLEntry{broker: {{User: false, ID: 1600, Read: true}}} }, "an ACL lets it read the secret " + broker}, {"polkit for every account", func(m *agentMachine) { m.dirs = map[string][]string{"/etc/polkit-1/rules.d": {"00-all.rules"}} m.texts = map[string]string{"/etc/polkit-1/rules.d/00-all.rules": `polkit.addRule(function(action, subject) { return polkit.Result.YES; });`} }, "a polkit rule grants every account: /etc/polkit-1/rules.d/00-all.rules"}, {"pkla for every account", func(m *agentMachine) { m.dirs = map[string][]string{"/etc/polkit-1/localauthority": {"all.pkla"}} m.texts = map[string]string{"/etc/polkit-1/localauthority/all.pkla": "[all]\nIdentity=unix-user:*\nAction=*\nResultAny=yes\n"} }, "a polkit rule grants every account: /etc/polkit-1/localauthority/all.pkla"}, {"docker on TCP", func(m *agentMachine) { m.proc["/proc/net/tcp6"] = " sl local_address rem_address st\n 0: 00000000000000000000000000000000:0947 00000000000000000000000000000000:0000 0A 0\n" }, "a container runtime's API listens on TCP port 2375"}, {"hard links unprotected", func(m *agentMachine) { m.proc["/proc/sys/fs/protected_hardlinks"] = "0\n" }, "hard links to other accounts' files are not protected (fs.protected_hardlinks=0)"}, {"setuid on a second filesystem", func(m *agentMachine) { m.proc["/proc/mounts"] += "/dev/sdb1 /srv xfs rw 0 0\n/dev/sdc1 /data ext4 rw,nosuid 0 0\n" m.setuid = "/srv/bin/rootshell\n" m.findArgs = func(args []string) { if len(args) != 2 || args[0] != "/" || args[1] != "/srv" { panic(fmt.Sprintf("searched %v", args)) } } }, "a setuid-root program no package owns, which agent can execute: /srv/bin/rootshell"}, {"setuid no package owns", func(m *agentMachine) { m.setuid = "/usr/bin/sudo\n/usr/local/bin/rootshell\n" m.packaged = map[string]bool{"/usr/bin/sudo": true} }, "a setuid-root program no package owns, which agent can execute: /usr/local/bin/rootshell"}, } { t.Run(c.name, func(t *testing.T) { m := clean() c.set(m) v := lookAgent(t, m) if v.State != Unhealthy || !strings.HasPrefix(v.Reason, ReasonRoot+": ") || !strings.Contains(v.Reason, c.said) { t.Fatalf("want %q said: %+v", c.said, v) } }) } } func TestWhatGrantsSomebodyElseIsNoWay(t *testing.T) { m := clean() m.texts = map[string]string{"/etc/doas.conf": "permit operator\npermit :wheel\n# permit agent\n", "/usr/share/polkit-1/rules.d/50-default.rules": `polkit.addAdminRule(function(a, s) { return ["unix-group:wheel"]; });`} m.dirs = map[string][]string{"/usr/share/polkit-1/rules.d": {"50-default.rules"}} m.files["/run/docker.sock"] = FileMode{UID: 0, GID: 970, Perm: 0o660} m.setuid = "/usr/bin/sudo\n/usr/bin/passwd\n" m.packaged = map[string]bool{"/usr/bin/sudo": true, "/usr/bin/passwd": true} if v := lookAgent(t, m); v.State != Healthy { t.Fatalf("the operator's and wheel's grants, a socket of another group, packaged setuid programs: %+v", v) } } func TestDoasWithoutSudoIsJudgedFromItsRules(t *testing.T) { m := clean() m.sudo, m.sudoErr = "", fmt.Errorf("sudo: %w", exec.ErrNotFound) m.texts = map[string]string{"/etc/doas.conf": "permit nopass agent\n"} v := lookAgent(t, m) if v.State != Unhealthy || !strings.Contains(v.Reason, "doas permits it") { t.Fatalf("no sudo is no sudo rule, and doas is read for itself: %+v", v) } } func TestAFurtherQuestionUnansweredIsUnknown(t *testing.T) { m := clean() m.findErr = errors.New("find: interrupted") if v := lookAgent(t, m); v.State != Unknown { t.Fatalf("a search that did not finish: %+v", v) } m = clean() m.setuid = "/usr/local/bin/x\n" j := New(Exec{Run: func(ctx context.Context, name string, args ...string) (string, error) { if name == "pacman" || name == "apk" { return "", fmt.Errorf("%s: %w", name, exec.ErrNotFound) } return m.run(ctx, name, args...) }, Walk: m.walk, Stat: m.stat, ReadFile: m.readFile, ReadDir: m.readDir, ACL: m.acl, Lstat: m.lstat}) j.Set([]Account{agent}) if st, _ := j.Look(t.Context()); st.Accounts[0].State != Unknown { t.Fatalf("no package manager to ask: %+v", st.Accounts[0]) } } func TestANotJudgedSearchStillSaysAWayFound(t *testing.T) { j := New(fakeReader{ways: []string{"in the group docker, which grants root"}, err: &SetuidNotJudged{Pending: true, Since: time.Now()}}) j.Set([]Account{{Module: "m", ID: "m.a", Name: "agent", Root: true}}) st, _ := j.Look(context.Background()) if v := st.Accounts[0]; v.State != Unhealthy || !strings.Contains(v.Reason, "docker") || !strings.Contains(v.Reason, ReasonPending) { t.Fatalf("a way found is unhealthy, with the search's state beside it: %+v", v) } j = New(fakeReader{err: &SetuidNotJudged{Since: time.Now(), Err: errors.New("timeout")}}) j.Set([]Account{{Module: "m", ID: "m.a", Name: "agent", Root: true}}) st, _ = j.Look(context.Background()) if v := st.Accounts[0]; v.State != Unknown || !strings.HasPrefix(v.Reason, ReasonIncomplete) { t.Fatalf("nothing found and the search incomplete: unknown, said so: %+v", v) } } type fakeReader struct { ways []string err error } func (f fakeReader) InDatabase(context.Context, string) ([]string, error) { return nil, nil } func (f fakeReader) Session(context.Context, string, []string) (Session, error) { return Session{}, nil } func (f fakeReader) Escalation(context.Context, string, []string) ([]string, error) { return f.ways, f.err } func TestParseACL(t *testing.T) { entry := func(tag, perm uint16, id uint32) []byte { b := make([]byte, 8) binary.LittleEndian.PutUint16(b, tag) binary.LittleEndian.PutUint16(b[2:], perm) binary.LittleEndian.PutUint32(b[4:], id) return b } raw := []byte{2, 0, 0, 0} raw = append(raw, entry(0x01, 6, 0xffffffff)...) raw = append(raw, entry(0x02, 6, 1600)...) // user agent rw raw = append(raw, entry(0x04, 4, 0xffffffff)...) raw = append(raw, entry(0x08, 6, 970)...) // group 970 rw raw = append(raw, entry(0x10, 4, 0xffffffff)...) // mask r-- raw = append(raw, entry(0x20, 0, 0xffffffff)...) acl, err := ParseACL(raw) if err != nil || len(acl) != 2 { t.Fatalf("%v %v", acl, err) } if !acl[0].User || acl[0].ID != 1600 || !acl[0].Read || acl[0].Write { t.Fatalf("the mask takes write away: %+v", acl[0]) } if grantsByACL(acl, 1600, nil, true) || !grantsByACL(acl, 1601, map[int]bool{970: true}, false) { t.Fatal("grants") } if _, err := ParseACL([]byte{2, 0, 0}); err == nil { t.Fatal("a short ACL") } } func TestTheJudgesCommandsRunInTheCLocale(t *testing.T) { t.Setenv("LC_ALL", "de_DE.UTF-8") t.Setenv("LANG", "de_DE.UTF-8") out, err := CLocale(t.Context(), "sh", "-c", `echo "$LC_ALL $LANG"`) if err != nil || strings.TrimSpace(out) != "C C" { t.Fatalf("%q %v", out, err) } if _, err := CLocale(t.Context(), "sh", "-c", "echo nope >&2; exit 3"); err == nil || !strings.Contains(err.Error(), "exited 3: nope") { t.Fatalf("an exit is said with its words: %v", err) } } func TestTheJudgedListIsWrittenDown(t *testing.T) { if len(Judged) < 8 || len(NotJudged) == 0 { t.Fatal("what is judged and what is not are both written down") } } func TestDistributionRulesForASeatOrForSomebodyAreNotEveryone(t *testing.T) { for _, rule := range []string{ `polkit.addRule(function(a, s) { if (s.local && s.active) return polkit.Result.YES; });`, `polkit.addRule(function(a, s) { if (s.isInGroup("wheel")) return polkit.Result.YES; });`, `polkit.addAdminRule(function(a, s) { return ["unix-group:wheel"]; });`, } { if PolkitGrantsEveryone("/x.rules", rule) { t.Errorf("not every account: %s", rule) } } if PolkitGrantsEveryone("/x.pkla", "[a]\nIdentity=unix-group:wheel\nResultAny=yes\n") { t.Error("a pkla for wheel is not every account") } } func TestAMachineMissingWhatTheJudgeReadsIsUnknown(t *testing.T) { for _, file := range []string{"/proc/sys/fs/protected_hardlinks", "/proc/net/tcp", "/proc/mounts"} { m := clean() delete(m.proc, file) if v := lookAgent(t, m); v.State != Unknown { t.Errorf("%s unread: %+v", file, v) } } } func TestSuidMountsLeaveOutNosuidPseudoAndContainerLayers(t *testing.T) { got := SuidMounts("/dev/sdb1 /srv xfs rw 0 0\n/dev/sda2 / ext4 rw 0 0\noverlay /var/lib/docker/overlay2/x/merged overlay rw 0 0\n" + "/dev/sdc1 /data ext4 rw,nosuid 0 0\nproc /proc proc rw 0 0\nhost:/x /mnt/nfs nfs4 rw 0 0\n/dev/sdd1 /my\\040disk ext4 rw 0 0\n") if strings.Join(got, "|") != "/|/my disk|/srv" { t.Fatalf("got %q", got) } } // Each polkit rule is judged on its own, comments taken out (the third review of 2026-10-08). func TestAnUnconditionalPolkitYesIsNotMaskedByAnotherRuleOrAComment(t *testing.T) { cases := []struct { name, text string every bool }{ {"another rule names a user", `polkit.addRule(function(a, s) { if (s.user == "operator") return polkit.Result.YES; }); polkit.addRule(function(a, s) { return polkit.Result.YES; });`, true}, {"a user named only in a comment", `// subject.user == "x" polkit.addRule(function(a, s) { /* isInGroup("wheel") */ return polkit.Result.YES; });`, true}, {"the yes only in a comment", `polkit.addRule(function(a, s) { // return polkit.Result.YES; return polkit.Result.NO; });`, false}, {"a condition in the same rule", `polkit.addRule(function(a, s) { if (s.isInGroup("wheel")) { return polkit.Result.YES; } });`, false}, {"a comment marker inside a string", `polkit.addRule(function(a, s) { var u = "http://x"; return polkit.Result.YES; });`, true}, } for _, c := range cases { if got := PolkitGrantsEveryone("/etc/polkit-1/rules.d/x.rules", c.text); got != c.every { t.Errorf("%s: grants every account %v, want %v", c.name, got, c.every) } } if PolkitGrantsEveryone("/x.pkla", "# [a]\n# Identity=unix-user:*\n# ResultAny=yes\n") { t.Error("a .pkla section only in comments grants nobody") } if !PolkitGrantsEveryone("/x.pkla", "[a]\nIdentity=unix-group:wheel\nResultAny=yes\n[b]\nIdentity=unix-user:*\nResultActive=yes\n") { t.Error("each .pkla section on its own") } } // The package manager is asked about every path found in one call, and answers each: owned, or owned by nothing. func TestThePackageManagerIsAskedOnceForEveryPathFound(t *testing.T) { m := clean() m.setuid = "/usr/bin/sudo\n/usr/bin/passwd\n/usr/local/bin/x\n/opt/y\n" m.packaged = map[string]bool{"/usr/bin/sudo": true, "/usr/bin/passwd": true} search := func(e Exec) ([]string, error) { return e.setuidSearch(t.Context(), &SetuidProgress{Mounts: []string{"/"}}, func() {}, nil) } found, err := search(Exec{Run: m.run, Walk: m.walk}) if err != nil { t.Fatal(err) } if m.pacmanCalls != 1 { t.Errorf("pacman was asked %d times for four paths", m.pacmanCalls) } if strings.Join(found, ",") != "/opt/y,/usr/local/bin/x" { t.Errorf("unowned: %v", found) } // An error pacman says that is not "owned by nothing" is no answer. odd := func(ctx context.Context, name string, args ...string) (string, error) { if name == "pacman" { return "", errors.New("pacman exited 1: error: could not open database") } return m.run(ctx, name, args...) } if _, err := search(Exec{Run: odd, Walk: m.walk}); err == nil { t.Error("a package manager that could not answer was read as an answer") } // A walk that did not complete is no answer either. m.findErr = errors.New("could not read /mnt/data/private: permission denied") if _, err := search(Exec{Run: m.run, Walk: m.walk}); err == nil { t.Error("a walk that did not complete was read as an answer") } }