Skip to content

Commit

Permalink
interfaces/greengrass-support: add additional "process" flavor for 1.…
Browse files Browse the repository at this point in the history
…11 update

This adds a new attribute to the greengrass-support interface, "flavor", which
indicates what mode of containerization the greengrassd daemon is meant to be
supporting with the plug. With no flavor attribute, or the "container" flavor,
then the old policy is available so as to not break old users of the snap, but
with a new "process" flavor, then a far less privileged version of the interface
is provided, which allows the greengrassd daemon to implement no
containerization and thus the lambdas that are run are not run with the
additional privilege afforded to the original implementation of the interface,
as that would allow lambdas to trivially escape the sandbox.

Signed-off-by: Ian Johnson <ian.johnson@canonical.com>
  • Loading branch information
anonymouse64 committed Nov 3, 2020
1 parent a627e96 commit 4dee788
Show file tree
Hide file tree
Showing 2 changed files with 184 additions and 41 deletions.
76 changes: 67 additions & 9 deletions interfaces/builtin/greengrass_support.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,12 @@
package builtin

import (
"fmt"

"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/seccomp"
"github.com/snapcore/snapd/interfaces/udev"
"github.com/snapcore/snapd/release"
)

Expand Down Expand Up @@ -51,7 +55,18 @@ const greengrassSupportConnectedPlugAppArmorCore = `
/system-data/var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/*/ggc-writable/{,**} rw,
`

