Skip to content

Commit

Permalink
o/snapstate: make kill-snap-apps best effort (canonical#14580)
Browse files Browse the repository at this point in the history
This avoids a maliciously crafted snap from some how causing the
termination logic to fail (even though we should be able to handle
most scenarios even fork-bombs) and hence causing the snap to never
be removed.

Signed-off-by: Zeyad Gouda <[email protected]>
  • Loading branch information
ZeyadYasser authored Oct 8, 2024
1 parent e49559f commit 894b237
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 4 deletions.
8 changes: 7 additions & 1 deletion overlord/snapstate/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3410,7 +3410,13 @@ func (m *SnapManager) doKillSnapApps(t *state.Task, _ *tomb.Tomb) (err error) {
defer st.Lock()

if err := m.backend.KillSnapApps(snapName, reason, perfTimings); err != nil {
return err
// Snap processes termination is best-effort and task should continue
// without returning an error. This is to avoid a maliciously crafted snap
// from causing remove changes to always fail causing the snap to never be
// removed.
st.Lock()
st.Warnf("cannot terminate running app processes for %q: %v", snapName, err)
st.Unlock()
}

currentInfo, err := snapst.CurrentInfo()
Expand Down
54 changes: 51 additions & 3 deletions overlord/snapstate/handlers_link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2718,6 +2718,50 @@ func (s *linkSnapSuite) TestDoKillSnapAppsUnlocksOnError(c *C) {
Active: true,
})

snapstate.MockSnapReadInfo(func(name string, si *snap.SideInfo) (*snap.Info, error) {
return nil, fmt.Errorf("boom!")
})

task := s.state.NewTask("kill-snap-apps", "")
task.Set("kill-reason", snap.KillReasonRemove)
task.Set("snap-setup", &snapstate.SnapSetup{SideInfo: si})
chg := s.state.NewChange("test", "")
chg.AddTask(task)

s.state.Unlock()

s.se.Ensure()
s.se.Wait()

s.state.Lock()

c.Assert(task.Status(), Equals, state.ErrorStatus)

hint, _, err := runinhibit.IsLocked("some-snap")
c.Assert(err, IsNil)
// On error hint inhibition file is unlocked
c.Check(hint, Equals, runinhibit.HintNotInhibited)
// And snap lock is also unlocked
testLock, err := snaplock.OpenLock("some-snap")
c.Assert(err, IsNil)
c.Check(testLock.TryLock(), IsNil)
testLock.Close()
}

func (s *linkSnapSuite) TestDoKillSnapAppsTerminateBestEffort(c *C) {
s.state.Lock()
defer s.state.Unlock()

si := &snap.SideInfo{
RealName: "some-snap",
Revision: snap.R(1),
}
snapstate.Set(s.state, "some-snap", &snapstate.SnapState{
Sequence: snapstatetest.NewSequenceFromSnapSideInfos([]*snap.SideInfo{si}),
Current: si.Revision,
Active: true,
})

s.fakeBackend.maybeInjectErr = func(op *fakeOp) error {
if op.op == "kill-snap-apps:remove" {
return fmt.Errorf("boom!")
Expand All @@ -2738,17 +2782,21 @@ func (s *linkSnapSuite) TestDoKillSnapAppsUnlocksOnError(c *C) {

s.state.Lock()

c.Assert(task.Status(), Equals, state.ErrorStatus)
c.Assert(task.Status(), Equals, state.DoneStatus)

hint, _, err := runinhibit.IsLocked("some-snap")
c.Assert(err, IsNil)
// On error hint inhibition file is unlocked
c.Check(hint, Equals, runinhibit.HintNotInhibited)
// Error is ignored, inhibition lock is held
c.Check(hint, Equals, runinhibit.HintInhibitedForRemove)
// And snap lock is also unlocked
testLock, err := snaplock.OpenLock("some-snap")
c.Assert(err, IsNil)
c.Check(testLock.TryLock(), IsNil)
testLock.Close()
// But a warning is emitted
warnings := s.state.AllWarnings()
c.Assert(warnings, HasLen, 1)
c.Assert(warnings[0].String(), Equals, `cannot terminate running app processes for "some-snap": boom!`)
}

func (s *linkSnapSuite) testDoUndoKillSnapApps(c *C, svc bool) {
Expand Down

0 comments on commit 894b237

Please sign in to comment.