[apparmor] [patch] Add tests for RlimitRule and RlimitRuleset

Steve Beattie steve at nxnw.org
Thu Jun 4 18:50:10 UTC 2015


On Tue, May 26, 2015 at 12:41:19AM +0200, Christian Boltz wrote:
> Am Dienstag, 26. Mai 2015 schrieb Christian Boltz:
> > $subject.
> > 
> > This time we "only" have 98% coverage (2 missing, 3 partial) because
> > I didn't find corner cases that raise some exceptions ;-)
> > (maybe we can even drop those checks if they are never hit?)
> 
> Here's the patch again, but without linebreaks caused by my mail client:

> [ 42-add-tests-for-rlimit-rule.diff ]
> 
> === modified file utils/test/test-rlimit.py
> --- utils/test/test-rlimit.py   2015-05-25 23:59:49.484474818 +0200
> +++ utils/test/test-rlimit.py   2015-05-25 23:35:41.919727344 +0200
> @@ -0,0 +1,468 @@
> +#!/usr/bin/env python
> +# ----------------------------------------------------------------------
> +#    Copyright (C) 2015 Christian Boltz <apparmor at cboltz.de>
> +#
> +#    This program is free software; you can redistribute it and/or
> +#    modify it under the terms of version 2 of the GNU General Public
> +#    License 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.
> +#
> +# ----------------------------------------------------------------------
> +
> +import unittest
> +from collections import namedtuple
> +from common_test import AATest, setup_all_loops
> +
> +from apparmor.rule.rlimit import RlimitRule, RlimitRuleset, split_unit
> +from apparmor.rule import BaseRule
> +from apparmor.common import AppArmorException, AppArmorBug
> +#from apparmor.logparser import ReadLog
> +from apparmor.translations import init_translation
> +_ = init_translation()
> +
> +exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment',
> +        'rlimit', 'value', 'all_values'])
> +
> +# --- tests for single RlimitRule --- #
> +
> +class RlimitTest(AATest):
> +    def _compare_obj(self, obj, expected):
> +        self.assertEqual(expected.allow_keyword, obj.allow_keyword)
> +        self.assertEqual(expected.audit, obj.audit)
> +        self.assertEqual(expected.rlimit, obj.rlimit)
> +        self.assertEqual(expected.value, obj.value)
> +        self.assertEqual(expected.all_values, obj.all_values)
> +        self.assertEqual(expected.deny, obj.deny)
> +        self.assertEqual(expected.comment, obj.comment)
> +
> +class RlimitTestParse(RlimitTest):
> +    tests = [
> +        # rawrule                                    audit  allow  deny   comment         rlimit          value      all/infinity?
> +        ('set rlimit as <= 2047MB,'            , exp(False, False, False, ''           , 'as'           , '2047MB'      , False)),
> +        ('set rlimit cpu <= 1024,'             , exp(False, False, False, ''           , 'cpu'          , '1024'        , False)),
> +        ('set rlimit stack <= 1024GB,'         , exp(False, False, False, ''           , 'stack'        , '1024GB'      , False)),
> +        ('set rlimit rtprio <= 10, # comment'  , exp(False, False, False, ' # comment' , 'rtprio'       , '10'          , False)),
> +        ('set rlimit core <= 44444KB,'         , exp(False, False, False, ''           , 'core'         , '44444KB'     , False)),
> +        ('set rlimit rttime <= 60ms,'          , exp(False, False, False, ''           , 'rttime'       , '60ms'        , False)),
> +        ('set rlimit cpu <= infinity,'         , exp(False, False, False, ''           , 'cpu'          , None          , True )),
> +        ('set rlimit nofile <= 256,'           , exp(False, False, False, ''           , 'nofile'       , '256'         , False)),
> +        ('set rlimit data <= 4095KB,'          , exp(False, False, False, ''           , 'data'         , '4095KB'      , False)),
> +        ('set rlimit cpu <= 12, # cmt'         , exp(False, False, False, ' # cmt'     , 'cpu'          , '12'          , False)),
> +        ('set rlimit ofile <= 1234,'           , exp(False, False, False, ''           , 'ofile'        , '1234'        , False)),
> +        ('set rlimit msgqueue <= 4444,'        , exp(False, False, False, ''           , 'msgqueue'     , '4444'        , False)),
> +        ('set rlimit nice <= -10,'             , exp(False, False, False, ''           , 'nice'         , '-10'         , False)),
> +        ('set rlimit rttime <= 60minutes,'     , exp(False, False, False, ''           , 'rttime'       , '60minutes'   , False)),
> +        ('set rlimit fsize <= 1023MB,'         , exp(False, False, False, ''           , 'fsize'        , '1023MB'      , False)),
> +        ('set rlimit nproc <= 1,'              , exp(False, False, False, ''           , 'nproc'        , '1'           , False)),
> +        ('set rlimit rss <= infinity, # cmt'   , exp(False, False, False, ' # cmt'     , 'rss'          , None          , True )),
> +        ('set rlimit memlock <= 10240,'        , exp(False, False, False, ''           , 'memlock'      , '10240'       , False)),
> +        ('set rlimit sigpending <= 42,'        , exp(False, False, False, ''           , 'sigpending'   , '42'          , False)),
> +     ]
> +
> +    def _run_test(self, rawrule, expected):
> +        self.assertTrue(RlimitRule.match(rawrule))
> +        obj = RlimitRule.parse(rawrule)
> +        self.assertEqual(rawrule.strip(), obj.raw_rule)
> +        self._compare_obj(obj, expected)
> +
> +class RlimitTestParseInvalid(RlimitTest):
> +    tests = [
> +        ('set rlimit,'                      , AppArmorException), # missing parts
> +        ('set rlimit <= 5,'                 , AppArmorException),
> +        ('set rlimit cpu <= ,'              , AppArmorException),
> +        ('set rlimit foo <= 5,'             , AppArmorException), # unknown rlimit
> +        ('set rlimit rttime <= 60m,'        , AppArmorException), # 'm' could mean 'ms' or 'minutes'
> +        ('set rlimit nice <= 20MB,'         , AppArmorException), # invalid unit for this rlimit type
> +        ('set rlimit cpu <= 20MB,'          , AppArmorException),
> +        ('set rlimit data <= 20seconds,'    , AppArmorException),
> +        ('set rlimit locks <= 20seconds,'   , AppArmorException),
> +    ]
> +
> +    def _run_test(self, rawrule, expected):
> +        #self.assertFalse(RlimitRule.match(rawrule))  # the main regex isn't very strict
> +        with self.assertRaises(expected):
> +            RlimitRule.parse(rawrule)
> +
> +class RlimitTestParseFromLog(RlimitTest):
> +    pass
> +    # def test_net_from_log(self):
> +    #   parser = ReadLog('', '', '', '', '')
> +
> +    #   event = 'type=AVC ...'
> +
> +    #   parsed_event = parser.parse_event(event)
> +
> +    #   self.assertEqual(parsed_event, {
> +    #       ...
> +    #   })
> +
> +    #   obj = RlimitRule(RlimitRule.ALL, parsed_event['name2'], log_event=parsed_event)
> +
> +    #   #              audit  allow  deny   comment        rlimit  value     all?
> +    #   expected = exp(False, False, False, ''           , None,   '/foo/rename', False)
> +
> +    #   self._compare_obj(obj, expected)
> +
> +    #   self.assertEqual(obj.get_raw(1), '  rlimit -> /foo/rename,')
> +
> +
> +class RlimitFromInit(RlimitTest):
> +    tests = [
> +        # RlimitRule object                                  audit  allow  deny   comment        rlimit         value     all/infinity?
> +        (RlimitRule('as', '2047MB')                     , exp(False, False, False, ''           , 'as'      ,   '2047MB'    , False)),
> +        (RlimitRule('cpu', '1024')                      , exp(False, False, False, ''           , 'cpu'     ,   '1024'      , False)),
> +        (RlimitRule('rttime', '60minutes')              , exp(False, False, False, ''           , 'rttime'  ,    '60minutes', False)),
> +        (RlimitRule('nice', '-10')                      , exp(False, False, False, ''           , 'nice'    ,   '-10'       , False)),
> +        (RlimitRule('rss', RlimitRule.ALL)              , exp(False, False, False, ''           , 'rss'     ,   None        , True )),
> +    ]
> +
> +    def _run_test(self, obj, expected):
> +        self._compare_obj(obj, expected)
> +
> +
> +class InvalidRlimitInit(AATest):
> +    tests = [
> +        # init params                     expected exception
> +        (['as'  , ''               ]    , AppArmorBug), # empty value
> +        ([''    , '1024'           ]    , AppArmorException), # empty rlimit
> +        (['    ', '1024'           ]    , AppArmorException), # whitespace rlimit
> +        (['as'  , '   '            ]    , AppArmorBug), # whitespace value
> +        (['xyxy', '1024'           ]    , AppArmorException), # invalid rlimit
> +        ([dict(), '1024'           ]    , AppArmorBug), # wrong type for rlimit
> +        ([None  , '1024'           ]    , AppArmorBug), # wrong type for rlimit
> +        (['as'  , dict()           ]    , AppArmorBug), # wrong type for value
> +        (['as'  , None             ]    , AppArmorBug), # wrong type for value
> +    ]
> +
> +    def _run_test(self, params, expected):
> +        with self.assertRaises(expected):
> +            RlimitRule(params[0], params[1])
> +
> +    def test_missing_params_1(self):
> +        with self.assertRaises(TypeError):
> +            RlimitRule()
> +
> +    def test_missing_params_2(self):
> +        with self.assertRaises(TypeError):
> +            RlimitRule('as')
> +
> +    def test_allow_keyword(self):
> +        with self.assertRaises(AppArmorBug):
> +            RlimitRule('as', '1024MB', allow_keyword=True)
> +
> +    def test_deny_keyword(self):
> +        with self.assertRaises(AppArmorBug):
> +            RlimitRule('as', '1024MB', deny=True)
> +
> +    def test_audit_keyword(self):
> +        with self.assertRaises(AppArmorBug):
> +            RlimitRule('as', '1024MB', audit=True)
> +
> +
> +class InvalidRlimitTest(AATest):
> +    def _check_invalid_rawrule(self, rawrule):
> +        obj = None
> +        self.assertFalse(RlimitRule.match(rawrule))
> +        with self.assertRaises(AppArmorException):
> +            obj = RlimitRule(RlimitRule.parse(rawrule))
> +
> +        self.assertIsNone(obj, 'RlimitRule handed back an object unexpectedly')
> +
> +    def test_invalid_net_missing_comma(self):
> +        self._check_invalid_rawrule('rlimit')  # missing comma
> +
> +    def test_invalid_net_non_RlimitRule(self):
> +        self._check_invalid_rawrule('dbus,')  # not a rlimit rule
> +
> +    def test_empty_net_data_1(self):
> +        obj = RlimitRule('as', '1024MB')
> +        obj.rlimit = ''
> +        # no rlimit set, and ALL not set
> +        with self.assertRaises(AppArmorBug):
> +            obj.get_clean(1)
> +
> +    def test_empty_net_data_2(self):
> +        obj = RlimitRule('as', '1024MB')
> +        obj.value = ''
> +        # no value set, and ALL not set
> +        with self.assertRaises(AppArmorBug):
> +            obj.get_clean(1)
> +
> +
> +class WriteRlimitTest(AATest):
> +    tests = [
> +        #  raw rule                                                      clean rule
> +        ('     set rlimit    cpu   <= 1024     ,    # foo        '              , 'set rlimit cpu <= 1024, # foo'),
> +        ('        set     rlimit  stack <= 1024GB  ,'                          , 'set rlimit stack <= 1024GB,'),
> +        ('   set rlimit    rttime <= 100ms   ,   # foo bar'   , 'set rlimit rttime <= 100ms, # foo bar'),
> +        ('   set rlimit      cpu   <= infinity   ,  '         , 'set rlimit cpu <= infinity,'),
> +        ('    set rlimit     msgqueue <=   4444 ,   '         , 'set rlimit msgqueue <= 4444,'),
> +        ('    set rlimit   nice    <=  5   ,   # foo bar'     , 'set rlimit nice <= 5, # foo bar'),
> +        ('    set rlimit   nice <=   -5    , # cmt'           , 'set rlimit nice <= -5, # cmt'),
> +    ]
> +
> +    def _run_test(self, rawrule, expected):
> +        self.assertTrue(RlimitRule.match(rawrule))
> +        obj = RlimitRule.parse(rawrule)
> +        clean = obj.get_clean()
> +        raw = obj.get_raw()
> +
> +        self.assertEqual(expected.strip(), clean, 'unexpected clean rule')
> +        self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule')
> +
> +    def test_write_manually(self):
> +        obj = RlimitRule('as', '1024MB')
> +
> +        expected = '    set rlimit as <= 1024MB,'
> +
> +        self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule')
> +        self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule')
> +
> +
> +class RlimitCoveredTest(AATest):
> +    def _run_test(self, param, expected):
> +        obj = RlimitRule.parse(self.rule)
> +        check_obj = RlimitRule.parse(param)
> +
> +        self.assertTrue(RlimitRule.match(param))
> +
> +        self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0])
> +        self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1])
> +
> +        self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2])
> +        self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3])
> +
> +class RlimitCoveredTest_01(RlimitCoveredTest):
> +    rule = 'set rlimit cpu <= 150,'
> +
> +    tests = [
> +        #   rule                            equal     strict equal    covered     covered exact
> +        ('set rlimit as <= 100MB,'      , [ False   , False         , False     , False     ]),
> +        ('set rlimit rttime <= 150,'    , [ False   , False         , False     , False     ]),
> +        ('set rlimit cpu <= 100,'       , [ False   , False         , True      , True      ]),
> +        ('set rlimit cpu <= 150,'       , [ True    , True          , True      , True      ]),
> +        ('set rlimit cpu <= 300,'       , [ False   , False         , False     , False     ]),
> +        ('set rlimit cpu <= 10seconds,' , [ False   , False         , True      , True      ]),
> +        ('set rlimit cpu <= 150seconds,', [ True    , False         , True      , True      ]),
> +        ('set rlimit cpu <= 300seconds,', [ False   , False         , False     , False     ]),
> +        ('set rlimit cpu <= 1minutes,'  , [ False   , False         , True      , True      ]),
> +        ('set rlimit cpu <= 1m,'        , [ False   , False         , True      , True      ]),
> +        ('set rlimit cpu <= 3minutes,'  , [ False   , False         , False     , False     ]),
> +        ('set rlimit cpu <= 1hour,'     , [ False   , False         , False     , False     ]),
> +    ]
> +
> +class RlimitCoveredTest_02(RlimitCoveredTest):
> +    rule = 'set rlimit data <= 4MB,'
> +
> +    tests = [
> +        #   rule                            equal     strict equal    covered     covered exact
> +        ('set rlimit data <= 100,'      , [ False   , False         , True      , True      ]),
> +        ('set rlimit data <= 2KB,'      , [ False   , False         , True      , True      ]),
> +        ('set rlimit data <= 2MB,'      , [ False   , False         , True      , True      ]),
> +        ('set rlimit data <= 4194304,'  , [ True    , False         , True      , True      ]),
> +        ('set rlimit data <= 4096KB,'   , [ True    , False         , True      , True      ]),
> +        ('set rlimit data <= 4MB,'      , [ True    , True          , True      , True      ]),
> +        ('set rlimit data <= 6MB,'      , [ False   , False         , False     , False     ]),
> +        ('set rlimit data <= 1GB,'      , [ False   , False         , False     , False     ]),
> +    ]
> +
> +class RlimitCoveredTest_03(RlimitCoveredTest):
> +    rule = 'set rlimit nice <= -1,'
> +
> +    tests = [
> +        #   rule                            equal     strict equal    covered     covered exact
> +        ('set rlimit nice <= 5,'        , [ False   , False         , True      , True      ]),
> +        ('set rlimit nice <= 0,'        , [ False   , False         , True      , True      ]),
> +        ('set rlimit nice <= -1,'       , [ True    , True          , True      , True      ]),
> +        ('set rlimit nice <= -3,'       , [ False   , False         , False     , False     ]),
> +    ]
> +
> +class RlimitCoveredTest_04(RlimitCoveredTest):
> +    rule = 'set rlimit locks <= 42,'
> +
> +    tests = [
> +        #   rule                            equal     strict equal    covered     covered exact
> +        ('set rlimit locks <= 20,'      , [ False   , False         , True      , True      ]),
> +        ('set rlimit locks <= 40,'      , [ False   , False         , True      , True      ]),
> +        ('set rlimit locks <= 42,'      , [ True    , True          , True      , True      ]),
> +        ('set rlimit locks <= 60,'      , [ False   , False         , False     , False     ]),
> +        ('set rlimit locks <= infinity,', [ False   , False         , False     , False     ]),
> +    ]
> +
> +class RlimitCoveredTest_05(RlimitCoveredTest):
> +    rule = 'set rlimit locks <= infinity,'
> +
> +    tests = [
> +        #   rule                            equal     strict equal    covered     covered exact
> +        ('set rlimit locks <= 20,'      , [ False   , False         , True      , True      ]),
> +        ('set rlimit cpu <= 40,'        , [ False   , False         , False     , False     ]),
> +        ('set rlimit locks <= infinity,', [ True    , True          , True      , True      ]),
> +    ]
> +
> +class RlimitCoveredTest_Invalid(AATest):
> +    def test_borked_obj_is_covered_1(self):
> +        obj = RlimitRule.parse('set rlimit cpu <= 1024,')
> +
> +        testobj = RlimitRule('cpu', '1024')
> +        testobj.rlimit = ''
> +
> +        with self.assertRaises(AppArmorBug):
> +            obj.is_covered(testobj)
> +
> +    def test_borked_obj_is_covered_2(self):
> +        obj = RlimitRule.parse('set rlimit cpu <= 1024,')
> +
> +        testobj = RlimitRule('cpu', '1024')
> +        testobj.value = ''
> +
> +        with self.assertRaises(AppArmorBug):
> +            obj.is_covered(testobj)
> +
> +    def test_invalid_is_covered(self):
> +        obj = RlimitRule.parse('set rlimit cpu <= 1024,')
> +
> +        testobj = BaseRule()  # different type
> +
> +        with self.assertRaises(AppArmorBug):
> +            obj.is_covered(testobj)
> +
> +    def test_invalid_is_equal(self):
> +        obj = RlimitRule.parse('set rlimit cpu <= 1024,')
> +
> +        testobj = BaseRule()  # different type
> +
> +        with self.assertRaises(AppArmorBug):
> +            obj.is_equal(testobj)
> +
> +class CapabilityLogprofHeaderTest(AATest):

