From 00c5ba371a90733e167c0a407d09c65629f103ae Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 28 Nov 2022 10:37:00 +0000 Subject: [PATCH] cve-2021-2021-44730-44731-4120-auto-remove =================================================================== Gbp-Pq: Name 0016-cve-2021-2021-44730-44731-4120-auto-remove.patch --- overlord/devicestate/firstboot_test.go | 9 +- overlord/managers_test.go | 21 +- overlord/snapstate/snapmgr.go | 118 +++++++++ .../snapstate_config_defaults_test.go | 2 +- overlord/snapstate/snapstate_test.go | 244 +++++++++++++++++- .../task.yaml | 110 ++++++++ 6 files changed, 497 insertions(+), 7 deletions(-) create mode 100644 tests/nested/manual/snapd-removes-vulnerable-snap-confine-revs/task.yaml diff --git a/overlord/devicestate/firstboot_test.go b/overlord/devicestate/firstboot_test.go index 6bdf6688..250c2118 100644 --- a/overlord/devicestate/firstboot_test.go +++ b/overlord/devicestate/firstboot_test.go @@ -1215,7 +1215,14 @@ type: base` snapdYaml := `name: snapd version: 1.0 ` - snapdFname, snapdDecl, snapdRev := s.MakeAssertedSnap(c, snapdYaml, nil, snap.R(2), "canonical") + // the info file is needed by the Ensure() loop of snapstate manager + snapdSnapFiles := [][]string{ + {"usr/lib/snapd/info", ` +VERSION=2.54.3+git1.g479e745-dirty +SNAPD_APPARMOR_REEXEC=0 +`}, + } + snapdFname, snapdDecl, snapdRev := s.MakeAssertedSnap(c, snapdYaml, snapdSnapFiles, snap.R(2), "canonical") s.WriteAssertions("snapd.asserts", snapdRev, snapdDecl) var kernelFname string diff --git a/overlord/managers_test.go b/overlord/managers_test.go index 2ec26961..94bd8af3 100644 --- a/overlord/managers_test.go +++ b/overlord/managers_test.go @@ -347,6 +347,21 @@ func (s *baseMgrsSuite) SetUpTest(c *C) { }, }) + // commonly used core and snapd revisions in tests + defaultInfoFile := ` +VERSION=2.54.3+git1.g479e745-dirty +SNAPD_APPARMOR_REEXEC=0 +` + for _, snapName := range []string{"snapd", "core"} { + for _, rev := range []string{"1", "11", "30"} { + infoFile := filepath.Join(dirs.GlobalRootDir, "snap", snapName, rev, dirs.CoreLibExecDir, "info") + err = os.MkdirAll(filepath.Dir(infoFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(infoFile, []byte(defaultInfoFile), 0644) + c.Assert(err, IsNil) + } + } + // don't actually try to talk to the store on snapstate.Ensure // needs doing after the call to devicestate.Manager (which happens in overlord.New) snapstate.CanAutoRefresh = nil @@ -520,8 +535,10 @@ hooks: snapdirs, err := filepath.Glob(filepath.Join(dirs.SnapMountDir, "*")) c.Assert(err, IsNil) - // just README and bin - c.Check(snapdirs, HasLen, 2) + // just README, bin, snapd, and core (snapd and core are there because we + // have info files for those snaps which need to be read from the snapstate + // Ensure loop) + c.Check(snapdirs, HasLen, 4) for _, d := range snapdirs { c.Check(filepath.Base(d), Not(Equals), "foo") } diff --git a/overlord/snapstate/snapmgr.go b/overlord/snapstate/snapmgr.go index f7cb8c16..d7b8ff41 100644 --- a/overlord/snapstate/snapmgr.go +++ b/overlord/snapstate/snapmgr.go @@ -25,6 +25,7 @@ import ( "fmt" "io" "os" + "path/filepath" "strings" "time" @@ -43,7 +44,9 @@ import ( "github.com/snapcore/snapd/snap" "github.com/snapcore/snapd/snap/channel" "github.com/snapcore/snapd/snapdenv" + "github.com/snapcore/snapd/snapdtool" "github.com/snapcore/snapd/store" + "github.com/snapcore/snapd/strutil" ) var ( @@ -574,6 +577,120 @@ func (m *SnapManager) EnsureAutoRefreshesAreDelayed(delay time.Duration) ([]*sta return autoRefreshChgsInFlight, nil } +func (m *SnapManager) ensureVulnerableSnapRemoved(name string) error { + var removedYet bool + key := fmt.Sprintf("%s-snap-cve-2021-44731-vuln-removed", name) + if err := m.state.Get(key, &removedYet); err != nil && err != state.ErrNoState { + return err + } + if removedYet { + return nil + } + var snapSt SnapState + err := Get(m.state, name, &snapSt) + if err != nil && err != state.ErrNoState { + return err + } + if err == state.ErrNoState { + // not installed, nothing to do + return nil + } + + // check if the installed, active version is fixed + fixedVersionInstalled := false + inactiveVulnRevisions := []snap.Revision{} + for _, si := range snapSt.Sequence { + // check this version + s := snap.Info{SideInfo: *si} + ver, err := snapdtool.SnapdVersionFromInfoFile(filepath.Join(s.MountDir(), dirs.CoreLibExecDir, "info")) + if err != nil { + return err + } + // res is < 0 if "ver" is lower than "2.54.3" + res, err := strutil.VersionCompare(ver, "2.54.3") + if err != nil { + return err + } + revIsVulnerable := (res < 0) + switch { + case !revIsVulnerable && si.Revision == snapSt.Current: + fixedVersionInstalled = true + case revIsVulnerable && si.Revision == snapSt.Current: + // the active installed revision is not fixed, we can break out + // early since we know we won't be able to remove old revisions + return nil + case revIsVulnerable && si.Revision != snapSt.Current: + // si revision is not fixed, but is not active, so it is a candidate + // for removal + inactiveVulnRevisions = append(inactiveVulnRevisions, si.Revision) + default: + // si revision is not active, but it is fixed, so just ignore it + } + } + + if !fixedVersionInstalled { + return nil + } + // TODO: should we use one change for removing all the snap revisions? + + // remove all the inactive vulnerable revisions + for _, rev := range inactiveVulnRevisions { + tss, err := Remove(m.state, name, rev, nil) + + if err != nil { + // in case of conflict, just trigger another ensure in a little + // bit and try again later + if _, ok := err.(*ChangeConflictError); ok { + m.state.EnsureBefore(time.Minute) + return nil + } + return fmt.Errorf("cannot make task set for removing %s snap: %v", name, err) + } + + msg := fmt.Sprintf(i18n.G("Remove vulnerable %q snap"), name) + + chg := m.state.NewChange("remove-snap", msg) + chg.AddAll(tss) + chg.Set("snap-names", []string{name}) + } + + // TODO: is it okay to set state here as done or should we do this + // elsewhere after the change is done somehow? + + // mark state as done + m.state.Set(key, true) + + // not strictly necessary, but does not hurt to ensure anyways + m.state.EnsureBefore(0) + + return nil +} + +func (m *SnapManager) ensureVulnerableSnapConfineVersionsRemovedOnClassic() error { + // only remove snaps on classic + if !release.OnClassic { + return nil + } + + m.state.Lock() + defer m.state.Unlock() + + // we have to remove vulnerable versions of both the core and snapd snaps + // only when we now have fixed versions installed / active + // the fixed version is 2.54.3, so if the version of the current core/snapd + // snap is that or higher, then we proceed (if we didn't already do this) + + if err := m.ensureVulnerableSnapRemoved("snapd"); err != nil { + return err + } + + if err := m.ensureVulnerableSnapRemoved("core"); err != nil { + return err + } + + return nil +} + // ensureForceDevmodeDropsDevmodeFromState undoes the forced devmode // in snapstate for forced devmode distros. func (m *SnapManager) ensureForceDevmodeDropsDevmodeFromState() error { @@ -873,6 +990,7 @@ func (m *SnapManager) Ensure() error { m.refreshHints.Ensure(), m.catalogRefresh.Ensure(), m.localInstallCleanup(), + m.ensureVulnerableSnapConfineVersionsRemovedOnClassic(), } //FIXME: use firstErr helper diff --git a/overlord/snapstate/snapstate_config_defaults_test.go b/overlord/snapstate/snapstate_config_defaults_test.go index 44cb0775..afe03f40 100644 --- a/overlord/snapstate/snapstate_config_defaults_test.go +++ b/overlord/snapstate/snapstate_config_defaults_test.go @@ -271,7 +271,7 @@ func (s *snapmgrTestSuite) TestTransitionCoreTasksNoUbuntuCore(c *C) { snapstate.Set(s.state, "core", &snapstate.SnapState{ Active: true, - Sequence: []*snap.SideInfo{{RealName: "corecore", SnapID: "core-snap-id", Revision: snap.R(1)}}, + Sequence: []*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1)}}, Current: snap.R(1), SnapType: "os", }) diff --git a/overlord/snapstate/snapstate_test.go b/overlord/snapstate/snapstate_test.go index 374ee958..5eb8326f 100644 --- a/overlord/snapstate/snapstate_test.go +++ b/overlord/snapstate/snapstate_test.go @@ -213,6 +213,22 @@ func (s *snapmgrTestSuite) SetUpTest(c *C) { Current: snap.R(1), SnapType: "os", }) + + // commonly used revisions in tests + defaultInfoFile := ` +VERSION=2.54.3+git1.g479e745-dirty +SNAPD_APPARMOR_REEXEC=0 +` + for _, snapName := range []string{"snapd", "core"} { + for _, rev := range []string{"1", "11"} { + infoFile := filepath.Join(dirs.GlobalRootDir, "snap", snapName, rev, dirs.CoreLibExecDir, "info") + err = os.MkdirAll(filepath.Dir(infoFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(infoFile, []byte(defaultInfoFile), 0644) + c.Assert(err, IsNil) + } + } + s.state.Unlock() snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { @@ -2668,6 +2684,228 @@ func (s *snapmgrTestSuite) TestErrreportDisable(c *C) { // no failure report was generated } +func (s *snapmgrTestSuite) TestEnsureRemovesVulnerableCoreSnap(c *C) { + s.testEnsureRemovesVulnerableSnap(c, "core") +} + +func (s *snapmgrTestSuite) TestEnsureRemovesVulnerableSnapdSnap(c *C) { + s.testEnsureRemovesVulnerableSnap(c, "snapd") +} + +func (s *snapmgrTestSuite) testEnsureRemovesVulnerableSnap(c *C, snapName string) { + // make the currently installed snap info file fixed but an old version + // vulnerable + fixedInfoFile := ` +VERSION=2.54.3+git1.g479e745-dirty +SNAPD_APPARMOR_REEXEC=0 +` + vulnInfoFile := ` +VERSION=2.54.2+git1.g479e745-dirty +SNAPD_APPARMOR_REEXEC=0 +` + + // revision 1 vulnerable + infoFile := filepath.Join(dirs.GlobalRootDir, "snap", snapName, "1", dirs.CoreLibExecDir, "info") + err := os.MkdirAll(filepath.Dir(infoFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(infoFile, []byte(vulnInfoFile), 0644) + c.Assert(err, IsNil) + + // revision 2 fixed + infoFile2 := filepath.Join(dirs.GlobalRootDir, "snap", snapName, "2", dirs.CoreLibExecDir, "info") + err = os.MkdirAll(filepath.Dir(infoFile2), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(infoFile2, []byte(fixedInfoFile), 0644) + c.Assert(err, IsNil) + + // revision 11 fixed + infoFile11 := filepath.Join(dirs.GlobalRootDir, "snap", snapName, "11", dirs.CoreLibExecDir, "info") + err = os.MkdirAll(filepath.Dir(infoFile11), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(infoFile11, []byte(fixedInfoFile), 0644) + c.Assert(err, IsNil) + + // use generic classic model + r := snapstatetest.UseFallbackDeviceModel() + defer r() + + st := s.state + st.Lock() + // ensure that only this specific snap is installed + snapstate.Set(s.state, "core", nil) + snapstate.Set(s.state, "snapd", nil) + + snapSt := &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{ + {RealName: snapName, Revision: snap.R(1)}, + {RealName: snapName, Revision: snap.R(2)}, + {RealName: snapName, Revision: snap.R(11)}, + }, + Current: snap.R(11), + SnapType: "os", + } + if snapName == "snapd" { + snapSt.SnapType = "snapd" + } + snapstate.Set(s.state, snapName, snapSt) + st.Unlock() + + // special policy only on classic + r = release.MockOnClassic(true) + defer r() + ensureErr := s.snapmgr.Ensure() + c.Assert(ensureErr, IsNil) + + // we should have created a single remove change for revision 1, revision 2 + // should have been left alone + st.Lock() + defer st.Unlock() + + allChgs := st.Changes() + c.Assert(allChgs, HasLen, 1) + removeChg := allChgs[0] + c.Assert(removeChg.Status(), Equals, state.DoStatus) + c.Assert(removeChg.Kind(), Equals, "remove-snap") + c.Assert(removeChg.Summary(), Equals, fmt.Sprintf(`Remove vulnerable %q snap`, snapName)) + + c.Assert(removeChg.Tasks(), HasLen, 2) + clearSnap := removeChg.Tasks()[0] + discardSnap := removeChg.Tasks()[1] + c.Assert(clearSnap.Kind(), Equals, "clear-snap") + c.Assert(discardSnap.Kind(), Equals, "discard-snap") + var snapsup snapstate.SnapSetup + err = clearSnap.Get("snap-setup", &snapsup) + c.Assert(err, IsNil) + c.Assert(snapsup.SideInfo.Revision, Equals, snap.R(1)) + + // and we set the appropriate key in the state + var removeDone bool + st.Get(snapName+"-snap-cve-2021-44731-vuln-removed", &removeDone) + c.Assert(removeDone, Equals, true) +} + +func (s *snapmgrTestSuite) TestEnsureChecksSnapdInfoFileOnClassicOnly(c *C) { + // delete the core/snapd snap info files - they should always exist in real + // devices, but deleting them here makes it so we can see the failure + // trying to read the files easily + + infoFile := filepath.Join(dirs.GlobalRootDir, "snap", "core", "1", dirs.CoreLibExecDir, "info") + err := os.Remove(infoFile) + c.Assert(err, IsNil) + + // special policy only on classic + r := release.MockOnClassic(true) + defer r() + ensureErr := s.snapmgr.Ensure() + c.Assert(ensureErr, ErrorMatches, "cannot open snapd info file.*") + + // if we are not on classic nothing happens + r = release.MockOnClassic(false) + defer r() + + ensureErr = s.snapmgr.Ensure() + c.Assert(ensureErr, IsNil) +} + +func (s *snapmgrTestSuite) TestEnsureSkipsCheckingSnapdSnapInfoFileWhenStateSet(c *C) { + // we default from SetUp to having the core snap installed, remove it so we + // only have the snapd snap available + s.state.Lock() + snapstate.Set(s.state, "core", nil) + snapstate.Set(s.state, "snapd", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{ + {RealName: "snapd", Revision: snap.R(1)}, + }, + Current: snap.R(1), + SnapType: "snapd", + }) + s.state.Unlock() + + s.testEnsureSkipsCheckingSnapdInfoFileWhenStateSet(c, "snapd") +} + +func (s *snapmgrTestSuite) TestEnsureSkipsCheckingCoreSnapInfoFileWhenStateSet(c *C) { + s.testEnsureSkipsCheckingSnapdInfoFileWhenStateSet(c, "core") +} + +func (s *snapmgrTestSuite) TestEnsureSkipsCheckingBothCoreAndSnapdSnapsInfoFileWhenStateSet(c *C) { + // special policy only on classic + r := release.MockOnClassic(true) + defer r() + + st := s.state + // also set snapd snapd as installed + st.Lock() + snapstate.Set(st, "snapd", &snapstate.SnapState{ + Active: true, + Sequence: []*snap.SideInfo{ + {RealName: "snapd", Revision: snap.R(1)}, + }, + Current: snap.R(1), + SnapType: "snapd", + }) + st.Unlock() + + infoFileFor := func(snapName string) string { + return filepath.Join(dirs.GlobalRootDir, "snap", snapName, "1", dirs.CoreLibExecDir, "info") + } + + // delete both snapd and core snap info files + for _, snapName := range []string{"core", "snapd"} { + err := os.Remove(infoFileFor(snapName)) + c.Assert(err, IsNil) + } + + // make sure Ensure makes a whole hearted attempt to read both files - snapd + // is tried first + ensureErr := s.snapmgr.Ensure() + c.Assert(ensureErr, ErrorMatches, fmt.Sprintf(`cannot open snapd info file "%s".*`, infoFileFor("snapd"))) + + st.Lock() + st.Set("snapd-snap-cve-2021-44731-vuln-removed", true) + st.Unlock() + + // still unhappy about core file missing + ensureErr = s.snapmgr.Ensure() + c.Assert(ensureErr, ErrorMatches, fmt.Sprintf(`cannot open snapd info file "%s".*`, infoFileFor("core"))) + + // but with core state flag set too, we are now happy + st.Lock() + st.Set("core-snap-cve-2021-44731-vuln-removed", true) + st.Unlock() + + ensureErr = s.snapmgr.Ensure() + c.Assert(ensureErr, IsNil) +} + +func (s *snapmgrTestSuite) testEnsureSkipsCheckingSnapdInfoFileWhenStateSet(c *C, snapName string) { + // special policy only on classic + r := release.MockOnClassic(true) + defer r() + + // delete the snap info file for this snap - they should always exist in + // real devices, but deleting them here makes it so we can see the failure + // trying to read the files easily + infoFile := filepath.Join(dirs.GlobalRootDir, "snap", snapName, "1", dirs.CoreLibExecDir, "info") + err := os.Remove(infoFile) + c.Assert(err, IsNil) + + // make sure it makes a whole hearted attempt to read it + ensureErr := s.snapmgr.Ensure() + c.Assert(ensureErr, ErrorMatches, "cannot open snapd info file.*") + + // now it should stop trying to check if state says so + st := s.state + st.Lock() + st.Set(snapName+"-snap-cve-2021-44731-vuln-removed", true) + st.Unlock() + + ensureErr = s.snapmgr.Ensure() + c.Assert(ensureErr, IsNil) +} + func (s *snapmgrTestSuite) TestEnsureRefreshesAtSeedPolicy(c *C) { // special policy only on classic r := release.MockOnClassic(true) @@ -4782,7 +5020,7 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapDoesNotRunWhenNotEnabled(c *C) snapstate.Set(s.state, "core", &snapstate.SnapState{ Active: true, - Sequence: []*snap.SideInfo{{RealName: "corecore", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "beta"}}, + Sequence: []*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "beta"}}, Current: snap.R(1), SnapType: "os", }) @@ -4801,7 +5039,7 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapStartsAutomaticallyWhenEnabled snapstate.Set(s.state, "core", &snapstate.SnapState{ Active: true, - Sequence: []*snap.SideInfo{{RealName: "corecore", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "beta"}}, + Sequence: []*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "beta"}}, Current: snap.R(1), SnapType: "os", }) @@ -4832,7 +5070,7 @@ func (s *snapmgrTestSuite) TestTransitionSnapdSnapWithCoreRunthrough(c *C) { snapstate.Set(s.state, "core", &snapstate.SnapState{ Active: true, - Sequence: []*snap.SideInfo{{RealName: "corecore", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "edge"}}, + Sequence: []*snap.SideInfo{{RealName: "core", SnapID: "core-snap-id", Revision: snap.R(1), Channel: "edge"}}, Current: snap.R(1), SnapType: "os", // TrackingChannel diff --git a/tests/nested/manual/snapd-removes-vulnerable-snap-confine-revs/task.yaml b/tests/nested/manual/snapd-removes-vulnerable-snap-confine-revs/task.yaml new file mode 100644 index 00000000..bc7fd05b --- /dev/null +++ b/tests/nested/manual/snapd-removes-vulnerable-snap-confine-revs/task.yaml @@ -0,0 +1,110 @@ +summary: Check that refreshing snapd to a fixed version removes vulnerable revs + +# just focal is fine for this test - we only need to check that things happen on +# classic +systems: [ubuntu-20.04-*] + +environment: + # which snap snapd comes from in this test + SNAPD_SOURCE_SNAP/snapd: snapd + SNAPD_SOURCE_SNAP/core: core + + # needed to get a custom image + NESTED_IMAGE_ID: vuln-$SNAPD_SOURCE_SNAP-auto-removed + + # where we mount the image + IMAGE_MOUNTPOINT: /mnt/cloudimg + + # we don't actually use snapd from the branch in the seed of the VM initially, + # but we have to define this in order to get a custom image, see + # nested_get_image_name in nested.sh for this logic + NESTED_BUILD_SNAPD_FROM_CURRENT: true + + # meh snap-state doesn't have a consistent naming for these snaps, so we can't + # just do "$SNAPD_SOURCE_SNAP-from-deb.snap" + REPACKED_SNAP_NAME/core: core-from-snapd-deb.snap + REPACKED_SNAP_NAME/snapd: snapd-from-deb.snap + + # TODO: use more up to date, but still vulnerable revisions of core and snapd + # for this test to reduce the delta in functionality we end up testing here + + # a specific vulnerable snapd version we can base our image on + VULN_SNAP_REV_URL/snapd: https://storage.googleapis.com/snapd-spread-tests/snaps/snapd_2.49.1_11402.snap + + # a specific vulnerable core version we can base our image on + VULN_SNAP_REV_URL/core: https://storage.googleapis.com/snapd-spread-tests/snaps/core_2.45_9289.snap + +prepare: | + #shellcheck source=tests/lib/preseed.sh + . "$TESTSLIB/preseed.sh" + + # create a VM and mount a cloud image + tests.nested build-image classic + mkdir -p "$IMAGE_MOUNTPOINT" + IMAGE_NAME=$(tests.nested get image-name classic) + mount_ubuntu_image "$(tests.nested get images-path)/$IMAGE_NAME" "$IMAGE_MOUNTPOINT" + + # repack the deb into the snap we want + "$TESTSTOOLS"/snaps-state repack_snapd_deb_into_snap "$SNAPD_SOURCE_SNAP" + + # add the known vulnerable version of snapd into the seed, dangerously + curl -s -o "$SNAPD_SOURCE_SNAP-vuln.snap" "$VULN_SNAP_REV_URL" + + # repack to ensure it is a dangerous revision + unsquashfs -d "$SNAPD_SOURCE_SNAP-vuln" "$SNAPD_SOURCE_SNAP-vuln.snap" + rm "$SNAPD_SOURCE_SNAP-vuln.snap" + snap pack "$SNAPD_SOURCE_SNAP-vuln" --filename="$SNAPD_SOURCE_SNAP.snap" + + # inject the vulnerable snap into the seed + inject_snap_into_seed "$IMAGE_MOUNTPOINT" "$SNAPD_SOURCE_SNAP" + + # undo any preseeding, the images may have been preseeded without our snaps + # so we want to undo that to ensure our snaps are on them + SNAPD_DEBUG=1 /usr/lib/snapd/snap-preseed --reset "$IMAGE_MOUNTPOINT" + + # unmount the image and start the VM + umount_ubuntu_image "$IMAGE_MOUNTPOINT" + tests.nested create-vm classic + +execute: | + # check the current snapd snap is vulnerable + tests.nested exec cat /snap/$SNAPD_SOURCE_SNAP/current/usr/lib/snapd/info | MATCH '^VERSION=2\.4.*' + VULN_REV=$(tests.nested exec "snap list $SNAPD_SOURCE_SNAP" | tail -n +2 | awk '{print $3}') + + # now install our snapd deb from the branch - this is so we know the patched + # snapd is always executing, regardless of which snapd/core snap re-exec + # nonsense is going on + SNAPD_DEB_ARR=( "$SPREAD_PATH"/../snapd_*.deb ) + SNAPD_DEB=${SNAPD_DEB_ARR[0]} + tests.nested copy "$SNAPD_DEB" + tests.nested exec "sudo dpkg -i $(basename "$SNAPD_DEB")" + + # now send the snap version of snapd under test to the VM + tests.nested copy "$REPACKED_SNAP_NAME" + tests.nested exec "sudo snap install $REPACKED_SNAP_NAME --dangerous" + + # there is a race between the snap install finishing and removing the + # vulnerable revision, so we have to wait a bit + VULN_SNAP_REMOVED=false + #shellcheck disable=SC2034 + for i in $(seq 1 60); do + if tests.nested exec "snap list $SNAPD_SOURCE_SNAP --all" | NOMATCH "$VULN_REV"; then + VULN_SNAP_REMOVED=true + break + fi + sleep 1 + done + + if [ "$VULN_SNAP_REMOVED" != "true" ]; then + echo "vulnerable snap was not automatically removed" + exit 1 + fi + + # check that the current revision is not vulnerable + tests.nested exec cat /snap/$SNAPD_SOURCE_SNAP/current/usr/lib/snapd/info | NOMATCH '^VERSION=2\.4.*' + + # and there are no other revisions + if [ "$(tests.nested exec "snap list $SNAPD_SOURCE_SNAP" | tail -n +2 | wc -l)" != "1" ]; then + echo "unexpected extra revision of $SNAPD_SOURCE_SNAP installed" + exit 1 + fi -- 2.30.2