github.com/moby/docker@v26.1.3+incompatible/internal/safepath/join_test.go (about)

     1  package safepath
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  	"testing"
    10  
    11  	"gotest.tools/v3/assert"
    12  	is "gotest.tools/v3/assert/cmp"
    13  )
    14  
    15  func TestJoinEscapingSymlink(t *testing.T) {
    16  	type testCase struct {
    17  		name   string
    18  		target string
    19  	}
    20  	var cases []testCase
    21  
    22  	if runtime.GOOS == "windows" {
    23  		cases = []testCase{
    24  			{name: "root", target: `C:\`},
    25  			{name: "absolute file", target: `C:\Windows\System32\cmd.exe`},
    26  		}
    27  	} else {
    28  		cases = []testCase{
    29  			{name: "root", target: "/"},
    30  			{name: "absolute file", target: "/etc/passwd"},
    31  		}
    32  	}
    33  	cases = append(cases, testCase{name: "relative", target: "../../"})
    34  
    35  	for _, tc := range cases {
    36  		t.Run(tc.name, func(t *testing.T) {
    37  			tempDir := t.TempDir()
    38  			dir, err := filepath.EvalSymlinks(tempDir)
    39  			assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir)
    40  
    41  			err = os.Symlink(tc.target, filepath.Join(dir, "link"))
    42  			assert.NilError(t, err, "failed to create symlink to %s", tc.target)
    43  
    44  			safe, err := Join(context.Background(), dir, "link")
    45  			if err == nil {
    46  				safe.Close(context.Background())
    47  			}
    48  			assert.ErrorType(t, err, &ErrEscapesBase{})
    49  		})
    50  	}
    51  }
    52  
    53  func TestJoinGoodSymlink(t *testing.T) {
    54  	tempDir := t.TempDir()
    55  	dir, err := filepath.EvalSymlinks(tempDir)
    56  	assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir)
    57  
    58  	assert.Assert(t, os.WriteFile(filepath.Join(dir, "foo"), []byte("bar"), 0o744), "failed to create file 'foo'")
    59  	assert.Assert(t, os.Mkdir(filepath.Join(dir, "subdir"), 0o744), "failed to create directory 'subdir'")
    60  	assert.Assert(t, os.WriteFile(filepath.Join(dir, "subdir/hello.txt"), []byte("world"), 0o744), "failed to create file 'subdir/hello.txt'")
    61  
    62  	assert.Assert(t, os.Symlink(filepath.Join(dir, "subdir"), filepath.Join(dir, "subdir_link_absolute")), "failed to create absolute symlink to directory 'subdir'")
    63  	assert.Assert(t, os.Symlink("subdir", filepath.Join(dir, "subdir_link_relative")), "failed to create relative symlink to directory 'subdir'")
    64  
    65  	assert.Assert(t, os.Symlink(filepath.Join(dir, "foo"), filepath.Join(dir, "foo_link_absolute")), "failed to create absolute symlink to file 'foo'")
    66  	assert.Assert(t, os.Symlink("foo", filepath.Join(dir, "foo_link_relative")), "failed to create relative symlink to file 'foo'")
    67  
    68  	for _, target := range []string{
    69  		"foo", "subdir",
    70  		"subdir_link_absolute", "foo_link_absolute",
    71  		"subdir_link_relative", "foo_link_relative",
    72  	} {
    73  		t.Run(target, func(t *testing.T) {
    74  			safe, err := Join(context.Background(), dir, target)
    75  			assert.NilError(t, err)
    76  
    77  			defer safe.Close(context.Background())
    78  			if strings.HasPrefix(target, "subdir") {
    79  				data, err := os.ReadFile(filepath.Join(safe.Path(), "hello.txt"))
    80  				assert.NilError(t, err)
    81  				assert.Assert(t, is.Equal(string(data), "world"))
    82  			}
    83  		})
    84  	}
    85  }
    86  
    87  func TestJoinWithSymlinkReplace(t *testing.T) {
    88  	tempDir := t.TempDir()
    89  	dir, err := filepath.EvalSymlinks(tempDir)
    90  	assert.NilError(t, err, "filepath.EvalSymlinks failed for temporary directory %s", tempDir)
    91  
    92  	link := filepath.Join(dir, "link")
    93  	target := filepath.Join(dir, "foo")
    94  
    95  	err = os.WriteFile(target, []byte("bar"), 0o744)
    96  	assert.NilError(t, err, "failed to create test file")
    97  
    98  	err = os.Symlink(target, link)
    99  	assert.Check(t, err, "failed to create symlink to foo")
   100  
   101  	safe, err := Join(context.Background(), dir, "link")
   102  	assert.NilError(t, err)
   103  
   104  	defer safe.Close(context.Background())
   105  
   106  	// Delete the link target.
   107  	err = os.Remove(target)
   108  	if runtime.GOOS == "windows" {
   109  		// On Windows it shouldn't be possible.
   110  		assert.Assert(t, is.ErrorType(err, &os.PathError{}), "link shouldn't be deletable before cleanup")
   111  	} else {
   112  		// On Linux we can delete it just fine.
   113  		assert.NilError(t, err, "failed to remove symlink")
   114  
   115  		// Replace target with a symlink to /etc/paswd
   116  		err = os.Symlink("/etc/passwd", target)
   117  		assert.NilError(t, err, "failed to create symlink")
   118  	}
   119  
   120  	// The returned safe path should still point to the old file.
   121  	data, err := os.ReadFile(safe.Path())
   122  	assert.NilError(t, err, "failed to read file")
   123  
   124  	assert.Check(t, is.Equal(string(data), "bar"))
   125  
   126  }
   127  
   128  func TestJoinCloseInvalidates(t *testing.T) {
   129  	tempDir := t.TempDir()
   130  	dir, err := filepath.EvalSymlinks(tempDir)
   131  	assert.NilError(t, err)
   132  
   133  	foo := filepath.Join(dir, "foo")
   134  	err = os.WriteFile(foo, []byte("bar"), 0o744)
   135  	assert.NilError(t, err, "failed to create test file")
   136  
   137  	safe, err := Join(context.Background(), dir, "foo")
   138  	assert.NilError(t, err)
   139  
   140  	assert.Check(t, safe.IsValid())
   141  
   142  	assert.NilError(t, safe.Close(context.Background()))
   143  
   144  	assert.Check(t, !safe.IsValid())
   145  }