Skip to content

Commit

Permalink
selinux: package to query SELinux status and verify/restore file cont…
Browse files Browse the repository at this point in the history
…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
bboozzoo committed Dec 11, 2018
1 parent df2787d commit 81dd13c
Show file tree
Hide file tree
Showing 5 changed files with 486 additions and 0 deletions.
43 changes: 43 additions & 0 deletions 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 <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
}
82 changes: 82 additions & 0 deletions 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 <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()
}
130 changes: 130 additions & 0 deletions 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 <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")
}
78 changes: 78 additions & 0 deletions 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 <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
}

0 comments on commit 81dd13c

Please sign in to comment.