Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: support probing SELinux state #6286

Merged
merged 10 commits into from Jan 9, 2019
13 changes: 6 additions & 7 deletions osutil/exec.go
Expand Up @@ -35,12 +35,11 @@ import (

"gopkg.in/tomb.v2"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/strutil"
)

func parseCoreLdSoConf(confPath string) []string {
root := filepath.Join(dirs.SnapMountDir, "/core/current")
func parseCoreLdSoConf(snapMountDir string, confPath string) []string {
root := filepath.Join(snapMountDir, "/core/current")

f, err := os.Open(filepath.Join(root, confPath))
if err != nil {
Expand All @@ -64,7 +63,7 @@ func parseCoreLdSoConf(confPath string) []string {
return nil
}
for _, f := range files {
out = append(out, parseCoreLdSoConf(f[len(root):])...)
out = append(out, parseCoreLdSoConf(snapMountDir, f[len(root):])...)
}
default:
out = append(out, filepath.Join(root, line))
Expand Down Expand Up @@ -105,8 +104,8 @@ func elfInterp(cmd string) (string, error) {
//
// At the moment it can only run ELF files, expects a standard ld.so
// interpreter, and can't handle RPATH.
func CommandFromCore(name string, cmdArgs ...string) (*exec.Cmd, error) {
root := filepath.Join(dirs.SnapMountDir, "/core/current")
func CommandFromCore(snapMountDir string, name string, cmdArgs ...string) (*exec.Cmd, error) {
root := filepath.Join(snapMountDir, "/core/current")

cmdPath := filepath.Join(root, name)
interp, err := elfInterp(cmdPath)
Expand Down Expand Up @@ -134,7 +133,7 @@ func CommandFromCore(name string, cmdArgs ...string) (*exec.Cmd, error) {
seen[coreLdSo] = true
}

ldLibraryPathForCore := parseCoreLdSoConf("/etc/ld.so.conf")
ldLibraryPathForCore := parseCoreLdSoConf(snapMountDir, "/etc/ld.so.conf")

ldSoArgs := []string{"--library-path", strings.Join(ldLibraryPathForCore, ":"), cmdPath}
allArgs := append(ldSoArgs, cmdArgs...)
Expand Down
4 changes: 2 additions & 2 deletions osutil/exec_test.go
Expand Up @@ -77,7 +77,7 @@ func (s *execSuite) TestCommandFromCore(c *C) {

os.MkdirAll(filepath.Join(root, "/usr/bin"), 0755)
osutil.CopyFile(truePath, filepath.Join(root, "/usr/bin/xdelta3"), 0)
cmd, err := osutil.CommandFromCore("/usr/bin/xdelta3", "--some-xdelta-arg")
cmd, err := osutil.CommandFromCore(dirs.SnapMountDir, "/usr/bin/xdelta3", "--some-xdelta-arg")
c.Assert(err, IsNil)

out, err := exec.Command("/bin/sh", "-c", fmt.Sprintf("readelf -l %s |grep interpreter:|cut -f2 -d:|cut -f1 -d]", truePath)).Output()
Expand Down Expand Up @@ -108,7 +108,7 @@ func (s *execSuite) TestCommandFromCoreSymlinkCycle(c *C) {
c.Assert(os.MkdirAll(filepath.Dir(coreInterp), 0755), IsNil)
c.Assert(os.Symlink(filepath.Base(coreInterp), coreInterp), IsNil)

_, err = osutil.CommandFromCore("/usr/bin/xdelta3", "--some-xdelta-arg")
_, err = osutil.CommandFromCore(dirs.SnapMountDir, "/usr/bin/xdelta3", "--some-xdelta-arg")
c.Assert(err, ErrorMatches, "cannot run command from core: symlink cycle found")

}
Expand Down
3 changes: 2 additions & 1 deletion overlord/snapstate/backend/fontconfig.go
Expand Up @@ -22,6 +22,7 @@ package backend
import (
"fmt"

"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/osutil"
)

Expand All @@ -31,7 +32,7 @@ var updateFontconfigCaches = updateFontconfigCachesImpl
func updateFontconfigCachesImpl() error {
for _, fc := range []string{"fc-cache-v6", "fc-cache-v7"} {
// FIXME: also use the snapd snap if available
cmd, err := osutil.CommandFromCore("/bin/" + fc)
cmd, err := osutil.CommandFromCore(dirs.SnapMountDir, "/bin/"+fc)
if err != nil {
return fmt.Errorf("cannot get %s from core: %v", fc, err)
}
Expand Down
18 changes: 18 additions & 0 deletions release/export_test.go
Expand Up @@ -58,6 +58,22 @@ func MockIoutilReadfile(newReadfile func(string) ([]byte, error)) (restorer func
}
}

func MockSELinuxIsEnabled(isEnabled func() (bool, error)) (restore func()) {
old := selinuxIsEnabled
selinuxIsEnabled = isEnabled
return func() {
selinuxIsEnabled = old
}
}

func MockSELinuxIsEnforcing(isEnforcing func() (bool, error)) (restore func()) {
old := selinuxIsEnforcing
selinuxIsEnforcing = isEnforcing
return func() {
selinuxIsEnforcing = old
}
}

// CurrentAppArmorLevel returns the internal cached apparmor level.
func CurrentAppArmorLevel() AppArmorLevelType {
return appArmorLevel
Expand All @@ -84,4 +100,6 @@ var (
PreferredAppArmorParserFeatures = preferredAppArmorParserFeatures

IsWSL = isWSL

ProbeSELinux = probeSELinux
)
80 changes: 80 additions & 0 deletions release/selinux.go
@@ -0,0 +1,80 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2014-2018 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 release

import (
"fmt"

"github.com/snapcore/snapd/selinux"
)

// SELinuxLevelType encodes the state of SELinux support found on this system.
type SELinuxLevelType int

const (
// NoSELinux indicates that SELinux is not enabled
NoSELinux SELinuxLevelType = iota
// SELinux is supported and in permissive mode
SELinuxPermissive
// SELinux is supported and in enforcing mode
SELinuxEnforcing
)

var (
selinuxIsEnabled = selinux.IsEnabled
selinuxIsEnforcing = selinux.IsEnforcing
)

// SELinuxLevel tells what level of SELinux enforcement is currently used
func SELinuxLevel() SELinuxLevelType {
level, _ := probeSELinux()
return level
}

// SELinuxSummary describes SELinux status
func SELinuxSummary() string {
_, summary := probeSELinux()
return summary
}

// SELinuxStatus returns the current level of SELinux support and a descriptive
// summary
func SELinuxStatus() (level SELinuxLevelType, summary string) {
return probeSELinux()
}

func probeSELinux() (SELinuxLevelType, string) {
enabled, err := selinuxIsEnabled()
if err != nil {
return NoSELinux, err.Error()
}
if !enabled {
return NoSELinux, ""
}

enforcing, err := selinuxIsEnforcing()
if err != nil {
return NoSELinux, fmt.Sprintf("SELinux is enabled, but status cannot be determined: %v", err)
}
if !enforcing {
return SELinuxPermissive, "SELinux is enabled and in permissive mode"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a small nitpick about wording in this case:

Suggested change
return SELinuxPermissive, "SELinux is enabled and in permissive mode"
return SELinuxPermissive, "SELinux is enabled but in permissive mode"

}
return SELinuxEnforcing, "SELinux is enabled and in enforcing mode"
}
98 changes: 98 additions & 0 deletions release/selinux_test.go
@@ -0,0 +1,98 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2014-2018 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 release_test

import (
"errors"

. "gopkg.in/check.v1"

"github.com/snapcore/snapd/release"
)

type selinuxSuite struct{}

var _ = Suite(&selinuxSuite{})

func (s *selinuxSuite) TestProbeNone(c *C) {
restore := release.MockSELinuxIsEnabled(func() (bool, error) { return false, nil })
defer restore()

level, status := release.ProbeSELinux()
c.Assert(level, Equals, release.NoSELinux)
c.Assert(status, Equals, "")

c.Assert(release.SELinuxLevel(), Equals, level)
c.Assert(release.SELinuxSummary(), Equals, status)
}

func (s *selinuxSuite) TestProbeEnforcingHappy(c *C) {
restore := release.MockSELinuxIsEnabled(func() (bool, error) { return true, nil })
defer restore()
restore = release.MockSELinuxIsEnforcing(func() (bool, error) { return true, nil })
defer restore()

level, status := release.ProbeSELinux()
c.Assert(level, Equals, release.SELinuxEnforcing)
c.Assert(status, Equals, "SELinux is enabled and in enforcing mode")

c.Assert(release.SELinuxLevel(), Equals, level)
c.Assert(release.SELinuxSummary(), Equals, status)
}

func (s *selinuxSuite) TestProbeEnabledError(c *C) {
restore := release.MockSELinuxIsEnabled(func() (bool, error) { return true, errors.New("so much fail") })
defer restore()

level, status := release.ProbeSELinux()
c.Assert(level, Equals, release.NoSELinux)
c.Assert(status, Equals, "so much fail")

c.Assert(release.SELinuxLevel(), Equals, level)
c.Assert(release.SELinuxSummary(), Equals, status)
}

func (s *selinuxSuite) TestProbeEnforcingError(c *C) {
restore := release.MockSELinuxIsEnabled(func() (bool, error) { return true, nil })
defer restore()
restore = release.MockSELinuxIsEnforcing(func() (bool, error) { return true, errors.New("so much fail") })
defer restore()

level, status := release.ProbeSELinux()
c.Assert(level, Equals, release.NoSELinux)
c.Assert(status, Equals, "SELinux is enabled, but status cannot be determined: so much fail")

c.Assert(release.SELinuxLevel(), Equals, level)
c.Assert(release.SELinuxSummary(), Equals, status)
}

func (s *selinuxSuite) TestProbePermissive(c *C) {
restore := release.MockSELinuxIsEnabled(func() (bool, error) { return true, nil })
defer restore()
restore = release.MockSELinuxIsEnforcing(func() (bool, error) { return false, nil })
defer restore()

level, status := release.ProbeSELinux()
c.Assert(level, Equals, release.SELinuxPermissive)
c.Assert(status, Equals, "SELinux is enabled and in permissive mode")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To match the suggestion above:

Suggested change
c.Assert(status, Equals, "SELinux is enabled and in permissive mode")
c.Assert(status, Equals, "SELinux is enabled but in permissive mode")


c.Assert(release.SELinuxLevel(), Equals, level)
c.Assert(release.SELinuxSummary(), Equals, status)
}
2 changes: 1 addition & 1 deletion snap/squashfs/export_test.go
Expand Up @@ -43,7 +43,7 @@ func MockLink(newLink func(string, string) error) (restore func()) {
}
}

func MockFromCore(newFromCore func(string, ...string) (*exec.Cmd, error)) (restore func()) {
func MockFromCore(newFromCore func(string, string, ...string) (*exec.Cmd, error)) (restore func()) {
oldFromCore := osutilCommandFromCore
osutilCommandFromCore = newFromCore
return func() {
Expand Down
2 changes: 1 addition & 1 deletion snap/squashfs/squashfs.go
Expand Up @@ -305,7 +305,7 @@ func (s *Snap) Build(sourceDir, snapType string, excludeFiles ...string) error {
if err != nil {
return err
}
cmd, err := osutilCommandFromCore("/usr/bin/mksquashfs")
cmd, err := osutilCommandFromCore(dirs.SnapMountDir, "/usr/bin/mksquashfs")
if err != nil {
cmd = exec.Command("mksquashfs")
}
Expand Down
15 changes: 10 additions & 5 deletions snap/squashfs/squashfs_test.go
Expand Up @@ -412,7 +412,8 @@ squashfs-root/random/data.bin
}

func (s *SquashfsTestSuite) TestBuildSupportsMultipleExcludesWithOnlyOneWildcardsFlag(c *C) {
defer squashfs.MockFromCore(func(cmd string, args ...string) (*exec.Cmd, error) {
defer squashfs.MockFromCore(func(mountDir, cmd string, args ...string) (*exec.Cmd, error) {
c.Check(mountDir, Equals, dirs.SnapMountDir)
c.Check(cmd, Equals, "/usr/bin/mksquashfs")
return nil, errors.New("bzzt")
})()
Expand All @@ -435,8 +436,9 @@ func (s *SquashfsTestSuite) TestBuildSupportsMultipleExcludesWithOnlyOneWildcard

func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromCoreIfAvailable(c *C) {
usedFromCore := false
defer squashfs.MockFromCore(func(cmd string, args ...string) (*exec.Cmd, error) {
defer squashfs.MockFromCore(func(mountDir, cmd string, args ...string) (*exec.Cmd, error) {
usedFromCore = true
c.Check(mountDir, Equals, dirs.SnapMountDir)
c.Check(cmd, Equals, "/usr/bin/mksquashfs")
return &exec.Cmd{Path: "/bin/true"}, nil
})()
Expand All @@ -454,8 +456,9 @@ func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromCoreIfAvailable(c *C) {

func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromClassicIfCoreUnavailable(c *C) {
triedFromCore := false
defer squashfs.MockFromCore(func(cmd string, args ...string) (*exec.Cmd, error) {
defer squashfs.MockFromCore(func(mountDir, cmd string, args ...string) (*exec.Cmd, error) {
triedFromCore = true
c.Check(mountDir, Equals, dirs.SnapMountDir)
c.Check(cmd, Equals, "/usr/bin/mksquashfs")
return nil, errors.New("bzzt")
})()
Expand All @@ -473,8 +476,9 @@ func (s *SquashfsTestSuite) TestBuildUsesMksquashfsFromClassicIfCoreUnavailable(

func (s *SquashfsTestSuite) TestBuildFailsIfNoMksquashfs(c *C) {
triedFromCore := false
defer squashfs.MockFromCore(func(cmd string, args ...string) (*exec.Cmd, error) {
defer squashfs.MockFromCore(func(mountDir, cmd string, args ...string) (*exec.Cmd, error) {
triedFromCore = true
c.Check(mountDir, Equals, dirs.SnapMountDir)
c.Check(cmd, Equals, "/usr/bin/mksquashfs")
return nil, errors.New("bzzt")
})()
Expand All @@ -491,7 +495,8 @@ func (s *SquashfsTestSuite) TestBuildFailsIfNoMksquashfs(c *C) {
}

func (s *SquashfsTestSuite) TestBuildVariesArgsByType(c *C) {
defer squashfs.MockFromCore(func(cmd string, args ...string) (*exec.Cmd, error) {
defer squashfs.MockFromCore(func(mountDir, cmd string, args ...string) (*exec.Cmd, error) {
c.Check(mountDir, Equals, dirs.SnapMountDir)
return nil, errors.New("bzzt")
})()
mksq := testutil.MockCommand(c, "mksquashfs", "")
Expand Down
2 changes: 1 addition & 1 deletion store/store.go
Expand Up @@ -1582,7 +1582,7 @@ func getXdelta3Cmd(args ...string) (*exec.Cmd, error) {
case osutil.ExecutableExists("xdelta3"):
return exec.Command("xdelta3", args...), nil
case osutil.FileExists(filepath.Join(dirs.SnapMountDir, "/core/current/usr/bin/xdelta3")):
return osutil.CommandFromCore("/usr/bin/xdelta3", args...)
return osutil.CommandFromCore(dirs.SnapMountDir, "/usr/bin/xdelta3", args...)
}
return nil, fmt.Errorf("cannot find xdelta3 binary in PATH or core snap")
}
Expand Down