Refuse a build output directory that is a link out of the module (issue 1589)
This commit is contained in:
@@ -300,3 +300,46 @@ func within(base, path string) bool {
|
||||
rel, err := filepath.Rel(base, path)
|
||||
return err == nil && rel != ".." && !strings.HasPrefix(rel, ".."+string(filepath.Separator))
|
||||
}
|
||||
|
||||
// resolveDeepest follows the links of the deepest part of p that exists and appends the rest, so a path not yet made
|
||||
// is judged by where it would be made.
|
||||
func resolveDeepest(p string) (string, error) {
|
||||
rest := ""
|
||||
for cur := filepath.Clean(p); ; {
|
||||
if real, err := filepath.EvalSymlinks(cur); err == nil {
|
||||
return filepath.Join(real, rest), nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
parent := filepath.Dir(cur)
|
||||
if parent == cur {
|
||||
return "", fmt.Errorf("no part of %s exists", p)
|
||||
}
|
||||
rest = filepath.Join(filepath.Base(cur), rest)
|
||||
cur = parent
|
||||
}
|
||||
}
|
||||
|
||||
// insideModule refuses a path whose real place is not inside the module's directory, which is itself inside the
|
||||
// workspace's tree: a link committed in the repository must not lead a build's writes or its pack out.
|
||||
func (m *Module) insideModule(workspace, dir, path string) error {
|
||||
realTree, err := filepath.EvalSymlinks(filepath.Join(m.Work, workspace, "tree"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
realDir, err := filepath.EvalSymlinks(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !within(realTree, realDir) {
|
||||
return fmt.Errorf("the module directory resolves to %s, which lies outside the workspace's tree %s", realDir, realTree)
|
||||
}
|
||||
real, err := resolveDeepest(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !within(realDir, real) {
|
||||
return fmt.Errorf("%s resolves to %s, which lies outside the module %s", path, real, realDir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -117,6 +117,9 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) {
|
||||
|
||||
// Its own output directory, made empty: a build of the same tree twice is one build.
|
||||
out := filepath.Join(dir, outDir, art.Name)
|
||||
if err := m.insideModule(workspace, dir, out); err != nil {
|
||||
return nil, fmt.Errorf("the output directory of %s: %w", art.Name, err)
|
||||
}
|
||||
if err := os.RemoveAll(out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,6 +136,9 @@ func (m *Module) build(ctx context.Context, args map[string]any) (any, error) {
|
||||
if r := m.Run(ctx, m.buildEnv(dir), "go", cmd...); r.Status != 0 || r.Err != "" {
|
||||
return nil, fmt.Errorf("go build of %s: %s", art.Name, causeLine(r.Err+"\n"+r.Stderr+"\n"+r.Stdout))
|
||||
}
|
||||
if err := m.insideModule(workspace, dir, out); err != nil {
|
||||
return nil, fmt.Errorf("the output directory of %s: %w", art.Name, err)
|
||||
}
|
||||
res, err := m.packInto(workspace, module, art.Name, out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -215,3 +215,27 @@ func TestAModuleDirectoryOutsideTheWorkspaceIsRefused(t *testing.T) {
|
||||
t.Errorf("the step ended %s after %d commands", st.State, len(*calls))
|
||||
}
|
||||
}
|
||||
|
||||
// Review of issue 1589: the output directory lies in the module; a committed `.mesh-build` that is a link out of it
|
||||
// must not take the build's writes, removals or the pack outside.
|
||||
func TestABuildOutputDirectoryLinkedOutsideTheModuleIsRefused(t *testing.T) {
|
||||
m, states, calls := buildModule(t, manifestWith(goBundle), "the program")
|
||||
outside := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(outside, "tools"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
kept := filepath.Join(outside, "tools", "precious")
|
||||
if err := os.WriteFile(kept, []byte("mine"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Symlink(outside, filepath.Join(m.Work, "ws1", "tree", "modules", "demo", ".mesh-build")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, st := runBuild(t, m, states, "tools")
|
||||
if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) || len(*calls) != 0 {
|
||||
t.Errorf("the step ended %s: %q after %d commands", st.State, st.Reason, len(*calls))
|
||||
}
|
||||
if body, err := os.ReadFile(kept); err != nil || string(body) != "mine" {
|
||||
t.Errorf("a file outside the module was touched: %q, %v", body, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user