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: use locking around lazy intialized state #6306

Merged
merged 11 commits into from
Dec 20, 2018
61 changes: 49 additions & 12 deletions release/apparmor.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"path/filepath"
"sort"
"strings"
"sync"

"github.com/snapcore/snapd/strutil"
)
Expand Down Expand Up @@ -83,43 +84,59 @@ var (
// appArmorParserError contains an error, if any, encountered when
// discovering available parser features.
appArmorParserError error

appArmorProbeParserOnce sync.Once
appArmorProbeKernelOnce sync.Once
appArmorAssessOnce sync.Once

appArmorProbeKernel = probeAppArmorKernelFeaturesOnce
appArmorProbeParser = probeAppArmorParserFeaturesOnce
appArmorAssess = assessAppArmorOnce
)

func assessAppArmorOnce() {
appArmorAssessOnce.Do(assessAppArmor)
Copy link
Collaborator

Choose a reason for hiding this comment

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

were the actual functions like assesAppArmor etc tested?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}

// AppArmorLevel quantifies how well apparmor is supported on the current
// kernel. The computation is costly to perform. The result is cached internally.
func AppArmorLevel() AppArmorLevelType {
if appArmorLevel == UnknownAppArmor {
assessAppArmor()
}
appArmorAssess()
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a test that checks if the assessment is cached and actually done once? Since this extra if goes away, it would be good to have a test, in case the implementation based on sync.Once gets replaced in the future with new Go.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I looked into this, in the end I reorganized things quite a bit, mostly because the Mock* functions below are complicated/long but didn't help fully with testing this. I'm pushing the new code now, with it commenting the Once.Do bits always make some test fail.

return appArmorLevel
}

// AppArmorSummary describes how well apparmor is supported on the current
// kernel. The computation is costly to perform. The result is cached
// internally.
func AppArmorSummary() string {
if appArmorLevel == UnknownAppArmor {
assessAppArmor()
}
appArmorAssess()
return appArmorSummary
}

func probeAppArmorKernelFeaturesOnce() {
appArmorProbeKernelOnce.Do(func() {
appArmorKernelFeatures, appArmorKernelError = probeAppArmorKernelFeatures()
})
}

// AppArmorKernelFeatures returns a sorted list of apparmor features like
// []string{"dbus", "network"}. The result is cached internally.
func AppArmorKernelFeatures() ([]string, error) {
if appArmorKernelFeatures == nil {
appArmorKernelFeatures, appArmorKernelError = probeAppArmorKernelFeatures()
}
appArmorProbeKernel()
return appArmorKernelFeatures, appArmorKernelError
}

func probeAppArmorParserFeaturesOnce() {
appArmorProbeParserOnce.Do(func() {
appArmorParserFeatures, appArmorParserError = probeAppArmorParserFeatures()
})
}

// AppArmorParserFeatures returns a sorted list of apparmor parser features
// like []string{"unsafe", ...}. The computation is costly to perform. The
// result is cached internally.
func AppArmorParserFeatures() ([]string, error) {
if appArmorParserFeatures == nil {
appArmorParserFeatures, appArmorParserError = probeAppArmorParserFeatures()
}
appArmorProbeParser()
return appArmorParserFeatures, appArmorParserError
}

Expand Down Expand Up @@ -149,19 +166,29 @@ func MockAppArmorLevel(level AppArmorLevelType) (restore func()) {
oldAppArmorKernelError := appArmorKernelError
oldAppArmorParserFeatures := appArmorParserFeatures
oldAppArmorParserError := appArmorParserError
oldAppArmorProbeKernel := appArmorProbeKernel
oldAppArmorProbeParser := appArmorProbeParser
oldAppArmorAssess := appArmorAssess
appArmorLevel = level
appArmorSummary = fmt.Sprintf("mocked apparmor level: %s", level)
appArmorKernelFeatures = []string{"mocked-kernel-feature"}
appArmorKernelError = nil
appArmorParserFeatures = []string{"mocked-parser-feature"}
appArmorParserError = nil
nop := func() {}
appArmorProbeKernel = nop
appArmorProbeParser = nop
appArmorAssess = nop
return func() {
appArmorLevel = oldAppArmorLevel
appArmorSummary = oldAppArmorSummary
appArmorKernelFeatures = oldAppArmorKernelFeatures
appArmorKernelError = oldAppArmorKernelError
appArmorParserFeatures = oldAppArmorParserFeatures
appArmorParserError = oldAppArmorParserError
appArmorProbeKernel = oldAppArmorProbeKernel
appArmorProbeParser = oldAppArmorProbeParser
appArmorAssess = oldAppArmorAssess
}
}

