github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/interfaces/builtin/desktop.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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  	"fmt"
    24  
    25  	"github.com/snapcore/snapd/dirs"
    26  	"github.com/snapcore/snapd/interfaces"
    27  	"github.com/snapcore/snapd/interfaces/apparmor"
    28  	"github.com/snapcore/snapd/interfaces/mount"
    29  	"github.com/snapcore/snapd/osutil"
    30  	"github.com/snapcore/snapd/release"
    31  	"github.com/snapcore/snapd/snap"
    32  	"github.com/snapcore/snapd/strutil"
    33  )
    34  
    35  const desktopSummary = `allows access to basic graphical desktop resources`
    36  
    37  const desktopBaseDeclarationSlots = `
    38    desktop:
    39      allow-installation:
    40        slot-snap-type:
    41          - core
    42  `
    43  
    44  const desktopConnectedPlugAppArmor = `
    45  # Description: Can access basic graphical desktop resources. To be used with
    46  # other interfaces (eg, wayland).
    47  
    48  #include <abstractions/dbus-strict>
    49  #include <abstractions/dbus-session-strict>
    50  
    51  # Allow finding the DBus session bus id (eg, via dbus_bus_get_id())
    52  dbus (send)
    53       bus=session
    54       path=/org/freedesktop/DBus
    55       interface=org.freedesktop.DBus
    56       member=GetId
    57       peer=(name=org.freedesktop.DBus, label=unconfined),
    58  
    59  #include <abstractions/fonts>
    60  owner @{HOME}/.local/share/fonts/{,**} r,
    61  /var/cache/fontconfig/   r,
    62  /var/cache/fontconfig/** mr,
    63  # some applications are known to mmap fonts
    64  /usr/{,local/}share/fonts/** m,
    65  
    66  # subset of gnome abstraction
    67  /etc/gtk-3.0/settings.ini r,
    68  owner @{HOME}/.config/gtk-3.0/settings.ini r,
    69  # Note: this leaks directory names that wouldn't otherwise be known to the snap
    70  owner @{HOME}/.config/gtk-3.0/bookmarks r,
    71  
    72  /usr/share/icons/                          r,
    73  /usr/share/icons/**                        r,
    74  /usr/share/icons/*/index.theme             rk,
    75  /usr/share/pixmaps/                        r,
    76  /usr/share/pixmaps/**                      r,
    77  /usr/share/unity/icons/**                  r,
    78  /usr/share/thumbnailer/icons/**            r,
    79  /usr/share/themes/**                       r,
    80  
    81  # The snapcraft desktop part may look for schema files in various locations, so
    82  # allow reading system installed schemas.
    83  /usr/share/glib*/schemas/{,*}              r,
    84  /usr/share/gnome/glib*/schemas/{,*}        r,
    85  /usr/share/ubuntu/glib*/schemas/{,*}       r,
    86  
    87  # subset of freedesktop.org
    88  owner @{HOME}/.local/share/mime/**   r,
    89  owner @{HOME}/.config/user-dirs.* r,
    90  
    91  /etc/xdg/user-dirs.conf r,
    92  /etc/xdg/user-dirs.defaults r,
    93  
    94  # gmenu
    95  dbus (send)
    96       bus=session
    97       interface=org.gtk.Actions
    98       member=Changed
    99       peer=(name=org.freedesktop.DBus, label=unconfined),
   100  
   101  # notifications
   102  dbus (send)
   103      bus=session
   104      path=/org/freedesktop/Notifications
   105      interface=org.freedesktop.Notifications
   106      member="{GetCapabilities,GetServerInformation,Notify,CloseNotification}"
   107      peer=(label=unconfined),
   108  
   109  dbus (receive)
   110      bus=session
   111      path=/org/freedesktop/Notifications
   112      interface=org.freedesktop.Notifications
   113      member={ActionInvoked,NotificationClosed,NotificationReplied}
   114      peer=(label=unconfined),
   115  
   116  # KDE Plasma's Inhibited property indicating "do not disturb" mode
   117  # https://invent.kde.org/plasma/plasma-workspace/-/blob/master/libnotificationmanager/dbus/org.freedesktop.Notifications.xml#L42
   118  dbus (send)
   119      bus=session
   120      path=/org/freedesktop/Notifications
   121      interface=org.freedesktop.DBus.Properties
   122      member="Get{,All}"
   123      peer=(label=unconfined),
   124  
   125  dbus (receive)
   126      bus=session
   127      path=/org/freedesktop/Notifications
   128      interface=org.freedesktop.DBus.Properties
   129      member=PropertiesChanged
   130      peer=(label=unconfined),
   131  
   132  # DesktopAppInfo Launched
   133  dbus (send)
   134      bus=session
   135      path=/org/gtk/gio/DesktopAppInfo
   136      interface=org.gtk.gio.DesktopAppInfo
   137      member=Launched
   138      peer=(label=unconfined),
   139  
   140  # Allow requesting interest in receiving media key events. This tells Gnome
   141  # settings that our application should be notified when key events we are
   142  # interested in are pressed, and allows us to receive those events.
   143  dbus (receive, send)
   144    bus=session
   145    interface=org.gnome.SettingsDaemon.MediaKeys
   146    path=/org/gnome/SettingsDaemon/MediaKeys
   147    peer=(label=unconfined),
   148  dbus (send)
   149    bus=session
   150    interface=org.freedesktop.DBus.Properties
   151    path=/org/gnome/SettingsDaemon/MediaKeys
   152    member="Get{,All}"
   153    peer=(label=unconfined),
   154  
   155  # Allow accessing the GNOME crypto services prompt APIs as used by
   156  # applications using libgcr (such as pinentry-gnome3) for secure pin
   157  # entry to unlock GPG keys etc. See:
   158  # https://developer.gnome.org/gcr/unstable/GcrPrompt.html
   159  # https://developer.gnome.org/gcr/unstable/GcrSecretExchange.html
   160  dbus (send)
   161      bus=session
   162      path=/org/gnome/keyring/Prompter
   163      interface=org.gnome.keyring.internal.Prompter
   164      member="{BeginPrompting,PerformPrompt,StopPrompting}"
   165      peer=(label=unconfined),
   166  
   167  # While the DBus path is not snap-specific, by the time an application
   168  # registers the prompt path via DBus, Gcr will check that it isn't
   169  # already in use and send the client an error if it is. See:
   170  # https://github.com/snapcore/snapd/pull/7673#issuecomment-592229711
   171  dbus (receive)
   172      bus=session
   173      path=/org/gnome/keyring/Prompt/p[0-9]*
   174      interface=org.gnome.keyring.internal.Prompter.Callback
   175      member="{PromptReady,PromptDone}"
   176      peer=(label=unconfined),
   177  
   178  # Allow use of snapd's internal 'xdg-open'
   179  /usr/bin/xdg-open ixr,
   180  # While /usr/share/applications comes from the base runtime of the snap, it
   181  # has some things that snaps actually need, so allow access to those and deny
   182  # access to the others
   183  /usr/share/applications/ r,
   184  /usr/share/applications/mimeapps.list r,
   185  /usr/share/applications/xdg-open.desktop r,
   186  # silence noisy denials from desktop files in core* snaps that aren't usable by
   187  # snaps
   188  deny /usr/share/applications/python*.desktop r,
   189  deny /usr/share/applications/vim.desktop r,
   190  deny /usr/share/applications/snap-handle-link.desktop r,  # core16
   191  
   192  dbus (send)
   193      bus=session
   194      path=/
   195      interface=com.canonical.SafeLauncher
   196      member=OpenURL
   197      peer=(label=unconfined),
   198  # ... and this allows access to the new xdg-open service which
   199  # is now part of snapd itself.
   200  dbus (send)
   201      bus=session
   202      path=/io/snapcraft/Launcher
   203      interface=io.snapcraft.Launcher
   204      member={OpenURL,OpenFile}
   205      peer=(label=unconfined),
   206  
   207  # Allow checking status, activating and locking the screensaver
   208  # gnome/kde/freedesktop.org
   209  dbus (send)
   210      bus=session
   211      path="/{,org/freedesktop/,org/gnome/}ScreenSaver"
   212      interface="org.{freedesktop,gnome}.ScreenSaver"
   213      member="{GetActive,GetActiveTime,Lock,SetActive}"
   214      peer=(label=unconfined),
   215  
   216  dbus (receive)
   217      bus=session
   218      path="/{,org/freedesktop/,org/gnome/}ScreenSaver"
   219      interface="org.{freedesktop,gnome}.ScreenSaver"
   220      member=ActiveChanged
   221      peer=(label=unconfined),
   222  
   223  # Allow unconfined to introspect us
   224  dbus (receive)
   225      bus=session
   226      interface=org.freedesktop.DBus.Introspectable
   227      member=Introspect
   228      peer=(label=unconfined),
   229  
   230  # Allow use of snapd's internal 'xdg-settings'
   231  /usr/bin/xdg-settings ixr,
   232  dbus (send)
   233      bus=session
   234      path=/io/snapcraft/Settings
   235      interface=io.snapcraft.Settings
   236      member={Check,CheckSub,Get,GetSub,Set,SetSub}
   237      peer=(label=unconfined),
   238  
   239  # Allow access to xdg-document-portal file system.  Access control is
   240  # handled by bind mounting a snap-specific sub-tree to this location
   241  # (ie, this is /run/user/<uid>/doc/by-app/snap.@{SNAP_INSTANCE_NAME}
   242  # on the host).
   243  owner /run/user/[0-9]*/doc/{,*/} r,
   244  # Allow rw access without owner match to the documents themselves since
   245  # the user guided the access and can specify anything DAC allows.
   246  /run/user/[0-9]*/doc/*/** rw,
   247  
   248  # Allow access to xdg-desktop-portal and xdg-document-portal
   249  dbus (receive, send)
   250      bus=session
   251      interface=org.freedesktop.portal.*
   252      path=/org/freedesktop/portal/{desktop,documents}{,/**}
   253      peer=(label=unconfined),
   254  
   255  dbus (receive, send)
   256      bus=session
   257      interface=org.freedesktop.DBus.Properties
   258      path=/org/freedesktop/portal/{desktop,documents}{,/**}
   259      peer=(label=unconfined),
   260  
   261  # The portals service is normally running and newer versions of
   262  # xdg-desktop-portal include AssumedAppArmor=unconfined. Since older
   263  # systems don't have this and because gtkfilechoosernativeportal.c relies on
   264  # service activation, allow sends to peer=(name=org.freedesktop.portal.Desktop)
   265  # for service activation.
   266  dbus (send)
   267      bus=session
   268      interface=org.freedesktop.portal.*
   269      path=/org/freedesktop/portal/{desktop,documents}{,/**}
   270      peer=(name=org.freedesktop.portal.Desktop),
   271  dbus (send)
   272      bus=session
   273      interface=org.freedesktop.DBus.Properties
   274      path=/org/freedesktop/portal/{desktop,documents}{,/**}
   275      peer=(name=org.freedesktop.portal.Desktop),
   276  
   277  # These accesses are noisy and applications can't do anything with the found
   278  # icon files, so explicitly deny to silence the denials
   279  deny /var/lib/snapd/desktop/icons/{,**/} r,
   280  
   281  # These accesses occur when flatpaks are on the system since it updates
   282  # XDG_DATA_DIRS to contain $HOME/.local/share/flatpak/exports/share. Until
   283  # we have better XDG_DATA_DIRS handling, silence these noisy denials.
   284  # https://github.com/snapcrafters/discord/issues/23#issuecomment-637607843
   285  deny @{HOME}/.local/share/flatpak/exports/share/** r,
   286  
   287  # Allow access to the IBus portal (IBUS_USE_PORTAL=1)
   288  dbus (send)
   289        bus=session
   290        path=/org/freedesktop/IBus
   291        interface=org.freedesktop.IBus.Portal
   292        member=CreateInputContext
   293        peer=(name=org.freedesktop.portal.IBus),
   294  
   295  dbus (send, receive)
   296        bus=session
   297        path=/org/freedesktop/IBus/InputContext_[0-9]*
   298        interface=org.freedesktop.IBus.InputContext
   299        peer=(label=unconfined),
   300  `
   301  
   302  type desktopInterface struct {
   303  	commonInterface
   304  }
   305  
   306  func (iface *desktopInterface) shouldMountHostFontCache(attribs interfaces.Attrer) (bool, error) {
   307  	value, ok := attribs.Lookup("mount-host-font-cache")
   308  	if !ok {
   309  		// If the attribute is not present, we mount the font cache
   310  		return true, nil
   311  	}
   312  	shouldMount, ok := value.(bool)
   313  	if !ok {
   314  		return false, fmt.Errorf("desktop plug requires bool with 'mount-host-font-cache'")
   315  	}
   316  	return shouldMount, nil
   317  }
   318  
   319  func (iface *desktopInterface) fontconfigDirs(plug *interfaces.ConnectedPlug) ([]string, error) {
   320  	fontDirs := []string{
   321  		dirs.SystemFontsDir,
   322  		dirs.SystemLocalFontsDir,
   323  	}
   324  
   325  	shouldMountHostFontCache, err := iface.shouldMountHostFontCache(plug)
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	if shouldMountHostFontCache {
   330  		fontDirs = append(fontDirs, dirs.SystemFontconfigCacheDirs...)
   331  	}
   332  
   333  	return fontDirs, nil
   334  }
   335  
   336  func (iface *desktopInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   337  	spec.AddSnippet(desktopConnectedPlugAppArmor)
   338  
   339  	// Allow mounting document portal
   340  	emit := spec.AddUpdateNSf
   341  	emit("  # Mount the document portal\n")
   342  	emit("  mount options=(bind) /run/user/[0-9]*/doc/by-app/snap.%s/ -> /run/user/[0-9]*/doc/,\n", plug.Snap().InstanceName())
   343  	emit("  umount /run/user/[0-9]*/doc/,\n\n")
   344  
   345  	if !release.OnClassic {
   346  		// We only need the font mount rules on classic systems
   347  		return nil
   348  	}
   349  
   350  	// Allow mounting fonts
   351  	fontDirs, err := iface.fontconfigDirs(plug)
   352  	if err != nil {
   353  		return err
   354  	}
   355  	for _, dir := range fontDirs {
   356  		source := "/var/lib/snapd/hostfs" + dir
   357  		target := dirs.StripRootDir(dir)
   358  		emit("  # Read-only access to %s\n", target)
   359  		emit("  mount options=(bind) %s/ -> %s/,\n", source, target)
   360  		emit("  remount options=(bind, ro) %s/,\n", target)
   361  		emit("  umount %s/,\n\n", target)
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  func (iface *desktopInterface) MountConnectedPlug(spec *mount.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error {
   368  	appId := "snap." + plug.Snap().InstanceName()
   369  	spec.AddUserMountEntry(osutil.MountEntry{
   370  		Name:    "$XDG_RUNTIME_DIR/doc/by-app/" + appId,
   371  		Dir:     "$XDG_RUNTIME_DIR/doc",
   372  		Options: []string{"bind", "rw", osutil.XSnapdIgnoreMissing()},
   373  	})
   374  
   375  	if !release.OnClassic {
   376  		// We only need the font mount rules on classic systems
   377  		return nil
   378  	}
   379  
   380  	fontDirs, err := iface.fontconfigDirs(plug)
   381  	if err != nil {
   382  		return err
   383  	}
   384  	for _, dir := range fontDirs {
   385  		if !osutil.IsDirectory(dir) {
   386  			continue
   387  		}
   388  		if release.DistroLike("arch", "fedora") {
   389  			// XXX: on Arch and Fedora 32+ there is a known
   390  			// incompatibility between the binary fonts cache files
   391  			// and ones expected by desktop snaps; even though the
   392  			// cache format level is same for both, the host
   393  			// generated cache files cause instability, segfaults or
   394  			// incorrect rendering of fonts, for this reason do not
   395  			// mount the cache directories on those distributions,
   396  			// see https://bugs.launchpad.net/snapd/+bug/1877109
   397  			if strutil.ListContains(dirs.SystemFontconfigCacheDirs, dir) {
   398  				continue
   399  			}
   400  		}
   401  		// Since /etc/fonts/fonts.conf in the snap mount ns is the same
   402  		// as on the host, we need to preserve the original directory
   403  		// paths for the fontconfig runtime to poke the correct
   404  		// locations
   405  		spec.AddMountEntry(osutil.MountEntry{
   406  			Name:    "/var/lib/snapd/hostfs" + dir,
   407  			Dir:     dirs.StripRootDir(dir),
   408  			Options: []string{"bind", "ro"},
   409  		})
   410  	}
   411  
   412  	return nil
   413  }
   414  
   415  func (iface *desktopInterface) BeforePreparePlug(plug *snap.PlugInfo) error {
   416  	_, err := iface.shouldMountHostFontCache(plug)
   417  	return err
   418  }
   419  
   420  func init() {
   421  	registerIface(&desktopInterface{
   422  		commonInterface: commonInterface{
   423  			name:                 "desktop",
   424  			summary:              desktopSummary,
   425  			implicitOnClassic:    true,
   426  			baseDeclarationSlots: desktopBaseDeclarationSlots,
   427  		},
   428  	})
   429  }