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 }