Skip to content

Commit 2f30958

Browse files
authored
Rely on os.Root for checking paths (#2752)
Remove pathIsInRepositoryRoot, and rely on implicit os.Root operations to check if a path is under the repository root.
1 parent d42983d commit 2f30958

File tree

2 files changed

+43
-66
lines changed

2 files changed

+43
-66
lines changed

internal/files/linkedfiles.go

Lines changed: 18 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ func CreateLinksFSFromPath(workDir string) (*LinksFS, error) {
4040
return nil, fmt.Errorf("opening repository root: %w", err)
4141
}
4242

43-
return NewLinksFS(root, workDir)
43+
absWorkDir, err := filepath.Abs(workDir)
44+
if err != nil {
45+
return nil, fmt.Errorf("obtaining absolute path of working directory: %w", err)
46+
}
47+
48+
return NewLinksFS(root, absWorkDir)
4449
}
4550

4651
var _ fs.FS = (*LinksFS)(nil)
@@ -56,39 +61,28 @@ type LinksFS struct {
5661
inner fs.FS
5762
}
5863

59-
// NewLinksFS creates a new LinksFS.
64+
// NewLinksFS creates a new LinksFS. workDir must be an absolute path, or a path relative to
65+
// the repository root.
6066
func NewLinksFS(repoRoot *os.Root, workDir string) (*LinksFS, error) {
6167
// Ensure workDir is absolute for os.DirFS
6268
var absWorkDir string
6369
if filepath.IsAbs(workDir) {
6470
absWorkDir = workDir
65-
} else {
66-
// First try: assume workDir is relative to the repository root
67-
absWorkDir = filepath.Join(repoRoot.Name(), workDir)
68-
69-
// Check if path exists
70-
if _, err := os.Stat(absWorkDir); os.IsNotExist(err) {
71-
// Second try: path might be relative to current working directory
72-
currentDir, err := os.Getwd()
73-
if err == nil {
74-
alternativePath := filepath.Join(currentDir, workDir)
75-
if _, err := os.Stat(alternativePath); err == nil {
76-
// If this path exists, use it instead
77-
absWorkDir = alternativePath
78-
logger.Debugf("Using path relative to current working directory: %s", absWorkDir)
79-
}
80-
}
71+
relative, err := filepath.Rel(repoRoot.Name(), absWorkDir)
72+
if err != nil {
73+
return nil, fmt.Errorf("invalid working directory %s: %w", absWorkDir, err)
8174
}
75+
workDir = relative
76+
} else {
77+
absWorkDir = filepath.Clean(filepath.Join(repoRoot.Name(), workDir))
8278
}
8379

84-
// Validate that workDir is within the repository root
85-
inRoot, err := pathIsInRepositoryRoot(repoRoot, absWorkDir)
80+
info, err := repoRoot.Stat(workDir)
8681
if err != nil {
87-
return nil, fmt.Errorf("could not validate workDir %s: %w", absWorkDir, err)
82+
return nil, fmt.Errorf("invalid working directory %s: %w", absWorkDir, err)
8883
}
89-
90-
if !inRoot {
91-
return nil, fmt.Errorf("workDir %s is outside the repository root %s", absWorkDir, repoRoot.Name())
84+
if !info.IsDir() {
85+
return nil, fmt.Errorf("working directory %s is not a directory", absWorkDir)
9286
}
9387

9488
return &LinksFS{repoRoot: repoRoot, workDir: absWorkDir, inner: os.DirFS(absWorkDir)}, nil
@@ -221,14 +215,6 @@ func newLinkedFile(root *os.Root, linkFilePath string) (Link, error) {
221215

222216
pathName := filepath.Clean(filepath.Join(l.WorkDir, filepath.FromSlash(l.IncludedFilePath)))
223217

224-
inRoot, err := pathIsInRepositoryRoot(root, pathName)
225-
if err != nil {
226-
return Link{}, fmt.Errorf("could not check if path %s is in repository root: %w", pathName, err)
227-
}
228-
if !inRoot {
229-
return Link{}, fmt.Errorf("path %s escapes the repository root", pathName)
230-
}
231-
232218
// Store the original absolute path for package root detection
233219
originalAbsPath := pathName
234220

@@ -545,28 +531,3 @@ func checksum(b []byte) (string, error) {
545531
hash := sha256.Sum256(b)
546532
return hex.EncodeToString(hash[:]), nil
547533
}
548-
549-
// pathIsInRepositoryRoot checks if a path is within the repository root and doesn't escape it.
550-
func pathIsInRepositoryRoot(root *os.Root, path string) (bool, error) {
551-
path = filepath.FromSlash(path)
552-
var err error
553-
if filepath.IsAbs(path) {
554-
path, err = filepath.Rel(root.Name(), path)
555-
if err != nil {
556-
return false, fmt.Errorf("could not get relative path: %w", err)
557-
}
558-
}
559-
560-
// Clean the path to resolve any ".." components
561-
cleanPath := filepath.Clean(path)
562-
563-
// Check if the cleaned path tries to escape the root
564-
if strings.HasPrefix(cleanPath, "..") {
565-
return false, nil
566-
}
567-
568-
if _, err := root.Stat(cleanPath); err != nil {
569-
return false, nil
570-
}
571-
return true, nil
572-
}

internal/files/linkedfiles_test.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"io"
1313
"os"
1414
"path/filepath"
15+
"runtime"
1516
"strings"
1617
"testing"
1718

@@ -33,7 +34,7 @@ func TestLinkUpdateChecksum(t *testing.T) {
3334
require.NoError(t, copyDir(testDataSrc, filepath.Join(tempDir, "testdata")))
3435

3536
// Set up paths within the temporary directory
36-
basePath := filepath.Join(tempDir, "testdata/links")
37+
basePath := filepath.Join(tempDir, "testdata", "links")
3738

3839
// Create an os.Root for secure file operations within tempDir
3940
root, err := os.OpenRoot(tempDir)
@@ -82,7 +83,7 @@ func TestListLinkedFiles(t *testing.T) {
8283
// Get current working directory to locate test data
8384
wd, err := os.Getwd()
8485
assert.NoError(t, err)
85-
basePath := filepath.Join(wd, filepath.FromSlash("testdata/links"))
86+
basePath := filepath.Join(wd, "testdata", "links")
8687

8788
// Find the repository root to create a secure os.Root context
8889
root, err := FindRepositoryRoot()
@@ -193,7 +194,7 @@ func TestUpdateLinkedFilesChecksums(t *testing.T) {
193194
require.NoError(t, copyDir(testDataSrc, filepath.Join(tempDir, "testdata")))
194195

195196
// Set up paths within the temporary directory
196-
basePath := filepath.Join(tempDir, "testdata/links")
197+
basePath := filepath.Join(tempDir, "testdata", "links")
197198

198199
// Create an os.Root for secure file operations within tempDir
199200
root, err := os.OpenRoot(tempDir)
@@ -227,7 +228,7 @@ func TestLinkedFilesByPackageFrom(t *testing.T) {
227228
// Get current working directory to locate test data
228229
wd, err := os.Getwd()
229230
assert.NoError(t, err)
230-
basePath := filepath.Join(wd, filepath.FromSlash("testdata/links"))
231+
basePath := filepath.Join(wd, "testdata", "links")
231232

232233
// Find the repository root to create a secure os.Root context
233234
root, err := FindRepositoryRoot()
@@ -269,14 +270,14 @@ func TestIncludeLinkedFiles(t *testing.T) {
269270
// Get current working directory to locate test data
270271
wd, err := os.Getwd()
271272
assert.NoError(t, err)
272-
testPkg := filepath.Join(wd, filepath.FromSlash("testdata"))
273+
testPkg := filepath.Join(wd, "testdata")
273274

274275
// Create a temporary directory and copy test data to avoid modifying originals
275276
tempDir := t.TempDir()
276277
require.NoError(t, copyDir(testPkg, filepath.Join(tempDir, "testdata")))
277278

278279
// Set up source and destination directories
279-
fromDir := filepath.Join(tempDir, "testdata/testpackage")
280+
fromDir := filepath.Join(tempDir, "testdata", "testpackage")
280281
toDir := filepath.Join(tempDir, "dest")
281282

282283
// Create an os.Root for secure file operations within tempDir
@@ -769,6 +770,11 @@ func TestLinksFS_ErrorConditions(t *testing.T) {
769770
err = os.WriteFile(invalidLinkFile, []byte(""), 0644)
770771
require.NoError(t, err)
771772

773+
// Create link that escapes root
774+
outOfRootLinkFile := filepath.Join(workDir, "escapesroot.txt.link")
775+
err = os.WriteFile(outOfRootLinkFile, []byte("../../etc/passwd"), 0644)
776+
require.NoError(t, err)
777+
772778
// Setup LinksFS
773779
root, err := os.OpenRoot(repoDir)
774780
require.NoError(t, err)
@@ -777,6 +783,11 @@ func TestLinksFS_ErrorConditions(t *testing.T) {
777783
lfs, err := NewLinksFS(root, workDir)
778784
require.NoError(t, err)
779785

786+
notFoundErrorMsg := "no such file or directory"
787+
if runtime.GOOS == "windows" {
788+
notFoundErrorMsg = "The system cannot find the file specified"
789+
}
790+
780791
tests := []struct {
781792
name string
782793
fileName string
@@ -785,13 +796,18 @@ func TestLinksFS_ErrorConditions(t *testing.T) {
785796
{
786797
name: "broken link to non-existent file",
787798
fileName: "broken.txt.link",
788-
errorMsg: "escapes the repository root",
799+
errorMsg: notFoundErrorMsg,
789800
},
790801
{
791802
name: "invalid link file format",
792803
fileName: "invalid.txt.link",
793804
errorMsg: "file is empty or first line is missing",
794805
},
806+
{
807+
name: "escapes root",
808+
fileName: "escapesroot.txt.link",
809+
errorMsg: "path escapes from parent",
810+
},
795811
}
796812

797813
for _, tc := range tests {
@@ -845,13 +861,13 @@ func TestLinksFS_WorkDirValidation(t *testing.T) {
845861
name: "invalid absolute workDir outside repo",
846862
workDir: outsideDir,
847863
expectError: true,
848-
errorMsg: "is outside the repository root",
864+
errorMsg: "path escapes from parent",
849865
},
850866
{
851867
name: "invalid relative workDir escaping repo",
852868
workDir: "../outside",
853869
expectError: true,
854-
errorMsg: "is outside the repository root",
870+
errorMsg: "path escapes from parent",
855871
},
856872
}
857873

0 commit comments

Comments
 (0)