Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
selinux: package to query SELinux status and verify/restore file cont…
…exts 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 <maciej.zenon.borzecki@canonical.com>
- Loading branch information
Showing
5 changed files
with
486 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
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 | ||
} |
Oops, something went wrong.