gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/utils_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "syscall" 29 30 . "gopkg.in/check.v1" 31 32 update "gitee.com/mysnapcore/mysnapd/cmd/snap-update-ns" 33 "gitee.com/mysnapcore/mysnapd/logger" 34 "gitee.com/mysnapcore/mysnapd/osutil" 35 "gitee.com/mysnapcore/mysnapd/osutil/sys" 36 "gitee.com/mysnapcore/mysnapd/testutil" 37 ) 38 39 type utilsSuite struct { 40 testutil.BaseTest 41 sys *testutil.SyscallRecorder 42 log *bytes.Buffer 43 as *update.Assumptions 44 } 45 46 var _ = Suite(&utilsSuite{}) 47 48 func (s *utilsSuite) SetUpTest(c *C) { 49 s.BaseTest.SetUpTest(c) 50 s.sys = &testutil.SyscallRecorder{} 51 s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys)) 52 buf, restore := logger.MockLogger() 53 s.BaseTest.AddCleanup(restore) 54 s.log = buf 55 s.as = &update.Assumptions{} 56 } 57 58 func (s *utilsSuite) TearDownTest(c *C) { 59 s.BaseTest.TearDownTest(c) 60 s.sys.CheckForStrayDescriptors(c) 61 } 62 63 // secure-mkdir-all 64 65 // Ensure that we reject unclean paths. 66 func (s *utilsSuite) TestSecureMkdirAllUnclean(c *C) { 67 err := update.MkdirAll("/unclean//path", 0755, 123, 456, nil) 68 c.Assert(err, ErrorMatches, `cannot split unclean path .*`) 69 c.Assert(s.sys.RCalls(), HasLen, 0) 70 } 71 72 // Ensure that we refuse to create a directory with an relative path. 73 func (s *utilsSuite) TestSecureMkdirAllRelative(c *C) { 74 err := update.MkdirAll("rel/path", 0755, 123, 456, nil) 75 c.Assert(err, ErrorMatches, `cannot create directory with relative path: "rel/path"`) 76 c.Assert(s.sys.RCalls(), HasLen, 0) 77 } 78 79 // Ensure that we can "create the root directory. 80 func (s *utilsSuite) TestSecureMkdirAllLevel0(c *C) { 81 c.Assert(update.MkdirAll("/", 0755, 123, 456, nil), IsNil) 82 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 83 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 84 {C: `close 3`}, 85 }) 86 } 87 88 // Ensure that we can create a directory in the top-level directory. 89 func (s *utilsSuite) TestSecureMkdirAllLevel1(c *C) { 90 c.Assert(update.MkdirAll("/path", 0755, 123, 456, nil), IsNil) 91 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 92 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 93 {C: `mkdirat 3 "path" 0755`}, 94 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 95 {C: `fchown 4 123 456`}, 96 {C: `close 4`}, 97 {C: `close 3`}, 98 }) 99 } 100 101 // Ensure that we can create a directory two levels from the top-level directory. 102 func (s *utilsSuite) TestSecureMkdirAllLevel2(c *C) { 103 c.Assert(update.MkdirAll("/path/to", 0755, 123, 456, nil), IsNil) 104 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 105 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 106 {C: `mkdirat 3 "path" 0755`}, 107 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 108 {C: `fchown 4 123 456`}, 109 {C: `close 3`}, 110 {C: `mkdirat 4 "to" 0755`}, 111 {C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 112 {C: `fchown 3 123 456`}, 113 {C: `close 3`}, 114 {C: `close 4`}, 115 }) 116 } 117 118 // Ensure that we can create a directory three levels from the top-level directory. 119 func (s *utilsSuite) TestSecureMkdirAllLevel3(c *C) { 120 c.Assert(update.MkdirAll("/path/to/something", 0755, 123, 456, nil), IsNil) 121 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 122 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 123 {C: `mkdirat 3 "path" 0755`}, 124 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 125 {C: `fchown 4 123 456`}, 126 {C: `mkdirat 4 "to" 0755`}, 127 {C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 128 {C: `fchown 5 123 456`}, 129 {C: `close 4`}, 130 {C: `close 3`}, 131 {C: `mkdirat 5 "something" 0755`}, 132 {C: `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 133 {C: `fchown 3 123 456`}, 134 {C: `close 3`}, 135 {C: `close 5`}, 136 }) 137 } 138 139 // Ensure that we are not masking out the sticky bit when creating directories 140 func (s *utilsSuite) TestSecureMkdirAllAllowsStickyBit(c *C) { 141 s.sys.InsertFault(`mkdirat 3 "dev" 01777`, syscall.EEXIST) 142 s.sys.InsertFault(`mkdirat 4 "shm" 01777`, syscall.EEXIST) 143 c.Assert(update.MkdirAll("/dev/shm/snap.foo", 0777|os.ModeSticky, 0, 0, nil), IsNil) 144 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 145 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 146 {C: `mkdirat 3 "dev" 01777`, E: syscall.EEXIST}, 147 {C: `openat 3 "dev" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 148 {C: `mkdirat 4 "shm" 01777`, E: syscall.EEXIST}, 149 {C: `openat 4 "shm" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 150 {C: `close 4`}, 151 {C: `close 3`}, 152 {C: `mkdirat 5 "snap.foo" 01777`}, 153 {C: `openat 5 "snap.foo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 154 {C: `fchown 3 0 0`}, 155 {C: `close 3`}, 156 {C: `close 5`}, 157 }) 158 } 159 160 // Ensure that trespassing for prefix is matched using clean base path. 161 func (s *utilsSuite) TestTrespassingMatcher(c *C) { 162 // We mounted tmpfs at "/path". 163 s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/path", Type: "tmpfs", Name: "tmpfs"}}) 164 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 165 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 166 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 167 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 168 s.sys.InsertFault(`mkdirat 3 "path" 0755`, syscall.EEXIST) 169 rs := s.as.RestrictionsFor("/path/to/something") 170 // Trespassing detector checked "/path", not "/path/" (which would not match). 171 c.Assert(update.MkdirAll("/path/to/something", 0755, 123, 456, rs), IsNil) 172 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 173 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 174 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 175 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 176 {C: `mkdirat 3 "path" 0755`, E: syscall.EEXIST}, 177 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 178 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}}, 179 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 180 181 {C: `mkdirat 4 "to" 0755`}, 182 {C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 183 {C: `fchown 5 123 456`}, 184 {C: `close 4`}, 185 {C: `close 3`}, 186 {C: `mkdirat 5 "something" 0755`}, 187 {C: `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 188 {C: `fchown 3 123 456`}, 189 {C: `close 3`}, 190 {C: `close 5`}, 191 }) 192 } 193 194 // Ensure that writes to /etc/demo are interrupted if /etc is restricted. 195 func (s *utilsSuite) TestSecureMkdirAllWithRestrictedEtc(c *C) { 196 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 197 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 198 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 199 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 200 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 201 rs := s.as.RestrictionsFor("/etc/demo") 202 err := update.MkdirAll("/etc/demo", 0755, 123, 456, rs) 203 c.Assert(err, ErrorMatches, `cannot write to "/etc/demo" because it would affect the host in "/etc"`) 204 c.Assert(err.(*update.TrespassingError).ViolatedPath, Equals, "/etc") 205 c.Assert(err.(*update.TrespassingError).DesiredPath, Equals, "/etc/demo") 206 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 207 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 208 // we are inspecting the type of the filesystem we are about to perform operation on. 209 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 210 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 211 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 212 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 213 {C: `close 3`}, 214 // ext4 is writable, refuse further operations. 215 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 216 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 217 {C: `close 4`}, 218 }) 219 } 220 221 // Ensure that writes to /etc/demo allowed if /etc is unrestricted. 222 func (s *utilsSuite) TestSecureMkdirAllWithUnrestrictedEtc(c *C) { 223 defer s.as.MockUnrestrictedPaths("/etc")() // Mark /etc as unrestricted. 224 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 225 rs := s.as.RestrictionsFor("/etc/demo") 226 c.Assert(update.MkdirAll("/etc/demo", 0755, 123, 456, rs), IsNil) 227 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 228 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 229 // We are not interested in the type of filesystem at / 230 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 231 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 232 // We are not interested in the type of filesystem at /etc 233 {C: `close 3`}, 234 {C: `mkdirat 4 "demo" 0755`}, 235 {C: `openat 4 "demo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 236 {C: `fchown 3 123 456`}, 237 {C: `close 3`}, 238 {C: `close 4`}, 239 }) 240 } 241 242 // Ensure that we can detect read only filesystems. 243 func (s *utilsSuite) TestSecureMkdirAllROFS(c *C) { 244 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) // just realistic 245 s.sys.InsertFault(`mkdirat 4 "path" 0755`, syscall.EROFS) 246 err := update.MkdirAll("/rofs/path", 0755, 123, 456, nil) 247 c.Assert(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`) 248 c.Assert(err.(*update.ReadOnlyFsError).Path, Equals, "/rofs") 249 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 250 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 251 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 252 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 253 {C: `close 3`}, 254 {C: `mkdirat 4 "path" 0755`, E: syscall.EROFS}, 255 {C: `close 4`}, 256 }) 257 } 258 259 // Ensure that we don't chown existing directories. 260 func (s *utilsSuite) TestSecureMkdirAllExistingDirsDontChown(c *C) { 261 s.sys.InsertFault(`mkdirat 3 "abs" 0755`, syscall.EEXIST) 262 s.sys.InsertFault(`mkdirat 4 "path" 0755`, syscall.EEXIST) 263 err := update.MkdirAll("/abs/path", 0755, 123, 456, nil) 264 c.Assert(err, IsNil) 265 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 266 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 267 {C: `mkdirat 3 "abs" 0755`, E: syscall.EEXIST}, 268 {C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 269 {C: `close 3`}, 270 {C: `mkdirat 4 "path" 0755`, E: syscall.EEXIST}, 271 {C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 272 {C: `close 3`}, 273 {C: `close 4`}, 274 }) 275 } 276 277 // Ensure that we we close everything when mkdirat fails. 278 func (s *utilsSuite) TestSecureMkdirAllMkdiratError(c *C) { 279 s.sys.InsertFault(`mkdirat 3 "abs" 0755`, errTesting) 280 err := update.MkdirAll("/abs", 0755, 123, 456, nil) 281 c.Assert(err, ErrorMatches, `cannot create directory "/abs": testing`) 282 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 283 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 284 {C: `mkdirat 3 "abs" 0755`, E: errTesting}, 285 {C: `close 3`}, 286 }) 287 } 288 289 // Ensure that we we close everything when fchown fails. 290 func (s *utilsSuite) TestSecureMkdirAllFchownError(c *C) { 291 s.sys.InsertFault(`fchown 4 123 456`, errTesting) 292 err := update.MkdirAll("/path", 0755, 123, 456, nil) 293 c.Assert(err, ErrorMatches, `cannot chown directory "/path" to 123.456: testing`) 294 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 295 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 296 {C: `mkdirat 3 "path" 0755`}, 297 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 298 {C: `fchown 4 123 456`, E: errTesting}, 299 {C: `close 4`}, 300 {C: `close 3`}, 301 }) 302 } 303 304 // Check error path when we cannot open root directory. 305 func (s *utilsSuite) TestSecureMkdirAllOpenRootError(c *C) { 306 s.sys.InsertFault(`open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) 307 err := update.MkdirAll("/abs/path", 0755, 123, 456, nil) 308 c.Assert(err, ErrorMatches, "cannot open root directory: testing") 309 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 310 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting}, 311 }) 312 } 313 314 // Check error path when we cannot open non-root directory. 315 func (s *utilsSuite) TestSecureMkdirAllOpenError(c *C) { 316 s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) 317 err := update.MkdirAll("/abs/path", 0755, 123, 456, nil) 318 c.Assert(err, ErrorMatches, `cannot open directory "/abs": testing`) 319 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 320 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 321 {C: `mkdirat 3 "abs" 0755`}, 322 {C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting}, 323 {C: `close 3`}, 324 }) 325 } 326 327 func (s *utilsSuite) TestPlanWritableMimic(c *C) { 328 s.sys.InsertSysLstatResult(`lstat "/foo" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 329 restore := update.MockReadDir(func(dir string) ([]os.FileInfo, error) { 330 c.Assert(dir, Equals, "/foo") 331 return []os.FileInfo{ 332 testutil.FakeFileInfo("file", 0), 333 testutil.FakeFileInfo("dir", os.ModeDir), 334 testutil.FakeFileInfo("symlink", os.ModeSymlink), 335 testutil.FakeFileInfo("error-symlink-readlink", os.ModeSymlink), 336 // NOTE: None of the filesystem entries below are supported because 337 // they cannot be placed inside snaps or can only be created at 338 // runtime in areas that are already writable and this would never 339 // have to be handled in a writable mimic. 340 testutil.FakeFileInfo("block-dev", os.ModeDevice), 341 testutil.FakeFileInfo("char-dev", os.ModeDevice|os.ModeCharDevice), 342 testutil.FakeFileInfo("socket", os.ModeSocket), 343 testutil.FakeFileInfo("pipe", os.ModeNamedPipe), 344 }, nil 345 }) 346 defer restore() 347 restore = update.MockReadlink(func(name string) (string, error) { 348 switch name { 349 case "/foo/symlink": 350 return "target", nil 351 case "/foo/error-symlink-readlink": 352 return "", errTesting 353 } 354 panic("unexpected") 355 }) 356 defer restore() 357 358 changes, err := update.PlanWritableMimic("/foo", "/foo/bar") 359 c.Assert(err, IsNil) 360 361 c.Assert(changes, DeepEquals, []*update.Change{ 362 // Store /foo in /tmp/.snap/foo while we set things up 363 {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, 364 // Put a tmpfs over /foo 365 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar", "mode=0755", "uid=0", "gid=0"}}, Action: update.Mount}, 366 // Bind mount files and directories over. Note that files are identified by x-snapd.kind=file option. 367 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 368 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 369 // Create symlinks. 370 // Bad symlinks and all other file types are skipped and not 371 // recorded in mount changes. 372 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 373 // Unmount the safe-keeping directory 374 {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, 375 }) 376 } 377 378 func (s *utilsSuite) TestPlanWritableMimicErrors(c *C) { 379 s.sys.InsertSysLstatResult(`lstat "/foo" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 380 restore := update.MockReadDir(func(dir string) ([]os.FileInfo, error) { 381 c.Assert(dir, Equals, "/foo") 382 return nil, errTesting 383 }) 384 defer restore() 385 restore = update.MockReadlink(func(name string) (string, error) { 386 return "", errTesting 387 }) 388 defer restore() 389 390 changes, err := update.PlanWritableMimic("/foo", "/foo/bar") 391 c.Assert(err, ErrorMatches, "testing") 392 c.Assert(changes, HasLen, 0) 393 } 394 395 func (s *utilsSuite) TestExecWirableMimicSuccess(c *C) { 396 // This plan is the same as in the test above. This is what comes out of planWritableMimic. 397 plan := []*update.Change{ 398 {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, 399 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 400 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 401 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 402 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 403 {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, 404 } 405 406 // Mock the act of performing changes, each of the change we perform is coming from the plan. 407 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 408 c.Assert(plan, testutil.DeepContains, chg) 409 return nil, nil 410 }) 411 defer restore() 412 413 // The executed plan leaves us with a simplified view of the plan that is suitable for undo. 414 undoPlan, err := update.ExecWritableMimic(plan, s.as) 415 c.Assert(err, IsNil) 416 c.Assert(undoPlan, DeepEquals, []*update.Change{ 417 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 418 {Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 419 {Entry: osutil.MountEntry{Name: "/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar", "x-snapd.detach"}}, Action: update.Mount}, 420 }) 421 } 422 423 func (s *utilsSuite) TestExecWirableMimicErrorWithRecovery(c *C) { 424 // This plan is the same as in the test above. This is what comes out of planWritableMimic. 425 plan := []*update.Change{ 426 {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, 427 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 428 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 429 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 430 // NOTE: the next perform will fail. Notably the symlink did not fail. 431 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind"}}, Action: update.Mount}, 432 {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, 433 } 434 435 // Mock the act of performing changes. Before we inject a failure we ensure 436 // that each of the change we perform is coming from the plan. For the 437 // purpose of the test the change that bind mounts the "dir" over itself 438 // will fail and will trigger an recovery path. The changes performed in 439 // the recovery path are recorded. 440 var recoveryPlan []*update.Change 441 recovery := false 442 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 443 if !recovery { 444 c.Assert(plan, testutil.DeepContains, chg) 445 if chg.Entry.Name == "/tmp/.snap/foo/dir" { 446 recovery = true // switch to recovery mode 447 return nil, errTesting 448 } 449 } else { 450 recoveryPlan = append(recoveryPlan, chg) 451 } 452 return nil, nil 453 }) 454 defer restore() 455 456 // The executed plan fails, leaving us with the error and an empty undo plan. 457 undoPlan, err := update.ExecWritableMimic(plan, s.as) 458 c.Assert(err, Equals, errTesting) 459 c.Assert(undoPlan, HasLen, 0) 460 // The changes we managed to perform were undone correctly. 461 c.Assert(recoveryPlan, DeepEquals, []*update.Change{ 462 // NOTE: there is no symlink undo entry as it is implicitly undone by unmounting the tmpfs. 463 {Entry: osutil.MountEntry{Name: "/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Unmount}, 464 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Unmount}, 465 {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind", "x-snapd.detach"}}, Action: update.Unmount}, 466 }) 467 } 468 469 func (s *utilsSuite) TestExecWirableMimicErrorNothingDone(c *C) { 470 // This plan is the same as in the test above. This is what comes out of planWritableMimic. 471 plan := []*update.Change{ 472 {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, 473 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 474 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 475 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 476 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 477 {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, 478 } 479 480 // Mock the act of performing changes and just fail on any request. 481 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 482 return nil, errTesting 483 }) 484 defer restore() 485 486 // The executed plan fails, the recovery didn't fail (it's empty) so we just return that error. 487 undoPlan, err := update.ExecWritableMimic(plan, s.as) 488 c.Assert(err, Equals, errTesting) 489 c.Assert(undoPlan, HasLen, 0) 490 } 491 492 func (s *utilsSuite) TestExecWirableMimicErrorCannotUndo(c *C) { 493 // This plan is the same as in the test above. This is what comes out of planWritableMimic. 494 plan := []*update.Change{ 495 {Entry: osutil.MountEntry{Name: "/foo", Dir: "/tmp/.snap/foo", Options: []string{"rbind"}}, Action: update.Mount}, 496 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/foo", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 497 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/file", Dir: "/foo/file", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 498 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/dir", Dir: "/foo/dir", Options: []string{"rbind", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 499 {Entry: osutil.MountEntry{Name: "/tmp/.snap/foo/symlink", Dir: "/foo/symlink", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=target", "x-snapd.synthetic", "x-snapd.needed-by=/foo/bar"}}, Action: update.Mount}, 500 {Entry: osutil.MountEntry{Name: "none", Dir: "/tmp/.snap/foo", Options: []string{"x-snapd.detach"}}, Action: update.Unmount}, 501 } 502 503 // Mock the act of performing changes. After performing the first change 504 // correctly we will fail forever (this includes the recovery path) so the 505 // execute function ends up in a situation where it cannot perform the 506 // recovery path and will have to return a fatal error. 507 i := -1 508 restore := update.MockChangePerform(func(chg *update.Change, as *update.Assumptions) ([]*update.Change, error) { 509 i++ 510 if i > 0 { 511 return nil, fmt.Errorf("failure-%d", i) 512 } 513 return nil, nil 514 }) 515 defer restore() 516 517 // The plan partially succeeded and we cannot undo those changes. 518 _, err := update.ExecWritableMimic(plan, s.as) 519 c.Assert(err, ErrorMatches, `cannot undo change ".*" while recovering from earlier error failure-1: failure-2`) 520 c.Assert(err, FitsTypeOf, &update.FatalError{}) 521 } 522 523 // realSystemSuite is not isolated / mocked from the system. 524 type realSystemSuite struct { 525 as *update.Assumptions 526 } 527 528 var _ = Suite(&realSystemSuite{}) 529 530 func (s *realSystemSuite) SetUpTest(c *C) { 531 s.as = &update.Assumptions{} 532 s.as.AddUnrestrictedPaths("/tmp") 533 } 534 535 // Check that we can actually create directories. 536 // This doesn't test the chown logic as that requires root. 537 func (s *realSystemSuite) TestSecureMkdirAllForReal(c *C) { 538 d := c.MkDir() 539 540 // Create d (which already exists) with mode 0777 (but c.MkDir() used 0700 541 // internally and since we are not creating the directory we should not be 542 // changing that. 543 c.Assert(update.MkdirAll(d, 0777, sys.FlagID, sys.FlagID, nil), IsNil) 544 fi, err := os.Stat(d) 545 c.Assert(err, IsNil) 546 c.Check(fi.IsDir(), Equals, true) 547 c.Check(fi.Mode().Perm(), Equals, os.FileMode(0700)) 548 549 // Create d1, which is a simple subdirectory, with a distinct mode and 550 // check that it was applied. Note that default umask 022 is subtracted so 551 // effective directory has different permissions. 552 d1 := filepath.Join(d, "subdir") 553 c.Assert(update.MkdirAll(d1, 0707, sys.FlagID, sys.FlagID, nil), IsNil) 554 fi, err = os.Stat(d1) 555 c.Assert(err, IsNil) 556 c.Check(fi.IsDir(), Equals, true) 557 c.Check(fi.Mode().Perm(), Equals, os.FileMode(0705)) 558 559 // Create d2, which is a deeper subdirectory, with another distinct mode 560 // and check that it was applied. 561 d2 := filepath.Join(d, "subdir/subdir/subdir") 562 c.Assert(update.MkdirAll(d2, 0750, sys.FlagID, sys.FlagID, nil), IsNil) 563 fi, err = os.Stat(d2) 564 c.Assert(err, IsNil) 565 c.Check(fi.IsDir(), Equals, true) 566 c.Check(fi.Mode().Perm(), Equals, os.FileMode(0750)) 567 } 568 569 // secure-mkfile-all 570 571 // Ensure that we reject unclean paths. 572 func (s *utilsSuite) TestSecureMkfileAllUnclean(c *C) { 573 err := update.MkfileAll("/unclean//path", 0755, 123, 456, nil) 574 c.Assert(err, ErrorMatches, `cannot split unclean path .*`) 575 c.Assert(s.sys.RCalls(), HasLen, 0) 576 } 577 578 // Ensure that we refuse to create a file with an relative path. 579 func (s *utilsSuite) TestSecureMkfileAllRelative(c *C) { 580 err := update.MkfileAll("rel/path", 0755, 123, 456, nil) 581 c.Assert(err, ErrorMatches, `cannot create file with relative path: "rel/path"`) 582 c.Assert(s.sys.RCalls(), HasLen, 0) 583 } 584 585 // Ensure that we refuse creating the root directory as a file. 586 func (s *utilsSuite) TestSecureMkfileAllLevel0(c *C) { 587 err := update.MkfileAll("/", 0755, 123, 456, nil) 588 c.Assert(err, ErrorMatches, `cannot create non-file path: "/"`) 589 c.Assert(s.sys.RCalls(), HasLen, 0) 590 } 591 592 // Ensure that we can create a file in the top-level directory. 593 func (s *utilsSuite) TestSecureMkfileAllLevel1(c *C) { 594 c.Assert(update.MkfileAll("/path", 0755, 123, 456, nil), IsNil) 595 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 596 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 597 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 4}, 598 {C: `fchown 4 123 456`}, 599 {C: `close 4`}, 600 {C: `close 3`}, 601 }) 602 } 603 604 // Ensure that we can create a file two levels from the top-level directory. 605 func (s *utilsSuite) TestSecureMkfileAllLevel2(c *C) { 606 c.Assert(update.MkfileAll("/path/to", 0755, 123, 456, nil), IsNil) 607 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 608 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 609 {C: `mkdirat 3 "path" 0755`}, 610 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 611 {C: `fchown 4 123 456`}, 612 {C: `close 3`}, 613 {C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3}, 614 {C: `fchown 3 123 456`}, 615 {C: `close 3`}, 616 {C: `close 4`}, 617 }) 618 } 619 620 // Ensure that we can create a file three levels from the top-level directory. 621 func (s *utilsSuite) TestSecureMkfileAllLevel3(c *C) { 622 c.Assert(update.MkfileAll("/path/to/something", 0755, 123, 456, nil), IsNil) 623 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 624 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 625 {C: `mkdirat 3 "path" 0755`}, 626 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 627 {C: `fchown 4 123 456`}, 628 {C: `mkdirat 4 "to" 0755`}, 629 {C: `openat 4 "to" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 630 {C: `fchown 5 123 456`}, 631 {C: `close 4`}, 632 {C: `close 3`}, 633 {C: `openat 5 "something" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3}, 634 {C: `fchown 3 123 456`}, 635 {C: `close 3`}, 636 {C: `close 5`}, 637 }) 638 } 639 640 // Ensure that we can detect read only filesystems. 641 func (s *utilsSuite) TestSecureMkfileAllROFS(c *C) { 642 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) // just realistic 643 s.sys.InsertFault(`openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EROFS) 644 err := update.MkfileAll("/rofs/path", 0755, 123, 456, nil) 645 c.Check(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`) 646 c.Assert(err.(*update.ReadOnlyFsError).Path, Equals, "/rofs") 647 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 648 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 649 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 650 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 651 {C: `close 3`}, 652 {C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: syscall.EROFS}, 653 {C: `close 4`}, 654 }) 655 } 656 657 // Ensure that we don't chown existing files or directories. 658 func (s *utilsSuite) TestSecureMkfileAllExistingDirsDontChown(c *C) { 659 s.sys.InsertFault(`mkdirat 3 "abs" 0755`, syscall.EEXIST) 660 s.sys.InsertFault(`openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EEXIST) 661 err := update.MkfileAll("/abs/path", 0755, 123, 456, nil) 662 c.Check(err, IsNil) 663 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 664 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 665 {C: `mkdirat 3 "abs" 0755`, E: syscall.EEXIST}, 666 {C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 667 {C: `close 3`}, 668 {C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: syscall.EEXIST}, 669 {C: `openat 4 "path" O_NOFOLLOW|O_CLOEXEC 0`, R: 3}, 670 {C: `close 3`}, 671 {C: `close 4`}, 672 }) 673 } 674 675 // Ensure that we we close everything when openat fails. 676 func (s *utilsSuite) TestSecureMkfileAllOpenat2ndError(c *C) { 677 s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EEXIST) 678 s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC 0`, errTesting) 679 err := update.MkfileAll("/abs", 0755, 123, 456, nil) 680 c.Assert(err, ErrorMatches, `cannot open file "/abs": testing`) 681 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 682 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 683 {C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: syscall.EEXIST}, 684 {C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC 0`, E: errTesting}, 685 {C: `close 3`}, 686 }) 687 } 688 689 // Ensure that we we close everything when openat (non-exclusive) fails. 690 func (s *utilsSuite) TestSecureMkfileAllOpenatError(c *C) { 691 s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, errTesting) 692 err := update.MkfileAll("/abs", 0755, 123, 456, nil) 693 c.Assert(err, ErrorMatches, `cannot open file "/abs": testing`) 694 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 695 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 696 {C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: errTesting}, 697 {C: `close 3`}, 698 }) 699 } 700 701 // Ensure that we we close everything when fchown fails. 702 func (s *utilsSuite) TestSecureMkfileAllFchownError(c *C) { 703 s.sys.InsertFault(`fchown 4 123 456`, errTesting) 704 err := update.MkfileAll("/path", 0755, 123, 456, nil) 705 c.Assert(err, ErrorMatches, `cannot chown file "/path" to 123.456: testing`) 706 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 707 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 708 {C: `openat 3 "path" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 4}, 709 {C: `fchown 4 123 456`, E: errTesting}, 710 {C: `close 4`}, 711 {C: `close 3`}, 712 }) 713 } 714 715 // Check error path when we cannot open root directory. 716 func (s *utilsSuite) TestSecureMkfileAllOpenRootError(c *C) { 717 s.sys.InsertFault(`open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) 718 err := update.MkfileAll("/abs/path", 0755, 123, 456, nil) 719 c.Assert(err, ErrorMatches, "cannot open root directory: testing") 720 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 721 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting}, 722 }) 723 } 724 725 // Check error path when we cannot open non-root directory. 726 func (s *utilsSuite) TestSecureMkfileAllOpenError(c *C) { 727 s.sys.InsertFault(`openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, errTesting) 728 err := update.MkfileAll("/abs/path", 0755, 123, 456, nil) 729 c.Assert(err, ErrorMatches, `cannot open directory "/abs": testing`) 730 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 731 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 732 {C: `mkdirat 3 "abs" 0755`}, 733 {C: `openat 3 "abs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, E: errTesting}, 734 {C: `close 3`}, 735 }) 736 } 737 738 // We want to create a symlink in $SNAP_DATA and that's fine. 739 func (s *utilsSuite) TestSecureMksymlinkAllInSnapData(c *C) { 740 s.sys.InsertFault(`mkdirat 3 "var" 0755`, syscall.EEXIST) 741 s.sys.InsertFault(`mkdirat 4 "snap" 0755`, syscall.EEXIST) 742 s.sys.InsertFault(`mkdirat 5 "foo" 0755`, syscall.EEXIST) 743 s.sys.InsertFault(`mkdirat 6 "42" 0755`, syscall.EEXIST) 744 745 err := update.MksymlinkAll("/var/snap/foo/42/symlink", 0755, 0, 0, "/oldname", nil) 746 c.Assert(err, IsNil) 747 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 748 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 749 {C: `mkdirat 3 "var" 0755`, E: syscall.EEXIST}, 750 {C: `openat 3 "var" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 751 {C: `mkdirat 4 "snap" 0755`, E: syscall.EEXIST}, 752 {C: `openat 4 "snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 753 {C: `mkdirat 5 "foo" 0755`, E: syscall.EEXIST}, 754 {C: `openat 5 "foo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 6}, 755 {C: `mkdirat 6 "42" 0755`, E: syscall.EEXIST}, 756 {C: `openat 6 "42" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 7}, 757 {C: `close 6`}, 758 {C: `close 5`}, 759 {C: `close 4`}, 760 {C: `close 3`}, 761 {C: `symlinkat "/oldname" 7 "symlink"`}, 762 {C: `close 7`}, 763 }) 764 } 765 766 // We want to create a symlink in /etc but the host filesystem would be affected. 767 func (s *utilsSuite) TestSecureMksymlinkAllInEtc(c *C) { 768 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 769 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 770 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 771 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 772 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 773 rs := s.as.RestrictionsFor("/etc/symlink") 774 err := update.MksymlinkAll("/etc/symlink", 0755, 0, 0, "/oldname", rs) 775 c.Assert(err, ErrorMatches, `cannot write to "/etc/symlink" because it would affect the host in "/etc"`) 776 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 777 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 778 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 779 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 780 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 781 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 782 {C: `close 3`}, 783 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 784 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 785 {C: `close 4`}, 786 }) 787 } 788 789 // We want to create a symlink deep in /etc but the host filesystem would be affected. 790 // This just shows that we pick the right place to construct the mimic 791 func (s *utilsSuite) TestSecureMksymlinkAllDeepInEtc(c *C) { 792 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 793 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 794 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 795 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 796 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 797 rs := s.as.RestrictionsFor("/etc/some/other/stuff/symlink") 798 err := update.MksymlinkAll("/etc/some/other/stuff/symlink", 0755, 0, 0, "/oldname", rs) 799 c.Assert(err, ErrorMatches, `cannot write to "/etc/some/other/stuff/symlink" because it would affect the host in "/etc"`) 800 c.Assert(err.(*update.TrespassingError).ViolatedPath, Equals, "/etc") 801 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 802 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 803 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 804 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 805 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 806 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 807 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 808 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 809 {C: `close 4`}, 810 {C: `close 3`}, 811 }) 812 } 813 814 // We want to create a file in /etc but the host filesystem would be affected. 815 func (s *utilsSuite) TestSecureMkfileAllInEtc(c *C) { 816 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 817 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 818 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 819 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 820 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 821 rs := s.as.RestrictionsFor("/etc/file") 822 err := update.MkfileAll("/etc/file", 0755, 0, 0, rs) 823 c.Assert(err, ErrorMatches, `cannot write to "/etc/file" because it would affect the host in "/etc"`) 824 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 825 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 826 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 827 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 828 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 829 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 830 {C: `close 3`}, 831 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 832 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 833 {C: `close 4`}, 834 }) 835 } 836 837 // We want to create a directory in /etc but the host filesystem would be affected. 838 func (s *utilsSuite) TestSecureMkdirAllInEtc(c *C) { 839 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 840 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 841 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 842 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 843 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 844 rs := s.as.RestrictionsFor("/etc/dir") 845 err := update.MkdirAll("/etc/dir", 0755, 0, 0, rs) 846 c.Assert(err, ErrorMatches, `cannot write to "/etc/dir" because it would affect the host in "/etc"`) 847 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 848 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 849 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 850 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 851 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 852 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 853 {C: `close 3`}, 854 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 855 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 856 {C: `close 4`}, 857 }) 858 } 859 860 // We want to create a directory in /snap/foo/42/dir and want to know what happens. 861 func (s *utilsSuite) TestSecureMkdirAllInSNAP(c *C) { 862 // Allow creating directories under /snap/ related to this snap ("foo"). 863 // This matches what is done inside main(). 864 restore := s.as.MockUnrestrictedPaths("/snap/foo") 865 defer restore() 866 867 s.sys.InsertFault(`mkdirat 3 "snap" 0755`, syscall.EEXIST) 868 s.sys.InsertFault(`mkdirat 4 "foo" 0755`, syscall.EEXIST) 869 s.sys.InsertFault(`mkdirat 5 "42" 0755`, syscall.EEXIST) 870 871 rs := s.as.RestrictionsFor("/snap/foo/42/dir") 872 err := update.MkdirAll("/snap/foo/42/dir", 0755, 0, 0, rs) 873 c.Assert(err, IsNil) 874 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 875 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 876 {C: `mkdirat 3 "snap" 0755`, E: syscall.EEXIST}, 877 {C: `openat 3 "snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 878 {C: `mkdirat 4 "foo" 0755`, E: syscall.EEXIST}, 879 {C: `openat 4 "foo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 880 {C: `mkdirat 5 "42" 0755`, E: syscall.EEXIST}, 881 {C: `openat 5 "42" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 6}, 882 {C: `close 5`}, 883 {C: `close 4`}, 884 {C: `close 3`}, 885 {C: `mkdirat 6 "dir" 0755`}, 886 {C: `openat 6 "dir" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 887 {C: `fchown 3 0 0`}, 888 {C: `close 3`}, 889 {C: `close 6`}, 890 }) 891 } 892 893 // We want to create a symlink in /etc which is a tmpfs that we mounted so that is ok. 894 func (s *utilsSuite) TestSecureMksymlinkAllInEtcAfterMimic(c *C) { 895 // Because /etc is not on a list of unrestricted paths the write to 896 // /etc/symlink must be validated with step-by-step operation. 897 rootStatfs := syscall.Statfs_t{Type: update.SquashfsMagic, Flags: update.StReadOnly} 898 rootStat := syscall.Stat_t{} 899 etcStatfs := syscall.Statfs_t{Type: update.TmpfsMagic} 900 etcStat := syscall.Stat_t{} 901 s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/etc", Type: "tmpfs", Name: "tmpfs"}}) 902 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, rootStatfs) 903 s.sys.InsertFstatResult(`fstat 3 <ptr>`, rootStat) 904 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 905 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, etcStatfs) 906 s.sys.InsertFstatResult(`fstat 4 <ptr>`, etcStat) 907 rs := s.as.RestrictionsFor("/etc/symlink") 908 err := update.MksymlinkAll("/etc/symlink", 0755, 0, 0, "/oldname", rs) 909 c.Assert(err, IsNil) 910 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 911 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 912 {C: `fstatfs 3 <ptr>`, R: rootStatfs}, 913 {C: `fstat 3 <ptr>`, R: rootStat}, 914 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 915 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 916 {C: `close 3`}, 917 {C: `fstatfs 4 <ptr>`, R: etcStatfs}, 918 {C: `fstat 4 <ptr>`, R: etcStat}, 919 {C: `symlinkat "/oldname" 4 "symlink"`}, 920 {C: `close 4`}, 921 }) 922 } 923 924 // We want to create a file in /etc which is a tmpfs created by snapd so that's okay. 925 func (s *utilsSuite) TestSecureMkfileAllInEtcAfterMimic(c *C) { 926 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 927 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 928 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 929 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 930 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 931 s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/etc", Type: "tmpfs", Name: "tmpfs"}}) 932 rs := s.as.RestrictionsFor("/etc/file") 933 err := update.MkfileAll("/etc/file", 0755, 0, 0, rs) 934 c.Assert(err, IsNil) 935 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 936 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 937 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 938 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 939 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 940 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 941 {C: `close 3`}, 942 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}}, 943 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 944 {C: `openat 4 "file" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3}, 945 {C: `fchown 3 0 0`}, 946 {C: `close 3`}, 947 {C: `close 4`}, 948 }) 949 } 950 951 // We want to create a directory in /etc which is a tmpfs created by snapd so that is ok. 952 func (s *utilsSuite) TestSecureMkdirAllInEtcAfterMimic(c *C) { 953 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 954 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 955 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 956 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 957 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 958 s.as.AddChange(&update.Change{Action: update.Mount, Entry: osutil.MountEntry{Dir: "/etc", Type: "tmpfs", Name: "tmpfs"}}) 959 rs := s.as.RestrictionsFor("/etc/dir") 960 err := update.MkdirAll("/etc/dir", 0755, 0, 0, rs) 961 c.Assert(err, IsNil) 962 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 963 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 964 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 965 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 966 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 967 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 968 {C: `close 3`}, 969 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}}, 970 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 971 {C: `mkdirat 4 "dir" 0755`}, 972 {C: `openat 4 "dir" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 973 {C: `fchown 3 0 0`}, 974 {C: `close 3`}, 975 {C: `close 4`}, 976 }) 977 } 978 979 // Check that we can actually create files. 980 // This doesn't test the chown logic as that requires root. 981 func (s *realSystemSuite) TestSecureMkfileAllForReal(c *C) { 982 d := c.MkDir() 983 984 // Create f1, which is a simple subdirectory, with a distinct mode and 985 // check that it was applied. Note that default umask 022 is subtracted so 986 // effective directory has different permissions. 987 f1 := filepath.Join(d, "file") 988 c.Assert(update.MkfileAll(f1, 0707, sys.FlagID, sys.FlagID, nil), IsNil) 989 fi, err := os.Stat(f1) 990 c.Assert(err, IsNil) 991 c.Check(fi.Mode().IsRegular(), Equals, true) 992 c.Check(fi.Mode().Perm(), Equals, os.FileMode(0705)) 993 994 // Create f2, which is a deeper subdirectory, with another distinct mode 995 // and check that it was applied. 996 f2 := filepath.Join(d, "subdir/subdir/file") 997 c.Assert(update.MkfileAll(f2, 0750, sys.FlagID, sys.FlagID, nil), IsNil) 998 fi, err = os.Stat(f2) 999 c.Assert(err, IsNil) 1000 c.Check(fi.Mode().IsRegular(), Equals, true) 1001 c.Check(fi.Mode().Perm(), Equals, os.FileMode(0750)) 1002 } 1003 1004 // Check that we can actually create symlinks. 1005 // This doesn't test the chown logic as that requires root. 1006 func (s *realSystemSuite) TestSecureMksymlinkAllForReal(c *C) { 1007 d := c.MkDir() 1008 1009 // Create symlink f1 that points to "oldname" and check that it 1010 // is correct. Note that symlink permissions are always set to 0777 1011 f1 := filepath.Join(d, "symlink") 1012 err := update.MksymlinkAll(f1, 0755, sys.FlagID, sys.FlagID, "oldname", nil) 1013 c.Assert(err, IsNil) 1014 fi, err := os.Lstat(f1) 1015 c.Assert(err, IsNil) 1016 c.Check(fi.Mode()&os.ModeSymlink, Equals, os.ModeSymlink) 1017 c.Check(fi.Mode().Perm(), Equals, os.FileMode(0777)) 1018 1019 target, err := os.Readlink(f1) 1020 c.Assert(err, IsNil) 1021 c.Check(target, Equals, "oldname") 1022 1023 // Create an identical symlink to see that it doesn't fail. 1024 err = update.MksymlinkAll(f1, 0755, sys.FlagID, sys.FlagID, "oldname", nil) 1025 c.Assert(err, IsNil) 1026 1027 // Create a different symlink and see that it fails now 1028 err = update.MksymlinkAll(f1, 0755, sys.FlagID, sys.FlagID, "other", nil) 1029 c.Assert(err, ErrorMatches, `cannot create symbolic link ".*/symlink": existing symbolic link in the way`) 1030 1031 // Create an file and check that it clashes with a symlink we attempt to create. 1032 f2 := filepath.Join(d, "file") 1033 err = update.MkfileAll(f2, 0755, sys.FlagID, sys.FlagID, nil) 1034 c.Assert(err, IsNil) 1035 err = update.MksymlinkAll(f2, 0755, sys.FlagID, sys.FlagID, "oldname", nil) 1036 c.Assert(err, ErrorMatches, `cannot create symbolic link ".*/file": existing file in the way`) 1037 1038 // Create an file and check that it clashes with a symlink we attempt to create. 1039 f3 := filepath.Join(d, "dir") 1040 err = update.MkdirAll(f3, 0755, sys.FlagID, sys.FlagID, nil) 1041 c.Assert(err, IsNil) 1042 err = update.MksymlinkAll(f3, 0755, sys.FlagID, sys.FlagID, "oldname", nil) 1043 c.Assert(err, ErrorMatches, `cannot create symbolic link ".*/dir": existing file in the way`) 1044 1045 err = update.MksymlinkAll("/", 0755, sys.FlagID, sys.FlagID, "oldname", nil) 1046 c.Assert(err, ErrorMatches, `cannot create non-file path: "/"`) 1047 } 1048 1049 func (s *utilsSuite) TestCleanTrailingSlash(c *C) { 1050 // This is a validity test for the use of filepath.Clean in secureMk{dir,file}All 1051 c.Assert(filepath.Clean("/path/"), Equals, "/path") 1052 c.Assert(filepath.Clean("path/"), Equals, "path") 1053 c.Assert(filepath.Clean("path/."), Equals, "path") 1054 c.Assert(filepath.Clean("path/.."), Equals, ".") 1055 c.Assert(filepath.Clean("other/path/.."), Equals, "other") 1056 } 1057 1058 // secure-open-path 1059 1060 func (s *utilsSuite) TestSecureOpenPath(c *C) { 1061 stat := syscall.Stat_t{Mode: syscall.S_IFDIR} 1062 s.sys.InsertFstatResult("fstat 5 <ptr>", stat) 1063 fd, err := update.OpenPath("/foo/bar") 1064 c.Assert(err, IsNil) 1065 defer s.sys.Close(fd) 1066 c.Assert(fd, Equals, 5) 1067 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1068 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1069 {C: `openat 3 "foo" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 1070 {C: `openat 4 "bar" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1071 {C: `fstat 5 <ptr>`, R: stat}, 1072 {C: `close 4`}, 1073 {C: `close 3`}, 1074 }) 1075 } 1076 1077 func (s *utilsSuite) TestSecureOpenPathSingleSegment(c *C) { 1078 stat := syscall.Stat_t{Mode: syscall.S_IFDIR} 1079 s.sys.InsertFstatResult("fstat 4 <ptr>", stat) 1080 fd, err := update.OpenPath("/foo") 1081 c.Assert(err, IsNil) 1082 defer s.sys.Close(fd) 1083 c.Assert(fd, Equals, 4) 1084 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1085 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1086 {C: `openat 3 "foo" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1087 {C: `fstat 4 <ptr>`, R: stat}, 1088 {C: `close 3`}, 1089 }) 1090 } 1091 1092 func (s *utilsSuite) TestSecureOpenPathRoot(c *C) { 1093 stat := syscall.Stat_t{Mode: syscall.S_IFDIR} 1094 s.sys.InsertFstatResult("fstat 3 <ptr>", stat) 1095 fd, err := update.OpenPath("/") 1096 c.Assert(err, IsNil) 1097 defer s.sys.Close(fd) 1098 c.Assert(fd, Equals, 3) 1099 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1100 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1101 {C: `fstat 3 <ptr>`, R: stat}, 1102 }) 1103 } 1104 1105 func (s *realSystemSuite) TestSecureOpenPathDirectory(c *C) { 1106 path := filepath.Join(c.MkDir(), "test") 1107 c.Assert(os.Mkdir(path, 0755), IsNil) 1108 1109 fd, err := update.OpenPath(path) 1110 c.Assert(err, IsNil) 1111 defer syscall.Close(fd) 1112 1113 // check that the file descriptor is for the expected path 1114 origDir, err := os.Getwd() 1115 c.Assert(err, IsNil) 1116 defer os.Chdir(origDir) 1117 1118 c.Assert(syscall.Fchdir(fd), IsNil) 1119 cwd, err := os.Getwd() 1120 c.Assert(err, IsNil) 1121 c.Check(cwd, Equals, path) 1122 } 1123 1124 func (s *realSystemSuite) TestSecureOpenPathRelativePath(c *C) { 1125 fd, err := update.OpenPath("relative/path") 1126 c.Check(fd, Equals, -1) 1127 c.Check(err, ErrorMatches, "path .* is not absolute") 1128 } 1129 1130 func (s *realSystemSuite) TestSecureOpenPathUncleanPath(c *C) { 1131 base := c.MkDir() 1132 path := filepath.Join(base, "test") 1133 c.Assert(os.Mkdir(path, 0755), IsNil) 1134 1135 fd, err := update.OpenPath(base + "//test") 1136 c.Check(fd, Equals, -1) 1137 c.Check(err, ErrorMatches, `cannot open path: cannot iterate over unclean path ".*//test"`) 1138 1139 fd, err = update.OpenPath(base + "/./test") 1140 c.Check(fd, Equals, -1) 1141 c.Check(err, ErrorMatches, `cannot open path: cannot iterate over unclean path ".*/./test"`) 1142 1143 fd, err = update.OpenPath(base + "/test/../test") 1144 c.Check(fd, Equals, -1) 1145 c.Check(err, ErrorMatches, `cannot open path: cannot iterate over unclean path ".*/test/../test"`) 1146 } 1147 1148 func (s *realSystemSuite) TestSecureOpenPathFile(c *C) { 1149 path := filepath.Join(c.MkDir(), "file.txt") 1150 c.Assert(ioutil.WriteFile(path, []byte("hello"), 0644), IsNil) 1151 1152 fd, err := update.OpenPath(path) 1153 c.Assert(err, IsNil) 1154 defer syscall.Close(fd) 1155 1156 // Check that the file descriptor matches the file. 1157 var pathStat, fdStat syscall.Stat_t 1158 c.Assert(syscall.Stat(path, &pathStat), IsNil) 1159 c.Assert(syscall.Fstat(fd, &fdStat), IsNil) 1160 c.Check(pathStat, Equals, fdStat) 1161 } 1162 1163 func (s *realSystemSuite) TestSecureOpenPathNotFound(c *C) { 1164 path := filepath.Join(c.MkDir(), "test") 1165 1166 fd, err := update.OpenPath(path) 1167 c.Check(fd, Equals, -1) 1168 c.Check(err, ErrorMatches, "no such file or directory") 1169 } 1170 1171 func (s *realSystemSuite) TestSecureOpenPathSymlink(c *C) { 1172 base := c.MkDir() 1173 dir := filepath.Join(base, "test") 1174 c.Assert(os.Mkdir(dir, 0755), IsNil) 1175 1176 symlink := filepath.Join(base, "symlink") 1177 c.Assert(os.Symlink(dir, symlink), IsNil) 1178 1179 fd, err := update.OpenPath(symlink) 1180 c.Check(fd, Equals, -1) 1181 c.Check(err, ErrorMatches, `".*" is a symbolic link`) 1182 } 1183 1184 func (s *realSystemSuite) TestSecureOpenPathSymlinkedParent(c *C) { 1185 base := c.MkDir() 1186 dir := filepath.Join(base, "dir1") 1187 symlink := filepath.Join(base, "symlink") 1188 1189 path := filepath.Join(dir, "dir2") 1190 symlinkedPath := filepath.Join(symlink, "dir2") 1191 1192 c.Assert(os.Mkdir(dir, 0755), IsNil) 1193 c.Assert(os.Symlink(dir, symlink), IsNil) 1194 c.Assert(os.Mkdir(path, 0755), IsNil) 1195 1196 fd, err := update.OpenPath(symlinkedPath) 1197 c.Check(fd, Equals, -1) 1198 c.Check(err, ErrorMatches, "not a directory") 1199 }