A mesh directory outside every home was chmodded and chowned by path, so a parent turned into a link between two applies carried root's chmod and chown to the link's target. It is now made and changed through the descriptor of the walk from / that follows a link only root alone could have made. A directory that walk refuses is a refusal like the placement guard's (review 47): judged before anything is applied, and again when the directory is applied, used as found or not; refused at either point it gates its module and refuses what is below it, so no file is written into the link's target and the report does not say everything was attempted.
92 lines
3.4 KiB
Go
92 lines
3.4 KiB
Go
//go:build linux
|
|
|
|
package apply
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// Defends novox/hq issue 358: a mesh directory outside every home is given its mode and owner only through a
|
|
// descriptor opened from /, so a parent swapped for a link between two applies cannot carry root's chmod and
|
|
// chown to the link's target — unless root alone could have made that link (/var/run is one on every node).
|
|
|
|
// A link only root could have made — itself root's, in directories no other account may write — is followed, as
|
|
// /var/run to /run is on every node; relative or absolute.
|
|
func TestALinkOnlyRootCouldMakeIsFollowed(t *testing.T) {
|
|
for name, to := range map[string]func(base string) string{
|
|
"absolute": func(base string) string { return filepath.Join(base, "elsewhere") },
|
|
"relative": func(string) string { return "elsewhere" },
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
rootAlone(t, "")
|
|
base, r, previous := aMeshDirectory(t)
|
|
target := swapParentForLink(t, base, to(base))
|
|
|
|
out, err := applyDirectory(r, previous, true)
|
|
if err != nil {
|
|
t.Fatalf("a directory behind a root-only link was refused: %v", err)
|
|
}
|
|
if got := permOf(t, target); got != 0o700 {
|
|
t.Errorf("the directory behind a root-only link is mode %o, want 700", got)
|
|
}
|
|
if out.Action != "updated" {
|
|
t.Errorf("the apply said %q, want updated", out.Action)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A link another account owns refuses the directory even in directories root alone may write, naming the account.
|
|
func TestALinkAnotherAccountOwnsIsRefused(t *testing.T) {
|
|
rootAlone(t, "the account mallory (it owns the link)")
|
|
base, r, previous := aMeshDirectory(t)
|
|
target := swapParentForLink(t, base, filepath.Join(base, "elsewhere"))
|
|
|
|
_, err := applyDirectory(r, previous, true)
|
|
if err == nil || !strings.Contains(err.Error(), "mallory") {
|
|
t.Fatalf("a link another account owns was not refused naming it: %v", err)
|
|
}
|
|
if got := permOf(t, target); got != 0o755 {
|
|
t.Errorf("the link's target was given mode %o (was 755)", got)
|
|
}
|
|
}
|
|
|
|
// A directory that is missing is made through the walk, below a link root alone could make.
|
|
func TestAMissingDirectoryIsMadeBehindARootOnlyLink(t *testing.T) {
|
|
rootAlone(t, "")
|
|
base := t.TempDir()
|
|
if err := os.Mkdir(filepath.Join(base, "real"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.Symlink("real", filepath.Join(base, "link")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r := &declaration.Directory{ID: "mod.state", Path: filepath.Join(base, "link", "a", "b"), Mode: "0750"}
|
|
out, err := applyDirectory(r, store.Applied{}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Action != "created" {
|
|
t.Errorf("the apply said %q, want created", out.Action)
|
|
}
|
|
if got := permOf(t, filepath.Join(base, "real", "a", "b")); got != 0o750 {
|
|
t.Errorf("the made directory is mode %o, want 750", got)
|
|
}
|
|
}
|
|
|
|
// rootAlone has every directory count as writable by root alone, as /, /var and /run are on a node, and a link's
|
|
// owner be linkOwner ("" for root): a test runs below /tmp, which every account may write.
|
|
func rootAlone(t *testing.T, linkOwner string) {
|
|
t.Helper()
|
|
wasDirs, wasLinks := couldReplace, linkReplacer
|
|
couldReplace = func(string, uint32, uint32, uint32) string { return "" }
|
|
linkReplacer = func(string, uint32) string { return linkOwner }
|
|
t.Cleanup(func() { couldReplace, linkReplacer = wasDirs, wasLinks })
|
|
}
|