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 }