-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #138 from benmoss/invalid-symlinks
- Loading branch information
Showing
4 changed files
with
258 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
// Copyright 2020 VMware, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package directory | ||
|
||
import ( | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
) | ||
|
||
// ValidateSymlinks enforces that symlinks inside the given path resolve to inside the path | ||
func ValidateSymlinks(path string) error { | ||
absRoot, err := filepath.Abs(path) | ||
if err != nil { | ||
return err | ||
} | ||
rootSegments := strings.Split(absRoot, string(os.PathSeparator)) | ||
return filepath.WalkDir(path, func(path string, info fs.DirEntry, err error) error { | ||
if info.Type()&os.ModeSymlink == os.ModeSymlink { | ||
resolvedPath, err := filepath.EvalSymlinks(path) | ||
if err != nil { | ||
return fmt.Errorf("Unable to resolve symlink: %w", err) | ||
} | ||
absPath, err := filepath.Abs(resolvedPath) | ||
if err != nil { | ||
return err | ||
} | ||
pathSegments := strings.Split(absPath, string(os.PathSeparator)) | ||
|
||
if len(rootSegments) > len(pathSegments) { | ||
return fmt.Errorf("Invalid symlink found to outside parent directory: %q", absPath) | ||
} | ||
for i, segment := range rootSegments { | ||
if pathSegments[i] != segment { | ||
return fmt.Errorf("Invalid symlink found to outside parent directory: %q", absPath) | ||
} | ||
} | ||
} | ||
return nil | ||
}) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Copyright 2020 VMware, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package directory | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
) | ||
|
||
func TestValidateSymlinks(t *testing.T) { | ||
root, err := os.MkdirTemp("", "vendir-test") | ||
if err != nil { | ||
t.Fatalf("failed to create tmpdir: %v", err) | ||
} | ||
wd := filepath.Join(root, "wd") | ||
validFilePath := filepath.Join(wd, "file") | ||
|
||
sibling := filepath.Join(root, "wd2") | ||
siblingFilePath := filepath.Join(sibling, "file") | ||
|
||
for _, path := range []string{wd, sibling} { | ||
if err = os.Mkdir(path, os.ModePerm); err != nil { | ||
t.Fatalf("failed to create dir: %v", err) | ||
} | ||
} | ||
for _, path := range []string{validFilePath, siblingFilePath} { | ||
file, err := os.Create(path) | ||
if err != nil { | ||
t.Fatalf("failed to create file: %v", err) | ||
} | ||
file.Close() | ||
} | ||
defer os.RemoveAll(root) | ||
|
||
tests := []struct { | ||
name string | ||
target string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "valid symlink", | ||
target: validFilePath, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "valid symlink to containing directory", | ||
target: wd, | ||
wantErr: false, | ||
}, | ||
{ | ||
name: "invalid symlink", | ||
target: siblingFilePath, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "invalid symlink to sibling directory", | ||
target: sibling, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "invalid symlink to parent directory", | ||
target: root, | ||
wantErr: true, | ||
}, | ||
{ | ||
name: "invalid symlink to non-existent path", | ||
target: filepath.Join(wd, "foo"), | ||
wantErr: true, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
test := func(t *testing.T) { | ||
newName := filepath.Join(wd, "symlink") | ||
t.Logf("target: %q\n", tt.target) | ||
err := os.Symlink(tt.target, newName) | ||
if err != nil { | ||
t.Fatalf("creating symlink: %v", err) | ||
} | ||
defer os.Remove(newName) | ||
if err := ValidateSymlinks(wd); (err != nil) != tt.wantErr { | ||
t.Errorf("ValidateSymlinks() error = %v, wantErr %v", err, tt.wantErr) | ||
} | ||
} | ||
|
||
t.Run(tt.name+" absolute", test) | ||
t.Run(tt.name+" relative", func(t *testing.T) { | ||
oldName, err := filepath.Rel(wd, tt.target) | ||
if err != nil { | ||
t.Fatalf("relativizing path: %v", err) | ||
} | ||
tt.target = oldName | ||
test(t) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
// Copyright 2020 VMware, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package e2e | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/vmware-tanzu/carvel-vendir/pkg/vendir/config" | ||
"gopkg.in/yaml.v2" | ||
) | ||
|
||
func TestInvalidSymlink(t *testing.T) { | ||
env := BuildEnv(t) | ||
vendir := Vendir{t, env.BinaryPath, Logger{}} | ||
|
||
tmpDir, err := os.MkdirTemp("", "vendir-test") | ||
if err != nil { | ||
t.Fatalf("creating tmpdir: %v", err) | ||
} | ||
defer os.RemoveAll(tmpDir) | ||
|
||
symlinkDir := filepath.Join(tmpDir, "symlink-dir") | ||
err = os.Mkdir(symlinkDir, os.ModePerm) | ||
if err != nil { | ||
t.Fatalf("creating symlink dir: %v", err) | ||
|
||
} | ||
|
||
// valid since it is in the symlink-dir | ||
validFilePath := filepath.Join(symlinkDir, "a_valid_file.txt") | ||
validFile, err := os.Create(validFilePath) | ||
if err != nil { | ||
t.Fatalf("creating file: %v", err) | ||
} | ||
validFile.Close() | ||
|
||
//invalid since it is outside the symlink-dir | ||
invalidFilePath := filepath.Join(tmpDir, "invalid_file.txt") | ||
invalidFile, err := os.Create(invalidFilePath) | ||
if err != nil { | ||
t.Fatalf("creating file: %v", err) | ||
} | ||
invalidFile.Close() | ||
|
||
config := config.Config{ | ||
APIVersion: "vendir.k14s.io/v1alpha1", | ||
Kind: "Config", | ||
Directories: []config.Directory{{ | ||
Path: "result", | ||
Contents: []config.DirectoryContents{{ | ||
Path: "bad", | ||
Directory: &config.DirectoryContentsDirectory{ | ||
Path: "symlink-dir", | ||
}, | ||
}}, | ||
}}, | ||
} | ||
vendirYML, err := os.Create(filepath.Join(tmpDir, "vendir.yml")) | ||
if err != nil { | ||
t.Fatalf("creating vendir.yml: %v", err) | ||
} | ||
defer vendirYML.Close() | ||
|
||
err = yaml.NewEncoder(vendirYML).Encode(&config) | ||
if err != nil { | ||
t.Fatalf("writing vendir.yml: %v", err) | ||
} | ||
|
||
tests := []struct { | ||
symlinkLocation string | ||
valid bool | ||
expectedErr string | ||
}{ | ||
{symlinkLocation: "a_valid_file.txt", valid: true}, | ||
{symlinkLocation: invalidFilePath, valid: false, expectedErr: "Invalid symlink found to outside parent directory"}, | ||
{symlinkLocation: "non_existent_file.txt", valid: false, expectedErr: "Unable to resolve symlink"}, | ||
} | ||
for _, tc := range tests { | ||
symlinkPath := filepath.Join(symlinkDir, "file") | ||
err = os.Symlink(tc.symlinkLocation, symlinkPath) | ||
if err != nil { | ||
t.Fatalf("creating symlink: %v", err) | ||
} | ||
|
||
_, err = vendir.RunWithOpts([]string{"sync"}, RunOpts{Dir: tmpDir, AllowError: true}) | ||
if tc.valid && err != nil { | ||
t.Fatalf("unexpected err: %v", err) | ||
} | ||
if !tc.valid { | ||
if err == nil { | ||
t.Fatalf("expected an err, got none") | ||
} | ||
if !strings.Contains(err.Error(), tc.expectedErr) { | ||
t.Fatalf("Expected invalid symlink err: %s", err) | ||
} | ||
} | ||
|
||
err = os.Remove(symlinkPath) | ||
if err != nil { | ||
t.Fatalf("deleting symlink: %v", err) | ||
} | ||
} | ||
} |