From 81dd13c31eede7d39d5aec00097fc9f7071174bb Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 11 Dec 2018 09:09:13 +0100 Subject: [PATCH 1/5] selinux: package to query SELinux status and verify/restore file contexts Adding a new package for working with SELinux. There is basic functionality to query SELinux state, enabled and permissive/enforcing. The code founds out where the selinux filesystem is mounted and uses that path to query the status, rather than hardcoding /sys/fs/selinux. The package has additional helpers to verify whether a path is labeled accroding it its default context and possibly restore the context. Trying to avoid calling to libselinux directly, the command line tools `matchpathcon` and `restorecon` are used. Signed-off-by: Maciej Borzecki --- selinux/export_test.go | 43 +++++++++++ selinux/label.go | 82 +++++++++++++++++++++ selinux/label_test.go | 130 ++++++++++++++++++++++++++++++++++ selinux/selinux.go | 78 ++++++++++++++++++++ selinux/selinux_test.go | 153 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 486 insertions(+) create mode 100644 selinux/export_test.go create mode 100644 selinux/label.go create mode 100644 selinux/label_test.go create mode 100644 selinux/selinux.go create mode 100644 selinux/selinux_test.go diff --git a/selinux/export_test.go b/selinux/export_test.go new file mode 100644 index 00000000000..5b03044bb9e --- /dev/null +++ b/selinux/export_test.go @@ -0,0 +1,43 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux + +import ( + "io/ioutil" + + "gopkg.in/check.v1" +) + +var ( + GetSELinuxMount = getSELinuxMount +) + +func MockMountInfo(c *check.C, text string) (where string, restore func()) { + old := procSelfMountInfo + dir := c.MkDir() + f, err := ioutil.TempFile(dir, "mountinfo") + c.Assert(err, check.IsNil) + err = ioutil.WriteFile(f.Name(), []byte(text), 0644) + c.Assert(err, check.IsNil) + procSelfMountInfo = f.Name() + restore = func() { + procSelfMountInfo = old + } + return procSelfMountInfo, restore +} diff --git a/selinux/label.go b/selinux/label.go new file mode 100644 index 00000000000..22b667b444d --- /dev/null +++ b/selinux/label.go @@ -0,0 +1,82 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux + +import ( + "os" + "os/exec" + "regexp" + + "github.com/snapcore/snapd/osutil" +) + +var ( + // actual matchpathcon -V output: + // /home/guest/snap has context unconfined_u:object_r:user_home_t:s0, should be unconfined_u:object_r:snappy_home_t:s0 + matchIncorrectLabel = regexp.MustCompile("^.* has context .* should be .*\n$") +) + +// Verifypathcon checks whether a given path is labeled according to its default +// SELinux context +func Verifypathcon(aPath string) (bool, error) { + if _, err := os.Stat(aPath); err != nil { + // path that cannot be accessed cannot be verified + return false, err + } + // if matchpathcon is found we may verify SELinux context + matchpathconPath, err := exec.LookPath("matchpathcon") + if err != nil { + return false, err + } + // -V: verify + cmd := exec.Command(matchpathconPath, "-V", aPath) + cmd.Env = append(os.Environ(), "LC_ALL=C") + out, err := cmd.Output() + if err == nil { + // the path was verified + return true, nil + } + exit, _ := osutil.ExitCode(err) + if exit == 1 && matchIncorrectLabel.Match(out) { + return false, nil + } + return false, err +} + +// Restorecon restores the default SELinux context of given path +func Restorecon(aPath string, recursive bool) error { + if _, err := os.Stat(aPath); err != nil { + // path that cannot be accessed cannot be restored + return err + } + // if restorecon is found we may restore SELinux context + restoreconPath, err := exec.LookPath("restorecon") + if err != nil { + return err + } + + args := make([]string, 0, 2) + if recursive { + // -R: recursive + args = append(args, "-R") + } + args = append(args, aPath) + + return exec.Command(restoreconPath, args...).Run() +} diff --git a/selinux/label_test.go b/selinux/label_test.go new file mode 100644 index 00000000000..855f54d2bb9 --- /dev/null +++ b/selinux/label_test.go @@ -0,0 +1,130 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux_test + +import ( + "fmt" + "io/ioutil" + "path/filepath" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/selinux" + "github.com/snapcore/snapd/testutil" +) + +type labelSuite struct { + path string +} + +var _ = check.Suite(&labelSuite{}) + +func (l *labelSuite) SetUpTest(c *check.C) { + l.path = filepath.Join(c.MkDir(), "foo") + ioutil.WriteFile(l.path, []byte("foo"), 0644) +} + +func (l *labelSuite) TestVerifyHappyOk(c *check.C) { + cmd := testutil.MockCommand(c, "matchpathcon", "") + defer cmd.Restore() + + ok, err := selinux.Verifypathcon(l.path) + c.Assert(err, check.IsNil) + c.Assert(ok, check.Equals, true) + c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ + {"matchpathcon", "-V", l.path}, + }) +} + +func (l *labelSuite) TestVerifyFailGibberish(c *check.C) { + cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 1 ") + defer cmd.Restore() + + ok, err := selinux.Verifypathcon(l.path) + c.Assert(err, check.ErrorMatches, "exit status 1") + c.Assert(ok, check.Equals, false) +} + +func (l *labelSuite) TestVerifyFailStatus(c *check.C) { + cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 5 ") + defer cmd.Restore() + + ok, err := selinux.Verifypathcon(l.path) + c.Assert(err, check.ErrorMatches, "exit status 5") + c.Assert(ok, check.Equals, false) +} + +func (l *labelSuite) TestVerifyFailNoPath(c *check.C) { + cmd := testutil.MockCommand(c, "matchpathcon", ``) + defer cmd.Restore() + + ok, err := selinux.Verifypathcon("does-not-exist") + c.Assert(err, check.ErrorMatches, ".* does-not-exist: no such file or directory") + c.Assert(ok, check.Equals, false) +} + +func (l *labelSuite) TestVerifyFailNoTool(c *check.C) { + ok, err := selinux.Verifypathcon(l.path) + c.Assert(err, check.ErrorMatches, `exec: "matchpathcon": executable file not found in \$PATH`) + c.Assert(ok, check.Equals, false) +} + +func (l *labelSuite) TestVerifyHappyMismatch(c *check.C) { + cmd := testutil.MockCommand(c, "matchpathcon", fmt.Sprintf(` +echo %s has context unconfined_u:object_r:user_home_t:s0, should be unconfined_u:object_r:snappy_home_t:s0 +exit 1`, l.path)) + defer cmd.Restore() + + ok, err := selinux.Verifypathcon(l.path) + c.Assert(err, check.IsNil) + c.Assert(ok, check.Equals, false) +} + +func (l *labelSuite) TestRestoreHappy(c *check.C) { + cmd := testutil.MockCommand(c, "restorecon", "") + defer cmd.Restore() + + err := selinux.Restorecon(l.path, false) + c.Assert(err, check.IsNil) + c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ + {"restorecon", l.path}, + }) + + cmd.ForgetCalls() + + err = selinux.Restorecon(l.path, true) + c.Assert(err, check.IsNil) + c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ + {"restorecon", "-R", l.path}, + }) +} + +func (l *labelSuite) TestRestoreFail(c *check.C) { + err := selinux.Restorecon(l.path, false) + c.Assert(err, check.ErrorMatches, `exec: "restorecon": executable file not found in \$PATH`) + + cmd := testutil.MockCommand(c, "restorecon", "exit 1") + defer cmd.Restore() + + err = selinux.Restorecon(l.path, false) + c.Assert(err, check.ErrorMatches, "exit status 1") + + err = selinux.Restorecon("does-not-exist", false) + c.Assert(err, check.ErrorMatches, ".* does-not-exist: no such file or directory") +} diff --git a/selinux/selinux.go b/selinux/selinux.go new file mode 100644 index 00000000000..729a3c497e2 --- /dev/null +++ b/selinux/selinux.go @@ -0,0 +1,78 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux + +import ( + "bytes" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/snapcore/snapd/osutil" +) + +var ( + procSelfMountInfo = osutil.ProcSelfMountInfo +) + +// IsEnabled checks whether SELinux is enabled +func IsEnabled() (bool, error) { + mnt, err := getSELinuxMount() + if err != nil { + return false, fmt.Errorf("failed to obtain SELinux mount path: %v", err) + } + return mnt != "", nil +} + +// IsEnabled checks whether SELinux is in enforcing mode +func IsEnforcing() (bool, error) { + mnt, err := getSELinuxMount() + if err != nil { + return false, fmt.Errorf("failed to obtain SELinux mount path: %v", err) + } + if mnt == "" { + // not enabled + return false, nil + } + + rawState, err := ioutil.ReadFile(filepath.Join(mnt, "enforce")) + if err != nil { + return false, err + } + switch { + case bytes.Equal(rawState, []byte("0")): + return false, nil + case bytes.Equal(rawState, []byte("1")): + return true, nil + } + return false, fmt.Errorf("unknown SELinux status: %s", rawState) +} + +func getSELinuxMount() (string, error) { + mountinfo, err := osutil.LoadMountInfo(procSelfMountInfo) + if err != nil { + return "", err + } + for _, entry := range mountinfo { + if entry.FsType == "selinuxfs" { + return entry.MountDir, nil + } + } + return "", nil +} diff --git a/selinux/selinux_test.go b/selinux/selinux_test.go new file mode 100644 index 00000000000..cb0ca067ad2 --- /dev/null +++ b/selinux/selinux_test.go @@ -0,0 +1,153 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "gopkg.in/check.v1" + + "github.com/snapcore/snapd/selinux" +) + +func Test(t *testing.T) { check.TestingT(t) } + +type selinuxSuite struct{} + +var _ = check.Suite(&selinuxSuite{}) + +const selinuxMountInfo = `90 0 252:1 / / rw,relatime shared:1 - ext4 /dev/vda1 rw,seclabel +41 19 0:18 / /sys/fs/selinux rw,relatime shared:20 - selinuxfs selinuxfs rw +42 21 0:17 / /dev/mqueue rw,relatime shared:26 - mqueue mqueue rw,seclabel +` + +func (s *selinuxSuite) TestGetMount(c *check.C) { + _, restore := selinux.MockMountInfo(c, selinuxMountInfo) + defer restore() + + mnt, err := selinux.GetSELinuxMount() + c.Assert(err, check.IsNil) + c.Assert(mnt, check.Equals, "/sys/fs/selinux") +} + +func (s *selinuxSuite) TestIsEnabledHappyEnabled(c *check.C) { + _, restore := selinux.MockMountInfo(c, selinuxMountInfo) + defer restore() + + enabled, err := selinux.IsEnabled() + c.Assert(err, check.IsNil) + c.Assert(enabled, check.Equals, true) +} + +func (s *selinuxSuite) TestIsEnabledHappyNoSelinux(c *check.C) { + _, restore := selinux.MockMountInfo(c, ``) + defer restore() + + enabled, err := selinux.IsEnabled() + c.Assert(err, check.IsNil) + c.Assert(enabled, check.Equals, false) +} + +func (s *selinuxSuite) TestIsEnabledFailMountInfo(c *check.C) { + mi, restore := selinux.MockMountInfo(c, ``) + defer restore() + err := os.Chmod(mi, 0000) + c.Assert(err, check.IsNil) + + enabled, err := selinux.IsEnabled() + c.Assert(err, check.ErrorMatches, `failed to obtain SELinux mount path: .*`) + c.Assert(enabled, check.Equals, false) +} + +func (s *selinuxSuite) TestIsEnabledFailGarbage(c *check.C) { + _, restore := selinux.MockMountInfo(c, `garbage`) + defer restore() + + enabled, err := selinux.IsEnabled() + c.Assert(err, check.ErrorMatches, `failed to obtain SELinux mount path: .*`) + c.Assert(enabled, check.Equals, false) +} + +func (s *selinuxSuite) TestIsEnforcingHappy(c *check.C) { + dir := c.MkDir() + miLine := fmt.Sprintf("41 19 0:18 / %s rw,relatime shared:20 - selinuxfs selinuxfs rw\n", dir) + _, restore := selinux.MockMountInfo(c, miLine) + defer restore() + + enforcePath := filepath.Join(dir, "enforce") + + err := ioutil.WriteFile(enforcePath, []byte("1"), 0644) + c.Assert(err, check.IsNil) + + enforcing, err := selinux.IsEnforcing() + c.Assert(err, check.IsNil) + c.Assert(enforcing, check.Equals, true) + + err = ioutil.WriteFile(enforcePath, []byte("0"), 0644) + c.Assert(err, check.IsNil) + + enforcing, err = selinux.IsEnforcing() + c.Assert(err, check.IsNil) + c.Assert(enforcing, check.Equals, false) +} + +func (s *selinuxSuite) TestIsEnforcingNoSELinux(c *check.C) { + _, restore := selinux.MockMountInfo(c, ``) + defer restore() + + enforcing, err := selinux.IsEnforcing() + c.Assert(err, check.IsNil) + c.Assert(enforcing, check.Equals, false) +} + +func (s *selinuxSuite) TestIsEnforcingFailGarbage(c *check.C) { + dir := c.MkDir() + miLine := fmt.Sprintf("41 19 0:18 / %s rw,relatime shared:20 - selinuxfs selinuxfs rw\n", dir) + _, restore := selinux.MockMountInfo(c, miLine) + defer restore() + + enforcePath := filepath.Join(dir, "enforce") + + err := ioutil.WriteFile(enforcePath, []byte("garbage"), 0644) + c.Assert(err, check.IsNil) + + enforcing, err := selinux.IsEnforcing() + c.Assert(err, check.ErrorMatches, "unknown SELinux status: garbage") + c.Assert(enforcing, check.Equals, false) +} + +func (s *selinuxSuite) TestIsEnforcingFailOther(c *check.C) { + dir := c.MkDir() + miLine := fmt.Sprintf("41 19 0:18 / %s rw,relatime shared:20 - selinuxfs selinuxfs rw\n", dir) + _, restore := selinux.MockMountInfo(c, miLine) + defer restore() + + enforcePath := filepath.Join(dir, "enforce") + + err := ioutil.WriteFile(enforcePath, []byte("not-readable"), 0000) + c.Assert(err, check.IsNil) + + enforcing, err := selinux.IsEnforcing() + c.Assert(err, check.ErrorMatches, "open .*: permission denied") + c.Assert(enforcing, check.Equals, false) +} From e29faafb4c5b8919a97a068d52e1e6730fe92c13 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 11 Dec 2018 13:21:14 +0100 Subject: [PATCH 2/5] selinux: tag implementation files are linux specific, add stubs for darwin Signed-off-by: Maciej Borzecki --- selinux/label_darwin.go | 30 +++++++++++++++++++ selinux/{label.go => label_linux.go} | 0 .../{label_test.go => label_linux_test.go} | 0 selinux/selinux_darwin.go | 29 ++++++++++++++++++ selinux/{selinux.go => selinux_linux.go} | 0 ...{selinux_test.go => selinux_linux_test.go} | 0 6 files changed, 59 insertions(+) create mode 100644 selinux/label_darwin.go rename selinux/{label.go => label_linux.go} (100%) rename selinux/{label_test.go => label_linux_test.go} (100%) create mode 100644 selinux/selinux_darwin.go rename selinux/{selinux.go => selinux_linux.go} (100%) rename selinux/{selinux_test.go => selinux_linux_test.go} (100%) diff --git a/selinux/label_darwin.go b/selinux/label_darwin.go new file mode 100644 index 00000000000..764bd8a5eb3 --- /dev/null +++ b/selinux/label_darwin.go @@ -0,0 +1,30 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux + +// Verifypathcon checks whether a given path is labeled according to its default +// SELinux context +func Verifypathcon(aPath string) (bool, error) { + return true, nil +} + +// Restorecon restores the default SELinux context of given path +func Restorecon(aPath string, recursive bool) error { + return nil +} diff --git a/selinux/label.go b/selinux/label_linux.go similarity index 100% rename from selinux/label.go rename to selinux/label_linux.go diff --git a/selinux/label_test.go b/selinux/label_linux_test.go similarity index 100% rename from selinux/label_test.go rename to selinux/label_linux_test.go diff --git a/selinux/selinux_darwin.go b/selinux/selinux_darwin.go new file mode 100644 index 00000000000..15f63f2e799 --- /dev/null +++ b/selinux/selinux_darwin.go @@ -0,0 +1,29 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux + +// IsEnabled checks whether SELinux is enabled +func IsEnabled() (bool, error) { + return false, nil +} + +// IsEnabled checks whether SELinux is in enforcing mode +func IsEnforcing() (bool, error) { + return false, nil +} diff --git a/selinux/selinux.go b/selinux/selinux_linux.go similarity index 100% rename from selinux/selinux.go rename to selinux/selinux_linux.go diff --git a/selinux/selinux_test.go b/selinux/selinux_linux_test.go similarity index 100% rename from selinux/selinux_test.go rename to selinux/selinux_linux_test.go From 2bcbe940a43346fa27ee4c277d4923bbc9537bba Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 12 Dec 2018 11:48:08 +0100 Subject: [PATCH 3/5] selinux: renames and tweaks Renames suggested during the review (thanks @zyga!) Extend the comment on matchpathcon. Signed-off-by: Maciej Borzecki --- selinux/label_darwin.go | 8 ++++---- selinux/label_linux.go | 31 ++++++++++++++----------------- selinux/label_linux_test.go | 22 +++++++++++----------- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/selinux/label_darwin.go b/selinux/label_darwin.go index 764bd8a5eb3..f4a320ab5bf 100644 --- a/selinux/label_darwin.go +++ b/selinux/label_darwin.go @@ -18,13 +18,13 @@ */ package selinux -// Verifypathcon checks whether a given path is labeled according to its default +// VerifyPathContext checks whether a given path is labeled according to its default // SELinux context -func Verifypathcon(aPath string) (bool, error) { +func VerifyPathContext(aPath string) (bool, error) { return true, nil } -// Restorecon restores the default SELinux context of given path -func Restorecon(aPath string, recursive bool) error { +// RestoreContext restores the default SELinux context of given path +func RestoreContext(aPath string, recursive bool) error { return nil } diff --git a/selinux/label_linux.go b/selinux/label_linux.go index 22b667b444d..b1aaab96469 100644 --- a/selinux/label_linux.go +++ b/selinux/label_linux.go @@ -32,20 +32,16 @@ var ( matchIncorrectLabel = regexp.MustCompile("^.* has context .* should be .*\n$") ) -// Verifypathcon checks whether a given path is labeled according to its default +// VerifyPathContext checks whether a given path is labeled according to its default // SELinux context -func Verifypathcon(aPath string) (bool, error) { +func VerifyPathContext(aPath string) (bool, error) { if _, err := os.Stat(aPath); err != nil { // path that cannot be accessed cannot be verified return false, err } - // if matchpathcon is found we may verify SELinux context - matchpathconPath, err := exec.LookPath("matchpathcon") - if err != nil { - return false, err - } - // -V: verify - cmd := exec.Command(matchpathconPath, "-V", aPath) + // matchpathcon -V verifies whether the context of a path matches the + // default + cmd := exec.Command("matchpathcon", "-V", aPath) cmd.Env = append(os.Environ(), "LC_ALL=C") out, err := cmd.Output() if err == nil { @@ -53,23 +49,24 @@ func Verifypathcon(aPath string) (bool, error) { return true, nil } exit, _ := osutil.ExitCode(err) + // exits with 1 when the verification failed or other error occurred, + // when verification failed a message like this will be printed to + // stdout: + // has context , should be + // match the message so that we can distinguish a failed verification + // case from other errors if exit == 1 && matchIncorrectLabel.Match(out) { return false, nil } return false, err } -// Restorecon restores the default SELinux context of given path -func Restorecon(aPath string, recursive bool) error { +// RestoreContext restores the default SELinux context of given path +func RestoreContext(aPath string, recursive bool) error { if _, err := os.Stat(aPath); err != nil { // path that cannot be accessed cannot be restored return err } - // if restorecon is found we may restore SELinux context - restoreconPath, err := exec.LookPath("restorecon") - if err != nil { - return err - } args := make([]string, 0, 2) if recursive { @@ -78,5 +75,5 @@ func Restorecon(aPath string, recursive bool) error { } args = append(args, aPath) - return exec.Command(restoreconPath, args...).Run() + return exec.Command("restorecon", args...).Run() } diff --git a/selinux/label_linux_test.go b/selinux/label_linux_test.go index 855f54d2bb9..ef5972b4bbf 100644 --- a/selinux/label_linux_test.go +++ b/selinux/label_linux_test.go @@ -44,7 +44,7 @@ func (l *labelSuite) TestVerifyHappyOk(c *check.C) { cmd := testutil.MockCommand(c, "matchpathcon", "") defer cmd.Restore() - ok, err := selinux.Verifypathcon(l.path) + ok, err := selinux.VerifyPathContext(l.path) c.Assert(err, check.IsNil) c.Assert(ok, check.Equals, true) c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ @@ -56,7 +56,7 @@ func (l *labelSuite) TestVerifyFailGibberish(c *check.C) { cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 1 ") defer cmd.Restore() - ok, err := selinux.Verifypathcon(l.path) + ok, err := selinux.VerifyPathContext(l.path) c.Assert(err, check.ErrorMatches, "exit status 1") c.Assert(ok, check.Equals, false) } @@ -65,7 +65,7 @@ func (l *labelSuite) TestVerifyFailStatus(c *check.C) { cmd := testutil.MockCommand(c, "matchpathcon", "echo gibberish; exit 5 ") defer cmd.Restore() - ok, err := selinux.Verifypathcon(l.path) + ok, err := selinux.VerifyPathContext(l.path) c.Assert(err, check.ErrorMatches, "exit status 5") c.Assert(ok, check.Equals, false) } @@ -74,13 +74,13 @@ func (l *labelSuite) TestVerifyFailNoPath(c *check.C) { cmd := testutil.MockCommand(c, "matchpathcon", ``) defer cmd.Restore() - ok, err := selinux.Verifypathcon("does-not-exist") + ok, err := selinux.VerifyPathContext("does-not-exist") c.Assert(err, check.ErrorMatches, ".* does-not-exist: no such file or directory") c.Assert(ok, check.Equals, false) } func (l *labelSuite) TestVerifyFailNoTool(c *check.C) { - ok, err := selinux.Verifypathcon(l.path) + ok, err := selinux.VerifyPathContext(l.path) c.Assert(err, check.ErrorMatches, `exec: "matchpathcon": executable file not found in \$PATH`) c.Assert(ok, check.Equals, false) } @@ -91,7 +91,7 @@ echo %s has context unconfined_u:object_r:user_home_t:s0, should be unconfined_u exit 1`, l.path)) defer cmd.Restore() - ok, err := selinux.Verifypathcon(l.path) + ok, err := selinux.VerifyPathContext(l.path) c.Assert(err, check.IsNil) c.Assert(ok, check.Equals, false) } @@ -100,7 +100,7 @@ func (l *labelSuite) TestRestoreHappy(c *check.C) { cmd := testutil.MockCommand(c, "restorecon", "") defer cmd.Restore() - err := selinux.Restorecon(l.path, false) + err := selinux.RestoreContext(l.path, false) c.Assert(err, check.IsNil) c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ {"restorecon", l.path}, @@ -108,7 +108,7 @@ func (l *labelSuite) TestRestoreHappy(c *check.C) { cmd.ForgetCalls() - err = selinux.Restorecon(l.path, true) + err = selinux.RestoreContext(l.path, true) c.Assert(err, check.IsNil) c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ {"restorecon", "-R", l.path}, @@ -116,15 +116,15 @@ func (l *labelSuite) TestRestoreHappy(c *check.C) { } func (l *labelSuite) TestRestoreFail(c *check.C) { - err := selinux.Restorecon(l.path, false) + err := selinux.RestoreContext(l.path, false) c.Assert(err, check.ErrorMatches, `exec: "restorecon": executable file not found in \$PATH`) cmd := testutil.MockCommand(c, "restorecon", "exit 1") defer cmd.Restore() - err = selinux.Restorecon(l.path, false) + err = selinux.RestoreContext(l.path, false) c.Assert(err, check.ErrorMatches, "exit status 1") - err = selinux.Restorecon("does-not-exist", false) + err = selinux.RestoreContext("does-not-exist", false) c.Assert(err, check.ErrorMatches, ".* does-not-exist: no such file or directory") } From 08632d8fce1e370d1b8f645b1568ba9a7a5b4ca7 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 12 Dec 2018 15:52:34 +0100 Subject: [PATCH 4/5] selinux: skip relevant tests when the tools are present Signed-off-by: Maciej Borzecki --- selinux/label_linux_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/selinux/label_linux_test.go b/selinux/label_linux_test.go index ef5972b4bbf..09f51f27105 100644 --- a/selinux/label_linux_test.go +++ b/selinux/label_linux_test.go @@ -21,6 +21,7 @@ package selinux_test import ( "fmt" "io/ioutil" + "os/exec" "path/filepath" "gopkg.in/check.v1" @@ -80,6 +81,9 @@ func (l *labelSuite) TestVerifyFailNoPath(c *check.C) { } func (l *labelSuite) TestVerifyFailNoTool(c *check.C) { + if _, err := exec.LookPath("matchpathcon"); err == nil { + c.Skip("matchpathcon found in $PATH") + } ok, err := selinux.VerifyPathContext(l.path) c.Assert(err, check.ErrorMatches, `exec: "matchpathcon": executable file not found in \$PATH`) c.Assert(ok, check.Equals, false) @@ -115,14 +119,19 @@ func (l *labelSuite) TestRestoreHappy(c *check.C) { }) } -func (l *labelSuite) TestRestoreFail(c *check.C) { +func (l *labelSuite) TestRestoreFailNoTool(c *check.C) { + if _, err := exec.LookPath("matchpathcon"); err == nil { + c.Skip("matchpathcon found in $PATH") + } err := selinux.RestoreContext(l.path, false) c.Assert(err, check.ErrorMatches, `exec: "restorecon": executable file not found in \$PATH`) +} +func (l *labelSuite) TestRestoreFail(c *check.C) { cmd := testutil.MockCommand(c, "restorecon", "exit 1") defer cmd.Restore() - err = selinux.RestoreContext(l.path, false) + err := selinux.RestoreContext(l.path, false) c.Assert(err, check.ErrorMatches, "exit status 1") err = selinux.RestoreContext("does-not-exist", false) From 8cd8b253e50d1807251c6bdef722601568b10f14 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Wed, 12 Dec 2018 16:17:31 +0100 Subject: [PATCH 5/5] selinux: drop naked bool in RestoreContext Replace a naked bool passed to RestoreContext with a struct to make the code easier to read/follow. Signed-off-by: Maciej Borzecki --- selinux/label.go | 26 ++++++++++++++++++++++++++ selinux/label_darwin.go | 2 +- selinux/label_linux.go | 4 ++-- selinux/label_linux_test.go | 10 +++++----- 4 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 selinux/label.go diff --git a/selinux/label.go b/selinux/label.go new file mode 100644 index 00000000000..32b28cd6865 --- /dev/null +++ b/selinux/label.go @@ -0,0 +1,26 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 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 . + * + */ +package selinux + +// RestoreMode configures how default path context is restored +type RestoreMode struct { + // Recursive indicates whether the default context shall be restored + // recursively + Recursive bool +} diff --git a/selinux/label_darwin.go b/selinux/label_darwin.go index f4a320ab5bf..14ab93a9db5 100644 --- a/selinux/label_darwin.go +++ b/selinux/label_darwin.go @@ -25,6 +25,6 @@ func VerifyPathContext(aPath string) (bool, error) { } // RestoreContext restores the default SELinux context of given path -func RestoreContext(aPath string, recursive bool) error { +func RestoreContext(aPath string, mode RestoreMode) error { return nil } diff --git a/selinux/label_linux.go b/selinux/label_linux.go index b1aaab96469..62ab718e586 100644 --- a/selinux/label_linux.go +++ b/selinux/label_linux.go @@ -62,14 +62,14 @@ func VerifyPathContext(aPath string) (bool, error) { } // RestoreContext restores the default SELinux context of given path -func RestoreContext(aPath string, recursive bool) error { +func RestoreContext(aPath string, mode RestoreMode) error { if _, err := os.Stat(aPath); err != nil { // path that cannot be accessed cannot be restored return err } args := make([]string, 0, 2) - if recursive { + if mode.Recursive { // -R: recursive args = append(args, "-R") } diff --git a/selinux/label_linux_test.go b/selinux/label_linux_test.go index 09f51f27105..fc0838e85b4 100644 --- a/selinux/label_linux_test.go +++ b/selinux/label_linux_test.go @@ -104,7 +104,7 @@ func (l *labelSuite) TestRestoreHappy(c *check.C) { cmd := testutil.MockCommand(c, "restorecon", "") defer cmd.Restore() - err := selinux.RestoreContext(l.path, false) + err := selinux.RestoreContext(l.path, selinux.RestoreMode{}) c.Assert(err, check.IsNil) c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ {"restorecon", l.path}, @@ -112,7 +112,7 @@ func (l *labelSuite) TestRestoreHappy(c *check.C) { cmd.ForgetCalls() - err = selinux.RestoreContext(l.path, true) + err = selinux.RestoreContext(l.path, selinux.RestoreMode{Recursive: true}) c.Assert(err, check.IsNil) c.Assert(cmd.Calls(), check.DeepEquals, [][]string{ {"restorecon", "-R", l.path}, @@ -123,7 +123,7 @@ func (l *labelSuite) TestRestoreFailNoTool(c *check.C) { if _, err := exec.LookPath("matchpathcon"); err == nil { c.Skip("matchpathcon found in $PATH") } - err := selinux.RestoreContext(l.path, false) + err := selinux.RestoreContext(l.path, selinux.RestoreMode{}) c.Assert(err, check.ErrorMatches, `exec: "restorecon": executable file not found in \$PATH`) } @@ -131,9 +131,9 @@ func (l *labelSuite) TestRestoreFail(c *check.C) { cmd := testutil.MockCommand(c, "restorecon", "exit 1") defer cmd.Restore() - err := selinux.RestoreContext(l.path, false) + err := selinux.RestoreContext(l.path, selinux.RestoreMode{}) c.Assert(err, check.ErrorMatches, "exit status 1") - err = selinux.RestoreContext("does-not-exist", false) + err = selinux.RestoreContext("does-not-exist", selinux.RestoreMode{}) c.Assert(err, check.ErrorMatches, ".* does-not-exist: no such file or directory") }