fileop: contain rm parent traversal

Anchor rm paths before splitting them so parent traversal is normalized
relative to the fileop root before the final path component is appended.

Add regression coverage for parent traversal attempts and preserve removal of
terminal symlinks without following them.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit cc8b70d02fb890880e79abd44b9a0ad8b7e15d5b)
(cherry picked from commit 3916124aa4)
This commit is contained in:
Tonis Tiigi
2026-07-08 16:38:14 -07:00
parent 9e8014e443
commit 4ed7a2ff33
2 changed files with 45 additions and 2 deletions

View File

@@ -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)
}

View File

@@ -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)
}