Skip to content

Commit

Permalink
i/b: dont have polkit interface being implicit on core read-only file…
Browse files Browse the repository at this point in the history
…systems (#13568)

* i/b: dont have polkit interface being implicit on core read-only filesystems

* i/b: fix TestStaticInfo

* osutil: add some defs for macos

* osutil: update copyright date
  • Loading branch information
Meulengracht committed Feb 16, 2024
1 parent 8fc5be2 commit 5407316
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 2 deletions.
1 change: 1 addition & 0 deletions interfaces/builtin/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var (
AareExclusivePatterns = aareExclusivePatterns
GetDesktopFileRules = getDesktopFileRules
StringListAttribute = stringListAttribute
IsPathMountedWritable = isPathMountedWritable
)

func MprisGetName(iface interfaces.Interface, attribs map[string]interface{}) (string, error) {
Expand Down
43 changes: 42 additions & 1 deletion interfaces/builtin/polkit.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"bytes"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"strings"

Expand Down Expand Up @@ -148,6 +149,28 @@ func (iface *polkitInterface) BeforePreparePlug(plug *snap.PlugInfo) error {
return err
}

func isPathMountedWritable(mntProfile *osutil.MountProfile, fsPath string) bool {
mntMap := make(map[string]*osutil.MountEntry, len(mntProfile.Entries))
for _, mnt := range mntProfile.Entries {
mntMap[mnt.Dir] = &mnt
}

// go backwards in path until we hit a match
currentPath := fsPath
for {
if mnt, ok := mntMap[currentPath]; ok {
return mnt.Options[0] == "rw"
}

// Make sure we terminate on the last path token
if currentPath == "/" || !strings.Contains(currentPath, "/") {
break
}
currentPath = path.Dir(currentPath)
}
return false
}

// hasPolkitDaemonExecutable checks known paths on core for the presence of
// the polkit daemon executable. This function can be shortened but keep it like
// this for readability.
Expand All @@ -160,12 +183,30 @@ func hasPolkitDaemonExecutable() bool {
return osutil.IsExecutable("/usr/lib/polkit-1/polkitd")
}

func polkitPoliciesSupported() bool {
// We must have the polkit daemon present on the system
if !hasPolkitDaemonExecutable() {
return false
}

mntProfile, err := osutil.LoadMountProfile("/proc/self/mounts")
if err != nil {
// XXX: we are called from init() what can we do here?
return false
}

// For core22+ polkitd is present, but it's not possible to install
// any policy files, as polkit only checks /usr/share/polkit-1/actions,
// which is readonly on core.
return isPathMountedWritable(mntProfile, "/usr/share/polkit-1/actions")
}

func init() {
registerIface(&polkitInterface{
commonInterface{
name: "polkit",
summary: polkitSummary,
implicitOnCore: hasPolkitDaemonExecutable(),
implicitOnCore: polkitPoliciesSupported(),
implicitOnClassic: true,
baseDeclarationPlugs: polkitBaseDeclarationPlugs,
baseDeclarationSlots: polkitBaseDeclarationSlots,
Expand Down
75 changes: 74 additions & 1 deletion interfaces/builtin/polkit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,9 +271,15 @@ apps:
c.Assert(err, ErrorMatches, `snap "other" has interface "polkit" with invalid value type bool for "action-prefix" attribute: \*string`)
}

func (s *polkitInterfaceSuite) isProfilePathAccessible(c *C) bool {
mntProfile, err := osutil.LoadMountProfile("/proc/self/mounts")
c.Assert(err, IsNil)
return builtin.IsPathMountedWritable(mntProfile, "/usr/share/polkit-1/actions")
}

func (s *polkitInterfaceSuite) TestStaticInfo(c *C) {
si := interfaces.StaticInfoOf(s.iface)
c.Check(si.ImplicitOnCore, Equals, osutil.IsExecutable("/usr/libexec/polkitd") || osutil.IsExecutable("/usr/lib/polkit-1/polkitd"))
c.Check(si.ImplicitOnCore, Equals, s.isProfilePathAccessible(c) && (osutil.IsExecutable("/usr/libexec/polkitd") || osutil.IsExecutable("/usr/lib/polkit-1/polkitd")))
c.Check(si.ImplicitOnClassic, Equals, true)
c.Check(si.Summary, Equals, "allows access to polkitd to check authorisation")
c.Check(si.BaseDeclarationPlugs, testutil.Contains, "polkit")
Expand All @@ -283,3 +289,70 @@ func (s *polkitInterfaceSuite) TestStaticInfo(c *C) {
func (s *polkitInterfaceSuite) TestInterfaces(c *C) {
c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface)
}

func (s *polkitInterfaceSuite) TestIsPathMountedWritable(c *C) {

tests := []struct {
mounts string
path string
expected bool
}{
// Test a base case where root is ro
{
`rpool/ROOT/ubuntu / zfs ro,relatime,xattr,posixacl,casesensitive 0 0
`,
"/usr/share/polkit-1/actions",
false,
},

// Test a base case where root is rw
{
`rpool/ROOT/ubuntu / zfs rw,relatime,xattr,posixacl,casesensitive 0 0
`,
"/usr/share/polkit-1/actions",
true,
},

// Test a case where the root is mounted rw, but /usr/share is ro
{
`rpool/ROOT/ubuntu / zfs rw,relatime,xattr,posixacl,casesensitive 0 0
rpool/ROOT/ubuntu/usr/share /usr/share zfs ro,relatime,xattr,posixacl,casesensitive 0 0
`,
"/usr/share/polkit-1/actions",
false,
},

// Test a case where the root is mounted ro, but /usr/share is rw
{
`rpool/ROOT/ubuntu / zfs ro,relatime,xattr,posixacl,casesensitive 0 0
rpool/ROOT/ubuntu/usr/share /usr/share zfs rw,relatime,xattr,posixacl,casesensitive 0 0
`,
"/usr/share/polkit-1/actions",
true,
},

// Test a case where the root is mounted rw, but the path specifically being ro
{
`rpool/ROOT/ubuntu / zfs ro,relatime,xattr,posixacl,casesensitive 0 0
rpool/ROOT/ubuntu/usr/share/polkit-1/actions /usr/share/polkit-1/actions zfs ro,relatime,xattr,posixacl,casesensitive 0 0
`,
"/usr/share/polkit-1/actions",
false,
},

// Test a case where the path string ends on '/', so we know it's handled
{
`rpool/ROOT/ubuntu / zfs ro,relatime,xattr,posixacl,casesensitive 0 0
rpool/ROOT/ubuntu/usr/share /usr/share zfs rw,relatime,xattr,posixacl,casesensitive 0 0
`,
"/usr/share/",
true,
},
}

for _, t := range tests {
mntProfile, err := osutil.LoadMountProfileText(t.mounts)
c.Assert(err, IsNil)
c.Check(builtin.IsPathMountedWritable(mntProfile, t.path), Equals, t.expected)
}
}
28 changes: 28 additions & 0 deletions osutil/mountprofile_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.
*
*/

package osutil

type MountProfile struct {
Entries []MountEntry
}

func LoadMountProfile(fname string) (*MountProfile, error) {
return nil, ErrDarwin
}

0 comments on commit 5407316

Please sign in to comment.