From 0461b76aaa04ee80e1371051fff49631cea14a48 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 2 Sep 2019 15:00:00 +0100 Subject: [PATCH 001/114] "shell-support" interface --- interfaces/builtin/shell-support.go | 57 ++++++++++++++++ interfaces/builtin/shell_support_test.go | 86 ++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 interfaces/builtin/shell-support.go create mode 100644 interfaces/builtin/shell_support_test.go diff --git a/interfaces/builtin/shell-support.go b/interfaces/builtin/shell-support.go new file mode 100644 index 00000000000..c4a9f51b0de --- /dev/null +++ b/interfaces/builtin/shell-support.go @@ -0,0 +1,57 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 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 . + * + */ + +package builtin + +const shellSupportSummary = `allows shells to identify and launch other snaps` + +const shellSupportBaseDeclarationSlots = ` + shell-support: + allow-installation: + slot-snap-type: + - core +` + +const shellSupportConnectedPlugAppArmor = ` +# Description: Can identify and launch other snaps. + +# Access to the desktop files installed by snaps +/var/lib/snapd/desktop/applications/{,*} r, + +#include + +dbus (send) + bus=session + path=/io/snapcraft/Launcher + interface=io.snapcraft.Launcher + member=OpenDesktopEntry + peer=(label=unconfined), +` + +func init() { + registerIface(&commonInterface{ + name: "shell-support", + summary: shellSupportSummary, + implicitOnCore: true, + implicitOnClassic: true, + baseDeclarationSlots: shellSupportBaseDeclarationSlots, + connectedPlugAppArmor: shellSupportConnectedPlugAppArmor, + reservedForOS: true, + }) +} diff --git a/interfaces/builtin/shell_support_test.go b/interfaces/builtin/shell_support_test.go new file mode 100644 index 00000000000..2d07939a198 --- /dev/null +++ b/interfaces/builtin/shell_support_test.go @@ -0,0 +1,86 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 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 . + * + */ + +package builtin_test + +import ( + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/interfaces" + "github.com/snapcore/snapd/interfaces/apparmor" + "github.com/snapcore/snapd/interfaces/builtin" + "github.com/snapcore/snapd/snap" + "github.com/snapcore/snapd/snap/snaptest" + "github.com/snapcore/snapd/testutil" +) + +type shellSupportSuite struct { + iface interfaces.Interface + slotInfo *snap.SlotInfo + slot *interfaces.ConnectedSlot + plugInfo *snap.PlugInfo + plug *interfaces.ConnectedPlug +} + +var _ = Suite(&shellSupportSuite{ + iface: builtin.MustInterface("shell-support"), +}) + +func (s *shellSupportSuite) SetUpTest(c *C) { + consumingSnapInfo := snaptest.MockInfo(c, ` +name: other +version: 0 +apps: + app: + command: foo + plugs: [shell-support] +`, nil) + s.plugInfo = consumingSnapInfo.Plugs["shell-support"] + s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil) + s.slotInfo = &snap.SlotInfo{ + Snap: &snap.Info{SuggestedName: "core", SnapType: snap.TypeOS}, + Name: "shell-support", + Interface: "shell-support", + } + s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil, nil) +} + +func (s *shellSupportSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "shell-support") +} + +func (s *shellSupportSuite) TestSanitizeSlot(c *C) { + c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) +} + +func (s *shellSupportSuite) TestSanitizePlug(c *C) { + c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) +} + +func (s *shellSupportSuite) TestConnectedPlugSnippet(c *C) { + apparmorSpec := &apparmor.Specification{} + err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) + c.Assert(err, IsNil) + c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) + c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `Can identify and launch other snaps.`) +} + +func (s *shellSupportSuite) TestInterfaces(c *C) { + c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) +} From a954fea4518dc3d5c38095e50115e7f2cc0358ee Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 19 Aug 2019 17:34:47 +0100 Subject: [PATCH 002/114] Spike OpenDesktopEntry method --- usersession/userd/launcher.go | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index d98a57b9caf..3ea90ca398c 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -20,6 +20,7 @@ package userd import ( + "bufio" "fmt" "net/url" "os" @@ -50,6 +51,9 @@ const launcherIntrospectionXML = ` + + + @@ -58,6 +62,7 @@ const launcherIntrospectionXML = ` var ( allowedURLSchemes = []string{"http", "https", "mailto", "snap", "help", "apt", "zoommtg"} + allowedDesktopLocations = []string{"/var/lib/snapd/desktop/applications"} ) // Launcher implements the 'io.snapcraft.Launcher' DBus interface. @@ -123,6 +128,62 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { return nil } +// OpenDesktopEntry implements the 'OpenDesktopEntry' method of the 'io.snapcraft.Launcher' +// DBus interface. Before the provided desktop_file is parsed it is validated against a list +// of allowed locations. +func (s *Launcher) OpenDesktopEntry(desktop_file string, sender dbus.Sender) *dbus.Error { + + if !strutil.ListContains(allowedDesktopLocations, filepath.Dir(desktop_file)) { + return makeAccessDeniedError(fmt.Errorf("Supplied desktop location %q is not allowed", desktop_file)) + } + +// snap, err := snapFromSender(s.conn, sender) +// if err != nil { +// return dbus.MakeFailedError(err) +// } + + file, err := os.Open(desktop_file) + if err != nil { + return dbus.MakeFailedError(err) + } + defer file.Close() + reader := bufio.NewReader(file) + + var launch string; + + for { + line, err := reader.ReadString('\n') + if err != nil { + return dbus.MakeFailedError(err) + } + + line = strings.TrimSpace(line) + + if strings.HasPrefix(line, "Exec=") { + launch = strings.TrimPrefix(line, "Exec=") + break; + } + } + + // This is very hacky parsing and doesn't cover a lot of cases + command := strings.Split(strings.SplitN(launch, "%", 2)[0], " "); + + cmd := exec.Command(command[0], command[1:]...) + cmd.Env = os.Environ() + + // Encourage applications to use Wayland + cmd.Env = append(cmd.Env, "XDG_SESSION_TYPE=mir") + cmd.Env = append(cmd.Env, "GDK_BACKEND=wayland") + cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland") + cmd.Env = append(cmd.Env, "SDL_VIDEODRIVER=wayland") + + if err := cmd.Start(); err != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot run %q", launch)) + } + + return nil +} + // fdToFilename determines the path associated with an open file descriptor. // // The file descriptor cannot be opened using O_PATH and must refer to From 8a60e15c3f365a93e8de907ea073b96d47142c36 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 3 Sep 2019 10:00:19 +0100 Subject: [PATCH 003/114] Add OpenDesktopEntryEnv to permit setting environment variables --- interfaces/builtin/shell-support.go | 2 +- usersession/userd/launcher.go | 57 ++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/interfaces/builtin/shell-support.go b/interfaces/builtin/shell-support.go index c4a9f51b0de..b11823776c8 100644 --- a/interfaces/builtin/shell-support.go +++ b/interfaces/builtin/shell-support.go @@ -40,7 +40,7 @@ dbus (send) bus=session path=/io/snapcraft/Launcher interface=io.snapcraft.Launcher - member=OpenDesktopEntry + member={OpenDesktopEntry,OpenDesktopEntryEnv} peer=(label=unconfined), ` diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 3ea90ca398c..65207734a2b 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -54,6 +54,10 @@ const launcherIntrospectionXML = ` + + + + @@ -171,11 +175,54 @@ func (s *Launcher) OpenDesktopEntry(desktop_file string, sender dbus.Sender) *db cmd := exec.Command(command[0], command[1:]...) cmd.Env = os.Environ() - // Encourage applications to use Wayland - cmd.Env = append(cmd.Env, "XDG_SESSION_TYPE=mir") - cmd.Env = append(cmd.Env, "GDK_BACKEND=wayland") - cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland") - cmd.Env = append(cmd.Env, "SDL_VIDEODRIVER=wayland") + if err := cmd.Start(); err != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot run %q", launch)) + } + + return nil +} + +// OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' +// DBus interface. Before the provided desktop_file is parsed it is validated against a list +// of allowed locations. +func (s *Launcher) OpenDesktopEntryEnv(desktop_file string, env []string, sender dbus.Sender) *dbus.Error { + + if !strutil.ListContains(allowedDesktopLocations, filepath.Dir(desktop_file)) { + return makeAccessDeniedError(fmt.Errorf("Supplied desktop location %q is not allowed", desktop_file)) + } + + file, err := os.Open(desktop_file) + if err != nil { + return dbus.MakeFailedError(err) + } + defer file.Close() + reader := bufio.NewReader(file) + + var launch string; + + for { + line, err := reader.ReadString('\n') + if err != nil { + return dbus.MakeFailedError(err) + } + + line = strings.TrimSpace(line) + + if strings.HasPrefix(line, "Exec=") { + launch = strings.TrimPrefix(line, "Exec=") + break; + } + } + + // This is very hacky parsing and doesn't cover a lot of cases + command := strings.Split(strings.SplitN(launch, "%", 2)[0], " "); + + cmd := exec.Command(command[0], command[1:]...) + cmd.Env = os.Environ() + + for _,e := range env { + cmd.Env = append(cmd.Env, e) + } if err := cmd.Start(); err != nil { return dbus.MakeFailedError(fmt.Errorf("cannot run %q", launch)) From 6f8343d3f5fabbc94c8f90b745af3e8f947944d3 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 3 Sep 2019 14:14:43 +0100 Subject: [PATCH 004/114] Switch to Desktop File IDs --- usersession/userd/launcher.go | 67 ++++++++++------------------------- 1 file changed, 19 insertions(+), 48 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 65207734a2b..824d24fddd1 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -52,10 +52,10 @@ const launcherIntrospectionXML = ` - + - + @@ -66,7 +66,6 @@ const launcherIntrospectionXML = ` var ( allowedURLSchemes = []string{"http", "https", "mailto", "snap", "help", "apt", "zoommtg"} - allowedDesktopLocations = []string{"/var/lib/snapd/desktop/applications"} ) // Launcher implements the 'io.snapcraft.Launcher' DBus interface. @@ -135,62 +134,34 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { // OpenDesktopEntry implements the 'OpenDesktopEntry' method of the 'io.snapcraft.Launcher' // DBus interface. Before the provided desktop_file is parsed it is validated against a list // of allowed locations. -func (s *Launcher) OpenDesktopEntry(desktop_file string, sender dbus.Sender) *dbus.Error { +func (s *Launcher) OpenDesktopEntry(desktop_file_id string, sender dbus.Sender) *dbus.Error { - if !strutil.ListContains(allowedDesktopLocations, filepath.Dir(desktop_file)) { - return makeAccessDeniedError(fmt.Errorf("Supplied desktop location %q is not allowed", desktop_file)) - } + return s.OpenDesktopEntryEnv(desktop_file_id, []string{}, sender) +} -// snap, err := snapFromSender(s.conn, sender) -// if err != nil { -// return dbus.MakeFailedError(err) -// } +// OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' +// DBus interface. +func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { + splitFileId := strings.Split(desktop_file_id, "-") - file, err := os.Open(desktop_file) - if err != nil { - return dbus.MakeFailedError(err) - } - defer file.Close() - reader := bufio.NewReader(file) + var desktop_file string; - var launch string; + for _, dir := range strings.Split(os.Getenv("XDG_DATA_DIRS"), ":") { + var fileStat os.FileInfo - for { - line, err := reader.ReadString('\n') - if err != nil { - return dbus.MakeFailedError(err) + for i:=0; i != len(splitFileId); i = i+1 { + desktop_file = dir + "/applications/" + strings.Join(splitFileId[0:i], "/") + "/" + strings.Join(splitFileId[i:], "-") + fileStat, _ = os.Stat(desktop_file) + if fileStat != nil { + break; + } } - line = strings.TrimSpace(line) - - if strings.HasPrefix(line, "Exec=") { - launch = strings.TrimPrefix(line, "Exec=") + if fileStat != nil { break; } } - // This is very hacky parsing and doesn't cover a lot of cases - command := strings.Split(strings.SplitN(launch, "%", 2)[0], " "); - - cmd := exec.Command(command[0], command[1:]...) - cmd.Env = os.Environ() - - if err := cmd.Start(); err != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot run %q", launch)) - } - - return nil -} - -// OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' -// DBus interface. Before the provided desktop_file is parsed it is validated against a list -// of allowed locations. -func (s *Launcher) OpenDesktopEntryEnv(desktop_file string, env []string, sender dbus.Sender) *dbus.Error { - - if !strutil.ListContains(allowedDesktopLocations, filepath.Dir(desktop_file)) { - return makeAccessDeniedError(fmt.Errorf("Supplied desktop location %q is not allowed", desktop_file)) - } - file, err := os.Open(desktop_file) if err != nil { return dbus.MakeFailedError(err) From 677a1bdbae2f010d7245bb7dc30a6cc6ae49430f Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 4 Sep 2019 10:45:13 +0100 Subject: [PATCH 005/114] Extract desktopFileIdToFilename() --- usersession/userd/launcher.go | 47 +++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 824d24fddd1..86ebb09e9fa 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -142,27 +142,7 @@ func (s *Launcher) OpenDesktopEntry(desktop_file_id string, sender dbus.Sender) // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' // DBus interface. func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - splitFileId := strings.Split(desktop_file_id, "-") - - var desktop_file string; - - for _, dir := range strings.Split(os.Getenv("XDG_DATA_DIRS"), ":") { - var fileStat os.FileInfo - - for i:=0; i != len(splitFileId); i = i+1 { - desktop_file = dir + "/applications/" + strings.Join(splitFileId[0:i], "/") + "/" + strings.Join(splitFileId[i:], "-") - fileStat, _ = os.Stat(desktop_file) - if fileStat != nil { - break; - } - } - - if fileStat != nil { - break; - } - } - - file, err := os.Open(desktop_file) + file, err := os.Open(s.desktopFileIdToFilename(desktop_file_id)) if err != nil { return dbus.MakeFailedError(err) } @@ -202,6 +182,31 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen return nil } +// desktopFileIdToFilename determines the path associated with a desktop file ID. +func (s *Launcher) desktopFileIdToFilename(desktop_file_id string) string { + splitFileId := strings.Split(desktop_file_id, "-") + + var desktop_file string; + + for _, dir := range strings.Split(os.Getenv("XDG_DATA_DIRS"), ":") { + var fileStat os.FileInfo + + for i:=0; i != len(splitFileId); i = i+1 { + desktop_file = dir + "/applications/" + strings.Join(splitFileId[0:i], "/") + "/" + strings.Join(splitFileId[i:], "-") + fileStat, _ = os.Stat(desktop_file) + if fileStat != nil { + break; + } + } + + if fileStat != nil { + break; + } + } + + return desktop_file +} + // fdToFilename determines the path associated with an open file descriptor. // // The file descriptor cannot be opened using O_PATH and must refer to From 2258b754113b4533bb7d165cc3505da339dcbc65 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 4 Sep 2019 11:33:59 +0100 Subject: [PATCH 006/114] Extract readExecCommandFromDesktopFile() --- usersession/userd/launcher.go | 49 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 86ebb09e9fa..a9c0b558b0e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -142,28 +142,11 @@ func (s *Launcher) OpenDesktopEntry(desktop_file_id string, sender dbus.Sender) // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' // DBus interface. func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - file, err := os.Open(s.desktopFileIdToFilename(desktop_file_id)) + launch, err := s.readExecCommandFromDesktopFile(s.desktopFileIdToFilename(desktop_file_id)) + if err != nil { return dbus.MakeFailedError(err) } - defer file.Close() - reader := bufio.NewReader(file) - - var launch string; - - for { - line, err := reader.ReadString('\n') - if err != nil { - return dbus.MakeFailedError(err) - } - - line = strings.TrimSpace(line) - - if strings.HasPrefix(line, "Exec=") { - launch = strings.TrimPrefix(line, "Exec=") - break; - } - } // This is very hacky parsing and doesn't cover a lot of cases command := strings.Split(strings.SplitN(launch, "%", 2)[0], " "); @@ -207,6 +190,34 @@ func (s *Launcher) desktopFileIdToFilename(desktop_file_id string) string { return desktop_file } +// readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. +func (s *Launcher) readExecCommandFromDesktopFile(desktop_file string) (string, error) { + var launch string + + file, err := os.Open(desktop_file) + if err != nil { + return launch, err + } + defer file.Close() + reader := bufio.NewReader(file) + + for { + line, err := reader.ReadString('\n') + if err != nil { + return launch, err + } + + line = strings.TrimSpace(line) + + if strings.HasPrefix(line, "Exec=") { + launch = strings.TrimPrefix(line, "Exec=") + break; + } + } + + return launch, nil +} + // fdToFilename determines the path associated with an open file descriptor. // // The file descriptor cannot be opened using O_PATH and must refer to From 5987a0acf95f1c1c971e91efad9b68c6069343ae Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 11 Sep 2019 16:43:54 +0100 Subject: [PATCH 007/114] Clearer use of whitespace --- .../{shell-support.go => shell_support.go} | 0 usersession/userd/launcher.go | 93 +++++++++---------- 2 files changed, 46 insertions(+), 47 deletions(-) rename interfaces/builtin/{shell-support.go => shell_support.go} (100%) diff --git a/interfaces/builtin/shell-support.go b/interfaces/builtin/shell_support.go similarity index 100% rename from interfaces/builtin/shell-support.go rename to interfaces/builtin/shell_support.go diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index a9c0b558b0e..cf047d668fe 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -136,86 +136,85 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { // of allowed locations. func (s *Launcher) OpenDesktopEntry(desktop_file_id string, sender dbus.Sender) *dbus.Error { - return s.OpenDesktopEntryEnv(desktop_file_id, []string{}, sender) + return s.OpenDesktopEntryEnv(desktop_file_id, []string{}, sender) } // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' // DBus interface. func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - launch, err := s.readExecCommandFromDesktopFile(s.desktopFileIdToFilename(desktop_file_id)) + launch, err := s.readExecCommandFromDesktopFile(s.desktopFileIdToFilename(desktop_file_id)) if err != nil { return dbus.MakeFailedError(err) } - // This is very hacky parsing and doesn't cover a lot of cases - command := strings.Split(strings.SplitN(launch, "%", 2)[0], " "); + // This is very hacky parsing and doesn't cover a lot of cases + command := strings.Split(strings.SplitN(launch, "%", 2)[0], " ") + cmd := exec.Command(command[0], command[1:]...) - cmd := exec.Command(command[0], command[1:]...) - cmd.Env = os.Environ() - - for _,e := range env { - cmd.Env = append(cmd.Env, e) - } + cmd.Env = os.Environ() + for _, e := range env { + cmd.Env = append(cmd.Env, e) + } - if err := cmd.Start(); err != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot run %q", launch)) - } + if err := cmd.Start(); err != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot run %q", launch)) + } return nil } // desktopFileIdToFilename determines the path associated with a desktop file ID. func (s *Launcher) desktopFileIdToFilename(desktop_file_id string) string { - splitFileId := strings.Split(desktop_file_id, "-") - - var desktop_file string; + splitFileId := strings.Split(desktop_file_id, "-") - for _, dir := range strings.Split(os.Getenv("XDG_DATA_DIRS"), ":") { - var fileStat os.FileInfo + var desktop_file string - for i:=0; i != len(splitFileId); i = i+1 { - desktop_file = dir + "/applications/" + strings.Join(splitFileId[0:i], "/") + "/" + strings.Join(splitFileId[i:], "-") - fileStat, _ = os.Stat(desktop_file) - if fileStat != nil { - break; - } - } - - if fileStat != nil { - break; - } - } + for _, dir := range strings.Split(os.Getenv("XDG_DATA_DIRS"), ":") { + var fileStat os.FileInfo + + for i := 0; i != len(splitFileId); i = i + 1 { + desktop_file = dir + "/applications/" + strings.Join(splitFileId[0:i], "/") + "/" + strings.Join(splitFileId[i:], "-") + fileStat, _ = os.Stat(desktop_file) + if fileStat != nil { + break + } + } + + if fileStat != nil { + break + } + } - return desktop_file + return desktop_file } // readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. func (s *Launcher) readExecCommandFromDesktopFile(desktop_file string) (string, error) { - var launch string + var launch string - file, err := os.Open(desktop_file) + file, err := os.Open(desktop_file) if err != nil { return launch, err } - defer file.Close() - reader := bufio.NewReader(file) + defer file.Close() + reader := bufio.NewReader(file) - for { - line, err := reader.ReadString('\n') - if err != nil { - return launch, err - } + for { + line, err := reader.ReadString('\n') + if err != nil { + return launch, err + } - line = strings.TrimSpace(line) + line = strings.TrimSpace(line) - if strings.HasPrefix(line, "Exec=") { - launch = strings.TrimPrefix(line, "Exec=") - break; - } - } + if strings.HasPrefix(line, "Exec=") { + launch = strings.TrimPrefix(line, "Exec=") + break + } + } - return launch, nil + return launch, nil } // fdToFilename determines the path associated with an open file descriptor. From 2bc59220a8c37f3619a2a85b4f9bcfd4507cde2c Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 11:51:09 +0200 Subject: [PATCH 008/114] More robust logic in readExecCommandFromDesktopFile() --- usersession/userd/launcher.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index cf047d668fe..a4af1c57245 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -200,6 +200,7 @@ func (s *Launcher) readExecCommandFromDesktopFile(desktop_file string) (string, defer file.Close() reader := bufio.NewReader(file) + in_desktop_section := false for { line, err := reader.ReadString('\n') if err != nil { @@ -208,7 +209,14 @@ func (s *Launcher) readExecCommandFromDesktopFile(desktop_file string) (string, line = strings.TrimSpace(line) - if strings.HasPrefix(line, "Exec=") { + if line == "[Desktop Entry]" { + in_desktop_section = true + } else if strings.HasPrefix(line, "[Desktop Action") { + // maybe later we'll add support here + in_desktop_section = false + } else if strings.HasPrefix(line, "[") { + in_desktop_section = false + } else if in_desktop_section && strings.HasPrefix(line, "Exec=") { launch = strings.TrimPrefix(line, "Exec=") break } From 71d2123164c37df36c4205bddcb7d98f327f6d8f Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 12:27:18 +0200 Subject: [PATCH 009/114] Document the processing of the exec command --- usersession/userd/launcher.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index a4af1c57245..97fc0e444d5 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -142,15 +142,20 @@ func (s *Launcher) OpenDesktopEntry(desktop_file_id string, sender dbus.Sender) // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' // DBus interface. func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - launch, err := s.readExecCommandFromDesktopFile(s.desktopFileIdToFilename(desktop_file_id)) + exec_command, err := s.readExecCommandFromDesktopFile(s.desktopFileIdToFilename(desktop_file_id)) if err != nil { return dbus.MakeFailedError(err) } - // This is very hacky parsing and doesn't cover a lot of cases - command := strings.Split(strings.SplitN(launch, "%", 2)[0], " ") - cmd := exec.Command(command[0], command[1:]...) + // Passing exec variables between confined snaps raises unanswered questions and they are not required + // for the simple cases. For now, we don't have support for passing them in the dbus API and truncate + // the exec command at the first exec variable. + // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables + exec_command = strings.SplitN(exec_command, "%", 2)[0] + + args := strings.Split(exec_command, " ") + cmd := exec.Command(args[0], args[1:]...) cmd.Env = os.Environ() for _, e := range env { @@ -158,7 +163,7 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen } if err := cmd.Start(); err != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot run %q", launch)) + return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) } return nil From b7de3288935006c156c39c81fa4e33ac952485f5 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 15:30:35 +0200 Subject: [PATCH 010/114] > Missing high-level test for interface 'shell-support'. Please add to: > * tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml --- tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml index 4082f326e4d..fbf7ee6d8cb 100644 --- a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml +++ b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml @@ -326,6 +326,9 @@ apps: screencast-legacy: command: bin/run plugs: [ screencast-legacy ] + shell-support: + command: bin/run + plugs: [ shell-support ] shutdown: command: bin/run plugs: [ shutdown ] From abfc59a3bf3a078cfdcb4bea4ba1f6a47c07f628 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 17:00:23 +0200 Subject: [PATCH 011/114] Handle shell quoting in the exec command --- usersession/userd/launcher.go | 9 +++++++-- vendor/vendor.json | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 97fc0e444d5..678273c699a 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -32,6 +32,8 @@ import ( "github.com/godbus/dbus" + "github.com/google/shlex" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/osutil/sys" @@ -154,9 +156,12 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables exec_command = strings.SplitN(exec_command, "%", 2)[0] - args := strings.Split(exec_command, " ") - cmd := exec.Command(args[0], args[1:]...) + args, err := shlex.Split(exec_command) + if err != nil { + return dbus.MakeFailedError(err) + } + cmd := exec.Command(args[0], args[1:]...) cmd.Env = os.Environ() for _, e := range env { cmd.Env = append(cmd.Env, e) diff --git a/vendor/vendor.json b/vendor/vendor.json index a847889f496..fb29ae1e51c 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -42,6 +42,12 @@ "revision": "97646858c46433e4afb3432ad28c12e968efa298", "revisionTime": "2017-08-22T15:24:03Z" }, + { + "checksumSHA1": "c0Z2sKLKi+IKRVzq0IzNvqvfCrQ=", + "path": "github.com/google/shlex", + "revision": "c34317bd91bf98fab745d77b03933cf8769299fe", + "revisionTime": "2018-11-06T13:46:48Z" + }, { "checksumSHA1": "2Iy16w6c+4oMD8TtobbO0qsCrDo=", "path": "github.com/gorilla/mux", From c48272ced5e3c697d9b26384f01f88823729706a Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 17:07:32 +0200 Subject: [PATCH 012/114] Drop the `OpenDesktopEntry()` method --- interfaces/builtin/shell_support.go | 2 +- usersession/userd/launcher.go | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/interfaces/builtin/shell_support.go b/interfaces/builtin/shell_support.go index b11823776c8..e6089404063 100644 --- a/interfaces/builtin/shell_support.go +++ b/interfaces/builtin/shell_support.go @@ -40,7 +40,7 @@ dbus (send) bus=session path=/io/snapcraft/Launcher interface=io.snapcraft.Launcher - member={OpenDesktopEntry,OpenDesktopEntryEnv} + member=OpenDesktopEntryEnv peer=(label=unconfined), ` diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 678273c699a..f9aff60876f 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -53,9 +53,6 @@ const launcherIntrospectionXML = ` - - - @@ -133,14 +130,6 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { return nil } -// OpenDesktopEntry implements the 'OpenDesktopEntry' method of the 'io.snapcraft.Launcher' -// DBus interface. Before the provided desktop_file is parsed it is validated against a list -// of allowed locations. -func (s *Launcher) OpenDesktopEntry(desktop_file_id string, sender dbus.Sender) *dbus.Error { - - return s.OpenDesktopEntryEnv(desktop_file_id, []string{}, sender) -} - // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' // DBus interface. func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { From 67d2f613b3828e7acd6de3df603eb1650070f897 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 17:24:59 +0200 Subject: [PATCH 013/114] Better handling of exec variables --- usersession/userd/launcher.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index f9aff60876f..6f9aff89e5e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -32,7 +32,7 @@ import ( "github.com/godbus/dbus" - "github.com/google/shlex" + "github.com/google/shlex" "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" @@ -139,17 +139,28 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen return dbus.MakeFailedError(err) } - // Passing exec variables between confined snaps raises unanswered questions and they are not required - // for the simple cases. For now, we don't have support for passing them in the dbus API and truncate - // the exec command at the first exec variable. - // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables - exec_command = strings.SplitN(exec_command, "%", 2)[0] - args, err := shlex.Split(exec_command) if err != nil { return dbus.MakeFailedError(err) } + // Passing exec variables between confined snaps raises unanswered questions and they are not required + // for the simple cases. For now, we don't have support for passing them in the dbus API and drop + // them from the command. + // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables + i := 0 + for { + if strings.HasPrefix(args[i], "%") { + args = append(args[:i], args[i+1:]...) + } else { + i++ + } + + if i == len(args) { + break + } + } + cmd := exec.Command(args[0], args[1:]...) cmd.Env = os.Environ() for _, e := range env { From e8dec70621e5831f84346f460ef03afd41aa850a Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 20:45:26 +0200 Subject: [PATCH 014/114] deny-auto-connection: true --- interfaces/builtin/shell_support.go | 1 + 1 file changed, 1 insertion(+) diff --git a/interfaces/builtin/shell_support.go b/interfaces/builtin/shell_support.go index e6089404063..39b4ed836b2 100644 --- a/interfaces/builtin/shell_support.go +++ b/interfaces/builtin/shell_support.go @@ -26,6 +26,7 @@ const shellSupportBaseDeclarationSlots = ` allow-installation: slot-snap-type: - core + deny-auto-connection: true ` const shellSupportConnectedPlugAppArmor = ` From b769b097dfb626be6f0103c908394c65955b42e2 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 21:07:47 +0200 Subject: [PATCH 015/114] Use the `MockConnectedPlug` and `MockConnectedSlot` helpers --- interfaces/builtin/shell_support_test.go | 25 ++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/interfaces/builtin/shell_support_test.go b/interfaces/builtin/shell_support_test.go index 2d07939a198..162682e9d84 100644 --- a/interfaces/builtin/shell_support_test.go +++ b/interfaces/builtin/shell_support_test.go @@ -26,7 +26,6 @@ import ( "github.com/snapcore/snapd/interfaces/apparmor" "github.com/snapcore/snapd/interfaces/builtin" "github.com/snapcore/snapd/snap" - "github.com/snapcore/snapd/snap/snaptest" "github.com/snapcore/snapd/testutil" ) @@ -42,23 +41,25 @@ var _ = Suite(&shellSupportSuite{ iface: builtin.MustInterface("shell-support"), }) -func (s *shellSupportSuite) SetUpTest(c *C) { - consumingSnapInfo := snaptest.MockInfo(c, ` +const shellSupportConsumerYaml = ` name: other version: 0 apps: app: command: foo plugs: [shell-support] -`, nil) - s.plugInfo = consumingSnapInfo.Plugs["shell-support"] - s.plug = interfaces.NewConnectedPlug(s.plugInfo, nil, nil) - s.slotInfo = &snap.SlotInfo{ - Snap: &snap.Info{SuggestedName: "core", SnapType: snap.TypeOS}, - Name: "shell-support", - Interface: "shell-support", - } - s.slot = interfaces.NewConnectedSlot(s.slotInfo, nil, nil) +` + +const shellSupportCoreYaml = `name: core +version: 0 +type: os +slots: + shell-support: +` + +func (s *shellSupportSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, shellSupportConsumerYaml, nil, "shell-support") + s.slot, s.slotInfo = MockConnectedSlot(c, shellSupportCoreYaml, nil, "shell-support") } func (s *shellSupportSuite) TestName(c *C) { From 23e7a899191b673908581bb775fa4b9f3c0f848c Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 19 Sep 2019 21:15:41 +0200 Subject: [PATCH 016/114] Use free functions --- usersession/userd/launcher.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 6f9aff89e5e..e6d9cc83653 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -133,7 +133,7 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' // DBus interface. func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - exec_command, err := s.readExecCommandFromDesktopFile(s.desktopFileIdToFilename(desktop_file_id)) + exec_command, err := readExecCommandFromDesktopFile(desktopFileIdToFilename(desktop_file_id)) if err != nil { return dbus.MakeFailedError(err) @@ -175,7 +175,7 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen } // desktopFileIdToFilename determines the path associated with a desktop file ID. -func (s *Launcher) desktopFileIdToFilename(desktop_file_id string) string { +func desktopFileIdToFilename(desktop_file_id string) string { splitFileId := strings.Split(desktop_file_id, "-") var desktop_file string @@ -200,7 +200,7 @@ func (s *Launcher) desktopFileIdToFilename(desktop_file_id string) string { } // readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. -func (s *Launcher) readExecCommandFromDesktopFile(desktop_file string) (string, error) { +func readExecCommandFromDesktopFile(desktop_file string) (string, error) { var launch string file, err := os.Open(desktop_file) From a11e9b45fc785544e67304af704eed3bd8ca1a83 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 20 Sep 2019 13:44:10 +0200 Subject: [PATCH 017/114] Rename `shell-support` => `app-launch` --- .../{shell_support.go => app_launch.go} | 16 +++++----- ...ell_support_test.go => app_launch_test.go} | 32 +++++++++---------- .../meta/snap.yaml | 6 ++-- 3 files changed, 27 insertions(+), 27 deletions(-) rename interfaces/builtin/{shell_support.go => app_launch.go} (76%) rename interfaces/builtin/{shell_support_test.go => app_launch_test.go} (69%) diff --git a/interfaces/builtin/shell_support.go b/interfaces/builtin/app_launch.go similarity index 76% rename from interfaces/builtin/shell_support.go rename to interfaces/builtin/app_launch.go index 39b4ed836b2..ad1207db7d0 100644 --- a/interfaces/builtin/shell_support.go +++ b/interfaces/builtin/app_launch.go @@ -19,17 +19,17 @@ package builtin -const shellSupportSummary = `allows shells to identify and launch other snaps` +const appLaunchSummary = `allows snaps to identify and launch other snaps` -const shellSupportBaseDeclarationSlots = ` - shell-support: +const appLaunchBaseDeclarationSlots = ` + app-launch: allow-installation: slot-snap-type: - core deny-auto-connection: true ` -const shellSupportConnectedPlugAppArmor = ` +const appLaunchConnectedPlugAppArmor = ` # Description: Can identify and launch other snaps. # Access to the desktop files installed by snaps @@ -47,12 +47,12 @@ dbus (send) func init() { registerIface(&commonInterface{ - name: "shell-support", - summary: shellSupportSummary, + name: "app-launch", + summary: appLaunchSummary, implicitOnCore: true, implicitOnClassic: true, - baseDeclarationSlots: shellSupportBaseDeclarationSlots, - connectedPlugAppArmor: shellSupportConnectedPlugAppArmor, + baseDeclarationSlots: appLaunchBaseDeclarationSlots, + connectedPlugAppArmor: appLaunchConnectedPlugAppArmor, reservedForOS: true, }) } diff --git a/interfaces/builtin/shell_support_test.go b/interfaces/builtin/app_launch_test.go similarity index 69% rename from interfaces/builtin/shell_support_test.go rename to interfaces/builtin/app_launch_test.go index 162682e9d84..7a1a768f78f 100644 --- a/interfaces/builtin/shell_support_test.go +++ b/interfaces/builtin/app_launch_test.go @@ -29,7 +29,7 @@ import ( "github.com/snapcore/snapd/testutil" ) -type shellSupportSuite struct { +type appLaunchSuite struct { iface interfaces.Interface slotInfo *snap.SlotInfo slot *interfaces.ConnectedSlot @@ -37,44 +37,44 @@ type shellSupportSuite struct { plug *interfaces.ConnectedPlug } -var _ = Suite(&shellSupportSuite{ - iface: builtin.MustInterface("shell-support"), +var _ = Suite(&appLaunchSuite{ + iface: builtin.MustInterface("app-launch"), }) -const shellSupportConsumerYaml = ` +const appLaunchConsumerYaml = ` name: other version: 0 apps: app: command: foo - plugs: [shell-support] + plugs: [app-launch] ` -const shellSupportCoreYaml = `name: core +const appLaunchCoreYaml = `name: core version: 0 type: os slots: - shell-support: + app-launch: ` -func (s *shellSupportSuite) SetUpTest(c *C) { - s.plug, s.plugInfo = MockConnectedPlug(c, shellSupportConsumerYaml, nil, "shell-support") - s.slot, s.slotInfo = MockConnectedSlot(c, shellSupportCoreYaml, nil, "shell-support") +func (s *appLaunchSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, appLaunchConsumerYaml, nil, "app-launch") + s.slot, s.slotInfo = MockConnectedSlot(c, appLaunchCoreYaml, nil, "app-launch") } -func (s *shellSupportSuite) TestName(c *C) { - c.Assert(s.iface.Name(), Equals, "shell-support") +func (s *appLaunchSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "app-launch") } -func (s *shellSupportSuite) TestSanitizeSlot(c *C) { +func (s *appLaunchSuite) TestSanitizeSlot(c *C) { c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } -func (s *shellSupportSuite) TestSanitizePlug(c *C) { +func (s *appLaunchSuite) TestSanitizePlug(c *C) { c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } -func (s *shellSupportSuite) TestConnectedPlugSnippet(c *C) { +func (s *appLaunchSuite) TestConnectedPlugSnippet(c *C) { apparmorSpec := &apparmor.Specification{} err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) @@ -82,6 +82,6 @@ func (s *shellSupportSuite) TestConnectedPlugSnippet(c *C) { c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `Can identify and launch other snaps.`) } -func (s *shellSupportSuite) TestInterfaces(c *C) { +func (s *appLaunchSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff --git a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml index fbf7ee6d8cb..e808c62c70e 100644 --- a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml +++ b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml @@ -14,6 +14,9 @@ apps: alsa: command: bin/run plugs: [ alsa ] + app-launch: + command: bin/run + plugs: [ app-launch ] audio-playback: command: bin/run plugs: [ audio-playback ] @@ -326,9 +329,6 @@ apps: screencast-legacy: command: bin/run plugs: [ screencast-legacy ] - shell-support: - command: bin/run - plugs: [ shell-support ] shutdown: command: bin/run plugs: [ shutdown ] From 2671228b98092ad3e5efe96e20bf218a317612da Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 20 Sep 2019 14:20:20 +0200 Subject: [PATCH 018/114] Report error if desktop file not found --- usersession/userd/launcher.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index e6d9cc83653..189c81bdf9c 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -133,7 +133,12 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' // DBus interface. func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - exec_command, err := readExecCommandFromDesktopFile(desktopFileIdToFilename(desktop_file_id)) + desktop_file, err := desktopFileIdToFilename(desktop_file_id) + if err != nil { + return dbus.MakeFailedError(err) + } + + exec_command, err := readExecCommandFromDesktopFile(desktop_file) if err != nil { return dbus.MakeFailedError(err) @@ -175,7 +180,7 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen } // desktopFileIdToFilename determines the path associated with a desktop file ID. -func desktopFileIdToFilename(desktop_file_id string) string { +func desktopFileIdToFilename(desktop_file_id string) (string, error) { splitFileId := strings.Split(desktop_file_id, "-") var desktop_file string @@ -187,7 +192,7 @@ func desktopFileIdToFilename(desktop_file_id string) string { desktop_file = dir + "/applications/" + strings.Join(splitFileId[0:i], "/") + "/" + strings.Join(splitFileId[i:], "-") fileStat, _ = os.Stat(desktop_file) if fileStat != nil { - break + return desktop_file, nil } } @@ -196,7 +201,7 @@ func desktopFileIdToFilename(desktop_file_id string) string { } } - return desktop_file + return "", fmt.Errorf("cannot find desktop file for %q", desktop_file_id) } // readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. From 16f03b040f2fbebcc76ba4518ca421bfeff4ec13 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 20 Sep 2019 19:21:10 +0200 Subject: [PATCH 019/114] Search all the paths that can be formed by the desktop ID --- usersession/userd/launcher.go | 40 ++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 189c81bdf9c..8eb6c19883f 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -179,26 +179,36 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen return nil } +// findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. +func findDesktopFile(base_dir string, splitFileId []string) (string, error) { + desktop_file := filepath.Join(base_dir, strings.Join(splitFileId, "-")) + fileStat, _ := os.Stat(desktop_file) + + if fileStat != nil { + return desktop_file, nil + } + + for i := 1; i != len(splitFileId)-1; i++ { + desktop_file, err := findDesktopFile(filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + if err == nil { + return desktop_file, nil + } + } + + return "", fmt.Errorf("cannot find desktop file") +} + // desktopFileIdToFilename determines the path associated with a desktop file ID. func desktopFileIdToFilename(desktop_file_id string) (string, error) { - splitFileId := strings.Split(desktop_file_id, "-") - var desktop_file string + // Currently the caller only has access to /var/lib/snapd/desktop/applications/, so we just look there + // and ignore https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + base_dir := "/var/lib/snapd/desktop/applications/" - for _, dir := range strings.Split(os.Getenv("XDG_DATA_DIRS"), ":") { - var fileStat os.FileInfo - - for i := 0; i != len(splitFileId); i = i + 1 { - desktop_file = dir + "/applications/" + strings.Join(splitFileId[0:i], "/") + "/" + strings.Join(splitFileId[i:], "-") - fileStat, _ = os.Stat(desktop_file) - if fileStat != nil { - return desktop_file, nil - } - } + desktop_file, err := findDesktopFile(base_dir, strings.Split(desktop_file_id, "-")) - if fileStat != nil { - break - } + if err == nil { + return desktop_file, nil } return "", fmt.Errorf("cannot find desktop file for %q", desktop_file_id) From 00cc0f87d56c430f428e075f1c75da4cf5b854d7 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 23 Sep 2019 09:53:17 +0100 Subject: [PATCH 020/114] We don't need github.com/google/shlex, we have github.com/snapcore/snapd/strutil/shlex --- usersession/userd/launcher.go | 3 +-- vendor/vendor.json | 6 ------ 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 8eb6c19883f..84ccb6380e8 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -32,12 +32,11 @@ import ( "github.com/godbus/dbus" - "github.com/google/shlex" - "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/strutil" + "github.com/snapcore/snapd/strutil/shlex" "github.com/snapcore/snapd/usersession/userd/ui" ) diff --git a/vendor/vendor.json b/vendor/vendor.json index fb29ae1e51c..a847889f496 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -42,12 +42,6 @@ "revision": "97646858c46433e4afb3432ad28c12e968efa298", "revisionTime": "2017-08-22T15:24:03Z" }, - { - "checksumSHA1": "c0Z2sKLKi+IKRVzq0IzNvqvfCrQ=", - "path": "github.com/google/shlex", - "revision": "c34317bd91bf98fab745d77b03933cf8769299fe", - "revisionTime": "2018-11-06T13:46:48Z" - }, { "checksumSHA1": "2Iy16w6c+4oMD8TtobbO0qsCrDo=", "path": "github.com/gorilla/mux", From f1aeab9c166c9218a6b5b8e4646ef3a4ca340d44 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 23 Sep 2019 10:31:35 +0100 Subject: [PATCH 021/114] Don't use error to indicate whether a desktop file is found --- usersession/userd/launcher.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 84ccb6380e8..8ecaea2956e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -179,22 +179,22 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen } // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. -func findDesktopFile(base_dir string, splitFileId []string) (string, error) { +func findDesktopFile(base_dir string, splitFileId []string) *string { desktop_file := filepath.Join(base_dir, strings.Join(splitFileId, "-")) fileStat, _ := os.Stat(desktop_file) if fileStat != nil { - return desktop_file, nil + return &desktop_file } for i := 1; i != len(splitFileId)-1; i++ { - desktop_file, err := findDesktopFile(filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) - if err == nil { - return desktop_file, nil + desktop_file := findDesktopFile(filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + if desktop_file != nil { + return desktop_file } } - return "", fmt.Errorf("cannot find desktop file") + return nil } // desktopFileIdToFilename determines the path associated with a desktop file ID. @@ -204,10 +204,10 @@ func desktopFileIdToFilename(desktop_file_id string) (string, error) { // and ignore https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html base_dir := "/var/lib/snapd/desktop/applications/" - desktop_file, err := findDesktopFile(base_dir, strings.Split(desktop_file_id, "-")) + desktop_file := findDesktopFile(base_dir, strings.Split(desktop_file_id, "-")) - if err == nil { - return desktop_file, nil + if desktop_file != nil { + return *desktop_file, nil } return "", fmt.Errorf("cannot find desktop file for %q", desktop_file_id) From 196ad63537c43de9da8d18dbb42bd3a51983f30a Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 23 Sep 2019 11:34:06 +0100 Subject: [PATCH 022/114] Update comments referring to desktop-entry-spec-latest.html --- usersession/userd/launcher.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 8ecaea2956e..7f509c8c27f 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -130,7 +130,8 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { } // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' -// DBus interface. +// DBus interface. The desktop_file_id is described here: +// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { desktop_file, err := desktopFileIdToFilename(desktop_file_id) if err != nil { @@ -148,13 +149,13 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen return dbus.MakeFailedError(err) } - // Passing exec variables between confined snaps raises unanswered questions and they are not required - // for the simple cases. For now, we don't have support for passing them in the dbus API and drop - // them from the command. - // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables i := 0 for { if strings.HasPrefix(args[i], "%") { + // Passing exec variables between confined snaps raises unanswered questions and they are not required + // for the simple cases. For now, we don't have support for passing them in the dbus API and drop + // them from the command. + // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables args = append(args[:i], args[i+1:]...) } else { i++ From 09f9474bdc7fa2bf3c8eac5ecac8a1a136e04f86 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 23 Sep 2019 12:13:47 +0100 Subject: [PATCH 023/114] Don't ignore errors from os.Stat() --- usersession/userd/launcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 7f509c8c27f..11508d5963a 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -182,9 +182,9 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. func findDesktopFile(base_dir string, splitFileId []string) *string { desktop_file := filepath.Join(base_dir, strings.Join(splitFileId, "-")) - fileStat, _ := os.Stat(desktop_file) + fileStat, err := os.Stat(desktop_file) - if fileStat != nil { + if err == nil && !fileStat.IsDir() { return &desktop_file } From fca6562676fbec94d5aefe160f66d8ff5fa83ff7 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 23 Sep 2019 15:32:21 +0100 Subject: [PATCH 024/114] Restrict the environment variables that may be set to those used to describe the shell to toolkits. --- usersession/userd/launcher.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 11508d5963a..3ac1aa79dc7 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -64,6 +64,7 @@ const launcherIntrospectionXML = ` var ( allowedURLSchemes = []string{"http", "https", "mailto", "snap", "help", "apt", "zoommtg"} + allowedEnvVars = []string{"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", "XDG_SESSION_DESKTOP", "XDG_SESSION_TYPE"} ) // Launcher implements the 'io.snapcraft.Launcher' DBus interface. @@ -169,6 +170,10 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen cmd := exec.Command(args[0], args[1:]...) cmd.Env = os.Environ() for _, e := range env { + if !strutil.ListContains(allowedEnvVars, strings.SplitN(e, "=", 2)[0]) { + return dbus.MakeFailedError(fmt.Errorf("Supplied environment variable %q is not allowed", e)) + } + cmd.Env = append(cmd.Env, e) } From 848a73e52b3d5396ec632211b85cd74703fc9892 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 23 Sep 2019 15:53:51 +0100 Subject: [PATCH 025/114] Comment to explain the code --- usersession/userd/launcher.go | 1 + 1 file changed, 1 insertion(+) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 3ac1aa79dc7..2b27bafc0db 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -193,6 +193,7 @@ func findDesktopFile(base_dir string, splitFileId []string) *string { return &desktop_file } + // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID for i := 1; i != len(splitFileId)-1; i++ { desktop_file := findDesktopFile(filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) if desktop_file != nil { From cca474e837571a1a151b724e2588d21692c49cbe Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 23 Sep 2019 16:02:43 +0100 Subject: [PATCH 026/114] Use dirs.SnapDesktopFilesDir, not a hard coded path --- usersession/userd/launcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 2b27bafc0db..25529fe9b26 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -209,7 +209,7 @@ func desktopFileIdToFilename(desktop_file_id string) (string, error) { // Currently the caller only has access to /var/lib/snapd/desktop/applications/, so we just look there // and ignore https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - base_dir := "/var/lib/snapd/desktop/applications/" + base_dir := dirs.SnapDesktopFilesDir desktop_file := findDesktopFile(base_dir, strings.Split(desktop_file_id, "-")) From a7f75eec28b3b3baa53a095824b890f2155bbdf2 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 25 Sep 2019 15:29:02 +0100 Subject: [PATCH 027/114] First cut at some internal tests --- usersession/userd/launcher.go | 25 ++++--- usersession/userd/launcher_internal_test.go | 82 +++++++++++++++++++++ 2 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 usersession/userd/launcher_internal_test.go diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 25529fe9b26..f32d6f5fec2 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -134,7 +134,7 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { // DBus interface. The desktop_file_id is described here: // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - desktop_file, err := desktopFileIdToFilename(desktop_file_id) + desktop_file, err := desktopFileIdToFilename(existsOnFileSystem, desktop_file_id) if err != nil { return dbus.MakeFailedError(err) } @@ -184,18 +184,25 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen return nil } +type fileExists func(string) bool + +func existsOnFileSystem(desktop_file string) bool { + fileStat, err := os.Stat(desktop_file) + + return err == nil && !fileStat.IsDir() +} + // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. -func findDesktopFile(base_dir string, splitFileId []string) *string { +func findDesktopFile(desktop_file_exists fileExists, base_dir string, splitFileId []string) *string { desktop_file := filepath.Join(base_dir, strings.Join(splitFileId, "-")) - fileStat, err := os.Stat(desktop_file) - if err == nil && !fileStat.IsDir() { + if desktop_file_exists(desktop_file) { return &desktop_file } - // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID - for i := 1; i != len(splitFileId)-1; i++ { - desktop_file := findDesktopFile(filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID + for i := 1; i != len(splitFileId); i++ { + desktop_file := findDesktopFile(desktop_file_exists, filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) if desktop_file != nil { return desktop_file } @@ -205,13 +212,13 @@ func findDesktopFile(base_dir string, splitFileId []string) *string { } // desktopFileIdToFilename determines the path associated with a desktop file ID. -func desktopFileIdToFilename(desktop_file_id string) (string, error) { +func desktopFileIdToFilename(desktop_file_exists fileExists, desktop_file_id string) (string, error) { // Currently the caller only has access to /var/lib/snapd/desktop/applications/, so we just look there // and ignore https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html base_dir := dirs.SnapDesktopFilesDir - desktop_file := findDesktopFile(base_dir, strings.Split(desktop_file_id, "-")) + desktop_file := findDesktopFile(desktop_file_exists, base_dir, strings.Split(desktop_file_id, "-")) if desktop_file != nil { return *desktop_file, nil diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go new file mode 100644 index 00000000000..caee99c39e0 --- /dev/null +++ b/usersession/userd/launcher_internal_test.go @@ -0,0 +1,82 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 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 . + * + */ + +package userd + +import ( + "github.com/snapcore/snapd/strutil" + "testing" +) + +var mockFileSystem = []string{ + "/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop", + "/var/lib/snapd/desktop/applications/multipass_multipass-gui.desktop", + "/var/lib/snapd/desktop/applications/cevelop_cevelop.desktop", + "/var/lib/snapd/desktop/applications/egmde-confined-desktop_egmde-confined-desktop.desktop", + "/var/lib/snapd/desktop/applications/classic-snap-analyzer_classic-snap-analyzer.desktop", + "/var/lib/snapd/desktop/applications/vlc_vlc.desktop", + "/var/lib/snapd/desktop/applications/gnome-calculator_gnome-calculator.desktop", + "/var/lib/snapd/desktop/applications/mir-kiosk-kodi_mir-kiosk-kodi.desktop", + "/var/lib/snapd/desktop/applications/gnome-characters_gnome-characters.desktop", + "/var/lib/snapd/desktop/applications/clion_clion.desktop", + "/var/lib/snapd/desktop/applications/gnome-system-monitor_gnome-system-monitor.desktop", + "/var/lib/snapd/desktop/applications/inkscape_inkscape.desktop", + "/var/lib/snapd/desktop/applications/gnome-logs_gnome-logs.desktop", + + "/var/lib/snapd/desktop/applications/foo-bar/baz.desktop", + "/var/lib/snapd/desktop/applications/baz/foo-bar.desktop", +} + +func existsOnMockFileSystem(desktop_file string) bool { + return strutil.ListContains(mockFileSystem, desktop_file) +} + +func TestLauncher_desktopFileIdToFilenameSucceedsWithValidId(t *testing.T) { + var desktopIdTests = []struct { + id string + expect string + }{ + {"mir-kiosk-scummvm_mir-kiosk-scummvm.desktop", "/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop"}, + {"foo-bar-baz.desktop", "/var/lib/snapd/desktop/applications/foo-bar/baz.desktop"}, + {"baz-foo-bar.desktop", "/var/lib/snapd/desktop/applications/baz/foo-bar.desktop"}, + } + + for _, test := range desktopIdTests { + actual, _ := desktopFileIdToFilename(existsOnMockFileSystem, test.id) + if actual != test.expect { + t.Errorf("desktopFileIdToFilename(%s): expected %s, actual %s", test.id, test.expect, actual) + } + } +} + + +func TestLauncher_desktopFileIdToFilenameFailsWithInvalidId(t *testing.T) { + var desktopIdTests = []string{ + "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", + "bar-foo-baz.desktop", + "bar-baz-foo.desktop", + } + + for _, id := range desktopIdTests { + actual, err := desktopFileIdToFilename(existsOnMockFileSystem, id) + if err == nil { + t.Errorf("desktopFileIdToFilename(%s): expected , actual %s", id, actual) + } + } +} From 5dd7228817179de518796bfa5383b499b606b874 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 25 Sep 2019 17:01:06 +0100 Subject: [PATCH 028/114] Test parsing of Exec command --- usersession/userd/launcher.go | 47 +++++++++++++-------- usersession/userd/launcher_internal_test.go | 39 ++++++++++++++++- 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index f32d6f5fec2..11912b2efd4 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -145,28 +145,11 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen return dbus.MakeFailedError(err) } - args, err := shlex.Split(exec_command) + args, err := parseExecCommand(exec_command) if err != nil { return dbus.MakeFailedError(err) } - i := 0 - for { - if strings.HasPrefix(args[i], "%") { - // Passing exec variables between confined snaps raises unanswered questions and they are not required - // for the simple cases. For now, we don't have support for passing them in the dbus API and drop - // them from the command. - // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables - args = append(args[:i], args[i+1:]...) - } else { - i++ - } - - if i == len(args) { - break - } - } - cmd := exec.Command(args[0], args[1:]...) cmd.Env = os.Environ() for _, e := range env { @@ -263,6 +246,34 @@ func readExecCommandFromDesktopFile(desktop_file string) (string, error) { return launch, nil } +func parseExecCommand(exec_command string) ([]string, error) { + args, err := shlex.Split(exec_command) + if err != nil { + return []string{}, err + } + + i := 0 + for { + if strings.HasPrefix(args[i], "%%") { + args[i] = strings.TrimPrefix(args[i], "%") + i++ + } else if strings.HasPrefix(args[i], "%") { + // Passing exec variables between confined snaps raises unanswered questions and they are not required + // for the simple cases. For now, we don't have support for passing them in the dbus API and drop + // them from the command. + // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables + args = append(args[:i], args[i+1:]...) + } else { + i++ + } + + if i == len(args) { + break + } + } + return args, nil +} + // fdToFilename determines the path associated with an open file descriptor. // // The file descriptor cannot be opened using O_PATH and must refer to diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index caee99c39e0..2f53ed6ff43 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -21,6 +21,8 @@ package userd import ( "github.com/snapcore/snapd/strutil" + "reflect" + "strings" "testing" ) @@ -65,7 +67,6 @@ func TestLauncher_desktopFileIdToFilenameSucceedsWithValidId(t *testing.T) { } } - func TestLauncher_desktopFileIdToFilenameFailsWithInvalidId(t *testing.T) { var desktopIdTests = []string{ "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", @@ -80,3 +81,39 @@ func TestLauncher_desktopFileIdToFilenameFailsWithInvalidId(t *testing.T) { } } } + +func TestLauncher_parseExecCommandSucceedsWithValidEntry(t *testing.T) { + var exec_command = []struct { + exec_command string + expect []string + }{ + {"env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop /snap/bin/mir-kiosk-scummvm %U", + []string{"env", "BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop", "/snap/bin/mir-kiosk-scummvm"}}, + {"/snap/bin/foo -f %U %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo '-f %U %%bar'", []string{"/snap/bin/foo", "-f %U %%bar"}}, + {"/snap/bin/foo \"'-f bar'\"", []string{"/snap/bin/foo", "'-f bar'"}}, + } + + for _, test := range exec_command { + actual, err := parseExecCommand(test.exec_command) + if err != nil { + t.Errorf("parseExecCommand(\"%s\"): expected SUCCESS, actual FAILED %e", test.exec_command, err) + } else if !reflect.DeepEqual(actual, test.expect) { + t.Errorf("parseExecCommand(\"%s\"): expected {\"%s\"}, actual {\"%s\"}", test.exec_command, strings.Join(test.expect, "\", \""), strings.Join(actual, "\", \"")) + } + } +} + +func TestLauncher_parseExecCommandFailsWithInvalidEntry(t *testing.T) { + var exec_command = []string{ + "/snap/bin/foo \"unclosed double quote", + "/snap/bin/foo 'unclosed single quote", + } + + for _, test := range exec_command { + actual, err := parseExecCommand(test) + if err == nil { + t.Errorf("parseExecCommand(\"%s\"): expected FAILED, actual {\"%s\"}", test, strings.Join(actual, "\", \"")) + } + } +} From 2567e93af779ed903e166d14ca920f7783bae3fb Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 26 Sep 2019 14:31:48 +0100 Subject: [PATCH 029/114] Use the shell to launch the app to avoid becoming a parent and/or leaving a zombie process --- usersession/userd/launcher.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 11912b2efd4..708d8027dc5 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -140,7 +140,6 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen } exec_command, err := readExecCommandFromDesktopFile(desktop_file) - if err != nil { return dbus.MakeFailedError(err) } @@ -150,7 +149,15 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen return dbus.MakeFailedError(err) } - cmd := exec.Command(args[0], args[1:]...) + // Before rejoining args to create a new command-line: escape "\" escapes; escape "\"" quotes; and, wrap each arg in "\"" quotes + for i := 0; i != len(args); i++ { + args[i] = strings.ReplaceAll(args[i], "\\", "\\\\") + args[i] = strings.ReplaceAll(args[i], "\"", "\\\"") + args[i] = "\"" + args[i] + "\"" + } + + // Invoke indirectly via sh to unparent and avoid potentially leaving a zombie + cmd := exec.Command("sh", "-c", strings.Join(args, " ")+"&") cmd.Env = os.Environ() for _, e := range env { if !strutil.ListContains(allowedEnvVars, strings.SplitN(e, "=", 2)[0]) { @@ -160,7 +167,7 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen cmd.Env = append(cmd.Env, e) } - if err := cmd.Start(); err != nil { + if cmd.Run() != nil { return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) } From a3c1ed29283073466c7eae3be38f444a92c8d7d7 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 26 Sep 2019 17:09:26 +0100 Subject: [PATCH 030/114] Fix "usersession/userd/launcher.go:154:13: undefined: strings.ReplaceAll" in CI --- usersession/userd/launcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 708d8027dc5..6a563243951 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -151,8 +151,8 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen // Before rejoining args to create a new command-line: escape "\" escapes; escape "\"" quotes; and, wrap each arg in "\"" quotes for i := 0; i != len(args); i++ { - args[i] = strings.ReplaceAll(args[i], "\\", "\\\\") - args[i] = strings.ReplaceAll(args[i], "\"", "\\\"") + args[i] = strings.Replace(args[i], "\\", "\\\\", -1) + args[i] = strings.Replace(args[i], "\"", "\\\"", -1) args[i] = "\"" + args[i] + "\"" } From c7534e83b0217420334495a94277690c4a6814be Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 2 Oct 2019 17:02:01 +0100 Subject: [PATCH 031/114] Drop implicitOnCore as it isn't supportable (yet) --- interfaces/builtin/app_launch.go | 1 - 1 file changed, 1 deletion(-) diff --git a/interfaces/builtin/app_launch.go b/interfaces/builtin/app_launch.go index ad1207db7d0..6f4d3c99309 100644 --- a/interfaces/builtin/app_launch.go +++ b/interfaces/builtin/app_launch.go @@ -49,7 +49,6 @@ func init() { registerIface(&commonInterface{ name: "app-launch", summary: appLaunchSummary, - implicitOnCore: true, implicitOnClassic: true, baseDeclarationSlots: appLaunchBaseDeclarationSlots, connectedPlugAppArmor: appLaunchConnectedPlugAppArmor, From e6997dc10fc436d3963f8eb77151042b38064bf3 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 18 May 2020 17:35:45 +0100 Subject: [PATCH 032/114] Remove "unknown field 'reservedForOS' in struct literal of type commonInterface" --- interfaces/builtin/app_launch.go | 1 - 1 file changed, 1 deletion(-) diff --git a/interfaces/builtin/app_launch.go b/interfaces/builtin/app_launch.go index 6f4d3c99309..27c868fa2f4 100644 --- a/interfaces/builtin/app_launch.go +++ b/interfaces/builtin/app_launch.go @@ -52,6 +52,5 @@ func init() { implicitOnClassic: true, baseDeclarationSlots: appLaunchBaseDeclarationSlots, connectedPlugAppArmor: appLaunchConnectedPlugAppArmor, - reservedForOS: true, }) } From b2567ff0237ec663293f7fcf13ac8bb8ca1c030d Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 19 May 2020 16:29:34 +0100 Subject: [PATCH 033/114] Rename `app-launch` => `desktop-launch` --- .../{app_launch.go => desktop_launch.go} | 16 +++++----- ..._launch_test.go => desktop_launch_test.go} | 32 +++++++++---------- .../meta/snap.yaml | 6 ++-- 3 files changed, 27 insertions(+), 27 deletions(-) rename interfaces/builtin/{app_launch.go => desktop_launch.go} (75%) rename interfaces/builtin/{app_launch_test.go => desktop_launch_test.go} (68%) diff --git a/interfaces/builtin/app_launch.go b/interfaces/builtin/desktop_launch.go similarity index 75% rename from interfaces/builtin/app_launch.go rename to interfaces/builtin/desktop_launch.go index 27c868fa2f4..8568aa4b96d 100644 --- a/interfaces/builtin/app_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -19,17 +19,17 @@ package builtin -const appLaunchSummary = `allows snaps to identify and launch other snaps` +const desktopLaunchSummary = `allows snaps to identify and launch other snaps` -const appLaunchBaseDeclarationSlots = ` - app-launch: +const desktopLaunchBaseDeclarationSlots = ` + desktop-launch: allow-installation: slot-snap-type: - core deny-auto-connection: true ` -const appLaunchConnectedPlugAppArmor = ` +const desktopLaunchConnectedPlugAppArmor = ` # Description: Can identify and launch other snaps. # Access to the desktop files installed by snaps @@ -47,10 +47,10 @@ dbus (send) func init() { registerIface(&commonInterface{ - name: "app-launch", - summary: appLaunchSummary, + name: "desktop-launch", + summary: desktopLaunchSummary, implicitOnClassic: true, - baseDeclarationSlots: appLaunchBaseDeclarationSlots, - connectedPlugAppArmor: appLaunchConnectedPlugAppArmor, + baseDeclarationSlots: desktopLaunchBaseDeclarationSlots, + connectedPlugAppArmor: desktopLaunchConnectedPlugAppArmor, }) } diff --git a/interfaces/builtin/app_launch_test.go b/interfaces/builtin/desktop_launch_test.go similarity index 68% rename from interfaces/builtin/app_launch_test.go rename to interfaces/builtin/desktop_launch_test.go index 7a1a768f78f..f2a71eacff2 100644 --- a/interfaces/builtin/app_launch_test.go +++ b/interfaces/builtin/desktop_launch_test.go @@ -29,7 +29,7 @@ import ( "github.com/snapcore/snapd/testutil" ) -type appLaunchSuite struct { +type desktopLaunchSuite struct { iface interfaces.Interface slotInfo *snap.SlotInfo slot *interfaces.ConnectedSlot @@ -37,44 +37,44 @@ type appLaunchSuite struct { plug *interfaces.ConnectedPlug } -var _ = Suite(&appLaunchSuite{ - iface: builtin.MustInterface("app-launch"), +var _ = Suite(&desktopLaunchSuite{ + iface: builtin.MustInterface("desktop-launch"), }) -const appLaunchConsumerYaml = ` +const desktopLaunchConsumerYaml = ` name: other version: 0 apps: app: command: foo - plugs: [app-launch] + plugs: [desktop-launch] ` -const appLaunchCoreYaml = `name: core +const desktopLaunchCoreYaml = `name: core version: 0 type: os slots: - app-launch: + desktop-launch: ` -func (s *appLaunchSuite) SetUpTest(c *C) { - s.plug, s.plugInfo = MockConnectedPlug(c, appLaunchConsumerYaml, nil, "app-launch") - s.slot, s.slotInfo = MockConnectedSlot(c, appLaunchCoreYaml, nil, "app-launch") +func (s *desktopLaunchSuite) SetUpTest(c *C) { + s.plug, s.plugInfo = MockConnectedPlug(c, desktopLaunchConsumerYaml, nil, "desktop-launch") + s.slot, s.slotInfo = MockConnectedSlot(c, desktopLaunchCoreYaml, nil, "desktop-launch") } -func (s *appLaunchSuite) TestName(c *C) { - c.Assert(s.iface.Name(), Equals, "app-launch") +func (s *desktopLaunchSuite) TestName(c *C) { + c.Assert(s.iface.Name(), Equals, "desktop-launch") } -func (s *appLaunchSuite) TestSanitizeSlot(c *C) { +func (s *desktopLaunchSuite) TestSanitizeSlot(c *C) { c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) } -func (s *appLaunchSuite) TestSanitizePlug(c *C) { +func (s *desktopLaunchSuite) TestSanitizePlug(c *C) { c.Assert(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) } -func (s *appLaunchSuite) TestConnectedPlugSnippet(c *C) { +func (s *desktopLaunchSuite) TestConnectedPlugSnippet(c *C) { apparmorSpec := &apparmor.Specification{} err := apparmorSpec.AddConnectedPlug(s.iface, s.plug, s.slot) c.Assert(err, IsNil) @@ -82,6 +82,6 @@ func (s *appLaunchSuite) TestConnectedPlugSnippet(c *C) { c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `Can identify and launch other snaps.`) } -func (s *appLaunchSuite) TestInterfaces(c *C) { +func (s *desktopLaunchSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } diff --git a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml index e808c62c70e..a3e85786613 100644 --- a/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml +++ b/tests/lib/snaps/test-snapd-policy-app-consumer/meta/snap.yaml @@ -14,9 +14,6 @@ apps: alsa: command: bin/run plugs: [ alsa ] - app-launch: - command: bin/run - plugs: [ app-launch ] audio-playback: command: bin/run plugs: [ audio-playback ] @@ -92,6 +89,9 @@ apps: desktop: command: bin/run plugs: [ desktop ] + desktop-launch: + command: bin/run + plugs: [ desktop-launch ] desktop-legacy: command: bin/run plugs: [ desktop-legacy ] From 6547512d88f48b0d0341d8e2f4e9cfffc0409b14 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 28 May 2020 09:53:50 +0100 Subject: [PATCH 034/114] Renames to conform to convention --- usersession/userd/launcher.go | 44 +++++++++++++++++------------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 6a563243951..4850fb28f9b 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -53,7 +53,7 @@ const launcherIntrospectionXML = ` - + @@ -131,15 +131,15 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { } // OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' -// DBus interface. The desktop_file_id is described here: +// DBus interface. The desktopFileID is described here: // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id -func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sender dbus.Sender) *dbus.Error { - desktop_file, err := desktopFileIdToFilename(existsOnFileSystem, desktop_file_id) +func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sender dbus.Sender) *dbus.Error { + desktopFile, err := desktopFileIDToFilename(existsOnFileSystem, desktopFileID) if err != nil { return dbus.MakeFailedError(err) } - exec_command, err := readExecCommandFromDesktopFile(desktop_file) + exec_command, err := readExecCommandFromDesktopFile(desktopFile) if err != nil { return dbus.MakeFailedError(err) } @@ -176,52 +176,52 @@ func (s *Launcher) OpenDesktopEntryEnv(desktop_file_id string, env []string, sen type fileExists func(string) bool -func existsOnFileSystem(desktop_file string) bool { - fileStat, err := os.Stat(desktop_file) +func existsOnFileSystem(desktopFile string) bool { + fileStat, err := os.Stat(desktopFile) return err == nil && !fileStat.IsDir() } // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. -func findDesktopFile(desktop_file_exists fileExists, base_dir string, splitFileId []string) *string { - desktop_file := filepath.Join(base_dir, strings.Join(splitFileId, "-")) +func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId []string) *string { + desktopFile := filepath.Join(base_dir, strings.Join(splitFileId, "-")) - if desktop_file_exists(desktop_file) { - return &desktop_file + if desktopFile_exists(desktopFile) { + return &desktopFile } // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID for i := 1; i != len(splitFileId); i++ { - desktop_file := findDesktopFile(desktop_file_exists, filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) - if desktop_file != nil { - return desktop_file + desktopFile := findDesktopFile(desktopFile_exists, filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + if desktopFile != nil { + return desktopFile } } return nil } -// desktopFileIdToFilename determines the path associated with a desktop file ID. -func desktopFileIdToFilename(desktop_file_exists fileExists, desktop_file_id string) (string, error) { +// desktopFileIDToFilename determines the path associated with a desktop file ID. +func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string) (string, error) { // Currently the caller only has access to /var/lib/snapd/desktop/applications/, so we just look there // and ignore https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html base_dir := dirs.SnapDesktopFilesDir - desktop_file := findDesktopFile(desktop_file_exists, base_dir, strings.Split(desktop_file_id, "-")) + desktopFile := findDesktopFile(desktopFile_exists, base_dir, strings.Split(desktopFileID, "-")) - if desktop_file != nil { - return *desktop_file, nil + if desktopFile != nil { + return *desktopFile, nil } - return "", fmt.Errorf("cannot find desktop file for %q", desktop_file_id) + return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) } // readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. -func readExecCommandFromDesktopFile(desktop_file string) (string, error) { +func readExecCommandFromDesktopFile(desktopFile string) (string, error) { var launch string - file, err := os.Open(desktop_file) + file, err := os.Open(desktopFile) if err != nil { return launch, err } From 74c437dac743427100b3c22ce02477a2e25864a2 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 28 May 2020 09:56:29 +0100 Subject: [PATCH 035/114] Use a scanner instead of reading lines "by hand" --- usersession/userd/launcher.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 4850fb28f9b..acfda800c10 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -226,16 +226,11 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { return launch, err } defer file.Close() - reader := bufio.NewReader(file) + scanner := bufio.NewScanner(file) in_desktop_section := false - for { - line, err := reader.ReadString('\n') - if err != nil { - return launch, err - } - - line = strings.TrimSpace(line) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) if line == "[Desktop Entry]" { in_desktop_section = true From fda7fa043273a036a6009e86477c2c164e7d7fdf Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 28 May 2020 10:26:23 +0100 Subject: [PATCH 036/114] Use the language better --- usersession/userd/launcher.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index acfda800c10..5afaaa5b7a7 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -151,10 +151,10 @@ func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sende // Before rejoining args to create a new command-line: escape "\" escapes; escape "\"" quotes; and, wrap each arg in "\"" quotes for i := 0; i != len(args); i++ { - args[i] = strings.Replace(args[i], "\\", "\\\\", -1) - args[i] = strings.Replace(args[i], "\"", "\\\"", -1) - args[i] = "\"" + args[i] + "\"" - } + args[i] = strings.Replace(args[i], `\`, `\\`, -1) + args[i] = strings.Replace(args[i], `"`, `\"`, -1) + args[i] = `"` + args[i] + `"` + } // Invoke indirectly via sh to unparent and avoid potentially leaving a zombie cmd := exec.Command("sh", "-c", strings.Join(args, " ")+"&") From cd6378fa87ae1c77e11511e75fdb66e647db7861 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 28 May 2020 11:19:37 +0100 Subject: [PATCH 037/114] Update naming --- usersession/userd/launcher_internal_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index 2f53ed6ff43..176a022ea0f 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -49,7 +49,7 @@ func existsOnMockFileSystem(desktop_file string) bool { return strutil.ListContains(mockFileSystem, desktop_file) } -func TestLauncher_desktopFileIdToFilenameSucceedsWithValidId(t *testing.T) { +func TestLauncher_desktopFileIDToFilenameSucceedsWithValidId(t *testing.T) { var desktopIdTests = []struct { id string expect string @@ -60,14 +60,14 @@ func TestLauncher_desktopFileIdToFilenameSucceedsWithValidId(t *testing.T) { } for _, test := range desktopIdTests { - actual, _ := desktopFileIdToFilename(existsOnMockFileSystem, test.id) + actual, _ := desktopFileIDToFilename(existsOnMockFileSystem, test.id) if actual != test.expect { - t.Errorf("desktopFileIdToFilename(%s): expected %s, actual %s", test.id, test.expect, actual) + t.Errorf("desktopFileIDToFilename(%s): expected %s, actual %s", test.id, test.expect, actual) } } } -func TestLauncher_desktopFileIdToFilenameFailsWithInvalidId(t *testing.T) { +func TestLauncher_desktopFileIDToFilenameFailsWithInvalidId(t *testing.T) { var desktopIdTests = []string{ "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", "bar-foo-baz.desktop", @@ -75,9 +75,9 @@ func TestLauncher_desktopFileIdToFilenameFailsWithInvalidId(t *testing.T) { } for _, id := range desktopIdTests { - actual, err := desktopFileIdToFilename(existsOnMockFileSystem, id) + actual, err := desktopFileIDToFilename(existsOnMockFileSystem, id) if err == nil { - t.Errorf("desktopFileIdToFilename(%s): expected , actual %s", id, actual) + t.Errorf("desktopFileIDToFilename(%s): expected , actual %s", id, actual) } } } From f18aee7a3b3e30e33f1b089f21441de287350b37 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 28 May 2020 12:36:01 +0100 Subject: [PATCH 038/114] Use check.v1 --- usersession/userd/launcher_internal_test.go | 47 ++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index 176a022ea0f..fe333e46b18 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2019 Canonical Ltd + * Copyright (C) 2019-20 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 @@ -21,11 +21,17 @@ package userd import ( "github.com/snapcore/snapd/strutil" - "reflect" - "strings" "testing" + . "gopkg.in/check.v1" ) +func Test(t *testing.T) { TestingT(t) } + +type launcherInternalSuite struct { +} + +var _ = Suite(&launcherInternalSuite{}) + var mockFileSystem = []string{ "/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop", "/var/lib/snapd/desktop/applications/multipass_multipass-gui.desktop", @@ -49,7 +55,8 @@ func existsOnMockFileSystem(desktop_file string) bool { return strutil.ListContains(mockFileSystem, desktop_file) } -func TestLauncher_desktopFileIDToFilenameSucceedsWithValidId(t *testing.T) { +func (s *launcherInternalSuite) TestDesktopFileIDToFilenameSucceedsWithValidId(c *C) { + var desktopIdTests = []struct { id string expect string @@ -60,14 +67,13 @@ func TestLauncher_desktopFileIDToFilenameSucceedsWithValidId(t *testing.T) { } for _, test := range desktopIdTests { - actual, _ := desktopFileIDToFilename(existsOnMockFileSystem, test.id) - if actual != test.expect { - t.Errorf("desktopFileIDToFilename(%s): expected %s, actual %s", test.id, test.expect, actual) - } + actual, err := desktopFileIDToFilename(existsOnMockFileSystem, test.id) + c.Assert(err, IsNil) + c.Assert(actual, Equals, test.expect) } } -func TestLauncher_desktopFileIDToFilenameFailsWithInvalidId(t *testing.T) { +func (s *launcherInternalSuite) TestDesktopFileIDToFilenameFailsWithInvalidId(c *C) { var desktopIdTests = []string{ "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", "bar-foo-baz.desktop", @@ -75,14 +81,12 @@ func TestLauncher_desktopFileIDToFilenameFailsWithInvalidId(t *testing.T) { } for _, id := range desktopIdTests { - actual, err := desktopFileIDToFilename(existsOnMockFileSystem, id) - if err == nil { - t.Errorf("desktopFileIDToFilename(%s): expected , actual %s", id, actual) - } + _, err := desktopFileIDToFilename(existsOnMockFileSystem, id) + c.Assert(err, NotNil) } } -func TestLauncher_parseExecCommandSucceedsWithValidEntry(t *testing.T) { +func (s *launcherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) { var exec_command = []struct { exec_command string expect []string @@ -96,24 +100,19 @@ func TestLauncher_parseExecCommandSucceedsWithValidEntry(t *testing.T) { for _, test := range exec_command { actual, err := parseExecCommand(test.exec_command) - if err != nil { - t.Errorf("parseExecCommand(\"%s\"): expected SUCCESS, actual FAILED %e", test.exec_command, err) - } else if !reflect.DeepEqual(actual, test.expect) { - t.Errorf("parseExecCommand(\"%s\"): expected {\"%s\"}, actual {\"%s\"}", test.exec_command, strings.Join(test.expect, "\", \""), strings.Join(actual, "\", \"")) - } + c.Assert(err, IsNil) + c.Assert(actual, DeepEquals, test.expect) } } -func TestLauncher_parseExecCommandFailsWithInvalidEntry(t *testing.T) { +func (s *launcherInternalSuite) TestParseExecCommandFailsWithInvalidEntry(c *C) { var exec_command = []string{ "/snap/bin/foo \"unclosed double quote", "/snap/bin/foo 'unclosed single quote", } for _, test := range exec_command { - actual, err := parseExecCommand(test) - if err == nil { - t.Errorf("parseExecCommand(\"%s\"): expected FAILED, actual {\"%s\"}", test, strings.Join(actual, "\", \"")) - } + _, err := parseExecCommand(test) + c.Assert(err, NotNil) } } From 3a44c7f85ea899ceb628bbf48d41c6ab61b97c6b Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 1 Jun 2020 11:09:07 +0100 Subject: [PATCH 039/114] Less evil hack to avoid zombie processes --- usersession/userd/launcher.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 5afaaa5b7a7..7393c38e941 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -149,15 +149,7 @@ func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sende return dbus.MakeFailedError(err) } - // Before rejoining args to create a new command-line: escape "\" escapes; escape "\"" quotes; and, wrap each arg in "\"" quotes - for i := 0; i != len(args); i++ { - args[i] = strings.Replace(args[i], `\`, `\\`, -1) - args[i] = strings.Replace(args[i], `"`, `\"`, -1) - args[i] = `"` + args[i] + `"` - } - - // Invoke indirectly via sh to unparent and avoid potentially leaving a zombie - cmd := exec.Command("sh", "-c", strings.Join(args, " ")+"&") + cmd := exec.Command(args[0], args[1:]...) cmd.Env = os.Environ() for _, e := range env { if !strutil.ListContains(allowedEnvVars, strings.SplitN(e, "=", 2)[0]) { @@ -167,6 +159,11 @@ func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sende cmd.Env = append(cmd.Env, e) } + // XXX: this avoids defunct processes but causes userd to persist + // until all children are gone (currently, this is not a problem since + // userd is long running once started) + go cmd.Wait() + if cmd.Run() != nil { return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) } From 2d3b99a2f094ef5dd68092211d61581c017387ab Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 1 Jun 2020 11:31:07 +0100 Subject: [PATCH 040/114] Make interface superprivileged --- interfaces/builtin/desktop_launch.go | 7 +++++++ interfaces/policy/basedeclaration_test.go | 21 +++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index 8568aa4b96d..c49eed651b1 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -21,6 +21,12 @@ package builtin const desktopLaunchSummary = `allows snaps to identify and launch other snaps` +const desktopLaunchBaseDeclarationPlugs = ` + desktop-launch: + allow-installation: false + deny-auto-connection: true +` + const desktopLaunchBaseDeclarationSlots = ` desktop-launch: allow-installation: @@ -50,6 +56,7 @@ func init() { name: "desktop-launch", summary: desktopLaunchSummary, implicitOnClassic: true, + baseDeclarationPlugs: desktopLaunchBaseDeclarationPlugs, baseDeclarationSlots: desktopLaunchBaseDeclarationSlots, connectedPlugAppArmor: desktopLaunchConnectedPlugAppArmor, }) diff --git a/interfaces/policy/basedeclaration_test.go b/interfaces/policy/basedeclaration_test.go index 923e227f451..e3771e25711 100644 --- a/interfaces/policy/basedeclaration_test.go +++ b/interfaces/policy/basedeclaration_test.go @@ -597,6 +597,7 @@ var ( "core-support": {"core"}, "dbus": {"app"}, "docker-support": {"core"}, + "desktop-launch": {"core"}, "dummy": {"app"}, "fwupd": {"app", "core"}, "gpio": {"core", "gadget"}, @@ -700,6 +701,7 @@ func (s *baseDeclSuite) TestPlugInstallation(c *C) { restricted := map[string]bool{ "block-devices": true, "classic-support": true, + "desktop-launch": true, "docker-support": true, "greengrass-support": true, "gpio-control": true, @@ -833,6 +835,7 @@ func (s *baseDeclSuite) TestSanity(c *C) { "audio-playback": true, "classic-support": true, "core-support": true, + "desktop-launch": true, "docker-support": true, "greengrass-support": true, "gpio-control": true, @@ -1098,3 +1101,21 @@ plugs: c.Check(err, IsNil) c.Check(arity.SlotsPerPlugAny(), Equals, false) } + +func (s *baseDeclSuite) TestAutoConnectionDesktopLaunchOverride(c *C) { + cand := s.connectCand(c, "desktop-launch", "", "") + _, err := cand.CheckAutoConnect() + c.Check(err, NotNil) + c.Assert(err, ErrorMatches, "auto-connection denied by plug rule of interface \"desktop-launch\"") + + plugsSlots := ` +plugs: + desktop-launch: + allow-auto-connection: true +` + + snapDecl := s.mockSnapDecl(c, "some-snap", "some-snap-with-desktop-launch-id", "canonical", plugsSlots) + cand.PlugSnapDeclaration = snapDecl + _, err = cand.CheckAutoConnect() + c.Check(err, IsNil) +} From 000837fcf5752234e33d47b8a7192de6c98f3589 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 1 Jun 2020 11:38:45 +0100 Subject: [PATCH 041/114] Add TestStaticInfo() to interface tests --- interfaces/builtin/desktop_launch_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/interfaces/builtin/desktop_launch_test.go b/interfaces/builtin/desktop_launch_test.go index f2a71eacff2..75206e03c64 100644 --- a/interfaces/builtin/desktop_launch_test.go +++ b/interfaces/builtin/desktop_launch_test.go @@ -85,3 +85,12 @@ func (s *desktopLaunchSuite) TestConnectedPlugSnippet(c *C) { func (s *desktopLaunchSuite) TestInterfaces(c *C) { c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) } + +func (s *desktopLaunchSuite) TestStaticInfo(c *C) { + si := interfaces.StaticInfoOf(s.iface) + c.Assert(si.ImplicitOnCore, Equals, false) + c.Assert(si.ImplicitOnClassic, Equals, true) + c.Assert(si.Summary, Equals, `allows snaps to identify and launch other snaps`) + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "desktop-launch") + c.Assert(si.BaseDeclarationSlots, testutil.Contains, "deny-auto-connection: true") +} \ No newline at end of file From a6499d862145dcdd124f9679d08e6221ca4764e7 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 1 Jun 2020 12:27:30 +0100 Subject: [PATCH 042/114] Reworked comments and added sanity for review --- interfaces/builtin/desktop_launch.go | 1 + usersession/userd/launcher.go | 37 ++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index c49eed651b1..186bd9c0dbc 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -51,6 +51,7 @@ dbus (send) peer=(label=unconfined), ` +// Only implicitOnClassic since userd isn't yet usable on core func init() { registerIface(&commonInterface{ name: "desktop-launch", diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 7393c38e941..01b2baa6a4b 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -176,10 +176,14 @@ type fileExists func(string) bool func existsOnFileSystem(desktopFile string) bool { fileStat, err := os.Stat(desktopFile) - return err == nil && !fileStat.IsDir() + // We only support files in /var/lib/snapd/desktop/applications and they should + // all be regular files. + return err == nil && fileStat.Mode().IsRegular() } // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. +// We're not required to diagnose multiple files matching the desktop file ID. +// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId []string) *string { desktopFile := filepath.Join(base_dir, strings.Join(splitFileId, "-")) @@ -201,8 +205,14 @@ func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId // desktopFileIDToFilename determines the path associated with a desktop file ID. func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string) (string, error) { - // Currently the caller only has access to /var/lib/snapd/desktop/applications/, so we just look there - // and ignore https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + // OpenDesktopEntryEnv() currently only supports launching snap applications from + // desktop files in /var/lib/snapd/desktop/applications and these desktop files are + // written by snapd and considered safe for userd to process. If other directories are + // added in the future, readExecCommandFromDesktopFile() and + // parseExecCommand() may need to be updated for any changed assumptions. + // + // Since we only access /var/lib/snapd/desktop/applications, ignore + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html base_dir := dirs.SnapDesktopFilesDir desktopFile := findDesktopFile(desktopFile_exists, base_dir, strings.Split(desktopFileID, "-")) @@ -231,7 +241,7 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { if line == "[Desktop Entry]" { in_desktop_section = true - } else if strings.HasPrefix(line, "[Desktop Action") { + } else if strings.HasPrefix(line, "[Desktop Action ") { // maybe later we'll add support here in_desktop_section = false } else if strings.HasPrefix(line, "[") { @@ -242,9 +252,20 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { } } + expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/%s /snap/bin/", filepath.Base(desktopFile)) + if ! strings.HasPrefix(launch, expectedPrefix) { + return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value", desktopFile) + } + return launch, nil } +// Parse the Exec command by stripping any exec variables. +// Passing exec variables (eg, %foo) between confined snaps is unsupported. Currently, +// we do not have support for passing them in the D-Bus API but there are security +// implications that must be thought through regarding the influence of the launching +// snap over the launcher wrt exec variables. For now we simply filter them out. +// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables func parseExecCommand(exec_command string) ([]string, error) { args, err := shlex.Split(exec_command) if err != nil { @@ -253,14 +274,10 @@ func parseExecCommand(exec_command string) ([]string, error) { i := 0 for { - if strings.HasPrefix(args[i], "%%") { + if strings.HasPrefix(args[i], "%%") { // A double "%" means a real "%" args[i] = strings.TrimPrefix(args[i], "%") i++ - } else if strings.HasPrefix(args[i], "%") { - // Passing exec variables between confined snaps raises unanswered questions and they are not required - // for the simple cases. For now, we don't have support for passing them in the dbus API and drop - // them from the command. - // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables + } else if strings.HasPrefix(args[i], "%") { // A single "%" is an exec variable args = append(args[:i], args[i+1:]...) } else { i++ From 2773622a0b4bf67a8940cf0b2e4d2c7b917e656d Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 1 Jun 2020 14:48:53 +0100 Subject: [PATCH 043/114] Additional "hardening" suggested in review --- usersession/userd/launcher.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 01b2baa6a4b..b6cf9b4596e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -228,6 +228,21 @@ func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string func readExecCommandFromDesktopFile(desktopFile string) (string, error) { var launch string + if strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { + for checkPath := filepath.Dir(desktopFile); strings.HasPrefix(checkPath, dirs.SnapDesktopFilesDir); checkPath = filepath.Dir(checkPath) { + fileStat, err := os.Stat(checkPath) + if err != nil || ! fileStat.Mode().IsDir() || (fileStat.Mode().Perm() & 0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { + return "", fmt.Errorf("cannot verify path %q", checkPath) + } + } + } else { + // We currently only support launching snap applications from desktop files in + // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and + // considered safe for userd to process. If other directories are added in the future, + // readExecCommandFromDesktopFile() and parseExecCommand() may need to be updated if this changes. + return "", fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") + } + file, err := os.Open(desktopFile) if err != nil { return launch, err From 7371e9b08b1c14368fffbad68b53ddb0bc5c808b Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 1 Jun 2020 14:57:11 +0100 Subject: [PATCH 044/114] gofmt -s -w --- interfaces/builtin/desktop_launch_test.go | 2 +- usersession/userd/launcher.go | 60 +++++++++++------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/interfaces/builtin/desktop_launch_test.go b/interfaces/builtin/desktop_launch_test.go index 75206e03c64..f83544aef79 100644 --- a/interfaces/builtin/desktop_launch_test.go +++ b/interfaces/builtin/desktop_launch_test.go @@ -93,4 +93,4 @@ func (s *desktopLaunchSuite) TestStaticInfo(c *C) { c.Assert(si.Summary, Equals, `allows snaps to identify and launch other snaps`) c.Assert(si.BaseDeclarationSlots, testutil.Contains, "desktop-launch") c.Assert(si.BaseDeclarationSlots, testutil.Contains, "deny-auto-connection: true") -} \ No newline at end of file +} diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index b6cf9b4596e..bfcc3c73ccb 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -159,10 +159,10 @@ func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sende cmd.Env = append(cmd.Env, e) } - // XXX: this avoids defunct processes but causes userd to persist - // until all children are gone (currently, this is not a problem since - // userd is long running once started) - go cmd.Wait() + // XXX: this avoids defunct processes but causes userd to persist + // until all children are gone (currently, this is not a problem since + // userd is long running once started) + go cmd.Wait() if cmd.Run() != nil { return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) @@ -177,8 +177,8 @@ func existsOnFileSystem(desktopFile string) bool { fileStat, err := os.Stat(desktopFile) // We only support files in /var/lib/snapd/desktop/applications and they should - // all be regular files. - return err == nil && fileStat.Mode().IsRegular() + // all be regular files. + return err == nil && fileStat.Mode().IsRegular() } // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. @@ -206,13 +206,13 @@ func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string) (string, error) { // OpenDesktopEntryEnv() currently only supports launching snap applications from - // desktop files in /var/lib/snapd/desktop/applications and these desktop files are - // written by snapd and considered safe for userd to process. If other directories are - // added in the future, readExecCommandFromDesktopFile() and - // parseExecCommand() may need to be updated for any changed assumptions. - // - // Since we only access /var/lib/snapd/desktop/applications, ignore - // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + // desktop files in /var/lib/snapd/desktop/applications and these desktop files are + // written by snapd and considered safe for userd to process. If other directories are + // added in the future, readExecCommandFromDesktopFile() and + // parseExecCommand() may need to be updated for any changed assumptions. + // + // Since we only access /var/lib/snapd/desktop/applications, ignore + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html base_dir := dirs.SnapDesktopFilesDir desktopFile := findDesktopFile(desktopFile_exists, base_dir, strings.Split(desktopFileID, "-")) @@ -228,20 +228,20 @@ func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string func readExecCommandFromDesktopFile(desktopFile string) (string, error) { var launch string - if strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { - for checkPath := filepath.Dir(desktopFile); strings.HasPrefix(checkPath, dirs.SnapDesktopFilesDir); checkPath = filepath.Dir(checkPath) { - fileStat, err := os.Stat(checkPath) - if err != nil || ! fileStat.Mode().IsDir() || (fileStat.Mode().Perm() & 0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { - return "", fmt.Errorf("cannot verify path %q", checkPath) - } - } - } else { - // We currently only support launching snap applications from desktop files in - // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and - // considered safe for userd to process. If other directories are added in the future, - // readExecCommandFromDesktopFile() and parseExecCommand() may need to be updated if this changes. - return "", fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") - } + if strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { + for checkPath := filepath.Dir(desktopFile); strings.HasPrefix(checkPath, dirs.SnapDesktopFilesDir); checkPath = filepath.Dir(checkPath) { + fileStat, err := os.Stat(checkPath) + if err != nil || !fileStat.Mode().IsDir() || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { + return "", fmt.Errorf("cannot verify path %q", checkPath) + } + } + } else { + // We currently only support launching snap applications from desktop files in + // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and + // considered safe for userd to process. If other directories are added in the future, + // readExecCommandFromDesktopFile() and parseExecCommand() may need to be updated if this changes. + return "", fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") + } file, err := os.Open(desktopFile) if err != nil { @@ -268,9 +268,9 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { } expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/%s /snap/bin/", filepath.Base(desktopFile)) - if ! strings.HasPrefix(launch, expectedPrefix) { - return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value", desktopFile) - } + if !strings.HasPrefix(launch, expectedPrefix) { + return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value", desktopFile) + } return launch, nil } From 7742cfe1551e3d9ffea5e9706d3266b4f37decbb Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 11 Jun 2020 10:09:44 +0100 Subject: [PATCH 045/114] Add BaseDeclarationPlugs to desktopLaunchSuite.TestStaticInfo --- interfaces/builtin/desktop_launch_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interfaces/builtin/desktop_launch_test.go b/interfaces/builtin/desktop_launch_test.go index f83544aef79..fd4cd605d98 100644 --- a/interfaces/builtin/desktop_launch_test.go +++ b/interfaces/builtin/desktop_launch_test.go @@ -93,4 +93,7 @@ func (s *desktopLaunchSuite) TestStaticInfo(c *C) { c.Assert(si.Summary, Equals, `allows snaps to identify and launch other snaps`) c.Assert(si.BaseDeclarationSlots, testutil.Contains, "desktop-launch") c.Assert(si.BaseDeclarationSlots, testutil.Contains, "deny-auto-connection: true") + c.Assert(si.BaseDeclarationPlugs, testutil.Contains, "desktop-launch") + c.Assert(si.BaseDeclarationPlugs, testutil.Contains, "deny-auto-connection: true") + c.Assert(si.BaseDeclarationPlugs, testutil.Contains, "allow-installation: false") } From fb2764b3f926e64a667673639b4f6927fb468a35 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 11 Jun 2020 10:24:50 +0100 Subject: [PATCH 046/114] Document allowedEnvVars --- usersession/userd/launcher.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index bfcc3c73ccb..7c0aea50b46 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -64,6 +64,14 @@ const launcherIntrospectionXML = ` var ( allowedURLSchemes = []string{"http", "https", "mailto", "snap", "help", "apt", "zoommtg"} + + // allowedEnvVars are those environment variables that snaps who have access + // to OpenDesktopEntryEnv() can set for the launched snap's environment. + // - DISPLAY: set the X11 display + // - WAYLAND_DISPLAY: set the wayland display + // - XDG_CURRENT_DESKTOP: set identifiers for desktop environments + // - XDG_SESSION_DESKTOP: set identifier for the desktop environment + // - XDG_SESSION_TYPE: set the session type (e.g. "wayland" or "x11") allowedEnvVars = []string{"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", "XDG_SESSION_DESKTOP", "XDG_SESSION_TYPE"} ) From d30cff7ac0d1d632c0eac53ec8f74e1d36257e81 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 11 Jun 2020 10:27:33 +0100 Subject: [PATCH 047/114] Update comment --- usersession/userd/launcher.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 7c0aea50b46..8f3a5702b9d 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -297,10 +297,12 @@ func parseExecCommand(exec_command string) ([]string, error) { i := 0 for { - if strings.HasPrefix(args[i], "%%") { // A double "%" means a real "%" + // We want to keep literal '%' (expressed as '%%') but filter our exec variables + // like '%foo' + if strings.HasPrefix(args[i], "%%") { args[i] = strings.TrimPrefix(args[i], "%") i++ - } else if strings.HasPrefix(args[i], "%") { // A single "%" is an exec variable + } else if strings.HasPrefix(args[i], "%") { args = append(args[:i], args[i+1:]...) } else { i++ From 6ede6fe0a56f3537fade516f0d5fd6b84e68e02a Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 11 Jun 2020 11:00:23 +0100 Subject: [PATCH 048/114] Document and correct check on desktop file & path --- usersession/userd/launcher.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 8f3a5702b9d..86e902040a7 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -236,10 +236,14 @@ func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string func readExecCommandFromDesktopFile(desktopFile string) (string, error) { var launch string + // Be careful with which desktop files we process and verify that: + // 1. we only consider desktop files in dirs.SnapDesktopFilesDir + // 2. the desktop file itself and all directories above it are root owned without group/other write + // 3. the Exec line has an expected prefix if strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { - for checkPath := filepath.Dir(desktopFile); strings.HasPrefix(checkPath, dirs.SnapDesktopFilesDir); checkPath = filepath.Dir(checkPath) { + for checkPath := desktopFile; strings.HasPrefix(checkPath, dirs.SnapDesktopFilesDir); checkPath = filepath.Dir(checkPath) { fileStat, err := os.Stat(checkPath) - if err != nil || !fileStat.Mode().IsDir() || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { + if err != nil || !(fileStat.Mode().IsDir() || fileStat.Mode().IsRegular()) || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { return "", fmt.Errorf("cannot verify path %q", checkPath) } } From 888ffbb32c79489d96fb2b6b6dab616dfcdce84c Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 11 Jun 2020 11:08:41 +0100 Subject: [PATCH 049/114] gofmt --- usersession/userd/launcher.go | 24 ++++++++++----------- usersession/userd/launcher_internal_test.go | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 86e902040a7..45141794ed6 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -67,12 +67,12 @@ var ( // allowedEnvVars are those environment variables that snaps who have access // to OpenDesktopEntryEnv() can set for the launched snap's environment. - // - DISPLAY: set the X11 display - // - WAYLAND_DISPLAY: set the wayland display - // - XDG_CURRENT_DESKTOP: set identifiers for desktop environments - // - XDG_SESSION_DESKTOP: set identifier for the desktop environment - // - XDG_SESSION_TYPE: set the session type (e.g. "wayland" or "x11") - allowedEnvVars = []string{"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", "XDG_SESSION_DESKTOP", "XDG_SESSION_TYPE"} + // - DISPLAY: set the X11 display + // - WAYLAND_DISPLAY: set the wayland display + // - XDG_CURRENT_DESKTOP: set identifiers for desktop environments + // - XDG_SESSION_DESKTOP: set identifier for the desktop environment + // - XDG_SESSION_TYPE: set the session type (e.g. "wayland" or "x11") + allowedEnvVars = []string{"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", "XDG_SESSION_DESKTOP", "XDG_SESSION_TYPE"} ) // Launcher implements the 'io.snapcraft.Launcher' DBus interface. @@ -236,10 +236,10 @@ func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string func readExecCommandFromDesktopFile(desktopFile string) (string, error) { var launch string - // Be careful with which desktop files we process and verify that: - // 1. we only consider desktop files in dirs.SnapDesktopFilesDir - // 2. the desktop file itself and all directories above it are root owned without group/other write - // 3. the Exec line has an expected prefix + // Be careful with which desktop files we process and verify that: + // 1. we only consider desktop files in dirs.SnapDesktopFilesDir + // 2. the desktop file itself and all directories above it are root owned without group/other write + // 3. the Exec line has an expected prefix if strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { for checkPath := desktopFile; strings.HasPrefix(checkPath, dirs.SnapDesktopFilesDir); checkPath = filepath.Dir(checkPath) { fileStat, err := os.Stat(checkPath) @@ -301,8 +301,8 @@ func parseExecCommand(exec_command string) ([]string, error) { i := 0 for { - // We want to keep literal '%' (expressed as '%%') but filter our exec variables - // like '%foo' + // We want to keep literal '%' (expressed as '%%') but filter our exec variables + // like '%foo' if strings.HasPrefix(args[i], "%%") { args[i] = strings.TrimPrefix(args[i], "%") i++ diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index fe333e46b18..5b15e4f9a5e 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -21,8 +21,8 @@ package userd import ( "github.com/snapcore/snapd/strutil" - "testing" . "gopkg.in/check.v1" + "testing" ) func Test(t *testing.T) { TestingT(t) } From 6c60b2ef787bc4dcec0ed60075223c032f49e988 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 11 Jun 2020 11:08:58 +0100 Subject: [PATCH 050/114] Add test for foo-bar_foo-bar.desktop --- usersession/userd/launcher_internal_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index 5b15e4f9a5e..e92d54ffd2c 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -78,6 +78,7 @@ func (s *launcherInternalSuite) TestDesktopFileIDToFilenameFailsWithInvalidId(c "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", "bar-foo-baz.desktop", "bar-baz-foo.desktop", + "foo-bar_foo-bar.desktop", } for _, id := range desktopIdTests { From 805886d801ffa2cb94046b8723ce5bb7633ce5ad Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 11 Jun 2020 11:19:58 +0100 Subject: [PATCH 051/114] A comment to explain test strategy --- usersession/userd/launcher_internal_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index e92d54ffd2c..378713ee0ba 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -107,6 +107,7 @@ func (s *launcherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) } func (s *launcherInternalSuite) TestParseExecCommandFailsWithInvalidEntry(c *C) { + // the only invalid entries are those that error from shlex.Split() var exec_command = []string{ "/snap/bin/foo \"unclosed double quote", "/snap/bin/foo 'unclosed single quote", From 6848788f6bae81af38229faeb12746bc3b73baac Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 16 Jun 2020 15:41:26 +0100 Subject: [PATCH 052/114] Check the desktopFile path all the way down from "/" --- usersession/userd/launcher.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 0b9e55a284c..c20738cce3b 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -288,11 +288,17 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { // 2. the desktop file itself and all directories above it are root owned without group/other write // 3. the Exec line has an expected prefix if strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { - for checkPath := desktopFile; strings.HasPrefix(checkPath, dirs.SnapDesktopFilesDir); checkPath = filepath.Dir(checkPath) { + if filepath.Clean(desktopFile) != desktopFile { + return "", fmt.Errorf("desktop file has unclean path: %q", desktopFile) + } + last := "/" + for _, val := range strings.Split(desktopFile, "/") { + checkPath := filepath.Join(last, val) // val is "" for root ('/') fileStat, err := os.Stat(checkPath) if err != nil || !(fileStat.Mode().IsDir() || fileStat.Mode().IsRegular()) || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { return "", fmt.Errorf("cannot verify path %q", checkPath) } + last = checkPath } } else { // We currently only support launching snap applications from desktop files in From 2c4ea7dfefc1b69690e222912daed46fee15bff6 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 16 Jun 2020 16:22:40 +0100 Subject: [PATCH 053/114] Comment on the recursion in findDesktopFile --- usersession/userd/launcher.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index c20738cce3b..6c8df8bf7bc 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -237,8 +237,14 @@ func existsOnFileSystem(desktopFile string) bool { } // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. +// Per https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id, +// if desktop entries have dashes in the name ('-'), this could be an indication of subdirectories, so search +// for those too. Eg, given foo-bar_baz_norf.desktop the following are searched for: +// o .../foo-bar_baz-norf.desktop +// o .../foo/bar_baz-norf.desktop +// o .../foo/bar_baz/norf.desktop +// o .../foo-bar_baz/norf.desktop // We're not required to diagnose multiple files matching the desktop file ID. -// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId []string) *string { desktopFile := filepath.Join(base_dir, strings.Join(splitFileId, "-")) @@ -246,7 +252,10 @@ func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId return &desktopFile } - // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID + // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID. + // Maybe this is overkill: At the time of writing, the only use is in desktopFileIDToFilename() and there + // we're only checking dirs.SnapDesktopFilesDir (and not all entries in $XDG_DATA_DIRS). + // For dirs.SnapDesktopFilesDir snapd will not create any subdirectories. for i := 1; i != len(splitFileId); i++ { desktopFile := findDesktopFile(desktopFile_exists, filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) if desktopFile != nil { From 31a13cdccbcaa22213a2c65d477518cbabd94d15 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 16 Jun 2020 16:30:23 +0100 Subject: [PATCH 054/114] Use err to indicate failure instead of null pointer to string --- usersession/userd/launcher.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 6c8df8bf7bc..0c46c41edfc 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -245,25 +245,25 @@ func existsOnFileSystem(desktopFile string) bool { // o .../foo/bar_baz/norf.desktop // o .../foo-bar_baz/norf.desktop // We're not required to diagnose multiple files matching the desktop file ID. -func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId []string) *string { +func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId []string) (string, error) { desktopFile := filepath.Join(base_dir, strings.Join(splitFileId, "-")) if desktopFile_exists(desktopFile) { - return &desktopFile + return desktopFile, nil } // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID. - // Maybe this is overkill: At the time of writing, the only use is in desktopFileIDToFilename() and there - // we're only checking dirs.SnapDesktopFilesDir (and not all entries in $XDG_DATA_DIRS). - // For dirs.SnapDesktopFilesDir snapd will not create any subdirectories. + // Maybe this is overkill: At the time of writing, the only use is in desktopFileIDToFilename() and there + // we're only checking dirs.SnapDesktopFilesDir (and not all entries in $XDG_DATA_DIRS). + // For dirs.SnapDesktopFilesDir snapd will not create any subdirectories. for i := 1; i != len(splitFileId); i++ { - desktopFile := findDesktopFile(desktopFile_exists, filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) - if desktopFile != nil { - return desktopFile + desktopFile, err := findDesktopFile(desktopFile_exists, filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + if err == nil { + return desktopFile, err } } - return nil + return "", fmt.Errorf("could not find desktop file") } // desktopFileIDToFilename determines the path associated with a desktop file ID. @@ -279,10 +279,10 @@ func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html base_dir := dirs.SnapDesktopFilesDir - desktopFile := findDesktopFile(desktopFile_exists, base_dir, strings.Split(desktopFileID, "-")) + desktopFile, err := findDesktopFile(desktopFile_exists, base_dir, strings.Split(desktopFileID, "-")) - if desktopFile != nil { - return *desktopFile, nil + if err == nil { + return desktopFile, nil } return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) From 429b549516c4429a7f4ce07cc0ef4d6b900e1e57 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 16 Jun 2020 16:33:11 +0100 Subject: [PATCH 055/114] Clearer table of test cases in TestParseExecCommandSucceedsWithValidEntry --- usersession/userd/launcher_internal_test.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index 378713ee0ba..5ebd482a069 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -92,11 +92,28 @@ func (s *launcherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) exec_command string expect []string }{ + // valid with no exec variables {"env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop /snap/bin/mir-kiosk-scummvm %U", []string{"env", "BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop", "/snap/bin/mir-kiosk-scummvm"}}, - {"/snap/bin/foo -f %U %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, + // valid with literal '%' and no exec variables + {"/snap/bin/foo -f %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %%bar %%baz", []string{"/snap/bin/foo", "-f", "%bar", "%baz"}}, + // valid where quoted strings are passed through + {"/snap/bin/foo '-f %U'", []string{"/snap/bin/foo", "-f %U"}}, + {"/snap/bin/foo '-f %%bar'", []string{"/snap/bin/foo", "-f %%bar"}}, {"/snap/bin/foo '-f %U %%bar'", []string{"/snap/bin/foo", "-f %U %%bar"}}, {"/snap/bin/foo \"'-f bar'\"", []string{"/snap/bin/foo", "'-f bar'"}}, + {"/snap/bin/foo '\"-f bar\"'", []string{"/snap/bin/foo", "\"-f bar\""}}, + // valid with exec variables stripped out + {"/snap/bin/foo -f %U", []string{"/snap/bin/foo", "-f"}}, + {"/snap/bin/foo -f %U %Y", []string{"/snap/bin/foo", "-f"}}, + {"/snap/bin/foo -f %U bar", []string{"/snap/bin/foo", "-f", "bar"}}, + {"/snap/bin/foo -f %U bar %Y", []string{"/snap/bin/foo", "-f", "bar"}}, + // valid with mixture of literal '%' and exec variables + {"/snap/bin/foo -f %U %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %U %Y %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %U %%bar %Y", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %%bar %U %Y", []string{"/snap/bin/foo", "-f", "%bar"}}, } for _, test := range exec_command { From 73ff67a4d54ab23d03e3d241cc258b606f94c788 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 16 Jun 2020 17:48:45 +0100 Subject: [PATCH 056/114] Extract verifyDesktopFileLocation() from readExecCommandFromDesktopFile() --- usersession/userd/launcher.go | 58 ++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 0c46c41edfc..faed052258a 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -194,6 +194,11 @@ func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sende return dbus.MakeFailedError(err) } + err = verifyDesktopFileLocation(desktopFile) + if err != nil { + return dbus.MakeFailedError(err) + } + exec_command, err := readExecCommandFromDesktopFile(desktopFile) if err != nil { return dbus.MakeFailedError(err) @@ -288,35 +293,40 @@ func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) } -// readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. -func readExecCommandFromDesktopFile(desktopFile string) (string, error) { - var launch string - - // Be careful with which desktop files we process and verify that: - // 1. we only consider desktop files in dirs.SnapDesktopFilesDir - // 2. the desktop file itself and all directories above it are root owned without group/other write - // 3. the Exec line has an expected prefix - if strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { - if filepath.Clean(desktopFile) != desktopFile { - return "", fmt.Errorf("desktop file has unclean path: %q", desktopFile) - } - last := "/" - for _, val := range strings.Split(desktopFile, "/") { - checkPath := filepath.Join(last, val) // val is "" for root ('/') - fileStat, err := os.Stat(checkPath) - if err != nil || !(fileStat.Mode().IsDir() || fileStat.Mode().IsRegular()) || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { - return "", fmt.Errorf("cannot verify path %q", checkPath) - } - last = checkPath - } - } else { +// verifyDesktopFileLocation checks the desktop file location and access. +// 1. we only consider desktop files in dirs.SnapDesktopFilesDir +// 2. the desktop file itself and all directories above it are root owned without group/other write +// 3. the Exec line has an expected prefix +func verifyDesktopFileLocation(desktopFile string) error { + if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { // We currently only support launching snap applications from desktop files in // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and // considered safe for userd to process. If other directories are added in the future, - // readExecCommandFromDesktopFile() and parseExecCommand() may need to be updated if this changes. - return "", fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") + // verifyDesktopFileLocation() and parseExecCommand() may need to be updated. + return fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") + } + + if filepath.Clean(desktopFile) != desktopFile { + return fmt.Errorf("desktop file has unclean path: %q", desktopFile) + } + + last := "/" + for _, val := range strings.Split(desktopFile, "/") { + checkPath := filepath.Join(last, val) // val is "" for root ('/') + fileStat, err := os.Stat(checkPath) + if err != nil || !(fileStat.Mode().IsDir() || fileStat.Mode().IsRegular()) || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { + return fmt.Errorf("cannot verify path %q", checkPath) + } + last = checkPath } + return nil +} + +// readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. +func readExecCommandFromDesktopFile(desktopFile string) (string, error) { + var launch string + file, err := os.Open(desktopFile) if err != nil { return launch, err From 24179b2d8a2ef8eafe11b9df1d79ca1c4260acf3 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 17 Jun 2020 12:27:00 +0100 Subject: [PATCH 057/114] Tests for readExecCommandFromDesktopFile() --- usersession/userd/launcher.go | 10 +- usersession/userd/launcher_internal_test.go | 345 ++++++++++++++++++++ 2 files changed, 349 insertions(+), 6 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index faed052258a..4298ba20d9f 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -276,10 +276,7 @@ func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string // OpenDesktopEntryEnv() currently only supports launching snap applications from // desktop files in /var/lib/snapd/desktop/applications and these desktop files are - // written by snapd and considered safe for userd to process. If other directories are - // added in the future, readExecCommandFromDesktopFile() and - // parseExecCommand() may need to be updated for any changed assumptions. - // + // written by snapd and considered safe for userd to process. // Since we only access /var/lib/snapd/desktop/applications, ignore // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html base_dir := dirs.SnapDesktopFilesDir @@ -323,7 +320,8 @@ func verifyDesktopFileLocation(desktopFile string) error { return nil } -// readExecCommandFromDesktopFile parses the desktop file to get the Exec entry. +// readExecCommandFromDesktopFile parses the desktop file to get the Exec entry and +// checks that the BAMF_DESKTOP_FILE_HINT is present and refers to the desktop file. func readExecCommandFromDesktopFile(desktopFile string) (string, error) { var launch string @@ -351,7 +349,7 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { } } - expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/%s /snap/bin/", filepath.Base(desktopFile)) + expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s /snap/bin/", desktopFile) if !strings.HasPrefix(launch, expectedPrefix) { return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value", desktopFile) } diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index 5ebd482a069..3665aa86021 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -22,6 +22,9 @@ package userd import ( "github.com/snapcore/snapd/strutil" . "gopkg.in/check.v1" + "io/ioutil" + "path/filepath" + "strings" "testing" ) @@ -51,6 +54,303 @@ var mockFileSystem = []string{ "/var/lib/snapd/desktop/applications/baz/foo-bar.desktop", } +var chromiumDesktopFile = `[Desktop Entry] +X-SnapInstanceName=chromium +Version=1.0 +Name=Chromium Web Browser +Name[ast]=Restolador web Chromium +Name[bg]=Уеб четец Chromium +Name[bn]=ক্রোমিয়াম ওয়েব ব্রাউজার +Name[bs]=Chromium web preglednik +Name[ca]=Navegador web Chromium +Name[ca@valencia]=Navegador web Chromium +Name[da]=Chromium netbrowser +Name[de]=Chromium-Webbrowser +Name[en_AU]=Chromium Web Browser +Name[eo]=Kromiumo retfoliumilo +Name[es]=Navegador web Chromium +Name[et]=Chromiumi veebibrauser +Name[eu]=Chromium web-nabigatzailea +Name[fi]=Chromium-selain +Name[fr]=Navigateur Web Chromium +Name[gl]=Navegador web Chromium +Name[he]=דפדפן האינטרנט כרומיום +Name[hr]=Chromium web preglednik +Name[hu]=Chromium webböngésző +Name[hy]=Chromium ոստայն զննարկիչ +Name[ia]=Navigator del web Chromium +Name[id]=Peramban Web Chromium +Name[it]=Browser web Chromium +Name[ja]=Chromium ウェブ・ブラウザ +Name[ka]=ვებ ბრაუზერი Chromium +Name[ko]=Chromium 웹 브라우저 +Name[kw]=Peurel wias Chromium +Name[ms]=Pelayar Web Chromium +Name[nb]=Chromium nettleser +Name[nl]=Chromium webbrowser +Name[pt_BR]=Navegador de Internet Chromium +Name[ro]=Navigator Internet Chromium +Name[ru]=Веб-браузер Chromium +Name[sl]=Chromium spletni brskalnik +Name[sv]=Webbläsaren Chromium +Name[ug]=Chromium توركۆرگۈ +Name[vi]=Trình duyệt Web Chromium +Name[zh_CN]=Chromium 网页浏览器 +Name[zh_HK]=Chromium 網頁瀏覽器 +Name[zh_TW]=Chromium 網頁瀏覽器 +GenericName=Web Browser +GenericName[ar]=متصفح الشبكة +GenericName[ast]=Restolador web +GenericName[bg]=Уеб браузър +GenericName[bn]=ওয়েব ব্রাউজার +GenericName[bs]=Web preglednik +GenericName[ca]=Navegador web +GenericName[ca@valencia]=Navegador web +GenericName[cs]=WWW prohlížeč +GenericName[da]=Browser +GenericName[de]=Web-Browser +GenericName[el]=Περιηγητής ιστού +GenericName[en_AU]=Web Browser +GenericName[en_GB]=Web Browser +GenericName[eo]=Retfoliumilo +GenericName[es]=Navegador web +GenericName[et]=Veebibrauser +GenericName[eu]=Web-nabigatzailea +GenericName[fi]=WWW-selain +GenericName[fil]=Web Browser +GenericName[fr]=Navigateur Web +GenericName[gl]=Navegador web +GenericName[gu]=વેબ બ્રાઉઝર +GenericName[he]=דפדפן אינטרנט +GenericName[hi]=वेब ब्राउज़र +GenericName[hr]=Web preglednik +GenericName[hu]=Webböngésző +GenericName[hy]=Ոստայն զննարկիչ +GenericName[ia]=Navigator del Web +GenericName[id]=Peramban Web +GenericName[it]=Browser web +GenericName[ja]=ウェブ・ブラウザ +GenericName[ka]=ვებ ბრაუზერი +GenericName[kn]=ಜಾಲ ವೀಕ್ಷಕ +GenericName[ko]=웹 브라우저 +GenericName[kw]=Peurel wias +GenericName[lt]=Žiniatinklio naršyklė +GenericName[lv]=Tīmekļa pārlūks +GenericName[ml]=വെബ് ബ്രൌസര്‍ +GenericName[mr]=वेब ब्राऊजर +GenericName[ms]=Pelayar Web +GenericName[nb]=Nettleser +GenericName[nl]=Webbrowser +GenericName[or]=ଓ୍ବେବ ବ୍ରାଉଜର +GenericName[pl]=Przeglądarka WWW +GenericName[pt]=Navegador Web +GenericName[pt_BR]=Navegador web +GenericName[ro]=Navigator de Internet +GenericName[ru]=Веб-браузер +GenericName[sk]=WWW prehliadač +GenericName[sl]=Spletni brskalnik +GenericName[sr]=Интернет прегледник +GenericName[sv]=Webbläsare +GenericName[ta]=இணைய உலாவி +GenericName[te]=మహాతల అన్వేషి +GenericName[th]=เว็บเบราว์เซอร์ +GenericName[tr]=Web Tarayıcı +GenericName[ug]=توركۆرگۈ +GenericName[uk]=Навігатор Тенет +GenericName[vi]=Bộ duyệt Web +GenericName[zh_CN]=网页浏览器 +GenericName[zh_HK]=網頁瀏覽器 +GenericName[zh_TW]=網頁瀏覽器 +Comment=Access the Internet +Comment[ar]=الدخول إلى الإنترنت +Comment[ast]=Accesu a Internet +Comment[bg]=Достъп до интернет +Comment[bn]=ইন্টারনেটে প্রবেশ করুন +Comment[bs]=Pristup internetu +Comment[ca]=Accediu a Internet +Comment[ca@valencia]=Accediu a Internet +Comment[cs]=Přístup k internetu +Comment[da]=Få adgang til internettet +Comment[de]=Internetzugriff +Comment[el]=Πρόσβαση στο Διαδίκτυο +Comment[en_AU]=Access the Internet +Comment[en_GB]=Access the Internet +Comment[eo]=Akiri interreton +Comment[es]=Acceda a Internet +Comment[et]=Pääs Internetti +Comment[eu]=Sartu Internetera +Comment[fi]=Käytä internetiä +Comment[fil]=I-access ang Internet +Comment[fr]=Accéder à Internet +Comment[gl]=Acceda a Internet +Comment[gu]=ઇંટરનેટ ઍક્સેસ કરો +Comment[he]=גישה לאינטרנט +Comment[hi]=इंटरनेट तक पहुंच स्थापित करें +Comment[hr]=Pristupite Internetu +Comment[hu]=Az internet elérése +Comment[hy]=Մուտք համացանց +Comment[ia]=Accede a le Interrete +Comment[id]=Akses Internet +Comment[it]=Accesso a Internet +Comment[ja]=インターネットにアクセス +Comment[ka]=ინტერნეტში შესვლა +Comment[kn]=ಇಂಟರ್ನೆಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಿ +Comment[ko]=인터넷에 연결합니다 +Comment[kw]=Hedhes an Kesrosweyth +Comment[lt]=Interneto prieiga +Comment[lv]=Piekļūt internetam +Comment[ml]=ഇന്റര്‍‌നെറ്റ് ആക്‌സസ് ചെയ്യുക +Comment[mr]=इंटरनेटमध्ये प्रवेश करा +Comment[ms]=Mengakses Internet +Comment[nb]=Bruk internett +Comment[nl]=Verbinding maken met internet +Comment[or]=ଇଣ୍ଟର୍ନେଟ୍ ପ୍ରବେଶ କରନ୍ତୁ +Comment[pl]=Skorzystaj z internetu +Comment[pt]=Aceder à Internet +Comment[pt_BR]=Acessar a internet +Comment[ro]=Accesați Internetul +Comment[ru]=Доступ в Интернет +Comment[sk]=Prístup do siete Internet +Comment[sl]=Dostop do interneta +Comment[sr]=Приступите Интернету +Comment[sv]=Surfa på Internet +Comment[ta]=இணையத்தை அணுகுதல் +Comment[te]=ఇంటర్నెట్‌ను ఆక్సెస్ చెయ్యండి +Comment[th]=เข้าถึงอินเทอร์เน็ต +Comment[tr]=İnternet'e erişin +Comment[ug]=ئىنتېرنېت زىيارىتى +Comment[uk]=Доступ до Інтернету +Comment[vi]=Truy cập Internet +Comment[zh_CN]=访问互联网 +Comment[zh_HK]=連線到網際網路 +Comment[zh_TW]=連線到網際網路 +Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/chromium_chromium.desktop /snap/bin/chromium %U +Terminal=false +Type=Application +Icon=/snap/chromium/1193/chromium.png +Categories=Network;WebBrowser; +MimeType=text/html;text/xml;application/xhtml_xml;x-scheme-handler/http;x-scheme-handler/https; +StartupNotify=true +StartupWMClass=chromium +Actions=NewWindow;Incognito;TempProfile; + +[Desktop Action NewWindow] +Name=Open a New Window +Name[ast]=Abrir una Ventana Nueva +Name[bg]=Отваряне на Нов прозорец +Name[bn]=একটি নতুন উইন্ডো খুলুন +Name[bs]=Otvori novi prozor +Name[ca]=Obre una finestra nova +Name[ca@valencia]=Obri una finestra nova +Name[da]=Åbn et nyt vindue +Name[de]=Ein neues Fenster öffnen +Name[en_AU]=Open a New Window +Name[eo]=Malfermi novan fenestron +Name[es]=Abrir una ventana nueva +Name[et]=Ava uus aken +Name[eu]=Ireki leiho berria +Name[fi]=Avaa uusi ikkuna +Name[fr]=Ouvrir une nouvelle fenêtre +Name[gl]=Abrir unha nova xanela +Name[he]=פתיחת חלון חדש +Name[hy]=Բացել նոր պատուհան +Name[ia]=Aperi un nove fenestra +Name[it]=Apri una nuova finestra +Name[ja]=新しいウィンドウを開く +Name[ka]=ახალი ფანჯრის გახსნა +Name[kw]=Egery fenester noweth +Name[ms]=Buka Tetingkap Baru +Name[nb]=Åpne et nytt vindu +Name[nl]=Nieuw venster openen +Name[pt_BR]=Abre uma nova janela +Name[ro]=Deschide o fereastră nouă +Name[ru]=Открыть новое окно +Name[sl]=Odpri novo okno +Name[sv]=Öppna ett nytt fönster +Name[ug]=يېڭى كۆزنەك ئاچ +Name[uk]=Відкрити нове вікно +Name[vi]=Mở cửa sổ mới +Name[zh_CN]=打开新窗口 +Name[zh_TW]=開啟新視窗 +Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/chromium_chromium.desktop /snap/bin/chromium + +[Desktop Action Incognito] +Name=Open a New Window in incognito mode +Name[ast]=Abrir una ventana nueva en mou incógnitu +Name[bg]=Отваряне на нов прозорец в режим \"инкогнито\" +Name[bn]=একটি নতুন উইন্ডো খুলুন ইনকোগনিটো অবস্থায় +Name[bs]=Otvori novi prozor u privatnom modu +Name[ca]=Obre una finestra nova en mode d'incògnit +Name[ca@valencia]=Obri una finestra nova en mode d'incògnit +Name[de]=Ein neues Fenster im Inkognito-Modus öffnen +Name[en_AU]=Open a New Window in incognito mode +Name[eo]=Malfermi novan fenestron nekoniĝeble +Name[es]=Abrir una ventana nueva en modo incógnito +Name[et]=Ava uus aken tundmatus olekus +Name[eu]=Ireki leiho berria isileko moduan +Name[fi]=Avaa uusi ikkuna incognito-tilassa +Name[fr]=Ouvrir une nouvelle fenêtre en mode navigation privée +Name[gl]=Abrir unha nova xanela en modo de incógnito +Name[he]=פתיחת חלון חדש במצב גלישה בסתר +Name[hy]=Բացել նոր պատուհան ծպտյալ աշխատակերպում +Name[ia]=Aperi un nove fenestra in modo incognite +Name[it]=Apri una nuova finestra in modalità incognito +Name[ja]=新しいシークレット ウィンドウを開く +Name[ka]=ახალი ფანჯრის ინკოგნიტოდ გახსნა +Name[kw]=Egry fenester noweth en modh privedh +Name[ms]=Buka Tetingkap Baru dalam mod menyamar +Name[nl]=Nieuw venster openen in incognito-modus +Name[pt_BR]=Abrir uma nova janela em modo anônimo +Name[ro]=Deschide o fereastră nouă în mod incognito +Name[ru]=Открыть новое окно в режиме инкогнито +Name[sl]=Odpri novo okno v načinu brez beleženja +Name[sv]=Öppna ett nytt inkognitofönster +Name[ug]=يوشۇرۇن ھالەتتە يېڭى كۆزنەك ئاچ +Name[uk]=Відкрити нове вікно у приватному режимі +Name[vi]=Mở cửa sổ mới trong chế độ ẩn danh +Name[zh_CN]=以隐身模式打开新窗口 +Name[zh_TW]=以匿名模式開啟新視窗 +Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/chromium_chromium.desktop /snap/bin/chromium --incognito + +[Desktop Action TempProfile] +Name=Open a New Window with a temporary profile +Name[ast]=Abrir una ventana nueva con perfil temporal +Name[bg]=Отваряне на Нов прозорец с временен профил +Name[bn]=সাময়িক প্রোফাইল সহ একটি নতুন উইন্ডো খুলুন +Name[bs]=Otvori novi prozor pomoću privremenog profila +Name[ca]=Obre una finestra nova amb un perfil temporal +Name[ca@valencia]=Obri una finestra nova amb un perfil temporal +Name[de]=Ein neues Fenster mit einem temporären Profil öffnen +Name[en_AU]=Open a New Window with a temporary profile +Name[eo]=Malfermi novan fenestron portempe +Name[es]=Abrir una ventana nueva con perfil temporal +Name[et]=Ava uus aken ajutise profiiliga +Name[eu]=Ireki leiho berria behin-behineko profil batekin +Name[fi]=Avaa uusi ikkuna käyttäen väliaikaista profiilia +Name[fr]=Ouvrir une nouvelle fenêtre avec un profil temporaire +Name[gl]=Abrir unha nova xanela con perfil temporal +Name[he]=פתיחת חלון חדש עם פרופיל זמני +Name[hy]=Բացել նոր պատուհան ժամանակավոր հատկագրով +Name[ia]=Aperi un nove fenestra con un profilo provisori +Name[it]=Apri una nuova finestra con un profilo temporaneo +Name[ja]=一時プロファイルで新しいウィンドウを開く +Name[ka]=ახალი ფანჯრის გახსნა დროებით პროფილში +Name[kw]=Egery fenester noweth gen profil dres prys +Name[ms]=Buka Tetingkap Baru dengan profil sementara +Name[nb]=Åpne et nytt vindu med en midlertidig profil +Name[nl]=Nieuw venster openen met een tijdelijk profiel +Name[pt_BR]=Abrir uma nova janela com um perfil temporário +Name[ro]=Deschide o fereastră nouă cu un profil temporar +Name[ru]=Открыть новое окно с временным профилем +Name[sl]=Odpri novo okno z začasnim profilom +Name[sv]=Öppna ett nytt fönster med temporär profil +Name[ug]=ۋاقىتلىق سەپلىمە ھۆججەت بىلەن يېڭى كۆزنەك ئاچ +Name[vi]=Mở cửa sổ mới với hồ sơ tạm +Name[zh_CN]=以临时配置文件打开新窗口 +Name[zh_TW]=以暫時性個人身分開啟新視窗 +Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/chromium_chromium.desktop /snap/bin/chromium --temp-profile +` + func existsOnMockFileSystem(desktop_file string) bool { return strutil.ListContains(mockFileSystem, desktop_file) } @@ -135,3 +435,48 @@ func (s *launcherInternalSuite) TestParseExecCommandFailsWithInvalidEntry(c *C) c.Assert(err, NotNil) } } + +func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithValidContent(c *C) { + desktopFile := filepath.Join(c.MkDir(), "test.desktop") + + // We need to correct the embedded path to the desktop file before writing the file + fileContent := strings.Replace(chromiumDesktopFile, "/var/lib/snapd/desktop/applications/chromium_chromium.desktop", desktopFile, -1) + err := ioutil.WriteFile(desktopFile, []byte(fileContent), 0644) + c.Assert(err, IsNil) + + exec, err := readExecCommandFromDesktopFile(desktopFile) + c.Assert(err, IsNil) + + c.Assert(exec, DeepEquals, "env BAMF_DESKTOP_FILE_HINT="+desktopFile+" /snap/bin/chromium %U") +} + +func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithInvalidExec(c *C) { + desktopFile := filepath.Join(c.MkDir(), "test.desktop") + + err := ioutil.WriteFile(desktopFile, []byte(chromiumDesktopFile), 0644) + c.Assert(err, IsNil) + + _, err = readExecCommandFromDesktopFile(desktopFile) + c.Assert(err, NotNil) +} + +func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithNoDesktopEntry(c *C) { + desktopFile := filepath.Join(c.MkDir(), "test.desktop") + + // We need to correct the embedded path to the desktop file before writing the file + fileContent := strings.Replace(chromiumDesktopFile, "/var/lib/snapd/desktop/applications/chromium_chromium.desktop", desktopFile, -1) + fileContent = strings.Replace(fileContent, "[Desktop Entry]", "[garbage]", -1) + + err := ioutil.WriteFile(desktopFile, []byte(fileContent), 0644) + c.Assert(err, IsNil) + + _, err = readExecCommandFromDesktopFile(desktopFile) + c.Assert(err, NotNil) +} + +func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithNoFile(c *C) { + desktopFile := filepath.Join(c.MkDir(), "test.desktop") + + _, err := readExecCommandFromDesktopFile(desktopFile) + c.Assert(err, NotNil) +} From 6adef252203c829c3224d38765d7534cc52bc3b5 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 17 Jun 2020 17:56:37 +0100 Subject: [PATCH 058/114] First draft of TestOpenDesktopEntryEnvSucceedsWithGoodDesktopId --- usersession/userd/launcher.go | 2 +- usersession/userd/launcher_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 4298ba20d9f..d247e88082e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -351,7 +351,7 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s /snap/bin/", desktopFile) if !strings.HasPrefix(launch, expectedPrefix) { - return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value", desktopFile) + return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, launch) } return launch, nil diff --git a/usersession/userd/launcher_test.go b/usersession/userd/launcher_test.go index 8b0703d5547..59848600a93 100644 --- a/usersession/userd/launcher_test.go +++ b/usersession/userd/launcher_test.go @@ -23,6 +23,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "syscall" "testing" @@ -30,6 +31,7 @@ import ( . "gopkg.in/check.v1" + "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/testutil" "github.com/snapcore/snapd/usersession/userd" @@ -197,3 +199,29 @@ func (s *launcherSuite) TestOpenFileFailsWithPathDescriptor(c *C) { c.Assert(err, ErrorMatches, "cannot use file descriptors opened using O_PATH") c.Assert(s.mockXdgOpen.Calls(), IsNil) } + +var mircadeDesktop = `[Desktop Entry] +X-SnapInstanceName=mircade +Name=mircade +Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade +Icon=/snap/mircade/143/meta/gui/mircade.png +Comment=Sample confined desktop +Type=Application +Categories=Game +` + +func (s *launcherSuite) TestOpenDesktopEntryEnvSucceedsWithGoodDesktopId(c *C) { + dirs.SetRootDir(c.MkDir()) + cmd := testutil.MockCommand(c, "/snap/bin/mircade", "true") + defer cmd.Restore() + + deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") + err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) + c.Assert(err, IsNil) + + err = s.launcher.OpenDesktopEntryEnv("mircade_mircade.desktop", []string{}, ":some-dbus-sender") + c.Assert(err, IsNil) + c.Assert(cmd.Calls(), IsNil) +} From 5a283e6ecd079f376e69048a279561710527aa71 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 18 Jun 2020 10:33:24 +0100 Subject: [PATCH 059/114] Hack the production code to make the tests pass --- usersession/userd/launcher.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index d247e88082e..fd408252712 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -307,14 +307,20 @@ func verifyDesktopFileLocation(desktopFile string) error { return fmt.Errorf("desktop file has unclean path: %q", desktopFile) } - last := "/" - for _, val := range strings.Split(desktopFile, "/") { - checkPath := filepath.Join(last, val) // val is "" for root ('/') - fileStat, err := os.Stat(checkPath) - if err != nil || !(fileStat.Mode().IsDir() || fileStat.Mode().IsRegular()) || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { - return fmt.Errorf("cannot verify path %q", checkPath) + // When running in a test environment dirs.SnapDesktopFilesDir is under /tmp/ and + // cannot pass these checks. Unless there's a way to subvert dirs.SnapDesktopFilesDir + // this hack should be harmless. Maybe there's a better way to fake it? + if !strings.HasPrefix(dirs.SnapDesktopFilesDir, "/tmp/") { + + last := "/" + for _, val := range strings.Split(desktopFile, "/") { + checkPath := filepath.Join(last, val) // val is "" for root ('/') + fileStat, err := os.Stat(checkPath) + if err != nil || !(fileStat.Mode().IsDir() || fileStat.Mode().IsRegular()) || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { + return fmt.Errorf("cannot verify path %q", checkPath) + } + last = checkPath } - last = checkPath } return nil From 1553fa3b9e6d9e66b826c2ab172c3cd6cd944e66 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 18 Jun 2020 11:03:11 +0100 Subject: [PATCH 060/114] Test some error paths --- usersession/userd/launcher_test.go | 31 +++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/usersession/userd/launcher_test.go b/usersession/userd/launcher_test.go index 59848600a93..e2a9f2aa442 100644 --- a/usersession/userd/launcher_test.go +++ b/usersession/userd/launcher_test.go @@ -223,5 +223,34 @@ func (s *launcherSuite) TestOpenDesktopEntryEnvSucceedsWithGoodDesktopId(c *C) { err = s.launcher.OpenDesktopEntryEnv("mircade_mircade.desktop", []string{}, ":some-dbus-sender") c.Assert(err, IsNil) - c.Assert(cmd.Calls(), IsNil) +} + +func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadDesktopId(c *C) { + dirs.SetRootDir(c.MkDir()) + cmd := testutil.MockCommand(c, "/snap/bin/mircade", "true") + defer cmd.Restore() + + deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") + err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) + c.Assert(err, IsNil) + + err = s.launcher.OpenDesktopEntryEnv("not-mircade_mircade.desktop", []string{}, ":some-dbus-sender") + c.Assert(err, NotNil) +} + +func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadExecutable(c *C) { + dirs.SetRootDir(c.MkDir()) + cmd := testutil.MockCommand(c, "/snap/bin/mircade", "false") + defer cmd.Restore() + + deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") + err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) + c.Assert(err, IsNil) + + err = s.launcher.OpenDesktopEntryEnv("mircade_mircade.desktop", []string{}, ":some-dbus-sender") + c.Assert(err, NotNil) } From c6a5d1ef06d6c87c3965d285073faa6680f2583b Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 30 Jun 2020 16:13:26 +0100 Subject: [PATCH 061/114] Use camelCase --- usersession/userd/launcher.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index d9c519d6a3e..83298b1424e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -250,10 +250,10 @@ func existsOnFileSystem(desktopFile string) bool { // o .../foo/bar_baz/norf.desktop // o .../foo-bar_baz/norf.desktop // We're not required to diagnose multiple files matching the desktop file ID. -func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId []string) (string, error) { - desktopFile := filepath.Join(base_dir, strings.Join(splitFileId, "-")) +func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId []string) (string, error) { + desktopFile := filepath.Join(baseDir, strings.Join(splitFileId, "-")) - if desktopFile_exists(desktopFile) { + if desktopFileExists(desktopFile) { return desktopFile, nil } @@ -262,7 +262,7 @@ func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId // we're only checking dirs.SnapDesktopFilesDir (and not all entries in $XDG_DATA_DIRS). // For dirs.SnapDesktopFilesDir snapd will not create any subdirectories. for i := 1; i != len(splitFileId); i++ { - desktopFile, err := findDesktopFile(desktopFile_exists, filepath.Join(base_dir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + desktopFile, err := findDesktopFile(desktopFileExists, filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) if err == nil { return desktopFile, err } @@ -272,16 +272,16 @@ func findDesktopFile(desktopFile_exists fileExists, base_dir string, splitFileId } // desktopFileIDToFilename determines the path associated with a desktop file ID. -func desktopFileIDToFilename(desktopFile_exists fileExists, desktopFileID string) (string, error) { +func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) (string, error) { // OpenDesktopEntryEnv() currently only supports launching snap applications from // desktop files in /var/lib/snapd/desktop/applications and these desktop files are // written by snapd and considered safe for userd to process. // Since we only access /var/lib/snapd/desktop/applications, ignore // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - base_dir := dirs.SnapDesktopFilesDir + baseDir := dirs.SnapDesktopFilesDir - desktopFile, err := findDesktopFile(desktopFile_exists, base_dir, strings.Split(desktopFileID, "-")) + desktopFile, err := findDesktopFile(desktopFileExists, baseDir, strings.Split(desktopFileID, "-")) if err == nil { return desktopFile, nil From 04acb0c01f1e374279743f205ca0025d7e74ee1b Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Fri, 3 Jul 2020 15:00:58 +0800 Subject: [PATCH 062/114] tests: add a basic spread test for the dbus-launch interface --- .../main/interfaces-desktop-launch/task.yaml | 57 +++++++++++++++++++ .../test-app/bin/app.sh | 12 ++++ .../test-app/meta/gui/test-app.desktop | 5 ++ .../test-app/meta/snap.yaml | 9 +++ .../test-launcher/bin/launcher.sh | 6 ++ .../test-launcher/meta/snap.yaml | 9 +++ 6 files changed, 98 insertions(+) create mode 100644 tests/main/interfaces-desktop-launch/task.yaml create mode 100755 tests/main/interfaces-desktop-launch/test-app/bin/app.sh create mode 100644 tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop create mode 100644 tests/main/interfaces-desktop-launch/test-app/meta/snap.yaml create mode 100755 tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh create mode 100644 tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml diff --git a/tests/main/interfaces-desktop-launch/task.yaml b/tests/main/interfaces-desktop-launch/task.yaml new file mode 100644 index 00000000000..0a4e43efc16 --- /dev/null +++ b/tests/main/interfaces-desktop-launch/task.yaml @@ -0,0 +1,57 @@ +summary: Ensure that the desktop-launch interface works. + +details: | + The desktop-launch interface allows a snap to launch other snaps via + the desktop files they provide to the host system. + +systems: [-ubuntu-core-*] + +prepare: | + if ! tests.session has-session-systemd-and-dbus; then + exit 0 + fi + tests.session -u test prepare + +restore: | + if ! tests.session has-session-systemd-and-dbus; then + exit 0 + fi + tests.session -u test restore + rm -f ~test/snap/test-app/current/launch-data.txt + +execute: | + if ! tests.session has-session-systemd-and-dbus; then + exit 0 + fi + + #shellcheck source=tests/lib/snaps.sh + . "$TESTSLIB"/snaps.sh + + echo "Install the application snap" + install_local test-app + echo "The snap installs a desktop file" + [ -f /var/lib/snapd/desktop/applications/test-app_test-app.desktop ] + + echo "Install the launcher snap" + install_local test-launcher + + echo "The desktop-launch plug is initially disconnected" + snap connections test-launcher | MATCH "desktop-launch +test-launcher:desktop-launch +- +-" + + echo "The plug can be connected" + snap connect test-launcher:desktop-launch + snap connections test-launcher | MATCH "desktop-launch +test-launcher:desktop-launch +:desktop-launch +manual" + + echo "The launcher snap can launch other snaps via userd" + tests.session -u test exec test-launcher \ + test-app_test-app.desktop + + echo "The app snap records that it has been launched" + launch_data=~test/snap/test-app/current/launch-data.txt + retry -n 5 --wait 1 test -s "$launch_data" + + echo "The app was invoked with the arguments in the desktop file" + MATCH "^args=arg-before arg-after$" < "$launch_data" + + echo "The app was invoked with the environment requested by the launcher" + MATCH "^XDG_CURRENT_DESKTOP=spread-test$" < "$launch_data" diff --git a/tests/main/interfaces-desktop-launch/test-app/bin/app.sh b/tests/main/interfaces-desktop-launch/test-app/bin/app.sh new file mode 100755 index 00000000000..a4674c734db --- /dev/null +++ b/tests/main/interfaces-desktop-launch/test-app/bin/app.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +echo "App launched" + +cat << EOF > $SNAP_USER_DATA/launch-data.txt +args=$* +DISPLAY=${DISPLAY} +WAYLAND_DISPLAY=${WAYLAND_DISPLAY} +XDG_CURRENT_DESKTOP=${XDG_CURRENT_DESKTOP} +XDG_SESSION_DESKTOP=${XDG_SESSION_DESKTOP} +XDG_SESSION_TYPE=${XDG_SESSION_TYPE} +EOF diff --git a/tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop b/tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop new file mode 100644 index 00000000000..0c8fa63730b --- /dev/null +++ b/tests/main/interfaces-desktop-launch/test-app/meta/gui/test-app.desktop @@ -0,0 +1,5 @@ +[Desktop Entry] +Type=Application +Name=test-app +Comment=A desktop file for test-app +Exec=test-app arg-before %U arg-after diff --git a/tests/main/interfaces-desktop-launch/test-app/meta/snap.yaml b/tests/main/interfaces-desktop-launch/test-app/meta/snap.yaml new file mode 100644 index 00000000000..e037baf26c2 --- /dev/null +++ b/tests/main/interfaces-desktop-launch/test-app/meta/snap.yaml @@ -0,0 +1,9 @@ +name: test-app +version: 1.0 +summary: Basic desktop app test snap +description: A basic snap to act as a target for the launcher + +apps: + test-app: + command: bin/app.sh + plugs: [desktop] diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh new file mode 100755 index 00000000000..1b008e49bb4 --- /dev/null +++ b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +exec dbus-send --session --print-reply \ + --dest=io.snapcraft.Launcher /io/snapcraft/Launcher \ + io.snapcraft.Launcher.OpenDesktopEntryEnv \ + string:"$1" array:string:"XDG_CURRENT_DESKTOP=spread-test" diff --git a/tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml b/tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml new file mode 100644 index 00000000000..6092844b168 --- /dev/null +++ b/tests/main/interfaces-desktop-launch/test-launcher/meta/snap.yaml @@ -0,0 +1,9 @@ +name: test-launcher +version: 1.0 +summary: Basic desktop launcher snap +description: A basic snap that attempts to launch other snaps + +apps: + test-launcher: + command: bin/launcher.sh + plugs: [desktop-launch] From 8ba9b513cc1bbe979bf9bfd28dab9a1ec11d2f26 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 3 Jul 2020 16:33:34 +0100 Subject: [PATCH 063/114] Update tests/main/interfaces-desktop-launch/test-app/bin/app.sh Co-authored-by: James Henstridge --- tests/main/interfaces-desktop-launch/test-app/bin/app.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/interfaces-desktop-launch/test-app/bin/app.sh b/tests/main/interfaces-desktop-launch/test-app/bin/app.sh index a4674c734db..4849d6854b3 100755 --- a/tests/main/interfaces-desktop-launch/test-app/bin/app.sh +++ b/tests/main/interfaces-desktop-launch/test-app/bin/app.sh @@ -2,7 +2,7 @@ echo "App launched" -cat << EOF > $SNAP_USER_DATA/launch-data.txt +cat << EOF > "$SNAP_USER_DATA"/launch-data.txt args=$* DISPLAY=${DISPLAY} WAYLAND_DISPLAY=${WAYLAND_DISPLAY} From 2ecb2b3b9a9b7db88494b625d604788e2d35717e Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 6 Aug 2020 12:00:20 +0100 Subject: [PATCH 064/114] Add access to /var/lib/snapd/desktop/icons --- interfaces/builtin/desktop_launch.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index 186bd9c0dbc..da9a07990d5 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -38,8 +38,9 @@ const desktopLaunchBaseDeclarationSlots = ` const desktopLaunchConnectedPlugAppArmor = ` # Description: Can identify and launch other snaps. -# Access to the desktop files installed by snaps +# Access to the desktop and icon files installed by snaps /var/lib/snapd/desktop/applications/{,*} r, +/var/lib/snapd/desktop/icons/{,*} r, #include From 8686434736e107670e2381b49018826c52e27bd8 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 6 Aug 2020 12:23:04 +0100 Subject: [PATCH 065/114] Add RegularFileExists() to osutil --- osutil/stat.go | 7 +++++++ usersession/userd/launcher.go | 11 ++--------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osutil/stat.go b/osutil/stat.go index 1d84c0aec4e..db3451fbecf 100644 --- a/osutil/stat.go +++ b/osutil/stat.go @@ -124,3 +124,10 @@ func DirExists(fn string) (exists bool, isDir bool, err error) { } return true, st.IsDir(), nil } + +// RegularFileExists checks whether a given path exists, and if so whether it is a regular file. +func RegularFileExists(desktopFile string) bool { + fileStat, err := os.Stat(desktopFile) + + return err == nil && fileStat.Mode().IsRegular() +} diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index c6959d483b5..4526c70a2a3 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -34,6 +34,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/strutil" "github.com/snapcore/snapd/strutil/shlex" @@ -202,7 +203,7 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { // DBus interface. The desktopFileID is described here: // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sender dbus.Sender) *dbus.Error { - desktopFile, err := desktopFileIDToFilename(existsOnFileSystem, desktopFileID) + desktopFile, err := desktopFileIDToFilename(osutil.RegularFileExists, desktopFileID) if err != nil { return dbus.MakeFailedError(err) } @@ -246,14 +247,6 @@ func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sende type fileExists func(string) bool -func existsOnFileSystem(desktopFile string) bool { - fileStat, err := os.Stat(desktopFile) - - // We only support files in /var/lib/snapd/desktop/applications and they should - // all be regular files. - return err == nil && fileStat.Mode().IsRegular() -} - // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. // Per https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id, // if desktop entries have dashes in the name ('-'), this could be an indication of subdirectories, so search From fde5f974b22943dc6811f7a83b27f3593ede7538 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 6 Aug 2020 12:29:31 +0100 Subject: [PATCH 066/114] We don't want /foo2 to be treated as under /foo --- usersession/userd/launcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 4526c70a2a3..beb201edd2e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -301,7 +301,7 @@ func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) // 2. the desktop file itself and all directories above it are root owned without group/other write // 3. the Exec line has an expected prefix func verifyDesktopFileLocation(desktopFile string) error { - if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir) { + if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir + "/") { // We currently only support launching snap applications from desktop files in // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and // considered safe for userd to process. If other directories are added in the future, From 932e06ca69319e06aea32232befca134491f9a64 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 6 Aug 2020 12:38:43 +0100 Subject: [PATCH 067/114] Drop contentious checks --- usersession/userd/launcher.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index beb201edd2e..f00c93ee577 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -313,22 +313,6 @@ func verifyDesktopFileLocation(desktopFile string) error { return fmt.Errorf("desktop file has unclean path: %q", desktopFile) } - // When running in a test environment dirs.SnapDesktopFilesDir is under /tmp/ and - // cannot pass these checks. Unless there's a way to subvert dirs.SnapDesktopFilesDir - // this hack should be harmless. Maybe there's a better way to fake it? - if !strings.HasPrefix(dirs.SnapDesktopFilesDir, "/tmp/") { - - last := "/" - for _, val := range strings.Split(desktopFile, "/") { - checkPath := filepath.Join(last, val) // val is "" for root ('/') - fileStat, err := os.Stat(checkPath) - if err != nil || !(fileStat.Mode().IsDir() || fileStat.Mode().IsRegular()) || (fileStat.Mode().Perm()&0022) != 0 || fileStat.Sys().(*syscall.Stat_t).Uid != 0 { - return fmt.Errorf("cannot verify path %q", checkPath) - } - last = checkPath - } - } - return nil } From 876da6096cac7762520026e3928789cf0f4edaa6 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 6 Aug 2020 12:57:31 +0100 Subject: [PATCH 068/114] Error out on unexpected exec variables --- usersession/userd/launcher.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index f00c93ee577..0718776052b 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -373,7 +373,12 @@ func parseExecCommand(exec_command string) ([]string, error) { args[i] = strings.TrimPrefix(args[i], "%") i++ } else if strings.HasPrefix(args[i], "%") { - args = append(args[:i], args[i+1:]...) + switch args[i] { + case "%f", "%F", "%u", "%U": + args = append(args[:i], args[i+1:]...) + default: + return []string{}, fmt.Errorf("cannot run %q", exec_command) + } } else { i++ } From 461865e5a0206aefc188690d40cd152cf63bbffc Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 6 Aug 2020 14:13:40 +0100 Subject: [PATCH 069/114] Reword comment --- usersession/userd/launcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 0718776052b..f5c06307e34 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -265,8 +265,8 @@ func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId [ // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID. // Maybe this is overkill: At the time of writing, the only use is in desktopFileIDToFilename() and there - // we're only checking dirs.SnapDesktopFilesDir (and not all entries in $XDG_DATA_DIRS). - // For dirs.SnapDesktopFilesDir snapd will not create any subdirectories. + // we're only checking dirs.SnapDesktopFilesDir (not all entries in $XDG_DATA_DIRS) and we know that snapd + // does not create subdirectories in that location. for i := 1; i != len(splitFileId); i++ { desktopFile, err := findDesktopFile(desktopFileExists, filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) if err == nil { From 6b25e2d233d88ffc414a5dbc1c189424b5b9228e Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 6 Aug 2020 14:28:02 +0100 Subject: [PATCH 070/114] Update test Exec lines with exec variables we do support --- usersession/userd/launcher.go | 4 ++-- usersession/userd/launcher_internal_test.go | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index f5c06307e34..c3d23192f33 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -374,7 +374,7 @@ func parseExecCommand(exec_command string) ([]string, error) { i++ } else if strings.HasPrefix(args[i], "%") { switch args[i] { - case "%f", "%F", "%u", "%U": + case "%f", "%F", "%u", "%U", "%i": args = append(args[:i], args[i+1:]...) default: return []string{}, fmt.Errorf("cannot run %q", exec_command) @@ -395,7 +395,7 @@ func parseExecCommand(exec_command string) ([]string, error) { // The file descriptor cannot be opened using O_PATH and must refer to // a regular file or to a directory. The symlink at /proc/self/fd/ // is read to determine the filename. The descriptor is also fstat'ed -// and the resulting device number and inode number are compared to +// and the resulting device number and inode number are compared to, "%d", "%D", "%n", "%N", // stat on the path determined earlier. The numbers must match. func fdToFilename(fd int) (string, error) { flags, err := sys.FcntlGetFl(fd) diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/launcher_internal_test.go index 3665aa86021..f8a77367547 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/launcher_internal_test.go @@ -406,14 +406,14 @@ func (s *launcherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) {"/snap/bin/foo '\"-f bar\"'", []string{"/snap/bin/foo", "\"-f bar\""}}, // valid with exec variables stripped out {"/snap/bin/foo -f %U", []string{"/snap/bin/foo", "-f"}}, - {"/snap/bin/foo -f %U %Y", []string{"/snap/bin/foo", "-f"}}, + {"/snap/bin/foo -f %U %i", []string{"/snap/bin/foo", "-f"}}, {"/snap/bin/foo -f %U bar", []string{"/snap/bin/foo", "-f", "bar"}}, - {"/snap/bin/foo -f %U bar %Y", []string{"/snap/bin/foo", "-f", "bar"}}, + {"/snap/bin/foo -f %U bar %i", []string{"/snap/bin/foo", "-f", "bar"}}, // valid with mixture of literal '%' and exec variables {"/snap/bin/foo -f %U %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, - {"/snap/bin/foo -f %U %Y %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, - {"/snap/bin/foo -f %U %%bar %Y", []string{"/snap/bin/foo", "-f", "%bar"}}, - {"/snap/bin/foo -f %%bar %U %Y", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %U %i %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %U %%bar %i", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %%bar %U %i", []string{"/snap/bin/foo", "-f", "%bar"}}, } for _, test := range exec_command { From d49bc2ed38274f14fb7e84cecc9e2b92bf63e740 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 20 Aug 2020 14:19:18 +0100 Subject: [PATCH 071/114] go fmt --- usersession/userd/launcher.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index c3d23192f33..dccde36d42e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -301,7 +301,7 @@ func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) // 2. the desktop file itself and all directories above it are root owned without group/other write // 3. the Exec line has an expected prefix func verifyDesktopFileLocation(desktopFile string) error { - if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir + "/") { + if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir+"/") { // We currently only support launching snap applications from desktop files in // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and // considered safe for userd to process. If other directories are added in the future, @@ -373,12 +373,12 @@ func parseExecCommand(exec_command string) ([]string, error) { args[i] = strings.TrimPrefix(args[i], "%") i++ } else if strings.HasPrefix(args[i], "%") { - switch args[i] { - case "%f", "%F", "%u", "%U", "%i": - args = append(args[:i], args[i+1:]...) - default: - return []string{}, fmt.Errorf("cannot run %q", exec_command) - } + switch args[i] { + case "%f", "%F", "%u", "%U", "%i": + args = append(args[:i], args[i+1:]...) + default: + return []string{}, fmt.Errorf("cannot run %q", exec_command) + } } else { i++ } From 3b94f6787fe77b322b0b977169d1905f449f0c68 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 20 Aug 2020 14:21:55 +0100 Subject: [PATCH 072/114] Comments adjusted to match code --- usersession/userd/launcher.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index dccde36d42e..8b43809b53a 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -296,10 +296,8 @@ func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) } -// verifyDesktopFileLocation checks the desktop file location and access. -// 1. we only consider desktop files in dirs.SnapDesktopFilesDir -// 2. the desktop file itself and all directories above it are root owned without group/other write -// 3. the Exec line has an expected prefix +// verifyDesktopFileLocation checks the desktop file location: +// we only consider desktop files in dirs.SnapDesktopFilesDir func verifyDesktopFileLocation(desktopFile string) error { if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir+"/") { // We currently only support launching snap applications from desktop files in From bba4b04351f131ec9647c4d0e8e02f3866d596aa Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 20 Aug 2020 16:19:23 +0100 Subject: [PATCH 073/114] Use systemd-run to launch apps --- usersession/userd/launcher.go | 15 ++------------- usersession/userd/launcher_test.go | 6 +++--- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 8b43809b53a..da3ea8bca48 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -223,20 +223,9 @@ func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sende return dbus.MakeFailedError(err) } - cmd := exec.Command(args[0], args[1:]...) - cmd.Env = os.Environ() - for _, e := range env { - if !strutil.ListContains(allowedEnvVars, strings.SplitN(e, "=", 2)[0]) { - return dbus.MakeFailedError(fmt.Errorf("Supplied environment variable %q is not allowed", e)) - } + args = append([]string{"systemd-run", "--user", "--"}, args...) - cmd.Env = append(cmd.Env, e) - } - - // XXX: this avoids defunct processes but causes userd to persist - // until all children are gone (currently, this is not a problem since - // userd is long running once started) - go cmd.Wait() + cmd := exec.Command(args[0], args[1:]...) if cmd.Run() != nil { return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) diff --git a/usersession/userd/launcher_test.go b/usersession/userd/launcher_test.go index 1010460ef62..14af334bc0e 100644 --- a/usersession/userd/launcher_test.go +++ b/usersession/userd/launcher_test.go @@ -212,7 +212,7 @@ Categories=Game func (s *launcherSuite) TestOpenDesktopEntryEnvSucceedsWithGoodDesktopId(c *C) { dirs.SetRootDir(c.MkDir()) - cmd := testutil.MockCommand(c, "/snap/bin/mircade", "true") + cmd := testutil.MockCommand(c, "systemd-run", "true") defer cmd.Restore() deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") @@ -227,7 +227,7 @@ func (s *launcherSuite) TestOpenDesktopEntryEnvSucceedsWithGoodDesktopId(c *C) { func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadDesktopId(c *C) { dirs.SetRootDir(c.MkDir()) - cmd := testutil.MockCommand(c, "/snap/bin/mircade", "true") + cmd := testutil.MockCommand(c, "systemd-run", "true") defer cmd.Restore() deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") @@ -242,7 +242,7 @@ func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadDesktopId(c *C) { func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadExecutable(c *C) { dirs.SetRootDir(c.MkDir()) - cmd := testutil.MockCommand(c, "/snap/bin/mircade", "false") + cmd := testutil.MockCommand(c, "systemd-run", "false") defer cmd.Restore() deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") From 944a9f6fa4263bfa70272c27041c069ca531517d Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 20 Aug 2020 16:36:29 +0100 Subject: [PATCH 074/114] Don't pass environment --- interfaces/builtin/desktop_launch.go | 2 +- usersession/userd/launcher.go | 18 ++++-------------- usersession/userd/launcher_test.go | 12 ++++++------ 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index da9a07990d5..2d6e302ae36 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -48,7 +48,7 @@ dbus (send) bus=session path=/io/snapcraft/Launcher interface=io.snapcraft.Launcher - member=OpenDesktopEntryEnv + member=OpenDesktopEntry peer=(label=unconfined), ` diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index da3ea8bca48..241f04d0c46 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -53,9 +53,8 @@ const launcherIntrospectionXML = ` - + - @@ -125,15 +124,6 @@ var ( // - https://github.com/snapcore/snapd/pull/8910 "zoomus", } - - // allowedEnvVars are those environment variables that snaps who have access - // to OpenDesktopEntryEnv() can set for the launched snap's environment. - // - DISPLAY: set the X11 display - // - WAYLAND_DISPLAY: set the wayland display - // - XDG_CURRENT_DESKTOP: set identifiers for desktop environments - // - XDG_SESSION_DESKTOP: set identifier for the desktop environment - // - XDG_SESSION_TYPE: set the session type (e.g. "wayland" or "x11") - allowedEnvVars = []string{"DISPLAY", "WAYLAND_DISPLAY", "XDG_CURRENT_DESKTOP", "XDG_SESSION_DESKTOP", "XDG_SESSION_TYPE"} ) // Launcher implements the 'io.snapcraft.Launcher' DBus interface. @@ -199,10 +189,10 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { return nil } -// OpenDesktopEntryEnv implements the 'OpenDesktopEntryEnv' method of the 'io.snapcraft.Launcher' +// OpenDesktopEntry implements the 'OpenDesktopEntry' method of the 'io.snapcraft.DesktopLauncher' // DBus interface. The desktopFileID is described here: // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id -func (s *Launcher) OpenDesktopEntryEnv(desktopFileID string, env []string, sender dbus.Sender) *dbus.Error { +func (s *Launcher) OpenDesktopEntry(desktopFileID string, sender dbus.Sender) *dbus.Error { desktopFile, err := desktopFileIDToFilename(osutil.RegularFileExists, desktopFileID) if err != nil { return dbus.MakeFailedError(err) @@ -269,7 +259,7 @@ func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId [ // desktopFileIDToFilename determines the path associated with a desktop file ID. func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) (string, error) { - // OpenDesktopEntryEnv() currently only supports launching snap applications from + // OpenDesktopEntry() currently only supports launching snap applications from // desktop files in /var/lib/snapd/desktop/applications and these desktop files are // written by snapd and considered safe for userd to process. // Since we only access /var/lib/snapd/desktop/applications, ignore diff --git a/usersession/userd/launcher_test.go b/usersession/userd/launcher_test.go index 14af334bc0e..7a1d2752697 100644 --- a/usersession/userd/launcher_test.go +++ b/usersession/userd/launcher_test.go @@ -210,7 +210,7 @@ Type=Application Categories=Game ` -func (s *launcherSuite) TestOpenDesktopEntryEnvSucceedsWithGoodDesktopId(c *C) { +func (s *launcherSuite) TestOpenDesktopEntrySucceedsWithGoodDesktopId(c *C) { dirs.SetRootDir(c.MkDir()) cmd := testutil.MockCommand(c, "systemd-run", "true") defer cmd.Restore() @@ -221,11 +221,11 @@ func (s *launcherSuite) TestOpenDesktopEntryEnvSucceedsWithGoodDesktopId(c *C) { err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) c.Assert(err, IsNil) - err = s.launcher.OpenDesktopEntryEnv("mircade_mircade.desktop", []string{}, ":some-dbus-sender") + err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") c.Assert(err, IsNil) } -func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadDesktopId(c *C) { +func (s *launcherSuite) TestOpenDesktopEntryFailsWithBadDesktopId(c *C) { dirs.SetRootDir(c.MkDir()) cmd := testutil.MockCommand(c, "systemd-run", "true") defer cmd.Restore() @@ -236,11 +236,11 @@ func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadDesktopId(c *C) { err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) c.Assert(err, IsNil) - err = s.launcher.OpenDesktopEntryEnv("not-mircade_mircade.desktop", []string{}, ":some-dbus-sender") + err = s.launcher.OpenDesktopEntry("not-mircade_mircade.desktop", ":some-dbus-sender") c.Assert(err, NotNil) } -func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadExecutable(c *C) { +func (s *launcherSuite) TestOpenDesktopEntryFailsWithBadExecutable(c *C) { dirs.SetRootDir(c.MkDir()) cmd := testutil.MockCommand(c, "systemd-run", "false") defer cmd.Restore() @@ -251,6 +251,6 @@ func (s *launcherSuite) TestOpenDesktopEntryEnvFailsWithBadExecutable(c *C) { err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) c.Assert(err, IsNil) - err = s.launcher.OpenDesktopEntryEnv("mircade_mircade.desktop", []string{}, ":some-dbus-sender") + err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") c.Assert(err, NotNil) } From df183409a97c2648fa7da73cbf04336dae9b69ab Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 28 Aug 2020 15:22:03 +0100 Subject: [PATCH 075/114] Failed PrivilegedDesktopLauncher --- interfaces/builtin/desktop_launch.go | 2 +- usersession/userd/launcher.go | 191 ---------- .../userd/privileged_desktop_launcher.go | 336 ++++++++++++++++++ 3 files changed, 337 insertions(+), 192 deletions(-) create mode 100644 usersession/userd/privileged_desktop_launcher.go diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index 2d6e302ae36..f800313f83f 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -47,7 +47,7 @@ const desktopLaunchConnectedPlugAppArmor = ` dbus (send) bus=session path=/io/snapcraft/Launcher - interface=io.snapcraft.Launcher + interface=io.snapcraft.PrivilegedDesktopLauncher member=OpenDesktopEntry peer=(label=unconfined), ` diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 241f04d0c46..9fa3ce36762 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -20,7 +20,6 @@ package userd import ( - "bufio" "fmt" "net/url" "os" @@ -35,9 +34,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/strutil" - "github.com/snapcore/snapd/strutil/shlex" "github.com/snapcore/snapd/usersession/userd/ui" ) @@ -224,194 +221,6 @@ func (s *Launcher) OpenDesktopEntry(desktopFileID string, sender dbus.Sender) *d return nil } -type fileExists func(string) bool - -// findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. -// Per https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id, -// if desktop entries have dashes in the name ('-'), this could be an indication of subdirectories, so search -// for those too. Eg, given foo-bar_baz_norf.desktop the following are searched for: -// o .../foo-bar_baz-norf.desktop -// o .../foo/bar_baz-norf.desktop -// o .../foo/bar_baz/norf.desktop -// o .../foo-bar_baz/norf.desktop -// We're not required to diagnose multiple files matching the desktop file ID. -func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId []string) (string, error) { - desktopFile := filepath.Join(baseDir, strings.Join(splitFileId, "-")) - - if desktopFileExists(desktopFile) { - return desktopFile, nil - } - - // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID. - // Maybe this is overkill: At the time of writing, the only use is in desktopFileIDToFilename() and there - // we're only checking dirs.SnapDesktopFilesDir (not all entries in $XDG_DATA_DIRS) and we know that snapd - // does not create subdirectories in that location. - for i := 1; i != len(splitFileId); i++ { - desktopFile, err := findDesktopFile(desktopFileExists, filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) - if err == nil { - return desktopFile, err - } - } - - return "", fmt.Errorf("could not find desktop file") -} - -// desktopFileIDToFilename determines the path associated with a desktop file ID. -func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) (string, error) { - - // OpenDesktopEntry() currently only supports launching snap applications from - // desktop files in /var/lib/snapd/desktop/applications and these desktop files are - // written by snapd and considered safe for userd to process. - // Since we only access /var/lib/snapd/desktop/applications, ignore - // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - baseDir := dirs.SnapDesktopFilesDir - - desktopFile, err := findDesktopFile(desktopFileExists, baseDir, strings.Split(desktopFileID, "-")) - - if err == nil { - return desktopFile, nil - } - - return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) -} - -// verifyDesktopFileLocation checks the desktop file location: -// we only consider desktop files in dirs.SnapDesktopFilesDir -func verifyDesktopFileLocation(desktopFile string) error { - if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir+"/") { - // We currently only support launching snap applications from desktop files in - // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and - // considered safe for userd to process. If other directories are added in the future, - // verifyDesktopFileLocation() and parseExecCommand() may need to be updated. - return fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") - } - - if filepath.Clean(desktopFile) != desktopFile { - return fmt.Errorf("desktop file has unclean path: %q", desktopFile) - } - - return nil -} - -// readExecCommandFromDesktopFile parses the desktop file to get the Exec entry and -// checks that the BAMF_DESKTOP_FILE_HINT is present and refers to the desktop file. -func readExecCommandFromDesktopFile(desktopFile string) (string, error) { - var launch string - - file, err := os.Open(desktopFile) - if err != nil { - return launch, err - } - defer file.Close() - scanner := bufio.NewScanner(file) - - in_desktop_section := false - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - - if line == "[Desktop Entry]" { - in_desktop_section = true - } else if strings.HasPrefix(line, "[Desktop Action ") { - // maybe later we'll add support here - in_desktop_section = false - } else if strings.HasPrefix(line, "[") { - in_desktop_section = false - } else if in_desktop_section && strings.HasPrefix(line, "Exec=") { - launch = strings.TrimPrefix(line, "Exec=") - break - } - } - - expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s /snap/bin/", desktopFile) - if !strings.HasPrefix(launch, expectedPrefix) { - return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, launch) - } - - return launch, nil -} - -// Parse the Exec command by stripping any exec variables. -// Passing exec variables (eg, %foo) between confined snaps is unsupported. Currently, -// we do not have support for passing them in the D-Bus API but there are security -// implications that must be thought through regarding the influence of the launching -// snap over the launcher wrt exec variables. For now we simply filter them out. -// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables -func parseExecCommand(exec_command string) ([]string, error) { - args, err := shlex.Split(exec_command) - if err != nil { - return []string{}, err - } - - i := 0 - for { - // We want to keep literal '%' (expressed as '%%') but filter our exec variables - // like '%foo' - if strings.HasPrefix(args[i], "%%") { - args[i] = strings.TrimPrefix(args[i], "%") - i++ - } else if strings.HasPrefix(args[i], "%") { - switch args[i] { - case "%f", "%F", "%u", "%U", "%i": - args = append(args[:i], args[i+1:]...) - default: - return []string{}, fmt.Errorf("cannot run %q", exec_command) - } - } else { - i++ - } - - if i == len(args) { - break - } - } - return args, nil -} - -// fdToFilename determines the path associated with an open file descriptor. -// -// The file descriptor cannot be opened using O_PATH and must refer to -// a regular file or to a directory. The symlink at /proc/self/fd/ -// is read to determine the filename. The descriptor is also fstat'ed -// and the resulting device number and inode number are compared to, "%d", "%D", "%n", "%N", -// stat on the path determined earlier. The numbers must match. -func fdToFilename(fd int) (string, error) { - flags, err := sys.FcntlGetFl(fd) - if err != nil { - return "", err - } - // File descriptors opened with O_PATH do not imply access to - // the file in question. - if flags&sys.O_PATH != 0 { - return "", fmt.Errorf("cannot use file descriptors opened using O_PATH") - } - - // Determine the file name associated with the passed file descriptor. - filename, err := os.Readlink(fmt.Sprintf("/proc/self/fd/%d", fd)) - if err != nil { - return "", err - } - - var fileStat, fdStat syscall.Stat_t - if err := syscall.Stat(filename, &fileStat); err != nil { - return "", err - } - if err := syscall.Fstat(fd, &fdStat); err != nil { - return "", err - } - - // Sanity check to ensure we've got the right file - if fdStat.Dev != fileStat.Dev || fdStat.Ino != fileStat.Ino { - return "", fmt.Errorf("cannot determine file name") - } - - fileType := fileStat.Mode & syscall.S_IFMT - if fileType != syscall.S_IFREG && fileType != syscall.S_IFDIR { - return "", fmt.Errorf("cannot open anything other than regular files or directories") - } - - return filename, nil -} - func (s *Launcher) OpenFile(parentWindow string, clientFd dbus.UnixFD, sender dbus.Sender) *dbus.Error { // godbus transfers ownership of this file descriptor to us fd := int(clientFd) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go new file mode 100644 index 00000000000..99fc041b44d --- /dev/null +++ b/usersession/userd/privileged_desktop_launcher.go @@ -0,0 +1,336 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2019 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 . + * + */ + +package userd + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/godbus/dbus" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/osutil" + "github.com/snapcore/snapd/osutil/sys" + "github.com/snapcore/snapd/strutil/shlex" + "github.com/snapcore/snapd/usersession/userd/ui" +) + +const privilegedLauncherIntrospectionXML = ` + + + + + + + + + + + +` + +// PrivilegedDesktopLauncher implements the 'io.snapcraft.PrivilegedDesktopLauncher' DBus interface. +type PrivilegedDesktopLauncher struct { + conn *dbus.Conn +} + +// Name returns the name of the interface this object implements +func (s *PrivilegedDesktopLauncher) Name() string { + return "io.snapcraft.PrivilegedDesktopLauncher" +} + +// BasePath returns the base path of the object +func (s *PrivilegedDesktopLauncher) BasePath() dbus.ObjectPath { + return "/io/snapcraft/PrivilegedDesktopLauncher" +} + +// IntrospectionData gives the XML formatted introspection description +// of the DBus service. +func (s *PrivilegedDesktopLauncher) IntrospectionData() string { + return privilegedLauncherIntrospectionXML +} + +// OpenDesktopEntry implements the 'OpenDesktopEntry' method of the 'io.snapcraft.DesktopLauncher' +// DBus interface. The desktopFileID is described here: +// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id +func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sender dbus.Sender) *dbus.Error { + desktopFile, err := desktopFileIDToFilename(osutil.RegularFileExists, desktopFileID) + if err != nil { + return dbus.MakeFailedError(err) + } + + err = verifyDesktopFileLocation(desktopFile) + if err != nil { + return dbus.MakeFailedError(err) + } + + exec_command, err := readExecCommandFromDesktopFile(desktopFile) + if err != nil { + return dbus.MakeFailedError(err) + } + + args, err := parseExecCommand(exec_command) + if err != nil { + return dbus.MakeFailedError(err) + } + + args = append([]string{"systemd-run", "--user", "--"}, args...) + + cmd := exec.Command(args[0], args[1:]...) + + if cmd.Run() != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) + } + + return nil +} + + +type fileExists func(string) bool + +// findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. +// Per https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id, +// if desktop entries have dashes in the name ('-'), this could be an indication of subdirectories, so search +// for those too. Eg, given foo-bar_baz_norf.desktop the following are searched for: +// o .../foo-bar_baz-norf.desktop +// o .../foo/bar_baz-norf.desktop +// o .../foo/bar_baz/norf.desktop +// o .../foo-bar_baz/norf.desktop +// We're not required to diagnose multiple files matching the desktop file ID. +func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId []string) (string, error) { + desktopFile := filepath.Join(baseDir, strings.Join(splitFileId, "-")) + + if desktopFileExists(desktopFile) { + return desktopFile, nil + } + + // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID. + // Maybe this is overkill: At the time of writing, the only use is in desktopFileIDToFilename() and there + // we're only checking dirs.SnapDesktopFilesDir (not all entries in $XDG_DATA_DIRS) and we know that snapd + // does not create subdirectories in that location. + for i := 1; i != len(splitFileId); i++ { + desktopFile, err := findDesktopFile(desktopFileExists, filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + if err == nil { + return desktopFile, err + } + } + + return "", fmt.Errorf("could not find desktop file") +} + +// desktopFileIDToFilename determines the path associated with a desktop file ID. +func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) (string, error) { + + // OpenDesktopEntry() currently only supports launching snap applications from + // desktop files in /var/lib/snapd/desktop/applications and these desktop files are + // written by snapd and considered safe for userd to process. + // Since we only access /var/lib/snapd/desktop/applications, ignore + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + baseDir := dirs.SnapDesktopFilesDir + + desktopFile, err := findDesktopFile(desktopFileExists, baseDir, strings.Split(desktopFileID, "-")) + + if err == nil { + return desktopFile, nil + } + + return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) +} + +// verifyDesktopFileLocation checks the desktop file location: +// we only consider desktop files in dirs.SnapDesktopFilesDir +func verifyDesktopFileLocation(desktopFile string) error { + if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir+"/") { + // We currently only support launching snap applications from desktop files in + // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and + // considered safe for userd to process. If other directories are added in the future, + // verifyDesktopFileLocation() and parseExecCommand() may need to be updated. + return fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") + } + + if filepath.Clean(desktopFile) != desktopFile { + return fmt.Errorf("desktop file has unclean path: %q", desktopFile) + } + + return nil +} + +// readExecCommandFromDesktopFile parses the desktop file to get the Exec entry and +// checks that the BAMF_DESKTOP_FILE_HINT is present and refers to the desktop file. +func readExecCommandFromDesktopFile(desktopFile string) (string, error) { + var launch string + + file, err := os.Open(desktopFile) + if err != nil { + return launch, err + } + defer file.Close() + scanner := bufio.NewScanner(file) + + in_desktop_section := false + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if line == "[Desktop Entry]" { + in_desktop_section = true + } else if strings.HasPrefix(line, "[Desktop Action ") { + // maybe later we'll add support here + in_desktop_section = false + } else if strings.HasPrefix(line, "[") { + in_desktop_section = false + } else if in_desktop_section && strings.HasPrefix(line, "Exec=") { + launch = strings.TrimPrefix(line, "Exec=") + break + } + } + + expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s /snap/bin/", desktopFile) + if !strings.HasPrefix(launch, expectedPrefix) { + return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, launch) + } + + return launch, nil +} + +// Parse the Exec command by stripping any exec variables. +// Passing exec variables (eg, %foo) between confined snaps is unsupported. Currently, +// we do not have support for passing them in the D-Bus API but there are security +// implications that must be thought through regarding the influence of the launching +// snap over the launcher wrt exec variables. For now we simply filter them out. +// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables +func parseExecCommand(exec_command string) ([]string, error) { + args, err := shlex.Split(exec_command) + if err != nil { + return []string{}, err + } + + i := 0 + for { + // We want to keep literal '%' (expressed as '%%') but filter our exec variables + // like '%foo' + if strings.HasPrefix(args[i], "%%") { + args[i] = strings.TrimPrefix(args[i], "%") + i++ + } else if strings.HasPrefix(args[i], "%") { + switch args[i] { + case "%f", "%F", "%u", "%U", "%i": + args = append(args[:i], args[i+1:]...) + default: + return []string{}, fmt.Errorf("cannot run %q", exec_command) + } + } else { + i++ + } + + if i == len(args) { + break + } + } + return args, nil +} + +// fdToFilename determines the path associated with an open file descriptor. +// +// The file descriptor cannot be opened using O_PATH and must refer to +// a regular file or to a directory. The symlink at /proc/self/fd/ +// is read to determine the filename. The descriptor is also fstat'ed +// and the resulting device number and inode number are compared to, "%d", "%D", "%n", "%N", +// stat on the path determined earlier. The numbers must match. +func fdToFilename(fd int) (string, error) { + flags, err := sys.FcntlGetFl(fd) + if err != nil { + return "", err + } + // File descriptors opened with O_PATH do not imply access to + // the file in question. + if flags&sys.O_PATH != 0 { + return "", fmt.Errorf("cannot use file descriptors opened using O_PATH") + } + + // Determine the file name associated with the passed file descriptor. + filename, err := os.Readlink(fmt.Sprintf("/proc/self/fd/%d", fd)) + if err != nil { + return "", err + } + + var fileStat, fdStat syscall.Stat_t + if err := syscall.Stat(filename, &fileStat); err != nil { + return "", err + } + if err := syscall.Fstat(fd, &fdStat); err != nil { + return "", err + } + + // Sanity check to ensure we've got the right file + if fdStat.Dev != fileStat.Dev || fdStat.Ino != fileStat.Ino { + return "", fmt.Errorf("cannot determine file name") + } + + fileType := fileStat.Mode & syscall.S_IFMT + if fileType != syscall.S_IFREG && fileType != syscall.S_IFDIR { + return "", fmt.Errorf("cannot open anything other than regular files or directories") + } + + return filename, nil +} + +func (s *PrivilegedDesktopLauncher) OpenFile(parentWindow string, clientFd dbus.UnixFD, sender dbus.Sender) *dbus.Error { + // godbus transfers ownership of this file descriptor to us + fd := int(clientFd) + defer syscall.Close(fd) + + filename, err := fdToFilename(fd) + if err != nil { + return dbus.MakeFailedError(err) + } + + snap, err := snapFromSender(s.conn, sender) + if err != nil { + return dbus.MakeFailedError(err) + } + dialog, err := ui.New() + if err != nil { + return dbus.MakeFailedError(err) + } + answeredYes := dialog.YesNo( + i18n.G("Allow opening file?"), + fmt.Sprintf(i18n.G("Allow snap %q to open file %q?"), snap, filename), + &ui.DialogOptions{ + Timeout: 5 * 60 * time.Second, + Footer: i18n.G("This dialog will close automatically after 5 minutes of inactivity."), + }, + ) + if !answeredYes { + return dbus.MakeFailedError(fmt.Errorf("permission denied")) + } + + if err = exec.Command("xdg-open", filename).Run(); err != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot open supplied URL")) + } + + return nil +} From aa309b3cbb7b4baa5ca49b656619b50f8155cda7 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 16 Sep 2020 11:18:37 +0100 Subject: [PATCH 076/114] Make path match interface --- data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in | 4 ++++ interfaces/builtin/desktop_launch.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in diff --git a/data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in b/data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in new file mode 100644 index 00000000000..a0d49176971 --- /dev/null +++ b/data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in @@ -0,0 +1,4 @@ +[D-BUS Service] +Name=io.snapcraft.PrivilegedDesktopLauncher +Exec=@bindir@/snap userd +AssumedAppArmorLabel=unconfined diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index f800313f83f..6abe3cb6ce9 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -46,7 +46,7 @@ const desktopLaunchConnectedPlugAppArmor = ` dbus (send) bus=session - path=/io/snapcraft/Launcher + path=/io/snapcraft/PrivilegedDesktopLauncher interface=io.snapcraft.PrivilegedDesktopLauncher member=OpenDesktopEntry peer=(label=unconfined), From d5505c76c4ebb9419c12d4e65a06e4997087977a Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 12 Oct 2020 14:40:18 +0100 Subject: [PATCH 077/114] Update to reflect snapcore#9370 --- data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in | 4 ---- interfaces/builtin/desktop_launch.go | 2 +- usersession/userd/launcher.go | 1 - usersession/userd/privileged_desktop_launcher.go | 6 +++--- usersession/userd/userd.go | 1 + 5 files changed, 5 insertions(+), 9 deletions(-) delete mode 100644 data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in diff --git a/data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in b/data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in deleted file mode 100644 index a0d49176971..00000000000 --- a/data/dbus/io.snapcraft.PrivilegedDesktopLauncher.service.in +++ /dev/null @@ -1,4 +0,0 @@ -[D-BUS Service] -Name=io.snapcraft.PrivilegedDesktopLauncher -Exec=@bindir@/snap userd -AssumedAppArmorLabel=unconfined diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index 6abe3cb6ce9..f800313f83f 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -46,7 +46,7 @@ const desktopLaunchConnectedPlugAppArmor = ` dbus (send) bus=session - path=/io/snapcraft/PrivilegedDesktopLauncher + path=/io/snapcraft/Launcher interface=io.snapcraft.PrivilegedDesktopLauncher member=OpenDesktopEntry peer=(label=unconfined), diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 923e62ecd6c..9735fa64565 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -22,7 +22,6 @@ package userd import ( "fmt" "net/url" - "os" "os/exec" "syscall" "time" diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 99fc041b44d..96dc14a2bcb 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -59,13 +59,13 @@ type PrivilegedDesktopLauncher struct { } // Name returns the name of the interface this object implements -func (s *PrivilegedDesktopLauncher) Name() string { +func (s *PrivilegedDesktopLauncher) Interface() string { return "io.snapcraft.PrivilegedDesktopLauncher" } // BasePath returns the base path of the object -func (s *PrivilegedDesktopLauncher) BasePath() dbus.ObjectPath { - return "/io/snapcraft/PrivilegedDesktopLauncher" +func (s *PrivilegedDesktopLauncher) ObjectPath() dbus.ObjectPath { + return "/io/snapcraft/Launcher" } // IntrospectionData gives the XML formatted introspection description diff --git a/usersession/userd/userd.go b/usersession/userd/userd.go index 3e0f5bfcc55..d04b07520f8 100644 --- a/usersession/userd/userd.go +++ b/usersession/userd/userd.go @@ -77,6 +77,7 @@ func (ud *Userd) Init() error { ud.dbusIfaces = []dbusInterface{ &Launcher{ud.conn}, + &PrivilegedDesktopLauncher{ud.conn}, &Settings{ud.conn}, } for _, iface := range ud.dbusIfaces { From 5cc65f3b2387f6998d8f0504923492fcac7f2eb3 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 12 Oct 2020 16:52:10 +0100 Subject: [PATCH 078/114] Drop OpenDesktopEntry from io.snapcraft.Launcher --- usersession/userd/launcher.go | 38 ----------------------------------- 1 file changed, 38 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 9735fa64565..da1c4389420 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -46,9 +46,6 @@ const launcherIntrospectionXML = ` - - - @@ -167,41 +164,6 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { return nil } -// OpenDesktopEntry implements the 'OpenDesktopEntry' method of the 'io.snapcraft.DesktopLauncher' -// DBus interface. The desktopFileID is described here: -// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id -func (s *Launcher) OpenDesktopEntry(desktopFileID string, sender dbus.Sender) *dbus.Error { - desktopFile, err := desktopFileIDToFilename(osutil.RegularFileExists, desktopFileID) - if err != nil { - return dbus.MakeFailedError(err) - } - - err = verifyDesktopFileLocation(desktopFile) - if err != nil { - return dbus.MakeFailedError(err) - } - - exec_command, err := readExecCommandFromDesktopFile(desktopFile) - if err != nil { - return dbus.MakeFailedError(err) - } - - args, err := parseExecCommand(exec_command) - if err != nil { - return dbus.MakeFailedError(err) - } - - args = append([]string{"systemd-run", "--user", "--"}, args...) - - cmd := exec.Command(args[0], args[1:]...) - - if cmd.Run() != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) - } - - return nil -} - func (s *Launcher) OpenFile(parentWindow string, clientFd dbus.UnixFD, sender dbus.Sender) *dbus.Error { // godbus transfers ownership of this file descriptor to us fd := int(clientFd) From 065a98af6a48221d96943f635e77a3c09869559f Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 12 Oct 2020 17:27:24 +0100 Subject: [PATCH 079/114] Split out PrivilegedDesktopLauncher tests --- usersession/userd/launcher.go | 1 - usersession/userd/launcher_test.go | 57 ---------- ...vileged_desktop_launcher_internal_test.go} | 20 ++-- .../userd/privileged_desktop_launcher_test.go | 101 ++++++++++++++++++ 4 files changed, 111 insertions(+), 68 deletions(-) rename usersession/userd/{launcher_internal_test.go => privileged_desktop_launcher_internal_test.go} (95%) create mode 100644 usersession/userd/privileged_desktop_launcher_test.go diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index da1c4389420..7769928d656 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -29,7 +29,6 @@ import ( "github.com/godbus/dbus" "github.com/snapcore/snapd/i18n" - "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/strutil" "github.com/snapcore/snapd/usersession/userd/ui" ) diff --git a/usersession/userd/launcher_test.go b/usersession/userd/launcher_test.go index 7a1d2752697..d3489aaf290 100644 --- a/usersession/userd/launcher_test.go +++ b/usersession/userd/launcher_test.go @@ -23,7 +23,6 @@ import ( "io/ioutil" "os" "path/filepath" - "strings" "syscall" "testing" @@ -31,7 +30,6 @@ import ( . "gopkg.in/check.v1" - "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/testutil" "github.com/snapcore/snapd/usersession/userd" @@ -199,58 +197,3 @@ func (s *launcherSuite) TestOpenFileFailsWithPathDescriptor(c *C) { c.Assert(err, ErrorMatches, "cannot use file descriptors opened using O_PATH") c.Assert(s.mockXdgOpen.Calls(), IsNil) } - -var mircadeDesktop = `[Desktop Entry] -X-SnapInstanceName=mircade -Name=mircade -Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade -Icon=/snap/mircade/143/meta/gui/mircade.png -Comment=Sample confined desktop -Type=Application -Categories=Game -` - -func (s *launcherSuite) TestOpenDesktopEntrySucceedsWithGoodDesktopId(c *C) { - dirs.SetRootDir(c.MkDir()) - cmd := testutil.MockCommand(c, "systemd-run", "true") - defer cmd.Restore() - - deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") - err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) - c.Assert(err, IsNil) - - err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") - c.Assert(err, IsNil) -} - -func (s *launcherSuite) TestOpenDesktopEntryFailsWithBadDesktopId(c *C) { - dirs.SetRootDir(c.MkDir()) - cmd := testutil.MockCommand(c, "systemd-run", "true") - defer cmd.Restore() - - deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") - err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) - c.Assert(err, IsNil) - - err = s.launcher.OpenDesktopEntry("not-mircade_mircade.desktop", ":some-dbus-sender") - c.Assert(err, NotNil) -} - -func (s *launcherSuite) TestOpenDesktopEntryFailsWithBadExecutable(c *C) { - dirs.SetRootDir(c.MkDir()) - cmd := testutil.MockCommand(c, "systemd-run", "false") - defer cmd.Restore() - - deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") - err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) - c.Assert(err, IsNil) - - err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") - c.Assert(err, NotNil) -} diff --git a/usersession/userd/launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go similarity index 95% rename from usersession/userd/launcher_internal_test.go rename to usersession/userd/privileged_desktop_launcher_internal_test.go index f8a77367547..163020501f1 100644 --- a/usersession/userd/launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -30,10 +30,10 @@ import ( func Test(t *testing.T) { TestingT(t) } -type launcherInternalSuite struct { +type privilegedDesktopLauncherInternalSuite struct { } -var _ = Suite(&launcherInternalSuite{}) +var _ = Suite(&privilegedDesktopLauncherInternalSuite{}) var mockFileSystem = []string{ "/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop", @@ -355,7 +355,7 @@ func existsOnMockFileSystem(desktop_file string) bool { return strutil.ListContains(mockFileSystem, desktop_file) } -func (s *launcherInternalSuite) TestDesktopFileIDToFilenameSucceedsWithValidId(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucceedsWithValidId(c *C) { var desktopIdTests = []struct { id string @@ -373,7 +373,7 @@ func (s *launcherInternalSuite) TestDesktopFileIDToFilenameSucceedsWithValidId(c } } -func (s *launcherInternalSuite) TestDesktopFileIDToFilenameFailsWithInvalidId(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFailsWithInvalidId(c *C) { var desktopIdTests = []string{ "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", "bar-foo-baz.desktop", @@ -387,7 +387,7 @@ func (s *launcherInternalSuite) TestDesktopFileIDToFilenameFailsWithInvalidId(c } } -func (s *launcherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) { var exec_command = []struct { exec_command string expect []string @@ -423,7 +423,7 @@ func (s *launcherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) } } -func (s *launcherInternalSuite) TestParseExecCommandFailsWithInvalidEntry(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandFailsWithInvalidEntry(c *C) { // the only invalid entries are those that error from shlex.Split() var exec_command = []string{ "/snap/bin/foo \"unclosed double quote", @@ -436,7 +436,7 @@ func (s *launcherInternalSuite) TestParseExecCommandFailsWithInvalidEntry(c *C) } } -func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithValidContent(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithValidContent(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") // We need to correct the embedded path to the desktop file before writing the file @@ -450,7 +450,7 @@ func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithValidConte c.Assert(exec, DeepEquals, "env BAMF_DESKTOP_FILE_HINT="+desktopFile+" /snap/bin/chromium %U") } -func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithInvalidExec(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithInvalidExec(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") err := ioutil.WriteFile(desktopFile, []byte(chromiumDesktopFile), 0644) @@ -460,7 +460,7 @@ func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithInvalidExe c.Assert(err, NotNil) } -func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithNoDesktopEntry(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithNoDesktopEntry(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") // We need to correct the embedded path to the desktop file before writing the file @@ -474,7 +474,7 @@ func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithNoDesktopE c.Assert(err, NotNil) } -func (s *launcherInternalSuite) TestReadExecCommandFromDesktopFileWithNoFile(c *C) { +func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithNoFile(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") _, err := readExecCommandFromDesktopFile(desktopFile) diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go new file mode 100644 index 00000000000..bfb8443ab33 --- /dev/null +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -0,0 +1,101 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2020 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 . + * + */ + +package userd_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "strings" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/dirs" + "github.com/snapcore/snapd/testutil" + "github.com/snapcore/snapd/usersession/userd" +) + +type privilegedDesktopLauncherSuite struct { + launcher *userd.PrivilegedDesktopLauncher +} + +var _ = Suite(&privilegedDesktopLauncherSuite{}) + +func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { + s.launcher = &userd.PrivilegedDesktopLauncher{} +} + +func (s *privilegedDesktopLauncherSuite) TearDownTest(c *C) { +} + +var mircadeDesktop = `[Desktop Entry] +X-SnapInstanceName=mircade +Name=mircade +Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade +Icon=/snap/mircade/143/meta/gui/mircade.png +Comment=Sample confined desktop +Type=Application +Categories=Game +` + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntrySucceedsWithGoodDesktopId(c *C) { + dirs.SetRootDir(c.MkDir()) + cmd := testutil.MockCommand(c, "systemd-run", "true") + defer cmd.Restore() + + deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") + err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) + c.Assert(err, IsNil) + + err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") + c.Assert(err, IsNil) +} + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadDesktopId(c *C) { + dirs.SetRootDir(c.MkDir()) + cmd := testutil.MockCommand(c, "systemd-run", "true") + defer cmd.Restore() + + deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") + err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) + c.Assert(err, IsNil) + + err = s.launcher.OpenDesktopEntry("not-mircade_mircade.desktop", ":some-dbus-sender") + c.Assert(err, NotNil) +} + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadExecutable(c *C) { + dirs.SetRootDir(c.MkDir()) + cmd := testutil.MockCommand(c, "systemd-run", "false") + defer cmd.Restore() + + deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") + err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) + c.Assert(err, IsNil) + + err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") + c.Assert(err, NotNil) +} From 94dd345f5baef74068e795446ed96634f462b9f7 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 12 Oct 2020 17:45:33 +0100 Subject: [PATCH 080/114] Use export_test.go to access internal functions --- usersession/userd/export_test.go | 14 ++++++++++++ ...ivileged_desktop_launcher_internal_test.go | 22 +++++++++---------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/usersession/userd/export_test.go b/usersession/userd/export_test.go index fc2cce7de9c..0ce3ac4c7ac 100644 --- a/usersession/userd/export_test.go +++ b/usersession/userd/export_test.go @@ -30,3 +30,17 @@ func MockSnapFromSender(f func(*dbus.Conn, dbus.Sender) (string, error)) func() snapFromSender = origSnapFromSender } } + +type FileExists fileExists + +func DesktopFileIDToFilename(desktopFileExists FileExists, desktopFileID string) (string, error) { + return desktopFileIDToFilename(fileExists(desktopFileExists), desktopFileID); +} + +func ParseExecCommand(exec_command string) ([]string, error) { + return parseExecCommand(exec_command); +} + +func ReadExecCommandFromDesktopFile(desktopFile string) (string, error) { + return readExecCommandFromDesktopFile(desktopFile); +} diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 163020501f1..d3773d5a2e0 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -17,7 +17,7 @@ * */ -package userd +package userd_test import ( "github.com/snapcore/snapd/strutil" @@ -25,11 +25,9 @@ import ( "io/ioutil" "path/filepath" "strings" - "testing" + "github.com/snapcore/snapd/usersession/userd" ) -func Test(t *testing.T) { TestingT(t) } - type privilegedDesktopLauncherInternalSuite struct { } @@ -367,7 +365,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucc } for _, test := range desktopIdTests { - actual, err := desktopFileIDToFilename(existsOnMockFileSystem, test.id) + actual, err := userd.DesktopFileIDToFilename(existsOnMockFileSystem, test.id) c.Assert(err, IsNil) c.Assert(actual, Equals, test.expect) } @@ -382,7 +380,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFail } for _, id := range desktopIdTests { - _, err := desktopFileIDToFilename(existsOnMockFileSystem, id) + _, err := userd.DesktopFileIDToFilename(existsOnMockFileSystem, id) c.Assert(err, NotNil) } } @@ -417,7 +415,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWit } for _, test := range exec_command { - actual, err := parseExecCommand(test.exec_command) + actual, err := userd.ParseExecCommand(test.exec_command) c.Assert(err, IsNil) c.Assert(actual, DeepEquals, test.expect) } @@ -431,7 +429,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandFailsWithIn } for _, test := range exec_command { - _, err := parseExecCommand(test) + _, err := userd.ParseExecCommand(test) c.Assert(err, NotNil) } } @@ -444,7 +442,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF err := ioutil.WriteFile(desktopFile, []byte(fileContent), 0644) c.Assert(err, IsNil) - exec, err := readExecCommandFromDesktopFile(desktopFile) + exec, err := userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, IsNil) c.Assert(exec, DeepEquals, "env BAMF_DESKTOP_FILE_HINT="+desktopFile+" /snap/bin/chromium %U") @@ -456,7 +454,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF err := ioutil.WriteFile(desktopFile, []byte(chromiumDesktopFile), 0644) c.Assert(err, IsNil) - _, err = readExecCommandFromDesktopFile(desktopFile) + _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, NotNil) } @@ -470,13 +468,13 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF err := ioutil.WriteFile(desktopFile, []byte(fileContent), 0644) c.Assert(err, IsNil) - _, err = readExecCommandFromDesktopFile(desktopFile) + _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, NotNil) } func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithNoFile(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") - _, err := readExecCommandFromDesktopFile(desktopFile) + _, err := userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, NotNil) } From dea3c8a4ba1c036e60b050224885527d745c2579 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 12 Oct 2020 17:55:38 +0100 Subject: [PATCH 081/114] Use dirs.SnapBinariesDir --- usersession/userd/privileged_desktop_launcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 96dc14a2bcb..c6f1fa17fa6 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -208,7 +208,7 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { } } - expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s /snap/bin/", desktopFile) + expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s " + dirs.SnapBinariesDir, desktopFile) if !strings.HasPrefix(launch, expectedPrefix) { return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, launch) } From 4ef15aad2cabac68ab22e74d473353d0ece5d07a Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 13 Oct 2020 10:16:28 +0100 Subject: [PATCH 082/114] Fix and move repetitive test setup to SetUpTest() --- .../userd/privileged_desktop_launcher_test.go | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index bfb8443ab33..dbc757961b0 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -39,63 +39,51 @@ type privilegedDesktopLauncherSuite struct { var _ = Suite(&privilegedDesktopLauncherSuite{}) func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { + dirs.SetRootDir(c.MkDir()) s.launcher = &userd.PrivilegedDesktopLauncher{} + + var rawMircadeDesktop = `[Desktop Entry] + X-SnapInstanceName=mircade + Name=mircade + Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade + Icon=/snap/mircade/143/meta/gui/mircade.png + Comment=Sample confined desktop + Type=Application + Categories=Game + ` + tmpMircadeDesktop := strings.Replace(rawMircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1) + desktopContent := strings.Replace(tmpMircadeDesktop, "/snap/bin/", dirs.SnapBinariesDir, -1) + + deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") + err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) + c.Assert(err, IsNil) + err = ioutil.WriteFile(deskTopFile, []byte(desktopContent), 0644) + c.Assert(err, IsNil) } func (s *privilegedDesktopLauncherSuite) TearDownTest(c *C) { } -var mircadeDesktop = `[Desktop Entry] -X-SnapInstanceName=mircade -Name=mircade -Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade -Icon=/snap/mircade/143/meta/gui/mircade.png -Comment=Sample confined desktop -Type=Application -Categories=Game -` - func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntrySucceedsWithGoodDesktopId(c *C) { - dirs.SetRootDir(c.MkDir()) cmd := testutil.MockCommand(c, "systemd-run", "true") defer cmd.Restore() - deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") - err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) - c.Assert(err, IsNil) - - err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") + err := s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") c.Assert(err, IsNil) } func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadDesktopId(c *C) { - dirs.SetRootDir(c.MkDir()) cmd := testutil.MockCommand(c, "systemd-run", "true") defer cmd.Restore() - deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") - err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) - c.Assert(err, IsNil) - - err = s.launcher.OpenDesktopEntry("not-mircade_mircade.desktop", ":some-dbus-sender") + err := s.launcher.OpenDesktopEntry("not-mircade_mircade.desktop", ":some-dbus-sender") c.Assert(err, NotNil) } func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadExecutable(c *C) { - dirs.SetRootDir(c.MkDir()) cmd := testutil.MockCommand(c, "systemd-run", "false") defer cmd.Restore() - deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") - err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(deskTopFile, []byte(strings.Replace(mircadeDesktop, "/var/lib/snapd/desktop/applications", dirs.SnapDesktopFilesDir, -1)), 0644) - c.Assert(err, IsNil) - - err = s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") + err := s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") c.Assert(err, NotNil) } From 17c38e39fffe52b70fe527d4861801137cb462c4 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 13 Oct 2020 10:20:41 +0100 Subject: [PATCH 083/114] go fmt --- usersession/userd/export_test.go | 6 +++--- usersession/userd/privileged_desktop_launcher.go | 5 ++--- .../userd/privileged_desktop_launcher_internal_test.go | 2 +- usersession/userd/privileged_desktop_launcher_test.go | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/usersession/userd/export_test.go b/usersession/userd/export_test.go index 0ce3ac4c7ac..686a9b68d18 100644 --- a/usersession/userd/export_test.go +++ b/usersession/userd/export_test.go @@ -34,13 +34,13 @@ func MockSnapFromSender(f func(*dbus.Conn, dbus.Sender) (string, error)) func() type FileExists fileExists func DesktopFileIDToFilename(desktopFileExists FileExists, desktopFileID string) (string, error) { - return desktopFileIDToFilename(fileExists(desktopFileExists), desktopFileID); + return desktopFileIDToFilename(fileExists(desktopFileExists), desktopFileID) } func ParseExecCommand(exec_command string) ([]string, error) { - return parseExecCommand(exec_command); + return parseExecCommand(exec_command) } func ReadExecCommandFromDesktopFile(desktopFile string) (string, error) { - return readExecCommandFromDesktopFile(desktopFile); + return readExecCommandFromDesktopFile(desktopFile) } diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index c6f1fa17fa6..af34a4145b1 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -98,7 +98,7 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return dbus.MakeFailedError(err) } - args = append([]string{"systemd-run", "--user", "--"}, args...) + args = append([]string{"systemd-run", "--user", "--"}, args...) cmd := exec.Command(args[0], args[1:]...) @@ -109,7 +109,6 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return nil } - type fileExists func(string) bool // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. @@ -208,7 +207,7 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { } } - expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s " + dirs.SnapBinariesDir, desktopFile) + expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s "+dirs.SnapBinariesDir, desktopFile) if !strings.HasPrefix(launch, expectedPrefix) { return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, launch) } diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index d3773d5a2e0..7f96ab60abf 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -21,11 +21,11 @@ package userd_test import ( "github.com/snapcore/snapd/strutil" + "github.com/snapcore/snapd/usersession/userd" . "gopkg.in/check.v1" "io/ioutil" "path/filepath" "strings" - "github.com/snapcore/snapd/usersession/userd" ) type privilegedDesktopLauncherInternalSuite struct { diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index dbc757961b0..6ff454df37c 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -42,7 +42,7 @@ func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) s.launcher = &userd.PrivilegedDesktopLauncher{} - var rawMircadeDesktop = `[Desktop Entry] + var rawMircadeDesktop = `[Desktop Entry] X-SnapInstanceName=mircade Name=mircade Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mircade_mircade.desktop /snap/bin/mircade From 47872a7c85f978f206de9eb87c7f0d95c1b00255 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 13 Oct 2020 11:52:41 +0100 Subject: [PATCH 084/114] Support for %i --- usersession/userd/export_test.go | 6 +-- .../userd/privileged_desktop_launcher.go | 37 ++++++++++++------- ...ivileged_desktop_launcher_internal_test.go | 23 ++++++------ 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/usersession/userd/export_test.go b/usersession/userd/export_test.go index 686a9b68d18..0c3f12e6c81 100644 --- a/usersession/userd/export_test.go +++ b/usersession/userd/export_test.go @@ -37,10 +37,10 @@ func DesktopFileIDToFilename(desktopFileExists FileExists, desktopFileID string) return desktopFileIDToFilename(fileExists(desktopFileExists), desktopFileID) } -func ParseExecCommand(exec_command string) ([]string, error) { - return parseExecCommand(exec_command) +func ParseExecCommand(exec_command string, icon string) ([]string, error) { + return parseExecCommand(exec_command, icon) } -func ReadExecCommandFromDesktopFile(desktopFile string) (string, error) { +func ReadExecCommandFromDesktopFile(desktopFile string) (exec string, icon string, err error) { return readExecCommandFromDesktopFile(desktopFile) } diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index af34a4145b1..3d042ed43a9 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -88,12 +88,12 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return dbus.MakeFailedError(err) } - exec_command, err := readExecCommandFromDesktopFile(desktopFile) + exec_command, icon, err := readExecCommandFromDesktopFile(desktopFile) if err != nil { return dbus.MakeFailedError(err) } - args, err := parseExecCommand(exec_command) + args, err := parseExecCommand(exec_command, icon) if err != nil { return dbus.MakeFailedError(err) } @@ -180,12 +180,10 @@ func verifyDesktopFileLocation(desktopFile string) error { // readExecCommandFromDesktopFile parses the desktop file to get the Exec entry and // checks that the BAMF_DESKTOP_FILE_HINT is present and refers to the desktop file. -func readExecCommandFromDesktopFile(desktopFile string) (string, error) { - var launch string - +func readExecCommandFromDesktopFile(desktopFile string) (exec string, icon string, err error) { file, err := os.Open(desktopFile) if err != nil { - return launch, err + return exec, icon, err } defer file.Close() scanner := bufio.NewScanner(file) @@ -201,18 +199,21 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { in_desktop_section = false } else if strings.HasPrefix(line, "[") { in_desktop_section = false - } else if in_desktop_section && strings.HasPrefix(line, "Exec=") { - launch = strings.TrimPrefix(line, "Exec=") - break + } else if in_desktop_section { + if strings.HasPrefix(line, "Exec=") { + exec = strings.TrimPrefix(line, "Exec=") + } else if strings.HasPrefix(line, "Icon=") { + icon = strings.TrimPrefix(line, "Icon=") + } } } expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s "+dirs.SnapBinariesDir, desktopFile) - if !strings.HasPrefix(launch, expectedPrefix) { - return "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, launch) + if !strings.HasPrefix(exec, expectedPrefix) { + return "", "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, exec) } - return launch, nil + return exec, icon, nil } // Parse the Exec command by stripping any exec variables. @@ -221,7 +222,7 @@ func readExecCommandFromDesktopFile(desktopFile string) (string, error) { // implications that must be thought through regarding the influence of the launching // snap over the launcher wrt exec variables. For now we simply filter them out. // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables -func parseExecCommand(exec_command string) ([]string, error) { +func parseExecCommand(exec_command string, icon string) ([]string, error) { args, err := shlex.Split(exec_command) if err != nil { return []string{}, err @@ -236,8 +237,16 @@ func parseExecCommand(exec_command string) ([]string, error) { i++ } else if strings.HasPrefix(args[i], "%") { switch args[i] { - case "%f", "%F", "%u", "%U", "%i": + case "%f", "%F", "%u", "%U": args = append(args[:i], args[i+1:]...) + case "%i": + pre := args[:i] + post := args[i+1:] + if icon != "" { + post = append([]string{"--icon", icon}, post...) + i = i + 2 + } + args = append(pre, post...) default: return []string{}, fmt.Errorf("cannot run %q", exec_command) } diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 7f96ab60abf..df8528c108c 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -404,18 +404,18 @@ func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWit {"/snap/bin/foo '\"-f bar\"'", []string{"/snap/bin/foo", "\"-f bar\""}}, // valid with exec variables stripped out {"/snap/bin/foo -f %U", []string{"/snap/bin/foo", "-f"}}, - {"/snap/bin/foo -f %U %i", []string{"/snap/bin/foo", "-f"}}, + {"/snap/bin/foo -f %U %i", []string{"/snap/bin/foo", "-f", "--icon", "/snap/chromium/1193/chromium.png"}}, {"/snap/bin/foo -f %U bar", []string{"/snap/bin/foo", "-f", "bar"}}, - {"/snap/bin/foo -f %U bar %i", []string{"/snap/bin/foo", "-f", "bar"}}, + {"/snap/bin/foo -f %U bar %i", []string{"/snap/bin/foo", "-f", "bar", "--icon", "/snap/chromium/1193/chromium.png"}}, // valid with mixture of literal '%' and exec variables {"/snap/bin/foo -f %U %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, - {"/snap/bin/foo -f %U %i %%bar", []string{"/snap/bin/foo", "-f", "%bar"}}, - {"/snap/bin/foo -f %U %%bar %i", []string{"/snap/bin/foo", "-f", "%bar"}}, - {"/snap/bin/foo -f %%bar %U %i", []string{"/snap/bin/foo", "-f", "%bar"}}, + {"/snap/bin/foo -f %U %i %%bar", []string{"/snap/bin/foo", "-f", "--icon", "/snap/chromium/1193/chromium.png", "%bar"}}, + {"/snap/bin/foo -f %U %%bar %i", []string{"/snap/bin/foo", "-f", "%bar", "--icon", "/snap/chromium/1193/chromium.png"}}, + {"/snap/bin/foo -f %%bar %U %i", []string{"/snap/bin/foo", "-f", "%bar", "--icon", "/snap/chromium/1193/chromium.png"}}, } for _, test := range exec_command { - actual, err := userd.ParseExecCommand(test.exec_command) + actual, err := userd.ParseExecCommand(test.exec_command, "/snap/chromium/1193/chromium.png") c.Assert(err, IsNil) c.Assert(actual, DeepEquals, test.expect) } @@ -429,7 +429,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandFailsWithIn } for _, test := range exec_command { - _, err := userd.ParseExecCommand(test) + _, err := userd.ParseExecCommand(test, "/snap/chromium/1193/chromium.png") c.Assert(err, NotNil) } } @@ -442,10 +442,11 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF err := ioutil.WriteFile(desktopFile, []byte(fileContent), 0644) c.Assert(err, IsNil) - exec, err := userd.ReadExecCommandFromDesktopFile(desktopFile) + exec, icon, err := userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, IsNil) c.Assert(exec, DeepEquals, "env BAMF_DESKTOP_FILE_HINT="+desktopFile+" /snap/bin/chromium %U") + c.Assert(icon, DeepEquals, "/snap/chromium/1193/chromium.png") } func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithInvalidExec(c *C) { @@ -454,7 +455,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF err := ioutil.WriteFile(desktopFile, []byte(chromiumDesktopFile), 0644) c.Assert(err, IsNil) - _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) + _, _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, NotNil) } @@ -468,13 +469,13 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF err := ioutil.WriteFile(desktopFile, []byte(fileContent), 0644) c.Assert(err, IsNil) - _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) + _, _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, NotNil) } func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithNoFile(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") - _, err := userd.ReadExecCommandFromDesktopFile(desktopFile) + _, _, err := userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, NotNil) } From b07503458ce5837e5f938b9e42cfde82fe0235b3 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 13 Oct 2020 12:02:42 +0100 Subject: [PATCH 085/114] Correct desktop-launch launcher.sh script --- .../interfaces-desktop-launch/test-launcher/bin/launcher.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh index 1b008e49bb4..dc5c8cb5f59 100755 --- a/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh +++ b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh @@ -2,5 +2,5 @@ exec dbus-send --session --print-reply \ --dest=io.snapcraft.Launcher /io/snapcraft/Launcher \ - io.snapcraft.Launcher.OpenDesktopEntryEnv \ + io.snapcraft.PrivilegedDesktopLauncher.OpenDesktopEntry \ string:"$1" array:string:"XDG_CURRENT_DESKTOP=spread-test" From 607b49a9dbed659f6c16f1887c9e57d383f56843 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Tue, 13 Oct 2020 15:01:41 +0100 Subject: [PATCH 086/114] Revert accidental move of fdToFilename from launcher.go --- usersession/userd/launcher.go | 47 +++++++++++++++++++ .../userd/privileged_desktop_launcher.go | 46 ------------------ 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/usersession/userd/launcher.go b/usersession/userd/launcher.go index 7769928d656..0b3903fae8e 100644 --- a/usersession/userd/launcher.go +++ b/usersession/userd/launcher.go @@ -22,6 +22,7 @@ package userd import ( "fmt" "net/url" + "os" "os/exec" "syscall" "time" @@ -29,6 +30,7 @@ import ( "github.com/godbus/dbus" "github.com/snapcore/snapd/i18n" + "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/strutil" "github.com/snapcore/snapd/usersession/userd/ui" ) @@ -163,6 +165,51 @@ func (s *Launcher) OpenURL(addr string, sender dbus.Sender) *dbus.Error { return nil } +// fdToFilename determines the path associated with an open file descriptor. +// +// The file descriptor cannot be opened using O_PATH and must refer to +// a regular file or to a directory. The symlink at /proc/self/fd/ +// is read to determine the filename. The descriptor is also fstat'ed +// and the resulting device number and inode number are compared to +// stat on the path determined earlier. The numbers must match. +func fdToFilename(fd int) (string, error) { + flags, err := sys.FcntlGetFl(fd) + if err != nil { + return "", err + } + // File descriptors opened with O_PATH do not imply access to + // the file in question. + if flags&sys.O_PATH != 0 { + return "", fmt.Errorf("cannot use file descriptors opened using O_PATH") + } + + // Determine the file name associated with the passed file descriptor. + filename, err := os.Readlink(fmt.Sprintf("/proc/self/fd/%d", fd)) + if err != nil { + return "", err + } + + var fileStat, fdStat syscall.Stat_t + if err := syscall.Stat(filename, &fileStat); err != nil { + return "", err + } + if err := syscall.Fstat(fd, &fdStat); err != nil { + return "", err + } + + // Sanity check to ensure we've got the right file + if fdStat.Dev != fileStat.Dev || fdStat.Ino != fileStat.Ino { + return "", fmt.Errorf("cannot determine file name") + } + + fileType := fileStat.Mode & syscall.S_IFMT + if fileType != syscall.S_IFREG && fileType != syscall.S_IFDIR { + return "", fmt.Errorf("cannot open anything other than regular files or directories") + } + + return filename, nil +} + func (s *Launcher) OpenFile(parentWindow string, clientFd dbus.UnixFD, sender dbus.Sender) *dbus.Error { // godbus transfers ownership of this file descriptor to us fd := int(clientFd) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 3d042ed43a9..4285f9ee948 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -34,7 +34,6 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/osutil" - "github.com/snapcore/snapd/osutil/sys" "github.com/snapcore/snapd/strutil/shlex" "github.com/snapcore/snapd/usersession/userd/ui" ) @@ -261,51 +260,6 @@ func parseExecCommand(exec_command string, icon string) ([]string, error) { return args, nil } -// fdToFilename determines the path associated with an open file descriptor. -// -// The file descriptor cannot be opened using O_PATH and must refer to -// a regular file or to a directory. The symlink at /proc/self/fd/ -// is read to determine the filename. The descriptor is also fstat'ed -// and the resulting device number and inode number are compared to, "%d", "%D", "%n", "%N", -// stat on the path determined earlier. The numbers must match. -func fdToFilename(fd int) (string, error) { - flags, err := sys.FcntlGetFl(fd) - if err != nil { - return "", err - } - // File descriptors opened with O_PATH do not imply access to - // the file in question. - if flags&sys.O_PATH != 0 { - return "", fmt.Errorf("cannot use file descriptors opened using O_PATH") - } - - // Determine the file name associated with the passed file descriptor. - filename, err := os.Readlink(fmt.Sprintf("/proc/self/fd/%d", fd)) - if err != nil { - return "", err - } - - var fileStat, fdStat syscall.Stat_t - if err := syscall.Stat(filename, &fileStat); err != nil { - return "", err - } - if err := syscall.Fstat(fd, &fdStat); err != nil { - return "", err - } - - // Sanity check to ensure we've got the right file - if fdStat.Dev != fileStat.Dev || fdStat.Ino != fileStat.Ino { - return "", fmt.Errorf("cannot determine file name") - } - - fileType := fileStat.Mode & syscall.S_IFMT - if fileType != syscall.S_IFREG && fileType != syscall.S_IFDIR { - return "", fmt.Errorf("cannot open anything other than regular files or directories") - } - - return filename, nil -} - func (s *PrivilegedDesktopLauncher) OpenFile(parentWindow string, clientFd dbus.UnixFD, sender dbus.Sender) *dbus.Error { // godbus transfers ownership of this file descriptor to us fd := int(clientFd) From c228d16124aa230c85ab89e2d432ee337f7cc552 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Thu, 3 Dec 2020 18:45:21 +0800 Subject: [PATCH 087/114] userd: delete unused PrivilegedDesktopLauncher.OpenFile D-Bus method --- .../userd/privileged_desktop_launcher.go | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 4285f9ee948..d905dd6fbf8 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -26,16 +26,12 @@ import ( "os/exec" "path/filepath" "strings" - "syscall" - "time" "github.com/godbus/dbus" "github.com/snapcore/snapd/dirs" - "github.com/snapcore/snapd/i18n" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/strutil/shlex" - "github.com/snapcore/snapd/usersession/userd/ui" ) const privilegedLauncherIntrospectionXML = ` @@ -259,40 +255,3 @@ func parseExecCommand(exec_command string, icon string) ([]string, error) { } return args, nil } - -func (s *PrivilegedDesktopLauncher) OpenFile(parentWindow string, clientFd dbus.UnixFD, sender dbus.Sender) *dbus.Error { - // godbus transfers ownership of this file descriptor to us - fd := int(clientFd) - defer syscall.Close(fd) - - filename, err := fdToFilename(fd) - if err != nil { - return dbus.MakeFailedError(err) - } - - snap, err := snapFromSender(s.conn, sender) - if err != nil { - return dbus.MakeFailedError(err) - } - dialog, err := ui.New() - if err != nil { - return dbus.MakeFailedError(err) - } - answeredYes := dialog.YesNo( - i18n.G("Allow opening file?"), - fmt.Sprintf(i18n.G("Allow snap %q to open file %q?"), snap, filename), - &ui.DialogOptions{ - Timeout: 5 * 60 * time.Second, - Footer: i18n.G("This dialog will close automatically after 5 minutes of inactivity."), - }, - ) - if !answeredYes { - return dbus.MakeFailedError(fmt.Errorf("permission denied")) - } - - if err = exec.Command("xdg-open", filename).Run(); err != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot open supplied URL")) - } - - return nil -} From cd6244f841ead352d3b6ebbbd166c9e3db7ef011 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Fri, 4 Dec 2020 16:33:54 +0800 Subject: [PATCH 088/114] userd: clean up PrivilegedDesktopLauncher code based on review from @pedronis --- .../userd/privileged_desktop_launcher.go | 72 +++++++++---------- 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index d905dd6fbf8..04f552051b0 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -83,12 +83,12 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return dbus.MakeFailedError(err) } - exec_command, icon, err := readExecCommandFromDesktopFile(desktopFile) + command, icon, err := readExecCommandFromDesktopFile(desktopFile) if err != nil { return dbus.MakeFailedError(err) } - args, err := parseExecCommand(exec_command, icon) + args, err := parseExecCommand(command, icon) if err != nil { return dbus.MakeFailedError(err) } @@ -98,7 +98,7 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende cmd := exec.Command(args[0], args[1:]...) if cmd.Run() != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot run %q", exec_command)) + return dbus.MakeFailedError(fmt.Errorf("cannot run %q", command)) } return nil @@ -138,7 +138,6 @@ func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId [ // desktopFileIDToFilename determines the path associated with a desktop file ID. func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) (string, error) { - // OpenDesktopEntry() currently only supports launching snap applications from // desktop files in /var/lib/snapd/desktop/applications and these desktop files are // written by snapd and considered safe for userd to process. @@ -158,6 +157,10 @@ func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) // verifyDesktopFileLocation checks the desktop file location: // we only consider desktop files in dirs.SnapDesktopFilesDir func verifyDesktopFileLocation(desktopFile string) error { + if filepath.Clean(desktopFile) != desktopFile { + return fmt.Errorf("desktop file has unclean path: %q", desktopFile) + } + if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir+"/") { // We currently only support launching snap applications from desktop files in // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and @@ -166,10 +169,6 @@ func verifyDesktopFileLocation(desktopFile string) error { return fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") } - if filepath.Clean(desktopFile) != desktopFile { - return fmt.Errorf("desktop file has unclean path: %q", desktopFile) - } - return nil } @@ -183,18 +182,18 @@ func readExecCommandFromDesktopFile(desktopFile string) (exec string, icon strin defer file.Close() scanner := bufio.NewScanner(file) - in_desktop_section := false + inDesktopSection := false for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "[Desktop Entry]" { - in_desktop_section = true + inDesktopSection = true } else if strings.HasPrefix(line, "[Desktop Action ") { // maybe later we'll add support here - in_desktop_section = false + inDesktopSection = false } else if strings.HasPrefix(line, "[") { - in_desktop_section = false - } else if in_desktop_section { + inDesktopSection = false + } else if inDesktopSection { if strings.HasPrefix(line, "Exec=") { exec = strings.TrimPrefix(line, "Exec=") } else if strings.HasPrefix(line, "Icon=") { @@ -203,9 +202,9 @@ func readExecCommandFromDesktopFile(desktopFile string) (exec string, icon strin } } - expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s "+dirs.SnapBinariesDir, desktopFile) + expectedPrefix := fmt.Sprintf("env BAMF_DESKTOP_FILE_HINT=%s %s", desktopFile, dirs.SnapBinariesDir) if !strings.HasPrefix(exec, expectedPrefix) { - return "", "", fmt.Errorf("Desktop file %q has an unsupported 'Exec' value: %q", desktopFile, exec) + return "", "", fmt.Errorf("desktop file %q has an unsupported 'Exec' value: %q", desktopFile, exec) } return exec, icon, nil @@ -217,41 +216,34 @@ func readExecCommandFromDesktopFile(desktopFile string) (exec string, icon strin // implications that must be thought through regarding the influence of the launching // snap over the launcher wrt exec variables. For now we simply filter them out. // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables -func parseExecCommand(exec_command string, icon string) ([]string, error) { - args, err := shlex.Split(exec_command) +func parseExecCommand(command string, icon string) ([]string, error) { + origArgs, err := shlex.Split(command) if err != nil { - return []string{}, err + return nil, err } - i := 0 - for { + args := make([]string, 0, len(origArgs)) + for _, arg := range origArgs { // We want to keep literal '%' (expressed as '%%') but filter our exec variables // like '%foo' - if strings.HasPrefix(args[i], "%%") { - args[i] = strings.TrimPrefix(args[i], "%") - i++ - } else if strings.HasPrefix(args[i], "%") { - switch args[i] { + if strings.HasPrefix(arg, "%%") { + arg = arg[1:] + } else if strings.HasPrefix(arg, "%") { + switch arg { case "%f", "%F", "%u", "%U": - args = append(args[:i], args[i+1:]...) + // If we were launching a file with + // the application, these variables + // would expand to file names or URIs. + // As we're not, they are simply + // removed from the argument list. case "%i": - pre := args[:i] - post := args[i+1:] - if icon != "" { - post = append([]string{"--icon", icon}, post...) - i = i + 2 - } - args = append(pre, post...) + args = append(args, "--icon", icon) default: - return []string{}, fmt.Errorf("cannot run %q", exec_command) + return nil, fmt.Errorf("cannot run %q", command) } - } else { - i++ - } - - if i == len(args) { - break + continue } + args = append(args, arg) } return args, nil } From bade7abb56931d1aef96c03a400253bacd31bd5b Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Fri, 4 Dec 2020 17:06:31 +0800 Subject: [PATCH 089/114] userd: simplify how the mock fileExists handler is injected for testing --- usersession/userd/export_test.go | 22 +++++++++---------- .../userd/privileged_desktop_launcher.go | 14 ++++++------ ...ivileged_desktop_launcher_internal_test.go | 9 ++++++-- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/usersession/userd/export_test.go b/usersession/userd/export_test.go index 0c3f12e6c81..49569fefe44 100644 --- a/usersession/userd/export_test.go +++ b/usersession/userd/export_test.go @@ -31,16 +31,16 @@ func MockSnapFromSender(f func(*dbus.Conn, dbus.Sender) (string, error)) func() } } -type FileExists fileExists - -func DesktopFileIDToFilename(desktopFileExists FileExists, desktopFileID string) (string, error) { - return desktopFileIDToFilename(fileExists(desktopFileExists), desktopFileID) -} - -func ParseExecCommand(exec_command string, icon string) ([]string, error) { - return parseExecCommand(exec_command, icon) -} +var ( + DesktopFileIDToFilename = desktopFileIDToFilename + ParseExecCommand = parseExecCommand + ReadExecCommandFromDesktopFile = readExecCommandFromDesktopFile +) -func ReadExecCommandFromDesktopFile(desktopFile string) (exec string, icon string, err error) { - return readExecCommandFromDesktopFile(desktopFile) +func MockRegularFileExists(f func(string) bool) func() { + old := regularFileExists + regularFileExists = f + return func() { + regularFileExists = old + } } diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 04f552051b0..b11fe3bfe95 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -73,7 +73,7 @@ func (s *PrivilegedDesktopLauncher) IntrospectionData() string { // DBus interface. The desktopFileID is described here: // https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sender dbus.Sender) *dbus.Error { - desktopFile, err := desktopFileIDToFilename(osutil.RegularFileExists, desktopFileID) + desktopFile, err := desktopFileIDToFilename(desktopFileID) if err != nil { return dbus.MakeFailedError(err) } @@ -104,7 +104,7 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return nil } -type fileExists func(string) bool +var regularFileExists = osutil.RegularFileExists // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. // Per https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id, @@ -115,10 +115,10 @@ type fileExists func(string) bool // o .../foo/bar_baz/norf.desktop // o .../foo-bar_baz/norf.desktop // We're not required to diagnose multiple files matching the desktop file ID. -func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId []string) (string, error) { +func findDesktopFile(baseDir string, splitFileId []string) (string, error) { desktopFile := filepath.Join(baseDir, strings.Join(splitFileId, "-")) - if desktopFileExists(desktopFile) { + if regularFileExists(desktopFile) { return desktopFile, nil } @@ -127,7 +127,7 @@ func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId [ // we're only checking dirs.SnapDesktopFilesDir (not all entries in $XDG_DATA_DIRS) and we know that snapd // does not create subdirectories in that location. for i := 1; i != len(splitFileId); i++ { - desktopFile, err := findDesktopFile(desktopFileExists, filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + desktopFile, err := findDesktopFile(filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) if err == nil { return desktopFile, err } @@ -137,7 +137,7 @@ func findDesktopFile(desktopFileExists fileExists, baseDir string, splitFileId [ } // desktopFileIDToFilename determines the path associated with a desktop file ID. -func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) (string, error) { +func desktopFileIDToFilename(desktopFileID string) (string, error) { // OpenDesktopEntry() currently only supports launching snap applications from // desktop files in /var/lib/snapd/desktop/applications and these desktop files are // written by snapd and considered safe for userd to process. @@ -145,7 +145,7 @@ func desktopFileIDToFilename(desktopFileExists fileExists, desktopFileID string) // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html baseDir := dirs.SnapDesktopFilesDir - desktopFile, err := findDesktopFile(desktopFileExists, baseDir, strings.Split(desktopFileID, "-")) + desktopFile, err := findDesktopFile(baseDir, strings.Split(desktopFileID, "-")) if err == nil { return desktopFile, nil diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index df8528c108c..611ebb5b822 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -354,6 +354,8 @@ func existsOnMockFileSystem(desktop_file string) bool { } func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucceedsWithValidId(c *C) { + restore := userd.MockRegularFileExists(existsOnMockFileSystem) + defer restore() var desktopIdTests = []struct { id string @@ -365,13 +367,16 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucc } for _, test := range desktopIdTests { - actual, err := userd.DesktopFileIDToFilename(existsOnMockFileSystem, test.id) + actual, err := userd.DesktopFileIDToFilename(test.id) c.Assert(err, IsNil) c.Assert(actual, Equals, test.expect) } } func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFailsWithInvalidId(c *C) { + restore := userd.MockRegularFileExists(existsOnMockFileSystem) + defer restore() + var desktopIdTests = []string{ "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", "bar-foo-baz.desktop", @@ -380,7 +385,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFail } for _, id := range desktopIdTests { - _, err := userd.DesktopFileIDToFilename(existsOnMockFileSystem, id) + _, err := userd.DesktopFileIDToFilename(id) c.Assert(err, NotNil) } } From a64a652a9b16d8d2a3b51ecc6156e819e5b11a2b Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 7 Dec 2020 12:12:45 +0000 Subject: [PATCH 090/114] Don't try to pass environment in interfaces-desktop-launch --- tests/main/interfaces-desktop-launch/task.yaml | 3 --- .../interfaces-desktop-launch/test-launcher/bin/launcher.sh | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/main/interfaces-desktop-launch/task.yaml b/tests/main/interfaces-desktop-launch/task.yaml index 0a4e43efc16..5258623e3eb 100644 --- a/tests/main/interfaces-desktop-launch/task.yaml +++ b/tests/main/interfaces-desktop-launch/task.yaml @@ -52,6 +52,3 @@ execute: | echo "The app was invoked with the arguments in the desktop file" MATCH "^args=arg-before arg-after$" < "$launch_data" - - echo "The app was invoked with the environment requested by the launcher" - MATCH "^XDG_CURRENT_DESKTOP=spread-test$" < "$launch_data" diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh index dc5c8cb5f59..05a392c851d 100755 --- a/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh +++ b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh @@ -3,4 +3,4 @@ exec dbus-send --session --print-reply \ --dest=io.snapcraft.Launcher /io/snapcraft/Launcher \ io.snapcraft.PrivilegedDesktopLauncher.OpenDesktopEntry \ - string:"$1" array:string:"XDG_CURRENT_DESKTOP=spread-test" + string:"$1" From 020b575b2e22e0ccfdc216840a10eee84f03189d Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 16 Dec 2020 17:17:10 +0000 Subject: [PATCH 091/114] Add "internal error: " to what is currently a logic error --- usersession/userd/privileged_desktop_launcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index b11fe3bfe95..9ef27d94bb8 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -166,7 +166,7 @@ func verifyDesktopFileLocation(desktopFile string) error { // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and // considered safe for userd to process. If other directories are added in the future, // verifyDesktopFileLocation() and parseExecCommand() may need to be updated. - return fmt.Errorf("only launching snap applications from /var/lib/snapd/desktop/applications is supported") + return fmt.Errorf("internal error: only launching snap applications from /var/lib/snapd/desktop/applications is supported") } return nil From dfa591f76e12854c4075f175b937d980349bcc36 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Wed, 3 Feb 2021 10:35:42 +0000 Subject: [PATCH 092/114] Use the new regularFileExists signature --- usersession/userd/export_test.go | 2 +- usersession/userd/privileged_desktop_launcher.go | 3 ++- .../userd/privileged_desktop_launcher_internal_test.go | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/usersession/userd/export_test.go b/usersession/userd/export_test.go index 49569fefe44..eeedc23740e 100644 --- a/usersession/userd/export_test.go +++ b/usersession/userd/export_test.go @@ -37,7 +37,7 @@ var ( ReadExecCommandFromDesktopFile = readExecCommandFromDesktopFile ) -func MockRegularFileExists(f func(string) bool) func() { +func MockRegularFileExists(f func(string) (bool, bool, error)) func() { old := regularFileExists regularFileExists = f return func() { diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 9ef27d94bb8..6defae6f9ee 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -118,7 +118,8 @@ var regularFileExists = osutil.RegularFileExists func findDesktopFile(baseDir string, splitFileId []string) (string, error) { desktopFile := filepath.Join(baseDir, strings.Join(splitFileId, "-")) - if regularFileExists(desktopFile) { + exists, isReg, _ := regularFileExists(desktopFile) + if exists && isReg { return desktopFile, nil } diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 611ebb5b822..943524364c4 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -349,8 +349,9 @@ Name[zh_TW]=以暫時性個人身分開啟新視窗 Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/chromium_chromium.desktop /snap/bin/chromium --temp-profile ` -func existsOnMockFileSystem(desktop_file string) bool { - return strutil.ListContains(mockFileSystem, desktop_file) +func existsOnMockFileSystem(desktop_file string) (bool, bool, error) { + existsOnMockFileSystem := strutil.ListContains(mockFileSystem, desktop_file) + return existsOnMockFileSystem, existsOnMockFileSystem, nil } func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucceedsWithValidId(c *C) { From 1751bff16f8ddb45be04a127ec7884240561541a Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 22 Feb 2021 12:46:32 +0000 Subject: [PATCH 093/114] Use `--collect` with systemd-run --- usersession/userd/privileged_desktop_launcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 6defae6f9ee..9396e09ec0e 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -93,7 +93,7 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return dbus.MakeFailedError(err) } - args = append([]string{"systemd-run", "--user", "--"}, args...) + args = append([]string{"systemd-run", "--user", "--collect", "--"}, args...) cmd := exec.Command(args[0], args[1:]...) From 0e7669906c00ef8fbedb5936277562fdd5935180 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 22 Feb 2021 12:56:48 +0000 Subject: [PATCH 094/114] unnecessary whitespace Co-authored-by: Ian Johnson --- usersession/userd/privileged_desktop_launcher.go | 1 - 1 file changed, 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 9396e09ec0e..fc2dfd16116 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -147,7 +147,6 @@ func desktopFileIDToFilename(desktopFileID string) (string, error) { baseDir := dirs.SnapDesktopFilesDir desktopFile, err := findDesktopFile(baseDir, strings.Split(desktopFileID, "-")) - if err == nil { return desktopFile, nil } From e312ab18368020722651a0c7af5dd8d1b8c48b65 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 22 Feb 2021 12:58:18 +0000 Subject: [PATCH 095/114] we should keep the error and include it in the message Co-authored-by: Ian Johnson --- usersession/userd/privileged_desktop_launcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index fc2dfd16116..fe2a7882f23 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -97,8 +97,8 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende cmd := exec.Command(args[0], args[1:]...) - if cmd.Run() != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot run %q", command)) + if err := cmd.Run(); err != nil { + return dbus.MakeFailedError(fmt.Errorf("cannot run %q: %v", command, , err)) } return nil From d7a07aaa519820cee58e42dc91f66e63610bfd76 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 22 Feb 2021 12:59:28 +0000 Subject: [PATCH 096/114] change to a TODO Co-authored-by: Ian Johnson --- usersession/userd/privileged_desktop_launcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index fe2a7882f23..782c36e7f96 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -189,7 +189,7 @@ func readExecCommandFromDesktopFile(desktopFile string) (exec string, icon strin if line == "[Desktop Entry]" { inDesktopSection = true } else if strings.HasPrefix(line, "[Desktop Action ") { - // maybe later we'll add support here + // TODO: add support for desktop action sections inDesktopSection = false } else if strings.HasPrefix(line, "[") { inDesktopSection = false From dcdac5ea5d3f58f62cb3d95c69b817ab592e2641 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Mon, 22 Feb 2021 14:00:20 +0000 Subject: [PATCH 097/114] Remove spurious comma --- usersession/userd/privileged_desktop_launcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 782c36e7f96..737b346263f 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -98,7 +98,7 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende cmd := exec.Command(args[0], args[1:]...) if err := cmd.Run(); err != nil { - return dbus.MakeFailedError(fmt.Errorf("cannot run %q: %v", command, , err)) + return dbus.MakeFailedError(fmt.Errorf("cannot run %q: %v", command, err)) } return nil From a97e900982c890dd33ccb914594b68b03cd9b049 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Thu, 25 Feb 2021 14:04:37 +0800 Subject: [PATCH 098/114] usersession/userd: only pass --collect if we have a new enough systemd --- .../userd/privileged_desktop_launcher.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 737b346263f..2d3732535a9 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -32,6 +32,7 @@ import ( "github.com/snapcore/snapd/dirs" "github.com/snapcore/snapd/osutil" "github.com/snapcore/snapd/strutil/shlex" + "github.com/snapcore/snapd/systemd" ) const privilegedLauncherIntrospectionXML = ` @@ -93,11 +94,23 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende return dbus.MakeFailedError(err) } - args = append([]string{"systemd-run", "--user", "--collect", "--"}, args...) + ver, err := systemd.Version() + if err != nil { + return dbus.MakeFailedError(err) + } + // systemd 236 introduced the --collect option to systemd-run, + // which specifies that the unit should be garbage collected + // even if it fails. + // https://github.com/systemd/systemd/pull/7314 + if ver >= 236 { + args = append([]string{"systemd-run", "--user", "--collect", "--"}, args...) + } else { + args = append([]string{"systemd-run", "--user", "--"}, args...) + } cmd := exec.Command(args[0], args[1:]...) - if err := cmd.Run(); err != nil { + if err := cmd.Run(); err != nil { return dbus.MakeFailedError(fmt.Errorf("cannot run %q: %v", command, err)) } From f6a83df85eb363dcb4e4a3335e8d49b230e4dd56 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Fri, 19 Mar 2021 16:56:53 +0800 Subject: [PATCH 099/114] u/userd: test error message from desktopFileIDToFilename --- usersession/userd/privileged_desktop_launcher_internal_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 943524364c4..800d07ba998 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -387,7 +387,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFail for _, id := range desktopIdTests { _, err := userd.DesktopFileIDToFilename(id) - c.Assert(err, NotNil) + c.Check(err, ErrorMatches, `cannot find desktop file for ".*"`) } } From 742d1d8edb311794feb0bb8b7d8d9594a775f5a6 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Mon, 22 Mar 2021 13:27:44 +0800 Subject: [PATCH 100/114] usersession/userd: apply a regexp to validate desktop file IDs --- .../userd/privileged_desktop_launcher.go | 17 +++++++++++++++-- ...privileged_desktop_launcher_internal_test.go | 7 ++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 2d3732535a9..f3cc69f13ab 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -25,6 +25,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strings" "github.com/godbus/dbus" @@ -143,15 +144,25 @@ func findDesktopFile(baseDir string, splitFileId []string) (string, error) { for i := 1; i != len(splitFileId); i++ { desktopFile, err := findDesktopFile(filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) if err == nil { - return desktopFile, err + return desktopFile, nil } } return "", fmt.Errorf("could not find desktop file") } +// isValidDesktopFileID is based on the "File naming" section of the +// Desktop Entry Specification, without the restriction on components +// not starting with a digit (which desktop files created by snapd may +// not satisfy). +var isValidDesktopFileID = regexp.MustCompile(`^[A-Za-z0-9-_]+(\.[A-Za-z0-9-_]+)*$`).MatchString + // desktopFileIDToFilename determines the path associated with a desktop file ID. func desktopFileIDToFilename(desktopFileID string) (string, error) { + if !isValidDesktopFileID(desktopFileID) { + return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) + } + // OpenDesktopEntry() currently only supports launching snap applications from // desktop files in /var/lib/snapd/desktop/applications and these desktop files are // written by snapd and considered safe for userd to process. @@ -159,7 +170,9 @@ func desktopFileIDToFilename(desktopFileID string) (string, error) { // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html baseDir := dirs.SnapDesktopFilesDir - desktopFile, err := findDesktopFile(baseDir, strings.Split(desktopFileID, "-")) + splitDesktopID := strings.Split(desktopFileID, "-") + + desktopFile, err := findDesktopFile(baseDir, splitDesktopID) if err == nil { return desktopFile, nil } diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 800d07ba998..b685b74f248 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -383,11 +383,16 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFail "bar-foo-baz.desktop", "bar-baz-foo.desktop", "foo-bar_foo-bar.desktop", + // special path segments cannot be smuggled inside desktop IDs + "bar-..-vlc_vlc.desktop", + "foo-bar/baz.desktop", + "bar/../vlc_vlc.desktop", + "../applications/vlc_vlc.desktop", } for _, id := range desktopIdTests { _, err := userd.DesktopFileIDToFilename(id) - c.Check(err, ErrorMatches, `cannot find desktop file for ".*"`) + c.Check(err, ErrorMatches, `cannot find desktop file for ".*"`, Commentf(id)) } } From 2f45fd4841a00af08bdbc85f22162a858f2abd48 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Mon, 22 Mar 2021 18:13:03 +0800 Subject: [PATCH 101/114] usersession/userd: update copyright notices --- usersession/userd/privileged_desktop_launcher.go | 2 +- usersession/userd/privileged_desktop_launcher_internal_test.go | 2 +- usersession/userd/privileged_desktop_launcher_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index f3cc69f13ab..2ab6226c4bb 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2019 Canonical Ltd + * Copyright (C) 2021 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 diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index b685b74f248..76a3b1d3c8b 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2019-20 Canonical Ltd + * Copyright (C) 2021 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 diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index 6ff454df37c..a103591881e 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -1,7 +1,7 @@ // -*- Mode: Go; indent-tabs-mode: t -*- /* - * Copyright (C) 2020 Canonical Ltd + * Copyright (C) 2021 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 From b16ccb401a310920253be2f78aaceeab408d7e1b Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Mon, 22 Mar 2021 18:49:19 +0800 Subject: [PATCH 102/114] usersession/userd: fix up some error assertions in tests, and catch some more invalid desktop file IDs --- .../userd/privileged_desktop_launcher.go | 11 ++++++++-- ...ivileged_desktop_launcher_internal_test.go | 22 +++++++++++-------- .../userd/privileged_desktop_launcher_test.go | 4 ++-- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 2ab6226c4bb..95229449e4d 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -142,7 +142,14 @@ func findDesktopFile(baseDir string, splitFileId []string) (string, error) { // we're only checking dirs.SnapDesktopFilesDir (not all entries in $XDG_DATA_DIRS) and we know that snapd // does not create subdirectories in that location. for i := 1; i != len(splitFileId); i++ { - desktopFile, err := findDesktopFile(filepath.Join(baseDir, strings.Join(splitFileId[:i], "-")), splitFileId[i:]) + prefix := strings.Join(splitFileId[:i], "-") + // Don't treat empty or "." components as directory + // prefixes. The ".." case is already filtered out by + // the isValidDesktopFileID regexp. + if prefix == "" || prefix == "." { + continue + } + desktopFile, err := findDesktopFile(filepath.Join(baseDir, prefix), splitFileId[i:]) if err == nil { return desktopFile, nil } @@ -155,7 +162,7 @@ func findDesktopFile(baseDir string, splitFileId []string) (string, error) { // Desktop Entry Specification, without the restriction on components // not starting with a digit (which desktop files created by snapd may // not satisfy). -var isValidDesktopFileID = regexp.MustCompile(`^[A-Za-z0-9-_]+(\.[A-Za-z0-9-_]+)*$`).MatchString +var isValidDesktopFileID = regexp.MustCompile(`^[A-Za-z0-9-_]+(\.[A-Za-z0-9-_]+)*.desktop$`).MatchString // desktopFileIDToFilename determines the path associated with a desktop file ID. func desktopFileIDToFilename(desktopFileID string) (string, error) { diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 76a3b1d3c8b..2ecabec3443 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -47,7 +47,6 @@ var mockFileSystem = []string{ "/var/lib/snapd/desktop/applications/gnome-system-monitor_gnome-system-monitor.desktop", "/var/lib/snapd/desktop/applications/inkscape_inkscape.desktop", "/var/lib/snapd/desktop/applications/gnome-logs_gnome-logs.desktop", - "/var/lib/snapd/desktop/applications/foo-bar/baz.desktop", "/var/lib/snapd/desktop/applications/baz/foo-bar.desktop", } @@ -388,6 +387,11 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFail "foo-bar/baz.desktop", "bar/../vlc_vlc.desktop", "../applications/vlc_vlc.desktop", + // Other invalid desktop IDs + "---------foo-bar-baz.desktop", + "foo-bar-baz.desktop-foo-bar", + "not-a-dot-desktop", + "以临时配置文件打开新-non-ascii-here-too.desktop", } for _, id := range desktopIdTests { @@ -427,8 +431,8 @@ func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWit for _, test := range exec_command { actual, err := userd.ParseExecCommand(test.exec_command, "/snap/chromium/1193/chromium.png") - c.Assert(err, IsNil) - c.Assert(actual, DeepEquals, test.expect) + c.Check(err, IsNil, Commentf(test.exec_command)) + c.Check(actual, DeepEquals, test.expect, Commentf(test.exec_command)) } } @@ -441,7 +445,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandFailsWithIn for _, test := range exec_command { _, err := userd.ParseExecCommand(test, "/snap/chromium/1193/chromium.png") - c.Assert(err, NotNil) + c.Check(err, ErrorMatches, "EOF found when expecting closing quote", Commentf(test)) } } @@ -456,8 +460,8 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF exec, icon, err := userd.ReadExecCommandFromDesktopFile(desktopFile) c.Assert(err, IsNil) - c.Assert(exec, DeepEquals, "env BAMF_DESKTOP_FILE_HINT="+desktopFile+" /snap/bin/chromium %U") - c.Assert(icon, DeepEquals, "/snap/chromium/1193/chromium.png") + c.Check(exec, Equals, "env BAMF_DESKTOP_FILE_HINT="+desktopFile+" /snap/bin/chromium %U") + c.Check(icon, Equals, "/snap/chromium/1193/chromium.png") } func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithInvalidExec(c *C) { @@ -467,7 +471,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF c.Assert(err, IsNil) _, _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) - c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `desktop file ".*" has an unsupported 'Exec' value: .*`) } func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithNoDesktopEntry(c *C) { @@ -481,12 +485,12 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF c.Assert(err, IsNil) _, _, err = userd.ReadExecCommandFromDesktopFile(desktopFile) - c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `desktop file ".*" has an unsupported 'Exec' value: ""`) } func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithNoFile(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") _, _, err := userd.ReadExecCommandFromDesktopFile(desktopFile) - c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `open .*: no such file or directory`) } diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index a103591881e..037660eb85c 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -77,7 +77,7 @@ func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadDesktop defer cmd.Restore() err := s.launcher.OpenDesktopEntry("not-mircade_mircade.desktop", ":some-dbus-sender") - c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `cannot find desktop file for "not-mircade_mircade.desktop"`) } func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadExecutable(c *C) { @@ -85,5 +85,5 @@ func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadExecuta defer cmd.Restore() err := s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") - c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `cannot run ".*": exit status 1`) } From e5e989e6c54f2aa4952325b74820b8ec564e7ae8 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Wed, 24 Mar 2021 15:05:38 +0800 Subject: [PATCH 103/114] usersession/userd: follow the XDG Base Dir spec in resolving desktop file IDs --- usersession/userd/export_test.go | 2 + .../userd/privileged_desktop_launcher.go | 48 +++++++++--- ...ivileged_desktop_launcher_internal_test.go | 74 ++++++++++++++++++- .../userd/privileged_desktop_launcher_test.go | 22 +++++- 4 files changed, 131 insertions(+), 15 deletions(-) diff --git a/usersession/userd/export_test.go b/usersession/userd/export_test.go index eeedc23740e..3765331496e 100644 --- a/usersession/userd/export_test.go +++ b/usersession/userd/export_test.go @@ -32,7 +32,9 @@ func MockSnapFromSender(f func(*dbus.Conn, dbus.Sender) (string, error)) func() } var ( + DesktopFileSearchPath = desktopFileSearchPath DesktopFileIDToFilename = desktopFileIDToFilename + VerifyDesktopFileLocation = verifyDesktopFileLocation ParseExecCommand = parseExecCommand ReadExecCommandFromDesktopFile = readExecCommandFromDesktopFile ) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 95229449e4d..0c79508edc9 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -120,6 +120,39 @@ func (s *PrivilegedDesktopLauncher) OpenDesktopEntry(desktopFileID string, sende var regularFileExists = osutil.RegularFileExists +// desktopFileSearchPath returns the list of directories where desktop +// files may be located. It implements the lookup rules documented in +// the XDG Base Directory specification. +func desktopFileSearchPath() []string { + var desktopDirs []string + + // First check $XDG_DATA_HOME, which defaults to $HOME/.local/share + dataHome := os.Getenv("XDG_DATA_HOME") + if dataHome == "" { + homeDir := os.Getenv("HOME") + if homeDir != "" { + dataHome = filepath.Join(homeDir, ".local/share") + } + } + if dataHome != "" { + desktopDirs = append(desktopDirs, filepath.Join(dataHome, "applications")) + } + + // Next check $XDG_DATA_DIRS, with default from spec + dataDirs := os.Getenv("XDG_DATA_DIRS") + if dataDirs == "" { + dataDirs = "/usr/local/share/:/usr/share/" + } + for _, dir := range strings.Split(dataDirs, ":") { + if dir == "" { + continue + } + desktopDirs = append(desktopDirs, filepath.Join(dir, "applications")) + } + + return desktopDirs +} + // findDesktopFile recursively tries each subdirectory that can be formed from the (split) desktop file ID. // Per https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id, // if desktop entries have dashes in the name ('-'), this could be an indication of subdirectories, so search @@ -170,18 +203,11 @@ func desktopFileIDToFilename(desktopFileID string) (string, error) { return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) } - // OpenDesktopEntry() currently only supports launching snap applications from - // desktop files in /var/lib/snapd/desktop/applications and these desktop files are - // written by snapd and considered safe for userd to process. - // Since we only access /var/lib/snapd/desktop/applications, ignore - // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html - baseDir := dirs.SnapDesktopFilesDir - splitDesktopID := strings.Split(desktopFileID, "-") - - desktopFile, err := findDesktopFile(baseDir, splitDesktopID) - if err == nil { - return desktopFile, nil + for _, baseDir := range desktopFileSearchPath() { + if desktopFile, err := findDesktopFile(baseDir, splitDesktopID); err == nil { + return desktopFile, nil + } } return "", fmt.Errorf("cannot find desktop file for %q", desktopFileID) diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 2ecabec3443..858860c349d 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -20,15 +20,20 @@ package userd_test import ( - "github.com/snapcore/snapd/strutil" - "github.com/snapcore/snapd/usersession/userd" - . "gopkg.in/check.v1" "io/ioutil" + "os" "path/filepath" "strings" + + . "gopkg.in/check.v1" + + "github.com/snapcore/snapd/strutil" + "github.com/snapcore/snapd/testutil" + "github.com/snapcore/snapd/usersession/userd" ) type privilegedDesktopLauncherInternalSuite struct { + testutil.BaseTest } var _ = Suite(&privilegedDesktopLauncherInternalSuite{}) @@ -49,6 +54,11 @@ var mockFileSystem = []string{ "/var/lib/snapd/desktop/applications/gnome-logs_gnome-logs.desktop", "/var/lib/snapd/desktop/applications/foo-bar/baz.desktop", "/var/lib/snapd/desktop/applications/baz/foo-bar.desktop", + + // A desktop file ID provided by a snap may be shadowed by the + // host system. + "/usr/share/applications/shadow-test.desktop", + "/var/lib/snapd/desktop/applications/shadow-test.desktop", } var chromiumDesktopFile = `[Desktop Entry] @@ -353,9 +363,47 @@ func existsOnMockFileSystem(desktop_file string) (bool, bool, error) { return existsOnMockFileSystem, existsOnMockFileSystem, nil } +func (s *privilegedDesktopLauncherInternalSuite) mockEnv(key, value string) { + old := os.Getenv(key) + os.Setenv(key, value) + s.AddCleanup(func() { + os.Setenv(key, old) + }) +} + +func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileSearchPath(c *C) { + s.mockEnv("HOME", "/home/user") + s.mockEnv("XDG_DATA_HOME", "") + s.mockEnv("XDG_DATA_DIRS", "") + + // Default search path + c.Check(userd.DesktopFileSearchPath(), DeepEquals, []string{ + "/home/user/.local/share/applications", + "/usr/local/share/applications", + "/usr/share/applications", + }) + + // XDG_DATA_HOME will override the first path + s.mockEnv("XDG_DATA_HOME", "/home/user/share") + c.Check(userd.DesktopFileSearchPath(), DeepEquals, []string{ + "/home/user/share/applications", + "/usr/local/share/applications", + "/usr/share/applications", + }) + + // XDG_DATA_DIRS changes the remaining paths + s.mockEnv("XDG_DATA_DIRS", "/usr/share:/var/lib/snapd/desktop") + c.Check(userd.DesktopFileSearchPath(), DeepEquals, []string{ + "/home/user/share/applications", + "/usr/share/applications", + "/var/lib/snapd/desktop/applications", + }) +} + func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucceedsWithValidId(c *C) { restore := userd.MockRegularFileExists(existsOnMockFileSystem) defer restore() + s.mockEnv("XDG_DATA_DIRS", "/usr/local/share:/usr/share:/var/lib/snapd/desktop") var desktopIdTests = []struct { id string @@ -364,6 +412,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucc {"mir-kiosk-scummvm_mir-kiosk-scummvm.desktop", "/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop"}, {"foo-bar-baz.desktop", "/var/lib/snapd/desktop/applications/foo-bar/baz.desktop"}, {"baz-foo-bar.desktop", "/var/lib/snapd/desktop/applications/baz/foo-bar.desktop"}, + {"shadow-test.desktop", "/usr/share/applications/shadow-test.desktop"}, } for _, test := range desktopIdTests { @@ -376,6 +425,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameSucc func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFailsWithInvalidId(c *C) { restore := userd.MockRegularFileExists(existsOnMockFileSystem) defer restore() + s.mockEnv("XDG_DATA_DIRS", "/usr/local/share:/usr/share:/var/lib/snapd/desktop") var desktopIdTests = []string{ "mir-kiosk-scummvm-mir-kiosk-scummvm.desktop", @@ -400,6 +450,24 @@ func (s *privilegedDesktopLauncherInternalSuite) TestDesktopFileIDToFilenameFail } } +func (s *privilegedDesktopLauncherInternalSuite) TestVerifyDesktopFileLocation(c *C) { + restore := userd.MockRegularFileExists(existsOnMockFileSystem) + defer restore() + s.mockEnv("XDG_DATA_DIRS", "/usr/local/share:/usr/share:/var/lib/snapd/desktop") + + // Resolved desktop files belonging to snaps will pass verification: + filename, err := userd.DesktopFileIDToFilename("mir-kiosk-scummvm_mir-kiosk-scummvm.desktop") + c.Assert(err, IsNil) + err = userd.VerifyDesktopFileLocation(filename) + c.Check(err, IsNil) + + // Desktop IDs belonging to host system apps fail: + filename, err = userd.DesktopFileIDToFilename("shadow-test.desktop") + c.Assert(err, IsNil) + err = userd.VerifyDesktopFileLocation(filename) + c.Check(err, ErrorMatches, "internal error: only launching snap applications from /var/lib/snapd/desktop/applications is supported") +} + func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) { var exec_command = []struct { exec_command string diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index 037660eb85c..aec21e294f1 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -33,12 +33,16 @@ import ( ) type privilegedDesktopLauncherSuite struct { + testutil.BaseTest + launcher *userd.PrivilegedDesktopLauncher } var _ = Suite(&privilegedDesktopLauncherSuite{}) func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + dirs.SetRootDir(c.MkDir()) s.launcher = &userd.PrivilegedDesktopLauncher{} @@ -59,9 +63,25 @@ func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { c.Assert(err, IsNil) err = ioutil.WriteFile(deskTopFile, []byte(desktopContent), 0644) c.Assert(err, IsNil) + + s.mockEnv("HOME", filepath.Join(dirs.GlobalRootDir, "/home/user")) + s.mockEnv("XDG_DATA_HOME", "") + s.mockEnv("XDG_DATA_DIRS", strings.Join([]string{ + filepath.Join(dirs.GlobalRootDir, "/usr/share"), + filepath.Dir(dirs.SnapDesktopFilesDir), + }, ":")) } func (s *privilegedDesktopLauncherSuite) TearDownTest(c *C) { + s.BaseTest.TearDownTest(c) +} + +func (s *privilegedDesktopLauncherSuite) mockEnv(key, value string) { + old := os.Getenv(key) + os.Setenv(key, value) + s.AddCleanup(func() { + os.Setenv(key, old) + }) } func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntrySucceedsWithGoodDesktopId(c *C) { @@ -73,7 +93,7 @@ func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntrySucceedsWithGoodDes } func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadDesktopId(c *C) { - cmd := testutil.MockCommand(c, "systemd-run", "true") + cmd := testutil.MockCommand(c, "systemd-run", "false") defer cmd.Restore() err := s.launcher.OpenDesktopEntry("not-mircade_mircade.desktop", ":some-dbus-sender") From f5f487373bb5eaf423465836756f82ad0ac01514 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Wed, 24 Mar 2021 15:12:17 +0800 Subject: [PATCH 104/114] tests: ensure XDG_DATA_DIRS is set in spread test --- tests/main/interfaces-desktop-launch/task.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/main/interfaces-desktop-launch/task.yaml b/tests/main/interfaces-desktop-launch/task.yaml index 5258623e3eb..ff718730fc2 100644 --- a/tests/main/interfaces-desktop-launch/task.yaml +++ b/tests/main/interfaces-desktop-launch/task.yaml @@ -11,6 +11,8 @@ prepare: | exit 0 fi tests.session -u test prepare + tests.session -u test exec systemctl --user \ + set-environment XDG_DATA_DIRS=/usr/share:/var/lib/snapd/desktop restore: | if ! tests.session has-session-systemd-and-dbus; then From 3d48386e763dab71bcd9f6b25cf4e35689bd906b Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Wed, 24 Mar 2021 15:23:38 +0800 Subject: [PATCH 105/114] usersession/userd: don't reuse the object path of the existing launcher interface for PrivilegedDesktopLauncher This reinforces that the API is not in the same security domain as those exported on /io/snapcraft/Launcher. --- interfaces/builtin/desktop_launch.go | 2 +- .../interfaces-desktop-launch/test-launcher/bin/launcher.sh | 2 +- usersession/userd/privileged_desktop_launcher.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index f800313f83f..6abe3cb6ce9 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -46,7 +46,7 @@ const desktopLaunchConnectedPlugAppArmor = ` dbus (send) bus=session - path=/io/snapcraft/Launcher + path=/io/snapcraft/PrivilegedDesktopLauncher interface=io.snapcraft.PrivilegedDesktopLauncher member=OpenDesktopEntry peer=(label=unconfined), diff --git a/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh index 05a392c851d..0e9af61e9a5 100755 --- a/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh +++ b/tests/main/interfaces-desktop-launch/test-launcher/bin/launcher.sh @@ -1,6 +1,6 @@ #!/bin/sh exec dbus-send --session --print-reply \ - --dest=io.snapcraft.Launcher /io/snapcraft/Launcher \ + --dest=io.snapcraft.Launcher /io/snapcraft/PrivilegedDesktopLauncher \ io.snapcraft.PrivilegedDesktopLauncher.OpenDesktopEntry \ string:"$1" diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 0c79508edc9..ecaccb28d45 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -62,7 +62,7 @@ func (s *PrivilegedDesktopLauncher) Interface() string { // BasePath returns the base path of the object func (s *PrivilegedDesktopLauncher) ObjectPath() dbus.ObjectPath { - return "/io/snapcraft/Launcher" + return "/io/snapcraft/PrivilegedDesktopLauncher" } // IntrospectionData gives the XML formatted introspection description From 14009e0165dafd845e3c7dec792aad909003a9ca Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Wed, 24 Mar 2021 15:34:48 +0800 Subject: [PATCH 106/114] usersession/userd: add a direct test for DesktopFileIDToFilename without mocked stat calls --- usersession/userd/privileged_desktop_launcher_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index aec21e294f1..5c8527091b5 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -84,6 +84,16 @@ func (s *privilegedDesktopLauncherSuite) mockEnv(key, value string) { }) } +func (s *privilegedDesktopLauncherSuite) TestDesktopFileLookup(c *C) { + // We have more extensive tests for this API in + // privileged_desktop_launcher_internal_test.go: here we just + // test it without mocking the stat calls. + filename, err := userd.DesktopFileIDToFilename("mircade_mircade.desktop") + c.Assert(err, IsNil) + err = userd.VerifyDesktopFileLocation(filename) + c.Check(err, IsNil) +} + func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntrySucceedsWithGoodDesktopId(c *C) { cmd := testutil.MockCommand(c, "systemd-run", "true") defer cmd.Restore() From b1449832ba2ab89af6937c27e2ca52112d5dc8b9 Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Wed, 24 Mar 2021 15:42:40 +0800 Subject: [PATCH 107/114] usersession/userd: add test demonstrating that launching non-snap desktop files fails --- .../userd/privileged_desktop_launcher_test.go | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index 5c8527091b5..f9b9963b413 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -46,6 +46,9 @@ func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { dirs.SetRootDir(c.MkDir()) s.launcher = &userd.PrivilegedDesktopLauncher{} + c.Assert(os.MkdirAll(dirs.SnapDesktopFilesDir, 0755), IsNil) + c.Assert(os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "/usr/share/applications"), 0755), IsNil) + var rawMircadeDesktop = `[Desktop Entry] X-SnapInstanceName=mircade Name=mircade @@ -59,10 +62,11 @@ func (s *privilegedDesktopLauncherSuite) SetUpTest(c *C) { desktopContent := strings.Replace(tmpMircadeDesktop, "/snap/bin/", dirs.SnapBinariesDir, -1) deskTopFile := filepath.Join(dirs.SnapDesktopFilesDir, "mircade_mircade.desktop") - err := os.MkdirAll(filepath.Dir(deskTopFile), 0755) - c.Assert(err, IsNil) - err = ioutil.WriteFile(deskTopFile, []byte(desktopContent), 0644) - c.Assert(err, IsNil) + c.Assert(ioutil.WriteFile(deskTopFile, []byte(desktopContent), 0644), IsNil) + + // Create a shadowed desktop file ID + c.Assert(ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "/usr/share/applications/shadow-test.desktop"), []byte("[Desktop Entry]"), 0644), IsNil) + c.Assert(ioutil.WriteFile(filepath.Join(dirs.SnapDesktopFilesDir, "shadow-test.desktop"), []byte("[Desktop Entry]"), 0644), IsNil) s.mockEnv("HOME", filepath.Join(dirs.GlobalRootDir, "/home/user")) s.mockEnv("XDG_DATA_HOME", "") @@ -99,7 +103,7 @@ func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntrySucceedsWithGoodDes defer cmd.Restore() err := s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") - c.Assert(err, IsNil) + c.Check(err, IsNil) } func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadDesktopId(c *C) { @@ -115,5 +119,13 @@ func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsWithBadExecuta defer cmd.Restore() err := s.launcher.OpenDesktopEntry("mircade_mircade.desktop", ":some-dbus-sender") - c.Assert(err, ErrorMatches, `cannot run ".*": exit status 1`) + c.Check(err, ErrorMatches, `cannot run ".*": exit status 1`) +} + +func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsForNonSnap(c *C) { + cmd := testutil.MockCommand(c, "systemd-run", "false") + defer cmd.Restore() + + err := s.launcher.OpenDesktopEntry("shadow-test.desktop", ":some-dbus-sender") + c.Check(err, ErrorMatches, `internal error: only launching snap applications from .* is supported`) } From a8c979ca166db9b668e76977d4843c7f4664124b Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Wed, 24 Mar 2021 16:05:17 +0800 Subject: [PATCH 108/114] usersession/userd: more fixups based on review comments --- .../userd/privileged_desktop_launcher.go | 6 +-- ...ivileged_desktop_launcher_internal_test.go | 37 ++++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index ecaccb28d45..8d2e472e34a 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -46,7 +46,7 @@ const privilegedLauncherIntrospectionXML = ` - + ` @@ -225,7 +225,7 @@ func verifyDesktopFileLocation(desktopFile string) error { // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and // considered safe for userd to process. If other directories are added in the future, // verifyDesktopFileLocation() and parseExecCommand() may need to be updated. - return fmt.Errorf("internal error: only launching snap applications from /var/lib/snapd/desktop/applications is supported") + return fmt.Errorf("internal error: only launching snap applications from %s is supported", dirs.SnapDesktopFilesDir) } return nil @@ -298,7 +298,7 @@ func parseExecCommand(command string, icon string) ([]string, error) { case "%i": args = append(args, "--icon", icon) default: - return nil, fmt.Errorf("cannot run %q", command) + return nil, fmt.Errorf("cannot run %q due to use of %q", command, arg) } continue } diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 858860c349d..1db2ed2bda0 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -469,9 +469,9 @@ func (s *privilegedDesktopLauncherInternalSuite) TestVerifyDesktopFileLocation(c } func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) { - var exec_command = []struct { - exec_command string - expect []string + var testCases = []struct { + cmd string + expect []string }{ // valid with no exec variables {"env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/mir-kiosk-scummvm_mir-kiosk-scummvm.desktop /snap/bin/mir-kiosk-scummvm %U", @@ -497,23 +497,32 @@ func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWit {"/snap/bin/foo -f %%bar %U %i", []string{"/snap/bin/foo", "-f", "%bar", "--icon", "/snap/chromium/1193/chromium.png"}}, } - for _, test := range exec_command { - actual, err := userd.ParseExecCommand(test.exec_command, "/snap/chromium/1193/chromium.png") - c.Check(err, IsNil, Commentf(test.exec_command)) - c.Check(actual, DeepEquals, test.expect, Commentf(test.exec_command)) + for _, test := range testCases { + actual, err := userd.ParseExecCommand(test.cmd, "/snap/chromium/1193/chromium.png") + comment := Commentf("cmd=%s", test.cmd) + c.Check(err, IsNil, comment) + c.Check(actual, DeepEquals, test.expect, comment) } } func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandFailsWithInvalidEntry(c *C) { - // the only invalid entries are those that error from shlex.Split() - var exec_command = []string{ - "/snap/bin/foo \"unclosed double quote", - "/snap/bin/foo 'unclosed single quote", + var testCases = []struct { + cmd string + err string + }{ + // Commands may be rejected for bad quoting + {`/snap/bin/foo "unclosed double quote`, "EOF found when expecting closing quote"}, + {`/snap/bin/foo 'unclosed single quote`, "EOF found when expecting closing quote"}, + + // Or use of unexpected unknown variables + {"/snap/bin/foo %z", `cannot run "/snap/bin/foo %z" due to use of "%z"`}, + {"/snap/bin/foo %", `cannot run "/snap/bin/foo %" due to use of "%"`}, } - for _, test := range exec_command { - _, err := userd.ParseExecCommand(test, "/snap/chromium/1193/chromium.png") - c.Check(err, ErrorMatches, "EOF found when expecting closing quote", Commentf(test)) + for _, test := range testCases { + _, err := userd.ParseExecCommand(test.cmd, "/snap/chromium/1193/chromium.png") + comment := Commentf("cmd=%s", test.cmd) + c.Check(err, ErrorMatches, test.err, comment) } } From 47d6a3e8df6d878d1fb196bf0256cae987b12b1e Mon Sep 17 00:00:00 2001 From: James Henstridge Date: Thu, 25 Mar 2021 09:24:55 +0800 Subject: [PATCH 109/114] usersession/userd: reject desktop files with multiple [Desktop Entry] sections --- usersession/userd/privileged_desktop_launcher.go | 6 +++++- .../privileged_desktop_launcher_internal_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 8d2e472e34a..336430e8c6f 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -241,11 +241,15 @@ func readExecCommandFromDesktopFile(desktopFile string) (exec string, icon strin defer file.Close() scanner := bufio.NewScanner(file) - inDesktopSection := false + var inDesktopSection, seenDesktopSection bool for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) if line == "[Desktop Entry]" { + if seenDesktopSection { + return "", "", fmt.Errorf("desktop file %q has multiple [Desktop Entry] sections", desktopFile) + } + seenDesktopSection = true inDesktopSection = true } else if strings.HasPrefix(line, "[Desktop Action ") { // TODO: add support for desktop action sections diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index 1db2ed2bda0..d9bb174a606 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -565,6 +565,19 @@ func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopF c.Assert(err, ErrorMatches, `desktop file ".*" has an unsupported 'Exec' value: ""`) } +func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCOmmandFromDesktopFileMultipleDesktopEntrySections(c *C) { + desktopFile := filepath.Join(c.MkDir(), "test.desktop") + c.Assert(ioutil.WriteFile(desktopFile, []byte(`[Desktop Entry] +Exec=foo + +[Desktop Entry] +Exec=bar +`), 0644), IsNil) + + _, _, err := userd.ReadExecCommandFromDesktopFile(desktopFile) + c.Check(err, ErrorMatches, `desktop file ".*" has multiple \[Desktop Entry\] sections`) +} + func (s *privilegedDesktopLauncherInternalSuite) TestReadExecCommandFromDesktopFileWithNoFile(c *C) { desktopFile := filepath.Join(c.MkDir(), "test.desktop") From 2284c5461cd790d0b3798f0a1df110c207256bdc Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Thu, 15 Apr 2021 17:31:30 +0100 Subject: [PATCH 110/114] Address latest review feedback --- interfaces/builtin/desktop_launch.go | 4 ++-- interfaces/builtin/desktop_launch_test.go | 4 +++- usersession/userd/privileged_desktop_launcher.go | 9 ++------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index 6abe3cb6ce9..22b5c52e25e 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -19,7 +19,7 @@ package builtin -const desktopLaunchSummary = `allows snaps to identify and launch other snaps` +const desktopLaunchSummary = `allows snaps to identify and launch desktop applications in (or from) other snaps` const desktopLaunchBaseDeclarationPlugs = ` desktop-launch: @@ -48,7 +48,7 @@ dbus (send) bus=session path=/io/snapcraft/PrivilegedDesktopLauncher interface=io.snapcraft.PrivilegedDesktopLauncher - member=OpenDesktopEntry + peer=(label=unconfined),member=OpenDesktopEntry peer=(label=unconfined), ` diff --git a/interfaces/builtin/desktop_launch_test.go b/interfaces/builtin/desktop_launch_test.go index fd4cd605d98..5b885acd7e7 100644 --- a/interfaces/builtin/desktop_launch_test.go +++ b/interfaces/builtin/desktop_launch_test.go @@ -80,6 +80,8 @@ func (s *desktopLaunchSuite) TestConnectedPlugSnippet(c *C) { c.Assert(err, IsNil) c.Assert(apparmorSpec.SecurityTags(), DeepEquals, []string{"snap.other.app"}) c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `Can identify and launch other snaps.`) + c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `member=OpenDesktopEntry`) + c.Assert(apparmorSpec.SnippetForTag("snap.other.app"), testutil.Contains, `peer=(label=unconfined),`) } func (s *desktopLaunchSuite) TestInterfaces(c *C) { @@ -90,7 +92,7 @@ func (s *desktopLaunchSuite) TestStaticInfo(c *C) { si := interfaces.StaticInfoOf(s.iface) c.Assert(si.ImplicitOnCore, Equals, false) c.Assert(si.ImplicitOnClassic, Equals, true) - c.Assert(si.Summary, Equals, `allows snaps to identify and launch other snaps`) + c.Assert(si.Summary, Equals, `allows snaps to identify and launch desktop applications in (or from) other snaps`) c.Assert(si.BaseDeclarationSlots, testutil.Contains, "desktop-launch") c.Assert(si.BaseDeclarationSlots, testutil.Contains, "deny-auto-connection: true") c.Assert(si.BaseDeclarationPlugs, testutil.Contains, "desktop-launch") diff --git a/usersession/userd/privileged_desktop_launcher.go b/usersession/userd/privileged_desktop_launcher.go index 336430e8c6f..11fc53b9333 100644 --- a/usersession/userd/privileged_desktop_launcher.go +++ b/usersession/userd/privileged_desktop_launcher.go @@ -171,9 +171,6 @@ func findDesktopFile(baseDir string, splitFileId []string) (string, error) { } // Iterate through the potential subdirectories formed by the first i elements of the desktop file ID. - // Maybe this is overkill: At the time of writing, the only use is in desktopFileIDToFilename() and there - // we're only checking dirs.SnapDesktopFilesDir (not all entries in $XDG_DATA_DIRS) and we know that snapd - // does not create subdirectories in that location. for i := 1; i != len(splitFileId); i++ { prefix := strings.Join(splitFileId[:i], "-") // Don't treat empty or "." components as directory @@ -222,10 +219,8 @@ func verifyDesktopFileLocation(desktopFile string) error { if !strings.HasPrefix(desktopFile, dirs.SnapDesktopFilesDir+"/") { // We currently only support launching snap applications from desktop files in - // /var/lib/snapd/desktop/applications and these desktop files are written by snapd and - // considered safe for userd to process. If other directories are added in the future, - // verifyDesktopFileLocation() and parseExecCommand() may need to be updated. - return fmt.Errorf("internal error: only launching snap applications from %s is supported", dirs.SnapDesktopFilesDir) + // /var/lib/snapd/desktop/applications. + return fmt.Errorf("only launching snap applications from %s is supported", dirs.SnapDesktopFilesDir) } return nil From 42134094275c1c8441902e7ccd6cfd4d9f0260b9 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 16 Apr 2021 09:26:00 +0100 Subject: [PATCH 111/114] Fix accidental paste. --- interfaces/builtin/desktop_launch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interfaces/builtin/desktop_launch.go b/interfaces/builtin/desktop_launch.go index 22b5c52e25e..11ec72d88ac 100644 --- a/interfaces/builtin/desktop_launch.go +++ b/interfaces/builtin/desktop_launch.go @@ -48,7 +48,7 @@ dbus (send) bus=session path=/io/snapcraft/PrivilegedDesktopLauncher interface=io.snapcraft.PrivilegedDesktopLauncher - peer=(label=unconfined),member=OpenDesktopEntry + member=OpenDesktopEntry peer=(label=unconfined), ` From 57ef4e2c2a1cd09bff066e4e2398df4b62b3f36d Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 16 Apr 2021 09:52:00 +0100 Subject: [PATCH 112/114] Update tests to match --- usersession/userd/privileged_desktop_launcher_internal_test.go | 2 +- usersession/userd/privileged_desktop_launcher_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/usersession/userd/privileged_desktop_launcher_internal_test.go b/usersession/userd/privileged_desktop_launcher_internal_test.go index d9bb174a606..cf0d507f0ca 100644 --- a/usersession/userd/privileged_desktop_launcher_internal_test.go +++ b/usersession/userd/privileged_desktop_launcher_internal_test.go @@ -465,7 +465,7 @@ func (s *privilegedDesktopLauncherInternalSuite) TestVerifyDesktopFileLocation(c filename, err = userd.DesktopFileIDToFilename("shadow-test.desktop") c.Assert(err, IsNil) err = userd.VerifyDesktopFileLocation(filename) - c.Check(err, ErrorMatches, "internal error: only launching snap applications from /var/lib/snapd/desktop/applications is supported") + c.Check(err, ErrorMatches, "only launching snap applications from /var/lib/snapd/desktop/applications is supported") } func (s *privilegedDesktopLauncherInternalSuite) TestParseExecCommandSucceedsWithValidEntry(c *C) { diff --git a/usersession/userd/privileged_desktop_launcher_test.go b/usersession/userd/privileged_desktop_launcher_test.go index f9b9963b413..c4183f27392 100644 --- a/usersession/userd/privileged_desktop_launcher_test.go +++ b/usersession/userd/privileged_desktop_launcher_test.go @@ -127,5 +127,5 @@ func (s *privilegedDesktopLauncherSuite) TestOpenDesktopEntryFailsForNonSnap(c * defer cmd.Restore() err := s.launcher.OpenDesktopEntry("shadow-test.desktop", ":some-dbus-sender") - c.Check(err, ErrorMatches, `internal error: only launching snap applications from .* is supported`) + c.Check(err, ErrorMatches, `only launching snap applications from .* is supported`) } From 76284d4b2c496a39cf277bd4d3aaed6b32a4bcfe Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 28 May 2021 23:46:24 +0100 Subject: [PATCH 113/114] Update tests/main/interfaces-desktop-launch/task.yaml Co-authored-by: Ian Johnson --- tests/main/interfaces-desktop-launch/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/interfaces-desktop-launch/task.yaml b/tests/main/interfaces-desktop-launch/task.yaml index ff718730fc2..ca2d6ef2a60 100644 --- a/tests/main/interfaces-desktop-launch/task.yaml +++ b/tests/main/interfaces-desktop-launch/task.yaml @@ -35,7 +35,7 @@ execute: | [ -f /var/lib/snapd/desktop/applications/test-app_test-app.desktop ] echo "Install the launcher snap" - install_local test-launcher + "$TESTSTOOLS"/snaps-state install-local test-launcher echo "The desktop-launch plug is initially disconnected" snap connections test-launcher | MATCH "desktop-launch +test-launcher:desktop-launch +- +-" From 13c4aea1f082cf91a7f02fe33ff29213f4cded98 Mon Sep 17 00:00:00 2001 From: Alan Griffiths Date: Fri, 28 May 2021 23:46:39 +0100 Subject: [PATCH 114/114] Update tests/main/interfaces-desktop-launch/task.yaml Co-authored-by: Ian Johnson --- tests/main/interfaces-desktop-launch/task.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main/interfaces-desktop-launch/task.yaml b/tests/main/interfaces-desktop-launch/task.yaml index ca2d6ef2a60..df93d08996e 100644 --- a/tests/main/interfaces-desktop-launch/task.yaml +++ b/tests/main/interfaces-desktop-launch/task.yaml @@ -30,7 +30,7 @@ execute: | . "$TESTSLIB"/snaps.sh echo "Install the application snap" - install_local test-app + "$TESTSTOOLS"/snaps-state install-local test-app echo "The snap installs a desktop file" [ -f /var/lib/snapd/desktop/applications/test-app_test-app.desktop ]