Hello,

(back from the openSUSE Conference, which was once more interesting and 
following the "have a lot of fun..." motto :-) )

BTW: One of the talks was about a new "MySQL Firewall" which does 
whitelisting of allowed queries - basically AppArmor for MySQL ;-)
(Link to the video on request.)


Am Samstag, 25. Juni 2016, 15:59:06 CEST schrieb Tyler Hicks:
> https://launchpad.net/bugs/1584069
> 
> This patch adds support for the safe and unsafe exec modes for
> change_profile rules. The logic is pretty simple at this point because
> the kernel's default for exec modes changed in newer versions.
> Therefore, this patch simply retains any specified exec mode in
> parsed rules. If an exec mode is not specified in a rule, there is no
> attempt to force the usage of "safe" because older kernels do not
> support it.

Looks like I was too busy with other things in the last weeks ;-)
Thanks for the patch!

Some comments inline - nothing terribly wrong, but nevertheless I'm 
looking forward for v2 ;-)

> Signed-off-by: Tyler Hicks <tyhi...@canonical.com>
> ---
>  utils/apparmor/regex.py                |   2 +
>  utils/apparmor/rule/change_profile.py  |  30 +++++++-
>  utils/test/test-change_profile.py      | 135
> +++++++++++++++++++--------------
> utils/test/test-parser-simple-tests.py |   5 ++
>  4 files changed, 112 insertions(+), 60 deletions(-)
> 
> diff --git a/utils/apparmor/regex.py b/utils/apparmor/regex.py
> index 756c3e0..75d1042 100644
> --- a/utils/apparmor/regex.py
> +++ b/utils/apparmor/regex.py
> @@ -30,6 +30,7 @@ RE_PROFILE_NAME         = '(?P<%s>(\S+|"[^"]+"))'   
> # string without spaces, or RE_PATH                 = '/\S+|"/[^"]+"'
>  # filename (starting with '/') without spaces, or quoted filename.
> RE_PROFILE_PATH         = '(?P<%s>(' + RE_PATH + '))'  # quoted or
> unquoted filename. %s is the match group name RE_PROFILE_PATH_OR_VAR 
> = '(?P<%s>(' + RE_PATH + '|@{\S+}\S*|"@{\S+}[^"]*"))'  # quoted or
> unquoted filename or variable. %s is the match group name
> +RE_SAFE_OR_UNSAFE       = '(?P<%s>(safe|unsafe))'

It's unlikely that we'll need the RE_SAFE_OR_UNSAFE sniplet more than 
once in the same regex, so you can embed the 'execmode' name directly 
instead of ...

>  RE_PROFILE_END          = re.compile('^\s*\}' + RE_EOL)
>  RE_PROFILE_CAP          = re.compile(RE_AUDIT_DENY +
> 'capability(?P<capability>(\s+\S+)+)?' + RE_COMMA_EOL) @@ -77,6 +78,7
> @@ RE_PROFILE_START          = re.compile(
>  RE_PROFILE_CHANGE_PROFILE = re.compile(
>      RE_AUDIT_DENY +
>      'change_profile' +
> +    '(\s+' + RE_SAFE_OR_UNSAFE % 'execmode' + ')?' +  # optionally

... inserting it here using % 'execmode'.

(We have rule types where RE_PROFILE_PATH and/or RE_PROFILE_PATH_OR_VAR 
can appear more than once, and that's the reason for using %s in them.)

> diff --git a/utils/apparmor/rule/change_profile.py
> b/utils/apparmor/rule/change_profile.py index 6063b70..cddc09a 100644
> --- a/utils/apparmor/rule/change_profile.py
> +++ b/utils/apparmor/rule/change_profile.py
> @@ -46,6 +46,13 @@ class ChangeProfileRule(BaseRule):
>                                               comment=comment,
>                                               log_event=log_event)
> 
> +        if execmode:
> +            if execmode != 'safe' and execmode != 'unsafe':
> +                raise AppArmorException('Unknown exec mode (%s) in
> change_profile rule' % execmode) 

I'd vote for AppArmorBug - if something passes in an invalid value here, 
that's clearly a bug. (The parsing regex makes sure that only safe/
unsafe/None comes in, so parsing a profile should never hit this.)

> +            elif not execcond or
> execcond == ChangeProfileRule.ALL: +                raise
> AppArmorException('Exec condition is required when unsafe or safe
> keywords are present') 
> +        self.execmode = execmode

Just for the records - for this case, AppArmorException makes sense.

>      def is_covered_localvars(self, other_rule):
>          '''check if other_rule is covered by this rule object'''
> 
> +        if self.execmode != other_rule.execmode:
> +            return False

The default execmode is 'safe', so 'safe' and '' / None should be 
considered as equal.

> @@ -139,6 +156,9 @@ class ChangeProfileRule(BaseRule):
>          if not type(rule_obj) == ChangeProfileRule:
>              raise AppArmorBug('Passed non-change_profile rule: %s' %
> str(rule_obj))
> 
> +        if self.execmode != rule_obj.execmode:
> +            return False

Same here. (Maybe add a TODO that strict mode should behave different - 
I'll add this flag to the function in the FileRule patches and will 
adjust the change_profile code afterwards.)

> @@ -150,10 +170,12 @@ class ChangeProfileRule(BaseRule):
>          return True
> 
>      def logprof_header_localvars(self):
> +        execmode_txt        = logprof_value_or_all(self.execmode,    
>   None) 

Same again ;-)

Also, did you really test this in all variants? I'd guess not specifying 
an exec mode will result in
    Exec Mode:
or even
    Exec Mode: None

'safe' / 'unsafe' would be better choices ;-)

Also, you don't need to use logprof_value_or_all() - with None as second 
parameter, it will just return the first parameter (self.execmode) so you 
can use that directly. (There's some special handling for sets, lists 
and tuples, but that doesn't matter here.)

So basically you should do

    if self.execmode:
        execmode_txt = self.execmode
    else
        execmode_txt = 'safe'

> @@ -144,20 +153,20 @@ class ChangeProfileFromInit(ChangeProfileTest):
>  class InvalidChangeProfileInit(AATest):

I'm not quoting your changes because KMail made them unreadable by 
changing the linebreaks ;-)

Nevertheless, please add another test that includes an invalid execmode, 
for example

        (['maybe'    , '/foo', '/bar'           ]    , AppArmorBug), # 
invalid execmode

(should be one line, but I won't do manual linebreaks in the whole mail 
just to get it right here ;-)

> @@ -248,6 +257,8 @@ class
> ChangeProfileCoveredTest_01(ChangeProfileCoveredTest): 

> +        ('           change_profile safe /foo,'     , [ False   , 
> False         , False     , False     ]),

After handling 'safe' and None/'' as equal, you'll need to adjust this 
test ;-)

