From 03b9c47af4c46871ce6991d9338772e5bc468674 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 22:27:45 +0200 Subject: [PATCH] Judge the mesh's tree by where a path leads, not only by its text (issue 1873, review) A root-only link inside /var/lib/mesh pointing out of it would have made a directory outside the tree, owned by the engine's account, the mesh's to give away: the fault of issue 1873 again. The path and the tree's root are now both resolved, links followed, and the directory is the tree's only when the resolved path lies below the resolved root as well. --- internal/apply/apply.go | 16 +++++++++-- internal/apply/mesh_tree_found_test.go | 38 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) 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) + } +}