Expand All @@ -176,10 +203,17 @@ func MockAppArmorFeatures(kernelFeatures []string, kernelError error, parserFeat
oldAppArmorKernelError := appArmorKernelError
oldAppArmorParserFeatures := appArmorParserFeatures
oldAppArmorParserError := appArmorParserError
oldAppArmorProbeKernel := appArmorProbeKernel
oldAppArmorProbeParser := appArmorProbeParser
oldAppArmorAssess := appArmorAssess
appArmorKernelFeatures = kernelFeatures
appArmorKernelError = kernelError
appArmorParserFeatures = parserFeatures
appArmorParserError = parserError
nop := func() {}
appArmorProbeKernel = nop
appArmorProbeParser = nop
appArmorAssess = nop
if appArmorKernelFeatures != nil && appArmorParserFeatures != nil {
assessAppArmor()
}
Expand All @@ -188,6 +222,9 @@ func MockAppArmorFeatures(kernelFeatures []string, kernelError error, parserFeat
appArmorKernelError = oldAppArmorKernelError
appArmorParserFeatures = oldAppArmorParserFeatures
appArmorParserError = oldAppArmorParserError
appArmorProbeKernel = oldAppArmorProbeKernel
appArmorProbeParser = oldAppArmorProbeParser
appArmorAssess = oldAppArmorAssess
if appArmorKernelFeatures != nil && appArmorParserFeatures != nil {
assessAppArmor()
}
Expand Down
29 changes: 0 additions & 29 deletions release/apparmor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,35 +44,6 @@ func (*apparmorSuite) TestAppArmorLevelTypeStringer(c *C) {
c.Check(release.AppArmorLevelType(42).String(), Equals, "AppArmorLevelType:42")
}

func (*apparmorSuite) TestAppArmorLevelTriggersAssesment(c *C) {
// Pretend that we know the apparmor kernel and parser features.
restore := release.MockAppArmorFeatures([]string{"feature"}, nil, []string{"feature"}, nil)
defer restore()
// Pretend that we don't know what the state of apparmor is.
release.ResetAppArmorAssesment()

// Calling AppArmorLevel assesses the kernel and parser features and sets
// the level to not-unknown value, returning it.
c.Check(release.CurrentAppArmorLevel(), Equals, release.UnknownAppArmor)
level := release.AppArmorLevel()
c.Check(level, Not(Equals), release.UnknownAppArmor)
c.Check(level, Equals, release.CurrentAppArmorLevel())
}

func (*apparmorSuite) TestAppArmorSummaryTriggersAssesment(c *C) {
// Pretend that we know the apparmor kernel and parser features.
restore := release.MockAppArmorFeatures([]string{"feature"}, nil, []string{"feature"}, nil)
defer restore()
// Pretend that we don't know what the state of apparmor is.
release.ResetAppArmorAssesment()

// Calling AppArmorSummary assesses the kernel and parser features and sets
// the level to something other than unknown.
c.Check(release.CurrentAppArmorLevel(), Equals, release.UnknownAppArmor)
release.AppArmorSummary()
c.Check(release.CurrentAppArmorLevel(), Not(Equals), release.UnknownAppArmor)
}

func (*apparmorSuite) TestMockAppArmorLevel(c *C) {
for _, lvl := range []release.AppArmorLevelType{release.NoAppArmor, release.UnusableAppArmor, release.PartialAppArmor, release.FullAppArmor} {
restore := release.MockAppArmorLevel(lvl)
Expand Down
36 changes: 26 additions & 10 deletions release/seccomp.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,49 @@ import (
"io/ioutil"
"sort"
"strings"
"sync"
)

var (
secCompAvailableActionsPath = "/proc/sys/kernel/seccomp/actions_avail"
)

var secCompActions []string
var (
secCompActions []string
secCompOnce sync.Once
probeSecCompActions = probeSecCompActionsOnce
)

func MockSecCompActions(actions []string) (restore func()) {
old := secCompActions
secCompActions = actions
return func() { secCompActions = old }
oldProbe := probeSecCompActions
// nop
probeSecCompActions = func() {}
return func() {
secCompActions = old
probeSecCompActions = oldProbe
}
}

// SecCompActions returns a sorted list of seccomp actions like
// []string{"allow", "errno", "kill", "log", "trace", "trap"}.
func SecCompActions() []string {
if secCompActions == nil {
var actions []string
func probeSecCompActionsOnce() {
secCompOnce.Do(func() {
secCompActions = []string{}

contents, err := ioutil.ReadFile(secCompAvailableActionsPath)
if err != nil {
return actions
return
}
actions = strings.Split(strings.TrimRight(string(contents), "\n"), " ")
actions := strings.Split(strings.TrimRight(string(contents), "\n"), " ")
sort.Strings(actions)
secCompActions = actions
}
})
}

// SecCompActions returns a sorted list of seccomp actions like
// []string{"allow", "errno", "kill", "log", "trace", "trap"}.
func SecCompActions() []string {
probeSecCompActions()
return secCompActions
}

Expand Down