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
interfaces/apparmor: conditionally add explicit deny rules for ptrace #5980
interfaces/apparmor: conditionally add explicit deny rules for ptrace #5980
Conversation
AppArmor deny rules cannot be undone by allow rules which makes deny rules difficult to work with arbitrary combinations of interfaces. Sometimes it is useful to suppress noisy denials and because that can currently only be done with explicit deny rules, adding explicit deny rules unconditionally makes it difficult for interfaces to be used in combination. Define suppressPtraceTrace to allow an interface to request suppression and define usesPtraceTrace to omit the explicit deny rules such that: if suppressPtraceTrace && !usesPtraceTrace { add 'deny ptrace (trace),' } Add UsesPtraceTrace() and SuppressPtraceTrace() that can be called in the interfaces to set the above and adjust addContent() to use the above logic to conditionally add a single explicit deny ptrace (trace) rule. Adjust system-observe and network-observe to suppress via the common interface and browser-support to use the new SuppressPtraceTrace(). With the above, the policy is equivalent to what was generated before (except we only have a single explicit denial rather than several when the above 3 interfaces are all connected at once). Future PRs will adjust other interfaces to use UsesPtraceTrace() as needed so these interfaces can still ptrace but continue to use the other interfaces.
bfb0723
to
c751355
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with some ideas on how to simplify the backend processing and a suggestion on naming the new functions and variables, IMO, more in the spirit of go naming. Please look at the question about jalimode though, I think this is a mistake given how jail mode was supposed to be a way to request strict.
@@ -419,12 +419,12 @@ func (b *Backend) deriveContent(spec *Specification, snapInfo *snap.Info, opts i | |||
// Add profile for each app. | |||
for _, appInfo := range snapInfo.Apps { | |||
securityTag := appInfo.SecurityTag() | |||
addContent(securityTag, snapInfo, opts, spec.SnippetForTag(securityTag), content) | |||
addContent(securityTag, snapInfo, opts, spec.SnippetForTag(securityTag), content, spec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we now pass both spec and securityTag into addContent we could make it call SnippetForTag internally. I can do this as a follow up, not to block this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds fine in a followup PR.
interfaces/apparmor/backend_test.go
Outdated
suppress: true, | ||
expected: false, | ||
}, | ||
// classic with jail, never suppress |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, wasn't classic with jail supposed to be the same as strict?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
interfaces/apparmor/spec.go
Outdated
@@ -477,3 +491,21 @@ func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *sn | |||
} | |||
return nil | |||
} | |||
|
|||
// UsesPtraceTrace records when to omit explicit ptrace deny rules | |||
func (spec *Specification) UsesPtraceTrace() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have expected UsesPtraceTrace
to return bool
and not set any internal state. How about calling the set UsePtraceTrace
, SupressPtraceTrace
. Since this is all a part of the package we could drop the getter functions entirely and just either expose a single public function that computes uses && !suppress
or just access the fields directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with zyga, this is a bit unusual in terms of go style. Most getters of the form "GetFoo() bool" are simply called "Foo() bool". Maybe something along the lines of what zyga suggested, e.g. http://paste.ubuntu.com/p/chSKC2jSBp/ ? This diff would add an explicit Set{Uses,Suppresses}PtraceTrace which simplifies some if/else. I kept the Get* to keep the diff small but I would like to rename them as zyga suggested. Happy to work on this fwiw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with reorganizing as requested. I'd prefer not to have Set* allow setting both ways because the interfaces will only need to call it with true
. If they call it with anything else, then it could introduce difficult to diagnose bugs where sometimes the rule is added and sometimes not based on on interface connection ordering. Eg, interface foo uses SetSuppressPtraceTrace(true)
, then interface bar does SetSuppressPtraceTrace(false)
. We'll get different behavior depending on which one snapd decides to connect first. Unless you strongly feel otherwise, I'll leave the one-way behavior (but still name it Set*).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zyga - regarding dropping getter, I use them in common_test.go and felt this was cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed but for such cases we can use export_test.go
, though at this stage it's not a strong feeling.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zyga - I recall having some trouble with mocking certain things in the PRs though I can't recall if this was one of them. If you don't have a strong feeling that it is a blocker, I suggest letting it in as is (it might be useful down the line). If it is strong enough for you wanting me to do a followup PR, I can.
@@ -566,6 +566,19 @@ var overlayRootSnippet = ` | |||
"###UPPERDIR###/{,**/}" r, | |||
` | |||
|
|||
var ptraceTraceDenySnippet = ` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is very nice and informational. Thank you! I love that you included the commit ID for the relevant mainline bug fix.
@@ -55,6 +55,9 @@ type commonInterface struct { | |||
connectedSlotKModModules []string | |||
permanentPlugKModModules []string | |||
permanentSlotKModModules []string | |||
|
|||
usesPtraceTrace bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you follow my naming suggestion above this could become
usePtraceTrace bool
suppressPtraceTrace bool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose 'usesPtraceTrace' since the interface is declaring it uses it. I chose 'suppressPtraceTrace' because the interface is saying "please supress the ptrace trace denials if you can". I can rename things if you still want.
@@ -106,6 +106,14 @@ execute: | | |||
fi | |||
|
|||
if [ "$(snap debug confinement)" = strict ] ; then | |||
if [ "$ALLOW_SANDBOX" = "false" ]; then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for updating the spread tests to capture the improvement!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thanks for working on this! I have some inline ideas to make slightly more go-ish, happy to work on those and push to this (or a follow) if you like them.
interfaces/apparmor/spec.go
Outdated
@@ -477,3 +491,21 @@ func (spec *Specification) AddPermanentSlot(iface interfaces.Interface, slot *sn | |||
} | |||
return nil | |||
} | |||
|
|||
// UsesPtraceTrace records when to omit explicit ptrace deny rules | |||
func (spec *Specification) UsesPtraceTrace() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with zyga, this is a bit unusual in terms of go style. Most getters of the form "GetFoo() bool" are simply called "Foo() bool". Maybe something along the lines of what zyga suggested, e.g. http://paste.ubuntu.com/p/chSKC2jSBp/ ? This diff would add an explicit Set{Uses,Suppresses}PtraceTrace which simplifies some if/else. I kept the Get* to keep the diff small but I would like to rename them as zyga suggested. Happy to work on this fwiw.
c.Assert(spec.GetSuppressPtraceTrace(), Equals, true) | ||
|
||
// setting both, only uses is set | ||
iface = &commonInterface{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above, not sure we need this special case.
@@ -86,6 +89,11 @@ func (iface *commonInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { | |||
} | |||
|
|||
func (iface *commonInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { | |||
if iface.usesPtraceTrace { | |||
spec.UsesPtraceTrace() | |||
} else if iface.suppressPtraceTrace { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why we need this? It seems like the apparmor/backend.go is doing the right thing if both are set. Not sure what this extra special case buys us. Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did it this way because there is no point setting uses
with suppress
since uses
always trumps suppress
and figured the common interface should reflect best practices. Happy to change if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the interface complain that both are set? I think this is orthogonal to the problem at hand, just about internal API usability. I think I agree with mvo, it's probably better as a "bunt" pusher of options than extra logic.
I can reduce this in the follow-up PR.
I think this is fine now but let me look quickly again. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, some comments for follow-up
@@ -553,6 +553,17 @@ func addContent(securityTag string, snapInfo *snap.Info, opts interfaces.Confine | |||
tagSnippets += snippet | |||
} | |||
} | |||
|
|||
if !ignoreSnippets { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine, it is done after we've seen all the snippets for a given snap.
suppress: true, | ||
expected: false, | ||
}, | ||
// classic, never suppress |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice :-)
@@ -86,6 +89,11 @@ func (iface *commonInterface) BeforePrepareSlot(slot *snap.SlotInfo) error { | |||
} | |||
|
|||
func (iface *commonInterface) AppArmorConnectedPlug(spec *apparmor.Specification, plug *interfaces.ConnectedPlug, slot *interfaces.ConnectedSlot) error { | |||
if iface.usesPtraceTrace { | |||
spec.UsesPtraceTrace() | |||
} else if iface.suppressPtraceTrace { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the interface complain that both are set? I think this is orthogonal to the problem at hand, just about internal API usability. I think I agree with mvo, it's probably better as a "bunt" pusher of options than extra logic.
I can reduce this in the follow-up PR.
Thanks for the reviews and merge! :) |
AppArmor deny rules cannot be undone by allow rules which makes deny
rules difficult to work with arbitrary combinations of interfaces.
Sometimes it is useful to suppress noisy denials and because that can
currently only be done with explicit deny rules, adding explicit deny
rules unconditionally makes it difficult for interfaces to be used in
combination. Define suppressPtraceTrace to allow an interface to request
suppression and define usesPtraceTrace to omit the explict deny rules
such that:
Add UsesPtraceTrace() and SuppressPtraceTrace() that can be called in
the interfaces to set the above and adjust addContent() to use the above
logic to conditionally add a single explicit deny ptrace (trace) rule.
Adjust system-observe and network-observe to suppress via the common
interface and browser-support to use the new SuppressPtraceTrace().
With the above, the policy is equivalent to what was generated before
(except we only have a single explicit denial rather than several when
the above 3 interfaces are all connected at once). Future PRs will
adjust other interfaces to use UsesPtraceTrace() as needed so these
interfaces can still ptrace but continue to use the other interfaces.