2022-06-28 21:08:50 -04:00
#!/usr/bin/python3
2012-03-22 13:26:20 -07:00
#
# 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>
import re
import subprocess
import sys
# dangerous capabilities
2022-06-18 14:30:49 -04:00
danger_caps = ( " audit_control " ,
2014-01-20 11:51:01 -08:00
" audit_write " ,
" mac_override " ,
" mac_admin " ,
2019-09-17 10:38:09 +01:00
" setfcap " ,
2014-01-20 11:51:01 -08:00
" sys_admin " ,
" sys_module " ,
2022-06-18 14:30:49 -04:00
" sys_rawio " )
2014-01-20 11:51:01 -08:00
def cmd ( command , input = None , stderr = subprocess . STDOUT , stdout = subprocess . PIPE , stdin = None , timeout = None ) :
2022-08-07 14:57:30 -04:00
""" Try to execute given command (array) and return its stdout, or
return a textual error if it failed . """
2012-03-22 13:26:20 -07:00
try :
2012-06-11 17:56:21 +01:00
sp = subprocess . Popen ( command , stdin = stdin , stdout = stdout , stderr = stderr , close_fds = True , universal_newlines = True )
2012-06-12 11:37:36 +01:00
except OSError as ex :
2022-06-18 14:30:49 -04:00
return 127 , str ( ex )
2012-03-22 13:26:20 -07:00
out , outerr = sp . communicate ( input )
# Handle redirection of stdout
2014-01-20 11:51:01 -08:00
if out is None :
2012-03-22 13:26:20 -07:00
out = ' '
# Handle redirection of stderr
2014-01-20 11:51:01 -08:00
if outerr is None :
2012-03-22 13:26:20 -07:00
outerr = ' '
2022-06-18 14:30:49 -04:00
return sp . returncode , out , outerr
2012-03-22 13:26:20 -07:00
2022-08-07 12:26:24 -04:00
2012-03-22 13:26:20 -07:00
# get capabilities list
2022-06-18 14:30:49 -04:00
( rc , output , outerr ) = cmd ( ( ' ../../common/list_capabilities.sh ' , ) )
2012-03-22 13:26:20 -07:00
if rc != 0 :
2020-04-05 14:26:15 +02:00
sys . stderr . write ( " make list_capabilities failed: " + output + outerr )
2022-08-28 22:40:28 -04:00
sys . exit ( rc )
2012-03-22 13:26:20 -07:00
2020-03-29 00:07:11 +01:00
capabilities = re . sub ( ' CAP_ ' , ' ' , output . strip ( ) ) . lower ( ) . split ( ' \n ' )
2014-01-20 11:51:01 -08:00
benign_caps = [ ]
2012-03-22 13:26:20 -07:00
for cap in capabilities :
if cap not in danger_caps :
benign_caps . append ( cap )
# get network protos list
2022-06-18 14:30:49 -04:00
( rc , output , outerr ) = cmd ( ( ' ../../common/list_af_names.sh ' , ) )
2012-03-22 13:26:20 -07:00
if rc != 0 :
2020-04-05 14:26:15 +02:00
sys . stderr . write ( " make list_af_names failed: " + output + outerr )
2022-08-28 22:40:28 -04:00
sys . exit ( rc )
2012-03-22 13:26:20 -07:00
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
2022-08-09 22:34:16 -04:00
if af_name and af_name != " max " :
2012-03-22 13:26:20 -07:00
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
2014-01-20 11:51:01 -08:00
aa_network_types = r ' \ s+tcp| \ s+udp| \ s+icmp '
2012-04-05 14:39:57 -07:00
2022-06-18 14:30:49 -04:00
aa_flags = ( ' complain ' ,
2014-01-20 11:51:01 -08:00
' audit ' ,
2014-01-29 23:16:36 +01:00
' attach_disconnected ' ,
2014-01-20 11:51:01 -08:00
' no_attach_disconnected ' ,
' chroot_attach ' ,
' chroot_no_attach ' ,
' chroot_relative ' ,
2014-08-11 23:13:55 +02:00
' namespace_relative ' ,
' mediate_deleted ' ,
2022-06-18 14:30:49 -04:00
' delegate_deleted ' )
2012-04-05 14:39:57 -07:00
2014-01-20 11:51:01 -08:00
filename = r ' ( \ /| \ @ \ { \ S* \ }) \ S* '
2012-04-05 14:39:57 -07:00
2012-03-22 13:26:20 -07:00
aa_regex_map = {
2012-04-05 14:39:57 -07:00
' FILENAME ' : filename ,
2014-01-24 11:17:23 -08:00
' FILE ' : r ' \ v^ \ s*(audit \ s+)?(deny \ s+|allow \ s+)?(owner \ s+|other \ s+)? ' + filename + r ' \ s+ ' , # Start of a file rule
2012-04-05 14:39:57 -07:00
# (whitespace_+_, owner etc. flag_?_, filename pattern, whitespace_+_)
2014-01-24 11:17:23 -08:00
' DENYFILE ' : r ' \ v^ \ s*(audit \ s+)?deny \ s+(owner \ s+|other \ s+)? ' + filename + r ' \ s+ ' , # deny, otherwise like FILE
' auditdenyowner ' : r ' (audit \ s+)?(deny \ s+|allow \ s+)?(owner \ s+|other \ s+)? ' ,
' audit_DENY_owner ' : r ' (audit \ s+)?deny \ s+(owner \ s+|other \ s+)? ' , # must include "deny", otherwise like auditdenyowner
2013-09-20 06:48:56 -07:00
' auditdeny ' : r ' (audit \ s+)?(deny \ s+|allow \ s+)? ' ,
2014-01-20 11:51:01 -08:00
' EOL ' : r ' \ s*,( \ s*$|( \ s*#.*$) \ @=) ' , # End of a line (whitespace_?_, comma, whitespace_?_ comment.*)
2012-03-22 13:26:20 -07:00
' TRANSITION ' : r ' ( \ s+- \ > \ s+ \ S+)? ' ,
' sdKapKey ' : " " . join ( benign_caps ) ,
' sdKapKeyDanger ' : " " . join ( danger_caps ) ,
' sdKapKeyRegex ' : " | " . join ( capabilities ) ,
' sdNetworkType ' : aa_network_types ,
' sdNetworkProto ' : " | " . join ( af_names ) ,
2012-04-05 14:39:57 -07:00
' flags ' : r ' ((flags \ s* \ = \ s*)? \ ( \ s*( ' + ' | ' . join ( aa_flags ) + r ' )( \ s*, \ s*( ' + ' | ' . join ( aa_flags ) + r ' ))* \ s* \ ) \ s+) ' ,
2012-03-22 13:26:20 -07:00
}
2014-01-20 11:51:01 -08:00
2012-03-22 13:26:20 -07:00
def my_repl ( matchobj ) :
2012-06-12 14:30:57 +01:00
matchobj . group ( 1 )
2012-03-22 13:26:20 -07:00
if matchobj . group ( 1 ) in aa_regex_map :
return aa_regex_map [ matchobj . group ( 1 ) ]
return matchobj . group ( 0 )
2012-06-05 21:18:30 +02:00
2014-01-20 11:51:01 -08:00
def create_file_rule ( highlighting , permissions , comment , denyrule = 0 ) :
2012-06-05 21:18:30 +02:00
2014-01-20 11:51:01 -08:00
if denyrule == 0 :
keywords = ' @@auditdenyowner@@ '
else :
keywords = ' @@audit_DENY_owner@@ ' # TODO: not defined yet, will be '(audit\s+)?deny\s+(owner\s+)?'
2012-06-05 21:18:30 +02:00
2014-01-20 11:51:01 -08:00
sniplet = ' '
sniplet = sniplet + " \n " + ' " ' + comment + " \n "
2012-06-05 21:18:30 +02:00
2014-01-20 11:51:01 -08:00
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
2012-06-05 21:18:30 +02:00
2014-01-20 11:51:01 -08:00
return sniplet
2012-06-05 21:18:30 +02:00
filerule = ' '
2014-01-20 11:51:01 -08:00
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 )
2022-08-07 12:26:24 -04:00
# syn match sdEntryM /@@DENYFILE@@(r|m|k|x)+@@EOL@@/ contains=sdGlob,sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
2012-06-05 21:18:30 +02:00
2014-01-20 11:51:01 -08:00
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 ' )
2012-06-05 21:18:30 +02:00
# " 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
2012-03-22 13:26:20 -07:00
regex = " @@( " + " | " . join ( aa_regex_map ) + " )@@ "
2012-06-11 17:56:21 +01:00
sys . stdout . write ( ' " generated from apparmor.vim.in by create-apparmor.vim.py \n ' )
2012-06-11 18:31:31 +01:00
sys . stdout . write ( ' " do not edit this file - edit apparmor.vim.in or create-apparmor.vim.py instead ' + " \n \n " )
2012-06-05 21:18:30 +02:00
2012-06-11 17:56:21 +01:00
with open ( " apparmor.vim.in " ) as template :
2012-03-22 13:26:20 -07:00
for line in template :
line = re . sub ( regex , my_repl , line . rstrip ( ) )
2023-02-19 16:26:14 -05:00
sys . stdout . write ( line + ' \n ' )
2012-06-05 21:18:30 +02:00
2012-06-11 17:56:21 +01:00
sys . stdout . write ( " \n \n \n \n " )
2012-06-05 21:18:30 +02:00
2012-06-11 17:56:21 +01:00
sys . stdout . write ( ' " file rules added with create_file_rule() \n ' )
2014-01-20 11:51:01 -08:00
sys . stdout . write ( re . sub ( regex , my_repl , filerule ) + ' \n ' )