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