github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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 }