github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/sandbox/cgroup/scanning_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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  package cgroup_test
    20  
    21  import (
    22  	"bytes"
    23  	"fmt"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/sandbox/cgroup"
    32  	"github.com/snapcore/snapd/snap/naming"
    33  	"github.com/snapcore/snapd/testutil"
    34  )
    35  
    36  type scanningSuite struct {
    37  	testutil.BaseTest
    38  	rootDir string
    39  }
    40  
    41  var _ = Suite(&scanningSuite{})
    42  
    43  func (s *scanningSuite) SetUpTest(c *C) {
    44  	s.BaseTest.SetUpTest(c)
    45  
    46  	s.rootDir = c.MkDir()
    47  	dirs.SetRootDir(s.rootDir)
    48  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    49  }
    50  
    51  func mustParseTag(tag string) naming.SecurityTag {
    52  	parsedTag, err := naming.ParseSecurityTag(tag)
    53  	if err != nil {
    54  		panic(err)
    55  	}
    56  	return parsedTag
    57  }
    58  
    59  func (s *scanningSuite) TestSecurityTagFromCgroupPath(c *C) {
    60  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.foo.service"), DeepEquals, mustParseTag("snap.foo.foo"))
    61  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.bar.service"), DeepEquals, mustParseTag("snap.foo.bar"))
    62  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.bar.$RANDOM.scope"), DeepEquals, mustParseTag("snap.foo.bar"))
    63  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.hook.bar.$RANDOM.scope"), DeepEquals, mustParseTag("snap.foo.hook.bar"))
    64  	// We are not confused by snapd things.
    65  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.service"), IsNil)
    66  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snapd.service"), IsNil)
    67  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.mount"), IsNil)
    68  	// Real data looks like this.
    69  	c.Check(cgroup.SecurityTagFromCgroupPath("snap.test-snapd-refresh.sh.d854bd35-2457-4ac8-b494-06061d74df33.scope"), DeepEquals, mustParseTag("snap.test-snapd-refresh.sh"))
    70  	c.Check(cgroup.SecurityTagFromCgroupPath("snap.test-snapd-refresh.hook.configure.d854bd35-2457-4ac8-b494-06061d74df33.scope"), DeepEquals, mustParseTag("snap.test-snapd-refresh.hook.configure"))
    71  	// Trailing slashes are automatically handled.
    72  	c.Check(cgroup.SecurityTagFromCgroupPath("/a/b/snap.foo.foo.service/"), DeepEquals, mustParseTag("snap.foo.foo"))
    73  }
    74  
    75  func (s *scanningSuite) writePids(c *C, dir string, pids []int) {
    76  	var buf bytes.Buffer
    77  	for _, pid := range pids {
    78  		fmt.Fprintf(&buf, "%d\n", pid)
    79  	}
    80  
    81  	var path string
    82  	ver, err := cgroup.Version()
    83  	c.Assert(err, IsNil)
    84  	c.Assert(ver == cgroup.V1 || ver == cgroup.V2, Equals, true)
    85  	switch ver {
    86  	case cgroup.V1:
    87  		path = filepath.Join(s.rootDir, "/sys/fs/cgroup/systemd", dir)
    88  	case cgroup.V2:
    89  		path = filepath.Join(s.rootDir, "/sys/fs/cgroup", dir)
    90  	}
    91  
    92  	c.Assert(os.MkdirAll(path, 0755), IsNil)
    93  	c.Assert(ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), buf.Bytes(), 0644), IsNil)
    94  }
    95  
    96  func (s *scanningSuite) TestPidsOfSnapEmpty(c *C) {
    97  	restore := cgroup.MockVersion(cgroup.V1, nil)
    98  	defer restore()
    99  
   100  	// Not having any cgroup directories is not an error.
   101  	pids, err := cgroup.PidsOfSnap("pkg")
   102  	c.Assert(err, IsNil)
   103  	c.Check(pids, HasLen, 0)
   104  }
   105  
   106  func (s *scanningSuite) TestPidsOfSnapUnrelatedStuff(c *C) {
   107  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   108  		comment := Commentf("cgroup version %v", ver)
   109  		restore := cgroup.MockVersion(ver, nil)
   110  		defer restore()
   111  
   112  		// Things that are not related to the snap are not being picked up.
   113  		s.writePids(c, "udisks2.service", []int{100})
   114  		s.writePids(c, "snap..service", []int{101})
   115  		s.writePids(c, "snap..scope", []int{102})
   116  		s.writePids(c, "snap.*.service", []int{103})
   117  		s.writePids(c, "snap.*.scope", []int{104})
   118  		s.writePids(c, "snapd.service", []int{105})
   119  		s.writePids(c, "snap-spotify-35.mount", []int{106})
   120  
   121  		pids, err := cgroup.PidsOfSnap("pkg")
   122  		c.Assert(err, IsNil, comment)
   123  		c.Check(pids, HasLen, 0, comment)
   124  	}
   125  }
   126  
   127  func (s *scanningSuite) TestPidsOfSnapSecurityTags(c *C) {
   128  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   129  		comment := Commentf("cgroup version %v", ver)
   130  		restore := cgroup.MockVersion(ver, nil)
   131  		defer restore()
   132  
   133  		// Pids are collected and assigned to bins by security tag
   134  		s.writePids(c, "system.slice/snap.pkg.hook.configure.$RANDOM.scope", []int{1})
   135  		s.writePids(c, "system.slice/snap.pkg.daemon.service", []int{2})
   136  
   137  		pids, err := cgroup.PidsOfSnap("pkg")
   138  		c.Assert(err, IsNil, comment)
   139  		c.Check(pids, DeepEquals, map[string][]int{
   140  			"snap.pkg.hook.configure": {1},
   141  			"snap.pkg.daemon":         {2},
   142  		}, comment)
   143  	}
   144  }
   145  
   146  func (s *scanningSuite) TestPidsOfInstances(c *C) {
   147  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   148  		comment := Commentf("cgroup version %v", ver)
   149  		restore := cgroup.MockVersion(ver, nil)
   150  		defer restore()
   151  
   152  		// Instances are not confused between themselves and between the non-instance version.
   153  		s.writePids(c, "system.slice/snap.pkg_prod.daemon.service", []int{1})
   154  		s.writePids(c, "system.slice/snap.pkg_devel.daemon.service", []int{2})
   155  		s.writePids(c, "system.slice/snap.pkg.daemon.service", []int{3})
   156  
   157  		// The main one
   158  		pids, err := cgroup.PidsOfSnap("pkg")
   159  		c.Assert(err, IsNil, comment)
   160  		c.Check(pids, DeepEquals, map[string][]int{
   161  			"snap.pkg.daemon": {3},
   162  		}, comment)
   163  
   164  		// The development one
   165  		pids, err = cgroup.PidsOfSnap("pkg_devel")
   166  		c.Assert(err, IsNil, comment)
   167  		c.Check(pids, DeepEquals, map[string][]int{
   168  			"snap.pkg_devel.daemon": {2},
   169  		}, comment)
   170  
   171  		// The production one
   172  		pids, err = cgroup.PidsOfSnap("pkg_prod")
   173  		c.Assert(err, IsNil, comment)
   174  		c.Check(pids, DeepEquals, map[string][]int{
   175  			"snap.pkg_prod.daemon": {1},
   176  		}, comment)
   177  	}
   178  }
   179  
   180  func (s *scanningSuite) TestPidsOfAggregation(c *C) {
   181  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   182  		comment := Commentf("cgroup version %v", ver)
   183  		restore := cgroup.MockVersion(ver, nil)
   184  		defer restore()
   185  
   186  		// A single snap may be invoked by multiple users in different sessions.
   187  		// All of their PIDs are collected though.
   188  		s.writePids(c, "user.slice/user-1000.slice/user@1000.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM1.scope", []int{1}) // mock 1st invocation
   189  		s.writePids(c, "user.slice/user-1000.slice/user@1000.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM2.scope", []int{2}) // mock fork() by pid 1
   190  		s.writePids(c, "user.slice/user-1001.slice/user@1001.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM3.scope", []int{3}) // mock 2nd invocation
   191  		s.writePids(c, "user.slice/user-1001.slice/user@1001.service/gnome-shell-wayland.service/snap.pkg.app.$RANDOM4.scope", []int{4}) // mock fork() by pid 3
   192  
   193  		pids, err := cgroup.PidsOfSnap("pkg")
   194  		c.Assert(err, IsNil, comment)
   195  		c.Check(pids, DeepEquals, map[string][]int{
   196  			"snap.pkg.app": {1, 2, 3, 4},
   197  		}, comment)
   198  	}
   199  }
   200  
   201  func (s *scanningSuite) TestPidsOfSnapUnrelated(c *C) {
   202  	for _, ver := range []int{cgroup.V2, cgroup.V1} {
   203  		comment := Commentf("cgroup version %v", ver)
   204  		restore := cgroup.MockVersion(ver, nil)
   205  		defer restore()
   206  
   207  		// We are not confusing snaps with other snaps, instances of our snap, and
   208  		// with non-snap hierarchies.
   209  		s.writePids(c, "user.slice/.../snap.pkg.app.$RANDOM1.scope", []int{1})
   210  		s.writePids(c, "user.slice/.../snap.other.snap.$RANDOM2.scope", []int{2})
   211  		s.writePids(c, "user.slice/.../pkg.service", []int{3})
   212  		s.writePids(c, "user.slice/.../snap.pkg_instance.app.$RANDOM3.scope", []int{4})
   213  
   214  		// Write a file which is not cgroup.procs with the number 666 inside.
   215  		// We want to ensure this is not read by accident.
   216  		f := filepath.Join(s.rootDir, "/sys/fs/cgroup/unrelated.txt")
   217  		c.Assert(os.MkdirAll(filepath.Dir(f), 0755), IsNil)
   218  		c.Assert(ioutil.WriteFile(f, []byte("666"), 0644), IsNil)
   219  
   220  		pids, err := cgroup.PidsOfSnap("pkg")
   221  		c.Assert(err, IsNil, comment)
   222  		c.Check(pids, DeepEquals, map[string][]int{
   223  			"snap.pkg.app": {1},
   224  		}, comment)
   225  	}
   226  }