github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/escapingfs/escapes_test.go (about)

     1  package escapingfs
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  )
    12  
    13  func write(t *testing.T, file, data string) {
    14  	err := ioutil.WriteFile(file, []byte(data), 0600)
    15  	require.NoError(t, err)
    16  }
    17  
    18  func Test_PathEscapesAllocViaRelative(t *testing.T) {
    19  	for _, test := range []struct {
    20  		prefix string
    21  		path   string
    22  		exp    bool
    23  	}{
    24  		// directly under alloc-dir/alloc-id/
    25  		{prefix: "", path: "", exp: false},
    26  		{prefix: "", path: "/foo", exp: false},
    27  		{prefix: "", path: "./", exp: false},
    28  		{prefix: "", path: "../", exp: true}, // at alloc-id/
    29  
    30  		// under alloc-dir/alloc-id/<foo>/
    31  		{prefix: "foo", path: "", exp: false},
    32  		{prefix: "foo", path: "/foo", exp: false},
    33  		{prefix: "foo", path: "../", exp: false},   // at foo/
    34  		{prefix: "foo", path: "../../", exp: true}, // at alloc-id/
    35  
    36  		// under alloc-dir/alloc-id/foo/bar/
    37  		{prefix: "foo/bar", path: "", exp: false},
    38  		{prefix: "foo/bar", path: "/foo", exp: false},
    39  		{prefix: "foo/bar", path: "../", exp: false},      // at bar/
    40  		{prefix: "foo/bar", path: "../../", exp: false},   // at foo/
    41  		{prefix: "foo/bar", path: "../../../", exp: true}, // at alloc-id/
    42  	} {
    43  		result, err := PathEscapesAllocViaRelative(test.prefix, test.path)
    44  		require.NoError(t, err)
    45  		require.Equal(t, test.exp, result)
    46  	}
    47  }
    48  
    49  func Test_pathEscapesBaseViaSymlink(t *testing.T) {
    50  	t.Run("symlink-escape", func(t *testing.T) {
    51  		dir := t.TempDir()
    52  
    53  		// link from dir/link
    54  		link := filepath.Join(dir, "link")
    55  
    56  		// link to /tmp
    57  		target := filepath.Clean("/tmp")
    58  		err := os.Symlink(target, link)
    59  		require.NoError(t, err)
    60  
    61  		escape, err := pathEscapesBaseViaSymlink(dir, link)
    62  		require.NoError(t, err)
    63  		require.True(t, escape)
    64  	})
    65  
    66  	t.Run("symlink-noescape", func(t *testing.T) {
    67  		dir := t.TempDir()
    68  
    69  		// create a file within dir
    70  		target := filepath.Join(dir, "foo")
    71  		write(t, target, "hi")
    72  
    73  		// link to file within dir
    74  		link := filepath.Join(dir, "link")
    75  		err := os.Symlink(target, link)
    76  		require.NoError(t, err)
    77  
    78  		// link to file within dir does not escape dir
    79  		escape, err := pathEscapesBaseViaSymlink(dir, link)
    80  		require.NoError(t, err)
    81  		require.False(t, escape)
    82  	})
    83  }
    84  
    85  func Test_PathEscapesAllocDir(t *testing.T) {
    86  
    87  	t.Run("no-escape-root", func(t *testing.T) {
    88  		dir := t.TempDir()
    89  
    90  		escape, err := PathEscapesAllocDir(dir, "", "/")
    91  		require.NoError(t, err)
    92  		require.False(t, escape)
    93  	})
    94  
    95  	t.Run("no-escape", func(t *testing.T) {
    96  		dir := t.TempDir()
    97  
    98  		write(t, filepath.Join(dir, "foo"), "hi")
    99  
   100  		escape, err := PathEscapesAllocDir(dir, "", "/foo")
   101  		require.NoError(t, err)
   102  		require.False(t, escape)
   103  	})
   104  
   105  	t.Run("no-escape-no-exist", func(t *testing.T) {
   106  		dir := t.TempDir()
   107  
   108  		escape, err := PathEscapesAllocDir(dir, "", "/no-exist")
   109  		require.NoError(t, err)
   110  		require.False(t, escape)
   111  	})
   112  
   113  	t.Run("symlink-escape", func(t *testing.T) {
   114  		dir := t.TempDir()
   115  
   116  		// link from dir/link
   117  		link := filepath.Join(dir, "link")
   118  
   119  		// link to /tmp
   120  		target := filepath.Clean("/tmp")
   121  		err := os.Symlink(target, link)
   122  		require.NoError(t, err)
   123  
   124  		escape, err := PathEscapesAllocDir(dir, "", "/link")
   125  		require.NoError(t, err)
   126  		require.True(t, escape)
   127  	})
   128  
   129  	t.Run("relative-escape", func(t *testing.T) {
   130  		dir := t.TempDir()
   131  
   132  		escape, err := PathEscapesAllocDir(dir, "", "../../foo")
   133  		require.NoError(t, err)
   134  		require.True(t, escape)
   135  	})
   136  
   137  	t.Run("relative-escape-prefix", func(t *testing.T) {
   138  		dir := t.TempDir()
   139  
   140  		escape, err := PathEscapesAllocDir(dir, "/foo/bar", "../../../foo")
   141  		require.NoError(t, err)
   142  		require.True(t, escape)
   143  	})
   144  }
   145  
   146  func TestPathEscapesSandbox(t *testing.T) {
   147  	cases := []struct {
   148  		name     string
   149  		path     string
   150  		dir      string
   151  		expected bool
   152  	}{
   153  		{
   154  			// this is the ${NOMAD_SECRETS_DIR} case
   155  			name:     "ok joined absolute path inside sandbox",
   156  			path:     filepath.Join("/alloc", "/secrets"),
   157  			dir:      "/alloc",
   158  			expected: false,
   159  		},
   160  		{
   161  			name:     "fail unjoined absolute path outside sandbox",
   162  			path:     "/secrets",
   163  			dir:      "/alloc",
   164  			expected: true,
   165  		},
   166  		{
   167  			name:     "ok joined relative path inside sandbox",
   168  			path:     filepath.Join("/alloc", "./safe"),
   169  			dir:      "/alloc",
   170  			expected: false,
   171  		},
   172  		{
   173  			name:     "fail unjoined relative path outside sandbox",
   174  			path:     "./safe",
   175  			dir:      "/alloc",
   176  			expected: true,
   177  		},
   178  		{
   179  			name:     "ok relative path traversal constrained to sandbox",
   180  			path:     filepath.Join("/alloc", "../../alloc/safe"),
   181  			dir:      "/alloc",
   182  			expected: false,
   183  		},
   184  		{
   185  			name:     "ok unjoined absolute path traversal constrained to sandbox",
   186  			path:     filepath.Join("/alloc", "/../alloc/safe"),
   187  			dir:      "/alloc",
   188  			expected: false,
   189  		},
   190  		{
   191  			name:     "ok unjoined absolute path traversal constrained to sandbox",
   192  			path:     "/../alloc/safe",
   193  			dir:      "/alloc",
   194  			expected: false,
   195  		},
   196  		{
   197  			name:     "fail joined relative path traverses outside sandbox",
   198  			path:     filepath.Join("/alloc", "../../../unsafe"),
   199  			dir:      "/alloc",
   200  			expected: true,
   201  		},
   202  		{
   203  			name:     "fail unjoined relative path traverses outside sandbox",
   204  			path:     "../../../unsafe",
   205  			dir:      "/alloc",
   206  			expected: true,
   207  		},
   208  		{
   209  			name:     "fail joined absolute path tries to transverse outside sandbox",
   210  			path:     filepath.Join("/alloc", "/alloc/../../unsafe"),
   211  			dir:      "/alloc",
   212  			expected: true,
   213  		},
   214  		{
   215  			name:     "fail unjoined absolute path tries to transverse outside sandbox",
   216  			path:     "/alloc/../../unsafe",
   217  			dir:      "/alloc",
   218  			expected: true,
   219  		},
   220  	}
   221  
   222  	for _, tc := range cases {
   223  		t.Run(tc.name, func(t *testing.T) {
   224  			caseMsg := fmt.Sprintf("path: %v\ndir: %v", tc.path, tc.dir)
   225  			escapes := PathEscapesSandbox(tc.dir, tc.path)
   226  			require.Equal(t, tc.expected, escapes, caseMsg)
   227  		})
   228  	}
   229  }