github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/sandbox/cgroup/freezer_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 cgroup_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "sort" 28 "strings" 29 30 . "gopkg.in/check.v1" 31 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/sandbox/cgroup" 34 "github.com/snapcore/snapd/testutil" 35 ) 36 37 type freezerV1Suite struct{} 38 39 var _ = Suite(&freezerV1Suite{}) 40 41 func (s *freezerV1Suite) TestFreezeSnapProcessesV1(c *C) { 42 defer cgroup.MockVersion(cgroup.V1, nil)() 43 dirs.SetRootDir(c.MkDir()) 44 defer dirs.SetRootDir("") 45 46 n := "foo" // snap name 47 p := filepath.Join(cgroup.FreezerCgroupV1Dir(), fmt.Sprintf("snap.%s", n)) // snap freezer cgroup 48 f := filepath.Join(p, "freezer.state") // freezer.state file of the cgroup 49 50 // When the freezer cgroup filesystem doesn't exist we do nothing at all. 51 c.Assert(cgroup.FreezeSnapProcesses(n), IsNil) 52 _, err := os.Stat(f) 53 c.Assert(os.IsNotExist(err), Equals, true) 54 55 // When the freezer cgroup filesystem exists but the particular cgroup 56 // doesn't exist we don nothing at all. 57 c.Assert(os.MkdirAll(cgroup.FreezerCgroupV1Dir(), 0755), IsNil) 58 c.Assert(cgroup.FreezeSnapProcesses(n), IsNil) 59 _, err = os.Stat(f) 60 c.Assert(os.IsNotExist(err), Equals, true) 61 62 // When the cgroup exists we write FROZEN the freezer.state file. 63 c.Assert(os.MkdirAll(p, 0755), IsNil) 64 c.Assert(cgroup.FreezeSnapProcesses(n), IsNil) 65 _, err = os.Stat(f) 66 c.Assert(err, IsNil) 67 c.Assert(f, testutil.FileEquals, `FROZEN`) 68 } 69 70 func (s *freezerV1Suite) TestThawSnapProcessesV1(c *C) { 71 defer cgroup.MockVersion(cgroup.V1, nil)() 72 dirs.SetRootDir(c.MkDir()) 73 defer dirs.SetRootDir("") 74 75 n := "foo" // snap name 76 p := filepath.Join(cgroup.FreezerCgroupV1Dir(), fmt.Sprintf("snap.%s", n)) // snap freezer cgroup 77 f := filepath.Join(p, "freezer.state") // freezer.state file of the cgroup 78 79 // When the freezer cgroup filesystem doesn't exist we do nothing at all. 80 c.Assert(cgroup.ThawSnapProcesses(n), IsNil) 81 _, err := os.Stat(f) 82 c.Assert(os.IsNotExist(err), Equals, true) 83 84 // When the freezer cgroup filesystem exists but the particular cgroup 85 // doesn't exist we don nothing at all. 86 c.Assert(os.MkdirAll(cgroup.FreezerCgroupV1Dir(), 0755), IsNil) 87 c.Assert(cgroup.ThawSnapProcesses(n), IsNil) 88 _, err = os.Stat(f) 89 c.Assert(os.IsNotExist(err), Equals, true) 90 91 // When the cgroup exists we write THAWED the freezer.state file. 92 c.Assert(os.MkdirAll(p, 0755), IsNil) 93 c.Assert(cgroup.ThawSnapProcesses(n), IsNil) 94 _, err = os.Stat(f) 95 c.Assert(err, IsNil) 96 c.Assert(f, testutil.FileEquals, `THAWED`) 97 } 98 99 type freezerV2Suite struct{} 100 101 var _ = Suite(&freezerV2Suite{}) 102 103 func (s *freezerV2Suite) TestFreezeSnapProcessesV2OtherGroups(c *C) { 104 defer cgroup.MockVersion(cgroup.V2, nil)() 105 dirs.SetRootDir(c.MkDir()) 106 defer dirs.SetRootDir("") 107 108 // app started by root 109 g1 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze") 110 // service started by systemd 111 g2 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/cgroup.freeze") 112 // user applications 113 g3 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/user.slice/user-1234.slice/user@1234.service/snap.foo.user-app.1234-1234-1234.scope/cgroup.freeze") 114 // user service 115 g4 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/user.slice/user-1234.slice/user@1234.service/snap.foo.user-svc.service/cgroup.freeze") 116 canary := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.canary.svc.service/cgroup.freeze") 117 // a subgroup of the group of a snap 118 canarySubgroup := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/snap.foo.subgroup.scope/cgroup.freeze") 119 120 pid := os.Getpid() 121 122 // freezing needs to inspect our own cgroup, which will fail without 123 // proper mocking 124 err := cgroup.FreezeSnapProcesses("foo") 125 c.Check(err, ErrorMatches, fmt.Sprintf("open %s/proc/%v/cgroup: no such file or directory", dirs.GlobalRootDir, pid)) 126 127 procPidCgroup := filepath.Join(dirs.GlobalRootDir, fmt.Sprintf("proc/%v/cgroup", pid)) 128 c.Assert(os.MkdirAll(filepath.Dir(procPidCgroup), 0755), IsNil) 129 c.Assert(ioutil.WriteFile(procPidCgroup, []byte("0::/foo/bar"), 0755), IsNil) 130 131 // When the freezer cgroup filesystem doesn't exist we do nothing at all. 132 c.Assert(cgroup.FreezeSnapProcesses("foo"), IsNil) 133 134 for _, p := range []string{g1, g2, g3, g4, canary, canarySubgroup} { 135 _, err := os.Stat(p) 136 c.Assert(os.IsNotExist(err), Equals, true) 137 } 138 139 // prepare the stage 140 for _, p := range []string{g1, g2, g3, g4, canary, canarySubgroup} { 141 c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil) 142 c.Assert(ioutil.WriteFile(p, []byte("0"), 0644), IsNil) 143 } 144 145 c.Assert(cgroup.FreezeSnapProcesses("foo"), IsNil) 146 for _, p := range []string{g1, g2, g3, g4} { 147 c.Check(p, testutil.FileEquals, "1") 148 } 149 // canaries have not been changed 150 c.Assert(canary, testutil.FileEquals, "0") 151 c.Assert(canarySubgroup, testutil.FileEquals, "0") 152 153 // all groups are 'frozen', repeating the action does not break anything 154 c.Assert(cgroup.FreezeSnapProcesses("foo"), IsNil) 155 for _, p := range []string{g1, g2, g3, g4} { 156 c.Check(p, testutil.FileEquals, "1") 157 } 158 // canaries have not been changed 159 c.Assert(canary, testutil.FileEquals, "0") 160 c.Assert(canarySubgroup, testutil.FileEquals, "0") 161 162 // unfreeze some groups 163 for _, p := range []string{g2, g3} { 164 c.Assert(ioutil.WriteFile(p, []byte("0"), 0644), IsNil) 165 } 166 c.Assert(cgroup.FreezeSnapProcesses("foo"), IsNil) 167 // all are frozen again 168 for _, p := range []string{g1, g2, g3, g4} { 169 c.Check(p, testutil.FileEquals, "1") 170 } 171 } 172 173 func (s *freezerV2Suite) TestFreezeSnapProcessesV2OwnGroup(c *C) { 174 defer cgroup.MockVersion(cgroup.V2, nil)() 175 dirs.SetRootDir(c.MkDir()) 176 defer dirs.SetRootDir("") 177 178 // our own cgroup 179 gOwn := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.own-own-own.scope/cgroup.freeze") 180 // app started by root 181 g1 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze") 182 // service started by systemd 183 g2 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/cgroup.freeze") 184 // user applications 185 g3 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/user.slice/user-1234.slice/user@1234.service/snap.foo.user-app.1234-1234-1234.scope/cgroup.freeze") 186 // user service 187 g4 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/user.slice/user-1234.slice/user@1234.service/snap.foo.user-svc.service/cgroup.freeze") 188 canary := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.canary.svc.service/cgroup.freeze") 189 // a subgroup of the group of a snap 190 191 pid := os.Getpid() 192 193 // freezing needs to inspect our own cgroup, which will fail without 194 // proper mocking 195 err := cgroup.FreezeSnapProcesses("foo") 196 c.Check(err, ErrorMatches, fmt.Sprintf("open %s/proc/%v/cgroup: no such file or directory", dirs.GlobalRootDir, pid)) 197 198 procPidCgroup := filepath.Join(dirs.GlobalRootDir, fmt.Sprintf("proc/%v/cgroup", pid)) 199 c.Assert(os.MkdirAll(filepath.Dir(procPidCgroup), 0755), IsNil) 200 // mock our own group 201 c.Assert(ioutil.WriteFile(procPidCgroup, []byte("0::/system.slice/snap.foo.app.own-own-own.scope"), 0755), IsNil) 202 // prepare the stage 203 for _, p := range []string{gOwn, g1, g2, g3, g4, canary} { 204 c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil) 205 c.Assert(ioutil.WriteFile(p, []byte("0"), 0644), IsNil) 206 } 207 208 c.Assert(cgroup.FreezeSnapProcesses("foo"), IsNil) 209 // our own group is not frozen 210 c.Assert(gOwn, testutil.FileEquals, "0") 211 // canaries have not been changed 212 c.Assert(canary, testutil.FileEquals, "0") 213 // other snap groups are frozen 214 for _, p := range []string{g1, g2, g3, g4} { 215 c.Check(p, testutil.FileEquals, "1") 216 } 217 } 218 219 func (s *freezerV2Suite) TestThawSnapProcessesV2(c *C) { 220 defer cgroup.MockVersion(cgroup.V2, nil)() 221 dirs.SetRootDir(c.MkDir()) 222 defer dirs.SetRootDir("") 223 224 // app started by root 225 g1 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze") 226 // service started by systemd 227 g2 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/cgroup.freeze") 228 // user applications 229 g3 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/user.slice/user-1234.slice/user@1234.service/snap.foo.user-app.1234-1234-1234.scope/cgroup.freeze") 230 // user service 231 g4 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/user.slice/user-1234.slice/user@1234.service/snap.foo.user-svc.service/cgroup.freeze") 232 canary := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.canary.svc.service/cgroup.freeze") 233 // a subgroup of the group of a snap 234 canarySubgroup := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/snap.foo.subgroup.scope/cgroup.freeze") 235 236 // thawing when no groups exist does not break anything 237 c.Assert(cgroup.ThawSnapProcesses("foo"), IsNil) 238 239 for _, p := range []string{g1, g2, g3, g4, canary} { 240 _, err := os.Stat(p) 241 c.Assert(os.IsNotExist(err), Equals, true) 242 } 243 244 // prepare the stage 245 for _, p := range []string{g1, g2, g3, g4, canary, canarySubgroup} { 246 c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil) 247 // groups aren't frozen 248 c.Assert(ioutil.WriteFile(p, []byte("0"), 0644), IsNil) 249 } 250 251 c.Assert(cgroup.ThawSnapProcesses("foo"), IsNil) 252 for _, p := range []string{g1, g2, g3, g4} { 253 c.Check(p, testutil.FileEquals, "0") 254 } 255 // canaries are still unfrozen 256 c.Assert(canary, testutil.FileEquals, "0") 257 c.Assert(canarySubgroup, testutil.FileEquals, "0") 258 259 for _, p := range []string{g1, g2, g3, g4, canary, canarySubgroup} { 260 // make them appear frozen 261 c.Assert(ioutil.WriteFile(p, []byte("1"), 0644), IsNil) 262 } 263 c.Assert(cgroup.ThawSnapProcesses("foo"), IsNil) 264 for _, p := range []string{g1, g2, g3, g4} { 265 c.Check(p, testutil.FileEquals, "0") 266 } 267 c.Assert(canary, testutil.FileEquals, "1") 268 c.Assert(canarySubgroup, testutil.FileEquals, "1") 269 270 // freeze only some the groups groups 271 for _, p := range []string{g2, g3} { 272 c.Assert(ioutil.WriteFile(p, []byte("1"), 0644), IsNil) 273 } 274 c.Assert(cgroup.ThawSnapProcesses("foo"), IsNil) 275 // all are frozen again 276 for _, p := range []string{g1, g2, g3, g4} { 277 c.Check(p, testutil.FileEquals, "0") 278 } 279 } 280 281 func (s *freezerV2Suite) TestFreezeThawSnapProcessesV2ErrWalking(c *C) { 282 if os.Getuid() == 0 { 283 c.Skip("the test cannot be run by the root user") 284 } 285 defer cgroup.MockVersion(cgroup.V2, nil)() 286 dirs.SetRootDir(c.MkDir()) 287 defer dirs.SetRootDir("") 288 289 // app started by root 290 g := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze") 291 gUnfreeze := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/cgroup.freeze") 292 293 pid := os.Getpid() 294 procPidCgroup := filepath.Join(dirs.GlobalRootDir, fmt.Sprintf("proc/%v/cgroup", pid)) 295 c.Assert(os.MkdirAll(filepath.Dir(procPidCgroup), 0755), IsNil) 296 // mock our own group 297 c.Assert(ioutil.WriteFile(procPidCgroup, []byte("0::/system.slice/snap.foo.app.own-own-own.scope"), 0755), IsNil) 298 // prepare the stage 299 c.Assert(os.MkdirAll(filepath.Dir(g), 0755), IsNil) 300 c.Assert(ioutil.WriteFile(g, []byte("0"), 0644), IsNil) 301 c.Assert(os.MkdirAll(filepath.Dir(gUnfreeze), 0755), IsNil) 302 c.Assert(ioutil.WriteFile(gUnfreeze, []byte("1"), 0644), IsNil) 303 304 c.Assert(os.Chmod(filepath.Dir(g), 0000), IsNil) 305 // make the cleanup happy 306 defer os.Chmod(filepath.Dir(g), 0755) 307 308 // freeze tries thawing on errors, so we'll observe both errors 309 err := cgroup.FreezeSnapProcesses("foo") 310 // go 1.10+ slightly changed the order of calls in filepath.Walk(), make 311 // sure the error check matches both 312 c.Check(err, ErrorMatches, `cannot finish freezing processes of snap "foo":( cannot freeze processes of snap "foo",)? open .*/sys/fs/cgroup/system.slice/snap.foo.app.1234.1234.1234.scope(/cgroup.freeze)?: permission denied`) 313 // other group was unfrozen 314 c.Check(gUnfreeze, testutil.FileEquals, "0") 315 316 c.Assert(ioutil.WriteFile(gUnfreeze, []byte("1"), 0644), IsNil) 317 // make file access fail 318 c.Assert(os.Chmod(filepath.Dir(g), 0755), IsNil) 319 c.Assert(os.Chmod(g, 0000), IsNil) 320 // other group was unfrozen 321 err = cgroup.FreezeSnapProcesses("foo") 322 c.Check(err, ErrorMatches, `cannot finish freezing processes of snap "foo": cannot freeze processes of snap "foo", open .*/sys/fs/cgroup/system.slice/snap.foo.app.1234.1234.1234.scope/cgroup.freeze: permission denied`) 323 // other group was unfrozen 324 c.Check(gUnfreeze, testutil.FileEquals, "0") 325 326 // thawing fails likewise 327 err = cgroup.ThawSnapProcesses("foo") 328 c.Check(err, ErrorMatches, `cannot thaw processes of snap "foo", open .*/sys/fs/cgroup/system.slice/snap.foo.app.1234.1234.1234.scope/cgroup.freeze: permission denied`) 329 // other group was unfrozen 330 c.Check(gUnfreeze, testutil.FileEquals, "0") 331 332 // make unfreezing fail 333 c.Assert(ioutil.WriteFile(gUnfreeze, []byte("1"), 0644), IsNil) 334 c.Assert(os.Chmod(filepath.Dir(gUnfreeze), 0000), IsNil) 335 defer os.Chmod(filepath.Dir(gUnfreeze), 0755) 336 337 err = cgroup.FreezeSnapProcesses("foo") 338 // but the unfreeze errors are ignored anyuway 339 c.Check(err, ErrorMatches, `cannot finish freezing processes of snap "foo": cannot freeze processes of snap "foo", open .*/sys/fs/cgroup/system.slice/snap.foo.app.1234.1234.1234.scope/cgroup.freeze: permission denied`) 340 // the other group is unmodified 341 os.Chmod(filepath.Dir(gUnfreeze), 0755) 342 c.Check(gUnfreeze, testutil.FileEquals, "1") 343 } 344 345 func (s *freezerV2Suite) TestFreezeThawSnapProcessesV2ErrNotFound(c *C) { 346 defer cgroup.MockVersion(cgroup.V2, nil)() 347 dirs.SetRootDir(c.MkDir()) 348 defer dirs.SetRootDir("") 349 350 // app started by root 351 g1 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze") 352 g2 := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.svc.service/cgroup.freeze") 353 354 pid := os.Getpid() 355 procPidCgroup := filepath.Join(dirs.GlobalRootDir, fmt.Sprintf("proc/%v/cgroup", pid)) 356 c.Assert(os.MkdirAll(filepath.Dir(procPidCgroup), 0755), IsNil) 357 // mock our own group 358 c.Assert(ioutil.WriteFile(procPidCgroup, []byte("0::/system.slice/snap.foo.app.own-own-own.scope"), 0755), IsNil) 359 // prepare the directories, but not the files, those should trigger ENOENT 360 c.Assert(os.MkdirAll(filepath.Dir(g1), 0755), IsNil) 361 c.Assert(os.MkdirAll(filepath.Dir(g2), 0755), IsNil) 362 363 err := cgroup.FreezeSnapProcesses("foo") 364 c.Assert(err, IsNil) 365 366 c.Check(g1, testutil.FileAbsent) 367 c.Check(g2, testutil.FileAbsent) 368 369 err = cgroup.ThawSnapProcesses("foo") 370 c.Assert(err, IsNil) 371 c.Check(g1, testutil.FileAbsent) 372 c.Check(g2, testutil.FileAbsent) 373 } 374 375 func (s *freezerV2Suite) TestApplyToSnapCallbacks(c *C) { 376 defer cgroup.MockVersion(cgroup.V2, nil)() 377 dirs.SetRootDir(c.MkDir()) 378 defer dirs.SetRootDir("") 379 380 c.Check(cgroup.ApplyToSnap("foo", nil, nil), ErrorMatches, "internal error: action is nil") 381 nop := func(_ string) error { return nil } 382 c.Check(cgroup.ApplyToSnap("foo", nop, nil), ErrorMatches, "internal error: skip error is nil") 383 384 g := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.1234-1234-1234.scope/cgroup.freeze") 385 gErr := filepath.Join(dirs.GlobalRootDir, "/sys/fs/cgroup/system.slice/snap.foo.app.fail.scope/cgroup.freeze") 386 387 for _, p := range []string{g, gErr} { 388 c.Assert(os.MkdirAll(filepath.Dir(p), 0755), IsNil) 389 // groups aren't frozen 390 c.Assert(ioutil.WriteFile(p, []byte("0"), 0644), IsNil) 391 } 392 393 var visited []string 394 err := cgroup.ApplyToSnap("foo", 395 func(p string) error { 396 visited = append(visited, p) 397 return nil 398 }, 399 func(err error) bool { 400 return true 401 }) 402 c.Assert(err, IsNil) 403 sort.Strings(visited) 404 c.Check(visited, DeepEquals, []string{filepath.Dir(g), filepath.Dir(gErr)}) 405 406 visited = nil 407 skip := true 408 var errors []string 409 maybeFail := func(p string) error { 410 visited = append(visited, p) 411 if strings.HasSuffix(p, "fail.scope") { 412 return fmt.Errorf("do not skip") 413 } 414 return nil 415 } 416 maybeSkip := func(err error) bool { 417 errors = append(errors, err.Error()) 418 return skip 419 } 420 err = cgroup.ApplyToSnap("foo", maybeFail, maybeSkip) 421 c.Assert(err, IsNil) 422 c.Check(visited, DeepEquals, []string{filepath.Dir(g), filepath.Dir(gErr)}) 423 c.Check(errors, DeepEquals, []string{"do not skip"}) 424 425 skip = false 426 visited = nil 427 errors = nil 428 err = cgroup.ApplyToSnap("foo", maybeFail, maybeSkip) 429 c.Assert(err, ErrorMatches, "do not skip") 430 c.Check(visited, DeepEquals, []string{filepath.Dir(g), filepath.Dir(gErr)}) 431 c.Check(errors, DeepEquals, []string{"do not skip"}) 432 }