2014-11-12 00:05:04 +01:00
|
|
|
# ----------------------------------------------------------------------
|
|
|
|
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
2015-04-03 17:28:03 +02:00
|
|
|
# Copyright (C) 2014-2015 Christian Boltz <apparmor@cboltz.de>
|
2014-11-12 00:05:04 +01:00
|
|
|
#
|
|
|
|
# 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
|
2015-03-31 22:44:18 +02:00
|
|
|
from apparmor.common import AppArmorBug, AppArmorException
|
|
|
|
|
|
|
|
# setup module translations
|
|
|
|
from apparmor.translations import init_translation
|
|
|
|
_ = init_translation()
|
2014-11-12 00:05:04 +01:00
|
|
|
|
|
|
|
## Profile parsing Regex
|
|
|
|
RE_AUDIT_DENY = '^\s*(?P<audit>audit\s+)?(?P<allow>allow\s+|deny\s+)?' # line start, optionally: leading whitespace, <audit> and <allow>/deny
|
|
|
|
RE_OWNER = '(?P<owner>owner\s+)?' # optionally: <owner>
|
|
|
|
RE_EOL = '\s*(?P<comment>#.*?)?\s*$' # optional whitespace, optional <comment>, optional whitespace, end of the line
|
|
|
|
RE_COMMA_EOL = '\s*,' + RE_EOL # optional whitespace, comma + RE_EOL
|
|
|
|
|
2015-05-09 01:09:08 +02:00
|
|
|
RE_PROFILE_NAME = '(?P<%s>(\S+|"[^"]+"))' # string without spaces, or quoted string. %s is the match group name
|
|
|
|
RE_PROFILE_PATH = '(?P<%s>(/\S+|"/[^"]+"))' # filename (starting with '/') without spaces, or quoted filename. %s is the match group name
|
|
|
|
|
2014-11-12 00:05:04 +01:00
|
|
|
RE_PROFILE_END = re.compile('^\s*\}' + RE_EOL)
|
|
|
|
RE_PROFILE_CAP = re.compile(RE_AUDIT_DENY + 'capability(?P<capability>(\s+\S+)+)?' + RE_COMMA_EOL)
|
|
|
|
RE_PROFILE_LINK = re.compile(RE_AUDIT_DENY + 'link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)' + RE_COMMA_EOL)
|
|
|
|
RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??)' + RE_COMMA_EOL)
|
|
|
|
RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)' + RE_COMMA_EOL)
|
|
|
|
RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)' + RE_COMMA_EOL)
|
|
|
|
RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?' + RE_EOL, flags=re.IGNORECASE)
|
|
|
|
RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?' + RE_EOL)
|
|
|
|
RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{' + RE_EOL)
|
|
|
|
RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$')
|
|
|
|
RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$')
|
|
|
|
RE_PROFILE_BARE_FILE_ENTRY = re.compile(RE_AUDIT_DENY + RE_OWNER + 'file' + RE_COMMA_EOL)
|
|
|
|
RE_PROFILE_PATH_ENTRY = re.compile(RE_AUDIT_DENY + RE_OWNER + '(file\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?' + RE_COMMA_EOL)
|
2015-04-26 21:54:38 +02:00
|
|
|
RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(?P<details>\s+.*)?' + RE_COMMA_EOL)
|
2014-11-12 00:05:04 +01:00
|
|
|
RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)' + RE_COMMA_EOL)
|
2015-05-28 22:14:37 +02:00
|
|
|
RE_PROFILE_HAT_DEF = re.compile('^(?P<leadingspace>\s*)(?P<hat_keyword>\^|hat\s+)(?P<hat>\"??.+?\"??)\s+((flags=)?\((?P<flags>.+)\)\s+)*\{' + RE_EOL)
|
2014-11-12 00:05:04 +01:00
|
|
|
RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + '(dbus\s*,|dbus\s+[^#]*\s*,)' + RE_EOL)
|
|
|
|
RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + '((mount|remount|umount|unmount)(\s+[^#]*)?\s*,)' + RE_EOL)
|
|
|
|
RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + '(signal\s*,|signal\s+[^#]*\s*,)' + RE_EOL)
|
|
|
|
RE_PROFILE_PTRACE = re.compile(RE_AUDIT_DENY + '(ptrace\s*,|ptrace\s+[^#]*\s*,)' + RE_EOL)
|
|
|
|
RE_PROFILE_PIVOT_ROOT = re.compile(RE_AUDIT_DENY + '(pivot_root\s*,|pivot_root\s+[^#]*\s*,)' + RE_EOL)
|
|
|
|
RE_PROFILE_UNIX = re.compile(RE_AUDIT_DENY + '(unix\s*,|unix\s+[^#]*\s*,)' + RE_EOL)
|
|
|
|
|
|
|
|
# match anything that's not " or #, or matching quotes with anything except quotes inside
|
|
|
|
__re_no_or_quoted_hash = '([^#"]|"[^"]*")*'
|
|
|
|
|
|
|
|
RE_RULE_HAS_COMMA = re.compile('^' + __re_no_or_quoted_hash +
|
|
|
|
',\s*(#.*)?$') # match comma plus any trailing comment
|
|
|
|
RE_HAS_COMMENT_SPLIT = re.compile('^(?P<not_comment>' + __re_no_or_quoted_hash + ')' + # store in 'not_comment' group
|
|
|
|
'(?P<comment>#.*)$') # match trailing comment and store in 'comment' group
|
|
|
|
|
2015-03-03 20:15:00 +01:00
|
|
|
|
2015-03-31 22:44:18 +02:00
|
|
|
|
2015-04-03 17:28:03 +02:00
|
|
|
RE_PROFILE_START = re.compile(
|
2015-03-31 22:44:18 +02:00
|
|
|
'^(?P<leadingspace>\s*)' +
|
|
|
|
'(' +
|
2015-05-09 01:09:08 +02:00
|
|
|
RE_PROFILE_PATH % 'plainprofile' + # just a path
|
2015-03-31 22:44:18 +02:00
|
|
|
'|' + # or
|
2015-05-09 01:09:08 +02:00
|
|
|
'(' + 'profile' + '\s+' + RE_PROFILE_NAME % 'namedprofile' + '(\s+' + RE_PROFILE_PATH % 'attachment' + ')?' + ')' + # 'profile', profile name, optionally attachment
|
2015-03-31 22:44:18 +02:00
|
|
|
')' +
|
|
|
|
'\s+((flags=)?\((?P<flags>.+)\)\s+)?\{' +
|
|
|
|
RE_EOL)
|
|
|
|
|
|
|
|
def parse_profile_start_line(line, filename):
|
2015-04-03 17:28:03 +02:00
|
|
|
matches = RE_PROFILE_START.search(line)
|
2015-03-31 22:44:18 +02:00
|
|
|
|
|
|
|
if not matches:
|
|
|
|
raise AppArmorBug('The given line from file %(filename)s is not the start of a profile: %(line)s' % { 'filename': filename, 'line': line } )
|
|
|
|
|
|
|
|
result = {}
|
|
|
|
|
|
|
|
for section in [ 'leadingspace', 'plainprofile', 'namedprofile', 'attachment', 'flags', 'comment']:
|
|
|
|
if matches.group(section):
|
|
|
|
result[section] = matches.group(section)
|
|
|
|
|
|
|
|
# sections with optional quotes
|
|
|
|
if section in ['plainprofile', 'namedprofile', 'attachment']:
|
|
|
|
result[section] = strip_quotes(result[section])
|
|
|
|
else:
|
|
|
|
result[section] = None
|
|
|
|
|
|
|
|
if result['flags'] and result['flags'].strip() == '':
|
|
|
|
raise AppArmorException(_('Invalid syntax in %(filename)s: Empty set of flags in line %(line)s.' % { 'filename': filename, 'line': line } ))
|
|
|
|
|
|
|
|
if result['plainprofile']:
|
|
|
|
result['profile'] = result['plainprofile']
|
|
|
|
result['profile_keyword'] = False
|
|
|
|
else:
|
|
|
|
result['profile'] = result['namedprofile']
|
|
|
|
result['profile_keyword'] = True
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
2015-03-03 20:15:00 +01:00
|
|
|
def strip_quotes(data):
|
|
|
|
if data[0] + data[-1] == '""':
|
|
|
|
return data[1:-1]
|
|
|
|
else:
|
|
|
|
return data
|