> ChangeProfileCoveredTest_02(ChangeProfileCoveredTest): (     

> +         (      'change_profile safe /foo -> /bar,' , [ False   , 
False         , False     , False     ]),

Same here.

> +class ChangeProfileCoveredTest_06(ChangeProfileCoveredTest):
> +    rule = 'change_profile safe /foo,'
> 
> +    tests = [
> +        #   rule                                       equal    
> strict equal    covered     covered exact +        (      'deny
> change_profile /foo,'         , [ False   , False         , False    
> , False     ]), +        ('audit deny change_profile /foo,'         ,
> [ False   , False         , False     , False     ]), +        (     
>      'change_profile /foo,'         , [ False   , False         ,
> False     , False     ]), # XXX should covered be true here? +       
> (      'deny change_profile /bar,'         , [ False   , False       
>  , False     , False     ]), +        (      'deny change_profile,'  
>            , [ False   , False         , False     , False     ]), + 
>   ]

and here ;-)

> @@ -333,7 +356,7 @@ class ChangeProfileCoveredTest_Invalid(AATest):
>      def test_borked_obj_is_covered_2(self):

You might want to add another testcase that sets an invalid 
testobj.execmode.

>  class ChangeProfileLogprofHeaderTest(AATest):
>      tests = [

> +         ('change_profile,',                         [                       
>         
_('Exec Mode'), None,     _('Exec Condition'), _('ALL'),  _('Target 
Profile'), _('ALL'),   ]),

Ah, here you confirm that
    Exec Condition:     None
will be displayed in aa-logprof or aa-mergeprof.

As I already wrote in the comment about logprof_headers_localvars(), you 
should avoid that ;-)  (and adjust the tests afterwards)

> diff --git a/utils/test/test-parser-simple-tests.py
> b/utils/test/test-parser-simple-tests.py index 304ff98..66b77ab
> 100644
> --- a/utils/test/test-parser-simple-tests.py
> +++ b/utils/test/test-parser-simple-tests.py
> @@ -47,6 +47,11 @@ exception_not_raised = [
>      'capability/bad_3.sd',
>      'capability/bad_4.sd',
>      'change_hat/bad_parsing.sd',
> +
> +    # The tools don't detect conflicting change_profile exec modes
> +    'change_profile/onx_conflict_unsafe1.sd',
> +    'change_profile/onx_conflict_unsafe2.sd',

OK for now, but having conflict detection would be nice.


Regards,

Christian Boltz
-- 
[Evolution - Message-ID] Oh ja... Apropos: die libcamel (die fuer
diesen Muell verantwortlich ist) ist, aehm. "interessant" zu lesen...
Und NEIN! Ich habe keine Lust, den Muell zu fixen. Es sei denn, man
zahlt mir Schmerzensgeld. [David Haller in suse-linux]

Attachment: signature.asc
Description: This is a digitally signed message part.

-- 
AppArmor mailing list
AppArmor@lists.ubuntu.com
Modify settings or unsubscribe at: 
https://lists.ubuntu.com/mailman/listinfo/apparmor

Reply via email to