github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/interfaces/mount/backend_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 mount_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "sort" 28 "strings" 29 "testing" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/dirs" 34 "github.com/snapcore/snapd/interfaces" 35 "github.com/snapcore/snapd/interfaces/ifacetest" 36 "github.com/snapcore/snapd/interfaces/mount" 37 "github.com/snapcore/snapd/osutil" 38 "github.com/snapcore/snapd/sandbox/cgroup" 39 "github.com/snapcore/snapd/snap" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 func Test(t *testing.T) { 44 TestingT(t) 45 } 46 47 type backendSuite struct { 48 ifacetest.BackendSuite 49 50 iface2 *ifacetest.TestInterface 51 } 52 53 var _ = Suite(&backendSuite{}) 54 55 func (s *backendSuite) SetUpTest(c *C) { 56 s.Backend = &mount.Backend{} 57 s.BackendSuite.SetUpTest(c) 58 59 c.Assert(s.Repo.AddBackend(s.Backend), IsNil) 60 61 c.Assert(os.MkdirAll(dirs.SnapMountPolicyDir, 0700), IsNil) 62 c.Assert(os.MkdirAll(dirs.SnapRunNsDir, 0700), IsNil) 63 64 // add second iface so that we actually test combining snippets 65 s.iface2 = &ifacetest.TestInterface{InterfaceName: "iface2"} 66 c.Assert(s.Repo.AddInterface(s.iface2), IsNil) 67 } 68 69 func (s *backendSuite) TearDownTest(c *C) { 70 s.BackendSuite.TearDownTest(c) 71 } 72 73 func (s *backendSuite) TestName(c *C) { 74 c.Check(s.Backend.Name(), Equals, interfaces.SecurityMount) 75 } 76 77 func (s *backendSuite) TestRemove(c *C) { 78 appCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.hello-world.fstab") 79 err := ioutil.WriteFile(appCanaryToGo, []byte("ni! ni! ni!"), 0644) 80 c.Assert(err, IsNil) 81 82 hookCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.hook.configure.fstab") 83 err = ioutil.WriteFile(hookCanaryToGo, []byte("ni! ni! ni!"), 0644) 84 c.Assert(err, IsNil) 85 86 snapCanaryToGo := filepath.Join(dirs.SnapMountPolicyDir, "snap.hello-world.fstab") 87 err = ioutil.WriteFile(snapCanaryToGo, []byte("ni! ni! ni!"), 0644) 88 c.Assert(err, IsNil) 89 90 appCanaryToStay := filepath.Join(dirs.SnapMountPolicyDir, "snap.i-stay.really.fstab") 91 err = ioutil.WriteFile(appCanaryToStay, []byte("stay!"), 0644) 92 c.Assert(err, IsNil) 93 94 snapCanaryToStay := filepath.Join(dirs.SnapMountPolicyDir, "snap.i-stay.fstab") 95 err = ioutil.WriteFile(snapCanaryToStay, []byte("stay!"), 0644) 96 c.Assert(err, IsNil) 97 98 // Write the .mnt file, the logic for discarding mount namespaces uses it 99 // as a canary file to look for to even attempt to run the mount discard 100 // tool. 101 mntFile := filepath.Join(dirs.SnapRunNsDir, "hello-world.mnt") 102 err = ioutil.WriteFile(mntFile, []byte(""), 0644) 103 c.Assert(err, IsNil) 104 105 // Mock snap-discard-ns and allow tweak distro libexec dir so that it is used. 106 cmd := testutil.MockCommand(c, "snap-discard-ns", "") 107 defer cmd.Restore() 108 dirs.DistroLibExecDir = cmd.BinDir() 109 110 err = s.Backend.Remove("hello-world") 111 c.Assert(err, IsNil) 112 113 c.Assert(osutil.FileExists(snapCanaryToGo), Equals, false) 114 c.Assert(osutil.FileExists(appCanaryToGo), Equals, false) 115 c.Assert(osutil.FileExists(hookCanaryToGo), Equals, false) 116 c.Assert(appCanaryToStay, testutil.FileEquals, "stay!") 117 c.Assert(snapCanaryToStay, testutil.FileEquals, "stay!") 118 c.Assert(cmd.Calls(), DeepEquals, [][]string{{"snap-discard-ns", "hello-world"}}) 119 } 120 121 var mockSnapYaml = `name: snap-name 122 version: 1 123 apps: 124 app1: 125 app2: 126 hooks: 127 configure: 128 plugs: [iface-plug] 129 plugs: 130 iface-plug: 131 interface: iface 132 slots: 133 iface-slot: 134 interface: iface2 135 ` 136 137 func (s *backendSuite) TestSetupSetsupSimple(c *C) { 138 fsEntry1 := osutil.MountEntry{Name: "/src-1", Dir: "/dst-1", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0} 139 fsEntry2 := osutil.MountEntry{Name: "/src-2", Dir: "/dst-2", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0} 140 fsEntry3 := osutil.MountEntry{Name: "/src-3", Dir: "/dst-3", Type: "none", Options: []string{"bind", "ro"}, DumpFrequency: 0, CheckPassNumber: 0} 141 142 // Give the plug a permanent effect 143 s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error { 144 if err := spec.AddMountEntry(fsEntry1); err != nil { 145 return err 146 } 147 return spec.AddUserMountEntry(fsEntry3) 148 } 149 // Give the slot a permanent effect 150 s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *snap.SlotInfo) error { 151 return spec.AddMountEntry(fsEntry2) 152 } 153 154 // confinement options are irrelevant to this security backend 155 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", mockSnapYaml, 0) 156 157 // ensure both security effects from iface/iface2 are combined 158 // (because mount profiles are global in the whole snap) 159 expected := strings.Split(fmt.Sprintf("%s\n%s\n", fsEntry1, fsEntry2), "\n") 160 sort.Strings(expected) 161 // and that we have the modern fstab file (global for snap) 162 fn := filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name.fstab") 163 content, err := ioutil.ReadFile(fn) 164 c.Assert(err, IsNil, Commentf("Expected mount profile for the whole snap")) 165 got := strings.Split(string(content), "\n") 166 sort.Strings(got) 167 c.Check(got, DeepEquals, expected) 168 169 // Check that the user-fstab file was written with the user mount 170 fn = filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name.user-fstab") 171 content, err = ioutil.ReadFile(fn) 172 c.Assert(err, IsNil, Commentf("Expected user mount profile for the whole snap")) 173 c.Check(string(content), Equals, fsEntry3.String()+"\n") 174 } 175 176 func (s *backendSuite) TestSetupSetsupWithoutDir(c *C) { 177 s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error { 178 return spec.AddMountEntry(osutil.MountEntry{}) 179 } 180 181 // Ensure that backend.Setup() creates the required dir on demand 182 os.Remove(dirs.SnapMountPolicyDir) 183 s.InstallSnap(c, interfaces.ConfinementOptions{}, "", mockSnapYaml, 0) 184 } 185 186 func (s *backendSuite) TestParallelInstanceSetup(c *C) { 187 old := dirs.SnapDataDir 188 defer func() { 189 dirs.SnapDataDir = old 190 }() 191 dirs.SnapDataDir = "/var/snap" 192 snapEntry := osutil.MountEntry{Name: "/snap/snap-name_instance", Dir: "/snap/snap-name", Type: "none", Options: []string{"rbind", osutil.XSnapdOriginOvername()}} 193 dataEntry := osutil.MountEntry{Name: "/var/snap/snap-name_instance", Dir: "/var/snap/snap-name", Type: "none", Options: []string{"rbind", osutil.XSnapdOriginOvername()}} 194 fsEntry1 := osutil.MountEntry{Name: "/src-1", Dir: "/dst-1", Type: "none", Options: []string{"bind", "ro"}} 195 fsEntry2 := osutil.MountEntry{Name: "/src-2", Dir: "/dst-2", Type: "none", Options: []string{"bind", "ro"}} 196 userFsEntry := osutil.MountEntry{Name: "/src-3", Dir: "/dst-3", Type: "none", Options: []string{"bind", "ro"}} 197 198 // Give the plug a permanent effect 199 s.Iface.MountPermanentPlugCallback = func(spec *mount.Specification, plug *snap.PlugInfo) error { 200 if err := spec.AddMountEntry(fsEntry1); err != nil { 201 return err 202 } 203 return spec.AddUserMountEntry(userFsEntry) 204 } 205 // Give the slot a permanent effect 206 s.iface2.MountPermanentSlotCallback = func(spec *mount.Specification, slot *snap.SlotInfo) error { 207 return spec.AddMountEntry(fsEntry2) 208 } 209 210 // confinement options are irrelevant to this security backend 211 s.InstallSnap(c, interfaces.ConfinementOptions{}, "snap-name_instance", mockSnapYaml, 0) 212 213 // Check that snap fstab file contains parallel instance setup and data from interfaces 214 expected := strings.Join([]string{snapEntry.String(), dataEntry.String(), fsEntry2.String(), fsEntry1.String()}, "\n") + "\n" 215 fn := filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name_instance.fstab") 216 c.Check(fn, testutil.FileEquals, expected) 217 218 // Check that the user-fstab file was written with user mount only 219 fn = filepath.Join(dirs.SnapMountPolicyDir, "snap.snap-name_instance.user-fstab") 220 c.Check(fn, testutil.FileEquals, userFsEntry.String()+"\n") 221 } 222 223 func (s *backendSuite) TestSandboxFeatures(c *C) { 224 restore := cgroup.MockVersion(cgroup.V1, nil) 225 defer restore() 226 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{ 227 "layouts", 228 "mount-namespace", 229 "per-snap-persistency", 230 "per-snap-profiles", 231 "per-snap-updates", 232 "per-snap-user-profiles", 233 "stale-base-invalidation", 234 "freezer-cgroup-v1", 235 }) 236 237 restore = cgroup.MockVersion(cgroup.V2, nil) 238 defer restore() 239 c.Assert(s.Backend.SandboxFeatures(), DeepEquals, []string{ 240 "layouts", 241 "mount-namespace", 242 "per-snap-persistency", 243 "per-snap-profiles", 244 "per-snap-updates", 245 "per-snap-user-profiles", 246 "stale-base-invalidation", 247 }) 248 }