Refuse a bundle whose output directory leads out of the module through a link, so a repository cannot make the build write into or pack files it does not hold (issue 1609)
mesh/merge-gate head 0caa8c6844d9 merged into trunk 0205b8f7080a — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 0caa8c6844d9 merged into trunk 0205b8f7080a — pass: its merge-check.sh passed
mesh/delivery delivered
mesh/merge-gate head 0caa8c6844d9 merged into trunk 0205b8f7080a — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 0caa8c6844d9 merged into trunk 0205b8f7080a — pass: its merge-check.sh passed
mesh/delivery delivered
This commit is contained in:
@@ -102,3 +102,48 @@ func TestAModuleDirectoryThatIsALinkOutOfTheRepositoryIsRefused(t *testing.T) {
|
||||
t.Errorf("an absent module directory was refused here: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// A repository that commits `.mesh-build` as a link must not make a bundle's build write into, or pack,
|
||||
// a directory outside the module (issue 1609): the compile, the bundling and the pack each refuse it.
|
||||
func TestABundleWhoseOutputDirectoryIsALinkOutOfTheModuleIsRefused(t *testing.T) {
|
||||
outside := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(outside, "secret"), []byte("s"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tree := t.TempDir()
|
||||
if err := os.Symlink(outside, filepath.Join(tree, ".mesh-build")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
chain, err := ToolchainFor("go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := catalogue.Artifact{Name: "b", Kind: catalogue.ArtifactBundle, Language: "go", From: "cmd/b"}
|
||||
ran := false
|
||||
run := func(context.Context, string, string, ...string) (string, error) { ran = true; return "", nil }
|
||||
realOutside, _ := filepath.EvalSymlinks(outside)
|
||||
realTree, _ := filepath.EvalSymlinks(tree)
|
||||
|
||||
check := func(what string, err error) {
|
||||
t.Helper()
|
||||
if err == nil {
|
||||
t.Fatalf("%s went through a link out of the module", what)
|
||||
}
|
||||
for _, want := range []string{realOutside, realTree} {
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("%s: the refusal %q does not name %q", what, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = compile(context.Background(), run, tree, chain, "base", a)
|
||||
check("compile", err)
|
||||
_, err = bundled(context.Background(), run, tree, chain, "base", a, nil)
|
||||
check("bundled", err)
|
||||
check("the pack", outsideTheModule(tree, filepath.Join(tree, Out(a.Name))))
|
||||
if ran {
|
||||
t.Error("a command ran for a refused bundle")
|
||||
}
|
||||
if entries, _ := os.ReadDir(outside); len(entries) != 1 {
|
||||
t.Errorf("a refused bundle wrote into the directory outside: %d entries", len(entries))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1295,6 +1295,9 @@ func one(ctx context.Context, run Runner, publish Publisher,
|
||||
}
|
||||
}
|
||||
say("bundle", "compiled, packing")
|
||||
if err := outsideTheModule(tree, compiled); err != nil {
|
||||
return catalogue.Built{}, fmt.Errorf("%s: packing %s failed: %w", module, a.Name, err)
|
||||
}
|
||||
body, err := pack(compiled)
|
||||
if err != nil {
|
||||
return catalogue.Built{}, fmt.Errorf("%s: packing %s failed: %w", module, a.Name, err)
|
||||
@@ -1384,6 +1387,36 @@ func archiveRoot(tree string, a catalogue.Artifact) (string, error) {
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// outsideTheModule refuses a path that, once every link in it is followed, is not inside the module's
|
||||
// directory, naming where it really is and where the module is. For a path not there yet (an output
|
||||
// directory about to be made) the deepest part that exists is the one followed. A repository commits
|
||||
// its own links, so `.mesh-build` as a link would otherwise have a bundle's build write into, and pack,
|
||||
// any directory the build account can read (issue 1609).
|
||||
func outsideTheModule(tree, path string) error {
|
||||
realTree, err := filepath.EvalSymlinks(tree)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
real, rest := path, ""
|
||||
for {
|
||||
resolved, err := filepath.EvalSymlinks(real)
|
||||
if err == nil {
|
||||
real = filepath.Join(resolved, rest)
|
||||
break
|
||||
}
|
||||
if !errors.Is(err, os.ErrNotExist) || filepath.Dir(real) == real {
|
||||
return err
|
||||
}
|
||||
rest = filepath.Join(filepath.Base(real), rest)
|
||||
real = filepath.Dir(real)
|
||||
}
|
||||
if rel, err := filepath.Rel(realTree, real); err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
|
||||
return fmt.Errorf("%s leads out of the module through a link: it resolves to %s, and the module is %s",
|
||||
path, real, realTree)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pack tars and gzips a directory.
|
||||
//
|
||||
// **Deterministically**: entries sorted, and no timestamps, uid, gid or original names carried
|
||||
@@ -1687,6 +1720,9 @@ func compile(ctx context.Context, run Runner, tree string, chain Toolchain,
|
||||
// a package in a third (ADR 0040). Compiling them all into one place would have them overwrite
|
||||
// each other and then be packed together, so each bundle compiles and packs alone.
|
||||
out := Out(a.Name)
|
||||
if err := outsideTheModule(tree, filepath.Join(tree, out)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// **The output directory exists before the compiler is told about it.** `tsc --outDir` makes
|
||||
// one; `go build -o` writes a file into a directory and does not create it, failing with a
|
||||
@@ -2013,6 +2049,9 @@ func bundled(ctx context.Context, run Runner, tree string, chain Toolchain, base
|
||||
a catalogue.Artifact, launchers map[string]string) (string, error) {
|
||||
const within = "/app/modules/module"
|
||||
out, final := Out(a.Name), Out(a.Name)+bundledSuffix
|
||||
if err := outsideTheModule(tree, filepath.Join(tree, final)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.RemoveAll(filepath.Join(tree, final)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user