I suspect you intended this to be s/Capability/Rlimit/

> +    tests = [
> +        ('set rlimit cpu <= infinity,',         [_('Rlimit'), 'cpu',     _('Value'), 'infinity', ]),
> +        ('set rlimit as <= 200MB,',             [_('Rlimit'), 'as',      _('Value'), '200MB',    ]),
> +        ('set rlimit rttime <= 200ms,',         [_('Rlimit'), 'rttime',  _('Value'), '200ms',    ]),
> +        ('set rlimit nproc <= 1,',              [_('Rlimit'), 'nproc',   _('Value'), '1',        ]),
> +    ]
> +
> +    def _run_test(self, params, expected):
> +        obj = RlimitRule._parse(params)
> +        self.assertEqual(obj.logprof_header(), expected)
> +
> +# --- tests for RlimitRuleset --- #
> +
> +class RlimitRulesTest(AATest):
> +    def test_empty_ruleset(self):
> +        ruleset = RlimitRuleset()
> +        ruleset_2 = RlimitRuleset()
> +        self.assertEqual([], ruleset.get_raw(2))
> +        self.assertEqual([], ruleset.get_clean(2))
> +        self.assertEqual([], ruleset_2.get_raw(2))
> +        self.assertEqual([], ruleset_2.get_clean(2))
> +
> +    def test_ruleset_1(self):
> +        ruleset = RlimitRuleset()
> +        rules = [
> +            '  set rlimit cpu  <= 100,',
> +            '  set rlimit as   <= 50MB,',
> +        ]
> +
> +        expected_raw = [
> +            'set rlimit cpu  <= 100,',
> +            'set rlimit as   <= 50MB,',
> +            '',
> +        ]
> +
> +        expected_clean = [
> +            'set rlimit as <= 50MB,',
> +            'set rlimit cpu <= 100,',
> +            '',
> +        ]
> +
> +        for rule in rules:
> +            ruleset.add(RlimitRule.parse(rule))
> +
> +        self.assertEqual(expected_raw, ruleset.get_raw())
> +        self.assertEqual(expected_clean, ruleset.get_clean())
> +
> +class RlimitGlobTestAATest(AATest):
> +    def setUp(self):
> +        self.ruleset = RlimitRuleset()
> +
> +    def test_glob_1(self):
> +        with self.assertRaises(AppArmorBug):
> +            self.ruleset.get_glob('set rlimit cpu <= 100,')
> +
> +    # not supported or used yet, glob behaviour not decided yet
> +    # def test_glob_2(self):
> +    #     self.assertEqual(self.ruleset.get_glob('rlimit /foo -> /bar,'), 'rlimit -> /bar,')
> +
> +    def test_glob_ext(self):
> +        with self.assertRaises(AppArmorBug):
> +            # get_glob_ext is not available for rlimit rules
> +            self.ruleset.get_glob_ext('set rlimit cpu <= 100,')
> +
> +class RlimitDeleteTestAATest(AATest):
> +    pass

