github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/interfaces/builtin/dbus.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 builtin
    21  
    22  import (
    23  	"bytes"
    24  	"fmt"
    25  	"regexp"
    26  	"strings"
    27  
    28  	"github.com/snapcore/snapd/interfaces"
    29  	"github.com/snapcore/snapd/interfaces/apparmor"
    30  	"github.com/snapcore/snapd/interfaces/dbus"
    31  	"github.com/snapcore/snapd/interfaces/seccomp"
    32  	"github.com/snapcore/snapd/release"
    33  	"github.com/snapcore/snapd/snap"
    34  )
    35  
    36  const dbusSummary = `allows owning a specifc name on DBus`
    37  
    38  const dbusBaseDeclarationSlots = `
    39    dbus:
    40      allow-installation:
    41        slot-snap-type:
    42          - app
    43      deny-connection:
    44        slot-attributes:
    45          name: .+
    46      deny-auto-connection: true
    47  `
    48  
    49  const dbusPermanentSlotAppArmor = `
    50  # Description: Allow owning a name on DBus public bus
    51  
    52  #include <abstractions/###DBUS_ABSTRACTION###>
    53  
    54  # register on DBus
    55  dbus (send)
    56      bus=###DBUS_BUS###
    57      path=/org/freedesktop/DBus
    58      interface=org.freedesktop.DBus
    59      member="{Request,Release}Name"
    60      peer=(name=org.freedesktop.DBus, label=unconfined),
    61  
    62  dbus (send)
    63      bus=###DBUS_BUS###
    64      path=/org/freedesktop/DBus
    65      interface=org.freedesktop.DBus
    66      member="GetConnectionUnix{ProcessID,User}"
    67      peer=(name=org.freedesktop.DBus, label=unconfined),
    68  
    69  dbus (send)
    70      bus=###DBUS_BUS###
    71      path=/org/freedesktop/DBus
    72      interface=org.freedesktop.DBus
    73      member="GetConnectionCredentials"
    74      peer=(name=org.freedesktop.DBus, label=unconfined),
    75  
    76  # bind to a well-known DBus name: ###DBUS_NAME###
    77  dbus (bind)
    78      bus=###DBUS_BUS###
    79      name=###DBUS_NAME###,
    80  
    81  # For KDE applications and some other cases, also support alternation for:
    82  # - using org.kde.foo-PID as the 'well-known' name
    83  # - using org.foo.cmd_<num>_<num> as the 'well-known' name
    84  # Note, snapd does not allow declaring a 'well-known' name that ends with
    85  # '-[0-9]+' or that contains '_'. Parallel installs of DBus services aren't
    86  # supported at this time, but if they were, this could allow a parallel
    87  # install'swell-known name to overlap with the normal install.
    88  dbus (bind)
    89      bus=###DBUS_BUS###
    90      name=###DBUS_NAME###{_,-}[1-9]{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9]}{,_[1-9]{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9_]}{,[0-9]}},
    91  
    92  # Allow us to talk to dbus-daemon
    93  dbus (receive)
    94      bus=###DBUS_BUS###
    95      path=###DBUS_PATH###
    96      peer=(name=org.freedesktop.DBus, label=unconfined),
    97  dbus (send)
    98      bus=###DBUS_BUS###
    99      path=###DBUS_PATH###
   100      interface=org.freedesktop.DBus.Properties
   101      peer=(name=org.freedesktop.DBus, label=unconfined),
   102  
   103  # Allow us to introspect org.freedesktop.DBus (needed by pydbus)
   104  dbus (send)
   105      bus=###DBUS_BUS###
   106      interface=org.freedesktop.DBus.Introspectable
   107      member=Introspect
   108      peer=(name=org.freedesktop.DBus, label=unconfined),
   109  `
   110  
   111  const dbusPermanentSlotAppArmorClassic = `
   112  # allow unconfined clients to introspect us on classic
   113  dbus (receive)
   114      bus=###DBUS_BUS###
   115      interface=org.freedesktop.DBus.Introspectable
   116      member=Introspect
   117      peer=(label=unconfined),
   118  
   119  # allow us to respond to unconfined clients via ###DBUS_INTERFACE###
   120  # on classic (send should be handled via another snappy interface).
   121  dbus (receive)
   122      bus=###DBUS_BUS###
   123      interface=###DBUS_INTERFACE###
   124      peer=(label=unconfined),
   125  
   126  # allow us to respond to unconfined clients via ###DBUS_PATH### (eg,
   127  # org.freedesktop.*, org.gtk.Application, etc) on classic (send should be
   128  # handled via another snappy interface).
   129  dbus (receive)
   130      bus=###DBUS_BUS###
   131      path=###DBUS_PATH###
   132      peer=(label=unconfined),
   133  `
   134  
   135  const dbusPermanentSlotDBus = `
   136  <policy user="root">
   137      <allow own="###DBUS_NAME###"/>
   138      <allow send_destination="###DBUS_NAME###"/>
   139  </policy>
   140  <policy context="default">
   141      <allow send_destination="###DBUS_NAME###"/>
   142  </policy>
   143  `
   144  
   145  const dbusPermanentSlotSecComp = `
   146  # Description: Allow owning a name and listening on DBus public bus
   147  listen
   148  accept
   149  accept4
   150  `
   151  
   152  const dbusConnectedSlotAppArmor = `
   153  # allow snaps to introspect us. This allows clients to introspect all
   154  # DBus interfaces of this service (but not use them).
   155  dbus (receive)
   156      bus=###DBUS_BUS###
   157      interface=org.freedesktop.DBus.Introspectable
   158      member=Introspect
   159      peer=(label=###PLUG_SECURITY_TAGS###),
   160  
   161  # allow connected snaps to all paths via ###DBUS_INTERFACE###
   162  dbus (receive, send)
   163      bus=###DBUS_BUS###
   164      interface=###DBUS_INTERFACE###
   165      peer=(label=###PLUG_SECURITY_TAGS###),
   166  
   167  # allow connected snaps to all interfaces via ###DBUS_PATH### (eg,
   168  # org.freedesktop.*, org.gtk.Application, etc) to allow full integration with
   169  # connected snaps.
   170  dbus (receive, send)
   171      bus=###DBUS_BUS###
   172      path=###DBUS_PATH###
   173      peer=(label=###PLUG_SECURITY_TAGS###),
   174  `
   175  
   176  const dbusConnectedPlugAppArmor = `
   177  #include <abstractions/###DBUS_ABSTRACTION###>
   178  
   179  # allow snaps to introspect the slot servive. This allows us to introspect
   180  # all DBus interfaces of the service (but not use them).
   181  dbus (send)
   182      bus=###DBUS_BUS###
   183      interface=org.freedesktop.DBus.Introspectable
   184      member=Introspect
   185      peer=(label=###SLOT_SECURITY_TAGS###),
   186  
   187  # allow connected snaps to ###DBUS_NAME###
   188  dbus (receive, send)
   189      bus=###DBUS_BUS###
   190      peer=(name=###DBUS_NAME###, label=###SLOT_SECURITY_TAGS###),
   191  # For KDE applications, also support alternation since they use org.kde.foo-PID
   192  # as their 'well-known' name. snapd does not allow ###DBUS_NAME### to end with
   193  # '-[0-9]+', so this is ok.
   194  dbus (receive, send)
   195      bus=###DBUS_BUS###
   196      peer=(name="###DBUS_NAME###-[1-9]{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}{,[0-9]}", label=###SLOT_SECURITY_TAGS###),
   197  
   198  # allow connected snaps to all paths via ###DBUS_INTERFACE### to allow full
   199  # integration with connected snaps.
   200  dbus (receive, send)
   201      bus=###DBUS_BUS###
   202      interface=###DBUS_INTERFACE###
   203      peer=(label=###SLOT_SECURITY_TAGS###),
   204  
   205  # allow connected snaps to all interfaces via ###DBUS_PATH### (eg,
   206  # org.freedesktop.*, org.gtk.Application, etc) to allow full integration with
   207  # connected snaps.
   208  dbus (receive, send)
   209      bus=###DBUS_BUS###
   210      path=###DBUS_PATH###
   211      peer=(label=###SLOT_SECURITY_TAGS###),
   212  `
   213  
   214  type dbusInterface struct{}
   215  
   216  func (iface *dbusInterface) Name() string {
   217  	return "dbus"
   218  }
   219  
   220  func (iface *dbusInterface) StaticInfo() interfaces.StaticInfo {
   221  	return interfaces.StaticInfo{
   222  		Summary:              dbusSummary,
   223  		BaseDeclarationSlots: dbusBaseDeclarationSlots,
   224  	}
   225  }
   226  
   227  // snapd has AppArmor rules (see above) allowing binds to busName-PID
   228  // so to avoid overlap with different snaps (eg, busName running as PID
   229  // 123 and busName-123), don't allow busName to end with -PID. If that
   230  // rule is removed, this limitation can be lifted.
   231  var isInvalidSnappyBusName = regexp.MustCompile("-[0-9]+$").MatchString
   232  
   233  // Obtain yaml-specified bus well-known name
   234  func (iface *dbusInterface) getAttribs(attribs interfaces.Attrer) (string, string, error) {
   235  	// bus attribute
   236  	var bus string
   237  	if err := attribs.Attr("bus", &bus); err != nil {
   238  		return "", "", fmt.Errorf("cannot find attribute 'bus'")
   239  	}
   240  
   241  	if bus != "session" && bus != "system" {
   242  		return "", "", fmt.Errorf("bus '%s' must be one of 'session' or 'system'", bus)
   243  	}
   244  
   245  	// name attribute
   246  	var name string
   247  	if err := attribs.Attr("name", &name); err != nil {
   248  		return "", "", fmt.Errorf("cannot find attribute 'name'")
   249  	}
   250  
   251  	err := interfaces.ValidateDBusBusName(name)
   252  	if err != nil {
   253  		return "", "", err
   254  	}
   255  
   256  	if isInvalidSnappyBusName(name) {
   257  		return "", "", fmt.Errorf("DBus bus name must not end with -NUMBER")
   258  	}
   259  
   260  	return bus, name, nil
   261  }
   262  
   263  // Determine AppArmor dbus abstraction to use based on bus
   264  func getAppArmorAbstraction(bus string) (string, error) {
   265  	var abstraction string
   266  	if bus == "system" {
   267  		abstraction = "dbus-strict"
   268  	} else if bus == "session" {
   269  		abstraction = "dbus-session-strict"
   270  	} else {
   271  		return "", fmt.Errorf("unknown abstraction for specified bus '%q'", bus)
   272  	}
   273  	return abstraction, nil
   274  }
   275  
   276  // Calculate individual snippet policy based on bus and name
   277  func getAppArmorSnippet(policy string, bus string, name string) string {
   278  	old := "###DBUS_BUS###"
   279  	new := bus
   280  	snippet := strings.Replace(policy, old, new, -1)
   281  
   282  	old = "###DBUS_NAME###"
   283  	new = name
   284  	snippet = strings.Replace(snippet, old, new, -1)
   285  
   286  	// convert name to AppArmor dbus path (eg 'org.foo' to '/org/foo{,/**}')
   287  	var pathBuf bytes.Buffer
   288  	pathBuf.WriteString(`"/`)
   289  	pathBuf.WriteString(strings.Replace(name, ".", "/", -1))
   290  	pathBuf.WriteString(`{,/**}"`)
   291  
   292  	old = "###DBUS_PATH###"
   293  	new = pathBuf.String()
   294  	snippet = strings.Replace(snippet, old, new, -1)
   295  
   296  	// convert name to AppArmor dbus interface (eg, 'org.foo' to 'org.foo{,.*}')
   297  	var ifaceBuf bytes.Buffer
   298  	ifaceBuf.WriteString(`"`)
   299  	ifaceBuf.WriteString(name)
   300  	ifaceBuf.WriteString(`{,.*}"`)
   301  
   302  	old = "###DBUS_INTERFACE###"
   303  	new = ifaceBuf.String()
   304  	snippet = strings.Replace(snippet, old, new, -1)
   305  
   306  	return snippet
   307  }
   308  
   309  func (iface *dbusInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   310  	bus, name, err := iface.getAttribs(plug)
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	busSlot, nameSlot, err := iface.getAttribs(slot)
   316  	if err != nil {
   317  		return err
   318  	}
   319  
   320  	// ensure that we only connect to slot with matching attributes
   321  	if bus != busSlot || name != nameSlot {
   322  		return nil
   323  	}
   324  
   325  	// well-known DBus name-specific connected plug policy
   326  	snippet := getAppArmorSnippet(dbusConnectedPlugAppArmor, bus, name)
   327  
   328  	// abstraction policy
   329  	abstraction, err := getAppArmorAbstraction(bus)
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	old := "###DBUS_ABSTRACTION###"
   335  	new := abstraction
   336  	snippet = strings.Replace(snippet, old, new, -1)
   337  
   338  	old = "###SLOT_SECURITY_TAGS###"
   339  	new = slotAppLabelExpr(slot)
   340  	snippet = strings.Replace(snippet, old, new, -1)
   341  
   342  	spec.AddSnippet(snippet)
   343  	return nil
   344  }
   345  
   346  func (iface *dbusInterface) DBusPermanentSlot(spec *dbus.Specification, slot *snap.SlotInfo) error {
   347  	bus, name, err := iface.getAttribs(slot)
   348  	if err != nil {
   349  		return err
   350  	}
   351  
   352  	// only system services need bus policy
   353  	if bus != "system" {
   354  		return nil
   355  	}
   356  
   357  	old := "###DBUS_NAME###"
   358  	new := name
   359  	spec.AddSnippet(strings.Replace(dbusPermanentSlotDBus, old, new, -1))
   360  	return nil
   361  }
   362  
   363  func (iface *dbusInterface) AppArmorPermanentSlot(spec *apparmor.Specification, slot *snap.SlotInfo) error {
   364  	bus, name, err := iface.getAttribs(slot)
   365  	if err != nil {
   366  		return err
   367  	}
   368  
   369  	// well-known DBus name-specific permanent slot policy
   370  	snippet := getAppArmorSnippet(dbusPermanentSlotAppArmor, bus, name)
   371  
   372  	// abstraction policy
   373  	abstraction, err := getAppArmorAbstraction(bus)
   374  	if err != nil {
   375  		return err
   376  	}
   377  
   378  	old := "###DBUS_ABSTRACTION###"
   379  	new := abstraction
   380  	snippet = strings.Replace(snippet, old, new, -1)
   381  	spec.AddSnippet(snippet)
   382  
   383  	if release.OnClassic {
   384  		// classic-only policy
   385  		spec.AddSnippet(getAppArmorSnippet(dbusPermanentSlotAppArmorClassic, bus, name))
   386  	}
   387  	return nil
   388  }
   389  
   390  func (iface *dbusInterface) SecCompPermanentSlot(spec *seccomp.Specification, slot *snap.SlotInfo) error {
   391  	spec.AddSnippet(dbusPermanentSlotSecComp)
   392  	return nil
   393  }
   394  
   395  func (iface *dbusInterface) AppArmorConnectedSlot(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   396  	bus, name, err := iface.getAttribs(slot)
   397  	if err != nil {
   398  		return err
   399  	}
   400  
   401  	busPlug, namePlug, err := iface.getAttribs(plug)
   402  	if err != nil {
   403  		return err
   404  	}
   405  
   406  	// ensure that we only connect to slot with matching attributes. This
   407  	// makes sure that the security policy is correct, but does not ensure
   408  	// that 'snap interfaces' is correct.
   409  	// TODO: we can fix the 'snap interfaces' issue when interface/policy
   410  	// checkers when they are available
   411  	if bus != busPlug || name != namePlug {
   412  		return nil
   413  	}
   414  
   415  	// well-known DBus name-specific connected slot policy
   416  	snippet := getAppArmorSnippet(dbusConnectedSlotAppArmor, bus, name)
   417  
   418  	old := "###PLUG_SECURITY_TAGS###"
   419  	new := plugAppLabelExpr(plug)
   420  	snippet = strings.Replace(snippet, old, new, -1)
   421  	spec.AddSnippet(snippet)
   422  	return nil
   423  }
   424  
   425  func (iface *dbusInterface) BeforePreparePlug(plug *snap.PlugInfo) error {
   426  	_, _, err := iface.getAttribs(plug)
   427  	return err
   428  }
   429  
   430  func (iface *dbusInterface) BeforePrepareSlot(slot *snap.SlotInfo) error {
   431  	_, _, err := iface.getAttribs(slot)
   432  	return err
   433  }
   434  
   435  func (iface *dbusInterface) AutoConnect(*snap.PlugInfo, *snap.SlotInfo) bool {
   436  	// allow what declarations allowed
   437  	return true
   438  }
   439  
   440  func init() {
   441  	registerIface(&dbusInterface{})
   442  }