const greengrassSupportConnectedPlugAppArmor = `
const greengrassSupportProcessModeConnectedPlugAppArmor = `
# Description: can manage greengrass 'things' and their sandboxes. This policy
# is meant currently only to enable Greengrass to run _only_ process-mode or
# "no container" lambdas.
# needed by older versions of cloneBinary.ensureSelfCloned() to avoid
# CVE-2019-5736
/ ix,
# newer versions of runC have this denial instead of "/ ix" above
/bin/runc rix,
`

const greengrassSupportFullContainerConnectedPlugAppArmor = `
# Description: can manage greengrass 'things' and their sandboxes. This
# policy is intentionally not restrictive and is here to help guard against
# programming errors and not for security confinement. The greengrassd
Expand Down Expand Up @@ -380,13 +395,58 @@ mknodat - - |S_IFCHR -
`

func (iface *greengrassSupportInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
if release.OnClassic {
spec.AddSnippet(greengrassSupportConnectedPlugAppArmor)
} else {
spec.AddSnippet(greengrassSupportConnectedPlugAppArmor + greengrassSupportConnectedPlugAppArmorCore)
// check the flavor
var flavor string
_ = plug.Attr("flavor", &flavor)
switch flavor {
case "", "container":
// default, legacy version of the interface
if release.OnClassic {
spec.AddSnippet(greengrassSupportFullContainerConnectedPlugAppArmor)
} else {
spec.AddSnippet(greengrassSupportFullContainerConnectedPlugAppArmor + greengrassSupportConnectedPlugAppArmorCore)
}
// greengrass needs to use ptrace for controlling it's containers
spec.SetUsesPtraceTrace()
case "process":
// this is the process-mode version, it does not use as much privilege
// as the default "container" flavor
spec.AddSnippet(greengrassSupportProcessModeConnectedPlugAppArmor)
default:
return fmt.Errorf("cannot add apparmor plug policy: unsupported flavor attribute value %q for greengrass-support interface", flavor)
}

return nil
}

func (iface *greengrassSupportInterface) SecCompConnectedPlug(spec *seccomp.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
// check the flavor
var flavor string
_ = plug.Attr("flavor", &flavor)
switch flavor {
case "", "container":
spec.AddSnippet(greengrassSupportConnectedPlugSeccomp)
case "process":
// process mode has no additional seccomp available to it
default:
return fmt.Errorf("cannot add seccomp plug policy: unsupported flavor attribute value %q for greengrass-support interface", flavor)
}

return nil
}

func (iface *greengrassSupportInterface) UDevConnectedPlug(spec *udev.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
var flavor string
_ = plug.Attr("flavor", &flavor)
switch flavor {
case "", "container":
// default containerization controls the device cgroup
spec.SetControlsDeviceCgroup()
case "process":
// process mode does not control the device cgroup
default:
return fmt.Errorf("cannot add udev plug policy: unsupported flavor attribute value %q for greengrass-support interface", flavor)
}
// greengrass needs to use ptrace
spec.SetUsesPtraceTrace()

return nil
}
Expand All @@ -404,7 +464,5 @@ func init() {
implicitOnClassic: true,
baseDeclarationSlots: greengrassSupportBaseDeclarationSlots,
baseDeclarationPlugs: greengrassSupportBaseDeclarationPlugs,
connectedPlugSecComp: greengrassSupportConnectedPlugSeccomp,
controlsDeviceCgroup: true,
}})
}
149 changes: 117 additions & 32 deletions interfaces/builtin/greengrass_support_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ type GreengrassSupportInterfaceSuite struct {
extraSlot *interfaces.ConnectedSlot
extraPlugInfo *snap.PlugInfo
extraPlug *interfaces.ConnectedPlug

// for the process flavor
processModePlugInfo *snap.PlugInfo
processModePlug *interfaces.ConnectedPlug

// for the container flavor
containerModePlugInfo *snap.PlugInfo
containerModePlug *interfaces.ConnectedPlug
}

const coreSlotYaml = `name: core
Expand All @@ -53,10 +61,26 @@ slots:
`
const ggMockPlugSnapInfoYaml = `name: other
version: 1.0
plugs:
greengrass-support-container-mode:
interface: greengrass-support
flavor: container
apps:
app2:
command: foo
plugs: [greengrass-support, network-control]
plugs: [greengrass-support-container-mode, greengrass-support, network-control]
`

const ggProcessModeMockPlugSnapInfoYaml = `name: other
version: 1.0
plugs:
greengrass-support-process-mode:
interface: greengrass-support
flavor: process
apps:
app2:
command: foo
plugs: [greengrass-support-process-mode, network-control]
`

var _ = Suite(&GreengrassSupportInterfaceSuite{
Expand All @@ -69,6 +93,10 @@ func (s *GreengrassSupportInterfaceSuite) SetUpTest(c *C) {
s.extraPlug, s.extraPlugInfo = MockConnectedPlug(c, ggMockPlugSnapInfoYaml, nil, "network-control")
s.extraSlot, s.extraSlotInfo = MockConnectedSlot(c, coreSlotYaml, nil, "network-control")

s.processModePlug, s.processModePlugInfo = MockConnectedPlug(c, ggProcessModeMockPlugSnapInfoYaml, nil, "greengrass-support-process-mode")

s.containerModePlug, s.containerModePlugInfo = MockConnectedPlug(c, ggMockPlugSnapInfoYaml, nil, "greengrass-support-container-mode")

}

func (s *GreengrassSupportInterfaceSuite) TestName(c *C) {
Expand All @@ -84,39 +112,86 @@ func (s *GreengrassSupportInterfaceSuite) TestSanitizePlug(c *C) {
}

func (s *GreengrassSupportInterfaceSuite) TestAppArmorSpec(c *C) {

for _, plug := range []*interfaces.ConnectedPlug{
s.plug,
s.containerModePlug,
} {
spec := &apparmor.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, plug, s.slot), IsNil)
c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})
c.Check(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "mount options=(rw, bind) /var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/** -> /var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/** ,\n")
c.Check(spec.UsesPtraceTrace(), Equals, true)
}
}

func (s *GreengrassSupportInterfaceSuite) TestProcessModeAppArmorSpec(c *C) {
spec := &apparmor.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
c.Assert(spec.AddConnectedPlug(s.iface, s.processModePlug, s.slot), IsNil)
c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})
c.Check(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "mount options=(rw, bind) /var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/** -> /var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/** ,\n")
c.Check(spec.UsesPtraceTrace(), Equals, true)
c.Check(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "/ ix,\n")
c.Check(spec.SnippetForTag("snap.other.app2"), Not(testutil.Contains), "mount options=(rw, bind) /var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/** -> /var/snap/{@{SNAP_NAME},@{SNAP_INSTANCE_NAME}}/** ,\n")
c.Check(spec.UsesPtraceTrace(), Equals, false)
}

func (s *GreengrassSupportInterfaceSuite) TestSecCompSpec(c *C) {
for _, plug := range []*interfaces.ConnectedPlug{
s.plug,
s.containerModePlug,
} {
spec := &seccomp.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, plug, s.slot), IsNil)
c.Check(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "# for overlayfs and various bind mounts\nmount\numount2\npivot_root\n")
}
}

func (s *GreengrassSupportInterfaceSuite) TestProcessModeSecCompSpec(c *C) {
spec := &seccomp.Specification{}
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
c.Check(spec.SnippetForTag("snap.other.app2"), testutil.Contains, "# for overlayfs and various bind mounts\nmount\numount2\npivot_root\n")
c.Assert(spec.AddConnectedPlug(s.iface, s.processModePlug, s.slot), IsNil)
c.Check(spec.SnippetForTag("snap.other.app2"), Not(testutil.Contains), "# for overlayfs and various bind mounts\nmount\numount2\npivot_root\n")
}

func (s *GreengrassSupportInterfaceSuite) TestUdevTaggingDisablingRemoveLast(c *C) {
// make a spec with network-control that has udev tagging
spec := &udev.Specification{}
c.Assert(spec.AddConnectedPlug(builtin.MustInterface("network-control"), s.extraPlug, s.extraSlot), IsNil)
c.Assert(spec.Snippets(), HasLen, 3)

// connect the greengrass-support interface and ensure the spec is now nil
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
c.Check(spec.Snippets(), HasLen, 0)
for _, plug := range []*interfaces.ConnectedPlug{
s.plug,
s.containerModePlug,
} {
// make a spec with network-control that has udev tagging
spec := &udev.Specification{}
c.Assert(spec.AddConnectedPlug(builtin.MustInterface("network-control"), s.extraPlug, s.extraSlot), IsNil)
c.Assert(spec.Snippets(), HasLen, 3)

// connect the greengrass-support interface and ensure the spec is now nil
c.Assert(spec.AddConnectedPlug(s.iface, plug, s.slot), IsNil)
c.Check(spec.Snippets(), HasLen, 0)
}
}

func (s *GreengrassSupportInterfaceSuite) TestUdevTaggingDisablingRemoveFirst(c *C) {
func (s *GreengrassSupportInterfaceSuite) TestProcessModeUdevTaggingWorks(c *C) {
spec := &udev.Specification{}
// connect the greengrass-support interface and ensure the spec is nil
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
c.Assert(spec.AddConnectedPlug(s.iface, s.processModePlug, s.slot), IsNil)
c.Check(spec.Snippets(), HasLen, 0)

// add network-control and ensure the spec is still nil
// add network-control and now the spec is not nil
c.Assert(spec.AddConnectedPlug(builtin.MustInterface("network-control"), s.extraPlug, s.extraSlot), IsNil)
c.Assert(spec.Snippets(), HasLen, 0)
c.Assert(spec.Snippets(), Not(HasLen), 0)
}

func (s *GreengrassSupportInterfaceSuite) TestUdevTaggingDisablingRemoveFirst(c *C) {
for _, plug := range []*interfaces.ConnectedPlug{
s.plug,
s.containerModePlug,
} {
spec := &udev.Specification{}
// connect the greengrass-support interface and ensure the spec is nil
c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil)
c.Check(spec.Snippets(), HasLen, 0)

// add network-control and ensure the spec is still nil
c.Assert(spec.AddConnectedPlug(builtin.MustInterface("network-control"), plug, s.extraSlot), IsNil)
c.Assert(spec.Snippets(), HasLen, 0)
}
}

func (s *GreengrassSupportInterfaceSuite) TestInterfaces(c *C) {
Expand All @@ -127,24 +202,34 @@ func (s *GreengrassSupportInterfaceSuite) TestPermanentSlotAppArmorSessionNative
restore := release.MockOnClassic(false)
defer restore()

apparmorSpec := &apparmor.Specification{}
err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
c.Assert(err, IsNil)
c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})

// verify core rule present
c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "# /system-data/var/snap/greengrass/x1/ggc-writable/packages/1.7.0/var/worker/overlays/$UUID/upper/\n")
for _, plug := range []*interfaces.ConnectedPlug{
s.plug,
s.containerModePlug,
} {
apparmorSpec := &apparmor.Specification{}
err := apparmorSpec.AddConnectedPlug(s.iface, plug, s.slot)
c.Assert(err, IsNil)
c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})

// verify core rule present
c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), testutil.Contains, "# /system-data/var/snap/greengrass/x1/ggc-writable/packages/1.7.0/var/worker/overlays/$UUID/upper/\n")
}
}

func (s *GreengrassSupportInterfaceSuite) TestPermanentSlotAppArmorSessionClassic(c *C) {
restore := release.MockOnClassic(true)
defer restore()

apparmorSpec := &apparmor.Specification{}
err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot)
c.Assert(err, IsNil)
c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})

// verify core rule not present
c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), Not(testutil.Contains), "# /system-data/var/snap/greengrass/x1/ggc-writable/packages/1.7.0/var/worker/overlays/$UUID/upper/\n")
for _, plug := range []*interfaces.ConnectedPlug{
s.plug,
s.containerModePlug,
} {
apparmorSpec := &apparmor.Specification{}
err := apparmorSpec.AddConnectedPlug(s.iface, plug, s.slot)
c.Assert(err, IsNil)
c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app2"})

// verify core rule not present
c.Check(apparmorSpec.SnippetForTag("snap.other.app2"), Not(testutil.Contains), "# /system-data/var/snap/greengrass/x1/ggc-writable/packages/1.7.0/var/worker/overlays/$UUID/upper/\n")
}
}

0 comments on commit 4dee788

Please sign in to comment.