diff --git a/solver/llbsolver/file/backend.go b/solver/llbsolver/file/backend.go index 3e380d53b..be431a4f7 100644 --- a/solver/llbsolver/file/backend.go +++ b/solver/llbsolver/file/backend.go @@ -162,12 +162,12 @@ func rm(d string, action *pb.FileActionRm) (err error) { } func rmPath(root, src string, allowNotFound bool) error { - src = filepath.Clean(src) + src = filepath.Join("/", src) dir, base := filepath.Split(src) if base == "" { return errors.New("rmPath: invalid empty path") } - dir, err := fs.RootPath(root, filepath.Join("/", dir)) + dir, err := fs.RootPath(root, dir) if err != nil { return errors.WithStack(err) } diff --git a/solver/llbsolver/file/backend_test.go b/solver/llbsolver/file/backend_test.go index 41bda56d9..3c9d798ed 100644 --- a/solver/llbsolver/file/backend_test.go +++ b/solver/llbsolver/file/backend_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "testing" + "github.com/moby/buildkit/solver/pb" "github.com/pkg/errors" "github.com/stretchr/testify/require" ) @@ -35,3 +36,45 @@ func TestRmPathFileExists(t *testing.T) { require.True(t, os.IsNotExist(err)) } + +func TestRmParentTraversalDoesNotEscapeRoot(t *testing.T) { + // Backslash variants are separators on Windows (real traversal there) and + // ordinary filename characters on Linux (inert, but still must not escape). + for _, p := range []string{ + "..", "../..", "a/../..", "../victim", + "..\\..", "a\\..\\..", "..\\victim", + } { + t.Run(p, func(t *testing.T) { + parent := t.TempDir() + root := filepath.Join(parent, "root") + victim := filepath.Join(parent, "victim") + + require.NoError(t, os.Mkdir(root, 0o755)) + require.NoError(t, os.Mkdir(victim, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(victim, "data"), []byte("data"), 0o644)) + + require.Error(t, rm(root, &pb.FileActionRm{Path: p})) + + _, err := os.Stat(victim) + require.NoError(t, err, "rm escaped root and deleted sibling %q", victim) + }) + } +} + +func TestRmPathRemovesSymlinkItself(t *testing.T) { + root := t.TempDir() + + target := filepath.Join(root, "target") + link := filepath.Join(root, "link") + + require.NoError(t, os.WriteFile(target, []byte("target"), 0o644)) + require.NoError(t, os.Symlink("target", link)) + + require.NoError(t, rmPath(root, "link", false)) + + _, err := os.Lstat(link) + require.True(t, os.IsNotExist(err)) + + _, err = os.Stat(target) + require.NoError(t, err) +}