Skip to content

Commit

Permalink
snapdtool: check for binary existence in InternalToolPath() (#13227)
Browse files Browse the repository at this point in the history
* snapdtool: check for binary existance in InternalToolPath()

The current version of `InternalToolPath()` does not actually
checks in all cases if the binary is actually there. Historically
this was not a problem but with recent snapd releases we have
the internal tool `apparmor_parser` that is only available in
the `snapd` snap but not in the `core` snap. This means that
on systems that have the snapd deb and only core `InternalToolPath`
may return `/snap/core/123/usr/lib/snapd/apparmor_parser` even
if this tool in not available inside the core snap.

This commit fixes this by checking if the actual executable
is available.

* snapdtool: add test for non-executable internal tool path

* snapdtool: simplify InternalToolPath() (thanks to Samuele)
  • Loading branch information
mvo5 authored Oct 24, 2023
1 parent d0e0b8b commit 1f621a9
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 16 deletions.
8 changes: 5 additions & 3 deletions snapdtool/tool_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ func InternalToolPath(tool string) (string, error) {
// only assume mounted location when path contains
// /usr/, but does not start with one
prefix := exe[:idx]
return filepath.Join(prefix, "/usr/lib/snapd", tool), nil
}
if idx == -1 {
maybeTool := filepath.Join(prefix, "/usr/lib/snapd", tool)
if osutil.IsExecutable(maybeTool) {
return maybeTool, nil
}
} else {
// or perhaps some other random location, make sure the tool
// exists there and is an executable
maybeTool := filepath.Join(filepath.Dir(exe), tool)
Expand Down
61 changes: 48 additions & 13 deletions snapdtool/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,17 @@ func (s *toolSuite) fakeCoreVersion(c *C, coreDir, version string) {
c.Assert(os.WriteFile(filepath.Join(p, "info"), []byte("VERSION="+version), 0644), IsNil)
}

func makeFakeExe(c *C, path string) {
err := os.MkdirAll(filepath.Dir(path), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(path, nil, 0755)
c.Assert(err, IsNil)
}

func (s *toolSuite) fakeInternalTool(c *C, coreDir, toolName string) string {
s.fakeCoreVersion(c, coreDir, "42")
p := filepath.Join(coreDir, "/usr/lib/snapd", toolName)
c.Assert(os.WriteFile(p, nil, 0755), IsNil)
makeFakeExe(c, p)

return p
}
Expand Down Expand Up @@ -223,37 +230,46 @@ func (s *toolSuite) TestInternalToolPathWithReexec(c *C) {
}

func (s *toolSuite) TestInternalToolPathWithOtherLocation(c *C) {
s.fakeInternalTool(c, s.snapdPath, "potato")
tmpdir := c.MkDir()
restore := snapdtool.MockOsReadlink(func(string) (string, error) {
return filepath.Join("/tmp/tmp.foo_1234/usr/lib/snapd/snapd"), nil
return filepath.Join(tmpdir, "/tmp/tmp.foo_1234/usr/lib/snapd/snapd"), nil
})
defer restore()

devTool := filepath.Join(tmpdir, "/tmp/tmp.foo_1234/usr/lib/snapd/potato")
makeFakeExe(c, devTool)

path, err := snapdtool.InternalToolPath("potato")
c.Check(err, IsNil)
c.Check(path, Equals, "/tmp/tmp.foo_1234/usr/lib/snapd/potato")
c.Check(path, Equals, tmpdir+"/tmp/tmp.foo_1234/usr/lib/snapd/potato")
}

func (s *toolSuite) TestInternalToolSnapPathWithOtherLocation(c *C) {
tmpdir := c.MkDir()
restore := snapdtool.MockOsReadlink(func(string) (string, error) {
return filepath.Join("/tmp/tmp.foo_1234/usr/bin/snap"), nil
return filepath.Join(tmpdir, "/tmp/tmp.foo_1234/usr/bin/snap"), nil
})
defer restore()

devTool := filepath.Join(tmpdir, "/tmp/tmp.foo_1234/usr/lib/snapd/potato")
makeFakeExe(c, devTool)

path, err := snapdtool.InternalToolPath("potato")
c.Check(err, IsNil)
c.Check(path, Equals, "/tmp/tmp.foo_1234/usr/lib/snapd/potato")
c.Check(path, Equals, tmpdir+"/tmp/tmp.foo_1234/usr/lib/snapd/potato")
}

func (s *toolSuite) TestInternalToolPathWithOtherCrazyLocation(c *C) {
tmpdir := c.MkDir()
s.fakeInternalTool(c, filepath.Join(tmpdir, "/usr/foo/usr/tmp/tmp.foo_1234"), "potato")
restore := snapdtool.MockOsReadlink(func(string) (string, error) {
return filepath.Join("/usr/foo/usr/tmp/tmp.foo_1234/usr/bin/snap"), nil
return filepath.Join(tmpdir, "/usr/foo/usr/tmp/tmp.foo_1234/usr/bin/snap"), nil
})
defer restore()

path, err := snapdtool.InternalToolPath("potato")
c.Check(err, IsNil)
c.Check(path, Equals, "/usr/foo/usr/tmp/tmp.foo_1234/usr/lib/snapd/potato")
c.Check(path, Equals, tmpdir+"/usr/foo/usr/tmp/tmp.foo_1234/usr/lib/snapd/potato")
}

func (s *toolSuite) TestInternalToolPathWithDevLocationFallback(c *C) {
Expand Down Expand Up @@ -290,18 +306,16 @@ func (s *toolSuite) TestInternalToolPathWithOtherDevLocationNonExecutable(c *C)
})
defer restore()

devTool := filepath.Join(dirs.GlobalRootDir, "/tmp/non-executable-potato")
err := os.MkdirAll(filepath.Dir(devTool), 0755)
c.Assert(err, IsNil)
err = os.WriteFile(devTool, []byte(""), 0644)
c.Assert(err, IsNil)
devTool := filepath.Join(dirs.GlobalRootDir, "/tmp/potato")
makeFakeExe(c, devTool)

path, err := snapdtool.InternalToolPath("non-executable-potato")
c.Check(err, IsNil)
c.Check(path, Equals, filepath.Join(dirs.DistroLibExecDir, "non-executable-potato"))
}

func (s *toolSuite) TestInternalToolPathSnapdPathReexec(c *C) {
s.fakeInternalTool(c, filepath.Join(dirs.SnapMountDir, "core/111"), "snapd")
restore := snapdtool.MockOsReadlink(func(string) (string, error) {
return filepath.Join(dirs.SnapMountDir, "core/111/usr/bin/snap"), nil
})
Expand All @@ -313,15 +327,36 @@ func (s *toolSuite) TestInternalToolPathSnapdPathReexec(c *C) {
}

func (s *toolSuite) TestInternalToolPathSnapdSnap(c *C) {
s.fakeInternalTool(c, filepath.Join(dirs.SnapMountDir, "snapd/22"), "snapd")
restore := snapdtool.MockOsReadlink(func(string) (string, error) {
return filepath.Join(dirs.SnapMountDir, "snapd/22/usr/bin/snap"), nil
})
defer restore()

p, err := snapdtool.InternalToolPath("snapd")
c.Assert(err, IsNil)
c.Check(p, Equals, filepath.Join(dirs.SnapMountDir, "/snapd/22/usr/lib/snapd/snapd"))
}

func (s *toolSuite) TestInternalToolPathSnapdSnapNotExecutable(c *C) {
snapdMountDir := filepath.Join(dirs.SnapMountDir, "snapd/22")
snapdSnapInternalToolPath := filepath.Join(snapdMountDir, "/usr/lib/snapd/snapd")
s.fakeInternalTool(c, snapdMountDir, "snapd")
restore := snapdtool.MockOsReadlink(func(string) (string, error) {
return snapdSnapInternalToolPath, nil
})
defer restore()

// make snapd *not* executable
c.Assert(os.Chmod(snapdSnapInternalToolPath, 0644), IsNil)

// Now the internal tool path falls back to the global snapd because
// the internal one is not executable
p, err := snapdtool.InternalToolPath("snapd")
c.Assert(err, IsNil)
c.Check(p, Equals, filepath.Join(dirs.GlobalRootDir, "/usr/lib/snapd/snapd"))
}

func (s *toolSuite) TestInternalToolPathWithLibexecdirLocation(c *C) {
defer dirs.SetRootDir(s.fakeroot)
restore := release.MockReleaseInfo(&release.OS{ID: "fedora"})
Expand Down

0 comments on commit 1f621a9

Please sign in to comment.