diff --git a/internal/apply/apply.go b/internal/apply/apply.go index 5c553eb..a6dac27 100644 --- a/internal/apply/apply.go +++ b/internal/apply/apply.go @@ -1564,14 +1564,26 @@ func applyDirectory(r *declaration.Directory, previous store.Applied, wasBefore // itself) and belongs to the engine's own account: only that account makes a directory there, so it is the mesh's // and not found (novox/hq issue 1873). Anywhere else, or owned by any other account, issue 339's rule stands. func madeInTheMeshsTree(path string, found os.FileInfo) bool { - root := filepath.Clean(meshDataRoot) - if !strings.HasPrefix(filepath.Clean(path), root+string(filepath.Separator)) { + // By its text and by where it leads: a link in the tree pointing out of it leads outside, and what is there is + // not the tree's (review of issue 1873). + if !inTheMeshsTree(path) { return false } uid, _, ok := ownerOf(found) return ok && uid == os.Geteuid() } +// inTheMeshsTree is whether a path lies strictly below the mesh's own tree, both as written and with every link in +// it followed (resolved), against the tree's root resolved the same way (novox/hq issue 1873). +func inTheMeshsTree(path string) bool { + root := filepath.Clean(meshDataRoot) + sep := string(filepath.Separator) + if !strings.HasPrefix(filepath.Clean(path), root+sep) { + return false + } + return strings.HasPrefix(resolved(filepath.Clean(path)), resolved(root)+sep) +} + // applyHandedOver gives a directory the operator handed to the mesh its declared mode and owner (novox/hq issue // 356). **No link, checked again at the moment of use**: the hand-over refused a link when it was recorded, and a // component of the path may have become one since — a parent swapped for a link to /etc would carry root's chmod diff --git a/internal/apply/mesh_tree_found_test.go b/internal/apply/mesh_tree_found_test.go index f37a893..a3603a0 100644 --- a/internal/apply/mesh_tree_found_test.go +++ b/internal/apply/mesh_tree_found_test.go @@ -1,3 +1,5 @@ +//go:build linux + package apply import ( @@ -7,6 +9,7 @@ import ( "strings" "testing" + "git.novox.be/novox/mesh-sdk/go/declaration" "github.com/novox/mesh-host/internal/store" ) @@ -60,3 +63,38 @@ func TestADirectoryInTheMeshsOwnTreeMadeByTheEngineIsTheMeshs(t *testing.T) { t.Errorf("a directory found outside the mesh's tree was not used as found: %q", said) } } + +// Review of issue 1873's fix: the tree is judged by where the path leads, not by its text. A root-only link inside the +// mesh's tree pointing out of it (to /var/lib/systemd, say) would otherwise make a directory outside the tree, owned by +// the engine's account, the mesh's to chown: issue 1873's fault again. It is used as found. +func TestALinkInTheMeshsTreeLeadingOutOfItIsNotTheTree(t *testing.T) { + rootAlone(t, "") + root := t.TempDir() + before := meshDataRoot + meshDataRoot = filepath.Join(root, "mesh") + t.Cleanup(func() { meshDataRoot = before }) + outside := filepath.Join(root, "var-lib", "systemd") + if err := os.MkdirAll(filepath.Join(outside, "data"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.Chmod(filepath.Join(outside, "data"), 0o700); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(meshDataRoot, 0o755); err != nil { + t.Fatal(err) + } + if err := os.Symlink(outside, filepath.Join(meshDataRoot, "postgres")); err != nil { + t.Fatal(err) + } + r := &declaration.Directory{ID: "postgres.data", Path: filepath.Join(meshDataRoot, "postgres", "data"), Mode: "0750"} + out, err := applyDirectory(r, store.Applied{}, true) + if err != nil { + t.Fatal(err) + } + if !out.asFound { + t.Errorf("a directory reached through a link out of the mesh's tree was taken as the mesh's: %+v", out) + } + if got := permOf(t, filepath.Join(outside, "data")); got != 0o700 { + t.Errorf("the directory outside the tree was given mode %o (was 700)", got) + } +}