From 0f9b3b6e3ec2014913844c56807f96f4b3d1ad4b Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 29 Nov 2018 10:14:22 +0100 Subject: [PATCH 1/5] release: detect SELinux Signed-off-by: Maciej Borzecki --- release/selinux.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 release/selinux.go diff --git a/release/selinux.go b/release/selinux.go new file mode 100644 index 00000000000..ad026ac64ff --- /dev/null +++ b/release/selinux.go @@ -0,0 +1,81 @@ +// -*- 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 . + * + */ + +package release + +import ( + "bytes" + "fmt" + "io/ioutil" + "path/filepath" +) + +// 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 ( + selinuxLevel SELinuxLevelType + selinuxSummary string +) + +func init() { + selinuxLevel, selinuxSummary = probeSELinux() +} + +// SELinuxLevel tells what level of SELinux enforcement is currently used +func SELinuxLevel() SELinuxLevelType { + return selinuxLevel +} + +// SELinuxSummary describes SELinux status +func SELinuxSummary() string { + return selinuxSummary +} + +// probe related code +var ( + selinuxSysPath = "sys/fs/selinux" +) + +func probeSELinux() (SELinuxLevelType, string) { + if !isDirectory(selinuxSysPath) { + return NoSELinux, "SELinux not enabled" + } + + rawState, err := ioutil.ReadFile(filepath.Join(selinuxSysPath, "enforce")) + if err != nil { + return NoSELinux, fmt.Sprintf("SELinux status cannot be determined: %v", err) + } + switch { + case bytes.Equal(rawState, []byte("0")): + return SELinuxPermissive, "SELinux is enabled and in permissive mode" + case bytes.Equal(rawState, []byte("1")): + return SELinuxEnforcing, "SELinux is enabled and in enforcing mode" + } + return NoSELinux, fmt.Sprintf("SELinux present but status cannot be determined: %s", rawState) +} From 879bb3f0f323e68a35a9f51a7f54a12c0c9b9510 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 11 Dec 2018 11:49:48 +0100 Subject: [PATCH 2/5] release: refactor selinux support to use selinux package Use the helper package. Signed-off-by: Maciej Borzecki --- release/export_test.go | 18 +++++++++ release/selinux.go | 32 ++++++++-------- release/selinux_test.go | 83 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 17 deletions(-) create mode 100644 release/selinux_test.go diff --git a/release/export_test.go b/release/export_test.go index ce836ed5d36..67434bc3288 100644 --- a/release/export_test.go +++ b/release/export_test.go @@ -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 @@ -84,4 +100,6 @@ var ( PreferredAppArmorParserFeatures = preferredAppArmorParserFeatures IsWSL = isWSL + + ProbeSELinux = probeSELinux ) diff --git a/release/selinux.go b/release/selinux.go index ad026ac64ff..d51e434acba 100644 --- a/release/selinux.go +++ b/release/selinux.go @@ -20,10 +20,9 @@ package release import ( - "bytes" "fmt" - "io/ioutil" - "path/filepath" + + "github.com/snapcore/snapd/selinux" ) // SELinuxLevelType encodes the state of SELinux support found on this system. @@ -41,6 +40,9 @@ const ( var ( selinuxLevel SELinuxLevelType selinuxSummary string + + selinuxIsEnabled = selinux.IsEnabled + selinuxIsEnforcing = selinux.IsEnforcing ) func init() { @@ -57,25 +59,21 @@ func SELinuxSummary() string { return selinuxSummary } -// probe related code -var ( - selinuxSysPath = "sys/fs/selinux" -) - func probeSELinux() (SELinuxLevelType, string) { - if !isDirectory(selinuxSysPath) { - return NoSELinux, "SELinux not enabled" + enabled, err := selinuxIsEnabled() + if err != nil { + return NoSELinux, err.Error() + } + if !enabled { + return NoSELinux, "" } - rawState, err := ioutil.ReadFile(filepath.Join(selinuxSysPath, "enforce")) + enforcing, err := selinuxIsEnforcing() if err != nil { - return NoSELinux, fmt.Sprintf("SELinux status cannot be determined: %v", err) + return NoSELinux, fmt.Sprintf("SELinux is enabled, but status cannot be determined: %v", err) } - switch { - case bytes.Equal(rawState, []byte("0")): + if !enforcing { return SELinuxPermissive, "SELinux is enabled and in permissive mode" - case bytes.Equal(rawState, []byte("1")): - return SELinuxEnforcing, "SELinux is enabled and in enforcing mode" } - return NoSELinux, fmt.Sprintf("SELinux present but status cannot be determined: %s", rawState) + return SELinuxEnforcing, "SELinux is enabled and in enforcing mode" } diff --git a/release/selinux_test.go b/release/selinux_test.go new file mode 100644 index 00000000000..f93800c5aa1 --- /dev/null +++ b/release/selinux_test.go @@ -0,0 +1,83 @@ +// -*- 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 . + * + */ + +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, "") +} + +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") +} + +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") +} + +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") +} + +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") +} From 768214567756efa8a135d51c907eb93fb177b553 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Thu, 13 Dec 2018 15:01:37 +0100 Subject: [PATCH 3/5] release: probe SELinux status on each call In contrast to AppArmor and Seccomp, SELinux state can change at runtime. Specifically, SELinux fs can be (un-)mounted, and the permissive/enforcing state can be switched. Instead of doing a once-only lazy initialization, check the status on each call. The actual cost is incurred only when called. Signed-off-by: Maciej Borzecki --- release/selinux.go | 19 ++++++++++--------- release/selinux_test.go | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/release/selinux.go b/release/selinux.go index d51e434acba..d3aeef76c38 100644 --- a/release/selinux.go +++ b/release/selinux.go @@ -38,25 +38,26 @@ const ( ) var ( - selinuxLevel SELinuxLevelType - selinuxSummary string - selinuxIsEnabled = selinux.IsEnabled selinuxIsEnforcing = selinux.IsEnforcing ) -func init() { - selinuxLevel, selinuxSummary = probeSELinux() -} - // SELinuxLevel tells what level of SELinux enforcement is currently used func SELinuxLevel() SELinuxLevelType { - return selinuxLevel + level, _ := probeSELinux() + return level } // SELinuxSummary describes SELinux status func SELinuxSummary() string { - return selinuxSummary + _, 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) { diff --git a/release/selinux_test.go b/release/selinux_test.go index f93800c5aa1..b6dde144d96 100644 --- a/release/selinux_test.go +++ b/release/selinux_test.go @@ -38,6 +38,9 @@ func (s *selinuxSuite) TestProbeNone(c *C) { 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) { @@ -49,6 +52,9 @@ func (s *selinuxSuite) TestProbeEnforcingHappy(c *C) { 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) { @@ -58,6 +64,9 @@ func (s *selinuxSuite) TestProbeEnabledError(c *C) { 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) { @@ -69,6 +78,9 @@ func (s *selinuxSuite) TestProbeEnforcingError(c *C) { 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) { @@ -80,4 +92,7 @@ func (s *selinuxSuite) TestProbePermissive(c *C) { level, status := release.ProbeSELinux() c.Assert(level, Equals, release.SELinuxPermissive) c.Assert(status, Equals, "SELinux is enabled and in permissive mode") + + c.Assert(release.SELinuxLevel(), Equals, level) + c.Assert(release.SELinuxSummary(), Equals, status) } From b7321e8661749d74267d7f568b83d6c7ace55f8d Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Mon, 7 Jan 2019 09:55:16 +0100 Subject: [PATCH 4/5] release: allow mocking SELinux state from external packages Signed-off-by: Maciej Borzecki --- release/export_test.go | 8 -------- release/selinux.go | 10 ++++++++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/release/export_test.go b/release/export_test.go index b629e2a4c04..1b1e0b07e75 100644 --- a/release/export_test.go +++ b/release/export_test.go @@ -58,14 +58,6 @@ 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 diff --git a/release/selinux.go b/release/selinux.go index d3aeef76c38..d6aa77a278e 100644 --- a/release/selinux.go +++ b/release/selinux.go @@ -78,3 +78,13 @@ func probeSELinux() (SELinuxLevelType, string) { } return SELinuxEnforcing, "SELinux is enabled and in enforcing mode" } + +// MockSELinuxIsEnabled makes the system believe a certain SELinux state is +// currently true +func MockSELinuxIsEnabled(isEnabled func() (bool, error)) (restore func()) { + old := selinuxIsEnabled + selinuxIsEnabled = isEnabled + return func() { + selinuxIsEnabled = old + } +} From 03860f76570bb6ba67c183c9d70509e4ed1bd927 Mon Sep 17 00:00:00 2001 From: Maciej Borzecki Date: Tue, 8 Jan 2019 12:55:20 +0100 Subject: [PATCH 5/5] release: tweak SELinux summary info text Signed-off-by: Maciej Borzecki --- release/selinux.go | 2 +- release/selinux_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/release/selinux.go b/release/selinux.go index d6aa77a278e..01e6a743613 100644 --- a/release/selinux.go +++ b/release/selinux.go @@ -74,7 +74,7 @@ func probeSELinux() (SELinuxLevelType, string) { 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" + return SELinuxPermissive, "SELinux is enabled but in permissive mode" } return SELinuxEnforcing, "SELinux is enabled and in enforcing mode" } diff --git a/release/selinux_test.go b/release/selinux_test.go index b6dde144d96..12e7bb088b0 100644 --- a/release/selinux_test.go +++ b/release/selinux_test.go @@ -91,7 +91,7 @@ func (s *selinuxSuite) TestProbePermissive(c *C) { level, status := release.ProbeSELinux() c.Assert(level, Equals, release.SELinuxPermissive) - 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)