mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-23 02:27:12 +00:00
From: John Johansen <john.johansen@canonical.com> let allow be used as a prefix in place of deny. Allow is the default and is implicit so it is not needed but some user keep tripping over it, and it makes the language more symmetric eg. /foo rw, allow /foo rw, deny /foo rw, Patch history: v1: - initial revision v2: - rename yacc target rule from opt_deny to opt_perm_mode to reflect that it can be either an allow or deny modifier - break apart tests into more digestible chunks and to clarify their purpose - fix some tests to exercise 'audit allow' - add negative tests for 'allow' and 'deny' in the same rule - add support for 'allow' keyword to apparmor.vim - fix a bug in apparmor.vim to let it recognize multiple capability entries in a single line. v3: - add support for optional keywords on capability rules in regression tests, as well as the bare capability keyword (via 'cap:ALL') - add allow, deny, and conflicting capability behavioral regression tests - fix vim syntax modeline to refer to apparmor in parser tests - adjust FILE regex in vim syntax file creator script Signed-off-by: John Johansen <john.johansen@canonical.com> Signed-off-by: Steve Beattie <steve@nxnw.org> Acked-by: Seth Arnold <seth.arnold@canonical.com>
179 lines
7.4 KiB
Python
179 lines
7.4 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright (C) 2012 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.
|
|
#
|
|
# Written by Steve Beattie <steve@nxnw.org>, based on work by
|
|
# Christian Boltz <apparmor@cboltz.de>
|
|
|
|
from __future__ import with_statement
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
# dangerous capabilities
|
|
danger_caps=["audit_control",
|
|
"audit_write",
|
|
"mac_override",
|
|
"mac_admin",
|
|
"set_fcap",
|
|
"sys_admin",
|
|
"sys_module",
|
|
"sys_rawio"]
|
|
|
|
def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.PIPE, stdin = None, timeout = None):
|
|
'''Try to execute given command (array) and return its stdout, or
|
|
return a textual error if it failed.'''
|
|
|
|
try:
|
|
sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True, universal_newlines=True)
|
|
except OSError as ex:
|
|
return [127, str(ex)]
|
|
|
|
out, outerr = sp.communicate(input)
|
|
|
|
# Handle redirection of stdout
|
|
if out == None:
|
|
out = ''
|
|
# Handle redirection of stderr
|
|
if outerr == None:
|
|
outerr = ''
|
|
return [sp.returncode,out+outerr]
|
|
|
|
# get capabilities list
|
|
(rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_capabilities'])
|
|
if rc != 0:
|
|
sys.stderr.write("make list_capabilities failed: " + output)
|
|
exit(rc)
|
|
|
|
capabilities = re.sub('CAP_', '', output.strip()).lower().split(" ")
|
|
benign_caps =[]
|
|
for cap in capabilities:
|
|
if cap not in danger_caps:
|
|
benign_caps.append(cap)
|
|
|
|
# get network protos list
|
|
(rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_af_names'])
|
|
if rc != 0:
|
|
sys.stderr.write("make list_af_names failed: " + output)
|
|
exit(rc)
|
|
|
|
af_names = []
|
|
af_pairs = re.sub('AF_', '', output.strip()).lower().split(",")
|
|
for af_pair in af_pairs:
|
|
af_name = af_pair.lstrip().split(" ")[0]
|
|
# skip max af name definition
|
|
if len(af_name) > 0 and af_name != "max":
|
|
af_names.append(af_name)
|
|
|
|
# TODO: does a "debug" flag exist? Listed in apparmor.vim.in sdFlagKey,
|
|
# but not in aa_flags...
|
|
# -> currently (2011-01-11) not, but might come back
|
|
|
|
aa_network_types=r'\s+tcp|\s+udp|\s+icmp'
|
|
|
|
aa_flags=['complain',
|
|
'audit',
|
|
'attach_disconnect',
|
|
'no_attach_disconnected',
|
|
'chroot_attach',
|
|
'chroot_no_attach',
|
|
'chroot_relative',
|
|
'namespace_relative']
|
|
|
|
filename=r'(\/|\@\{\S*\})\S*'
|
|
|
|
aa_regex_map = {
|
|
'FILENAME': filename,
|
|
'FILE': r'\v^\s*(audit\s+)?(deny\s+|allow\s+)?(owner\s+)?' + filename + r'\s+', # Start of a file rule
|
|
# (whitespace_+_, owner etc. flag_?_, filename pattern, whitespace_+_)
|
|
'DENYFILE': r'\v^\s*(audit\s+)?deny\s+(owner\s+)?' + filename + r'\s+', # deny, otherwise like FILE
|
|
'auditdenyowner': r'(audit\s+)?(deny\s+|allow\s+)?(owner\s+)?',
|
|
'audit_DENY_owner': r'(audit\s+)?deny\s+(owner\s+)?', # must include "deny", otherwise like auditdenyowner
|
|
'auditdeny': r'(audit\s+)?(deny\s+|allow\s+)?',
|
|
'EOL': r'\s*,(\s*$|(\s*#.*$)\@=)', # End of a line (whitespace_?_, comma, whitespace_?_ comment.*)
|
|
'TRANSITION': r'(\s+-\>\s+\S+)?',
|
|
'sdKapKey': " ".join(benign_caps),
|
|
'sdKapKeyDanger': " ".join(danger_caps),
|
|
'sdKapKeyRegex': "|".join(capabilities),
|
|
'sdNetworkType': aa_network_types,
|
|
'sdNetworkProto': "|".join(af_names),
|
|
'flags': r'((flags\s*\=\s*)?\(\s*(' + '|'.join(aa_flags) + r')(\s*,\s*(' + '|'.join(aa_flags) + r'))*\s*\)\s+)',
|
|
}
|
|
|
|
def my_repl(matchobj):
|
|
matchobj.group(1)
|
|
if matchobj.group(1) in aa_regex_map:
|
|
return aa_regex_map[matchobj.group(1)]
|
|
|
|
return matchobj.group(0)
|
|
|
|
|
|
def create_file_rule (highlighting, permissions, comment, denyrule = 0):
|
|
|
|
if denyrule == 0:
|
|
keywords = '@@auditdenyowner@@'
|
|
else:
|
|
keywords = '@@audit_DENY_owner@@' # TODO: not defined yet, will be '(audit\s+)?deny\s+(owner\s+)?'
|
|
|
|
sniplet = ''
|
|
sniplet = sniplet + "\n" + '" ' + comment + "\n"
|
|
|
|
prefix = r'syn match ' + highlighting + r' /\v^\s*' + keywords
|
|
suffix = r'@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude' + "\n"
|
|
# filename without quotes
|
|
sniplet = sniplet + prefix + r'@@FILENAME@@\s+' + permissions + suffix
|
|
# filename with quotes
|
|
sniplet = sniplet + prefix + r'"@@FILENAME@@"\s+' + permissions + suffix
|
|
# filename without quotes, reverse syntax
|
|
sniplet = sniplet + prefix + permissions + r'\s+@@FILENAME@@' + suffix
|
|
# filename with quotes, reverse syntax
|
|
sniplet = sniplet + prefix + permissions + r'\s+"@@FILENAME@@"+' + suffix
|
|
|
|
return sniplet
|
|
|
|
|
|
filerule = ''
|
|
filerule = filerule + create_file_rule ( 'sdEntryWriteExec ', r'(l|r|w|a|m|k|[iuUpPcC]x)+@@TRANSITION@@', 'write + exec/mmap - danger! (known bug: accepts aw to keep things simple)' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryUX', r'(r|m|k|ux|pux)+@@TRANSITION@@', 'ux(mr) - unconstrained entry, flag the line red. also includes pux which is unconstrained if no profile exists' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryUXe', r'(r|m|k|Ux|PUx)+@@TRANSITION@@', 'Ux(mr) and PUx(mr) - like ux + clean environment' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryPX', r'(r|m|k|px|cx|pix|cix)+@@TRANSITION@@', 'px/cx/pix/cix(mrk) - standard exec entry, flag the line blue' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryPXe', r'(r|m|k|Px|Cx|Pix|Cix)+@@TRANSITION@@', 'Px/Cx/Pix/Cix(mrk) - like px/cx + clean environment' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryIX', r'(r|m|k|ix)+', 'ix(mr) - standard exec entry, flag the line green' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryM', r'(r|m|k)+', 'mr - mmap with PROT_EXEC' )
|
|
|
|
filerule = filerule + create_file_rule ( 'sdEntryM', r'(r|m|k|x)+', 'special case: deny x is allowed (does not need to be ix, px, ux or cx)', 1)
|
|
#syn match sdEntryM /@@DENYFILE@@(r|m|k|x)+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
|
|
|
|
|
|
filerule = filerule + create_file_rule ( 'sdError', r'\S*(w\S*a|a\S*w)\S*', 'write + append is an error' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryW', r'(l|r|w|k)+', 'write entry, flag the line yellow' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryW', r'(l|r|a|k)+', 'append entry, flag the line yellow' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryK', r'[rlk]+', 'read entry + locking, currently no highlighting' )
|
|
filerule = filerule + create_file_rule ( 'sdEntryR', r'[rl]+', 'read entry, no highlighting' )
|
|
|
|
# " special case: deny x is allowed (doesn't need to be ix, px, ux or cx)
|
|
# syn match sdEntryM /@@DENYFILE@@(r|m|k|x)+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
|
|
|
|
# " TODO: Support filenames enclosed in quotes ("/home/foo/My Documents/") - ideally by only allowing quotes pair-wise
|
|
|
|
|
|
regex = "@@(" + "|".join(aa_regex_map) + ")@@"
|
|
|
|
sys.stdout.write('" generated from apparmor.vim.in by create-apparmor.vim.py\n')
|
|
sys.stdout.write('" do not edit this file - edit apparmor.vim.in or create-apparmor.vim.py instead' + "\n\n")
|
|
|
|
with open("apparmor.vim.in") as template:
|
|
for line in template:
|
|
line = re.sub(regex, my_repl, line.rstrip())
|
|
sys.stdout.write('%s\n' % line)
|
|
|
|
sys.stdout.write("\n\n\n\n")
|
|
|
|
sys.stdout.write('" file rules added with create_file_rule()\n')
|
|
sys.stdout.write(re.sub(regex, my_repl, filerule)+'\n')
|
|
|