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