gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/trespassing_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017-2018 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 "syscall" 24 25 . "gopkg.in/check.v1" 26 27 update "gitee.com/mysnapcore/mysnapd/cmd/snap-update-ns" 28 "gitee.com/mysnapcore/mysnapd/osutil" 29 "gitee.com/mysnapcore/mysnapd/testutil" 30 ) 31 32 type trespassingSuite struct { 33 testutil.BaseTest 34 sys *testutil.SyscallRecorder 35 } 36 37 var _ = Suite(&trespassingSuite{}) 38 39 func (s *trespassingSuite) SetUpTest(c *C) { 40 s.BaseTest.SetUpTest(c) 41 s.sys = &testutil.SyscallRecorder{} 42 s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys)) 43 } 44 45 func (s *trespassingSuite) TearDownTest(c *C) { 46 s.BaseTest.TearDownTest(c) 47 s.sys.CheckForStrayDescriptors(c) 48 } 49 50 // AddUnrestrictedPaths and IsRestricted 51 52 func (s *trespassingSuite) TestAddUnrestrictedPaths(c *C) { 53 a := &update.Assumptions{} 54 c.Assert(a.IsRestricted("/etc/test.conf"), Equals, true) 55 56 a.AddUnrestrictedPaths("/etc") 57 c.Assert(a.IsRestricted("/etc/test.conf"), Equals, false) 58 c.Assert(a.IsRestricted("/etc/"), Equals, false) 59 c.Assert(a.IsRestricted("/etc"), Equals, false) 60 c.Assert(a.IsRestricted("/etc2"), Equals, true) 61 62 a.AddUnrestrictedPaths("/") 63 c.Assert(a.IsRestricted("/foo"), Equals, false) 64 65 } 66 67 func (s *trespassingSuite) TestMockUnrestrictedPaths(c *C) { 68 a := &update.Assumptions{} 69 c.Assert(a.IsRestricted("/etc/test.conf"), Equals, true) 70 restore := a.MockUnrestrictedPaths("/etc/") 71 c.Assert(a.IsRestricted("/etc/test.conf"), Equals, false) 72 restore() 73 c.Assert(a.IsRestricted("/etc/test.conf"), Equals, true) 74 } 75 76 // canWriteToDirectory and AddChange 77 78 // We are not allowed to write to ext4. 79 func (s *trespassingSuite) TestCanWriteToDirectoryWritableExt4(c *C) { 80 a := &update.Assumptions{} 81 82 path := "/etc" 83 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 84 c.Assert(err, IsNil) 85 defer s.sys.Close(fd) 86 87 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 88 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 89 90 ok, err := a.CanWriteToDirectory(fd, path) 91 c.Assert(err, IsNil) 92 c.Assert(ok, Equals, false) 93 } 94 95 // We are allowed to write to ext4 that was mounted read-only. 96 func (s *trespassingSuite) TestCanWriteToDirectoryReadOnlyExt4(c *C) { 97 a := &update.Assumptions{} 98 99 path := "/etc" 100 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 101 c.Assert(err, IsNil) 102 defer s.sys.Close(fd) 103 104 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic, Flags: update.StReadOnly}) 105 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 106 107 ok, err := a.CanWriteToDirectory(fd, path) 108 c.Assert(err, IsNil) 109 c.Assert(ok, Equals, true) 110 } 111 112 // We are not allowed to write to tmpfs. 113 func (s *trespassingSuite) TestCanWriteToDirectoryTmpfs(c *C) { 114 a := &update.Assumptions{} 115 116 path := "/etc" 117 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 118 c.Assert(err, IsNil) 119 defer s.sys.Close(fd) 120 121 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 122 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 123 124 ok, err := a.CanWriteToDirectory(fd, path) 125 c.Assert(err, IsNil) 126 c.Assert(ok, Equals, false) 127 } 128 129 // We are allowed to write to tmpfs that was mounted by snapd. 130 func (s *trespassingSuite) TestCanWriteToDirectoryTmpfsMountedBySnapd(c *C) { 131 a := &update.Assumptions{} 132 133 path := "/etc" 134 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 135 c.Assert(err, IsNil) 136 defer s.sys.Close(fd) 137 138 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 139 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 140 141 a.AddChange(&update.Change{ 142 Action: update.Mount, 143 Entry: osutil.MountEntry{Type: "tmpfs", Dir: path}}) 144 145 ok, err := a.CanWriteToDirectory(fd, path) 146 c.Assert(err, IsNil) 147 c.Assert(ok, Equals, true) 148 } 149 150 // We are allowed to write to tmpfs that was mounted by snapd in another run. 151 func (s *trespassingSuite) TestCanWriteToDirectoryTmpfsMountedBySnapdEarlier(c *C) { 152 a := &update.Assumptions{} 153 154 path := "/etc" 155 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 156 c.Assert(err, IsNil) 157 defer s.sys.Close(fd) 158 159 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 160 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 161 162 a.AddChange(&update.Change{ 163 Action: update.Keep, 164 Entry: osutil.MountEntry{Type: "tmpfs", Dir: path}}) 165 166 ok, err := a.CanWriteToDirectory(fd, path) 167 c.Assert(err, IsNil) 168 c.Assert(ok, Equals, true) 169 } 170 171 // We are allowed to write to directory beneath a tmpfs that was mounted by snapd. 172 func (s *trespassingSuite) TestCanWriteToDirectoryUnderTmpfsMountedBySnapd(c *C) { 173 a := &update.Assumptions{} 174 175 fd, err := s.sys.Open("/etc", syscall.O_DIRECTORY, 0) 176 c.Assert(err, IsNil) 177 defer s.sys.Close(fd) 178 179 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 180 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{Dev: 0x42}) 181 182 a.AddChange(&update.Change{ 183 Action: update.Mount, 184 Entry: osutil.MountEntry{Type: "tmpfs", Dir: "/etc"}}) 185 186 ok, err := a.CanWriteToDirectory(fd, "/etc") 187 c.Assert(err, IsNil) 188 c.Assert(ok, Equals, true) 189 190 // Now we have primed the assumption state with knowledge of 0x42 device as 191 // a verified tmpfs. We can now exploit it by trying to write to 192 // /etc/conf.d and seeing that is allowed even though /etc/conf.d itself is 193 // not a mount point representing tmpfs. 194 195 fd2, err := s.sys.Open("/etc/conf.d", syscall.O_DIRECTORY, 0) 196 c.Assert(err, IsNil) 197 defer s.sys.Close(fd2) 198 199 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 200 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Dev: 0x42}) 201 202 ok, err = a.CanWriteToDirectory(fd2, "/etc/conf.d") 203 c.Assert(err, IsNil) 204 c.Assert(ok, Equals, true) 205 } 206 207 // We are allowed to write to directory which is a bind mount of something, beneath a tmpfs that was mounted by snapd. 208 func (s *trespassingSuite) TestCanWriteToDirectoryUnderReboundTmpfsMountedBySnapd(c *C) { 209 a := &update.Assumptions{} 210 211 fd, err := s.sys.Open("/etc", syscall.O_DIRECTORY, 0) 212 c.Assert(err, IsNil) 213 c.Assert(fd, Equals, 3) 214 defer s.sys.Close(fd) 215 216 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 217 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{Dev: 0x42}) 218 219 a.AddChange(&update.Change{ 220 Action: update.Mount, 221 Entry: osutil.MountEntry{Type: "tmpfs", Dir: "/etc"}}) 222 223 ok, err := a.CanWriteToDirectory(fd, "/etc") 224 c.Assert(err, IsNil) 225 c.Assert(ok, Equals, true) 226 227 // Now we have primed the assumption state with knowledge of 0x42 device as 228 // a verified tmpfs. Unlike in the test above though the directory 229 // /etc/conf.d is a bind mount from another tmpfs that we know nothing 230 // about. 231 fd2, err := s.sys.Open("/etc/conf.d", syscall.O_DIRECTORY, 0) 232 c.Assert(err, IsNil) 233 c.Assert(fd2, Equals, 4) 234 defer s.sys.Close(fd2) 235 236 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 237 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Dev: 0xdeadbeef}) 238 239 ok, err = a.CanWriteToDirectory(fd2, "/etc/conf.d") 240 c.Assert(err, IsNil) 241 c.Assert(ok, Equals, false) 242 } 243 244 // We are allowed to write to an unrestricted path. 245 func (s *trespassingSuite) TestCanWriteToDirectoryUnrestricted(c *C) { 246 a := &update.Assumptions{} 247 248 path := "/var/snap/foo/common" 249 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 250 c.Assert(err, IsNil) 251 defer s.sys.Close(fd) 252 253 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 254 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 255 256 a.AddUnrestrictedPaths(path) 257 258 ok, err := a.CanWriteToDirectory(fd, path) 259 c.Assert(err, IsNil) 260 c.Assert(ok, Equals, true) 261 } 262 263 // Errors from fstatfs are propagated to the caller. 264 func (s *trespassingSuite) TestCanWriteToDirectoryErrorsFstatfs(c *C) { 265 a := &update.Assumptions{} 266 267 path := "/etc" 268 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 269 c.Assert(err, IsNil) 270 defer s.sys.Close(fd) 271 272 s.sys.InsertFault(`fstatfs 3 <ptr>`, errTesting) 273 274 ok, err := a.CanWriteToDirectory(fd, path) 275 c.Assert(err, ErrorMatches, `cannot fstatfs "/etc": testing`) 276 c.Assert(ok, Equals, false) 277 } 278 279 // Errors from fstat are propagated to the caller. 280 func (s *trespassingSuite) TestCanWriteToDirectoryErrorsFstat(c *C) { 281 a := &update.Assumptions{} 282 283 path := "/etc" 284 fd, err := s.sys.Open(path, syscall.O_DIRECTORY, 0) 285 c.Assert(err, IsNil) 286 defer s.sys.Close(fd) 287 288 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{}) 289 s.sys.InsertFault(`fstat 3 <ptr>`, errTesting) 290 291 ok, err := a.CanWriteToDirectory(fd, path) 292 c.Assert(err, ErrorMatches, `cannot fstat "/etc": testing`) 293 c.Assert(ok, Equals, false) 294 } 295 296 // RestrictionsFor, Check and LiftRestrictions 297 298 func (s *trespassingSuite) TestRestrictionsForEtc(c *C) { 299 a := &update.Assumptions{} 300 301 // There are restrictions for writing in /etc. 302 rs := a.RestrictionsFor("/etc/test.conf") 303 c.Assert(rs, NotNil) 304 305 fd, err := s.sys.Open("/etc", syscall.O_DIRECTORY, 0) 306 c.Assert(err, IsNil) 307 defer s.sys.Close(fd) 308 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 309 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 310 311 // Check reports trespassing error, restrictions may be lifted though. 312 err = rs.Check(fd, "/etc") 313 c.Assert(err, ErrorMatches, `cannot write to "/etc/test.conf" because it would affect the host in "/etc"`) 314 c.Assert(err.(*update.TrespassingError).ViolatedPath, Equals, "/etc") 315 c.Assert(err.(*update.TrespassingError).DesiredPath, Equals, "/etc/test.conf") 316 317 rs.Lift() 318 c.Assert(rs.Check(fd, "/etc"), IsNil) 319 } 320 321 // Check returns errors from lower layers. 322 func (s *trespassingSuite) TestRestrictionsForErrors(c *C) { 323 a := &update.Assumptions{} 324 325 rs := a.RestrictionsFor("/etc/test.conf") 326 c.Assert(rs, NotNil) 327 328 fd, err := s.sys.Open("/etc", syscall.O_DIRECTORY, 0) 329 c.Assert(err, IsNil) 330 defer s.sys.Close(fd) 331 s.sys.InsertFault(`fstatfs 3 <ptr>`, errTesting) 332 333 err = rs.Check(fd, "/etc") 334 c.Assert(err, ErrorMatches, `cannot fstatfs "/etc": testing`) 335 } 336 337 func (s *trespassingSuite) TestRestrictionsForVarSnap(c *C) { 338 a := &update.Assumptions{} 339 a.AddUnrestrictedPaths("/var/snap") 340 341 // There are no restrictions in $SNAP_COMMON. 342 rs := a.RestrictionsFor("/var/snap/foo/common/test.conf") 343 c.Assert(rs, IsNil) 344 345 // Nil restrictions have working Check and Lift methods. 346 c.Assert(rs.Check(3, "unused"), IsNil) 347 rs.Lift() 348 } 349 350 func (s *trespassingSuite) TestRestrictionsForRunSystemd(c *C) { 351 a := &update.Assumptions{} 352 a.AddUnrestrictedPaths("/run/systemd") 353 354 // There should be no restrictions under /run/systemd 355 rs := a.RestrictionsFor("/run/systemd/journal") 356 c.Assert(rs, IsNil) 357 rs = a.RestrictionsFor("/run/systemd/journal.namespace") 358 c.Assert(rs, IsNil) 359 360 // however we should still disallow anything else under /run 361 rs = a.RestrictionsFor("/run/test.txt") 362 c.Assert(rs, NotNil) 363 364 fd, err := s.sys.Open("/run", syscall.O_DIRECTORY, 0) 365 c.Assert(err, IsNil) 366 defer s.sys.Close(fd) 367 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 368 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 369 370 err = rs.Check(fd, "/run") 371 c.Assert(err, ErrorMatches, `cannot write to "/run/test.txt" because it would affect the host in "/run"`) 372 c.Assert(err.(*update.TrespassingError).ViolatedPath, Equals, "/run") 373 c.Assert(err.(*update.TrespassingError).DesiredPath, Equals, "/run/test.txt") 374 375 rs.Lift() 376 c.Assert(rs.Check(fd, "/run"), IsNil) 377 } 378 379 func (s *trespassingSuite) TestRestrictionsForRootfsEntries(c *C) { 380 a := &update.Assumptions{} 381 382 // The root directory is special, it's not a trespassing error we can 383 // recover from because we cannot construct a writable mimic for the root 384 // directory today. 385 rs := a.RestrictionsFor("/foo.conf") 386 387 fd, err := s.sys.Open("/", syscall.O_DIRECTORY, 0) 388 c.Assert(err, IsNil) 389 defer s.sys.Close(fd) 390 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 391 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 392 393 // Nil restrictions have working Check and Lift methods. 394 c.Assert(rs.Check(fd, "/"), ErrorMatches, `cannot recover from trespassing over /`) 395 } 396 397 // isReadOnly 398 399 func (s *trespassingSuite) TestIsReadOnlySquashfsMountedRo(c *C) { 400 path := "/some/path" 401 statfs := &syscall.Statfs_t{Type: update.SquashfsMagic, Flags: update.StReadOnly} 402 result := update.IsReadOnly(path, statfs) 403 c.Assert(result, Equals, true) 404 } 405 406 func (s *trespassingSuite) TestIsReadOnlySquashfsMountedRw(c *C) { 407 path := "/some/path" 408 statfs := &syscall.Statfs_t{Type: update.SquashfsMagic} 409 result := update.IsReadOnly(path, statfs) 410 c.Assert(result, Equals, true) 411 } 412 413 func (s *trespassingSuite) TestIsReadOnlyExt4MountedRw(c *C) { 414 path := "/some/path" 415 statfs := &syscall.Statfs_t{Type: update.Ext4Magic} 416 result := update.IsReadOnly(path, statfs) 417 c.Assert(result, Equals, false) 418 } 419 420 // isSnapdCreatedPrivateTmpfs 421 422 func (s *trespassingSuite) TestIsPrivateTmpfsCreatedBySnapdNotATmpfs(c *C) { 423 path := "/some/path" 424 // An ext4 (which is not a tmpfs) is not a private tmpfs. 425 statfs := &syscall.Statfs_t{Type: update.Ext4Magic} 426 stat := &syscall.Stat_t{} 427 result := update.IsPrivateTmpfsCreatedBySnapd(path, statfs, stat, nil) 428 c.Assert(result, Equals, false) 429 } 430 431 func (s *trespassingSuite) TestIsPrivateTmpfsCreatedBySnapdNotTrusted(c *C) { 432 path := "/some/path" 433 // A tmpfs is not private if it doesn't come from a change we made. 434 statfs := &syscall.Statfs_t{Type: update.TmpfsMagic} 435 stat := &syscall.Stat_t{} 436 result := update.IsPrivateTmpfsCreatedBySnapd(path, statfs, stat, nil) 437 c.Assert(result, Equals, false) 438 } 439 440 func (s *trespassingSuite) TestIsPrivateTmpfsCreatedBySnapdViaChanges(c *C) { 441 path := "/some/path" 442 // A tmpfs is private because it was mounted by snap-update-ns. 443 statfs := &syscall.Statfs_t{Type: update.TmpfsMagic} 444 stat := &syscall.Stat_t{} 445 446 // A tmpfs was mounted in the past so it is private. 447 result := update.IsPrivateTmpfsCreatedBySnapd(path, statfs, stat, []*update.Change{ 448 {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: path, Type: "tmpfs"}}, 449 }) 450 c.Assert(result, Equals, true) 451 452 // A tmpfs was mounted but then it was unmounted so it is not private anymore. 453 result = update.IsPrivateTmpfsCreatedBySnapd(path, statfs, stat, []*update.Change{ 454 {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: path, Type: "tmpfs"}}, 455 {Action: update.Unmount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: path, Type: "tmpfs"}}, 456 }) 457 c.Assert(result, Equals, false) 458 459 // Finally, after the mounting and unmounting the tmpfs was mounted again. 460 result = update.IsPrivateTmpfsCreatedBySnapd(path, statfs, stat, []*update.Change{ 461 {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: path, Type: "tmpfs"}}, 462 {Action: update.Unmount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: path, Type: "tmpfs"}}, 463 {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: path, Type: "tmpfs"}}, 464 }) 465 c.Assert(result, Equals, true) 466 } 467 468 func (s *trespassingSuite) TestIsPrivateTmpfsCreatedBySnapdDeeper(c *C) { 469 path := "/some/path/below" 470 // A tmpfs is not private beyond the exact mount point from a change. 471 // That is, sub-directories of a private tmpfs are not recognized as private. 472 statfs := &syscall.Statfs_t{Type: update.TmpfsMagic} 473 stat := &syscall.Stat_t{} 474 result := update.IsPrivateTmpfsCreatedBySnapd(path, statfs, stat, []*update.Change{ 475 {Action: update.Mount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/some/path", Type: "tmpfs"}}, 476 }) 477 c.Assert(result, Equals, false) 478 }