Sorry, what's this class intended for?

> +# --- other tests --- #
> +class RlimitSplit_unitTest(AATest):
> +    tests = [
> +        ('40MB'  , ( 40, 'MB',)),
> +        ('40'  ,   ( 40, '',  )),
> +    ]
> +
> +    def _run_test(self, params, expected):
> +        self.assertEqual(split_unit(params), expected)
> +
> +    def test_invalid_split_unit(self):
> +        with self.assertRaises(AppArmorBug):
> +            split_unit('MB')
> +
> +class RlimitSize_to_intTest(AATest):
> +    def AASetup(self):
> +        self.obj = RlimitRule('cpu', '1')
> +
> +    tests = [
> +        ('40GB'  , 40 * 1024 * 1024 * 1024),
> +        ('40MB'  , 41943040),
> +        ('40KB'  , 40960),
> +        ('40'    , 40),
> +    ]
> +
> +    def _run_test(self, params, expected):
> +        self.assertEqual(self.obj.size_to_int(params), expected)
> +
> +    def test_invalid_size_to_int_01(self):
> +        with self.assertRaises(AppArmorException):
> +            self.obj.size_to_int('20mice')
> +
> +class RlimitTime_to_intTest(AATest):
> +    def AASetup(self):
> +        self.obj = RlimitRule('cpu', '1')
> +
> +    tests = [
> +        ('40ms'     ,       0.04),
> +        ('40'       ,      40),
> +        ('40seconds',      40),
> +        ('2minutes' ,    2*60),
> +        ('2hours'   , 2*60*60),
> +    ]
> +
> +    def _run_test(self, params, expected):
> +        self.assertEqual(self.obj.time_to_int(params, 'seconds'), expected)
> +
> +    def test_with_ms_as_default(self):
> +        self.assertEqual(self.obj.time_to_int('40', 'ms'), 0.04)
> +
> +    def test_invalid_time_to_int(self):
> +        with self.assertRaises(AppArmorException):
> +            self.obj.time_to_int('20mice', 'seconds')
> +
> +
> +
> +setup_all_loops(__name__)
> +if __name__ == '__main__':
> +    unittest.main(verbosity=2)

Acked-by: Steve Beattie <steve at nxnw.org> with the capability/rlimit fix;
would like to see a followup for whatever the RlimitDeleteTestAATest
class is supposed to do.

-- 
Steve Beattie
<sbeattie at ubuntu.com>
http://NxNW.org/~steve/
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <https://lists.ubuntu.com/archives/apparmor/attachments/20150604/bea13133/attachment-0001.pgp>


More information about the AppArmor mailing list