Permalink
Fetching contributors…
Cannot retrieve contributors at this time
438 lines (366 sloc) 12.2 KB
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2016 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package builtin
import (
"bytes"
"fmt"
"regexp"
"strings"
"github.com/snapcore/snapd/interfaces"
"github.com/snapcore/snapd/interfaces/apparmor"
"github.com/snapcore/snapd/interfaces/dbus"
"github.com/snapcore/snapd/interfaces/seccomp"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/snap"
)
const dbusSummary = `allows owning a specifc name on DBus`
const dbusBaseDeclarationSlots = `
dbus:
allow-installation:
slot-snap-type:
- app
deny-connection:
slot-attributes:
name: .+
deny-auto-connection: true
`
const dbusPermanentSlotAppArmor = `
# Description: Allow owning a name on DBus public bus
#include <abstractions/###DBUS_ABSTRACTION###>
# register on DBus
dbus (send)
bus=###DBUS_BUS###
path=/org/freedesktop/DBus
interface=org.freedesktop.DBus
member="{Request,Release}Name"
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (send)
bus=###DBUS_BUS###
path=/org/freedesktop/DBus
interface=org.freedesktop.DBus
member="GetConnectionUnix{ProcessID,User}"
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (send)
bus=###DBUS_BUS###
path=/org/freedesktop/DBus
interface=org.freedesktop.DBus
member="GetConnectionCredentials"
peer=(name=org.freedesktop.DBus, label=unconfined),
# bind to a well-known DBus name: ###DBUS_NAME###
dbus (bind)
bus=###DBUS_BUS###
name=###DBUS_NAME###,
# For KDE applications, also support alternation since they use org.kde.foo-PID
# as their 'well-known' name. snapd does not allow declaring a 'well-known'
# name that ends with '-[0-9]+', so this is ok.
dbus (bind)
bus=###DBUS_BUS###
name=###DBUS_NAME###-[1-9]{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]},
# Allow us to talk to dbus-daemon
dbus (receive)
bus=###DBUS_BUS###
path=###DBUS_PATH###
peer=(name=org.freedesktop.DBus, label=unconfined),
dbus (send)
bus=###DBUS_BUS###
path=###DBUS_PATH###
interface=org.freedesktop.DBus.Properties
peer=(name=org.freedesktop.DBus, label=unconfined),
# Allow us to introspect org.freedesktop.DBus (needed by pydbus)
dbus (send)
bus=###DBUS_BUS###
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(name=org.freedesktop.DBus, label=unconfined),
`
const dbusPermanentSlotAppArmorClassic = `
# allow unconfined clients to introspect us on classic
dbus (receive)
bus=###DBUS_BUS###
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(label=unconfined),
# allow us to respond to unconfined clients via ###DBUS_INTERFACE###
# on classic (send should be handled via another snappy interface).
dbus (receive)
bus=###DBUS_BUS###
interface=###DBUS_INTERFACE###
peer=(label=unconfined),
# allow us to respond to unconfined clients via ###DBUS_PATH### (eg,
# org.freedesktop.*, org.gtk.Application, etc) on classic (send should be
# handled via another snappy interface).
dbus (receive)
bus=###DBUS_BUS###
path=###DBUS_PATH###
peer=(label=unconfined),
`
const dbusPermanentSlotDBus = `
<policy user="root">
<allow own="###DBUS_NAME###"/>
<allow send_destination="###DBUS_NAME###"/>
</policy>
<policy context="default">
<allow send_destination="###DBUS_NAME###"/>
</policy>
`
const dbusPermanentSlotSecComp = `
# Description: Allow owning a name and listening on DBus public bus
listen
accept
accept4
`
const dbusConnectedSlotAppArmor = `
# allow snaps to introspect us. This allows clients to introspect all
# DBus interfaces of this service (but not use them).
dbus (receive)
bus=###DBUS_BUS###
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(label=###PLUG_SECURITY_TAGS###),
# allow connected snaps to all paths via ###DBUS_INTERFACE###
dbus (receive, send)
bus=###DBUS_BUS###
interface=###DBUS_INTERFACE###
peer=(label=###PLUG_SECURITY_TAGS###),
# allow connected snaps to all interfaces via ###DBUS_PATH### (eg,
# org.freedesktop.*, org.gtk.Application, etc) to allow full integration with
# connected snaps.
dbus (receive, send)
bus=###DBUS_BUS###
path=###DBUS_PATH###
peer=(label=###PLUG_SECURITY_TAGS###),
`
const dbusConnectedPlugAppArmor = `
#include <abstractions/###DBUS_ABSTRACTION###>
# allow snaps to introspect the slot servive. This allows us to introspect
# all DBus interfaces of the service (but not use them).
dbus (send)
bus=###DBUS_BUS###
interface=org.freedesktop.DBus.Introspectable
member=Introspect
peer=(label=###SLOT_SECURITY_TAGS###),
# allow connected snaps to ###DBUS_NAME###
dbus (receive, send)
bus=###DBUS_BUS###
peer=(name=###DBUS_NAME###, label=###SLOT_SECURITY_TAGS###),
# For KDE applications, also support alternation since they use org.kde.foo-PID
# as their 'well-known' name. snapd does not allow ###DBUS_NAME### to end with
# '-[0-9]+', so this is ok.
dbus (receive, send)
bus=###DBUS_BUS###
peer=(name="###DBUS_NAME###-[1-9]{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}", label=###SLOT_SECURITY_TAGS###),
# allow connected snaps to all paths via ###DBUS_INTERFACE### to allow full
# integration with connected snaps.
dbus (receive, send)
bus=###DBUS_BUS###
interface=###DBUS_INTERFACE###
peer=(label=###SLOT_SECURITY_TAGS###),
# allow connected snaps to all interfaces via ###DBUS_PATH### (eg,
# org.freedesktop.*, org.gtk.Application, etc) to allow full integration with
# connected snaps.
dbus (receive, send)
bus=###DBUS_BUS###
path=###DBUS_PATH###
peer=(label=###SLOT_SECURITY_TAGS###),
`
type dbusInterface struct{}
func (iface *dbusInterface) Name() string {
return "dbus"
}
func (iface *dbusInterface) StaticInfo() interfaces.StaticInfo {
return interfaces.StaticInfo{
Summary: dbusSummary,
BaseDeclarationSlots: dbusBaseDeclarationSlots,
}
}
// Obtain yaml-specified bus well-known name
func (iface *dbusInterface) getAttribs(attribs interfaces.Attrer) (string, string, error) {
// bus attribute
var bus string
if err := attribs.Attr("bus", &bus); err != nil {
return "", "", fmt.Errorf("cannot find attribute 'bus'")
}
if bus != "session" && bus != "system" {
return "", "", fmt.Errorf("bus '%s' must be one of 'session' or 'system'", bus)
}
// name attribute
var name string
if err := attribs.Attr("name", &name); err != nil {
return "", "", fmt.Errorf("cannot find attribute 'name'")
}
err := interfaces.ValidateDBusBusName(name)
if err != nil {
return "", "", err
}
// snapd has AppArmor rules (see above) allowing binds to busName-PID
// so to avoid overlap with different snaps (eg, busName running as PID
// 123 and busName-123), don't allow busName to end with -PID. If that
// rule is removed, this limitation can be lifted.
invalidSnappyBusName := regexp.MustCompile("-[0-9]+$")
if invalidSnappyBusName.MatchString(name) {
return "", "", fmt.Errorf("DBus bus name must not end with -NUMBER")
}
return bus, name, nil
}
// Determine AppArmor dbus abstraction to use based on bus
func getAppArmorAbstraction(bus string) (string, error) {
var abstraction string
if bus == "system" {
abstraction = "dbus-strict"
} else if bus == "session" {
abstraction = "dbus-session-strict"
} else {
return "", fmt.Errorf("unknown abstraction for specified bus '%q'", bus)
}
return abstraction, nil
}
// Calculate individual snippet policy based on bus and name
func getAppArmorSnippet(policy string, bus string, name string) string {
old := "###DBUS_BUS###"
new := bus
snippet := strings.Replace(policy, old, new, -1)
old = "###DBUS_NAME###"
new = name
snippet = strings.Replace(snippet, old, new, -1)
// convert name to AppArmor dbus path (eg 'org.foo' to '/org/foo{,/**}')
var pathBuf bytes.Buffer
pathBuf.WriteString(`"/`)
pathBuf.WriteString(strings.Replace(name, ".", "/", -1))
pathBuf.WriteString(`{,/**}"`)
old = "###DBUS_PATH###"
new = pathBuf.String()
snippet = strings.Replace(snippet, old, new, -1)
// convert name to AppArmor dbus interface (eg, 'org.foo' to 'org.foo{,.*}')
var ifaceBuf bytes.Buffer
ifaceBuf.WriteString(`"`)
ifaceBuf.WriteString(name)
ifaceBuf.WriteString(`{,.*}"`)
old = "###DBUS_INTERFACE###"
new = ifaceBuf.String()
snippet = strings.Replace(snippet, old, new, -1)
return snippet
}
func (iface *dbusInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
bus, name, err := iface.getAttribs(plug)
if err != nil {
return err
}
busSlot, nameSlot, err := iface.getAttribs(slot)
if err != nil {
return err
}
// ensure that we only connect to slot with matching attributes
if bus != busSlot || name != nameSlot {
return nil
}
// well-known DBus name-specific connected plug policy
snippet := getAppArmorSnippet(dbusConnectedPlugAppArmor, bus, name)
// abstraction policy
abstraction, err := getAppArmorAbstraction(bus)
if err != nil {
return err
}
old := "###DBUS_ABSTRACTION###"
new := abstraction
snippet = strings.Replace(snippet, old, new, -1)
old = "###SLOT_SECURITY_TAGS###"
new = slotAppLabelExpr(slot)
snippet = strings.Replace(snippet, old, new, -1)
spec.AddSnippet(snippet)
return nil
}
func (iface *dbusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error {
bus, name, err := iface.getAttribs(slot)
if err != nil {
return err
}
// only system services need bus policy
if bus != "system" {
return nil
}
old := "###DBUS_NAME###"
new := name
spec.AddSnippet(strings.Replace(dbusPermanentSlotDBus, old, new, -1))
return nil
}
func (iface *dbusInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error {
bus, name, err := iface.getAttribs(slot)
if err != nil {
return err
}
// well-known DBus name-specific permanent slot policy
snippet := getAppArmorSnippet(dbusPermanentSlotAppArmor, bus, name)
// abstraction policy
abstraction, err := getAppArmorAbstraction(bus)
if err != nil {
return err
}
old := "###DBUS_ABSTRACTION###"
new := abstraction
snippet = strings.Replace(snippet, old, new, -1)
spec.AddSnippet(snippet)
if release.OnClassic {
// classic-only policy
spec.AddSnippet(getAppArmorSnippet(dbusPermanentSlotAppArmorClassic, bus, name))
}
return nil
}
func (iface *dbusInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error {
spec.AddSnippet(dbusPermanentSlotSecComp)
return nil
}
func (iface *dbusInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
bus, name, err := iface.getAttribs(slot)
if err != nil {
return err
}
busPlug, namePlug, err := iface.getAttribs(plug)
if err != nil {
return err
}
// ensure that we only connect to slot with matching attributes. This
// makes sure that the security policy is correct, but does not ensure
// that 'snap interfaces' is correct.
// TODO: we can fix the 'snap interfaces' issue when interface/policy
// checkers when they are available
if bus != busPlug || name != namePlug {
return nil
}
// well-known DBus name-specific connected slot policy
snippet := getAppArmorSnippet(dbusConnectedSlotAppArmor, bus, name)
old := "###PLUG_SECURITY_TAGS###"
new := plugAppLabelExpr(plug)
snippet = strings.Replace(snippet, old, new, -1)
spec.AddSnippet(snippet)
return nil
}
func (iface *dbusInterface) BeforePreparePlug(plug *snap.PlugInfo) error {
_, _, err := iface.getAttribs(plug)
return err
}
func (iface *dbusInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
_, _, err := iface.getAttribs(slot)
return err
}
func (iface *dbusInterface) AutoConnect(*interfaces.Plug, *interfaces.Slot) bool {
// allow what declarations allowed
return true
}
func init() {
registerIface(&dbusInterface{})
}