gitee.com/mysnapcore/mysnapd@v0.1.0/sandbox/apparmor/profile_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2022 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 apparmor_test
    21  
    22  import (
    23  	"errors"
    24  	"io/ioutil"
    25  	"os"
    26  	"path"
    27  	"path/filepath"
    28  
    29  	. "gopkg.in/check.v1"
    30  
    31  	"gitee.com/mysnapcore/mysnapd/dirs"
    32  	"gitee.com/mysnapcore/mysnapd/osutil"
    33  	"gitee.com/mysnapcore/mysnapd/sandbox/apparmor"
    34  	"gitee.com/mysnapcore/mysnapd/testutil"
    35  )
    36  
    37  type appArmorSuite struct {
    38  	testutil.BaseTest
    39  	profilesFilename string
    40  }
    41  
    42  var _ = Suite(&appArmorSuite{})
    43  
    44  func (s *appArmorSuite) SetUpTest(c *C) {
    45  	s.BaseTest.SetUpTest(c)
    46  	// Mock the list of profiles in the running kernel
    47  	s.profilesFilename = path.Join(c.MkDir(), "profiles")
    48  	apparmor.MockProfilesPath(&s.BaseTest, s.profilesFilename)
    49  	dirs.SetRootDir("")
    50  }
    51  
    52  // Tests for LoadProfiles()
    53  
    54  func (s *appArmorSuite) TestLoadProfilesRunsAppArmorParserReplace(c *C) {
    55  	cmd := testutil.MockCommand(c, "apparmor_parser", "")
    56  	defer cmd.Restore()
    57  	err := apparmor.LoadProfiles([]string{"/path/to/snap.samba.smbd"}, apparmor.CacheDir, 0)
    58  	c.Assert(err, IsNil)
    59  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
    60  		{"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "--quiet", "/path/to/snap.samba.smbd"},
    61  	})
    62  }
    63  
    64  func (s *appArmorSuite) TestLoadProfilesMany(c *C) {
    65  	cmd := testutil.MockCommand(c, "apparmor_parser", "")
    66  	defer cmd.Restore()
    67  	err := apparmor.LoadProfiles([]string{"/path/to/snap.samba.smbd", "/path/to/another.profile"}, apparmor.CacheDir, 0)
    68  	c.Assert(err, IsNil)
    69  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
    70  		{"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "--quiet", "/path/to/snap.samba.smbd", "/path/to/another.profile"},
    71  	})
    72  }
    73  
    74  func (s *appArmorSuite) TestLoadProfilesNone(c *C) {
    75  	cmd := testutil.MockCommand(c, "apparmor_parser", "")
    76  	defer cmd.Restore()
    77  	err := apparmor.LoadProfiles([]string{}, apparmor.CacheDir, 0)
    78  	c.Assert(err, IsNil)
    79  	c.Check(cmd.Calls(), HasLen, 0)
    80  }
    81  
    82  func (s *appArmorSuite) TestLoadProfilesReportsErrors(c *C) {
    83  	cmd := testutil.MockCommand(c, "apparmor_parser", "exit 42")
    84  	defer cmd.Restore()
    85  	err := apparmor.LoadProfiles([]string{"/path/to/snap.samba.smbd"}, apparmor.CacheDir, 0)
    86  	c.Assert(err.Error(), Equals, `cannot load apparmor profiles: exit status 42
    87  apparmor_parser output:
    88  `)
    89  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
    90  		{"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "--quiet", "/path/to/snap.samba.smbd"},
    91  	})
    92  }
    93  
    94  func (s *appArmorSuite) TestLoadProfilesRunsAppArmorParserReplaceWithSnapdDebug(c *C) {
    95  	os.Setenv("SNAPD_DEBUG", "1")
    96  	defer os.Unsetenv("SNAPD_DEBUG")
    97  	cmd := testutil.MockCommand(c, "apparmor_parser", "")
    98  	defer cmd.Restore()
    99  	err := apparmor.LoadProfiles([]string{"/path/to/snap.samba.smbd"}, apparmor.CacheDir, 0)
   100  	c.Assert(err, IsNil)
   101  	c.Assert(cmd.Calls(), DeepEquals, [][]string{
   102  		{"apparmor_parser", "--replace", "--write-cache", "-O", "no-expr-simplify", "--cache-loc=/var/cache/apparmor", "/path/to/snap.samba.smbd"},
   103  	})
   104  }
   105  
   106  // Tests for Profile.Unload()
   107  
   108  func (s *appArmorSuite) TestUnloadProfilesMany(c *C) {
   109  	err := apparmor.UnloadProfiles([]string{"/path/to/snap.samba.smbd", "/path/to/another.profile"}, apparmor.CacheDir)
   110  	c.Assert(err, IsNil)
   111  }
   112  
   113  func (s *appArmorSuite) TestUnloadProfilesNone(c *C) {
   114  	err := apparmor.UnloadProfiles([]string{}, apparmor.CacheDir)
   115  	c.Assert(err, IsNil)
   116  }
   117  
   118  func (s *appArmorSuite) TestUnloadRemovesCachedProfile(c *C) {
   119  	cmd := testutil.MockCommand(c, "apparmor_parser", "")
   120  	defer cmd.Restore()
   121  
   122  	dirs.SetRootDir(c.MkDir())
   123  	defer dirs.SetRootDir("")
   124  	err := os.MkdirAll(apparmor.CacheDir, 0755)
   125  	c.Assert(err, IsNil)
   126  
   127  	fname := filepath.Join(apparmor.CacheDir, "profile")
   128  	ioutil.WriteFile(fname, []byte("blob"), 0600)
   129  	err = apparmor.UnloadProfiles([]string{"profile"}, apparmor.CacheDir)
   130  	c.Assert(err, IsNil)
   131  	_, err = os.Stat(fname)
   132  	c.Check(os.IsNotExist(err), Equals, true)
   133  }
   134  
   135  func (s *appArmorSuite) TestUnloadRemovesCachedProfileInForest(c *C) {
   136  	cmd := testutil.MockCommand(c, "apparmor_parser", "")
   137  	defer cmd.Restore()
   138  
   139  	dirs.SetRootDir(c.MkDir())
   140  	defer dirs.SetRootDir("")
   141  	err := os.MkdirAll(apparmor.CacheDir, 0755)
   142  	c.Assert(err, IsNil)
   143  	// mock the forest subdir and features file
   144  	subdir := filepath.Join(apparmor.CacheDir, "deadbeef.0")
   145  	err = os.MkdirAll(subdir, 0700)
   146  	c.Assert(err, IsNil)
   147  	features := filepath.Join(subdir, ".features")
   148  	ioutil.WriteFile(features, []byte("blob"), 0644)
   149  
   150  	fname := filepath.Join(subdir, "profile")
   151  	ioutil.WriteFile(fname, []byte("blob"), 0600)
   152  	err = apparmor.UnloadProfiles([]string{"profile"}, apparmor.CacheDir)
   153  	c.Assert(err, IsNil)
   154  	_, err = os.Stat(fname)
   155  	c.Check(os.IsNotExist(err), Equals, true)
   156  	c.Check(osutil.FileExists(features), Equals, true)
   157  }
   158  
   159  func (s *appArmorSuite) TestReloadAllSnapProfilesFailure(c *C) {
   160  	dirs.SetRootDir(c.MkDir())
   161  	defer dirs.SetRootDir("")
   162  
   163  	// Create a couple of empty profiles
   164  	err := os.MkdirAll(dirs.SnapAppArmorDir, 0755)
   165  	defer func() {
   166  		os.RemoveAll(dirs.SnapAppArmorDir)
   167  	}()
   168  	c.Assert(err, IsNil)
   169  	var profiles []string
   170  	for _, profile := range []string{"app1", "second_app"} {
   171  		path := filepath.Join(dirs.SnapAppArmorDir, profile)
   172  		f, err := os.Create(path)
   173  		f.Close()
   174  		c.Assert(err, IsNil)
   175  		profiles = append(profiles, path)
   176  	}
   177  
   178  	var passedProfiles []string
   179  	restore := apparmor.MockLoadProfiles(func(paths []string, cacheDir string, flags apparmor.AaParserFlags) error {
   180  		passedProfiles = paths
   181  		return errors.New("reload error")
   182  	})
   183  	defer restore()
   184  	err = apparmor.ReloadAllSnapProfiles()
   185  	c.Check(passedProfiles, DeepEquals, profiles)
   186  	c.Assert(err, ErrorMatches, "reload error")
   187  }
   188  
   189  func (s *appArmorSuite) TestReloadAllSnapProfilesHappy(c *C) {
   190  	dirs.SetRootDir(c.MkDir())
   191  	defer dirs.SetRootDir("")
   192  
   193  	// Create a couple of empty profiles
   194  	err := os.MkdirAll(dirs.SnapAppArmorDir, 0755)
   195  	defer func() {
   196  		os.RemoveAll(dirs.SnapAppArmorDir)
   197  	}()
   198  	c.Assert(err, IsNil)
   199  	var profiles []string
   200  	for _, profile := range []string{"first", "second", "third"} {
   201  		path := filepath.Join(dirs.SnapAppArmorDir, profile)
   202  		f, err := os.Create(path)
   203  		f.Close()
   204  		c.Assert(err, IsNil)
   205  		profiles = append(profiles, path)
   206  	}
   207  
   208  	const snapConfineProfile = "/etc/apparmor.d/some.where.snap-confine"
   209  	restore := apparmor.MockSnapConfineDistroProfilePath(func() string {
   210  		return snapConfineProfile
   211  	})
   212  	defer restore()
   213  	profiles = append(profiles, snapConfineProfile)
   214  
   215  	var passedProfiles []string
   216  	var passedCacheDir string
   217  	var passedFlags apparmor.AaParserFlags
   218  	restore = apparmor.MockLoadProfiles(func(paths []string, cacheDir string, flags apparmor.AaParserFlags) error {
   219  		passedProfiles = paths
   220  		passedCacheDir = cacheDir
   221  		passedFlags = flags
   222  		return nil
   223  	})
   224  	defer restore()
   225  
   226  	err = apparmor.ReloadAllSnapProfiles()
   227  	c.Check(passedProfiles, DeepEquals, profiles)
   228  	c.Check(passedCacheDir, Equals, filepath.Join(dirs.GlobalRootDir, "/var/cache/apparmor"))
   229  	c.Check(passedFlags, Equals, apparmor.SkipReadCache)
   230  	c.Assert(err, IsNil)
   231  }
   232  
   233  // Tests for LoadedProfiles()
   234  
   235  func (s *appArmorSuite) TestLoadedApparmorProfilesReturnsErrorOnMissingFile(c *C) {
   236  	profiles, err := apparmor.LoadedProfiles()
   237  	c.Assert(err, ErrorMatches, "open .*: no such file or directory")
   238  	c.Check(profiles, IsNil)
   239  }
   240  
   241  func (s *appArmorSuite) TestLoadedApparmorProfilesCanParseEmptyFile(c *C) {
   242  	ioutil.WriteFile(s.profilesFilename, []byte(""), 0600)
   243  	profiles, err := apparmor.LoadedProfiles()
   244  	c.Assert(err, IsNil)
   245  	c.Check(profiles, HasLen, 0)
   246  }
   247  
   248  func (s *appArmorSuite) TestLoadedApparmorProfilesParsesAndFiltersData(c *C) {
   249  	ioutil.WriteFile(s.profilesFilename, []byte(
   250  		// The output contains some of the snappy-specific elements
   251  		// and some non-snappy elements pulled from Ubuntu 16.04 desktop
   252  		//
   253  		// The pi2-piglow.{background,foreground}.snap entries are the only
   254  		// ones that should be reported by the function.
   255  		`/sbin/dhclient (enforce)
   256  /usr/bin/ubuntu-core-launcher (enforce)
   257  /usr/bin/ubuntu-core-launcher (enforce)
   258  /usr/lib/NetworkManager/nm-dhcp-client.action (enforce)
   259  /usr/lib/NetworkManager/nm-dhcp-helper (enforce)
   260  /usr/lib/connman/scripts/dhclient-script (enforce)
   261  /usr/lib/lightdm/lightdm-guest-session (enforce)
   262  /usr/lib/lightdm/lightdm-guest-session//chromium (enforce)
   263  /usr/lib/telepathy/telepathy-* (enforce)
   264  /usr/lib/telepathy/telepathy-*//pxgsettings (enforce)
   265  /usr/lib/telepathy/telepathy-*//sanitized_helper (enforce)
   266  snap.pi2-piglow.background (enforce)
   267  snap.pi2-piglow.foreground (enforce)
   268  webbrowser-app (enforce)
   269  webbrowser-app//oxide_helper (enforce)
   270  `), 0600)
   271  	profiles, err := apparmor.LoadedProfiles()
   272  	c.Assert(err, IsNil)
   273  	c.Check(profiles, DeepEquals, []string{
   274  		"snap.pi2-piglow.background",
   275  		"snap.pi2-piglow.foreground",
   276  	})
   277  }
   278  
   279  func (s *appArmorSuite) TestLoadedApparmorProfilesHandlesParsingErrors(c *C) {
   280  	ioutil.WriteFile(s.profilesFilename, []byte("broken stuff here\n"), 0600)
   281  	profiles, err := apparmor.LoadedProfiles()
   282  	c.Assert(err, ErrorMatches, "newline in format does not match input")
   283  	c.Check(profiles, IsNil)
   284  	ioutil.WriteFile(s.profilesFilename, []byte("truncated"), 0600)
   285  	profiles, err = apparmor.LoadedProfiles()
   286  	c.Assert(err, ErrorMatches, `syntax error, expected: name \(mode\)`)
   287  	c.Check(profiles, IsNil)
   288  }
   289  
   290  func (s *appArmorSuite) TestMaybeSetNumberOfJobs(c *C) {
   291  	var cpus int
   292  	restore := apparmor.MockRuntimeNumCPU(func() int {
   293  		return cpus
   294  	})
   295  	defer restore()
   296  
   297  	cpus = 10
   298  	c.Check(apparmor.NumberOfJobsParam(), Equals, "-j8")
   299  
   300  	cpus = 2
   301  	c.Check(apparmor.NumberOfJobsParam(), Equals, "-j1")
   302  
   303  	cpus = 1
   304  	c.Check(apparmor.NumberOfJobsParam(), Equals, "-j1")
   305  }
   306  
   307  func (s *appArmorSuite) TestSnapConfineDistroProfilePath(c *C) {
   308  	baseDir := c.MkDir()
   309  	restore := testutil.Backup(&apparmor.ConfDir)
   310  	apparmor.ConfDir = filepath.Join(baseDir, "/a/b/c")
   311  	defer restore()
   312  
   313  	for _, testData := range []struct {
   314  		existingFiles []string
   315  		expectedPath  string
   316  	}{
   317  		{[]string{}, ""},
   318  		{[]string{"/a/b/c/usr.lib.snapd.snap-confine.real"}, "/a/b/c/usr.lib.snapd.snap-confine.real"},
   319  		{[]string{"/a/b/c/usr.lib.snapd.snap-confine"}, "/a/b/c/usr.lib.snapd.snap-confine"},
   320  		{[]string{"/a/b/c/usr.libexec.snapd.snap-confine"}, "/a/b/c/usr.libexec.snapd.snap-confine"},
   321  		{
   322  			[]string{"/a/b/c/usr.lib.snapd.snap-confine.real", "/a/b/c/usr.lib.snapd.snap-confine"},
   323  			"/a/b/c/usr.lib.snapd.snap-confine.real",
   324  		},
   325  	} {
   326  		// Remove leftovers from the previous iteration
   327  		err := os.RemoveAll(baseDir)
   328  		c.Assert(err, IsNil)
   329  
   330  		existingFiles := testData.existingFiles
   331  		for _, path := range existingFiles {
   332  			fullPath := filepath.Join(baseDir, path)
   333  			err := os.MkdirAll(filepath.Dir(fullPath), 0755)
   334  			c.Assert(err, IsNil)
   335  			err = ioutil.WriteFile(fullPath, []byte("I'm an ELF binary"), 0755)
   336  			c.Assert(err, IsNil)
   337  		}
   338  		var expectedPath string
   339  		if testData.expectedPath != "" {
   340  			expectedPath = filepath.Join(baseDir, testData.expectedPath)
   341  		}
   342  		path := apparmor.SnapConfineDistroProfilePath()
   343  		c.Check(path, Equals, expectedPath, Commentf("Existing: %q", existingFiles))
   344  	}
   345  }