mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-28 21:07:56 +00:00
Merge Add PivotRootRule class
... and tests for it. This replaces the old code that just stores the full rule as text. We also get rid of the old ['allow'] and ['deny'] items in ProfileStorage, the handling of old write functions, and the last usage of _Raw_Rule (and therefore _Raw_Rule itsself). Also delete the old test-pivot_root_parse.py which relied on the ancient code, and even used a wrong syntax in its test rules. Oh, and aa-logprof can now ask about pivot_root events. See the individual commits for details. MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1232 Approved-by: Georgia Garcia <georgia.garcia@canonical.com> Merged-by: Christian Boltz <apparmor@cboltz.de>
This commit is contained in:
commit
38dfa14c60
@ -26,7 +26,6 @@ from tempfile import NamedTemporaryFile
|
|||||||
|
|
||||||
import apparmor.config
|
import apparmor.config
|
||||||
import apparmor.logparser
|
import apparmor.logparser
|
||||||
import apparmor.rules as aarules
|
|
||||||
import apparmor.severity
|
import apparmor.severity
|
||||||
import apparmor.ui as aaui
|
import apparmor.ui as aaui
|
||||||
from apparmor.aare import AARE
|
from apparmor.aare import AARE
|
||||||
@ -38,7 +37,7 @@ from apparmor.profile_storage import ProfileStorage, add_or_remove_flag, ruletyp
|
|||||||
from apparmor.regex import (
|
from apparmor.regex import (
|
||||||
RE_HAS_COMMENT_SPLIT, RE_PROFILE_CHANGE_HAT, RE_PROFILE_CONDITIONAL,
|
RE_HAS_COMMENT_SPLIT, RE_PROFILE_CHANGE_HAT, RE_PROFILE_CONDITIONAL,
|
||||||
RE_PROFILE_CONDITIONAL_BOOLEAN, RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_END,
|
RE_PROFILE_CONDITIONAL_BOOLEAN, RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_END,
|
||||||
RE_PROFILE_HAT_DEF, RE_PROFILE_PIVOT_ROOT, RE_PROFILE_START,
|
RE_PROFILE_HAT_DEF, RE_PROFILE_START,
|
||||||
RE_RULE_HAS_COMMA, parse_profile_start_line, re_match_include)
|
RE_RULE_HAS_COMMA, parse_profile_start_line, re_match_include)
|
||||||
from apparmor.rule.abi import AbiRule
|
from apparmor.rule.abi import AbiRule
|
||||||
from apparmor.rule.capability import CapabilityRule
|
from apparmor.rule.capability import CapabilityRule
|
||||||
@ -47,6 +46,7 @@ from apparmor.rule.dbus import DbusRule
|
|||||||
from apparmor.rule.file import FileRule
|
from apparmor.rule.file import FileRule
|
||||||
from apparmor.rule.include import IncludeRule
|
from apparmor.rule.include import IncludeRule
|
||||||
from apparmor.rule.network import NetworkRule
|
from apparmor.rule.network import NetworkRule
|
||||||
|
from apparmor.rule.pivot_root import PivotRootRule
|
||||||
from apparmor.rule.ptrace import PtraceRule
|
from apparmor.rule.ptrace import PtraceRule
|
||||||
from apparmor.rule.signal import SignalRule
|
from apparmor.rule.signal import SignalRule
|
||||||
from apparmor.rule.userns import UserNamespaceRule
|
from apparmor.rule.userns import UserNamespaceRule
|
||||||
@ -1731,6 +1731,14 @@ def collapse_log(hashlog, ignore_null_profiles=True):
|
|||||||
mount_event = MountRule(operation=operation, fstype=_fstype, options=_options, source=_source, dest=_dest)
|
mount_event = MountRule(operation=operation, fstype=_fstype, options=_options, source=_source, dest=_dest)
|
||||||
if not hat_exists or not is_known_rule(aa[profile][hat], 'mount', mount_event):
|
if not hat_exists or not is_known_rule(aa[profile][hat], 'mount', mount_event):
|
||||||
log_dict[aamode][final_name]['mount'].add(mount_event)
|
log_dict[aamode][final_name]['mount'].add(mount_event)
|
||||||
|
|
||||||
|
pivot_root = hashlog[aamode][full_profile]['pivot_root']
|
||||||
|
for oldroot in pivot_root.keys():
|
||||||
|
for newroot in pivot_root[oldroot]:
|
||||||
|
pivot_root_event = PivotRootRule(oldroot, newroot, PivotRootRule.ALL, log_event=True)
|
||||||
|
if not hat_exists or not is_known_rule(aa[profile][hat], 'pivot_root', pivot_root_event):
|
||||||
|
log_dict[aamode][final_name]['pivot_root'].add(pivot_root_event)
|
||||||
|
|
||||||
return log_dict
|
return log_dict
|
||||||
|
|
||||||
|
|
||||||
@ -1955,29 +1963,6 @@ def parse_profile_data(data, file, do_include, in_preamble):
|
|||||||
# Conditional Boolean defined
|
# Conditional Boolean defined
|
||||||
pass
|
pass
|
||||||
|
|
||||||
elif RE_PROFILE_PIVOT_ROOT.search(line):
|
|
||||||
matches = RE_PROFILE_PIVOT_ROOT.search(line).groups()
|
|
||||||
|
|
||||||
if not profile:
|
|
||||||
raise AppArmorException(_('Syntax Error: Unexpected pivot_root entry found in file: %(file)s line: %(line)s')
|
|
||||||
% {'file': file, 'line': lineno + 1})
|
|
||||||
|
|
||||||
audit = False
|
|
||||||
if matches[0]:
|
|
||||||
audit = True
|
|
||||||
allow = 'allow'
|
|
||||||
if matches[1] and matches[1].strip() == 'deny':
|
|
||||||
allow = 'deny'
|
|
||||||
pivot_root = matches[2].strip()
|
|
||||||
|
|
||||||
pivot_root_rule = parse_pivot_root_rule(pivot_root)
|
|
||||||
pivot_root_rule.audit = audit
|
|
||||||
pivot_root_rule.deny = (allow == 'deny')
|
|
||||||
|
|
||||||
pivot_root_rules = profile_data[profname][allow].get('pivot_root', [])
|
|
||||||
pivot_root_rules.append(pivot_root_rule)
|
|
||||||
profile_data[profname][allow]['pivot_root'] = pivot_root_rules
|
|
||||||
|
|
||||||
elif RE_PROFILE_CHANGE_HAT.search(line):
|
elif RE_PROFILE_CHANGE_HAT.search(line):
|
||||||
matches = RE_PROFILE_CHANGE_HAT.search(line).groups()
|
matches = RE_PROFILE_CHANGE_HAT.search(line).groups()
|
||||||
|
|
||||||
@ -2065,6 +2050,7 @@ def match_line_against_rule_classes(line, profile, file, lineno, in_preamble):
|
|||||||
'mqueue',
|
'mqueue',
|
||||||
'io_uring',
|
'io_uring',
|
||||||
'mount',
|
'mount',
|
||||||
|
'pivot_root',
|
||||||
'unix',
|
'unix',
|
||||||
):
|
):
|
||||||
|
|
||||||
@ -2117,11 +2103,6 @@ def split_to_merged(profile_data):
|
|||||||
return merged
|
return merged
|
||||||
|
|
||||||
|
|
||||||
def parse_pivot_root_rule(line):
|
|
||||||
# XXX Do real parsing here
|
|
||||||
return aarules.Raw_Pivot_Root_Rule(line)
|
|
||||||
|
|
||||||
|
|
||||||
def write_piece(profile_data, depth, name, nhat):
|
def write_piece(profile_data, depth, name, nhat):
|
||||||
pre = ' ' * depth
|
pre = ' ' * depth
|
||||||
data = []
|
data = []
|
||||||
|
@ -20,8 +20,6 @@ import termios
|
|||||||
import tty
|
import tty
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
|
|
||||||
import apparmor.rules as rules
|
|
||||||
|
|
||||||
DEBUGGING = False
|
DEBUGGING = False
|
||||||
|
|
||||||
|
|
||||||
@ -106,8 +104,6 @@ def recursive_print(src, dpth=0, key=''):
|
|||||||
for litem in src:
|
for litem in src:
|
||||||
recursive_print(litem, dpth + 1)
|
recursive_print(litem, dpth + 1)
|
||||||
print(tabs + "]")
|
print(tabs + "]")
|
||||||
elif isinstance(src, rules._Raw_Rule):
|
|
||||||
src.recursive_print(dpth)
|
|
||||||
else:
|
else:
|
||||||
if key:
|
if key:
|
||||||
print(tabs + '%s = %s' % (key, src))
|
print(tabs + '%s = %s' % (key, src))
|
||||||
|
@ -55,6 +55,7 @@ class ReadLog:
|
|||||||
'exec': hasher(),
|
'exec': hasher(),
|
||||||
'network': hasher(),
|
'network': hasher(),
|
||||||
'path': hasher(),
|
'path': hasher(),
|
||||||
|
'pivot_root': hasher(),
|
||||||
'ptrace': hasher(),
|
'ptrace': hasher(),
|
||||||
'signal': hasher(),
|
'signal': hasher(),
|
||||||
'userns': hasher(),
|
'userns': hasher(),
|
||||||
@ -118,6 +119,8 @@ class ReadLog:
|
|||||||
ev['peer'] = event.peer
|
ev['peer'] = event.peer
|
||||||
elif ev['operation'] and ev['operation'] == 'ptrace':
|
elif ev['operation'] and ev['operation'] == 'ptrace':
|
||||||
ev['peer'] = event.peer
|
ev['peer'] = event.peer
|
||||||
|
elif ev['operation'] and ev['operation'] == 'pivotroot':
|
||||||
|
ev['src_name'] = event.src_name
|
||||||
elif ev['operation'] and ev['operation'] == 'mount':
|
elif ev['operation'] and ev['operation'] == 'mount':
|
||||||
ev['flags'] = event.flags
|
ev['flags'] = event.flags
|
||||||
ev['fs_type'] = event.fs_type
|
ev['fs_type'] = event.fs_type
|
||||||
@ -238,6 +241,10 @@ class ReadLog:
|
|||||||
self.hashlog[aamode][full_profile]['mount'][e['operation']][e['flags']][e['fs_type']][e['name']][None] = True
|
self.hashlog[aamode][full_profile]['mount'][e['operation']][e['flags']][e['fs_type']][e['name']][None] = True
|
||||||
return
|
return
|
||||||
|
|
||||||
|
elif e['operation'] and e['operation'] == 'pivotroot':
|
||||||
|
# TODO: can the log contain the target profile?
|
||||||
|
self.hashlog[aamode][full_profile]['pivot_root'][e['src_name']][e['name']] = True
|
||||||
|
|
||||||
elif e['class'] and e['class'] == 'net' and e['family'] and e['family'] == 'unix':
|
elif e['class'] and e['class'] == 'net' and e['family'] and e['family'] == 'unix':
|
||||||
rule = (e['sock_type'], None) # Protocol is not supported yet.
|
rule = (e['sock_type'], None) # Protocol is not supported yet.
|
||||||
local = (e['addr'], None, e['attr'], None)
|
local = (e['addr'], None, e['attr'], None)
|
||||||
|
@ -32,6 +32,7 @@ from apparmor.rule.userns import UserNamespaceRule, UserNamespaceRuleset
|
|||||||
from apparmor.rule.mqueue import MessageQueueRule, MessageQueueRuleset
|
from apparmor.rule.mqueue import MessageQueueRule, MessageQueueRuleset
|
||||||
from apparmor.rule.io_uring import IOUringRule, IOUringRuleset
|
from apparmor.rule.io_uring import IOUringRule, IOUringRuleset
|
||||||
from apparmor.rule.mount import MountRule, MountRuleset
|
from apparmor.rule.mount import MountRule, MountRuleset
|
||||||
|
from apparmor.rule.pivot_root import PivotRootRule, PivotRootRuleset
|
||||||
from apparmor.rule.unix import UnixRule, UnixRuleset
|
from apparmor.rule.unix import UnixRule, UnixRuleset
|
||||||
|
|
||||||
from apparmor.translations import init_translation
|
from apparmor.translations import init_translation
|
||||||
@ -54,8 +55,8 @@ ruletypes = {
|
|||||||
'mqueue': {'rule': MessageQueueRule, 'ruleset': MessageQueueRuleset},
|
'mqueue': {'rule': MessageQueueRule, 'ruleset': MessageQueueRuleset},
|
||||||
'io_uring': {'rule': IOUringRule, 'ruleset': IOUringRuleset},
|
'io_uring': {'rule': IOUringRule, 'ruleset': IOUringRuleset},
|
||||||
'mount': {'rule': MountRule, 'ruleset': MountRuleset},
|
'mount': {'rule': MountRule, 'ruleset': MountRuleset},
|
||||||
|
'pivot_root': {'rule': PivotRootRule, 'ruleset': PivotRootRuleset},
|
||||||
'unix': {'rule': UnixRule, 'ruleset': UnixRuleset},
|
'unix': {'rule': UnixRule, 'ruleset': UnixRuleset},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -87,13 +88,6 @@ class ProfileStorage:
|
|||||||
data['is_hat'] = False # profile or hat?
|
data['is_hat'] = False # profile or hat?
|
||||||
data['hat_keyword'] = False # True for 'hat foo', False for '^foo'
|
data['hat_keyword'] = False # True for 'hat foo', False for '^foo'
|
||||||
|
|
||||||
data['allow'] = dict()
|
|
||||||
data['deny'] = dict()
|
|
||||||
|
|
||||||
# pivot_root has a .get() fallback to list() - initialize it nevertheless
|
|
||||||
data['allow']['pivot_root'] = []
|
|
||||||
data['deny']['pivot_root'] = []
|
|
||||||
|
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
@ -184,11 +178,6 @@ class ProfileStorage:
|
|||||||
Note that the profile header and the closing "}" are _not_ included.
|
Note that the profile header and the closing "}" are _not_ included.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# "old" write functions for rule types not implemented as *Rule class yet
|
|
||||||
write_functions = {
|
|
||||||
'pivot_root': write_pivot_root,
|
|
||||||
}
|
|
||||||
|
|
||||||
write_order = [
|
write_order = [
|
||||||
'abi',
|
'abi',
|
||||||
'inc_ie',
|
'inc_ie',
|
||||||
@ -211,9 +200,6 @@ class ProfileStorage:
|
|||||||
data = []
|
data = []
|
||||||
|
|
||||||
for ruletype in write_order:
|
for ruletype in write_order:
|
||||||
if write_functions.get(ruletype):
|
|
||||||
data.extend(write_functions[ruletype](self.data, depth))
|
|
||||||
else:
|
|
||||||
data.extend(self.data[ruletype].get_clean(depth))
|
data.extend(self.data[ruletype].get_clean(depth))
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@ -310,23 +296,3 @@ def var_transform(ref):
|
|||||||
value = '""'
|
value = '""'
|
||||||
data.append(quote_if_needed(value))
|
data.append(quote_if_needed(value))
|
||||||
return ' '.join(data)
|
return ' '.join(data)
|
||||||
|
|
||||||
|
|
||||||
def write_pivot_root_rules(prof_data, depth, allow):
|
|
||||||
pre = ' ' * depth
|
|
||||||
data = []
|
|
||||||
|
|
||||||
# no pivot_root rules, so return
|
|
||||||
if not prof_data[allow].get('pivot_root', False):
|
|
||||||
return data
|
|
||||||
|
|
||||||
for pivot_root_rule in prof_data[allow]['pivot_root']:
|
|
||||||
data.append('%s%s' % (pre, pivot_root_rule.serialize()))
|
|
||||||
data.append('')
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def write_pivot_root(prof_data, depth):
|
|
||||||
data = write_pivot_root_rules(prof_data, depth, 'deny')
|
|
||||||
data.extend(write_pivot_root_rules(prof_data, depth, 'allow'))
|
|
||||||
return data
|
|
||||||
|
@ -50,7 +50,7 @@ RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + r'(dbus\s*,|dbus(?P<details>\s+[^#]
|
|||||||
RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + r'((?P<operation>mount|remount|umount|unmount)(?P<details>\s+[^#]*)?\s*,)' + RE_EOL)
|
RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + r'((?P<operation>mount|remount|umount|unmount)(?P<details>\s+[^#]*)?\s*,)' + RE_EOL)
|
||||||
RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + r'(signal\s*,|signal(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + r'(signal\s*,|signal(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
||||||
RE_PROFILE_PTRACE = re.compile(RE_AUDIT_DENY + r'(ptrace\s*,|ptrace(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
RE_PROFILE_PTRACE = re.compile(RE_AUDIT_DENY + r'(ptrace\s*,|ptrace(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
||||||
RE_PROFILE_PIVOT_ROOT = re.compile(RE_AUDIT_DENY + r'(pivot_root\s*,|pivot_root\s+[^#]*\s*,)' + RE_EOL)
|
RE_PROFILE_PIVOT_ROOT = re.compile(RE_AUDIT_DENY + r'(pivot_root\s*,|pivot_root(?P<details>\s+[^#]*),)' + RE_EOL)
|
||||||
RE_PROFILE_UNIX = re.compile(RE_AUDIT_DENY + r'(unix\s*,|unix(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
RE_PROFILE_UNIX = re.compile(RE_AUDIT_DENY + r'(unix\s*,|unix(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
||||||
RE_PROFILE_USERNS = re.compile(RE_AUDIT_DENY + r'(userns\s*,|userns(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
RE_PROFILE_USERNS = re.compile(RE_AUDIT_DENY + r'(userns\s*,|userns(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
||||||
RE_PROFILE_MQUEUE = re.compile(RE_AUDIT_DENY + r'(mqueue\s*,|mqueue(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
RE_PROFILE_MQUEUE = re.compile(RE_AUDIT_DENY + r'(mqueue\s*,|mqueue(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
||||||
|
198
utils/apparmor/rule/pivot_root.py
Normal file
198
utils/apparmor/rule/pivot_root.py
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2024 Christian Boltz
|
||||||
|
#
|
||||||
|
# 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 re
|
||||||
|
|
||||||
|
from apparmor.common import AppArmorBug, AppArmorException
|
||||||
|
|
||||||
|
from apparmor.aare import AARE
|
||||||
|
from apparmor.regex import RE_PROFILE_PIVOT_ROOT, RE_PROFILE_NAME, RE_PROFILE_PATH_OR_VAR, strip_quotes
|
||||||
|
from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers, logprof_value_or_all, quote_if_needed
|
||||||
|
|
||||||
|
from apparmor.translations import init_translation
|
||||||
|
|
||||||
|
_ = init_translation()
|
||||||
|
|
||||||
|
RE_PIVOT_ROOT_DETAILS = re.compile(
|
||||||
|
r'^\s*'
|
||||||
|
+ r'(\s+oldroot=' + RE_PROFILE_PATH_OR_VAR % 'oldroot' + r')?' # noqa: E221
|
||||||
|
+ r'(\s+' + RE_PROFILE_PATH_OR_VAR % 'newroot' + r')?' # noqa: E221
|
||||||
|
+ r'(\s+->\s+' + RE_PROFILE_NAME % 'profile_name' + r')?' # noqa: E221
|
||||||
|
+ r'\s*$'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootRule(BaseRule):
|
||||||
|
'''Class to handle and store a single pivot_root rule'''
|
||||||
|
|
||||||
|
# Nothing external should reference this class, all external users
|
||||||
|
# should reference the class field PivotRootRule.ALL
|
||||||
|
class __PivotRootAll(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
ALL = __PivotRootAll
|
||||||
|
|
||||||
|
rule_name = 'pivot_root'
|
||||||
|
_match_re = RE_PROFILE_PIVOT_ROOT
|
||||||
|
|
||||||
|
# PIVOT ROOT RULE = [ QUALIFIERS ] pivot_root [ oldroot=OLD PUT FILEGLOB ] [ NEW ROOT FILEGLOB ] [ ’->’ PROFILE NAME ]
|
||||||
|
def __init__(self, oldroot, newroot, profile_name, audit=False, deny=False, allow_keyword=False, comment='', log_event=None):
|
||||||
|
|
||||||
|
super().__init__(audit=audit, deny=deny,
|
||||||
|
allow_keyword=allow_keyword,
|
||||||
|
comment=comment,
|
||||||
|
log_event=log_event)
|
||||||
|
|
||||||
|
self.oldroot, self.all_oldroots = self._aare_or_all(oldroot, 'oldroot', True, log_event) # noqa: E221
|
||||||
|
self.newroot, self.all_newroots = self._aare_or_all(newroot, 'newroot', True, log_event) # noqa: E221
|
||||||
|
self.profile_name, self.all_profile_names = self._aare_or_all(profile_name, 'profile_name', False, log_event) # noqa: E221
|
||||||
|
|
||||||
|
self.can_glob = not self.all_newroots
|
||||||
|
self.can_glob_ext = False
|
||||||
|
self.can_edit = not self.all_newroots
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _create_instance(cls, raw_rule, matches):
|
||||||
|
'''parse raw_rule and return instance of this class'''
|
||||||
|
|
||||||
|
audit, deny, allow_keyword, comment = parse_modifiers(matches)
|
||||||
|
|
||||||
|
rule_details = ''
|
||||||
|
if matches.group('details'):
|
||||||
|
rule_details = matches.group('details')
|
||||||
|
|
||||||
|
parsed = RE_PIVOT_ROOT_DETAILS.search(rule_details)
|
||||||
|
|
||||||
|
if not parsed:
|
||||||
|
raise AppArmorException('Cannot parse pivot_root rule ' + raw_rule)
|
||||||
|
|
||||||
|
r = parsed.groupdict()
|
||||||
|
|
||||||
|
if r['oldroot']:
|
||||||
|
oldroot = strip_quotes(r['oldroot'])
|
||||||
|
else:
|
||||||
|
oldroot = cls.ALL
|
||||||
|
|
||||||
|
if r['newroot']:
|
||||||
|
newroot = strip_quotes(r['newroot'])
|
||||||
|
else:
|
||||||
|
newroot = cls.ALL
|
||||||
|
|
||||||
|
if r['profile_name']:
|
||||||
|
profile_name = strip_quotes(r['profile_name'])
|
||||||
|
else:
|
||||||
|
profile_name = cls.ALL
|
||||||
|
|
||||||
|
else:
|
||||||
|
oldroot = cls.ALL
|
||||||
|
newroot = cls.ALL
|
||||||
|
profile_name = cls.ALL
|
||||||
|
|
||||||
|
return cls(oldroot=oldroot, newroot=newroot, profile_name=profile_name,
|
||||||
|
audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
|
||||||
|
|
||||||
|
def get_clean(self, depth=0):
|
||||||
|
space = ' ' * depth
|
||||||
|
|
||||||
|
if self.all_oldroots:
|
||||||
|
oldroot = ''
|
||||||
|
elif self.oldroot:
|
||||||
|
oldroot = ' oldroot=' + quote_if_needed(self.oldroot.regex)
|
||||||
|
else:
|
||||||
|
raise AppArmorBug('Empty oldroot in pivot_root rule')
|
||||||
|
|
||||||
|
if self.all_newroots:
|
||||||
|
newroot = ''
|
||||||
|
elif self.newroot:
|
||||||
|
newroot = ' ' + quote_if_needed(self.newroot.regex)
|
||||||
|
else:
|
||||||
|
raise AppArmorBug('Empty newroot in pivot_root rule')
|
||||||
|
|
||||||
|
if self.all_profile_names:
|
||||||
|
profile_name = ''
|
||||||
|
elif self.profile_name:
|
||||||
|
profile_name = ' -> ' + quote_if_needed(self.profile_name.regex)
|
||||||
|
else:
|
||||||
|
raise AppArmorBug('Empty profile_name in pivot_root rule')
|
||||||
|
|
||||||
|
return f'{space}{self.modifiers_str()}pivot_root{oldroot}{newroot}{profile_name},{self.comment}'
|
||||||
|
|
||||||
|
def _is_covered_localvars(self, other_rule):
|
||||||
|
if not self._is_covered_aare(self.oldroot, self.all_oldroots, other_rule.oldroot, other_rule.all_oldroots, 'oldroot'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._is_covered_aare(self.newroot, self.all_newroots, other_rule.newroot, other_rule.all_newroots, 'newroot'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._is_covered_aare(self.profile_name, self.all_profile_names, other_rule.profile_name, other_rule.all_profile_names, 'profile_name'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# still here? -> then it is covered
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _is_equal_localvars(self, rule_obj, strict):
|
||||||
|
if not self._is_equal_aare(self.oldroot, self.all_oldroots, rule_obj.oldroot, rule_obj.all_oldroots, 'oldroot'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._is_equal_aare(self.newroot, self.all_newroots, rule_obj.newroot, rule_obj.all_newroots, 'newroot'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self._is_equal_aare(self.profile_name, self.all_profile_names, rule_obj.profile_name, rule_obj.all_profile_names, 'profile_name'):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def glob(self):
|
||||||
|
'''Change newroot path to next possible glob'''
|
||||||
|
if self.all_newroots:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.newroot = self.newroot.glob_path()
|
||||||
|
self.raw_rule = None
|
||||||
|
|
||||||
|
def edit_header(self):
|
||||||
|
if self.all_newroots:
|
||||||
|
raise AppArmorBug('Attemp to edit pivot_root rule without newroot limitations')
|
||||||
|
|
||||||
|
return (_('Enter new newroot: '), self.newroot.regex)
|
||||||
|
|
||||||
|
def validate_edit(self, newpath):
|
||||||
|
if self.all_newroots:
|
||||||
|
raise AppArmorBug('Attemp to edit pivot_root rule without newroot limitations')
|
||||||
|
|
||||||
|
newpath = AARE(newpath, True) # might raise AppArmorException if the new path doesn't start with / or a variable
|
||||||
|
return newpath.match(self.newroot)
|
||||||
|
|
||||||
|
def store_edit(self, newpath):
|
||||||
|
if self.all_newroots:
|
||||||
|
raise AppArmorBug('Attemp to edit pivot_root rule without newroot limitations')
|
||||||
|
|
||||||
|
self.newroot = AARE(newpath, True) # might raise AppArmorException if the new path doesn't start with / or a variable
|
||||||
|
self.raw_rule = None
|
||||||
|
|
||||||
|
def _logprof_header_localvars(self):
|
||||||
|
|
||||||
|
oldroot = logprof_value_or_all(self.oldroot, self.all_oldroots)
|
||||||
|
newroot = logprof_value_or_all(self.newroot, self.all_newroots)
|
||||||
|
profile_name = logprof_value_or_all(self.profile_name, self.all_profile_names)
|
||||||
|
|
||||||
|
return (
|
||||||
|
_('Old root'), oldroot,
|
||||||
|
_('New root'), newroot,
|
||||||
|
_('Target profile'), profile_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootRuleset(BaseRuleset):
|
||||||
|
'''Class to handle and store a collection of pivot_root rules'''
|
@ -1,33 +0,0 @@
|
|||||||
# ------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Copyright (C) 2014 Canonical Ltd.
|
|
||||||
#
|
|
||||||
# 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 published by the Free Software Foundation.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
class _Raw_Rule:
|
|
||||||
audit = False
|
|
||||||
deny = False
|
|
||||||
|
|
||||||
def __init__(self, rule):
|
|
||||||
self.rule = rule
|
|
||||||
|
|
||||||
def serialize(self):
|
|
||||||
return "%s%s%s" % ('audit ' if self.audit else '',
|
|
||||||
'deny ' if self.deny else '',
|
|
||||||
self.rule)
|
|
||||||
|
|
||||||
def recursive_print(self, depth):
|
|
||||||
tabs = ' ' * depth * 4
|
|
||||||
print('%s[%s]' % (tabs, type(self).__name__))
|
|
||||||
tabs += ' ' * 4
|
|
||||||
print('%saudit = %s' % (tabs, self.audit))
|
|
||||||
print('%sdeny = %s' % (tabs, self.deny))
|
|
||||||
print('%sraw rule = %s' % (tabs, self.rule))
|
|
||||||
|
|
||||||
|
|
||||||
class Raw_Pivot_Root_Rule(_Raw_Rule):
|
|
||||||
pass
|
|
@ -21,7 +21,7 @@ COMMONDIR=../../common/
|
|||||||
include $(COMMONDIR)/Make.rules
|
include $(COMMONDIR)/Make.rules
|
||||||
|
|
||||||
# files that don't have 100% test coverage
|
# files that don't have 100% test coverage
|
||||||
INCOMPLETE_COVERAGE=libraries/libapparmor/swig/python/.*/LibAppArmor/LibAppArmor.py|utils/aa-logprof|utils/apparmor/aa.py|utils/apparmor/common.py|utils/apparmor/config.py|utils/apparmor/easyprof.py|utils/apparmor/fail.py|utils/apparmor/logparser.py|utils/apparmor/profile_storage.py|utils/apparmor/rules.py|utils/apparmor/ui.py|minitools_test.py
|
INCOMPLETE_COVERAGE=libraries/libapparmor/swig/python/.*/LibAppArmor/LibAppArmor.py|utils/aa-logprof|utils/apparmor/aa.py|utils/apparmor/common.py|utils/apparmor/config.py|utils/apparmor/easyprof.py|utils/apparmor/fail.py|utils/apparmor/logparser.py|utils/apparmor/ui.py|minitools_test.py
|
||||||
|
|
||||||
|
|
||||||
ifdef USE_SYSTEM
|
ifdef USE_SYSTEM
|
||||||
|
@ -153,8 +153,6 @@ log_to_skip = [
|
|||||||
|
|
||||||
# tests that do not produce the expected profile (checked with assertNotEqual)
|
# tests that do not produce the expected profile (checked with assertNotEqual)
|
||||||
log_to_profile_known_failures = [
|
log_to_profile_known_failures = [
|
||||||
'testcase_pivotroot_01', # pivot_rot not yet supported in logparser
|
|
||||||
|
|
||||||
# exec events
|
# exec events
|
||||||
'testcase01',
|
'testcase01',
|
||||||
'testcase12',
|
'testcase12',
|
||||||
@ -175,7 +173,6 @@ log_to_profile_skip = [
|
|||||||
# tests that cause an empty log
|
# tests that cause an empty log
|
||||||
log_to_profile_known_empty_log = [
|
log_to_profile_known_empty_log = [
|
||||||
'change_onexec_lp1648143', # change_onexec not supported in logparser.py yet (and the log is about "no new privs" error)
|
'change_onexec_lp1648143', # change_onexec not supported in logparser.py yet (and the log is about "no new privs" error)
|
||||||
'testcase_pivotroot_01', # pivotroot not yet supported in logparser
|
|
||||||
'ptrace_garbage_lp1689667_1', # no denied= in log
|
'ptrace_garbage_lp1689667_1', # no denied= in log
|
||||||
'ptrace_no_denied_mask', # no denied= in log
|
'ptrace_no_denied_mask', # no denied= in log
|
||||||
'unconfined-change_hat', # unconfined trying to change_hat, which isn't allowed
|
'unconfined-change_hat', # unconfined trying to change_hat, which isn't allowed
|
||||||
|
572
utils/test/test-pivot_root.py
Normal file
572
utils/test/test-pivot_root.py
Normal file
@ -0,0 +1,572 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Copyright (C) 2015 Christian Boltz <apparmor@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 apparmor.common import AppArmorBug, AppArmorException
|
||||||
|
from apparmor.logparser import ReadLog
|
||||||
|
from apparmor.aare import AARE
|
||||||
|
from apparmor.rule.pivot_root import PivotRootRule, PivotRootRuleset
|
||||||
|
from apparmor.translations import init_translation
|
||||||
|
from common_test import AATest, setup_all_loops
|
||||||
|
|
||||||
|
_ = init_translation()
|
||||||
|
|
||||||
|
exp = namedtuple(
|
||||||
|
'exp', ('audit', 'allow_keyword', 'deny', 'comment', 'oldroot', 'all_oldroots', 'newroot',
|
||||||
|
'all_newroots', 'profile_name', 'all_profile_names'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# # --- tests for single PivotRootRule --- #
|
||||||
|
|
||||||
|
class PivotRootTest(AATest):
|
||||||
|
def _compare_obj(self, obj, expected):
|
||||||
|
self.assertEqual(expected.audit, obj.audit)
|
||||||
|
self.assertEqual(expected.allow_keyword, obj.allow_keyword)
|
||||||
|
self.assertEqual(expected.deny, obj.deny)
|
||||||
|
self.assertEqual(expected.comment, obj.comment)
|
||||||
|
|
||||||
|
if type(obj.oldroot) is AARE:
|
||||||
|
self.assertEqual(expected.oldroot, obj.oldroot.regex)
|
||||||
|
else:
|
||||||
|
self.assertEqual(expected.oldroot, obj.oldroot)
|
||||||
|
|
||||||
|
self.assertEqual(expected.all_oldroots, obj.all_oldroots)
|
||||||
|
|
||||||
|
if type(obj.newroot) is AARE:
|
||||||
|
self.assertEqual(expected.newroot, obj.newroot.regex)
|
||||||
|
else:
|
||||||
|
self.assertEqual(expected.newroot, obj.newroot)
|
||||||
|
|
||||||
|
self.assertEqual(expected.all_newroots, obj.all_newroots)
|
||||||
|
|
||||||
|
if type(obj.profile_name) is AARE:
|
||||||
|
self.assertEqual(expected.profile_name, obj.profile_name.regex)
|
||||||
|
else:
|
||||||
|
self.assertEqual(expected.profile_name, obj.profile_name)
|
||||||
|
|
||||||
|
self.assertEqual(expected.all_profile_names, obj.all_profile_names)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootTestParse(PivotRootTest):
|
||||||
|
tests = (
|
||||||
|
# PivotRootRule object audit allow deny comment oldroot all? newroot all? profile_name all?
|
||||||
|
('pivot_root,', exp(False, False, False, '', None, True, None, True, None, True)),
|
||||||
|
('pivot_root oldroot=/oldroot, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, None, True, None, True)),
|
||||||
|
('pivot_root oldroot=/oldroot /new/root, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, '/new/root', False, None, True)),
|
||||||
|
('pivot_root oldroot=/oldroot /new/root -> targetprof, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, '/new/root', False, 'targetprof', False)),
|
||||||
|
('pivot_root oldroot=/oldroot -> targetprof, # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, None, True, 'targetprof', False)),
|
||||||
|
('pivot_root /new/root, # cmt', exp(False, False, False, ' # cmt', None, True, '/new/root', False, None, True)),
|
||||||
|
('pivot_root /new/root -> targetprof, # cmt', exp(False, False, False, ' # cmt', None, True, '/new/root', False, 'targetprof', False)),
|
||||||
|
('pivot_root -> targetprof, # cmt', exp(False, False, False, ' # cmt', None, True, None, True, 'targetprof', False)),
|
||||||
|
('pivot_root oldroot="/oldroot", # cmt', exp(False, False, False, ' # cmt', '/oldroot', False, None, True, None, True)),
|
||||||
|
('pivot_root "/new/root", # cmt', exp(False, False, False, ' # cmt', None, True, '/new/root', False, None, True)),
|
||||||
|
('pivot_root -> "targetprof", # cmt', exp(False, False, False, ' # cmt', None, True, None, True, 'targetprof', False)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, rawrule, expected):
|
||||||
|
self.assertTrue(PivotRootRule.match(rawrule))
|
||||||
|
obj = PivotRootRule.create_instance(rawrule)
|
||||||
|
self.assertEqual(rawrule.strip(), obj.raw_rule)
|
||||||
|
self._compare_obj(obj, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootTestParseInvalid(PivotRootTest):
|
||||||
|
tests = (
|
||||||
|
('pivot_root foo,', AppArmorException),
|
||||||
|
('pivot_root foo bar,', AppArmorException),
|
||||||
|
('pivot_root oldroot= ,', AppArmorException),
|
||||||
|
('pivot_root -> ,', AppArmorException),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, rawrule, expected):
|
||||||
|
self.assertTrue(PivotRootRule.match(rawrule)) # the above invalid rules still match the main regex!
|
||||||
|
with self.assertRaises(expected):
|
||||||
|
PivotRootRule.create_instance(rawrule)
|
||||||
|
|
||||||
|
def test_invalid_rule_name(self):
|
||||||
|
self.assertFalse(PivotRootRule.match('pivot_rootbeer,'))
|
||||||
|
with self.assertRaises(AppArmorException):
|
||||||
|
PivotRootRule.create_instance('pivot_rootbeer,')
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootTestParseFromLog(PivotRootTest):
|
||||||
|
def test_pivot_root_from_log(self):
|
||||||
|
parser = ReadLog('', '', '')
|
||||||
|
event = 'type=AVC msg=audit(1409700678.384:547594): apparmor="DENIED" operation="pivotroot" profile="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/pivot_root" name="/tmp/sdtest.21082-7446-EeefO6/new_root/" pid=21162 comm="pivot_root" srcname="/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/"'
|
||||||
|
|
||||||
|
parsed_event = parser.parse_event(event)
|
||||||
|
|
||||||
|
self.assertEqual(parsed_event, {
|
||||||
|
'request_mask': None,
|
||||||
|
'denied_mask': None,
|
||||||
|
'error_code': 0,
|
||||||
|
'magic_token': 0,
|
||||||
|
'parent': 0,
|
||||||
|
'profile': '/home/ubuntu/bzr/apparmor/tests/regression/apparmor/pivot_root',
|
||||||
|
'operation': 'pivotroot',
|
||||||
|
'resource': None,
|
||||||
|
'info': None,
|
||||||
|
'aamode': 'REJECTING',
|
||||||
|
'time': 1409700678,
|
||||||
|
'active_hat': None,
|
||||||
|
'pid': 21162,
|
||||||
|
'task': 0,
|
||||||
|
'attr': None,
|
||||||
|
'name2': None,
|
||||||
|
'src_name': '/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/',
|
||||||
|
'name': '/tmp/sdtest.21082-7446-EeefO6/new_root/',
|
||||||
|
'family': None,
|
||||||
|
'protocol': None,
|
||||||
|
'sock_type': None,
|
||||||
|
'class': None,
|
||||||
|
})
|
||||||
|
|
||||||
|
obj = PivotRootRule(parsed_event['src_name'], parsed_event['name'], PivotRootRule.ALL, log_event=parsed_event)
|
||||||
|
|
||||||
|
# audit allow deny comment oldroot all? newroot all? target all?
|
||||||
|
expected = exp(False, False, False, '', '/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/', False, '/tmp/sdtest.21082-7446-EeefO6/new_root/', False, None, True)
|
||||||
|
|
||||||
|
self._compare_obj(obj, expected)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
obj.get_raw(1),
|
||||||
|
' pivot_root oldroot=/tmp/sdtest.21082-7446-EeefO6/new_root/put_old/ /tmp/sdtest.21082-7446-EeefO6/new_root/,')
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootFromInit(PivotRootTest):
|
||||||
|
tests = (
|
||||||
|
# PivotRootRule object audit allow deny comment oldroot all? newroot all? profile_name all?
|
||||||
|
(PivotRootRule('/oldroot', '/new/root', 'some_profile', deny=True), exp(False, False, True, '', '/oldroot', False, '/new/root', False, 'some_profile', False)),
|
||||||
|
(PivotRootRule('/oldroot', '/new/root', PivotRootRule.ALL, deny=True), exp(False, False, True, '', '/oldroot', False, '/new/root', False, None, True)),
|
||||||
|
(PivotRootRule('/oldroot', PivotRootRule.ALL, '/someprofile', deny=True), exp(False, False, True, '', '/oldroot', False, None, True, '/someprofile', False)),
|
||||||
|
(PivotRootRule(PivotRootRule.ALL, '/new/root', '/someprofile', deny=True), exp(False, False, True, '', None, True, '/new/root', False, '/someprofile', False)),
|
||||||
|
(PivotRootRule('/oldroot', PivotRootRule.ALL, PivotRootRule.ALL, deny=True), exp(False, False, True, '', '/oldroot', False, None, True, None, True)),
|
||||||
|
(PivotRootRule(PivotRootRule.ALL, '/new/root', PivotRootRule.ALL, deny=True), exp(False, False, True, '', None, True, '/new/root', False, None, True)),
|
||||||
|
(PivotRootRule(PivotRootRule.ALL, PivotRootRule.ALL, 'some_profile', deny=True), exp(False, False, True, '', None, True, None, True, 'some_profile', False)),
|
||||||
|
(PivotRootRule(PivotRootRule.ALL, PivotRootRule.ALL, PivotRootRule.ALL, deny=True), exp(False, False, True, '', None, True, None, True, None, True)),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, obj, expected):
|
||||||
|
self._compare_obj(obj, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidPivotRootInit(AATest):
|
||||||
|
tests = (
|
||||||
|
# (init params, expected exception)
|
||||||
|
(('', '/foo', 'bar'), AppArmorBug), # empty oldroot
|
||||||
|
(('/old', '', 'bar'), AppArmorBug), # empty newroot
|
||||||
|
(('/old', '/foo', '' ), AppArmorBug), # empty targetprof # noqa: E202
|
||||||
|
|
||||||
|
(('old', '/foo', 'bar'), AppArmorException), # oldroot is not a path
|
||||||
|
(('/old', 'foo', 'bar'), AppArmorException), # newroot is not a path
|
||||||
|
|
||||||
|
|
||||||
|
((None, '/foo', 'bar'), AppArmorBug), # wrong type
|
||||||
|
(('/old', None, 'bar'), AppArmorBug), #
|
||||||
|
(('/old', '/foo', None ), AppArmorBug), # noqa: E202
|
||||||
|
|
||||||
|
((dict(), '/foo', 'bar'), AppArmorBug), # wrong type
|
||||||
|
(('/old', dict(), 'bar'), AppArmorBug), #
|
||||||
|
(('/old', '/foo', dict()), AppArmorBug), #
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
with self.assertRaises(expected):
|
||||||
|
PivotRootRule(*params)
|
||||||
|
|
||||||
|
def test_missing_params_1(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PivotRootRule()
|
||||||
|
|
||||||
|
def test_missing_params_2(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PivotRootRule('/foo')
|
||||||
|
|
||||||
|
def test_missing_params_3(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
PivotRootRule('/foo', '/bar')
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidPivotRootTest(AATest):
|
||||||
|
def _check_invalid_rawrule(self, rawrule):
|
||||||
|
obj = None
|
||||||
|
self.assertFalse(PivotRootRule.match(rawrule))
|
||||||
|
with self.assertRaises(AppArmorException):
|
||||||
|
obj = PivotRootRule.create_instance(rawrule)
|
||||||
|
|
||||||
|
self.assertIsNone(obj, 'PivotRootRule handed back an object unexpectedly')
|
||||||
|
|
||||||
|
def test_invalid_pivot_root_missing_comma(self):
|
||||||
|
self._check_invalid_rawrule('pivot_root') # missing comma
|
||||||
|
|
||||||
|
def test_invalid_non_PivotRootRule(self):
|
||||||
|
self._check_invalid_rawrule('dbus,') # not a pivot_root rule
|
||||||
|
|
||||||
|
def test_empty_data_1(self):
|
||||||
|
obj = PivotRootRule('/foo', '/bar', 'prof')
|
||||||
|
obj.oldroot = ''
|
||||||
|
# no oldroot set, and ALL not set
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
obj.get_clean(1)
|
||||||
|
|
||||||
|
def test_empty_data_2(self):
|
||||||
|
obj = PivotRootRule('/foo', '/bar', 'prof')
|
||||||
|
obj.newroot = ''
|
||||||
|
# no newroot set, and ALL not set
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
obj.get_clean(1)
|
||||||
|
|
||||||
|
def test_empty_data_3(self):
|
||||||
|
obj = PivotRootRule('/foo', '/bar', 'prof')
|
||||||
|
obj.profile_name = ''
|
||||||
|
# no profile_name set, and ALL not set
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
obj.get_clean(1)
|
||||||
|
|
||||||
|
|
||||||
|
class WritePivotRootTestAATest(AATest):
|
||||||
|
def _run_test(self, rawrule, expected):
|
||||||
|
self.assertTrue(PivotRootRule.match(rawrule))
|
||||||
|
obj = PivotRootRule.create_instance(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')
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
# raw rule clean rule
|
||||||
|
('pivot_root,', 'pivot_root,'),
|
||||||
|
(' pivot_root , # foo ', 'pivot_root, # foo'),
|
||||||
|
(' audit pivot_root /foo,', 'audit pivot_root /foo,'),
|
||||||
|
(' deny pivot_root /foo ,# foo bar', 'deny pivot_root /foo, # foo bar'),
|
||||||
|
(' deny pivot_root "/foo" ,# foo bar', 'deny pivot_root /foo, # foo bar'),
|
||||||
|
(' allow pivot_root ,# foo bar', 'allow pivot_root, # foo bar'),
|
||||||
|
(' pivot_root oldroot=/old , # foo ', 'pivot_root oldroot=/old, # foo'),
|
||||||
|
(' pivot_root oldroot="/old" , # foo ', 'pivot_root oldroot=/old, # foo'),
|
||||||
|
(' pivot_root oldroot=/old -> some_profile , ', 'pivot_root oldroot=/old -> some_profile,'),
|
||||||
|
(' pivot_root oldroot=/old /new -> some_profile , ', 'pivot_root oldroot=/old /new -> some_profile,'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_write_manually(self):
|
||||||
|
obj = PivotRootRule('/old', '/new', 'target', allow_keyword=True)
|
||||||
|
|
||||||
|
expected = ' allow pivot_root oldroot=/old /new -> target,'
|
||||||
|
|
||||||
|
self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule')
|
||||||
|
self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule')
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootCoveredTest(AATest):
|
||||||
|
def _run_test(self, param, expected):
|
||||||
|
obj = PivotRootRule.create_instance(self.rule)
|
||||||
|
check_obj = PivotRootRule.create_instance(param)
|
||||||
|
|
||||||
|
self.assertTrue(PivotRootRule.match(param))
|
||||||
|
|
||||||
|
self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected {}'.format(expected[0]))
|
||||||
|
self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected {}'.format(expected[1]))
|
||||||
|
|
||||||
|
self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected {}'.format(expected[2]))
|
||||||
|
self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected {}'.format(expected[3]))
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootCoveredTest_01(PivotRootCoveredTest):
|
||||||
|
rule = 'pivot_root /new,'
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
# rule equal strict equal covered covered exact
|
||||||
|
('pivot_root,', (False, False, False, False)),
|
||||||
|
('pivot_root /n*,', (False, False, False, False)),
|
||||||
|
('pivot_root oldroot=/old,', (False, False, False, False)),
|
||||||
|
('pivot_root /new,', (True, False, True, True)),
|
||||||
|
('pivot_root -> target,', (False, False, False, False)),
|
||||||
|
('pivot_root oldroot=/old /new,', (False, False, True, True)),
|
||||||
|
('pivot_root /new -> target,', (False, False, True, True)),
|
||||||
|
('pivot_root oldroot=/old -> target,', (False, False, False, False)),
|
||||||
|
('pivot_root oldroot=/old /new -> target,', (False, False, True, True)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootCoveredTest_02(PivotRootCoveredTest):
|
||||||
|
rule = 'audit pivot_root oldroot=/ol*,'
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
# rule equal strict equal covered covered exact
|
||||||
|
('audit pivot_root,', (False, False, False, False)),
|
||||||
|
('audit pivot_root oldroot=/ol*,', (True, True, True, True)),
|
||||||
|
('audit pivot_root oldroot=/old,', (False, False, True, True)),
|
||||||
|
('audit pivot_root /new,', (False, False, False, False)),
|
||||||
|
('audit pivot_root -> target,', (False, False, False, False)),
|
||||||
|
('audit pivot_root oldroot=/old /new,', (False, False, True, True)),
|
||||||
|
('audit pivot_root /new -> target,', (False, False, False, False)),
|
||||||
|
('audit pivot_root oldroot=/old -> target,', (False, False, True, True)), # covered exact - really?
|
||||||
|
('audit pivot_root oldroot=/old /new -> target,', (False, False, True, True)), # covered exact - really?
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootCoveredTest_03(PivotRootCoveredTest):
|
||||||
|
rule = 'pivot_root -> target,'
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
# rule equal strict equal covered covered exact
|
||||||
|
('pivot_root,', (False, False, False, False)),
|
||||||
|
('pivot_root oldroot=/ol*,', (False, False, False, False)),
|
||||||
|
('pivot_root oldroot=/old,', (False, False, False, False)),
|
||||||
|
('pivot_root /new,', (False, False, False, False)),
|
||||||
|
('pivot_root -> target,', (True, False, True, True)),
|
||||||
|
('pivot_root oldroot=/old /new,', (False, False, False, False)),
|
||||||
|
('pivot_root /new -> target,', (False, False, True, True)),
|
||||||
|
('pivot_root oldroot=/old -> target,', (False, False, True, True)),
|
||||||
|
('pivot_root oldroot=/old /new -> target,', (False, False, True, True)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootCoveredTest_04(PivotRootCoveredTest):
|
||||||
|
rule = 'deny pivot_root /foo,'
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
# rule equal strict equal covered covered exact
|
||||||
|
(' deny pivot_root /foo,', (True, True, True, True)),
|
||||||
|
('audit deny pivot_root /foo,', (False, False, False, False)),
|
||||||
|
(' pivot_root /foo,', (False, False, False, False)), # XXX should covered be true here?
|
||||||
|
(' deny pivot_root /bar,', (False, False, False, False)),
|
||||||
|
(' deny pivot_root,', (False, False, False, False)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootCoveredTest_Invalid(AATest):
|
||||||
|
# TODO: should this be detected?
|
||||||
|
# def test_borked_obj_is_covered_1(self):
|
||||||
|
# obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,')
|
||||||
|
|
||||||
|
# testobj = PivotRootRule('/old', '/foo', 'targetprof')
|
||||||
|
# testobj.oldrooot = None
|
||||||
|
|
||||||
|
# with self.assertRaises(AppArmorBug):
|
||||||
|
# obj.is_covered(testobj)
|
||||||
|
|
||||||
|
def test_borked_obj_is_covered_2(self):
|
||||||
|
obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,')
|
||||||
|
|
||||||
|
testobj = PivotRootRule('/old', '/foo', 'targetprof')
|
||||||
|
testobj.newroot = ''
|
||||||
|
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
obj.is_covered(testobj)
|
||||||
|
|
||||||
|
# def test_borked_obj_is_covered_3(self):
|
||||||
|
# TODO: should this be detected?
|
||||||
|
# obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,')
|
||||||
|
|
||||||
|
# testobj = PivotRootRule('/old', '/foo', 'targetprof')
|
||||||
|
# testobj.profile_name = ''
|
||||||
|
|
||||||
|
# with self.assertRaises(AppArmorBug):
|
||||||
|
# obj.is_covered(testobj)
|
||||||
|
|
||||||
|
def test_invalid_is_covered(self):
|
||||||
|
raw_rule = 'pivot_root oldroot=/old /new -> target,'
|
||||||
|
|
||||||
|
class SomeOtherClass(PivotRootRule):
|
||||||
|
pass
|
||||||
|
|
||||||
|
obj = PivotRootRule.create_instance(raw_rule)
|
||||||
|
testobj = SomeOtherClass.create_instance(raw_rule) # different type
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
obj.is_covered(testobj)
|
||||||
|
|
||||||
|
def test_invalid_is_equal_1(self):
|
||||||
|
raw_rule = 'pivot_root oldroot=/old /new -> target,'
|
||||||
|
|
||||||
|
class SomeOtherClass(PivotRootRule):
|
||||||
|
pass
|
||||||
|
|
||||||
|
obj = PivotRootRule.create_instance(raw_rule)
|
||||||
|
testobj = SomeOtherClass.create_instance(raw_rule) # different type
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
obj.is_equal(testobj)
|
||||||
|
|
||||||
|
# def test_invalid_is_equal_2(self):
|
||||||
|
# TODO: should this be detected?
|
||||||
|
# obj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,')
|
||||||
|
|
||||||
|
# testobj = PivotRootRule.create_instance('pivot_root oldroot=/old /new -> target,')
|
||||||
|
# testobj.all_oldroots = False # make testobj invalid (should trigger exception in _is_equal_aare())
|
||||||
|
|
||||||
|
# with self.assertRaises(AppArmorBug):
|
||||||
|
# obj.is_equal(testobj)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootLogprofHeaderTest(AATest):
|
||||||
|
tests = (
|
||||||
|
('pivot_root,', [ _('Old root'), _('ALL'), _('New root'), _('ALL'), _('Target profile'), _('ALL')]), # noqa: E201
|
||||||
|
('pivot_root oldroot=/old,', [ _('Old root'), '/old', _('New root'), _('ALL'), _('Target profile'), _('ALL')]), # noqa: E201
|
||||||
|
('deny pivot_root,', [_('Qualifier'), 'deny', _('Old root'), _('ALL'), _('New root'), _('ALL'), _('Target profile'), _('ALL')]),
|
||||||
|
('allow pivot_root oldroot=/old,', [_('Qualifier'), 'allow', _('Old root'), '/old', _('New root'), _('ALL'), _('Target profile'), _('ALL')]),
|
||||||
|
('audit pivot_root /new,', [_('Qualifier'), 'audit', _('Old root'), _('ALL'), _('New root'), '/new', _('Target profile'), _('ALL')]),
|
||||||
|
('audit deny pivot_root /new -> target,', [_('Qualifier'), 'audit deny', _('Old root'), _('ALL'), _('New root'), '/new', _('Target profile'), 'target']),
|
||||||
|
('pivot_root oldroot=/old /new -> target,', [ _('Old root'), '/old', _('New root'), '/new', _('Target profile'), 'target']), # noqa: E201
|
||||||
|
)
|
||||||
|
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
obj = PivotRootRule.create_instance(params)
|
||||||
|
self.assertEqual(obj.logprof_header(), expected)
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootEditHeaderTest(AATest):
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
rule_obj = PivotRootRule.create_instance(params)
|
||||||
|
self.assertEqual(rule_obj.can_edit, True)
|
||||||
|
prompt, path_to_edit = rule_obj.edit_header()
|
||||||
|
self.assertEqual(path_to_edit, expected)
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
('pivot_root oldroot=/old /foo/bar/baz -> target,', '/foo/bar/baz'),
|
||||||
|
('pivot_root /foo/**/baz,', '/foo/**/baz'),
|
||||||
|
('pivot_root /foo/** -> /bar,', '/foo/**'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_edit_header_bare_pivot_root(self):
|
||||||
|
rule_obj = PivotRootRule.create_instance('pivot_root,')
|
||||||
|
self.assertEqual(rule_obj.can_edit, False)
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
rule_obj.edit_header()
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootValidateAndStoreEditTest(AATest):
|
||||||
|
def _run_test(self, params, expected):
|
||||||
|
rule_obj = PivotRootRule('/old/', '/foo/bar/baz', 'target', log_event=True)
|
||||||
|
|
||||||
|
self.assertEqual(rule_obj.validate_edit(params), expected)
|
||||||
|
|
||||||
|
rule_obj.store_edit(params)
|
||||||
|
self.assertEqual(rule_obj.get_raw(), 'pivot_root oldroot=/old/ ' + params + ' -> target,')
|
||||||
|
|
||||||
|
tests = (
|
||||||
|
# edited path match
|
||||||
|
('/foo/bar/baz', True),
|
||||||
|
('/foo/bar/*', True),
|
||||||
|
('/foo/bar/???', True),
|
||||||
|
('/foo/xy**', False),
|
||||||
|
('/foo/bar/baz/', False),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_validate_not_a_path(self):
|
||||||
|
rule_obj = PivotRootRule.create_instance('pivot_root /foo/bar/baz,')
|
||||||
|
|
||||||
|
with self.assertRaises(AppArmorException):
|
||||||
|
rule_obj.validate_edit('foo/bar/baz')
|
||||||
|
|
||||||
|
with self.assertRaises(AppArmorException):
|
||||||
|
rule_obj.store_edit('foo/bar/baz')
|
||||||
|
|
||||||
|
def test_validate_edit_bare_pivot_root(self):
|
||||||
|
rule_obj = PivotRootRule.create_instance('pivot_root,')
|
||||||
|
self.assertEqual(rule_obj.can_edit, False)
|
||||||
|
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
rule_obj.validate_edit('/foo/bar')
|
||||||
|
|
||||||
|
with self.assertRaises(AppArmorBug):
|
||||||
|
rule_obj.store_edit('/foo/bar')
|
||||||
|
|
||||||
|
|
||||||
|
# --- tests for PivotRootRuleset --- #
|
||||||
|
|
||||||
|
class PivotRootRulesTest(AATest):
|
||||||
|
def test_empty_ruleset(self):
|
||||||
|
ruleset = PivotRootRuleset()
|
||||||
|
ruleset_2 = PivotRootRuleset()
|
||||||
|
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))
|
||||||
|
|
||||||
|
# test __repr__() for empty ruleset
|
||||||
|
self.assertEqual(str(ruleset), '<PivotRootRuleset (empty) />')
|
||||||
|
|
||||||
|
def test_ruleset_1(self):
|
||||||
|
ruleset = PivotRootRuleset()
|
||||||
|
rules = (
|
||||||
|
'pivot_root oldroot=/foo,',
|
||||||
|
'pivot_root /new,',
|
||||||
|
)
|
||||||
|
|
||||||
|
expected_raw = [
|
||||||
|
'pivot_root oldroot=/foo,',
|
||||||
|
'pivot_root /new,',
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
expected_clean = [
|
||||||
|
'pivot_root /new,',
|
||||||
|
'pivot_root oldroot=/foo,',
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
|
||||||
|
for rule in rules:
|
||||||
|
ruleset.add(PivotRootRule.create_instance(rule))
|
||||||
|
|
||||||
|
self.assertEqual(expected_raw, ruleset.get_raw())
|
||||||
|
self.assertEqual(expected_clean, ruleset.get_clean())
|
||||||
|
|
||||||
|
# test __repr__() for non-empty ruleset
|
||||||
|
self.assertEqual(
|
||||||
|
str(ruleset), '<PivotRootRuleset>\n pivot_root oldroot=/foo,\n pivot_root /new,\n</PivotRootRuleset>')
|
||||||
|
|
||||||
|
|
||||||
|
class PivotRootGlobTestAATest(AATest):
|
||||||
|
def test_glob(self):
|
||||||
|
glob_list = [(
|
||||||
|
'pivot_root /foo/bar,',
|
||||||
|
'pivot_root /foo/*,',
|
||||||
|
'pivot_root /**,',
|
||||||
|
)]
|
||||||
|
for globs in glob_list:
|
||||||
|
for i in range(len(globs) - 1):
|
||||||
|
rule = PivotRootRule.create_instance(globs[i])
|
||||||
|
rule.glob()
|
||||||
|
self.assertEqual(rule.get_clean(), globs[i + 1])
|
||||||
|
|
||||||
|
def test_glob_all(self):
|
||||||
|
glob_list = [(
|
||||||
|
'pivot_root,',
|
||||||
|
'pivot_root,',
|
||||||
|
)]
|
||||||
|
for globs in glob_list:
|
||||||
|
for i in range(len(globs) - 1):
|
||||||
|
rule = PivotRootRule.create_instance(globs[i])
|
||||||
|
rule.glob()
|
||||||
|
self.assertEqual(rule.get_clean(), globs[i + 1])
|
||||||
|
|
||||||
|
|
||||||
|
# def test_glob_ext(self):
|
||||||
|
# # rule = PivotRootRule.create_instance('pivot_root /foo/bar,')
|
||||||
|
# with self.assertRaises(NotImplementedError):
|
||||||
|
# # get_glob_ext is not available for pivot_root rules
|
||||||
|
# self.ruleset.get_glob_ext('pivot_root /foo,')
|
||||||
|
|
||||||
|
|
||||||
|
# class PivotRootDeleteTestAATest(AATest):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
setup_all_loops(__name__)
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main(verbosity=1)
|
@ -1,33 +0,0 @@
|
|||||||
#! /usr/bin/python3
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
#
|
|
||||||
# Copyright (C) 2014 Canonical Ltd.
|
|
||||||
#
|
|
||||||
# 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 published by the Free Software Foundation.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
import apparmor.aa as aa
|
|
||||||
from common_test import AAParseTest, setup_aa, setup_regex_tests
|
|
||||||
|
|
||||||
|
|
||||||
class AAParsePivotRootTest(AAParseTest):
|
|
||||||
def setUp(self):
|
|
||||||
self.parse_function = aa.parse_pivot_root_rule
|
|
||||||
|
|
||||||
tests = (
|
|
||||||
('pivot_root,', 'pivot_root base keyword'),
|
|
||||||
('pivot_root /old,', 'pivot_root oldroot rule'),
|
|
||||||
('pivot_root /old /new,', 'pivot_root old and new root rule'),
|
|
||||||
('pivot_root /old /new -> /usr/bin/child,', 'pivot_root child rule'),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
setup_aa(aa)
|
|
||||||
if __name__ == '__main__':
|
|
||||||
setup_regex_tests(AAParsePivotRootTest)
|
|
||||||
unittest.main(verbosity=1)
|
|
@ -16,6 +16,7 @@ from apparmor.common import AppArmorBug, AppArmorException
|
|||||||
from apparmor.regex import (
|
from apparmor.regex import (
|
||||||
RE_PROFILE_CAP, RE_PROFILE_DBUS, RE_PROFILE_MOUNT, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL,
|
RE_PROFILE_CAP, RE_PROFILE_DBUS, RE_PROFILE_MOUNT, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL,
|
||||||
RE_PROFILE_START, parse_profile_start_line, re_match_include, RE_PROFILE_UNIX,
|
RE_PROFILE_START, parse_profile_start_line, re_match_include, RE_PROFILE_UNIX,
|
||||||
|
RE_PROFILE_PIVOT_ROOT,
|
||||||
re_match_include_parse, strip_parenthesis, strip_quotes)
|
re_match_include_parse, strip_parenthesis, strip_quotes)
|
||||||
from common_test import AATest, setup_aa, setup_all_loops
|
from common_test import AATest, setup_aa, setup_all_loops
|
||||||
|
|
||||||
@ -313,15 +314,15 @@ class AARegexPivotRoot(AARegexTest):
|
|||||||
"""Tests for RE_PROFILE_PIVOT_ROOT"""
|
"""Tests for RE_PROFILE_PIVOT_ROOT"""
|
||||||
|
|
||||||
def AASetup(self):
|
def AASetup(self):
|
||||||
self.regex = aa.RE_PROFILE_PIVOT_ROOT
|
self.regex = RE_PROFILE_PIVOT_ROOT
|
||||||
|
|
||||||
tests = (
|
tests = (
|
||||||
(' pivot_root,', (None, None, 'pivot_root,', None)),
|
(' pivot_root,', (None, None, 'pivot_root,', None, None)),
|
||||||
(' audit pivot_root,', ('audit', None, 'pivot_root,', None)),
|
(' audit pivot_root,', ('audit', None, 'pivot_root,', None, None)),
|
||||||
(' pivot_root oldroot=/new/old,', (None, None, 'pivot_root oldroot=/new/old,', None)),
|
(' pivot_root oldroot=/new/old,', (None, None, 'pivot_root oldroot=/new/old,', 'oldroot=/new/old', None)),
|
||||||
(' pivot_root oldroot=/new/old /new,', (None, None, 'pivot_root oldroot=/new/old /new,', None)),
|
(' pivot_root oldroot=/new/old /new,', (None, None, 'pivot_root oldroot=/new/old /new,', 'oldroot=/new/old /new', None)),
|
||||||
(' pivot_root oldroot=/new/old /new -> child,', (None, None, 'pivot_root oldroot=/new/old /new -> child,', None)),
|
(' pivot_root oldroot=/new/old /new -> child,', (None, None, 'pivot_root oldroot=/new/old /new -> child,', 'oldroot=/new/old /new -> child', None)),
|
||||||
(' audit pivot_root oldroot=/new/old /new -> child,', ('audit', None, 'pivot_root oldroot=/new/old /new -> child,', None)),
|
(' audit pivot_root oldroot=/new/old /new -> child,', ('audit', None, 'pivot_root oldroot=/new/old /new -> child,', 'oldroot=/new/old /new -> child', None)),
|
||||||
|
|
||||||
('pivot_root', False), # comma missing
|
('pivot_root', False), # comma missing
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user