From e40445dea73644f6404adadd1feedcbc428c5539 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Jun 2013 15:37:31 +0530 Subject: [PATCH 001/183] config.py added to library --- .project | 17 ++++++++++ .pydevproject | 8 +++++ lib/AppArmor.py | 1 + lib/config.py | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 .project create mode 100644 .pydevproject create mode 100644 lib/AppArmor.py create mode 100644 lib/config.py diff --git a/.project b/.project new file mode 100644 index 000000000..303ccdf1f --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + apparmor-profile-tools + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 000000000..9d8f69996 --- /dev/null +++ b/.pydevproject @@ -0,0 +1,8 @@ + + + +/apparmor-profile-tools + +python 3.0 +Python3.3 + diff --git a/lib/AppArmor.py b/lib/AppArmor.py new file mode 100644 index 000000000..a93a4bf16 --- /dev/null +++ b/lib/AppArmor.py @@ -0,0 +1 @@ +#!/usr/bin/python3 diff --git a/lib/config.py b/lib/config.py new file mode 100644 index 000000000..c34c620e8 --- /dev/null +++ b/lib/config.py @@ -0,0 +1,84 @@ +import os +import re +import stat + +confdir = '/etc/apparmor' +cfg = None +repo_cfg = None + +def read_config(filename): + """Reads the file and returns a double dictionary config[section][attribute]=property""" + config = dict() + regex_label = re.compile('^\[(\S+)\]') + regex_value = re.compile('^\s*(\S+)\s*=\s*(.*)\s*$') + filepath = confdir + '/' + filename + try: + conf_file = open(filepath, 'r', 1) + except OSError: + pass + else: + section = '' # The default section + for line in conf_file: + # Ignore the comment lines + if line.lstrip().startswith('#'): + continue + line = line.rstrip('\n') + # Search for a new section + label_match = regex_label.search(line) + if label_match: + section = label_match.groups()[0] + else: + # Search for a attribute value pair + value_match = regex_value.search(line) + if value_match: + attribute = value_match.groups()[0] + value = value_match.groups()[1] + # A doubly nested dictionary + config[section] = config.get(section, {}) + config[section][attribute] = value + conf_file.close() + # LP: Bug #692406 + # Explicitly disabled repository + if filename == "repository.conf": + config['repository']={'enabled':'no'} + return config + +def write_config(filename, config): + """Writes the given configuration to the specified file""" + filepath = confdir + '/' + filename + try: + conf_file = open(filepath, 'w') + except OSError: + raise IOError("Unable to write to %s"%filename) + else: + for section in sorted(config.iterkeys()): + # Write the section and all attributes and values under the section + conf_file.write("[%s]\n"%section) + for attribute in sorted(config[section].iterkeys()): + conf_file.write(" %s = %s\n"%(attribute, config[section][attribute])) + permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write + # Set file permissions as 0600 + os.chmod(filepath, permission_600) + conf_file.close() + +def find_first_file(file_list): + """Returns name of first matching file None otherwise""" + # I don't understand why it searches the CWD, maybe I'll find out about it in some module + filename = None + if len(file_list): + for file in file_list.split(): + if os.path.isfile(file): + filename = file + break + return filename + +def find_first_dir(dir_list): + """Returns name of first matching directory None otherwise""" + dirname = None + if (len(dir_list)): + for direc in dir_list.split(): + if os.path.isdir(direc): + dirname = direc + break + return dirname + \ No newline at end of file From 6d32f3cb946d184ffa1192e058b02543d2afda70 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Jun 2013 15:56:56 +0530 Subject: [PATCH 002/183] updated OSError to IOError --- lib/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/config.py b/lib/config.py index c34c620e8..135e343fa 100644 --- a/lib/config.py +++ b/lib/config.py @@ -14,7 +14,7 @@ def read_config(filename): filepath = confdir + '/' + filename try: conf_file = open(filepath, 'r', 1) - except OSError: + except IOError: pass else: section = '' # The default section @@ -48,7 +48,7 @@ def write_config(filename, config): filepath = confdir + '/' + filename try: conf_file = open(filepath, 'w') - except OSError: + except IOError: raise IOError("Unable to write to %s"%filename) else: for section in sorted(config.iterkeys()): From adb993695902a5ee1898401380624ee07d892399 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Jun 2013 16:01:56 +0530 Subject: [PATCH 003/183] fixed a space --- lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config.py b/lib/config.py index 135e343fa..7f0a5680b 100644 --- a/lib/config.py +++ b/lib/config.py @@ -40,7 +40,7 @@ def read_config(filename): # LP: Bug #692406 # Explicitly disabled repository if filename == "repository.conf": - config['repository']={'enabled':'no'} + config['repository'] = {'enabled':'no'} return config def write_config(filename, config): From 80ce4c557bea5b4c33dca56cb1b43b6c3b2533e6 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Jun 2013 16:10:00 +0530 Subject: [PATCH 004/183] minor fix --- lib/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/config.py b/lib/config.py index 7f0a5680b..019c37608 100644 --- a/lib/config.py +++ b/lib/config.py @@ -51,10 +51,10 @@ def write_config(filename, config): except IOError: raise IOError("Unable to write to %s"%filename) else: - for section in sorted(config.iterkeys()): + for section in sorted(config.keys()): # Write the section and all attributes and values under the section conf_file.write("[%s]\n"%section) - for attribute in sorted(config[section].iterkeys()): + for attribute in sorted(config[section].keys()): conf_file.write(" %s = %s\n"%(attribute, config[section][attribute])) permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write # Set file permissions as 0600 From 6f38bb5c0ee13dfbb04bce161a5b82705cc9bbb8 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Jun 2013 16:11:55 +0530 Subject: [PATCH 005/183] minor typo fixed --- lib/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config.py b/lib/config.py index 019c37608..5839f3562 100644 --- a/lib/config.py +++ b/lib/config.py @@ -40,7 +40,7 @@ def read_config(filename): # LP: Bug #692406 # Explicitly disabled repository if filename == "repository.conf": - config['repository'] = {'enabled':'no'} + config['repository'] = {'enabled': 'no'} return config def write_config(filename, config): From c832f820277f47746188551794ff00f7a0878315 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Jun 2013 16:55:26 +0530 Subject: [PATCH 006/183] indentation bug in write method fixed --- lib/config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/config.py b/lib/config.py index 5839f3562..8287a96ba 100644 --- a/lib/config.py +++ b/lib/config.py @@ -53,9 +53,12 @@ def write_config(filename, config): else: for section in sorted(config.keys()): # Write the section and all attributes and values under the section - conf_file.write("[%s]\n"%section) + if section != '': # If default section then no section label + conf_file.write("[%s]\n"%section) for attribute in sorted(config[section].keys()): - conf_file.write(" %s = %s\n"%(attribute, config[section][attribute])) + if section != '': + conf_file.write(" ") # Indentation for a section + conf_file.write("%s = %s\n"%(attribute, config[section][attribute])) permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write # Set file permissions as 0600 os.chmod(filepath, permission_600) From 758d1c6e7da8e1993638a8944a1dbe46a33c9a84 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 18 Jun 2013 03:49:05 +0530 Subject: [PATCH 007/183] added severity.py with tested convert_regex and the old and new config --- lib/config.py | 48 ++++---------------------- lib/configbkp.py | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/severity.py | 67 +++++++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+), 41 deletions(-) create mode 100644 lib/configbkp.py create mode 100644 lib/severity.py diff --git a/lib/config.py b/lib/config.py index 8287a96ba..8564e51c0 100644 --- a/lib/config.py +++ b/lib/config.py @@ -1,42 +1,16 @@ import os -import re import stat +import configparser confdir = '/etc/apparmor' cfg = None repo_cfg = None def read_config(filename): - """Reads the file and returns a double dictionary config[section][attribute]=property""" - config = dict() - regex_label = re.compile('^\[(\S+)\]') - regex_value = re.compile('^\s*(\S+)\s*=\s*(.*)\s*$') + """Reads the file and returns a configparser config[section][attribute]=property""" + config = configparser.ConfigParser() filepath = confdir + '/' + filename - try: - conf_file = open(filepath, 'r', 1) - except IOError: - pass - else: - section = '' # The default section - for line in conf_file: - # Ignore the comment lines - if line.lstrip().startswith('#'): - continue - line = line.rstrip('\n') - # Search for a new section - label_match = regex_label.search(line) - if label_match: - section = label_match.groups()[0] - else: - # Search for a attribute value pair - value_match = regex_value.search(line) - if value_match: - attribute = value_match.groups()[0] - value = value_match.groups()[1] - # A doubly nested dictionary - config[section] = config.get(section, {}) - config[section][attribute] = value - conf_file.close() + config.read(filepath) # LP: Bug #692406 # Explicitly disabled repository if filename == "repository.conf": @@ -44,25 +18,17 @@ def read_config(filename): return config def write_config(filename, config): - """Writes the given configuration to the specified file""" + """Writes the given configparser to the specified file""" filepath = confdir + '/' + filename try: - conf_file = open(filepath, 'w') + with open(filepath, 'w') as config_file: + config.write(config_file) except IOError: raise IOError("Unable to write to %s"%filename) else: - for section in sorted(config.keys()): - # Write the section and all attributes and values under the section - if section != '': # If default section then no section label - conf_file.write("[%s]\n"%section) - for attribute in sorted(config[section].keys()): - if section != '': - conf_file.write(" ") # Indentation for a section - conf_file.write("%s = %s\n"%(attribute, config[section][attribute])) permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write # Set file permissions as 0600 os.chmod(filepath, permission_600) - conf_file.close() def find_first_file(file_list): """Returns name of first matching file None otherwise""" diff --git a/lib/configbkp.py b/lib/configbkp.py new file mode 100644 index 000000000..8287a96ba --- /dev/null +++ b/lib/configbkp.py @@ -0,0 +1,87 @@ +import os +import re +import stat + +confdir = '/etc/apparmor' +cfg = None +repo_cfg = None + +def read_config(filename): + """Reads the file and returns a double dictionary config[section][attribute]=property""" + config = dict() + regex_label = re.compile('^\[(\S+)\]') + regex_value = re.compile('^\s*(\S+)\s*=\s*(.*)\s*$') + filepath = confdir + '/' + filename + try: + conf_file = open(filepath, 'r', 1) + except IOError: + pass + else: + section = '' # The default section + for line in conf_file: + # Ignore the comment lines + if line.lstrip().startswith('#'): + continue + line = line.rstrip('\n') + # Search for a new section + label_match = regex_label.search(line) + if label_match: + section = label_match.groups()[0] + else: + # Search for a attribute value pair + value_match = regex_value.search(line) + if value_match: + attribute = value_match.groups()[0] + value = value_match.groups()[1] + # A doubly nested dictionary + config[section] = config.get(section, {}) + config[section][attribute] = value + conf_file.close() + # LP: Bug #692406 + # Explicitly disabled repository + if filename == "repository.conf": + config['repository'] = {'enabled': 'no'} + return config + +def write_config(filename, config): + """Writes the given configuration to the specified file""" + filepath = confdir + '/' + filename + try: + conf_file = open(filepath, 'w') + except IOError: + raise IOError("Unable to write to %s"%filename) + else: + for section in sorted(config.keys()): + # Write the section and all attributes and values under the section + if section != '': # If default section then no section label + conf_file.write("[%s]\n"%section) + for attribute in sorted(config[section].keys()): + if section != '': + conf_file.write(" ") # Indentation for a section + conf_file.write("%s = %s\n"%(attribute, config[section][attribute])) + permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write + # Set file permissions as 0600 + os.chmod(filepath, permission_600) + conf_file.close() + +def find_first_file(file_list): + """Returns name of first matching file None otherwise""" + # I don't understand why it searches the CWD, maybe I'll find out about it in some module + filename = None + if len(file_list): + for file in file_list.split(): + if os.path.isfile(file): + filename = file + break + return filename + +def find_first_dir(dir_list): + """Returns name of first matching directory None otherwise""" + dirname = None + if (len(dir_list)): + for direc in dir_list.split(): + if os.path.isdir(direc): + dirname = direc + break + return dirname + \ No newline at end of file diff --git a/lib/severity.py b/lib/severity.py new file mode 100644 index 000000000..9222ae6b3 --- /dev/null +++ b/lib/severity.py @@ -0,0 +1,67 @@ +import os +import re + +class Severity: + def __init__(self, dbname=None, default_rank=10): + self.severity = dict() + self.severity['DATABASENAME'] = dbname + self.severity['CAPABILITIES'] = {} + self.severity['FILES'] = {} + self.severity['REGEXPS'] = {} + self.severity['DEFAULT_RANK'] = default_rank + if not dbname: + return self.severity + try: + database = open(dbname, 'r') + except IOError: + raise("Could not open severity database %s"%dbname) + for line in database: + line = line.strip() # or only rstrip and lstrip? + if line == '' or line.startswith('#') : + continue + if line.startswith('/'): + try: + path, read, write, execute = line.split() + except ValueError: + raise("Insufficient values for permissions") + else: + path = path.lstrip('/') + if '*' not in path: + self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} + else: + ptr = self.severity['REGEXPS'] + pieces = path.split('/') + for index, piece in enumerate(pieces): + if '*' in piece: + path = '/'.join(pieces[index:]) + regexp = self.convert_regexp(path) + ptr[regexp] = {'SD_RANK': {'r': read, 'w': write, 'x': execute}} + break + else: + ptr[piece] = ptr.get(piece, {}) + ptr = ptr[piece] + elif line.startswith('CAP'): + resource, severity = line.split() + self.severity['CAPABILITIES'][resource] = severity + else: + print("unexpected database line: %s"%line) + database.close() + + def convert_regexp(self, path): + pattern_or = re.compile('{.*|,.*}') # The regex pattern for {a,b} + regex = path + for character in ['.', '+', '[', ']']: # Escape the regex symbols + regex = regex.replace(character, "\%s"%character) + # Convert the ** to regex + regex = regex.replace('**', '.SDPROF_INTERNAL_GLOB') + # Convert the * to regex + regex = regex.replace('*', '[^/]SDPROF_INTERNAL_GLOB') + # Convert {a,b} to (a|b) form + if pattern_or.match(regex): + for character, replacement in zip('{},', '()|'): + regex = regex.replace(character, replacement) + # Restore the * in the final regex + regex = regex.replace('SDPROF_INTERNAL_GLOB', '*') + return regex + + \ No newline at end of file From 47679582aa24a2d1b5e5d3b3087a324c2c674f66 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 18 Jun 2013 03:55:09 +0530 Subject: [PATCH 008/183] minor typo --- lib/severity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/severity.py b/lib/severity.py index 9222ae6b3..a520fdaff 100644 --- a/lib/severity.py +++ b/lib/severity.py @@ -48,7 +48,7 @@ class Severity: database.close() def convert_regexp(self, path): - pattern_or = re.compile('{.*|,.*}') # The regex pattern for {a,b} + pattern_or = re.compile('{.*\,.*}') # The regex pattern for {a,b} regex = path for character in ['.', '+', '[', ']']: # Escape the regex symbols regex = regex.replace(character, "\%s"%character) From 9692fbfd89f1a6960541647db83d2ebb9386a34d Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 21 Jun 2013 01:35:26 +0530 Subject: [PATCH 009/183] completed severity module, pending its module testing --- lib/config.py | 22 +++++++++---- lib/severity.py | 88 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 92 insertions(+), 18 deletions(-) diff --git a/lib/config.py b/lib/config.py index 8564e51c0..b242d76ca 100644 --- a/lib/config.py +++ b/lib/config.py @@ -1,6 +1,9 @@ -import os -import stat import configparser +import os +import shutil +import stat +import tempfile + confdir = '/etc/apparmor' cfg = None @@ -20,15 +23,20 @@ def read_config(filename): def write_config(filename, config): """Writes the given configparser to the specified file""" filepath = confdir + '/' + filename + permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write try: - with open(filepath, 'w') as config_file: - config.write(config_file) + # Open a temporary file to write the config file + config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False) + # Set file permissions as 0600 + os.chmod(config_file.name, permission_600) + config.write(config_file) + config_file.close() except IOError: raise IOError("Unable to write to %s"%filename) else: - permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write - # Set file permissions as 0600 - os.chmod(filepath, permission_600) + # Move the temporary file to the target config file + shutil.move(config_file.name, filepath) + def find_first_file(file_list): """Returns name of first matching file None otherwise""" diff --git a/lib/severity.py b/lib/severity.py index a520fdaff..35e54db2c 100644 --- a/lib/severity.py +++ b/lib/severity.py @@ -1,8 +1,8 @@ -import os import re class Severity: def __init__(self, dbname=None, default_rank=10): + """Initialises the class object""" self.severity = dict() self.severity['DATABASENAME'] = dbname self.severity['CAPABILITIES'] = {} @@ -23,8 +23,10 @@ class Severity: try: path, read, write, execute = line.split() except ValueError: - raise("Insufficient values for permissions") + raise("Insufficient values for permissions in line: %s"%line) else: + if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11): + raise("Inappropriate values for permissions in line: %s"%line) path = path.lstrip('/') if '*' not in path: self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} @@ -35,33 +37,97 @@ class Severity: if '*' in piece: path = '/'.join(pieces[index:]) regexp = self.convert_regexp(path) - ptr[regexp] = {'SD_RANK': {'r': read, 'w': write, 'x': execute}} + ptr[regexp] = {'AA_RANK': {'r': read, 'w': write, 'x': execute}} break else: ptr[piece] = ptr.get(piece, {}) ptr = ptr[piece] - elif line.startswith('CAP'): - resource, severity = line.split() - self.severity['CAPABILITIES'][resource] = severity + elif line.startswith('CAP_'): + try: + resource, severity = line.split() + except ValueError: + raise("No severity value present in line: %s"%line) + else: + if severity not in range(0,11): + raise("Inappropriate severity value present in line: %s"%line) + self.severity['CAPABILITIES'][resource] = severity else: - print("unexpected database line: %s"%line) + print("unexpected database line: %s \nin file: %s"%(line,dbname)) database.close() def convert_regexp(self, path): + """Returns the regex form of the path""" pattern_or = re.compile('{.*\,.*}') # The regex pattern for {a,b} + internal_glob = '__KJHDKVZH_AAPROF_INTERNAL_GLOB_SVCUZDGZID__' regex = path for character in ['.', '+', '[', ']']: # Escape the regex symbols regex = regex.replace(character, "\%s"%character) # Convert the ** to regex - regex = regex.replace('**', '.SDPROF_INTERNAL_GLOB') + regex = regex.replace('**', '.'+internal_glob) # Convert the * to regex - regex = regex.replace('*', '[^/]SDPROF_INTERNAL_GLOB') + regex = regex.replace('*', '[^/]'+internal_glob) # Convert {a,b} to (a|b) form if pattern_or.match(regex): for character, replacement in zip('{},', '()|'): regex = regex.replace(character, replacement) # Restore the * in the final regex - regex = regex.replace('SDPROF_INTERNAL_GLOB', '*') + regex = regex.replace(internal_glob, '*') return regex - \ No newline at end of file + def handle_capability(self, resource): + """Returns the severity of a resource or raises an""" + if self.severity['CAPABILITIES'].get(resource, False): + raise("unexpected capability rank input: %s\n"%resource) + return self.severity['CAPABILITIES'][resource] + + def check_subtree(self, tree, mode, sev, segments): + """Returns the max severity from the regex tree""" + first = segments[0] + rest = segments[1:] + path = '/'.join([first]+rest) + # Check if we have a matching directory tree to descend into + if tree.get(first, False): + sev = self.check_subtree(tree[first], mode, sev, rest) + # If severity still not found, match against globs + if sev == None: + # Match against all globs at this directory level + for chunk in tree.keys(): + if '*' in chunk: + # Match rest of the path + if re.search("^"+chunk, path): + # Find max rank + if tree[chunk].get("AA_RANK", False): + for m in mode: + if sev == None or tree[chunk]["AA_RANK"].get(m, -1) > sev: + sev = tree[chunk]["AA_RANK"][m] + return sev + + def handle_file(self, resource, mode): + """Returns the severity for the file, default value if no match found""" + resource = resource[1:] # remove initial / from path + pieces = resource.split('/') # break path into directory level chunks + sev = None + # Check for an exact match in the db + if self.severity['FILES'].get(resource, False): + # Find max value among the given modes + for m in mode: + if sev == None or self.severity['FILES'][resource].get(m, -1) > sev: + sev = self.severity['FILES'][resource].get(m, None) + else: + # Search regex tree for matching glob + sev = self.check_subtree(self.severity['REGEXPS', mode, sev, pieces]) + if sev == None: + # Return default rank if severity cannot be found + return self.severity['DEFAULT_RANK'] + else: + return sev + + def rank(self, resource, mode=None): + """Returns the rank for the resource file/capability""" + if resource[0] == '/': # file resource + return self.handle_file(resource, mode) + elif resource[0:4] == 'CAP_': # capability resource + return self.handle_capability(resource) + else: + raise("unexpected rank input: %s\n"%resource) + # return "unexpected rank input: %s\n"%resource \ No newline at end of file From c70af14af3392e149248eb0bce3fb72a70cbeb7a Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 21 Jun 2013 20:08:32 +0530 Subject: [PATCH 010/183] modified severity and testing modules --- Testing/severity.db | 460 +++++++++++++++++++++++++++++++++++++++ Testing/severity_test.py | 33 +++ lib/severity.py | 31 +-- 3 files changed, 511 insertions(+), 13 deletions(-) create mode 100644 Testing/severity.db create mode 100644 Testing/severity_test.py diff --git a/Testing/severity.db b/Testing/severity.db new file mode 100644 index 000000000..f15fae3c9 --- /dev/null +++ b/Testing/severity.db @@ -0,0 +1,460 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2002-2005 Novell/SUSE +# +# 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. +# +# ------------------------------------------------------------------ + +# Allow this process to 0wn the machine: + CAP_SYS_ADMIN 10 + CAP_SYS_CHROOT 10 + CAP_SYS_MODULE 10 + CAP_SYS_PTRACE 10 + CAP_SYS_RAWIO 10 + CAP_MAC_ADMIN 10 + CAP_MAC_OVERRIDE 10 +# Allow other processes to 0wn the machine: + CAP_SETPCAP 9 + CAP_SETFCAP 9 + CAP_CHOWN 9 + CAP_FSETID 9 + CAP_MKNOD 9 + CAP_LINUX_IMMUTABLE 9 + CAP_DAC_OVERRIDE 9 + CAP_SETGID 9 + CAP_SETUID 9 + CAP_FOWNER 9 +# Denial of service, bypass audit controls, information leak + CAP_SYS_TIME 8 + CAP_NET_ADMIN 8 + CAP_SYS_RESOURCE 8 + CAP_KILL 8 + CAP_IPC_OWNER 8 + CAP_SYS_PACCT 8 + CAP_SYS_BOOT 8 + CAP_NET_BIND_SERVICE 8 + CAP_NET_RAW 8 + CAP_SYS_NICE 8 + CAP_LEASE 8 + CAP_IPC_LOCK 8 + CAP_SYS_TTY_CONFIG 8 + CAP_AUDIT_CONTROL 8 + CAP_AUDIT_WRITE 8 + CAP_SYSLOG 8 + CAP_WAKE_ALARM 8 + CAP_BLOCK_SUSPEND 8 + CAP_DAC_READ_SEARCH 7 +# unused + CAP_NET_BROADCAST 0 + +# filename r w x +# 'hard drives' are generally 4 10 0 +/**/lost+found/** 5 5 0 +/boot/** 7 10 0 +/etc/passwd* 4 8 0 +/etc/group* 4 8 0 +/etc/shadow* 7 9 0 +/etc/shadow* 7 9 0 +/home/*/.ssh/** 7 9 0 +/home/*/.gnupg/** 5 7 0 +/home/** 4 6 0 +/srv/** 4 6 0 +/proc/** 6 9 0 +/proc/sys/kernel/hotplug 2 10 0 +/proc/sys/kernel/modprobe 2 10 0 +/proc/kallsyms 7 0 0 +/sys/** 4 8 0 +/sys/power/state 2 8 0 +/sys/firmware/** 2 10 0 +/dev/pts/* 8 9 0 +/dev/ptmx 8 9 0 +/dev/pty* 8 9 0 +/dev/null 0 0 0 +/dev/adbmouse 3 8 0 +/dev/ataraid 9 10 0 +/dev/zero 0 0 0 +/dev/agpgart* 8 10 0 +/dev/aio 3 3 0 +/dev/cbd/* 5 5 0 +/dev/cciss/* 4 10 0 +/dev/capi* 4 6 0 +/dev/cfs0 4 10 0 +/dev/compaq/* 4 10 0 +/dev/cdouble* 4 8 0 +/dev/cpu** 5 5 0 +/dev/cpu**microcode 1 10 0 +/dev/double* 4 8 0 +/dev/hd* 4 10 0 +/dev/sd* 4 10 0 +/dev/ida/* 4 10 0 +/dev/input/* 4 8 0 +/dev/mapper/control 4 10 0 +/dev/*mem 8 10 0 +/dev/loop* 4 10 0 +/dev/lp* 0 4 0 +/dev/md* 4 10 0 +/dev/msr 4 10 0 +/dev/nb* 4 10 0 +/dev/ram* 8 10 0 +/dev/rd/* 4 10 0 +/dev/*random 3 1 0 +/dev/sbpcd* 4 0 0 +/dev/rtc 6 0 0 +/dev/sd* 4 10 0 +/dev/sc* 4 10 0 +/dev/sg* 4 10 0 +/dev/st* 4 10 0 +/dev/snd/* 3 8 0 +/dev/usb/mouse* 4 6 0 +/dev/usb/hid* 4 6 0 +/dev/usb/tty* 4 6 0 +/dev/tty* 8 9 0 +/dev/stderr 0 0 0 +/dev/stdin 0 0 0 +/dev/stdout 0 0 0 +/dev/ubd* 4 10 0 +/dev/usbmouse* 4 6 0 +/dev/userdma 8 10 0 +/dev/vcs* 8 9 0 +/dev/xta* 4 10 0 +/dev/zero 0 0 0 +/dev/inittcl 8 10 0 +/dev/log 5 7 0 +/etc/fstab 3 8 0 +/etc/mtab 3 5 0 +/etc/SuSEconfig/* 1 8 0 +/etc/X11/* 2 7 0 +/etc/X11/xinit/* 2 8 0 +/etc/SuSE-release 1 5 0 +/etc/issue* 1 3 0 +/etc/motd 1 3 0 +/etc/aliases.d/* 1 7 0 +/etc/cron* 1 9 0 +/etc/cups/* 2 7 0 +/etc/default/* 3 8 0 +/etc/init.d/* 1 10 0 +/etc/permissions.d/* 1 8 0 +/etc/ppp/* 2 6 0 +/etc/ppp/*secrets 8 6 0 +/etc/profile.d/* 1 8 0 +/etc/skel/* 0 7 0 +/etc/sysconfig/* 4 10 0 +/etc/xinetd.d/* 1 9 0 +/etc/termcap/* 1 4 0 +/etc/ld.so.* 1 9 0 +/etc/pam.d/* 3 9 0 +/etc/udev/* 3 9 0 +/etc/insserv.conf 3 6 0 +/etc/security/* 1 9 0 +/etc/securetty 0 7 0 +/etc/sudoers 4 9 0 +/etc/hotplug/* 2 10 0 +/etc/xinitd.conf 1 9 0 +/etc/gpm/* 2 10 0 +/etc/ssl/** 2 7 0 +/etc/shadow* 5 9 0 +/etc/bash.bashrc 1 9 0 +/etc/csh.cshrc 1 9 0 +/etc/csh.login 1 9 0 +/etc/inittab 1 10 0 +/etc/profile* 1 9 0 +/etc/shells 1 5 0 +/etc/alternatives 1 6 0 +/etc/sysctl.conf 3 7 0 +/etc/dev.d/* 1 8 0 +/etc/manpath.config 1 6 0 +/etc/permissions* 1 8 0 +/etc/evms.conf 3 8 0 +/etc/exports 3 8 0 +/etc/samba/* 5 8 0 +/etc/ssh/* 3 8 0 +/etc/ssh/ssh_host_*key 8 8 0 +/etc/krb5.conf 4 8 0 +/etc/ntp.conf 3 8 0 +/etc/auto.* 3 8 0 +/etc/postfix/* 3 7 0 +/etc/postfix/*passwd* 6 7 0 +/etc/postfix/*cert* 6 7 0 +/etc/foomatic/* 3 5 0 +/etc/printcap 3 5 0 +/etc/youservers 4 9 0 +/etc/grub.conf 7 10 0 +/etc/modules.conf 4 10 0 +/etc/resolv.conf 2 7 0 +/etc/apache2/** 3 7 0 +/etc/apache2/**ssl** 7 7 0 +/etc/subdomain.d/** 6 10 0 +/etc/apparmor.d/** 6 10 0 +/etc/apparmor/** 6 10 0 +/var/log/** 3 8 0 +/var/adm/SuSEconfig/** 3 8 0 +/var/adm/** 3 7 0 +/var/lib/rpm/** 4 8 0 +/var/run/nscd/* 3 3 0 +/var/run/.nscd_socket 3 3 0 +/usr/share/doc/** 1 1 0 +/usr/share/man/** 3 5 0 +/usr/X11/man/** 3 5 0 +/usr/share/info/** 2 4 0 +/usr/share/java/** 2 5 0 +/usr/share/locale/** 2 4 0 +/usr/share/sgml/** 2 4 0 +/usr/share/YaST2/** 3 9 0 +/usr/share/ghostscript/** 3 5 0 +/usr/share/terminfo/** 1 8 0 +/usr/share/latex2html/** 2 4 0 +/usr/share/cups/** 5 6 0 +/usr/share/susehelp/** 2 6 0 +/usr/share/susehelp/cgi-bin/** 3 7 7 +/usr/share/zoneinfo/** 2 7 0 +/usr/share/zsh/** 3 6 0 +/usr/share/vim/** 3 8 0 +/usr/share/groff/** 3 7 0 +/usr/share/vnc/** 3 8 0 +/usr/share/wallpapers/** 2 4 0 +/usr/X11** 3 8 5 +/usr/X11*/bin/XFree86 3 8 8 +/usr/X11*/bin/Xorg 3 8 8 +/usr/X11*/bin/sux 3 8 8 +/usr/X11*/bin/xconsole 3 7 7 +/usr/X11*/bin/xhost 3 7 7 +/usr/X11*/bin/xauth 3 7 7 +/usr/X11*/bin/ethereal 3 6 8 +/usr/lib/ooo-** 3 6 5 +/usr/lib/lsb/** 2 8 8 +/usr/lib/pt_chwon 2 8 5 +/usr/lib/tcl** 2 5 3 +/usr/lib/lib*so* 3 8 4 +/usr/lib/iptables/* 2 8 2 +/usr/lib/perl5/** 4 10 6 +/usr/lib/gconv/* 4 7 4 +/usr/lib/locale/** 4 8 0 +/usr/lib/jvm/** 5 7 5 +/usr/lib/sasl*/** 5 8 4 +/usr/lib/jvm-exports/** 5 7 5 +/usr/lib/jvm-private/** 5 7 5 +/usr/lib/python*/** 5 7 5 +/usr/lib/libkrb5* 4 8 4 +/usr/lib/postfix/* 4 7 4 +/usr/lib/rpm/** 4 8 6 +/usr/lib/rpm/gnupg/** 4 9 0 +/usr/lib/apache2** 4 7 4 +/usr/lib/mailman/** 4 6 4 +/usr/bin/ldd 1 7 4 +/usr/bin/netcat 5 7 8 +/usr/bin/clear 2 6 3 +/usr/bin/reset 2 6 3 +/usr/bin/tput 2 6 3 +/usr/bin/tset 2 6 3 +/usr/bin/file 2 6 3 +/usr/bin/ftp 3 7 5 +/usr/bin/busybox 4 8 6 +/usr/bin/rbash 4 8 5 +/usr/bin/screen 3 6 5 +/usr/bin/getfacl 3 7 4 +/usr/bin/setfacl 3 7 9 +/usr/bin/*awk* 3 7 7 +/usr/bin/sudo 2 9 10 +/usr/bin/lsattr 2 6 5 +/usr/bin/chattr 2 7 8 +/usr/bin/sed 3 7 6 +/usr/bin/grep 2 7 2 +/usr/bin/chroot 2 6 10 +/usr/bin/dircolors 2 9 3 +/usr/bin/cut 2 7 2 +/usr/bin/du 2 7 3 +/usr/bin/env 2 7 2 +/usr/bin/head 2 7 2 +/usr/bin/tail 2 7 2 +/usr/bin/install 2 8 4 +/usr/bin/link 2 6 4 +/usr/bin/logname 2 6 2 +/usr/bin/md5sum 2 8 3 +/usr/bin/mkfifo 2 6 10 +/usr/bin/nice 2 7 7 +/usr/bin/nohup 2 7 7 +/usr/bin/printf 2 7 1 +/usr/bin/readlink 2 7 3 +/usr/bin/seq 2 7 1 +/usr/bin/sha1sum 2 8 3 +/usr/bin/shred 2 7 3 +/usr/bin/sort 2 7 3 +/usr/bin/split 2 7 3 +/usr/bin/stat 2 7 4 +/usr/bin/sum 2 8 3 +/usr/bin/tac 2 7 3 +/usr/bin/tail 3 8 4 +/usr/bin/tee 2 7 3 +/usr/bin/test 2 8 4 +/usr/bin/touch 2 7 3 +/usr/bin/tr 2 8 3 +/usr/bin/tsort 2 7 3 +/usr/bin/tty 2 7 3 +/usr/bin/unexpand 2 7 3 +/usr/bin/uniq 2 7 3 +/usr/bin/unlink 2 8 4 +/usr/bin/uptime 2 7 3 +/usr/bin/users 2 8 4 +/usr/bin/vdir 2 8 4 +/usr/bin/wc 2 7 3 +/usr/bin/who 2 8 4 +/usr/bin/whoami 2 8 4 +/usr/bin/yes 1 6 1 +/usr/bin/ed 2 7 5 +/usr/bin/red 2 7 4 +/usr/bin/find 2 8 5 +/usr/bin/xargs 2 7 5 +/usr/bin/ispell 2 7 4 +/usr/bin/a2p 2 7 5 +/usr/bin/perlcc 2 7 5 +/usr/bin/perldoc 2 7 5 +/usr/bin/pod2* 2 7 5 +/usr/bin/prove 2 7 5 +/usr/bin/perl 2 10 7 +/usr/bin/perl* 2 10 7 +/usr/bin/suidperl 2 8 8 +/usr/bin/csh 2 8 8 +/usr/bin/tcsh 2 8 8 +/usr/bin/tree 2 6 5 +/usr/bin/last 2 7 5 +/usr/bin/lastb 2 7 5 +/usr/bin/utmpdump 2 6 5 +/usr/bin/alsamixer 2 6 8 +/usr/bin/amixer 2 6 8 +/usr/bin/amidi 2 6 8 +/usr/bin/aoss 2 6 8 +/usr/bin/aplay 2 6 8 +/usr/bin/aplaymidi 2 6 8 +/usr/bin/arecord 2 6 8 +/usr/bin/arecordmidi 2 6 8 +/usr/bin/aseqnet 2 6 8 +/usr/bin/aserver 2 6 8 +/usr/bin/iecset 2 6 8 +/usr/bin/rview 2 6 5 +/usr/bin/ex 2 7 5 +/usr/bin/enscript 2 6 5 +/usr/bin/genscript 2 6 5 +/usr/bin/xdelta 2 6 5 +/usr/bin/edit 2 6 5 +/usr/bin/vimtutor 2 6 5 +/usr/bin/rvim 2 6 5 +/usr/bin/vim 2 8 7 +/usr/bin/vimdiff 2 8 7 +/usr/bin/aspell 2 6 5 +/usr/bin/xxd 2 6 5 +/usr/bin/spell 2 6 5 +/usr/bin/eqn 2 6 5 +/usr/bin/eqn2graph 2 6 5 +/usr/bin/word-list-compress 2 6 4 +/usr/bin/afmtodit 2 6 4 +/usr/bin/hpf2dit 2 6 4 +/usr/bin/geqn 2 6 4 +/usr/bin/grn 2 6 4 +/usr/bin/grodvi 2 6 4 +/usr/bin/groff 2 6 5 +/usr/bin/groffer 2 6 4 +/usr/bin/grolj4 2 6 4 +/usr/bin/grotty 2 6 4 +/usr/bin/gtbl 2 6 4 +/usr/bin/pic2graph 2 6 4 +/usr/bin/indxbib 2 6 4 +/usr/bin/lkbib 2 6 4 +/usr/bin/lookbib 2 6 4 +/usr/bin/mmroff 2 6 4 +/usr/bin/neqn 2 6 4 +/usr/bin/pfbtops 2 6 4 +/usr/bin/pic 2 6 4 +/usr/bin/tfmtodit 2 6 4 +/usr/bin/tbl 2 6 4 +/usr/bin/post-grohtml 2 6 4 +/usr/bin/pre-grohtml 2 6 4 +/usr/bin/refer 2 6 4 +/usr/bin/soelim 2 6 4 +/usr/bin/disable-paste 2 6 6 +/usr/bin/troff 2 6 4 +/usr/bin/strace-graph 2 6 4 +/usr/bin/gpm-root 2 6 7 +/usr/bin/hltest 2 6 7 +/usr/bin/mev 2 6 6 +/usr/bin/mouse-test 2 6 6 +/usr/bin/strace 2 8 9 +/usr/bin/scsiformat 2 7 10 +/usr/bin/lsscsi 2 7 7 +/usr/bin/scsiinfo 2 7 7 +/usr/bin/sg_* 2 7 7 +/usr/bin/build-classpath 2 6 6 +/usr/bin/build-classpath-directory 2 6 6 +/usr/bin/build-jar-repository 2 6 6 +/usr/bin/diff-jars 2 6 6 +/usr/bin/jvmjar 2 6 6 +/usr/bin/rebuild-jar-repository 2 6 6 +/usr/bin/scriptreplay 2 6 5 +/usr/bin/cal 2 6 3 +/usr/bin/chkdupexe 2 6 5 +/usr/bin/col 2 6 4 +/usr/bin/colcrt 2 6 4 +/usr/bin/colrm 2 6 3 +/usr/bin/column 2 6 4 +/usr/bin/cytune 2 6 6 +/usr/bin/ddate 2 6 3 +/usr/bin/fdformat 2 6 6 +/usr/bin/getopt 2 8 6 +/usr/bin/hexdump 2 6 4 +/usr/bin/hostid 2 6 4 +/usr/bin/ipcrm 2 7 7 +/usr/bin/ipcs 2 7 6 +/usr/bin/isosize 2 6 4 +/usr/bin/line 2 6 4 +/usr/bin/look 2 6 5 +/usr/bin/mcookie 2 7 5 +/usr/bin/mesg 2 6 4 +/usr/bin/namei 2 6 5 +/usr/bin/rename 2 6 5 +/usr/bin/renice 2 6 7 +/usr/bin/rev 2 6 5 +/usr/bin/script 2 6 6 +/usr/bin/ChangeSymlinks 2 8 8 +/usr/bin/setfdprm 2 6 7 +/usr/bin/setsid 2 6 3 +/usr/bin/setterm 2 6 5 +/usr/bin/tailf 2 6 4 +/usr/bin/time 2 6 4 +/usr/bin/ul 2 6 4 +/usr/bin/wall 2 6 5 +/usr/bin/whereis 2 6 4 +/usr/bin/which 2 6 3 +/usr/bin/c_rehash 2 7 6 +/usr/bin/openssl 2 8 6 +/usr/bin/lsdev 2 6 5 +/usr/bin/procinfo 2 6 5 +/usr/bin/socklist 2 6 5 +/usr/bin/filesize 2 6 3 +/usr/bin/linkto 2 6 3 +/usr/bin/mkinfodir 2 6 5 +/usr/bin/old 2 6 4 +/usr/bin/rpmlocate 2 6 5 +/usr/bin/safe-rm 2 8 6 +/usr/bin/safe-rmdir 2 8 6 +/usr/bin/setJava 2 6 1 +/usr/bin/vmstat 2 6 4 +/usr/bin/top 2 6 6 +/usr/bin/pinentry* 2 7 6 +/usr/bin/free 2 8 4 +/usr/bin/pmap 2 6 5 +/usr/bin/slabtop 2 6 4 +/usr/bin/tload 2 6 4 +/usr/bin/watch 2 6 3 +/usr/bin/w 2 6 4 +/usr/bin/pstree.x11 2 6 4 +/usr/bin/pstree 2 6 4 +/usr/bin/snice 2 6 6 +/usr/bin/skill 2 6 7 +/usr/bin/pgrep 2 6 4 +/usr/bin/killall 2 6 7 +/usr/bin/curl 2 7 7 +/usr/bin/slptool 2 7 8 +/usr/bin/ldap* 2 7 7 +/usr/bin/whatis 2 7 5 diff --git a/Testing/severity_test.py b/Testing/severity_test.py new file mode 100644 index 000000000..1dd975e40 --- /dev/null +++ b/Testing/severity_test.py @@ -0,0 +1,33 @@ +''' +Created on Jun 21, 2013 + +@author: kshitij +''' +import sys +import unittest +sys.path.append('../lib') + +import severity + +class Test(unittest.TestCase): + + + def testName(self): + z = severity.Severity() + s = severity.Severity('severity.db') + cases_file = [('/usr/bin/whatis', 'x'), ('/etc', 'x'), ('/dev/doublehit', 'x')] + cases_cap = ['CAP_SETPCAP', 'CAP_KILL'] + for case in cases_file: + rank = s.rank(case[0], case[1]) + self.assertIn(rank, range(0,11), "Invalid rank") + print(rank) + for case in cases_cap: + rank = s.rank(case) + self.assertIn(rank, range(0,11), "Invalid rank") + print(rank) + + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] + unittest.main() \ No newline at end of file diff --git a/lib/severity.py b/lib/severity.py index 35e54db2c..078a93a2a 100644 --- a/lib/severity.py +++ b/lib/severity.py @@ -10,11 +10,11 @@ class Severity: self.severity['REGEXPS'] = {} self.severity['DEFAULT_RANK'] = default_rank if not dbname: - return self.severity + return None try: database = open(dbname, 'r') except IOError: - raise("Could not open severity database %s"%dbname) + raise IOError("Could not open severity database %s"%dbname) for line in database: line = line.strip() # or only rstrip and lstrip? if line == '' or line.startswith('#') : @@ -22,6 +22,7 @@ class Severity: if line.startswith('/'): try: path, read, write, execute = line.split() + read, write, execute = int(read), int(write), int(execute) except ValueError: raise("Insufficient values for permissions in line: %s"%line) else: @@ -45,11 +46,12 @@ class Severity: elif line.startswith('CAP_'): try: resource, severity = line.split() + severity = int(severity) except ValueError: - raise("No severity value present in line: %s"%line) + raise ValueError("No severity value present in line: %s"%line) else: if severity not in range(0,11): - raise("Inappropriate severity value present in line: %s"%line) + raise ValueError("Inappropriate severity value present in line: %s"%line) self.severity['CAPABILITIES'][resource] = severity else: print("unexpected database line: %s \nin file: %s"%(line,dbname)) @@ -76,13 +78,17 @@ class Severity: def handle_capability(self, resource): """Returns the severity of a resource or raises an""" - if self.severity['CAPABILITIES'].get(resource, False): - raise("unexpected capability rank input: %s\n"%resource) - return self.severity['CAPABILITIES'][resource] + if resource in self.severity['CAPABILITIES'].keys(): + return self.severity['CAPABILITIES'][resource] + raise ValueError("unexpected capability rank input: %s"%resource) + def check_subtree(self, tree, mode, sev, segments): """Returns the max severity from the regex tree""" - first = segments[0] + if len(segments) == 0: + first = '' + else: + first = segments[0] rest = segments[1:] path = '/'.join([first]+rest) # Check if we have a matching directory tree to descend into @@ -96,7 +102,7 @@ class Severity: # Match rest of the path if re.search("^"+chunk, path): # Find max rank - if tree[chunk].get("AA_RANK", False): + if "AA_RANK" in tree[chunk].keys(): for m in mode: if sev == None or tree[chunk]["AA_RANK"].get(m, -1) > sev: sev = tree[chunk]["AA_RANK"][m] @@ -108,14 +114,14 @@ class Severity: pieces = resource.split('/') # break path into directory level chunks sev = None # Check for an exact match in the db - if self.severity['FILES'].get(resource, False): + if resource in self.severity['FILES'].keys(): # Find max value among the given modes for m in mode: if sev == None or self.severity['FILES'][resource].get(m, -1) > sev: sev = self.severity['FILES'][resource].get(m, None) else: # Search regex tree for matching glob - sev = self.check_subtree(self.severity['REGEXPS', mode, sev, pieces]) + sev = self.check_subtree(self.severity['REGEXPS'], mode, sev, pieces) if sev == None: # Return default rank if severity cannot be found return self.severity['DEFAULT_RANK'] @@ -129,5 +135,4 @@ class Severity: elif resource[0:4] == 'CAP_': # capability resource return self.handle_capability(resource) else: - raise("unexpected rank input: %s\n"%resource) - # return "unexpected rank input: %s\n"%resource \ No newline at end of file + raise ValueError("unexpected rank input: %s"%resource) \ No newline at end of file From 1c10749be297c459b03c86030eaa0281511c1c90 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 23 Jun 2013 05:04:22 +0530 Subject: [PATCH 011/183] added variable handling also added a loader for all paths --- Testing/severity_broken.db | 460 +++++++++++++++++++++++++++++++++++++ Testing/severity_test.py | 51 +++- Testing/variable_finder.py | 19 ++ lib/severity.py | 71 +++++- 4 files changed, 579 insertions(+), 22 deletions(-) create mode 100644 Testing/severity_broken.db create mode 100644 Testing/variable_finder.py diff --git a/Testing/severity_broken.db b/Testing/severity_broken.db new file mode 100644 index 000000000..417945324 --- /dev/null +++ b/Testing/severity_broken.db @@ -0,0 +1,460 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2002-2005 Novell/SUSE +# +# 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. +# +# ------------------------------------------------------------------ + +# Allow this process to 0wn the machine: + CAP_SYS_ADMIN 10 + CAP_SYS_CHROOT 10 + CAP_SYS_MODULE + CAP_SYS_PTRACE 10 + CAP_SYS_RAWIO 10 + CAP_MAC_ADMIN 10 + CAP_MAC_OVERRIDE 10 +# Allow other processes to 0wn the machine: + CAP_SETPCAP 9 + CAP_SETFCAP 9 + CAP_CHOWN 9 + CAP_FSETID 9 + CAP_MKNOD 9 + CAP_LINUX_IMMUTABLE 9 + CAP_DAC_OVERRIDE 9 + CAP_SETGID 9 + CAP_SETUID 9 + CAP_FOWNER 9 +# Denial of service, bypass audit controls, information leak + CAP_SYS_TIME 8 + CAP_NET_ADMIN 8 + CAP_SYS_RESOURCE 8 + CAP_KILL 8 + CAP_IPC_OWNER 8 + CAP_SYS_PACCT 8 + CAP_SYS_BOOT 8 + CAP_NET_BIND_SERVICE 8 + CAP_NET_RAW 8 + CAP_SYS_NICE 8 + CAP_LEASE 8 + CAP_IPC_LOCK 8 + CAP_SYS_TTY_CONFIG 8 + CAP_AUDIT_CONTROL 8 + CAP_AUDIT_WRITE 8 + CAP_SYSLOG 8 + CAP_WAKE_ALARM 8 + CAP_BLOCK_SUSPEND 8 + CAP_DAC_READ_SEARCH 7 +# unused + CAP_NET_BROADCAST 0 + +# filename r w x +# 'hard drives' are generally 4 10 0 +/**/lost+found/** 5 5 0 +/boot/** 7 10 0 +/etc/passwd* 4 8 0 +/etc/group* 4 8 0 +/etc/shadow* 7 9 0 +/etc/shadow* 7 9 0 +/home/*/.ssh/** 7 9 0 +/home/*/.gnupg/** 5 7 0 +/home/** 4 6 0 +/srv/** 4 6 0 +/proc/** 6 9 0 +/proc/sys/kernel/hotplug 2 10 0 +/proc/sys/kernel/modprobe 2 10 0 +/proc/kallsyms 7 0 0 +/sys/** 4 8 0 +/sys/power/state 2 8 0 +/sys/firmware/** 2 10 0 +/dev/pts/* 8 9 0 +/dev/ptmx 8 9 0 +/dev/pty* 8 9 0 +/dev/null 0 0 0 +/dev/adbmouse 3 8 0 +/dev/ataraid 9 10 0 +/dev/zero 0 0 0 +/dev/agpgart* 8 10 0 +/dev/aio 3 3 0 +/dev/cbd/* 5 5 0 +/dev/cciss/* 4 10 0 +/dev/capi* 4 6 0 +/dev/cfs0 4 10 0 +/dev/compaq/* 4 10 0 +/dev/cdouble* 4 8 0 +/dev/cpu** 5 5 0 +/dev/cpu**microcode 1 10 0 +/dev/double* 4 8 0 +/dev/hd* 4 10 0 +/dev/sd* 4 10 0 +/dev/ida/* 4 10 0 +/dev/input/* 4 8 0 +/dev/mapper/control 4 10 0 +/dev/*mem 8 10 0 +/dev/loop* 4 10 0 +/dev/lp* 0 4 0 +/dev/md* 4 10 0 +/dev/msr 4 10 0 +/dev/nb* 4 10 0 +/dev/ram* 8 10 0 +/dev/rd/* 4 10 0 +/dev/*random 3 1 0 +/dev/sbpcd* 4 0 0 +/dev/rtc 6 0 0 +/dev/sd* 4 10 0 +/dev/sc* 4 10 0 +/dev/sg* 4 10 0 +/dev/st* 4 10 0 +/dev/snd/* 3 8 0 +/dev/usb/mouse* 4 6 0 +/dev/usb/hid* 4 6 0 +/dev/usb/tty* 4 6 0 +/dev/tty* 8 9 0 +/dev/stderr 0 0 0 +/dev/stdin 0 0 0 +/dev/stdout 0 0 0 +/dev/ubd* 4 10 0 +/dev/usbmouse* 4 6 0 +/dev/userdma 8 10 0 +/dev/vcs* 8 9 0 +/dev/xta* 4 10 0 +/dev/zero 0 0 0 +/dev/inittcl 8 10 0 +/dev/log 5 7 0 +/etc/fstab 3 8 0 +/etc/mtab 3 5 0 +/etc/SuSEconfig/* 1 8 0 +/etc/X11/* 2 7 0 +/etc/X11/xinit/* 2 8 0 +/etc/SuSE-release 1 5 0 +/etc/issue* 1 3 0 +/etc/motd 1 3 0 +/etc/aliases.d/* 1 7 0 +/etc/cron* 1 9 0 +/etc/cups/* 2 7 0 +/etc/default/* 3 8 0 +/etc/init.d/* 1 10 0 +/etc/permissions.d/* 1 8 0 +/etc/ppp/* 2 6 0 +/etc/ppp/*secrets 8 6 0 +/etc/profile.d/* 1 8 0 +/etc/skel/* 0 7 0 +/etc/sysconfig/* 4 10 0 +/etc/xinetd.d/* 1 9 0 +/etc/termcap/* 1 4 0 +/etc/ld.so.* 1 9 0 +/etc/pam.d/* 3 9 0 +/etc/udev/* 3 9 0 +/etc/insserv.conf 3 6 0 +/etc/security/* 1 9 0 +/etc/securetty 0 7 0 +/etc/sudoers 4 9 0 +/etc/hotplug/* 2 10 0 +/etc/xinitd.conf 1 9 0 +/etc/gpm/* 2 10 0 +/etc/ssl/** 2 7 0 +/etc/shadow* 5 9 0 +/etc/bash.bashrc 1 9 0 +/etc/csh.cshrc 1 9 0 +/etc/csh.login 1 9 0 +/etc/inittab 1 10 0 +/etc/profile* 1 9 0 +/etc/shells 1 5 0 +/etc/alternatives 1 6 0 +/etc/sysctl.conf 3 7 0 +/etc/dev.d/* 1 8 0 +/etc/manpath.config 1 6 0 +/etc/permissions* 1 8 0 +/etc/evms.conf 3 8 0 +/etc/exports 3 8 0 +/etc/samba/* 5 8 0 +/etc/ssh/* 3 8 0 +/etc/ssh/ssh_host_*key 8 8 0 +/etc/krb5.conf 4 8 0 +/etc/ntp.conf 3 8 0 +/etc/auto.* 3 8 0 +/etc/postfix/* 3 7 0 +/etc/postfix/*passwd* 6 7 0 +/etc/postfix/*cert* 6 7 0 +/etc/foomatic/* 3 5 0 +/etc/printcap 3 5 0 +/etc/youservers 4 9 0 +/etc/grub.conf 7 10 0 +/etc/modules.conf 4 10 0 +/etc/resolv.conf 2 7 0 +/etc/apache2/** 3 7 0 +/etc/apache2/**ssl** 7 7 0 +/etc/subdomain.d/** 6 10 0 +/etc/apparmor.d/** 6 10 0 +/etc/apparmor/** 6 10 0 +/var/log/** 3 8 0 +/var/adm/SuSEconfig/** 3 8 0 +/var/adm/** 3 7 0 +/var/lib/rpm/** 4 8 0 +/var/run/nscd/* 3 3 0 +/var/run/.nscd_socket 3 3 0 +/usr/share/doc/** 1 1 0 +/usr/share/man/** 3 5 0 +/usr/X11/man/** 3 5 0 +/usr/share/info/** 2 4 0 +/usr/share/java/** 2 5 0 +/usr/share/locale/** 2 4 0 +/usr/share/sgml/** 2 4 0 +/usr/share/YaST2/** 3 9 0 +/usr/share/ghostscript/** 3 5 0 +/usr/share/terminfo/** 1 8 0 +/usr/share/latex2html/** 2 4 0 +/usr/share/cups/** 5 6 0 +/usr/share/susehelp/** 2 6 0 +/usr/share/susehelp/cgi-bin/** 3 7 7 +/usr/share/zoneinfo/** 2 7 0 +/usr/share/zsh/** 3 6 0 +/usr/share/vim/** 3 8 0 +/usr/share/groff/** 3 7 0 +/usr/share/vnc/** 3 8 0 +/usr/share/wallpapers/** 2 4 0 +/usr/X11** 3 8 5 +/usr/X11*/bin/XFree86 3 8 8 +/usr/X11*/bin/Xorg 3 8 8 +/usr/X11*/bin/sux 3 8 8 +/usr/X11*/bin/xconsole 3 7 7 +/usr/X11*/bin/xhost 3 7 7 +/usr/X11*/bin/xauth 3 7 7 +/usr/X11*/bin/ethereal 3 6 8 +/usr/lib/ooo-** 3 6 5 +/usr/lib/lsb/** 2 8 8 +/usr/lib/pt_chwon 2 8 5 +/usr/lib/tcl** 2 5 3 +/usr/lib/lib*so* 3 8 4 +/usr/lib/iptables/* 2 8 2 +/usr/lib/perl5/** 4 10 6 +/usr/lib/gconv/* 4 7 4 +/usr/lib/locale/** 4 8 0 +/usr/lib/jvm/** 5 7 5 +/usr/lib/sasl*/** 5 8 4 +/usr/lib/jvm-exports/** 5 7 5 +/usr/lib/jvm-private/** 5 7 5 +/usr/lib/python*/** 5 7 5 +/usr/lib/libkrb5* 4 8 4 +/usr/lib/postfix/* 4 7 4 +/usr/lib/rpm/** 4 8 6 +/usr/lib/rpm/gnupg/** 4 9 0 +/usr/lib/apache2** 4 7 4 +/usr/lib/mailman/** 4 6 4 +/usr/bin/ldd 1 7 4 +/usr/bin/netcat 5 7 8 +/usr/bin/clear 2 6 3 +/usr/bin/reset 2 6 3 +/usr/bin/tput 2 6 3 +/usr/bin/tset 2 6 3 +/usr/bin/file 2 6 3 +/usr/bin/ftp 3 7 5 +/usr/bin/busybox 4 8 6 +/usr/bin/rbash 4 8 5 +/usr/bin/screen 3 6 5 +/usr/bin/getfacl 3 7 4 +/usr/bin/setfacl 3 7 9 +/usr/bin/*awk* 3 7 7 +/usr/bin/sudo 2 9 10 +/usr/bin/lsattr 2 6 5 +/usr/bin/chattr 2 7 8 +/usr/bin/sed 3 7 6 +/usr/bin/grep 2 7 2 +/usr/bin/chroot 2 6 10 +/usr/bin/dircolors 2 9 3 +/usr/bin/cut 2 7 2 +/usr/bin/du 2 7 3 +/usr/bin/env 2 7 2 +/usr/bin/head 2 7 2 +/usr/bin/tail 2 7 2 +/usr/bin/install 2 8 4 +/usr/bin/link 2 6 4 +/usr/bin/logname 2 6 2 +/usr/bin/md5sum 2 8 3 +/usr/bin/mkfifo 2 6 10 +/usr/bin/nice 2 7 7 +/usr/bin/nohup 2 7 7 +/usr/bin/printf 2 7 1 +/usr/bin/readlink 2 7 3 +/usr/bin/seq 2 7 1 +/usr/bin/sha1sum 2 8 3 +/usr/bin/shred 2 7 3 +/usr/bin/sort 2 7 3 +/usr/bin/split 2 7 3 +/usr/bin/stat 2 7 4 +/usr/bin/sum 2 8 3 +/usr/bin/tac 2 7 3 +/usr/bin/tail 3 8 4 +/usr/bin/tee 2 7 3 +/usr/bin/test 2 8 4 +/usr/bin/touch 2 7 3 +/usr/bin/tr 2 8 3 +/usr/bin/tsort 2 7 3 +/usr/bin/tty 2 7 3 +/usr/bin/unexpand 2 7 3 +/usr/bin/uniq 2 7 3 +/usr/bin/unlink 2 8 4 +/usr/bin/uptime 2 7 3 +/usr/bin/users 2 8 4 +/usr/bin/vdir 2 8 4 +/usr/bin/wc 2 7 3 +/usr/bin/who 2 8 4 +/usr/bin/whoami 2 8 4 +/usr/bin/yes 1 6 1 +/usr/bin/ed 2 7 5 +/usr/bin/red 2 7 4 +/usr/bin/find 2 8 5 +/usr/bin/xargs 2 7 5 +/usr/bin/ispell 2 7 4 +/usr/bin/a2p 2 7 5 +/usr/bin/perlcc 2 7 5 +/usr/bin/perldoc 2 7 5 +/usr/bin/pod2* 2 7 5 +/usr/bin/prove 2 7 5 +/usr/bin/perl 2 10 7 +/usr/bin/perl* 2 10 7 +/usr/bin/suidperl 2 8 8 +/usr/bin/csh 2 8 8 +/usr/bin/tcsh 2 8 8 +/usr/bin/tree 2 6 5 +/usr/bin/last 2 7 5 +/usr/bin/lastb 2 7 5 +/usr/bin/utmpdump 2 6 5 +/usr/bin/alsamixer 2 6 8 +/usr/bin/amixer 2 6 8 +/usr/bin/amidi 2 6 8 +/usr/bin/aoss 2 6 8 +/usr/bin/aplay 2 6 8 +/usr/bin/aplaymidi 2 6 8 +/usr/bin/arecord 2 6 8 +/usr/bin/arecordmidi 2 6 8 +/usr/bin/aseqnet 2 6 8 +/usr/bin/aserver 2 6 8 +/usr/bin/iecset 2 6 8 +/usr/bin/rview 2 6 5 +/usr/bin/ex 2 7 5 +/usr/bin/enscript 2 6 5 +/usr/bin/genscript 2 6 5 +/usr/bin/xdelta 2 6 5 +/usr/bin/edit 2 6 5 +/usr/bin/vimtutor 2 6 5 +/usr/bin/rvim 2 6 5 +/usr/bin/vim 2 8 7 +/usr/bin/vimdiff 2 8 7 +/usr/bin/aspell 2 6 5 +/usr/bin/xxd 2 6 5 +/usr/bin/spell 2 6 5 +/usr/bin/eqn 2 6 5 +/usr/bin/eqn2graph 2 6 5 +/usr/bin/word-list-compress 2 6 4 +/usr/bin/afmtodit 2 6 4 +/usr/bin/hpf2dit 2 6 4 +/usr/bin/geqn 2 6 4 +/usr/bin/grn 2 6 4 +/usr/bin/grodvi 2 6 4 +/usr/bin/groff 2 6 5 +/usr/bin/groffer 2 6 4 +/usr/bin/grolj4 2 6 4 +/usr/bin/grotty 2 6 4 +/usr/bin/gtbl 2 6 4 +/usr/bin/pic2graph 2 6 4 +/usr/bin/indxbib 2 6 4 +/usr/bin/lkbib 2 6 4 +/usr/bin/lookbib 2 6 4 +/usr/bin/mmroff 2 6 4 +/usr/bin/neqn 2 6 4 +/usr/bin/pfbtops 2 6 4 +/usr/bin/pic 2 6 4 +/usr/bin/tfmtodit 2 6 4 +/usr/bin/tbl 2 6 4 +/usr/bin/post-grohtml 2 6 4 +/usr/bin/pre-grohtml 2 6 4 +/usr/bin/refer 2 6 4 +/usr/bin/soelim 2 6 4 +/usr/bin/disable-paste 2 6 6 +/usr/bin/troff 2 6 4 +/usr/bin/strace-graph 2 6 4 +/usr/bin/gpm-root 2 6 7 +/usr/bin/hltest 2 6 7 +/usr/bin/mev 2 6 6 +/usr/bin/mouse-test 2 6 6 +/usr/bin/strace 2 8 9 +/usr/bin/scsiformat 2 7 10 +/usr/bin/lsscsi 2 7 7 +/usr/bin/scsiinfo 2 7 7 +/usr/bin/sg_* 2 7 7 +/usr/bin/build-classpath 2 6 6 +/usr/bin/build-classpath-directory 2 6 6 +/usr/bin/build-jar-repository 2 6 6 +/usr/bin/diff-jars 2 6 6 +/usr/bin/jvmjar 2 6 6 +/usr/bin/rebuild-jar-repository 2 6 6 +/usr/bin/scriptreplay 2 6 5 +/usr/bin/cal 2 6 3 +/usr/bin/chkdupexe 2 6 5 +/usr/bin/col 2 6 4 +/usr/bin/colcrt 2 6 4 +/usr/bin/colrm 2 6 3 +/usr/bin/column 2 6 4 +/usr/bin/cytune 2 6 6 +/usr/bin/ddate 2 6 3 +/usr/bin/fdformat 2 6 6 +/usr/bin/getopt 2 8 6 +/usr/bin/hexdump 2 6 4 +/usr/bin/hostid 2 6 4 +/usr/bin/ipcrm 2 7 7 +/usr/bin/ipcs 2 7 6 +/usr/bin/isosize 2 6 4 +/usr/bin/line 2 6 4 +/usr/bin/look 2 6 5 +/usr/bin/mcookie 2 7 5 +/usr/bin/mesg 2 6 4 +/usr/bin/namei 2 6 5 +/usr/bin/rename 2 6 5 +/usr/bin/renice 2 6 7 +/usr/bin/rev 2 6 5 +/usr/bin/script 2 6 6 +/usr/bin/ChangeSymlinks 2 8 8 +/usr/bin/setfdprm 2 6 7 +/usr/bin/setsid 2 6 3 +/usr/bin/setterm 2 6 5 +/usr/bin/tailf 2 6 4 +/usr/bin/time 2 6 4 +/usr/bin/ul 2 6 4 +/usr/bin/wall 2 6 5 +/usr/bin/whereis 2 6 4 +/usr/bin/which 2 6 3 +/usr/bin/c_rehash 2 7 6 +/usr/bin/openssl 2 8 6 +/usr/bin/lsdev 2 6 5 +/usr/bin/procinfo 2 6 5 +/usr/bin/socklist 2 6 5 +/usr/bin/filesize 2 6 3 +/usr/bin/linkto 2 6 3 +/usr/bin/mkinfodir 2 6 5 +/usr/bin/old 2 6 4 +/usr/bin/rpmlocate 2 6 5 +/usr/bin/safe-rm 2 8 6 +/usr/bin/safe-rmdir 2 8 6 +/usr/bin/setJava 2 6 1 +/usr/bin/vmstat 2 6 4 +/usr/bin/top 2 6 6 +/usr/bin/pinentry* 2 7 6 +/usr/bin/free 2 8 4 +/usr/bin/pmap 2 6 5 +/usr/bin/slabtop 2 6 4 +/usr/bin/tload 2 6 4 +/usr/bin/watch 2 6 3 +/usr/bin/w 2 6 4 +/usr/bin/pstree.x11 2 6 4 +/usr/bin/pstree 2 6 4 +/usr/bin/snice 2 6 6 +/usr/bin/skill 2 6 7 +/usr/bin/pgrep 2 6 4 +/usr/bin/killall 2 6 7 +/usr/bin/curl 2 7 7 +/usr/bin/slptool 2 7 8 +/usr/bin/ldap* 2 7 7 +/usr/bin/whatis 2 7 5 diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 1dd975e40..65d40a0fc 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -11,22 +11,49 @@ import severity class Test(unittest.TestCase): - - def testName(self): + def testInvalid(self): + s = severity.Severity('severity.db') + rank = s.rank('/dev/doublehit', 'i') + self.assertEqual(rank, 10, 'Wrong') + try: + broken = severity.Severity('severity_broken.db') + rank = s.rank('CAP_UNKOWN') + rank = s.rank('CAP_K*') + except ValueError: + pass + + def testRank_Test(self): z = severity.Severity() s = severity.Severity('severity.db') - cases_file = [('/usr/bin/whatis', 'x'), ('/etc', 'x'), ('/dev/doublehit', 'x')] - cases_cap = ['CAP_SETPCAP', 'CAP_KILL'] - for case in cases_file: - rank = s.rank(case[0], case[1]) - self.assertIn(rank, range(0,11), "Invalid rank") - print(rank) - for case in cases_cap: - rank = s.rank(case) - self.assertIn(rank, range(0,11), "Invalid rank") - print(rank) + rank = s.rank('/usr/bin/whatis', 'x') + self.assertEqual(rank, 5, 'Wrong rank') + rank = s.rank('/etc', 'x') + self.assertEqual(rank, 10, 'Wrong rank') + rank = s.rank('/dev/doublehit', 'x') + self.assertEqual(rank, 0, 'Wrong rank') + rank = s.rank('/dev/doublehit', 'rx') + self.assertEqual(rank, 4, 'Wrong rank') + rank = s.rank('/dev/doublehit', 'rwx') + self.assertEqual(rank, 8, 'Wrong rank') + rank = s.rank('/dev/tty10', 'rwx') + self.assertEqual(rank, 9, 'Wrong rank') + rank = s.rank('/var/adm/foo/**', 'rx') + self.assertEqual(rank, 3, 'Wrong rank') + rank = s.rank('CAP_KILL') + self.assertEqual(rank, 8, 'Wrong rank') + rank = s.rank('CAP_SETPCAP') + self.assertEqual(rank, 9, 'Wrong rank') + self.assertEqual(s.rank('/etc/apparmor/**', 'r') , 6, 'Invalid Rank') + self.assertEqual(s.rank('/etc/**', 'r') , 10, 'Invalid Rank') + self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') + self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') + self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') + + #self.assertEqual(s.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') + + if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] diff --git a/Testing/variable_finder.py b/Testing/variable_finder.py new file mode 100644 index 000000000..bcdd3bc1b --- /dev/null +++ b/Testing/variable_finder.py @@ -0,0 +1,19 @@ +''' +Created on Jun 22, 2013 + +@author: kshitij +''' +import os +variable = dict() +for root, dirs, files in os.walk('/etc/apparmor.d'): + for file in files: + for line in open(os.path.join(root, file), 'r'): + line.strip() + if line.startswith('@') and '=' in line: + line = line.strip() + line = line.split('=') + variable[line[0]] = [i.strip('"') for i in line[1].split()] #.strip('"') +for i in variable.keys(): + print(i,variable[i]) + + \ No newline at end of file diff --git a/lib/severity.py b/lib/severity.py index 078a93a2a..90a1374fe 100644 --- a/lib/severity.py +++ b/lib/severity.py @@ -1,3 +1,4 @@ +import os import re class Severity: @@ -9,6 +10,18 @@ class Severity: self.severity['FILES'] = {} self.severity['REGEXPS'] = {} self.severity['DEFAULT_RANK'] = default_rank + # For variable expansions from /etc/apparmor.d + self.severity['VARIABLES'] = dict() + # Recursively visits all the profiles to identify all the variable expansions and stores them + for root, dirs, files in os.walk('/etc/apparmor.d'): + for file in files: + for line in open(os.path.join(root, file), 'r'): + line.strip() + # Expected format is @{Variable} = value1 value2 .. + if line.startswith('@') and '=' in line: + line = line.strip() + line = line.split('=') + self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] if not dbname: return None try: @@ -24,10 +37,10 @@ class Severity: path, read, write, execute = line.split() read, write, execute = int(read), int(write), int(execute) except ValueError: - raise("Insufficient values for permissions in line: %s"%line) + raise ValueError("Insufficient values for permissions in line: %s\nin File: %s"%(line, dbname)) else: if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11): - raise("Inappropriate values for permissions in line: %s"%line) + raise ValueError("Inappropriate values for permissions in line: %s\nin File: %s"%(line, dbname)) path = path.lstrip('/') if '*' not in path: self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} @@ -48,13 +61,13 @@ class Severity: resource, severity = line.split() severity = int(severity) except ValueError: - raise ValueError("No severity value present in line: %s"%line) + raise ValueError("No severity value present in line: %s\nin File: %s"%(line, dbname)) else: if severity not in range(0,11): - raise ValueError("Inappropriate severity value present in line: %s"%line) + raise ValueError("Inappropriate severity value present in line: %s\nin File: %s"%(line, dbname)) self.severity['CAPABILITIES'][resource] = severity else: - print("unexpected database line: %s \nin file: %s"%(line,dbname)) + raise ValueError("Unexpected database line: %s \nin File: %s"%(line,dbname)) database.close() def convert_regexp(self, path): @@ -77,10 +90,11 @@ class Severity: return regex def handle_capability(self, resource): - """Returns the severity of a resource or raises an""" + """Returns the severity of for the capability resource, default value if no match""" if resource in self.severity['CAPABILITIES'].keys(): return self.severity['CAPABILITIES'][resource] - raise ValueError("unexpected capability rank input: %s"%resource) + # raise ValueError("unexpected capability rank input: %s"%resource) + return self.severity['DEFAULT_RANK'] def check_subtree(self, tree, mode, sev, segments): @@ -105,7 +119,7 @@ class Severity: if "AA_RANK" in tree[chunk].keys(): for m in mode: if sev == None or tree[chunk]["AA_RANK"].get(m, -1) > sev: - sev = tree[chunk]["AA_RANK"][m] + sev = tree[chunk]["AA_RANK"].get(m, None) return sev def handle_file(self, resource, mode): @@ -130,9 +144,46 @@ class Severity: def rank(self, resource, mode=None): """Returns the rank for the resource file/capability""" - if resource[0] == '/': # file resource + if '@' in resource: # path contains variable + return self.handle_variable_rank(resource, mode) + elif resource[0] == '/': # file resource return self.handle_file(resource, mode) elif resource[0:4] == 'CAP_': # capability resource return self.handle_capability(resource) else: - raise ValueError("unexpected rank input: %s"%resource) \ No newline at end of file + raise ValueError("Unexpected rank input: %s"%resource) + + def handle_variable_rank(self, resource, mode): + """Returns the max possible rank for file resources containing variables""" + regex_variable = re.compile('@{([^{.]*)}') + rank = None + if '@' in resource: + variable = regex_variable.search(resource).groups()[0] + variable = '@{'+variable+'}' + #variables = regex_variable.findall(resource) + for replacement in self.severity['VARIABLES'][variable]: + resource_replaced = self.variable_replace(variable, replacement, resource) + rank_new = self.handle_variable_rank(resource_replaced, mode) + #rank_new = self.handle_variable_rank(resource.replace('@{'+variable+'}', replacement), mode) + if rank == None or rank_new > rank: + rank = rank_new + return rank + else: + #print(resource) + #print(self.handle_file(resource, mode)) + return self.handle_file(resource, mode) + + def variable_replace(self, variable, replacement, resource): + """Returns the expanded path for the passed variable""" + leading = False + trailing = False + # Check for leading or trailing / that may need to be collapsed + if resource.find("/"+variable) != -1 and resource.find("//"+variable) == -1: + leading = True + if resource.find(variable+"/") != -1 and resource.find(variable+"//") == -1: + trailing = True + if replacement[0] == '/' and replacement[0:2] != '//' and leading: + replacement = replacement[1:] + if replacement[-1] == '/' and replacement[-1:-2:-1] !='//' and trailing: + replacement = replacement[:-1] + return resource.replace(variable, replacement) \ No newline at end of file From e4ad1bde21bf5a5801de79b1a68c3cf8a3fe0b00 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 25 Jun 2013 04:46:59 +0530 Subject: [PATCH 012/183] tested and completed config.py ans severity.py with the exception of source hooks in severity --- Testing/out.conf2 | 104 ++++++++++++++++++++ Testing/severity_test.py | 2 + Testing/variable_finder.py | 19 ---- lib/config.py | 188 ++++++++++++++++++++++++++++++++++--- lib/configbkp.py | 87 ----------------- lib/severity.py | 31 +++--- 6 files changed, 298 insertions(+), 133 deletions(-) create mode 100644 Testing/out.conf2 delete mode 100644 Testing/variable_finder.py delete mode 100644 lib/configbkp.py diff --git a/Testing/out.conf2 b/Testing/out.conf2 new file mode 100644 index 000000000..f6eab1952 --- /dev/null +++ b/Testing/out.conf2 @@ -0,0 +1,104 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2004-2006 Novell/SUSE +# +# 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. +# +# ------------------------------------------------------------------ + +[repository] + distro = ubuntu-intrepid + url = http://apparmor.test.opensuse.org/backend/api + preferred_user = ubuntu + +[qualifiers] + # things will be painfully broken if bash has a profile + /bin/bash = icnu + /bin/ksh = icnu + /bin/dash = icnu + + # these programs can't function if they're confined + /bin/mount = u + /etc/init.d/subdomain = u + /sbin/cardmgr = u + /sbin/subdomain_parser = u + /usr/sbin/genprof = u + /usr/sbin/logprof = u + /usr/lib/YaST2/servers_non_y2/ag_genprof = u + /usr/lib/YaST2/servers_non_y2/ag_logprof = u + + # these ones shouln't have their own profiles + /bin/awk = icn + /bin/cat = icn + /bin/chmod = icn + /bin/chown = icn + /bin/cp = icn + /bin/gawk = icn + /bin/grep = icn + /bin/gunzip = icn + /bin/gzip = icn + /bin/kill = icn + /bin/ln = icn + /bin/ls = icn + /bin/mkdir = icn + /bin/mv = icn + /bin/readlink = icn + /bin/rm = icn + /bin/sed = icn + /bin/touch = icn + /sbin/killall5 = icn + /usr/bin/find = icn + /usr/bin/killall = icn + /usr/bin/nice = icn + /usr/bin/perl = icn + /usr/bin/tr = icn + +[required_hats] + ^.+/apache(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT #a + ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT + +[defaulthat] + ^.+/apache(|2|2-prefork)$ = DEFAULT_URI + ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI + +[globs] + # /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib* + /lib/lib[^\/]+so[^\/]*$ = /lib/lib*so* + + # strip kernel version numbers from kernel module accesses + ^/lib/modules/[^\/]+\/ = /lib/modules/*/ + + # strip pid numbers from /proc accesses + ^/proc/\d+/ = /proc/*/ + + # if it looks like a home directory, glob out the username + ^/home/[^\/]+ = /home/* + + # if they use any perl modules, grant access to all + ^/usr/lib/perl5/.+$ = /usr/lib/perl5/** + + # locale foo + ^/usr/lib/locale/.+$ = /usr/lib/locale/** + ^/usr/share/locale/.+$ = /usr/share/locale/** + + # timezone fun + ^/usr/share/zoneinfo/.+$ = /usr/share/zoneinfo/** + + # /foobar/fonts/baz -> /foobar/fonts/** + /fonts/.+$ = /fonts/** + + # turn /foo/bar/baz.8907234 into /foo/bar/baz.* + # BUGBUG - this one looked weird because it would suggest a glob for + # BUGBUG - libfoo.so.5.6.0 that looks like libfoo.so.5.6.* + # \.\d+$ = .* + + # some various /etc/security poo -- dunno about these ones... + ^/etc/security/_[^\/]+$ = /etc/security/* + ^/lib/security/pam_filter/[^\/]+$ = /lib/security/pam_filter/* + ^/lib/security/pam_[^\/]+\.so$ = /lib/security/pam_*.so + + ^/etc/pam.d/[^\/]+$ = /etc/pam.d/* + ^/etc/profile.d/[^\/]+\.sh$ = /etc/profile.d/*.sh + diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 65d40a0fc..ae713e82d 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -5,6 +5,7 @@ Created on Jun 21, 2013 ''' import sys import unittest + sys.path.append('../lib') import severity @@ -48,6 +49,7 @@ class Test(unittest.TestCase): self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') + self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') #self.assertEqual(s.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') diff --git a/Testing/variable_finder.py b/Testing/variable_finder.py deleted file mode 100644 index bcdd3bc1b..000000000 --- a/Testing/variable_finder.py +++ /dev/null @@ -1,19 +0,0 @@ -''' -Created on Jun 22, 2013 - -@author: kshitij -''' -import os -variable = dict() -for root, dirs, files in os.walk('/etc/apparmor.d'): - for file in files: - for line in open(os.path.join(root, file), 'r'): - line.strip() - if line.startswith('@') and '=' in line: - line = line.strip() - line = line.split('=') - variable[line[0]] = [i.strip('"') for i in line[1].split()] #.strip('"') -for i in variable.keys(): - print(i,variable[i]) - - \ No newline at end of file diff --git a/lib/config.py b/lib/config.py index b242d76ca..175598a9e 100644 --- a/lib/config.py +++ b/lib/config.py @@ -1,5 +1,6 @@ import configparser import os +import shlex import shutil import stat import tempfile @@ -8,34 +9,49 @@ import tempfile confdir = '/etc/apparmor' cfg = None repo_cfg = None +shell_files = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] -def read_config(filename): - """Reads the file and returns a configparser config[section][attribute]=property""" - config = configparser.ConfigParser() - filepath = confdir + '/' + filename - config.read(filepath) +def read_config(filename, conf_type=None): + """Reads the file and returns a config[section][attribute]=property object""" # LP: Bug #692406 # Explicitly disabled repository + filepath = confdir + '/' + filename if filename == "repository.conf": + config = dict() config['repository'] = {'enabled': 'no'} + elif filename in shell_files or conf_type == 'shell': + config = read_shell(filepath) + else: + config = configparser.ConfigParser() + config.optionxform = str + config.read(filepath) return config -def write_config(filename, config): - """Writes the given configparser to the specified file""" +def write_config(filename, config, conf_type=None): + """Writes the given config to the specified file""" filepath = confdir + '/' + filename permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write try: - # Open a temporary file to write the config file - config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False) - # Set file permissions as 0600 - os.chmod(config_file.name, permission_600) - config.write(config_file) + # Open a temporary file in the confdir to write the config file + config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=confdir) + if os.path.exists(filepath): + # Copy permissions from an existing file to temporary file + shutil.copymode(filepath, config_file.name) + else: + # If no existing permission set the file permissions as 0600 + os.chmod(config_file.name, permission_600) + write_shell(filepath, config_file, config) + if filename in shell_files or conf_type == 'shell': + write_shell(filepath, config_file, config) + else: + write_configparser(filepath, config_file, config) + #config.write(config_file) config_file.close() except IOError: raise IOError("Unable to write to %s"%filename) else: - # Move the temporary file to the target config file - shutil.move(config_file.name, filepath) + # Replace the target config file with the temporary file + os.rename(config_file.name, filepath) def find_first_file(file_list): @@ -58,4 +74,146 @@ def find_first_dir(dir_list): dirname = direc break return dirname - \ No newline at end of file + +def read_shell(filepath): + """Reads the shell type conf files and returns config[''][option]=value""" + config = {'': dict()} + with open(filepath, 'r') as file: + for line in file: + result = shlex.split(line, True) + # If not a comment of empty line + if result != []: + # option="value" or option=value type + if '=' in result[0]: + option, value = result[0].split('=') + # option type + else: + option = result[0] + value = None + config[''][option] = value + return config + +def write_shell(filepath, f_out, config): + """Writes the config object in shell file format""" + # All the options in the file + options = [key for key in config[''].keys()] + # If a previous file exists modify it keeping the comments + if os.path.exists(filepath): + with open(filepath, 'r') as f_in: + for line in f_in: + result = shlex.split(line, True) + # If line is not empty or comment + if result != []: + # If option=value or option="value" type + if '=' in result[0]: + option, value = result[0].split('=') + # If option exists in the new config file + if option in options: + # If value is different + if value != config[''][option]: + value_new = config[''][option] + if value_new != None: + # Update value + if '"' in line: + value_new = '"' + value_new + '"' + line = option + '=' + value_new + '\n' + else: + # If option changed to option type from option=value type + line = option + '\n' + f_out.write(line) + # Remove from remaining options list + options.remove(option) + else: + # If option type + option = result[0] + value = None + # If option exists in the new config file + if option in options: + # If its no longer option type + if config[''][option] != None: + value = config[''][option] + line = option + '=' + value + '\n' + f_out.write(line) + # Remove from remaining options list + options.remove(option) + else: + # If its empty or comment copy as it is + f_out.write(line) + # If any new options are present + if options != []: + for option in options: + value = config[''][option] + # option type entry + if value == None: + line = option + '\n' + # option=value type entry + else: + line = option + '=' + value + '\n' + f_out.write(line) + +def write_configparser(filepath, f_out, config): + # All the sections in the file + sections = config.sections() + write = True + section = None + options = [] + # If a previous file exists modify it keeping the comments + if os.path.exists(filepath): + with open(filepath, 'r') as f_in: + for line in f_in: + # If its a section + if line.lstrip().startswith('['): + # If any options from preceding section remain write them + if options != []: + for option in options: + line_new = ' ' + option + ' = ' + config[section][option] + '\n' + f_out.write(line_new) + options = [] + if section in sections: + # Remove the written section from the list + sections.remove(section) + section = line.strip()[1:-1] + if section in sections: + # enable write for all entries in that section + write = True + options = config.options(section) + # write the section + f_out.write(line) + else: + # disable writing until next valid section + write = False + # If write enabled + elif write: + value = shlex.split(line, True) + # If the line is empty or a comment + if value == []: + f_out.write(line) + else: + option, value = line.split('=', 1) + try: + # split any inline comments + value, comment = value.split('#', 1) + comment = '#' + comment + except ValueError: + comment = '' + if option.strip() in options: + if config[section][option.strip()] != value.strip(): + value = value.replace(value, config[section][option.strip()]) + line = option + '=' + value + comment + f_out.write(line) + options.remove(option.strip()) + # If any options remain from the preceding section + if options != []: + for option in options: + line = ' ' + option + ' = ' + config[section][option] + '\n' + f_out.write(line) + options = [] + # If any new sections are present + if section in sections: + sections.remove(section) + for section in sections: + f_out.write('\n['+section+']\n') + options = config.options(section) + for option in options: + line = ' ' + option + ' = ' + config[section][option] + '\n' + f_out.write(line) \ No newline at end of file diff --git a/lib/configbkp.py b/lib/configbkp.py deleted file mode 100644 index 8287a96ba..000000000 --- a/lib/configbkp.py +++ /dev/null @@ -1,87 +0,0 @@ -import os -import re -import stat - -confdir = '/etc/apparmor' -cfg = None -repo_cfg = None - -def read_config(filename): - """Reads the file and returns a double dictionary config[section][attribute]=property""" - config = dict() - regex_label = re.compile('^\[(\S+)\]') - regex_value = re.compile('^\s*(\S+)\s*=\s*(.*)\s*$') - filepath = confdir + '/' + filename - try: - conf_file = open(filepath, 'r', 1) - except IOError: - pass - else: - section = '' # The default section - for line in conf_file: - # Ignore the comment lines - if line.lstrip().startswith('#'): - continue - line = line.rstrip('\n') - # Search for a new section - label_match = regex_label.search(line) - if label_match: - section = label_match.groups()[0] - else: - # Search for a attribute value pair - value_match = regex_value.search(line) - if value_match: - attribute = value_match.groups()[0] - value = value_match.groups()[1] - # A doubly nested dictionary - config[section] = config.get(section, {}) - config[section][attribute] = value - conf_file.close() - # LP: Bug #692406 - # Explicitly disabled repository - if filename == "repository.conf": - config['repository'] = {'enabled': 'no'} - return config - -def write_config(filename, config): - """Writes the given configuration to the specified file""" - filepath = confdir + '/' + filename - try: - conf_file = open(filepath, 'w') - except IOError: - raise IOError("Unable to write to %s"%filename) - else: - for section in sorted(config.keys()): - # Write the section and all attributes and values under the section - if section != '': # If default section then no section label - conf_file.write("[%s]\n"%section) - for attribute in sorted(config[section].keys()): - if section != '': - conf_file.write(" ") # Indentation for a section - conf_file.write("%s = %s\n"%(attribute, config[section][attribute])) - permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write - # Set file permissions as 0600 - os.chmod(filepath, permission_600) - conf_file.close() - -def find_first_file(file_list): - """Returns name of first matching file None otherwise""" - # I don't understand why it searches the CWD, maybe I'll find out about it in some module - filename = None - if len(file_list): - for file in file_list.split(): - if os.path.isfile(file): - filename = file - break - return filename - -def find_first_dir(dir_list): - """Returns name of first matching directory None otherwise""" - dirname = None - if (len(dir_list)): - for direc in dir_list.split(): - if os.path.isdir(direc): - dirname = direc - break - return dirname - \ No newline at end of file diff --git a/lib/severity.py b/lib/severity.py index 90a1374fe..597bff6de 100644 --- a/lib/severity.py +++ b/lib/severity.py @@ -12,16 +12,23 @@ class Severity: self.severity['DEFAULT_RANK'] = default_rank # For variable expansions from /etc/apparmor.d self.severity['VARIABLES'] = dict() - # Recursively visits all the profiles to identify all the variable expansions and stores them + # Recursively visits all the profiles to identify all the variable expansions and stores them need to use sourcehooks for root, dirs, files in os.walk('/etc/apparmor.d'): for file in files: - for line in open(os.path.join(root, file), 'r'): - line.strip() - # Expected format is @{Variable} = value1 value2 .. - if line.startswith('@') and '=' in line: - line = line.strip() - line = line.split('=') - self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] + try: + with open(os.path.join(root, file), 'r') as f: + for line in f: + line.strip() + # Expected format is @{Variable} = value1 value2 .. + if line.startswith('@') and '=' in line: + line = line.strip() + if '+=' in line: + line = line.split('+=') + else: + line = line.split('=') + self.severity['VARIABLES'][line[0]] = self.severity['VARIABLES'].get(line[0], []) + [i.strip('"') for i in line[1].split()] + except IOError: + raise IOError("unable to open file: %s"%file) if not dbname: return None try: @@ -169,7 +176,7 @@ class Severity: rank = rank_new return rank else: - #print(resource) + print(resource) #print(self.handle_file(resource, mode)) return self.handle_file(resource, mode) @@ -178,12 +185,12 @@ class Severity: leading = False trailing = False # Check for leading or trailing / that may need to be collapsed - if resource.find("/"+variable) != -1 and resource.find("//"+variable) == -1: + if resource.find("/"+variable) != -1 and resource.find("//"+variable) == -1: # find that a single / exists before variable or not leading = True if resource.find(variable+"/") != -1 and resource.find(variable+"//") == -1: trailing = True - if replacement[0] == '/' and replacement[0:2] != '//' and leading: + if replacement[0] == '/' and replacement[:2] != '//' and leading: # finds if the replacement has leading / or not replacement = replacement[1:] - if replacement[-1] == '/' and replacement[-1:-2:-1] !='//' and trailing: + if replacement[-1] == '/' and replacement[-2:] !='//' and trailing: replacement = replacement[:-1] return resource.replace(variable, replacement) \ No newline at end of file From b3767766ef2c90c8de1a853075cec07d62d01f43 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 4 Jul 2013 04:12:04 +0530 Subject: [PATCH 013/183] Python2 compatible code except for configparser, code from week2 --- Testing/out.conf2 | 104 -------------- Testing/severity_test.py | 15 ++- apparmor/__init__.py | 5 + apparmor/aa.py | 173 ++++++++++++++++++++++++ apparmor/common.py | 126 +++++++++++++++++ apparmor/config.py | 247 ++++++++++++++++++++++++++++++++++ {lib => apparmor}/severity.py | 40 ++++-- lib/AppArmor.py | 1 - lib/config.py | 219 ------------------------------ 9 files changed, 586 insertions(+), 344 deletions(-) delete mode 100644 Testing/out.conf2 create mode 100644 apparmor/__init__.py create mode 100644 apparmor/aa.py create mode 100644 apparmor/common.py create mode 100644 apparmor/config.py rename {lib => apparmor}/severity.py (80%) delete mode 100644 lib/AppArmor.py delete mode 100644 lib/config.py diff --git a/Testing/out.conf2 b/Testing/out.conf2 deleted file mode 100644 index f6eab1952..000000000 --- a/Testing/out.conf2 +++ /dev/null @@ -1,104 +0,0 @@ -# ------------------------------------------------------------------ -# -# Copyright (C) 2004-2006 Novell/SUSE -# -# 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. -# -# ------------------------------------------------------------------ - -[repository] - distro = ubuntu-intrepid - url = http://apparmor.test.opensuse.org/backend/api - preferred_user = ubuntu - -[qualifiers] - # things will be painfully broken if bash has a profile - /bin/bash = icnu - /bin/ksh = icnu - /bin/dash = icnu - - # these programs can't function if they're confined - /bin/mount = u - /etc/init.d/subdomain = u - /sbin/cardmgr = u - /sbin/subdomain_parser = u - /usr/sbin/genprof = u - /usr/sbin/logprof = u - /usr/lib/YaST2/servers_non_y2/ag_genprof = u - /usr/lib/YaST2/servers_non_y2/ag_logprof = u - - # these ones shouln't have their own profiles - /bin/awk = icn - /bin/cat = icn - /bin/chmod = icn - /bin/chown = icn - /bin/cp = icn - /bin/gawk = icn - /bin/grep = icn - /bin/gunzip = icn - /bin/gzip = icn - /bin/kill = icn - /bin/ln = icn - /bin/ls = icn - /bin/mkdir = icn - /bin/mv = icn - /bin/readlink = icn - /bin/rm = icn - /bin/sed = icn - /bin/touch = icn - /sbin/killall5 = icn - /usr/bin/find = icn - /usr/bin/killall = icn - /usr/bin/nice = icn - /usr/bin/perl = icn - /usr/bin/tr = icn - -[required_hats] - ^.+/apache(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT #a - ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT - -[defaulthat] - ^.+/apache(|2|2-prefork)$ = DEFAULT_URI - ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI - -[globs] - # /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib* - /lib/lib[^\/]+so[^\/]*$ = /lib/lib*so* - - # strip kernel version numbers from kernel module accesses - ^/lib/modules/[^\/]+\/ = /lib/modules/*/ - - # strip pid numbers from /proc accesses - ^/proc/\d+/ = /proc/*/ - - # if it looks like a home directory, glob out the username - ^/home/[^\/]+ = /home/* - - # if they use any perl modules, grant access to all - ^/usr/lib/perl5/.+$ = /usr/lib/perl5/** - - # locale foo - ^/usr/lib/locale/.+$ = /usr/lib/locale/** - ^/usr/share/locale/.+$ = /usr/share/locale/** - - # timezone fun - ^/usr/share/zoneinfo/.+$ = /usr/share/zoneinfo/** - - # /foobar/fonts/baz -> /foobar/fonts/** - /fonts/.+$ = /fonts/** - - # turn /foo/bar/baz.8907234 into /foo/bar/baz.* - # BUGBUG - this one looked weird because it would suggest a glob for - # BUGBUG - libfoo.so.5.6.0 that looks like libfoo.so.5.6.* - # \.\d+$ = .* - - # some various /etc/security poo -- dunno about these ones... - ^/etc/security/_[^\/]+$ = /etc/security/* - ^/lib/security/pam_filter/[^\/]+$ = /lib/security/pam_filter/* - ^/lib/security/pam_[^\/]+\.so$ = /lib/security/pam_*.so - - ^/etc/pam.d/[^\/]+$ = /etc/pam.d/* - ^/etc/profile.d/[^\/]+\.sh$ = /etc/profile.d/*.sh - diff --git a/Testing/severity_test.py b/Testing/severity_test.py index ae713e82d..4572b69a6 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -6,10 +6,11 @@ Created on Jun 21, 2013 import sys import unittest -sys.path.append('../lib') - -import severity +sys.path.append('../') +sys.path.append('../apparmor') +import AppArmor.severity as severity +from AppArmor.common import AppArmorException class Test(unittest.TestCase): def testInvalid(self): @@ -17,11 +18,11 @@ class Test(unittest.TestCase): rank = s.rank('/dev/doublehit', 'i') self.assertEqual(rank, 10, 'Wrong') try: - broken = severity.Severity('severity_broken.db') - rank = s.rank('CAP_UNKOWN') - rank = s.rank('CAP_K*') - except ValueError: + broken = severity.Severity('severity_broken.db') + except AppArmorException: pass + rank = s.rank('CAP_UNKOWN') + rank = s.rank('CAP_K*') def testRank_Test(self): z = severity.Severity() diff --git a/apparmor/__init__.py b/apparmor/__init__.py new file mode 100644 index 000000000..c70a751df --- /dev/null +++ b/apparmor/__init__.py @@ -0,0 +1,5 @@ +''' +Created on Jun 27, 2013 + +@author: kshitij +''' diff --git a/apparmor/aa.py b/apparmor/aa.py new file mode 100644 index 000000000..d85949b3d --- /dev/null +++ b/apparmor/aa.py @@ -0,0 +1,173 @@ +#mv $0 bed/ now +#Line 470 +import os +import re +import sys + +import apparmor.config +import apparmor.severity +import LibAppArmor + +from AppArmor.common import AppArmorException, error, debug, msg, open_file_read, valid_path + +DEBUGGING = False + +CONFDIR = '/etc/apparmor' +running_under_genprof = False +unimplemented_warning = False +# The operating mode: yast or text, text by default +UI_mode = 'text' +# The database for severity +sev_db = None +# The file to read log messages from +### Was our +filename = None + +cfg = None +repo_cfg = None + +parser = None +ldd = None +logger = None +profile_dir = None +extra_profile_dir = None +### end our +# To keep track of previously included profile fragments +include = dict() + +existing_profiles = dict() + +seen_events = 0 # was our +# To store the globs entered by users so they can be provided again +user_globs = [] + +## Variables used under logprof +### Were our +t = dict() +transitions = dict() +aa = dict() # Profiles originally in sd, replace by aa +original_aa = dict() +extras = dict() # Inactive profiles from extras +### end our +log = [] +pid = None + +seen = dir() +profile_changes = dict() +prelog = dict() +log = dict() +changed = dict() +created = [] +helpers = dict() # Preserve this between passes # was our +### logprof ends + +filelist = dict() # File level variables and stuff in config files + +AA_MAY_EXEC = 1 +AA_MAY_WRITE = 2 +AA_MAY_READ = 4 +AA_MAY_APPEND = 8 +AA_MAY_LINK = 16 +AA_MAY_LOCK = 32 +AA_EXEC_MMAP = 64 +AA_EXEC_UNSAFE = 128 +AA_EXEC_INHERIT = 256 +AA_EXEC_UNCONFINED = 512 +AA_EXEC_PROFILE = 1024 +AA_EXEC_CHILD = 2048 +AA_EXEC_NT = 4096 +AA_LINK_SUBSET = 8192 +AA_OTHER_SHIFT = 14 +AA_USER_MASK = 16384 - 1 + +AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | + AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) + +ALL_AA_EXEC_TYPE = AA_EXEC_TYPE # The same value + +# Modes and their values +MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, + 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, + 'r': AA_MAY_READ, 'R': AA_MAY_READ, + 'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND, + 'l': AA_MAY_LINK, 'L': AA_MAY_LINK, + 'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK, + 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, + 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, + 'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'U': AA_EXEC_UNCONFINED, + 'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe + 'P': AA_EXEC_PROFILE, + 'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe + 'C': AA_EXEC_CHILD, + 'n': AA_EXEC_NT + AA_EXEC_UNSAFE, + 'N': AA_EXEC_NT + } + +# Used by netdomain to identify the operation types +OPERATION_TYPES = { + # New socket names + 'create': 'net', + 'post_create': 'net', + 'bind': 'net', + 'connect': 'net', + 'listen': 'net', + 'accept': 'net', + 'sendmsg': 'net', + 'recvmsg': 'net', + 'getsockname': 'net', + 'getpeername': 'net', + 'getsockopt': 'net', + 'setsockopt': 'net', + 'sock_shutdown': 'net' + } + +ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'} + +def opt_type(operation): + """Returns the operation type if known, unkown otherwise""" + operation_type = OPERATION_TYPES.get(operation, 'unknown') + return operation_type + +def getkey(): + """Returns the pressed key""" + # Used incase of Y or N without pressing enter + ## Needs to be done using curses? read a single key + pass + +def check_for_apparmor(): + """Finds and returns the mointpoint for apparmor None otherwise""" + filesystem = '/proc/filesystems' + mounts = '/proc/mounts' + support_securityfs = False + aa_mountpoint = None + regex_securityfs = re.compile('^\S+\s+(\S+)\s+securityfs\s') + if valid_path(filesystem): + with open_file_read(filesystem) as f_in: + for line in f_in: + if 'securityfs' in line: + support_securityfs = True + if valid_path(mounts): + with open_file_read(mounts) as f_in: + for line in f_in: + if support_securityfs: + match = regex_securityfs(line) + if match: + mountpoint = match.groups()[0] + '/apparmor' + if valid_path(mountpoint): + aa_mountpoint = mountpoint + # Check if apparmor is actually mounted there + if not valid_path(aa_mountpoint + '/profiles'): + aa_mountpoint = None + return aa_mountpoint + +def which(file): + """Returns the executable fullpath for the file None otherwise""" + env_dirs = os.getenv('PATH').split(':') + for env_dir in env_dirs: + env_path = env_dir + '/' + file + # Test if the path is executable or not + if os.access(env_path, os.X_OK): + return env_path + return None + diff --git a/apparmor/common.py b/apparmor/common.py new file mode 100644 index 000000000..0064004ef --- /dev/null +++ b/apparmor/common.py @@ -0,0 +1,126 @@ +from __future__ import print_function +import codecs +import glob +import os +import subprocess +import sys + +DEBUGGING = False + +# +# Utility classes +# +class AppArmorException(Exception): + '''This class represents AppArmor exceptions''' + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + +# +# Utility functions +# +def error(out, exit_code=1, do_exit=True): + '''Print error message and exit''' + try: + print("ERROR: %s" % (out), file=sys.stderr) + except IOError: + pass + + if do_exit: + sys.exit(exit_code) + +def warn(out): + '''Print warning message''' + try: + print("WARN: %s" % (out), file=sys.stderr) + except IOError: + pass + +def msg(out, output=sys.stdout): + '''Print message''' + try: + print("%s" % (out), file=output) + except IOError: + pass + +def debug(out): + '''Print debug message''' + global DEBUGGING + if DEBUGGING: + try: + print("DEBUG: %s" % (out), file=sys.stderr) + except IOError: + pass + +def cmd(command): + '''Try to execute the given command.''' + debug(command) + try: + sp = subprocess.Popen(command, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + except OSError as ex: + return [127, str(ex)] + + if sys.version_info[0] >= 3: + out = sp.communicate()[0].decode('ascii', 'ignore') + else: + out = sp.communicate()[0] + + return [sp.returncode, out] + + +def cmd_pipe(command1, command2): + '''Try to pipe command1 into command2.''' + try: + sp1 = subprocess.Popen(command1, stdout=subprocess.PIPE) + sp2 = subprocess.Popen(command2, stdin=sp1.stdout) + except OSError as ex: + return [127, str(ex)] + + if sys.version_info[0] >= 3: + out = sp2.communicate()[0].decode('ascii', 'ignore') + else: + out = sp2.communicate()[0] + + return [sp2.returncode, out] + +def valid_path(path): + '''Valid path''' + # No relative paths + m = "Invalid path: %s" % (path) + if not path.startswith('/'): + debug("%s (relative)" % (m)) + return False + + if '"' in path: # We double quote elsewhere + return False + + try: + os.path.normpath(path) + except Exception: + debug("%s (could not normalize)" % (m)) + return False + return True + +def get_directory_contents(path): + '''Find contents of the given directory''' + if not valid_path(path): + return None + + files = [] + for f in glob.glob(path + "/*"): + files.append(f) + + files.sort() + return files + +def open_file_read(path): + '''Open specified file read-only''' + try: + orig = codecs.open(path, 'r', "UTF-8") + except Exception: + raise + + return orig \ No newline at end of file diff --git a/apparmor/config.py b/apparmor/config.py new file mode 100644 index 000000000..77d621955 --- /dev/null +++ b/apparmor/config.py @@ -0,0 +1,247 @@ +from __future__ import with_statement +import os +import shlex +import shutil +import stat +import sys +import tempfile +if sys.version_info < (3,0): + import ConfigParser as configparser +else: + import configparser + + +from AppArmor.common import AppArmorException, warn, msg, open_file_read + +CONF_DIR = '/etc/apparmor' +CFG = None +REPO_CFG = None +SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] +class Config: + def __init__(self, conf_type): + # The type of config file that'll be read and/or written + self.conf_type = conf_type + self.input_file = None + + def new_config(self): + if self.conf_type == 'shell': + config = {'': dict()} + else: + config = configparser.ConfigParser() + return config + + def read_config(self, filename): + """Reads the file and returns a config[section][attribute]=property object""" + # LP: Bug #692406 + # Explicitly disabled repository + filepath = CONF_DIR + '/' + filename + self.input_file = filepath + if filename == "repository.conf": + config = dict() + config['repository'] = {'enabled': 'no'} + elif self.conf_type == 'shell': + config = self.read_shell(filepath) + else: + config = configparser.ConfigParser() + # Set the option form to string -prevents forced conversion to lowercase + #config.optionxform = str + if sys.version_info > (3,0): + config.read(filepath) + else: + config.readfp(open_file_read(filepath)) + return config + + def write_config(self, filename, config): + """Writes the given config to the specified file""" + filepath = CONF_DIR + '/' + filename + permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write + try: + # Open a temporary file in the CONF_DIR to write the config file + config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=CONF_DIR) + if os.path.exists(self.input_file): + # Copy permissions from an existing file to temporary file + shutil.copymode(self.input_file, config_file.name) + else: + # If no existing permission set the file permissions as 0600 + os.chmod(config_file.name, permission_600) + if self.conf_type == 'shell': + self.write_shell(filepath, config_file, config) + else: + self.write_configparser(filepath, config_file, config) + #config.write(config_file) + config_file.close() + except IOError: + raise AppArmorException("Unable to write to %s"%filename) + else: + # Replace the target config file with the temporary file + os.rename(config_file.name, filepath) + + + def find_first_file(self, file_list): + """Returns name of first matching file None otherwise""" + filename = None + if filename: + for file in file_list.split(): + if os.path.isfile(file): + filename = file + break + return filename + + def find_first_dir(self, dir_list): + """Returns name of first matching directory None otherwise""" + dirname = None + if dir_list: + for direc in dir_list.split(): + if os.path.isdir(direc): + dirname = direc + break + return dirname + + def read_shell(self, filepath): + """Reads the shell type conf files and returns config[''][option]=value""" + config = {'': dict()} + with open_file_read(filepath) as file: + for line in file: + result = shlex.split(line, True) + # If not a comment of empty line + if result: + # option="value" or option=value type + if '=' in result[0]: + option, value = result[0].split('=') + # option type + else: + option = result[0] + value = None + config[''][option] = value + return config + + def write_shell(self, filepath, f_out, config): + """Writes the config object in shell file format""" + # All the options in the file + options = [key for key in config[''].keys()] + # If a previous file exists modify it keeping the comments + if os.path.exists(self.input_file): + with open_file_read(self.input_file) as f_in: + for line in f_in: + result = shlex.split(line, True) + # If line is not empty or comment + if result: + # If option=value or option="value" type + if '=' in result[0]: + option, value = result[0].split('=') + if '#' in line: + comment = value.split('#', 1)[1] + comment = '#'+comment + else: + comment = '' + # If option exists in the new config file + if option in options: + # If value is different + if value != config[''][option]: + value_new = config[''][option] + if value_new != None: + # Update value + if '"' in line: + value_new = '"' + value_new + '"' + line = option + '=' + value_new + comment + '\n' + else: + # If option changed to option type from option=value type + line = option + comment + '\n' + f_out.write(line) + # Remove from remaining options list + options.remove(option) + else: + # If option type + option = result[0] + value = None + # If option exists in the new config file + if option in options: + # If its no longer option type + if config[''][option] != None: + value = config[''][option] + line = option + '=' + value + '\n' + f_out.write(line) + # Remove from remaining options list + options.remove(option) + else: + # If its empty or comment copy as it is + f_out.write(line) + # If any new options are present + if options: + for option in options: + value = config[''][option] + # option type entry + if value == None: + line = option + '\n' + # option=value type entry + else: + line = option + '=' + value + '\n' + f_out.write(line) + + def write_configparser(self, filepath, f_out, config): + """Writes/updates the given file with given config object""" + # All the sections in the file + sections = config.sections() + write = True + section = None + options = [] + # If a previous file exists modify it keeping the comments + if os.path.exists(self.input_file): + with open_file_read(self.input_file) as f_in: + for line in f_in: + # If its a section + if line.lstrip().startswith('['): + # If any options from preceding section remain write them + if options: + for option in options: + line_new = ' ' + option + ' = ' + config[section][option] + '\n' + f_out.write(line_new) + options = [] + if section in sections: + # Remove the written section from the list + sections.remove(section) + section = line.strip()[1:-1] + if section in sections: + # enable write for all entries in that section + write = True + options = config.options(section) + # write the section + f_out.write(line) + else: + # disable writing until next valid section + write = False + # If write enabled + elif write: + value = shlex.split(line, True) + # If the line is empty or a comment + if not value: + f_out.write(line) + else: + option, value = line.split('=', 1) + try: + # split any inline comments + value, comment = value.split('#', 1) + comment = '#' + comment + except ValueError: + comment = '' + if option.strip() in options: + if config[section][option.strip()] != value.strip(): + value = value.replace(value, config[section][option.strip()]) + line = option + '=' + value + comment + f_out.write(line) + options.remove(option.strip()) + # If any options remain from the preceding section + if options: + for option in options: + line = ' ' + option + ' = ' + config[section][option] + '\n' + f_out.write(line) + options = [] + # If any new sections are present + if section in sections: + sections.remove(section) + for section in sections: + f_out.write('\n['+section+']\n') + options = config.options(section) + for option in options: + line = ' ' + option + ' = ' + config[section][option] + '\n' + f_out.write(line) \ No newline at end of file diff --git a/lib/severity.py b/apparmor/severity.py similarity index 80% rename from lib/severity.py rename to apparmor/severity.py index 597bff6de..960c79a2e 100644 --- a/lib/severity.py +++ b/apparmor/severity.py @@ -1,5 +1,7 @@ +from __future__ import with_statement import os import re +from AppArmor.common import AppArmorException, error, debug, open_file_read, warn, msg class Severity: def __init__(self, dbname=None, default_rank=10): @@ -16,7 +18,7 @@ class Severity: for root, dirs, files in os.walk('/etc/apparmor.d'): for file in files: try: - with open(os.path.join(root, file), 'r') as f: + with open_file_read(os.path.join(root, file)) as f: for line in f: line.strip() # Expected format is @{Variable} = value1 value2 .. @@ -24,17 +26,23 @@ class Severity: line = line.strip() if '+=' in line: line = line.split('+=') + try: + self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()] + except KeyError: + raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values" % line[0]) else: line = line.split('=') - self.severity['VARIABLES'][line[0]] = self.severity['VARIABLES'].get(line[0], []) + [i.strip('"') for i in line[1].split()] + if line[0] in self.severity['VARIABLES'].keys(): + raise AppArmorException("Variable %s was previously declared" % line[0]) + self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] except IOError: - raise IOError("unable to open file: %s"%file) + raise AppArmorException("unable to open file: %s" % file) if not dbname: return None try: - database = open(dbname, 'r') + database = open_file_read(dbname)#open(dbname, 'r') except IOError: - raise IOError("Could not open severity database %s"%dbname) + raise AppArmorException("Could not open severity database %s" % dbname) for line in database: line = line.strip() # or only rstrip and lstrip? if line == '' or line.startswith('#') : @@ -44,10 +52,12 @@ class Severity: path, read, write, execute = line.split() read, write, execute = int(read), int(write), int(execute) except ValueError: - raise ValueError("Insufficient values for permissions in line: %s\nin File: %s"%(line, dbname)) + msg("\nFile: %s" % dbname) + raise AppArmorException("Insufficient values for permissions in line: %s" % (line)) else: if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11): - raise ValueError("Inappropriate values for permissions in line: %s\nin File: %s"%(line, dbname)) + msg("\nFile:"%(dbname)) + raise AppArmorException("Inappropriate values for permissions in line: %s" % line) path = path.lstrip('/') if '*' not in path: self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} @@ -68,13 +78,16 @@ class Severity: resource, severity = line.split() severity = int(severity) except ValueError: - raise ValueError("No severity value present in line: %s\nin File: %s"%(line, dbname)) + msg("\nFile: %s" % dbname) + raise AppArmorException("No severity value present in line: %s" % (line)) else: if severity not in range(0,11): - raise ValueError("Inappropriate severity value present in line: %s\nin File: %s"%(line, dbname)) + msg("\nFile: %s" % dbname) + raise AppArmorException("Inappropriate severity value present in line: %s" % (line)) self.severity['CAPABILITIES'][resource] = severity else: - raise ValueError("Unexpected database line: %s \nin File: %s"%(line,dbname)) + msg("\nFile: %s" % dbname) + raise AppArmorException("Unexpected database line: %s" % (line)) database.close() def convert_regexp(self, path): @@ -83,7 +96,7 @@ class Severity: internal_glob = '__KJHDKVZH_AAPROF_INTERNAL_GLOB_SVCUZDGZID__' regex = path for character in ['.', '+', '[', ']']: # Escape the regex symbols - regex = regex.replace(character, "\%s"%character) + regex = regex.replace(character, "\%s" % character) # Convert the ** to regex regex = regex.replace('**', '.'+internal_glob) # Convert the * to regex @@ -101,6 +114,7 @@ class Severity: if resource in self.severity['CAPABILITIES'].keys(): return self.severity['CAPABILITIES'][resource] # raise ValueError("unexpected capability rank input: %s"%resource) + warn("unknown capability: %s" % resource) return self.severity['DEFAULT_RANK'] @@ -158,7 +172,7 @@ class Severity: elif resource[0:4] == 'CAP_': # capability resource return self.handle_capability(resource) else: - raise ValueError("Unexpected rank input: %s"%resource) + raise AppArmorException("Unexpected rank input: %s" % resource) def handle_variable_rank(self, resource, mode): """Returns the max possible rank for file resources containing variables""" @@ -176,7 +190,7 @@ class Severity: rank = rank_new return rank else: - print(resource) + #print(resource) #print(self.handle_file(resource, mode)) return self.handle_file(resource, mode) diff --git a/lib/AppArmor.py b/lib/AppArmor.py deleted file mode 100644 index a93a4bf16..000000000 --- a/lib/AppArmor.py +++ /dev/null @@ -1 +0,0 @@ -#!/usr/bin/python3 diff --git a/lib/config.py b/lib/config.py deleted file mode 100644 index 175598a9e..000000000 --- a/lib/config.py +++ /dev/null @@ -1,219 +0,0 @@ -import configparser -import os -import shlex -import shutil -import stat -import tempfile - - -confdir = '/etc/apparmor' -cfg = None -repo_cfg = None -shell_files = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] - -def read_config(filename, conf_type=None): - """Reads the file and returns a config[section][attribute]=property object""" - # LP: Bug #692406 - # Explicitly disabled repository - filepath = confdir + '/' + filename - if filename == "repository.conf": - config = dict() - config['repository'] = {'enabled': 'no'} - elif filename in shell_files or conf_type == 'shell': - config = read_shell(filepath) - else: - config = configparser.ConfigParser() - config.optionxform = str - config.read(filepath) - return config - -def write_config(filename, config, conf_type=None): - """Writes the given config to the specified file""" - filepath = confdir + '/' + filename - permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write - try: - # Open a temporary file in the confdir to write the config file - config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=confdir) - if os.path.exists(filepath): - # Copy permissions from an existing file to temporary file - shutil.copymode(filepath, config_file.name) - else: - # If no existing permission set the file permissions as 0600 - os.chmod(config_file.name, permission_600) - write_shell(filepath, config_file, config) - if filename in shell_files or conf_type == 'shell': - write_shell(filepath, config_file, config) - else: - write_configparser(filepath, config_file, config) - #config.write(config_file) - config_file.close() - except IOError: - raise IOError("Unable to write to %s"%filename) - else: - # Replace the target config file with the temporary file - os.rename(config_file.name, filepath) - - -def find_first_file(file_list): - """Returns name of first matching file None otherwise""" - # I don't understand why it searches the CWD, maybe I'll find out about it in some module - filename = None - if len(file_list): - for file in file_list.split(): - if os.path.isfile(file): - filename = file - break - return filename - -def find_first_dir(dir_list): - """Returns name of first matching directory None otherwise""" - dirname = None - if (len(dir_list)): - for direc in dir_list.split(): - if os.path.isdir(direc): - dirname = direc - break - return dirname - -def read_shell(filepath): - """Reads the shell type conf files and returns config[''][option]=value""" - config = {'': dict()} - with open(filepath, 'r') as file: - for line in file: - result = shlex.split(line, True) - # If not a comment of empty line - if result != []: - # option="value" or option=value type - if '=' in result[0]: - option, value = result[0].split('=') - # option type - else: - option = result[0] - value = None - config[''][option] = value - return config - -def write_shell(filepath, f_out, config): - """Writes the config object in shell file format""" - # All the options in the file - options = [key for key in config[''].keys()] - # If a previous file exists modify it keeping the comments - if os.path.exists(filepath): - with open(filepath, 'r') as f_in: - for line in f_in: - result = shlex.split(line, True) - # If line is not empty or comment - if result != []: - # If option=value or option="value" type - if '=' in result[0]: - option, value = result[0].split('=') - # If option exists in the new config file - if option in options: - # If value is different - if value != config[''][option]: - value_new = config[''][option] - if value_new != None: - # Update value - if '"' in line: - value_new = '"' + value_new + '"' - line = option + '=' + value_new + '\n' - else: - # If option changed to option type from option=value type - line = option + '\n' - f_out.write(line) - # Remove from remaining options list - options.remove(option) - else: - # If option type - option = result[0] - value = None - # If option exists in the new config file - if option in options: - # If its no longer option type - if config[''][option] != None: - value = config[''][option] - line = option + '=' + value + '\n' - f_out.write(line) - # Remove from remaining options list - options.remove(option) - else: - # If its empty or comment copy as it is - f_out.write(line) - # If any new options are present - if options != []: - for option in options: - value = config[''][option] - # option type entry - if value == None: - line = option + '\n' - # option=value type entry - else: - line = option + '=' + value + '\n' - f_out.write(line) - -def write_configparser(filepath, f_out, config): - # All the sections in the file - sections = config.sections() - write = True - section = None - options = [] - # If a previous file exists modify it keeping the comments - if os.path.exists(filepath): - with open(filepath, 'r') as f_in: - for line in f_in: - # If its a section - if line.lstrip().startswith('['): - # If any options from preceding section remain write them - if options != []: - for option in options: - line_new = ' ' + option + ' = ' + config[section][option] + '\n' - f_out.write(line_new) - options = [] - if section in sections: - # Remove the written section from the list - sections.remove(section) - section = line.strip()[1:-1] - if section in sections: - # enable write for all entries in that section - write = True - options = config.options(section) - # write the section - f_out.write(line) - else: - # disable writing until next valid section - write = False - # If write enabled - elif write: - value = shlex.split(line, True) - # If the line is empty or a comment - if value == []: - f_out.write(line) - else: - option, value = line.split('=', 1) - try: - # split any inline comments - value, comment = value.split('#', 1) - comment = '#' + comment - except ValueError: - comment = '' - if option.strip() in options: - if config[section][option.strip()] != value.strip(): - value = value.replace(value, config[section][option.strip()]) - line = option + '=' + value + comment - f_out.write(line) - options.remove(option.strip()) - # If any options remain from the preceding section - if options != []: - for option in options: - line = ' ' + option + ' = ' + config[section][option] + '\n' - f_out.write(line) - options = [] - # If any new sections are present - if section in sections: - sections.remove(section) - for section in sections: - f_out.write('\n['+section+']\n') - options = config.options(section) - for option in options: - line = ' ' + option + ' = ' + config[section][option] + '\n' - f_out.write(line) \ No newline at end of file From 48fdbda9cd669a07963f6ed57b22853c8ae5f28e Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 4 Jul 2013 05:04:04 +0530 Subject: [PATCH 014/183] some minor bugs fixed after package name change --- apparmor/aa.py | 17 ++++++++++------- apparmor/common.py | 16 +++++++++++++++- apparmor/config.py | 2 +- apparmor/severity.py | 2 +- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index d85949b3d..72314de01 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,3 @@ -#mv $0 bed/ now #Line 470 import os import re @@ -8,7 +7,7 @@ import apparmor.config import apparmor.severity import LibAppArmor -from AppArmor.common import AppArmorException, error, debug, msg, open_file_read, valid_path +from apparmor.common import AppArmorException, error, debug, msg, open_file_read, readkey, valid_path DEBUGGING = False @@ -130,11 +129,15 @@ def opt_type(operation): return operation_type def getkey(): - """Returns the pressed key""" - # Used incase of Y or N without pressing enter - ## Needs to be done using curses? read a single key - pass - + key = readkey() + if key == '\x1B': + key = readkey() + if key == '[': + key = readkey() + if(ARROWS.get(key, False)): + key = ARROWS[key] + return key + def check_for_apparmor(): """Finds and returns the mointpoint for apparmor None otherwise""" filesystem = '/proc/filesystems' diff --git a/apparmor/common.py b/apparmor/common.py index 0064004ef..6f7f88907 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -4,6 +4,8 @@ import glob import os import subprocess import sys +import termios +import tty DEBUGGING = False @@ -123,4 +125,16 @@ def open_file_read(path): except Exception: raise - return orig \ No newline at end of file + return orig + +def readkey(): + """Returns the pressed key""" + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + return ch \ No newline at end of file diff --git a/apparmor/config.py b/apparmor/config.py index 77d621955..f343372b6 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -11,7 +11,7 @@ else: import configparser -from AppArmor.common import AppArmorException, warn, msg, open_file_read +from apparmor.common import AppArmorException, warn, msg, open_file_read CONF_DIR = '/etc/apparmor' CFG = None diff --git a/apparmor/severity.py b/apparmor/severity.py index 960c79a2e..4b65ddbe9 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -1,7 +1,7 @@ from __future__ import with_statement import os import re -from AppArmor.common import AppArmorException, error, debug, open_file_read, warn, msg +from apparmor.common import AppArmorException, error, debug, open_file_read, warn, msg class Severity: def __init__(self, dbname=None, default_rank=10): From 58f48db3817c26598be5a2c983c4eca9e2253c24 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 6 Jul 2013 18:57:06 +0530 Subject: [PATCH 015/183] updated codebase --- Testing/severity_test.py | 5 +- apparmor/aa.py | 230 ++++++++++++++++++++++++++++++++++++++- apparmor/config.py | 18 +-- apparmor/severity.py | 21 ++-- 4 files changed, 246 insertions(+), 28 deletions(-) diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 4572b69a6..05e1d74aa 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -9,14 +9,15 @@ import unittest sys.path.append('../') sys.path.append('../apparmor') -import AppArmor.severity as severity -from AppArmor.common import AppArmorException +import apparmor.severity as severity +from apparmor.common import AppArmorException class Test(unittest.TestCase): def testInvalid(self): s = severity.Severity('severity.db') rank = s.rank('/dev/doublehit', 'i') self.assertEqual(rank, 10, 'Wrong') + broken = severity.Severity('severity_broken.db') try: broken = severity.Severity('severity_broken.db') except AppArmorException: diff --git a/apparmor/aa.py b/apparmor/aa.py index 72314de01..a62c26e7a 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,7 +1,25 @@ -#Line 470 +#711 +#382-430 +#480-525 +#global variable names corruption +from __future__ import with_statement +import inspect import os import re +import subprocess import sys +import traceback + +DEBUGGING = False +debug_logger = None + +# Setup logging incase of debugging is enabled +if os.getenv('LOGPROF_DEBUG', False): + import logging, atexit + DEBUGGING = True + logprof_debug = os.environ['LOGPROF_DEBUG'] + logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) + debug_logger = logging.getLogger('logprof') import apparmor.config import apparmor.severity @@ -9,7 +27,7 @@ import LibAppArmor from apparmor.common import AppArmorException, error, debug, msg, open_file_read, readkey, valid_path -DEBUGGING = False + CONFDIR = '/etc/apparmor' running_under_genprof = False @@ -123,6 +141,15 @@ OPERATION_TYPES = { ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'} +def on_exit(): + """Shutdowns the logger and records exit if debugging enabled""" + if DEBUGGING: + debug_logger.debug('Exiting..') + logging.shutdown() + +# Register the on_exit method with atexit +atexit.register(on_exit) + def opt_type(operation): """Returns the operation type if known, unkown otherwise""" operation_type = OPERATION_TYPES.get(operation, 'unknown') @@ -137,7 +164,49 @@ def getkey(): if(ARROWS.get(key, False)): key = ARROWS[key] return key + +def check_for_LD_XXX(file): + """Returns True if specified program contains references to LD_PRELOAD or + LD_LIBRARY_PATH to give the PX/UX code better suggestions""" + found = False + if not os.path.isfile(file): + return False + size = os.stat(file).st_size + # Limit to checking files under 10k for the sake of speed + if size >10000: + return False + with open_file_read(file) as f_in: + for line in f_in: + if 'LD_PRELOAD' in line or 'LD_LIBRARY_PATH' in line: + found = True + return found + +def fatal_error(message): + if DEBUGGING: + # Get the traceback to the message + tb_stack = traceback.format_list(traceback.extract_stack()) + tb_stack = ''.join(tb_stack) + # Append the traceback to message + message = message + '\n' + tb_stack + debug_logger.error(message) + caller = inspect.stack()[1][3] + # If caller is SendDataToYast or GetDatFromYast simply exit + sys.exit(1) + + # Else tell user what happened + UI_Important(message) + shutdown_yast() + sys.exit(1) + +def setup_yast(): + # To-Do + pass + +def shutdown_yast(): + # To-Do + pass + def check_for_apparmor(): """Finds and returns the mointpoint for apparmor None otherwise""" filesystem = '/proc/filesystems' @@ -154,7 +223,7 @@ def check_for_apparmor(): with open_file_read(mounts) as f_in: for line in f_in: if support_securityfs: - match = regex_securityfs(line) + match = regex_securityfs.search(line) if match: mountpoint = match.groups()[0] + '/apparmor' if valid_path(mountpoint): @@ -165,7 +234,7 @@ def check_for_apparmor(): return aa_mountpoint def which(file): - """Returns the executable fullpath for the file None otherwise""" + """Returns the executable fullpath for the file, None otherwise""" env_dirs = os.getenv('PATH').split(':') for env_dir in env_dirs: env_path = env_dir + '/' + file @@ -174,3 +243,156 @@ def which(file): return env_path return None +def convert_regexp(regexp): + ## To Do + #regex_escape = re.compile('(? 64: + fatal_error("Followed too many links while resolving %s" % (original_path)) + direc, file = os.path.split(path) + link = os.readlink(path) + # If the link an absolute path + if link.startswith('/'): + path = link + else: + # Link is relative path + path = direc + '/' + link + return os.path.realpath(path) + +def find_executable(bin_path): + """Returns the full executable path for the binary given, None otherwise""" + full_bin = None + if os.path.exists(bin_path): + full_bin = get_full_path(bin_path) + else: + if '/' not in bin: + env_bin = which(bin) + if env_bin: + full_bin = get_full_path(env_bin) + if full_bin and os.path.exists(full_bin): + return full_bin + return None + +def get_profile_filename(profile): + """Returns the full profile name""" + filename = profile + if filename.startswith('/'): + # Remove leading / + filename = filename[1:] + else: + filename = "profile_" + filename + filename.replace('/', '.') + full_profilename = profile_dir + '/' + filename + return full_profilename + +def name_to_prof_filename(filename): + """Returns the profile""" + if bin.startswith(profile_dir): + profile = filename.split(profile_dir, 1)[1] + return (filename, profile) + else: + bin_path = find_executable(filename) + if bin_path: + filename = get_profile_filename(bin_path) + if os.path.isfile(filename): + return (filename, bin_path) + else: + return None, None + +def complain(path): + """Sets the profile to complain mode if it exists""" + filename, name = name_to_prof_filename(path) + if not filename : + fatal_error("Can't find %s" % path) + UI_Info('Setting %s to complain mode.' % name) + set_profile_flags(filename, 'complain') + +def enforce(path): + """Sets the profile to complain mode if it exists""" + filename, name = name_to_prof_filename(path) + if not filename : + fatal_error("Can't find %s" % path) + UI_Info('Setting %s to enforce moode' % name) + set_profile_flags(filename, '') + +def head(file): + """Returns the first/head line of the file""" + first = '' + if os.path.isfile(file): + with open_file_read(file) as f_in: + first = f_in.readline().rstrip() + return first + +def get_output(params): + """Returns the return code output by running the program with the args given in the list""" + program = params[0] + args = params[1:] + ret = -1 + output = [] + # program is executable + if os.access(program, os.X_OK): + try: + # Get the output of the program + output = subprocess.check_output(params) + except OSError as e: + raise AppArmorException("Unable to fork: %s\n\t%s" %(program, str(e))) + # If exit-codes besides 0 + except subprocess.CalledProcessError as e: + output = e.output + output = output.decode('utf-8').split('\n') + ret = e.returncode + else: + ret = 0 + output = output.decode('utf-8').split('\n') + # Remove the extra empty string caused due to \n if present + if len(output) > 1: + output.pop() + return (ret, output) + +def get_reqs(file): + """Returns a list of paths from ldd output""" + pattern1 = re.compile('^\s*\S+ => (\/\S+)') + pattern2 = re.compile('^\s*(\/\S+)') + reqs = [] + ret, ldd_out = get_output(ldd, file) + if ret == 0: + for line in ldd_out: + if 'not a dynamic executable' in line: + break + if 'cannot read header' in line: + break + if 'statically linked' in line: + break + match = pattern1.search(line) + if match: + reqs.append(match.groups()[0]) + else: + match = pattern2.search(line) + if match: + reqs.append(match.groups()[0]) + return reqs + +def handle_binfmt(profile, fqdbin): + reqs = dict() + \ No newline at end of file diff --git a/apparmor/config.py b/apparmor/config.py index f343372b6..4346aaa12 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -20,13 +20,16 @@ SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] class Config: def __init__(self, conf_type): # The type of config file that'll be read and/or written - self.conf_type = conf_type - self.input_file = None + if conf_type != 'shell' or conf_type != 'ini': + raise AppArmorException("Unknown configuration file type") + else: + self.conf_type = conf_type + self.input_file = None def new_config(self): if self.conf_type == 'shell': config = {'': dict()} - else: + elif self.conf_type == 'ini': config = configparser.ConfigParser() return config @@ -41,10 +44,10 @@ class Config: config['repository'] = {'enabled': 'no'} elif self.conf_type == 'shell': config = self.read_shell(filepath) - else: + elif self.conf_type == 'ini': config = configparser.ConfigParser() # Set the option form to string -prevents forced conversion to lowercase - #config.optionxform = str + config.optionxform = str if sys.version_info > (3,0): config.read(filepath) else: @@ -66,12 +69,11 @@ class Config: os.chmod(config_file.name, permission_600) if self.conf_type == 'shell': self.write_shell(filepath, config_file, config) - else: + elif self.conf_type == 'ini': self.write_configparser(filepath, config_file, config) - #config.write(config_file) config_file.close() except IOError: - raise AppArmorException("Unable to write to %s"%filename) + raise AppArmorException("Unable to write to %s" % filename) else: # Replace the target config file with the temporary file os.rename(config_file.name, filepath) diff --git a/apparmor/severity.py b/apparmor/severity.py index 4b65ddbe9..7d4f9283e 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -42,8 +42,8 @@ class Severity: try: database = open_file_read(dbname)#open(dbname, 'r') except IOError: - raise AppArmorException("Could not open severity database %s" % dbname) - for line in database: + raise AppArmorException("Could not open severity database: %s" % dbname) + for lineno, line in enumerate(database, start=1): line = line.strip() # or only rstrip and lstrip? if line == '' or line.startswith('#') : continue @@ -52,12 +52,10 @@ class Severity: path, read, write, execute = line.split() read, write, execute = int(read), int(write), int(execute) except ValueError: - msg("\nFile: %s" % dbname) - raise AppArmorException("Insufficient values for permissions in line: %s" % (line)) + raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) else: if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11): - msg("\nFile:"%(dbname)) - raise AppArmorException("Inappropriate values for permissions in line: %s" % line) + raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) path = path.lstrip('/') if '*' not in path: self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} @@ -78,16 +76,13 @@ class Severity: resource, severity = line.split() severity = int(severity) except ValueError: - msg("\nFile: %s" % dbname) - raise AppArmorException("No severity value present in line: %s" % (line)) + raise AppArmorException("No severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) else: if severity not in range(0,11): - msg("\nFile: %s" % dbname) - raise AppArmorException("Inappropriate severity value present in line: %s" % (line)) + raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) self.severity['CAPABILITIES'][resource] = severity else: - msg("\nFile: %s" % dbname) - raise AppArmorException("Unexpected database line: %s" % (line)) + raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) database.close() def convert_regexp(self, path): @@ -190,8 +185,6 @@ class Severity: rank = rank_new return rank else: - #print(resource) - #print(self.handle_file(resource, mode)) return self.handle_file(resource, mode) def variable_replace(self, variable, replacement, resource): From ccee5cd5e003d4ee58a0c53bb64973acb5720f47 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 9 Jul 2013 03:46:26 +0530 Subject: [PATCH 016/183] Codebase update 2 --- Testing/severity_test.py | 2 - apparmor/aa.py | 332 ++++++++++++++++++++++++++++++++++----- apparmor/common.py | 19 ++- apparmor/config.py | 17 +- apparmor/severity.py | 8 +- 5 files changed, 325 insertions(+), 53 deletions(-) diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 05e1d74aa..081f0f72c 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -6,7 +6,6 @@ Created on Jun 21, 2013 import sys import unittest -sys.path.append('../') sys.path.append('../apparmor') import apparmor.severity as severity @@ -17,7 +16,6 @@ class Test(unittest.TestCase): s = severity.Severity('severity.db') rank = s.rank('/dev/doublehit', 'i') self.assertEqual(rank, 10, 'Wrong') - broken = severity.Severity('severity_broken.db') try: broken = severity.Severity('severity_broken.db') except AppArmorException: diff --git a/apparmor/aa.py b/apparmor/aa.py index a62c26e7a..43985fc80 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,33 +1,35 @@ -#711 +#1082 #382-430 #480-525 #global variable names corruption from __future__ import with_statement import inspect +import logging import os import re import subprocess import sys import traceback +import atexit + +import apparmor.config +import apparmor.severity +import LibAppArmor + +from apparmor.common import (AppArmorException, error, debug, msg, + open_file_read, readkey, valid_path, + hasher, open_file_write) DEBUGGING = False debug_logger = None # Setup logging incase of debugging is enabled if os.getenv('LOGPROF_DEBUG', False): - import logging, atexit DEBUGGING = True logprof_debug = os.environ['LOGPROF_DEBUG'] logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) debug_logger = logging.getLogger('logprof') -import apparmor.config -import apparmor.severity -import LibAppArmor - -from apparmor.common import AppArmorException, error, debug, msg, open_file_read, readkey, valid_path - - CONFDIR = '/etc/apparmor' running_under_genprof = False @@ -72,7 +74,7 @@ pid = None seen = dir() profile_changes = dict() prelog = dict() -log = dict() +log_dict = dict() changed = dict() created = [] helpers = dict() # Preserve this between passes # was our @@ -264,7 +266,7 @@ def get_full_path(original_path): """Return the full path after resolving any symlinks""" path = original_path link_count = 0 - if not '/' in path: + if not path.startswith('/'): path = os.getcwd() + '/' + path while os.path.islink(path): link_count += 1 @@ -286,8 +288,8 @@ def find_executable(bin_path): if os.path.exists(bin_path): full_bin = get_full_path(bin_path) else: - if '/' not in bin: - env_bin = which(bin) + if '/' not in bin_path: + env_bin = which(bin_path) if env_bin: full_bin = get_full_path(env_bin) if full_bin and os.path.exists(full_bin): @@ -296,45 +298,44 @@ def find_executable(bin_path): def get_profile_filename(profile): """Returns the full profile name""" - filename = profile - if filename.startswith('/'): + if profile.startswith('/'): # Remove leading / - filename = filename[1:] + profile = profile[1:] else: - filename = "profile_" + filename - filename.replace('/', '.') - full_profilename = profile_dir + '/' + filename + profile = "profile_" + profile + profile.replace('/', '.') + full_profilename = profile_dir + '/' + profile return full_profilename -def name_to_prof_filename(filename): +def name_to_prof_filename(prof_filename): """Returns the profile""" - if bin.startswith(profile_dir): - profile = filename.split(profile_dir, 1)[1] - return (filename, profile) + if prof_filename.startswith(profile_dir): + profile = prof_filename.split(profile_dir, 1)[1] + return (prof_filename, profile) else: - bin_path = find_executable(filename) + bin_path = find_executable(prof_filename) if bin_path: - filename = get_profile_filename(bin_path) - if os.path.isfile(filename): - return (filename, bin_path) + prof_filename = get_profile_filename(bin_path) + if os.path.isfile(prof_filename): + return (prof_filename, bin_path) else: return None, None def complain(path): """Sets the profile to complain mode if it exists""" - filename, name = name_to_prof_filename(path) - if not filename : + prof_filename, name = name_to_prof_filename(path) + if not prof_filename : fatal_error("Can't find %s" % path) UI_Info('Setting %s to complain mode.' % name) - set_profile_flags(filename, 'complain') + set_profile_flags(prof_filename, 'complain') def enforce(path): """Sets the profile to complain mode if it exists""" - filename, name = name_to_prof_filename(path) - if not filename : + prof_filename, name = name_to_prof_filename(path) + if not prof_filename : fatal_error("Can't find %s" % path) UI_Info('Setting %s to enforce moode' % name) - set_profile_flags(filename, '') + set_profile_flags(prof_filename, '') def head(file): """Returns the first/head line of the file""" @@ -375,7 +376,7 @@ def get_reqs(file): pattern1 = re.compile('^\s*\S+ => (\/\S+)') pattern2 = re.compile('^\s*(\/\S+)') reqs = [] - ret, ldd_out = get_output(ldd, file) + ret, ldd_out = get_output([ldd, file]) if ret == 0: for line in ldd_out: if 'not a dynamic executable' in line: @@ -393,6 +394,263 @@ def get_reqs(file): reqs.append(match.groups()[0]) return reqs -def handle_binfmt(profile, fqdbin): - reqs = dict() - \ No newline at end of file +def handle_binfmt(profile, path): + """Modifies the profile to add the requirements""" + reqs_processed = dict() + reqs = get_reqs(path) + while reqs: + library = reqs.pop() + if not reqs_processed.get(library, False): + reqs.append(get_reqs(library)) + reqs_processed[library] = True + combined_mode = match_prof_incs_to_path(profile, 'allow', library) + if combined_mode: + continue + library = glob_common(library) + if not library: + continue + try: + profile['allow']['path'][library]['mode'] |= str_to_mode('mr') + except TypeError: + profile['allow']['path'][library]['mode'] = str_to_mode('mr') + try: + profile['allow']['path'][library]['audit'] |= 0 + except TypeError: + profile['allow']['path'][library]['audit'] = 0 + +def get_inactive_profile(local_profile): + if extras.get(local_profile, False): + return {local_profile: extras[local_profile]} + return dict() + +def create_new_profile(localfile): + local_profile = hasher() + local_profile[localfile]['flags'] = 'complain' + local_profile[localfile]['include']['abstractions/base'] = 1 + #local_profile = { + # localfile: { + # 'flags': 'complain', + # 'include': {'abstraction/base': 1}, + # 'allow': {'path': {}} + # } + # } + if os.path.isfile(localfile): + hashbang = head(localfile) + if hashbang.startswith('#!'): + interpreter = get_full_path(hashbang.lstrip('#!').strip()) + try: + local_profile[localfile]['allow']['path'][localfile]['mode'] |= str_to_mode('r') + except TypeError: + local_profile[localfile]['allow']['path'][localfile]['mode'] = str_to_mode('r') + try: + local_profile[localfile]['allow']['path'][localfile]['audit'] |= 0 + except TypeError: + local_profile[localfile]['allow']['path'][localfile]['audit'] = 0 + try: + local_profile[localfile]['allow']['path'][interpreter]['mode'] |= str_to_mode('ix') + except TypeError: + local_profile[localfile]['allow']['path'][interpreter]['mode'] = str_to_mode('ix') + try: + local_profile[localfile]['allow']['path'][interpreter]['audit'] |= 0 + except TypeError: + local_profile[localfile]['allow']['path'][interpreter]['audit'] = 0 + if 'perl' in interpreter: + local_profile[localfile]['include']['abstractions/perl'] = 1 + elif 'python' in interpreter: + local_profile[localfile]['include']['abstractions/python'] = 1 + elif 'ruby' in interpreter: + local_profile[localfile]['include']['abstractions/ruby'] = 1 + elif '/bin/bash' in interpreter or '/bin/dash' in interpreter or '/bin/sh' in interpreter: + local_profile[localfile]['include']['abstractions/ruby'] = 1 + handle_binfmt(local_profile[localfile], interpreter) + else: + try: + local_profile[localfile]['allow']['path'][localfile]['mode'] |= str_to_mode('mr') + except TypeError: + local_profile[localfile]['allow']['path'][localfile]['mode'] = str_to_mode('mr') + try: + local_profile[localfile]['allow']['path'][localfile]['audit'] |= 0 + except TypeError: + local_profile[localfile]['allow']['path'][localfile] = 0 + handle_binfmt(local_profile[localfile], localfile) + # Add required hats to the profile if they match the localfile + for hatglob in cfg['required_hats'].keys(): + if re.search(hatglob, localfile): + for hat in sorted(cfg['required_hats'][hatglob].split()): + local_profile[hat]['flags'] = 'complain' + + created.append(localfile) + if DEBUGGING: + debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) + return {localfile: local_profile} + +def delete_profile(local_prof): + """Deletes the specified file from the disk and remove it from our list""" + profile_file = get_profile_filename(local_prof) + if os.path.isfile(profile_file): + os.remove(profile_file) + if aa.get(local_prof, False): + aa.pop(local_prof) + +def get_profile(prof_name): + profile_data = None + distro = cfg['repository']['distro'] + repo_url = cfg['repository']['url'] + local_profiles = [] + profile_hash = hasher() + if repo_is_enabled(): + UI_BusyStart('Coonecting to repository.....') + status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name) + UI_BustStop() + if status_ok: + profile_hash = ret + else: + UI_Important('WARNING: Error fetching profiles from the repository') + inactive_profile = get_inactive_profile(prof_name) + if inactive_profile: + uname = 'Inactive local profile for %s' % prof_name + inactive_profile[prof_name][prof_name]['flags'] = 'complain' + inactive_profile[prof_name][prof_name].pop('filename') + profile_hash[uname]['username'] = uname + profile_hash[uname]['profile_type'] = 'INACTIVE_LOCAL' + profile_hash[uname]['profile'] = serialize_profile(inactive_profile[prof_name], prof_name) + profile_hash[uname]['profile_data'] = inactive_profile + # If no profiles in repo and no inactive profiles + if not profile_hash.keys(): + return None + options = [] + tmp_list = [] + preferred_present = False + preferred_user = cfg['repository'].get('preferred_user', 'NOVELL') + + for p in profile_hash.keys(): + if profile_hash[p]['username'] == preferred_user: + preferred_present = True + else: + tmp_list.append(profile_hash[p]['username']) + + if preferred_present: + options.append(preferred_user) + options += tmp_list + + q = dict() + q['headers'] = ['Profile', prof_name] + q['functions'] = ['CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE', + 'CMD_ABORT', 'CMD_FINISHED'] + q['default'] = "CMD_VIEW_PROFILE" + q['options'] = options + q['selected'] = 0 + + ans = '' + while 'CMD_USE_PROFILE' not in ans and 'CMD_CREATE_PROFILE' not in ans: + ans, arg = UI_PromptUser(q) + p = profile_hash[options[arg]] + q['selected'] = options.index(options[arg]) + if ans == 'CMD_VIEW_PROFILE': + if UI_mode == 'yast': + SendDataToYast({ + 'type': 'dialogue-view-profile', + 'user': options[arg], + 'profile': p['profile'], + 'profile_type': p['profile_type'] + }) + ypath, yarg = GetDataFromYast() + else: + pager = get_pager() + proc = subprocess.Popen(pager, stdin=subprocess.PIPE) + proc.communicate('Profile submitted by %s:\n\n%s\n\n' % + (options[arg], p['profile'])) + proc.kill() + elif ans == 'CMD_USE_PROFILE': + if p['profile_type'] == 'INACTIVE_LOCAL': + profile_data = p['profile_data'] + created.append(prof_name) + else: + profile_data = parse_repo_profile(prof_name, repo_url, p) + return profile_data + +def activate_repo_profiles(url, profiles, complain): + read_profiles() + try: + for p in profiles: + pname = p[0] + profile_data = parse_repo_profile(pname, url, p[1]) + attach_profile_data(aa, profile_data) + write_profile(pname) + if complain: + fname = get_profile_filename(pname) + set_profile_flags(fname, 'complain') + UI_Info('Setting %s to complain mode.' % pname) + except Exception as e: + sys.stderr.write("Error activating profiles: %s" % e) + +def autodep(bin_name, pname=''): + bin_full = None + if not bin_name and pname.startswith('/'): + bin_name = pname + if not repo_cfg and not cfg['repository'].get('url', False): + repo_cfg = read_config('repository.conf') + if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later': + UI_ask_to_enable_repo() + if bin_name: + bin_full = find_executable(bin_name) + #if not bin_full: + # bin_full = bin_name + #if not bin_full.startswith('/'): + #return None + # Return if exectuable path not found + if not bin_full: + return None + pname = bin_full + read_inactive_profile() + profile_data = get_profile(pname) + # Create a new profile if no existing profile + if not profile_data: + profile_data = create_new_profile(pname) + file = get_profile_filename(pname) + attach_profile_data(aa, profile_data) + attach_profile_data(aa_original, profile_data) + if os.path.isfile(profile_dir + '/tunables/global'): + if not filelist.get(file, False): + filelist.file = hasher() + filelist[file][include]['tunables/global'] = True + write_profile_ui_feedback(pname) + +def set_profile_flags(prof_filename, newflags): + """Reads the old profile file and updates the flags accordingly""" + regex_bin_flag = re.compile('^(\s*)(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+(flags=\(.+\)\s+)*\{\s*$/') + regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*$') + if os.path.isfile(prof_filename): + with open_file_read(prof_filename) as f_in: + with open_file_write(prof_filename + '.new') as f_out: + for line in f_in: + match = regex_bin_flag.search(line) + if match: + space, binary, flags = match.groups() + if newflags: + line = '%s%s flags=(%s) {\n' % (space, binary, newflags) + else: + line = '%s%s {\n' % (space, binary) + else: + match = regex_hat_flag.search(line) + if match: + hat, flags = match.groups() + if newflags: + line = '%s flags=(%s) {\n' % (hat, newflags) + else: + line = '%s {\n' % hat + f_out.write(line) + os.rename(prof_filename+'.new', prof_filename) + +def profile_exists(program): + """Returns True if profile exists, False otherwise""" + # Check cache of profiles + if existing_profiles.get(program, False): + return True + # Check the disk for profile + prof_path = get_profile_filename(program) + if os.path.isfile(prof_path): + # Add to cache of profile + existing_profiles[program] = True + return True + return False diff --git a/apparmor/common.py b/apparmor/common.py index 6f7f88907..41bfb7a4b 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -1,5 +1,6 @@ from __future__ import print_function import codecs +import collections import glob import os import subprocess @@ -18,6 +19,7 @@ class AppArmorException(Exception): self.value = value def __str__(self): + return self.value return repr(self.value) # @@ -121,12 +123,20 @@ def get_directory_contents(path): def open_file_read(path): '''Open specified file read-only''' try: - orig = codecs.open(path, 'r', "UTF-8") + orig = codecs.open(path, 'r', 'UTF-8') except Exception: raise return orig +def open_file_write(path): + """Open specified file in write/overwrite mode""" + try: + orig = codecs.open(path, 'w', 'UTF-8') + except Exception: + raise + return orig + def readkey(): """Returns the pressed key""" fd = sys.stdin.fileno() @@ -137,4 +147,9 @@ def readkey(): finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - return ch \ No newline at end of file + return ch + +def hasher(): + """A neat alternative to perl's hash reference""" + # Creates a dictionary for any depth and returns empty dictionary otherwise + return collections.defaultdict(hasher) \ No newline at end of file diff --git a/apparmor/config.py b/apparmor/config.py index 4346aaa12..ba57615d8 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -20,12 +20,12 @@ SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] class Config: def __init__(self, conf_type): # The type of config file that'll be read and/or written - if conf_type != 'shell' or conf_type != 'ini': - raise AppArmorException("Unknown configuration file type") - else: + if conf_type == 'shell' or conf_type == 'ini': self.conf_type = conf_type self.input_file = None - + else: + raise AppArmorException("Unknown configuration file type") + def new_config(self): if self.conf_type == 'shell': config = {'': dict()} @@ -82,11 +82,10 @@ class Config: def find_first_file(self, file_list): """Returns name of first matching file None otherwise""" filename = None - if filename: - for file in file_list.split(): - if os.path.isfile(file): - filename = file - break + for file in file_list.split(): + if os.path.isfile(file): + filename = file + break return filename def find_first_dir(self, dir_list): diff --git a/apparmor/severity.py b/apparmor/severity.py index 7d4f9283e..7b1d0b800 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -28,7 +28,7 @@ class Severity: line = line.split('+=') try: self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()] - except KeyError: + except KeyError as e: raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values" % line[0]) else: line = line.split('=') @@ -75,8 +75,10 @@ class Severity: try: resource, severity = line.split() severity = int(severity) - except ValueError: - raise AppArmorException("No severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) + except ValueError as e: + error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) + error(error_message) + raise AppArmorException(error_message) from None else: if severity not in range(0,11): raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) From a33c95f8b17c7b1d392adb59812eb620b070a310 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Wed, 17 Jul 2013 20:38:13 +0530 Subject: [PATCH 017/183] Some fixes from review16 and updated codebase --- Testing/severity_test.py | 2 +- apparmor/aa.py | 266 +++++++++++++++++++++++++++++++++++++-- apparmor/common.py | 1 - apparmor/ui.py | 255 +++++++++++++++++++++++++++++++++++++ apparmor/yasti.py | 82 ++++++++++++ 5 files changed, 596 insertions(+), 10 deletions(-) create mode 100644 apparmor/ui.py create mode 100644 apparmor/yasti.py diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 081f0f72c..aa923f1de 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -6,7 +6,7 @@ Created on Jun 21, 2013 import sys import unittest -sys.path.append('../apparmor') +sys.path.append('../') import apparmor.severity as severity from apparmor.common import AppArmorException diff --git a/apparmor/aa.py b/apparmor/aa.py index 43985fc80..4055ac1ba 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,4 @@ -#1082 +#1321 #382-430 #480-525 #global variable names corruption @@ -7,10 +7,12 @@ import inspect import logging import os import re +import shutil import subprocess import sys import traceback import atexit +import tempfile import apparmor.config import apparmor.severity @@ -20,6 +22,8 @@ from apparmor.common import (AppArmorException, error, debug, msg, open_file_read, readkey, valid_path, hasher, open_file_write) +from apparmor.ui import * + DEBUGGING = False debug_logger = None @@ -491,6 +495,7 @@ def delete_profile(local_prof): os.remove(profile_file) if aa.get(local_prof, False): aa.pop(local_prof) + prof_unload(local_prof) def get_profile(prof_name): profile_data = None @@ -619,28 +624,36 @@ def autodep(bin_name, pname=''): def set_profile_flags(prof_filename, newflags): """Reads the old profile file and updates the flags accordingly""" regex_bin_flag = re.compile('^(\s*)(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+(flags=\(.+\)\s+)*\{\s*$/') - regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*$') + regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') + a=re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') + regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*(#*\S*)$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: - with open_file_write(prof_filename + '.new') as f_out: + tempfile = tempfile.NamedTemporaryFile('w', prefix=prof_filename , delete=False, dir='/etc/apparmor.d/') + shutil.copymode('/etc/apparmor.d/' + prof_filename, tempfile.name) + with open_file_write(tempfile.name) as f_out: for line in f_in: + if '#' in line: + comment = '#' + line.split('#', 1)[1].rstrip() + else: + comment = '' match = regex_bin_flag.search(line) if match: space, binary, flags = match.groups() if newflags: - line = '%s%s flags=(%s) {\n' % (space, binary, newflags) + line = '%s%s flags=(%s) {%s\n' % (space, binary, newflags, comment) else: - line = '%s%s {\n' % (space, binary) + line = '%s%s {%s\n' % (space, binary, comment) else: match = regex_hat_flag.search(line) if match: hat, flags = match.groups() if newflags: - line = '%s flags=(%s) {\n' % (hat, newflags) + line = '%s flags=(%s) {%s\n' % (hat, newflags, comment) else: - line = '%s {\n' % hat + line = '%s {%s\n' % (hat, comment) f_out.write(line) - os.rename(prof_filename+'.new', prof_filename) + os.rename(tempfile.name, prof_filename) def profile_exists(program): """Returns True if profile exists, False otherwise""" @@ -654,3 +667,240 @@ def profile_exists(program): existing_profiles[program] = True return True return False + +def sync_profile(): + user, passw = get_repo_user_pass() + if not user or not passw: + return None + repo_profiles = [] + changed_profiles = [] + new_profiles = [] + serialize_opts = hasher() + status_ok, ret = fetch_profiles_by_user(cfg['repository']['url'], + cfg['repository']['distro'], user) + if not status_ok: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: Error synchronizing profiles with the repository:\n%s\n' % ret) + else: + users_repo_profiles = ret + serialuze_opts['NO_FLAGS'] = True + for prof in sorted(aa.keys()): + if is_repo_profile([aa[prof][prof]]): + repo_profiles.append(prof) + if prof in created: + p_local = seralize_profile(aa[prof], prof, serialize_opts) + if not users_repo_profiles.get(prof, False): + new_profiles.append(prof) + new_profiles.append(p_local) + new_profiles.append('') + else: + p_repo = users_repo_profiles[prof]['profile'] + if p_local != p_repo: + changed_profiles.append(prof) + changed_profiles.append(p_local) + changed_profiles.append(p_repo) + if repo_profiles: + for prof in repo_profiles: + p_local = serialize_profile(aa[prof], prof, serialize_opts) + if not users_repo_profiles.get(prof, False): + new_profiles.append(prof) + new_profiles.append(p_local) + new_profiles.append('') + else: + p_repo = '' + if aa[prof][prof]['repo']['user'] == user: + p_repo = users_repo_profiles[prof]['profile'] + else: + status_ok, ret = fetch_profile_by_id(cfg['repository']['url'], + aa[prof][prof]['repo']['id']) + if status_ok: + p_repo = ret['profile'] + else: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: Error synchronizing profiles witht he repository\n%s\n' % ret) + continue + if p_repo != p_local: + changed_profiles.append(prof) + changed_profiles.append(p_local) + changed_profiles.append(p_repo) + if changed_profiles: + submit_changed_profiles(changed_profiles) + if new_profiles: + submit_created_profiles(new_profiles) + +def submit_created_profiles(new_profiles): + #url = cfg['repository']['url'] + if new_profiles: + if UI_mode == 'yast': + title = 'New Profiles' + message = 'Please select the newly created profiles that you would like to store in the repository' + yast_select_and_upload_profiles(title, message, new_profiles) + else: + title = 'Submit newly created profiles to the repository' + message = 'Would you like to upload newly created profiles?' + console_select_and_upload_profiles(title, message, new_profiles) + +def submit_changed_profiles(changed_profiles): + #url = cfg['repository']['url'] + if changed_profiles: + if UI_mode == 'yast': + title = 'Changed Profiles' + message = 'Please select which of the changed profiles would you like to upload to the repository' + yast_select_and_upload_profiles(title, message, changed_profiles) + else: + title = 'Submit changed profiles to the repository' + message = 'The following profiles from the repository were changed.\nWould you like to upload your changes?' + console_select_and_upload_profiles(title, message, changed_profiles) + +def yast_select_and_upload_profiles(title, message, profiles_up): + url = cfg['repository']['url'] + profile_changes = hasher() + profs = profiles_up[:] + for p in profs: + profile_changes[p[0]] = get_profile_diff(p[2], p[1]) + SendDataToYast({ + 'type': 'dialog-select-profiles', + 'title': title, + 'explanation': message, + 'default_select': 'false', + 'disable_ask_upload': 'true', + 'profiles': profile_changes + }) + ypath, yarg = GetDataFromYast() + selected_profiles = [] + changelog = None + changelogs = None + single_changelog = False + if yarg['STATUS'] == 'cancel': + return + else: + selected_profiles = yarg['PROFILES'] + changelogs = yarg['CHANGELOG'] + if changelogs.get('SINGLE_CHANGELOG', False): + changelog = changelogs['SINGLE_CHANGELOG'] + single_changelog = True + user, passw = get_repo_user_pass() + for p in selected_profiles: + profile_string = serialize_profile(aa[p], p) + if not single_changelog: + changelog = changelogs[p] + status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'], + p, profile_string, changelog) + if status_ok: + newprofile = ret + newid = newprofile['id'] + set_repo_info(aa[p][p], url, user, newid) + write_profile_ui_feedback(p) + else: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: An error occured while uploading the profile %s\n%s\n' % (p, ret)) + UI_Info('Uploaded changes to repository.') + if yarg.get('NEVER_ASK_AGAIN'): + unselected_profiles = [] + for p in profs: + if p[0] not in selected_profiles: + unselected_profiles.append(p[0]) + set_profiles_local_only(unselected_profiles) + +def console_select_and_upload_profiles(title, message, profiles_up): + url = cfg['repository']['url'] + profs = profiles_up[:] + q = hasher() + q['title'] = title + q['headers'] = ['Repository', url] + q['explanation'] = message + q['functions'] = ['CMD_UPLOAD_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ASK_LATER', + 'CMD_ASK_NEVER', 'CMD_ABORT'] + q['default'] = 'CMD_VIEW_CHANGES' + q['options'] = [i[0] for i in profs] + q['selected'] = 0 + ans = '' + while 'CMD_UPLOAD_CHANGES' not in ans and 'CMD_ASK_NEVER' not in ans and 'CMD_ASK_LATER' not in ans: + ans, arg = UI_PromptUser(q) + if ans == 'CMD_VIEW_CHANGES': + display_changes(profs[arg][2], profs[arg][1]) + if ans == 'CMD_NEVER_ASK': + set_profiles_local_only([i[0] for i in profs]) + elif ans == 'CMD_UPLOAD_CHANGES': + changelog = UI_GetString('Changelog Entry: ', '') + user, passw = get_repo_user_pass() + if user and passw: + for p_data in profs: + prof = p_data[0] + prof_string = p_data[1] + status_ok, ret = upload_profile(url, user, passw, + cfg['repository']['distro'], + prof, prof_string, changelog ) + if status_ok: + newprof = ret + newid = newprof['id'] + set_repo_info(aa[prof][prof], url, user, newid) + write_profile_ui_feedback(prof) + UI_Info('Uploaded %s to repository' % prof) + else: + if not ret: + ret = 'UNKNOWN ERROR' + UI_Important('WARNING: An error occured while uploading the profile %s\n%s\n' % (prof, ret)) + else: + UI_Important('Repository Error\nRegistration or Sigin was unsuccessful. User login\n' + + 'information is required to upload profiles to the repository.\n' + + 'These changes could not be sent.\n') + +def set_profile_local_only(profs): + for p in profs: + aa[profs][profs]['repo']['neversubmit'] = True + writeback_ui_feedback(profs) + +def confirm_and_abort(): + ans = UI_YesNo('Are you sure you want to abandon this set of profile changes and exit?', 'n') + if ans == 'y': + UI_Info('Abandoning all changes.') + shutdown_yast() + for prof in created: + delete_profile(prof) + sys.exit(0) + +def confirm_and_finish(): + sys.stdout.write('Finishing\n') + sys.exit(0) + +def build_x_functions(default, options, exec_toggle): + ret_list = [] + if exec_toggle: + if 'i' in options: + ret_list.append('CMD_ix') + if 'p' in options: + ret_list.append('CMD_pix') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'c' in options: + ret_list.append('CMD_cix') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'n' in options: + ret_list.append('CMD_nix') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'u' in options: + ret_list.append('CMD_ux') + else: + if 'i' in options: + ret_list.append('CMD_ix') + elif 'c' in options: + ret_list.append('CMD_cx') + ret_list.append('CMD_EXEC_IX_ON') + elif 'p' in options: + ret_list.append('CMD_px') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'n' in options: + ret_list.append('CMD_nx') + ret_list.append('CMD_EXEC_IX_OFF') + elif 'u' in options: + ret_list.append('CMD_ux') + ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] + return ret_list + +def handle_children(profile, hat, root): + entries = root[:] + for entry in entries: + \ No newline at end of file diff --git a/apparmor/common.py b/apparmor/common.py index 41bfb7a4b..8653eca48 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -19,7 +19,6 @@ class AppArmorException(Exception): self.value = value def __str__(self): - return self.value return repr(self.value) # diff --git a/apparmor/ui.py b/apparmor/ui.py new file mode 100644 index 000000000..1ce7de2fe --- /dev/null +++ b/apparmor/ui.py @@ -0,0 +1,255 @@ +# 1728 +import sys +import gettext +import locale +from yasti import yastLog, SendDataToYast, GetDataFromYast + +from apparmor.common import readkey + +DEBUGGING = False +debug_logger = None +UI_mode = 'text' + +def init_localisation(): + locale.setlocale(locale.LC_ALL, '') + #cur_locale = locale.getlocale() + filename = 'res/messages_%s.mo' % locale.getlocale()[0][0:2] + try: + trans = gettext.GNUTranslations(open( filename, 'rb')) + except IOError: + trans = gettext.NullTranslations() + trans.install() + +def UI_Info(text): + if DEBUGGING: + debug_logger.info(text) + if UI_mode == 'text': + sys.stdout.write(text + '\n') + else: + yastLog(text) + +def UI_Important(text): + if DEBUGGING: + debug_logger.debug(text) + if UI_mode == 'text': + sys.stdout.write('\n' + text + '\n') + else: + SendDataToYast({ + 'type': 'dialog-error', + 'message': text + }) + path, yarg = GetDataFromYast() + +def UI_YesNo(text, default): + if DEBUGGING: + debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default)) + ans = default + if UI_mode == 'text': + yes = '(Y)es' + no = '(N)o' + usrmsg = 'PromptUser: Invalid hotkey for' + yeskey = 'y' + nokey = 'n' + sys.stdout.write('\n' + text + '\n') + if default == 'y': + sys.stdout.write('\n[%s] / %s\n' % (yes, no)) + else: + sys.stdout.write('\n%s / [%s]\n' % (yes, no)) + ans = readkey() + if ans: + ans = ans.lower() + else: + ans = default + else: + SendDataTooYast({ + 'type': 'dialog-yesno', + 'question': text + }) + ypath, yarg = GetDataFromYast() + ans = yarg['answer'] + if not ans: + ans = default + return ans + +def UI_YesNoCancel(text, default): + if DEBUGGING: + debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default)) + + if UI_mode == 'text': + yes = '(Y)es' + no = '(N)o' + cancel = '(C)ancel' + yeskey = 'y' + nokey = 'n' + cancelkey = 'c' + ans = 'XXXINVALIDXXX' + while ans != 'c' and ans != 'n' and ans != 'y': + sys.stdout.write('\n' + text + '\n') + if default == 'y': + sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel)) + elif default == 'n': + sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel)) + else: + sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel)) + ans = readkey() + if ans: + ans = ans.lower() + else: + ans = default + else: + SendDataToYast({ + 'type': 'dialog-yesnocancel', + 'question': text + }) + ypath, yarg = GetDataFromYast() + ans = yarg['answer'] + if not ans: + ans = default + return ans + +def UI_GetString(text, default): + if DEBUGGING: + debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default)) + string = default + if UI_mode == 'text': + sys.stdout.write('\n' + text + '\n') + string = sys.stdin.readline() + else: + SendDataToYast({ + 'type': 'dialog-getstring', + 'label': text, + 'default': default + }) + ypath, yarg = GetDatFromYast() + string = yarg['string'] + return string + +def UI_GetFile(file): + if DEBUGGING: + debug_logger.debug('UI_GetFile: %s' % UI_mode) + filename = None + if UI_mode == 'text': + sys.stdout.write(file['description'] + '\n') + filename = sys.stdin.read() + else: + file['type'] = 'dialog-getfile' + SendDataToYast(file) + ypath, yarg = GetDataFromYast() + if yarg['answer'] == 'okay': + filename = yarg['filename'] + return filename + +def UI_BusyStart(message): + if DEBUGGING: + debug_logger.debug('UI_BusyStart: %s' % UI_mode) + if UI_mode == 'text': + UI_Info(message) + else: + SendDataToYast({ + 'type': 'dialog-busy-start', + 'message': message + }) + ypath, yarg = GetDataFromYast() + +def UI_BusyStop(): + if DEBUGGING: + debug_logger.debug('UI_BusyStop: %s' % UI_mode) + if UI_mode != 'text': + SendDataToYast({'type': 'dialog-busy-stop'}) + ypath, yarg = GetDataFromYast() + +CMDS = { + 'CMD_ALLOW': '(A)llow', + 'CMD_OTHER': '(M)ore', + 'CMD_AUDIT_NEW': 'Audi(t)', + 'CMD_AUDIT_OFF': 'Audi(t) off', + 'CMD_AUDIT_FULL': 'Audit (A)ll', + #'CMD_OTHER': '(O)pts', + 'CMD_USER_ON': '(O)wner permissions on', + 'CMD_USER_OFF': '(O)wner permissions off', + 'CMD_DENY': '(D)eny', + 'CMD_ABORT': 'Abo(r)t', + 'CMD_FINISHED': '(F)inish', + 'CMD_ix': '(I)nherit', + 'CMD_px': '(P)rofile', + 'CMD_px_safe': '(P)rofile Clean Exec', + 'CMD_cx': '(C)hild', + 'CMD_cx_safe': '(C)hild Clean Exec', + 'CMD_nx': 'Named', + 'CMD_nx_safe': 'Named Clean Exec', + 'CMD_ux': '(U)nconfined', + 'CMD_ux_safe': '(U)nconfined Clean Exec', + 'CMD_pix': '(P)rofile Inherit', + 'CMD_pix_safe': '(P)rofile Inherit Clean Exec', + 'CMD_cix': '(C)hild Inherit', + 'CMD_cix_safe': '(C)hild Inherit Clean Exec', + 'CMD_nix': '(N)amed Inherit', + 'CMD_nix_safe': '(N)amed Inherit Clean Exec', + 'CMD_EXEC_IX_ON': '(X) ix On', + 'CMD_EXEC_IX_OFF': '(X) ix Off', + 'CMD_SAVE': '(S)ave Changes', + 'CMD_CONTINUE': '(C)ontinue Profiling', + 'CMD_NEW': '(N)ew', + 'CMD_GLOB': '(G)lob', + 'CMD_GLOBEXT': 'Glob with (E)xtension', + 'CMD_ADDHAT': '(A)dd Requested Hat', + 'CMD_USEDEFAULT': '(U)se Default Hat', + 'CMD_SCAN': '(S)can system log for AppArmor events', + 'CMD_HELP': '(H)elp', + 'CMD_VIEW_PROFILE': '(V)iew Profile', + 'CMD_USE_PROFILE': '(U)se Profile', + 'CMD_CREATE_PROFILE': '(C)reate New Profile', + 'CMD_UPDATE_PROFILE': '(U)pdate Profile', + 'CMD_IGNORE_UPDATE': '(I)gnore Update', + 'CMD_SAVE_CHANGES': '(S)ave Changes', + 'CMD_UPLOAD_CHANGES': '(U)pload Changes', + 'CMD_VIEW_CHANGES': '(V)iew Changes', + 'CMD_VIEW': '(V)iew', + 'CMD_ENABLE_REPO': '(E)nable Repository', + 'CMD_DISABLE_REPO': '(D)isable Repository', + 'CMD_ASK_NEVER': '(N)ever Ask Again', + 'CMD_ASK_LATER': 'Ask Me (L)ater', + 'CMD_YES': '(Y)es', + 'CMD_NO': '(N)o', + 'CMD_ALL_NET': 'Allow All (N)etwork', + 'CMD_NET_FAMILY': 'Allow Network Fa(m)ily', + 'CMD_OVERWRITE': '(O)verwrite Profile', + 'CMD_KEEP': '(K)eep Profile', + 'CMD_CONTINUE': '(C)ontinue' + } + +def UI_PromptUser(q): + cmd = None + arg = None + if UI_mode == 'text': + cmd, arg = Text_PromptUser(q) + else: + q['type'] = 'wizard' + SendDataToYast(q) + ypath, yarg = GetDataFromYast() + if not cmd: + cmd = 'CMD_ABORT' + arg = yarg['selected'] + if cmd == 'CMD_ABORT': + confirm_and_abort() + cmd == 'XXXINVALIDXXX' + elif cmd == 'CMD_FINISHED': + confirm_and_finish() + cmd == 'XXXINVALIDXXX' + return (cmd, arg) + +def UI_ShortMessage(title, message): + SendDataToYast({ + 'type': 'short-dialog-message', + 'headline': title, + 'message': message + }) + ypath, yarg = GetDataFromYast() + +def UI_longMessage(title, message): + SendDataToYast({ + 'type': 'long-dialog-message', + 'headline': title, + 'message': message + }) + ypath, yarg = GetDataFromYast() diff --git a/apparmor/yasti.py b/apparmor/yasti.py new file mode 100644 index 000000000..a2928c08b --- /dev/null +++ b/apparmor/yasti.py @@ -0,0 +1,82 @@ +import re +import ycp + +def yastLog(text): + ycp.y2milestone(text) + +def SendDataToYast(data): + if DEBUGGING: + debug_logger.info('SendDataToYast: Waiting for YCP command') + for line in sys.stdin: + ycommand, ypath, yargument = ParseCommand(line) + if ycommand and ycommand == 'Read': + if DEBUGGING: + debug_logger.info('SendDataToYast: Sending--%s' % data) + Return(data) + return True + else: + if DEBUGGING: + debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) + fatal_error('SendDataToYast: didn\'t receive YCP command before connection died') + +def GetDataFromYast(): + if DEBUGGING: + debug_logger.inf('GetDataFromYast: Waiting for YCP command') + for line in sys.stdin: + if DEBUGGING: + debug_logger.info('GetDataFromYast: YCP: %s' % line) + ycommand, ypath, yarg = ParseCommand(line) + if DEBUGGING: + debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg) + if ycommand and ycommand == 'Write': + Return('true') + return ypath, yarg + else: + if DEBUGGING: + debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) + fatal_error('GetDataFromYast: didn\'t receive YCP command before connection died') + +def ParseCommand(commands): + term = ParseTerm(commands) + if term: + command = term[0] + term = term[1:] + else: + command = '' + path = '' + pathref = None + if term: + pathref = term[0] + term = term[1:] + if pathref: + if pathref.strip(): + path = pathref.strip() + elif command != 'result': + ycp.y2error('The first arguement is not a path. (%s)' % pathref) + argument = None + if term: + argument = term[0] + if len(term) > 1: + ycp.y2warning('Superfluous command arguments ignored') + return (command, path, argument) + +def ParseTerm(input): + regex_term = re.compile('^\s*`?(\w*)\s*') + term = regex_term.search(input) + ret = [] + symbol = None + if term: + symbol = term.groups()[0] + else: + ycp.y2error('No term symbol') + ret.append(symbol) + input = regex_term.sub('', input) + if not input.startswith('('): + ycp.y2error('No term parantheses') + argref, err, rest = ParseYcpTermBody(input) + if err: + ycp.y2error('%s (%s)' % (err, rest)) + else: + ret += argref + return ret + From f4b89ce45b5df89bf59cf9d19ec20e22d7e69e57 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 18 Jul 2013 03:11:05 +0530 Subject: [PATCH 018/183] ugly solution to py2 configparser by stripping 2 spaces off everyline into a tempfile --- apparmor/aa.py | 2 +- apparmor/config.py | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 4055ac1ba..775b29d83 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -506,7 +506,7 @@ def get_profile(prof_name): if repo_is_enabled(): UI_BusyStart('Coonecting to repository.....') status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name) - UI_BustStop() + UI_BusyStop() if status_ok: profile_hash = ret else: diff --git a/apparmor/config.py b/apparmor/config.py index ba57615d8..cb2bd7f6d 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -51,7 +51,8 @@ class Config: if sys.version_info > (3,0): config.read(filepath) else: - config.readfp(open_file_read(filepath)) + tmp_filepath = py2_parser(filepath) + config.read(tmp_filepath.name) return config def write_config(self, filename, config): @@ -245,4 +246,18 @@ class Config: options = config.options(section) for option in options: line = ' ' + option + ' = ' + config[section][option] + '\n' - f_out.write(line) \ No newline at end of file + f_out.write(line) + +def py2_parser(filename): + tmp = tempfile.NamedTemporaryFile('rw') + f_out = open(tmp.name, 'w') + if os.path.exists(filename): + with open_file_read(filename) as f_in: + for line in f_in: + if line[:2] == ' ': + line = line[2:] + f_out.write(line) + f_out.flush() + return tmp + + \ No newline at end of file From f5b43cc7b4a96bdd29c611a1713d71b04dcdb0b0 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 18 Jul 2013 03:21:44 +0530 Subject: [PATCH 019/183] --- apparmor/config.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apparmor/config.py b/apparmor/config.py index cb2bd7f6d..f89630481 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -51,8 +51,11 @@ class Config: if sys.version_info > (3,0): config.read(filepath) else: - tmp_filepath = py2_parser(filepath) - config.read(tmp_filepath.name) + try: + config.read(filepath) + except configparser.ParsingError: + tmp_filepath = py2_parser(filepath) + config.read(tmp_filepath.name) return config def write_config(self, filename, config): @@ -249,6 +252,7 @@ class Config: f_out.write(line) def py2_parser(filename): + "Returns the de-dented ini file from the new format ini" tmp = tempfile.NamedTemporaryFile('rw') f_out = open(tmp.name, 'w') if os.path.exists(filename): @@ -258,6 +262,4 @@ def py2_parser(filename): line = line[2:] f_out.write(line) f_out.flush() - return tmp - - \ No newline at end of file + return tmp \ No newline at end of file From da9cd60ec480ca77c515e2f89368978cb009781a Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 18 Jul 2013 05:29:54 +0530 Subject: [PATCH 020/183] --- apparmor/aa.py | 18 +++++------------- apparmor/ui.py | 10 ++++++++++ apparmor/yasti.py | 25 +++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 775b29d83..2f7a52a8c 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -30,7 +30,7 @@ debug_logger = None # Setup logging incase of debugging is enabled if os.getenv('LOGPROF_DEBUG', False): DEBUGGING = True - logprof_debug = os.environ['LOGPROF_DEBUG'] + logprof_debug = '/var/log/apparmor/logprof.log' logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) debug_logger = logging.getLogger('logprof') @@ -38,8 +38,7 @@ if os.getenv('LOGPROF_DEBUG', False): CONFDIR = '/etc/apparmor' running_under_genprof = False unimplemented_warning = False -# The operating mode: yast or text, text by default -UI_mode = 'text' + # The database for severity sev_db = None # The file to read log messages from @@ -173,7 +172,7 @@ def getkey(): def check_for_LD_XXX(file): """Returns True if specified program contains references to LD_PRELOAD or - LD_LIBRARY_PATH to give the PX/UX code better suggestions""" + LD_LIBRARY_PATH to give the Px/Ux code better suggestions""" found = False if not os.path.isfile(file): return False @@ -198,20 +197,13 @@ def fatal_error(message): caller = inspect.stack()[1][3] # If caller is SendDataToYast or GetDatFromYast simply exit - sys.exit(1) + if caller == 'SendDataToYast' or caller== 'GetDatFromYast': + sys.exit(1) # Else tell user what happened UI_Important(message) shutdown_yast() sys.exit(1) - -def setup_yast(): - # To-Do - pass - -def shutdown_yast(): - # To-Do - pass def check_for_apparmor(): """Finds and returns the mointpoint for apparmor None otherwise""" diff --git a/apparmor/ui.py b/apparmor/ui.py index 1ce7de2fe..f84f58233 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -2,12 +2,22 @@ import sys import gettext import locale +import logging +import os from yasti import yastLog, SendDataToYast, GetDataFromYast from apparmor.common import readkey DEBUGGING = False debug_logger = None +# Set up UI logger for separate messages from UI module +if os.getenv('LOGPROF_DEBUG', False): + DEBUGGING = True + logprof_debug = '/var/log/apparmor/logprof.log' + logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) + debug_logger = logging.getLogger('UI') + +# The operating mode: yast or text, text by default UI_mode = 'text' def init_localisation(): diff --git a/apparmor/yasti.py b/apparmor/yasti.py index a2928c08b..22d6e12c3 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -1,5 +1,26 @@ import re import ycp +import os +import sys +import logging + +from apparmor.common import error +DEBUGGING = False +debug_logger = None +# Set up UI logger for separate messages from YaST module +if os.getenv('LOGPROF_DEBUG', False): + DEBUGGING = True + logprof_debug = '/var/log/apparmor/logprof.log' + logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) + debug_logger = logging.getLogger('YaST') + +def setup_yast(): + # To-Do + pass + +def shutdown_yast(): + # To-Do + pass def yastLog(text): ycp.y2milestone(text) @@ -17,7 +38,7 @@ def SendDataToYast(data): else: if DEBUGGING: debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) - fatal_error('SendDataToYast: didn\'t receive YCP command before connection died') + error('SendDataToYast: didn\'t receive YCP command before connection died') def GetDataFromYast(): if DEBUGGING: @@ -34,7 +55,7 @@ def GetDataFromYast(): else: if DEBUGGING: debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) - fatal_error('GetDataFromYast: didn\'t receive YCP command before connection died') + error('GetDataFromYast: didn\'t receive YCP command before connection died') def ParseCommand(commands): term = ParseTerm(commands) From af034537fc5162b4d3e98964a0a9208ddd43e1a8 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 18 Jul 2013 19:17:43 +0530 Subject: [PATCH 021/183] A new version of the variable loader for severity --- Testing/severity_test.py | 29 +++++++++++---------- apparmor/config.py | 2 +- apparmor/severity.py | 56 +++++++++++++++++++++++----------------- 3 files changed, 48 insertions(+), 39 deletions(-) diff --git a/Testing/severity_test.py b/Testing/severity_test.py index aa923f1de..7480093c3 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -10,18 +10,7 @@ sys.path.append('../') import apparmor.severity as severity from apparmor.common import AppArmorException -class Test(unittest.TestCase): - - def testInvalid(self): - s = severity.Severity('severity.db') - rank = s.rank('/dev/doublehit', 'i') - self.assertEqual(rank, 10, 'Wrong') - try: - broken = severity.Severity('severity_broken.db') - except AppArmorException: - pass - rank = s.rank('CAP_UNKOWN') - rank = s.rank('CAP_K*') +class Test(unittest.TestCase): def testRank_Test(self): z = severity.Severity() @@ -46,14 +35,26 @@ class Test(unittest.TestCase): self.assertEqual(rank, 9, 'Wrong rank') self.assertEqual(s.rank('/etc/apparmor/**', 'r') , 6, 'Invalid Rank') self.assertEqual(s.rank('/etc/**', 'r') , 10, 'Invalid Rank') + + # Load all variables for /sbin/klogd and test them + s.load_variables('/etc/apparmor.d/sbin.klogd') self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') - self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') + #self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') #self.assertEqual(s.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') - + def testInvalid(self): + s = severity.Severity('severity.db') + rank = s.rank('/dev/doublehit', 'i') + self.assertEqual(rank, 10, 'Wrong') + try: + broken = severity.Severity('severity_broken.db') + except AppArmorException: + pass + rank = s.rank('CAP_UNKOWN') + rank = s.rank('CAP_K*') diff --git a/apparmor/config.py b/apparmor/config.py index f89630481..be906a598 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -252,7 +252,7 @@ class Config: f_out.write(line) def py2_parser(filename): - "Returns the de-dented ini file from the new format ini" + """Returns the de-dented ini file from the new format ini""" tmp = tempfile.NamedTemporaryFile('rw') f_out = open(tmp.name, 'w') if os.path.exists(filename): diff --git a/apparmor/severity.py b/apparmor/severity.py index 7b1d0b800..4c501f37b 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -6,6 +6,7 @@ from apparmor.common import AppArmorException, error, debug, open_file_read, war class Severity: def __init__(self, dbname=None, default_rank=10): """Initialises the class object""" + self.prof_dir = '/etc/apparmor.d/' # The profile directory self.severity = dict() self.severity['DATABASENAME'] = dbname self.severity['CAPABILITIES'] = {} @@ -14,29 +15,6 @@ class Severity: self.severity['DEFAULT_RANK'] = default_rank # For variable expansions from /etc/apparmor.d self.severity['VARIABLES'] = dict() - # Recursively visits all the profiles to identify all the variable expansions and stores them need to use sourcehooks - for root, dirs, files in os.walk('/etc/apparmor.d'): - for file in files: - try: - with open_file_read(os.path.join(root, file)) as f: - for line in f: - line.strip() - # Expected format is @{Variable} = value1 value2 .. - if line.startswith('@') and '=' in line: - line = line.strip() - if '+=' in line: - line = line.split('+=') - try: - self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()] - except KeyError as e: - raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values" % line[0]) - else: - line = line.split('=') - if line[0] in self.severity['VARIABLES'].keys(): - raise AppArmorException("Variable %s was previously declared" % line[0]) - self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] - except IOError: - raise AppArmorException("unable to open file: %s" % file) if not dbname: return None try: @@ -202,4 +180,34 @@ class Severity: replacement = replacement[1:] if replacement[-1] == '/' and replacement[-2:] !='//' and trailing: replacement = replacement[:-1] - return resource.replace(variable, replacement) \ No newline at end of file + return resource.replace(variable, replacement) + + def load_variables(self, prof_path): + """Loads the variables for the given profile""" + if os.path.isfile(prof_path): + with open_file_read(prof_path) as f_in: + for line in f_in: + line = line.strip() + # If any includes, load variables from them fitst + if '#include' in line: + new_path = line.split('<')[1].rstrip('>').strip() + new_path = self.prof_dir + new_path + self.load_variables(new_path) + else: + # Expected format is @{Variable} = value1 value2 .. + if line.startswith('@') and '=' in line: + if '+=' in line: + line = line.split('+=') + try: + self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()] + except KeyError: + raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values" % line[0]) + else: + line = line.split('=') + if line[0] in self.severity['VARIABLES'].keys(): + raise AppArmorException("Variable %s was previously declared" % line[0]) + self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] + + def unload_variables(self): + """Clears all loaded variables""" + self.severity['VARIABLES'] = dict() From 211b40419547de135f955e30969607dcfe8cd422 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 19 Jul 2013 00:44:55 +0530 Subject: [PATCH 022/183] Fixed configparser and added unit test for the same tried on python2 and python3 --- Testing/config_test.py | 42 ++++++++++++++++++++++++++++++++++++++++ Testing/severity_test.py | 6 +----- apparmor/config.py | 32 +++++++++++++++++++++--------- apparmor/severity.py | 6 +++--- 4 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 Testing/config_test.py diff --git a/Testing/config_test.py b/Testing/config_test.py new file mode 100644 index 000000000..1f3755a41 --- /dev/null +++ b/Testing/config_test.py @@ -0,0 +1,42 @@ +''' +Created on Jul 18, 2013 + +@author: kshitij +''' +import unittest +import sys + +sys.path.append('../') +import apparmor.config as config + +class Test(unittest.TestCase): + + + def test_IniConfig(self): + ini_config = config.Config('ini') + conf = ini_config.read_config('logprof.conf') + logprof_sections = ['settings', 'repository', 'qualifiers', 'required_hats', 'defaulthat', 'globs'] + logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes'] + logprof_settings_parser = '/sbin/apparmor_parser /sbin/subdomain_parser' + + self.assertEqual(conf.sections(), logprof_sections) + self.assertEqual(conf.options('settings'), logprof_sections_options) + self.assertEqual(conf['settings']['parser'], logprof_settings_parser) + + def test_ShellConfig(self): + ini_config = config.Config('shell') + conf = ini_config.read_config('easyprof.conf') + easyprof_sections = ['POLICYGROUPS_DIR', 'TEMPLATES_DIR'] + easyprof_Policygroup = '/usr/share/apparmor/easyprof/policygroups' + easyprof_Templates = '/usr/share/apparmor/easyprof/templates' + + self.assertEqual(list(conf[''].keys()), easyprof_sections) + self.assertEqual(conf['']['POLICYGROUPS_DIR'], easyprof_Policygroup) + self.assertEqual(conf['']['TEMPLATES_DIR'], easyprof_Templates) + + + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testConfig'] + unittest.main() \ No newline at end of file diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 7480093c3..6f418516d 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -1,8 +1,3 @@ -''' -Created on Jun 21, 2013 - -@author: kshitij -''' import sys import unittest @@ -10,6 +5,7 @@ sys.path.append('../') import apparmor.severity as severity from apparmor.common import AppArmorException + class Test(unittest.TestCase): def testRank_Test(self): diff --git a/apparmor/config.py b/apparmor/config.py index be906a598..17cc15082 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -7,18 +7,28 @@ import sys import tempfile if sys.version_info < (3,0): import ConfigParser as configparser + # Class to provide the object[section][option] behavior in Python2 + class configparser_py2(configparser.ConfigParser): + def __getitem__(self, section): + section_val = self.items(section) + section_options = dict() + for option, value in section_val: + section_options[option] = value + return section_options + else: import configparser from apparmor.common import AppArmorException, warn, msg, open_file_read -CONF_DIR = '/etc/apparmor' -CFG = None -REPO_CFG = None -SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] + +# CFG = None +# REPO_CFG = None +# SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] class Config: def __init__(self, conf_type): + self.CONF_DIR = '/etc/apparmor' # The type of config file that'll be read and/or written if conf_type == 'shell' or conf_type == 'ini': self.conf_type = conf_type @@ -37,7 +47,7 @@ class Config: """Reads the file and returns a config[section][attribute]=property object""" # LP: Bug #692406 # Explicitly disabled repository - filepath = CONF_DIR + '/' + filename + filepath = self.CONF_DIR + '/' + filename self.input_file = filepath if filename == "repository.conf": config = dict() @@ -45,7 +55,10 @@ class Config: elif self.conf_type == 'shell': config = self.read_shell(filepath) elif self.conf_type == 'ini': - config = configparser.ConfigParser() + if sys.version_info > (3,0): + config = configparser.ConfigParser() + else: + config = configparser_py2() # Set the option form to string -prevents forced conversion to lowercase config.optionxform = str if sys.version_info > (3,0): @@ -55,16 +68,17 @@ class Config: config.read(filepath) except configparser.ParsingError: tmp_filepath = py2_parser(filepath) - config.read(tmp_filepath.name) + config.read(tmp_filepath.name) + ##config.__get__() return config def write_config(self, filename, config): """Writes the given config to the specified file""" - filepath = CONF_DIR + '/' + filename + filepath = self.CONF_DIR + '/' + filename permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write try: # Open a temporary file in the CONF_DIR to write the config file - config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=CONF_DIR) + config_file = tempfile.NamedTemporaryFile('w', prefix='aa_temp', delete=False, dir=self.CONF_DIR) if os.path.exists(self.input_file): # Copy permissions from an existing file to temporary file shutil.copymode(self.input_file, config_file.name) diff --git a/apparmor/severity.py b/apparmor/severity.py index 4c501f37b..dd02bace1 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -6,7 +6,7 @@ from apparmor.common import AppArmorException, error, debug, open_file_read, war class Severity: def __init__(self, dbname=None, default_rank=10): """Initialises the class object""" - self.prof_dir = '/etc/apparmor.d/' # The profile directory + self.PROF_DIR = '/etc/apparmor.d' # The profile directory self.severity = dict() self.severity['DATABASENAME'] = dbname self.severity['CAPABILITIES'] = {} @@ -56,7 +56,7 @@ class Severity: except ValueError as e: error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) error(error_message) - raise AppArmorException(error_message) from None + raise AppArmorException(error_message) # from None else: if severity not in range(0,11): raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) @@ -191,7 +191,7 @@ class Severity: # If any includes, load variables from them fitst if '#include' in line: new_path = line.split('<')[1].rstrip('>').strip() - new_path = self.prof_dir + new_path + new_path = self.PROF_DIR + '/' + new_path self.load_variables(new_path) else: # Expected format is @{Variable} = value1 value2 .. From e727c62e76c6046b031ebc421712fc149e8aeb4f Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 20 Jul 2013 04:19:07 +0530 Subject: [PATCH 023/183] Fixes from review 19-22 and updated codebase --- Testing/config_test.py | 8 +- Testing/easyprof.conf | 5 + Testing/logprof.conf | 131 ++++++++++++++++++++++ Testing/sbin.klogd | 35 ++++++ Testing/severity_test.py | 12 +- Testing/usr.sbin.dnsmasq | 60 ++++++++++ apparmor/aa.py | 231 +++++++++++++++++++++++++++++++++------ apparmor/severity.py | 12 +- 8 files changed, 454 insertions(+), 40 deletions(-) create mode 100644 Testing/easyprof.conf create mode 100644 Testing/logprof.conf create mode 100644 Testing/sbin.klogd create mode 100644 Testing/usr.sbin.dnsmasq diff --git a/Testing/config_test.py b/Testing/config_test.py index 1f3755a41..64dbd9bca 100644 --- a/Testing/config_test.py +++ b/Testing/config_test.py @@ -14,6 +14,7 @@ class Test(unittest.TestCase): def test_IniConfig(self): ini_config = config.Config('ini') + ini_config.CONF_DIR = '.' conf = ini_config.read_config('logprof.conf') logprof_sections = ['settings', 'repository', 'qualifiers', 'required_hats', 'defaulthat', 'globs'] logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes'] @@ -24,13 +25,14 @@ class Test(unittest.TestCase): self.assertEqual(conf['settings']['parser'], logprof_settings_parser) def test_ShellConfig(self): - ini_config = config.Config('shell') - conf = ini_config.read_config('easyprof.conf') + shell_config = config.Config('shell') + shell_config.CONF_DIR = '.' + conf = shell_config.read_config('easyprof.conf') easyprof_sections = ['POLICYGROUPS_DIR', 'TEMPLATES_DIR'] easyprof_Policygroup = '/usr/share/apparmor/easyprof/policygroups' easyprof_Templates = '/usr/share/apparmor/easyprof/templates' - self.assertEqual(list(conf[''].keys()), easyprof_sections) + self.assertEqual(sorted(list(conf[''].keys())), sorted(easyprof_sections)) self.assertEqual(conf['']['POLICYGROUPS_DIR'], easyprof_Policygroup) self.assertEqual(conf['']['TEMPLATES_DIR'], easyprof_Templates) diff --git a/Testing/easyprof.conf b/Testing/easyprof.conf new file mode 100644 index 000000000..fdd9cc837 --- /dev/null +++ b/Testing/easyprof.conf @@ -0,0 +1,5 @@ +# Location of system policygroups +POLICYGROUPS_DIR="/usr/share/apparmor/easyprof/policygroups" + +# Location of system templates +TEMPLATES_DIR="/usr/share/apparmor/easyprof/templates" diff --git a/Testing/logprof.conf b/Testing/logprof.conf new file mode 100644 index 000000000..e073eb70a --- /dev/null +++ b/Testing/logprof.conf @@ -0,0 +1,131 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2004-2006 Novell/SUSE +# +# 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. +# +# ------------------------------------------------------------------ + +[settings] + profiledir = /etc/apparmor.d /etc/subdomain.d + inactive_profiledir = /usr/share/doc/apparmor-profiles/extras + logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages + + parser = /sbin/apparmor_parser /sbin/subdomain_parser + ldd = /usr/bin/ldd + logger = /bin/logger /usr/bin/logger + + # customize how file ownership permissions are presented + # 0 - off + # 1 - default of what ever mode the log reported + # 2 - force the new permissions to be user + # 3 - force all perms on the rule to be user + default_owner_prompt = 1 + + # custom directory locations to look for #includes + # + # each name should be a valid directory containing possible #include + # candidate files under the profile dir which by default is /etc/apparmor.d. + # + # So an entry of my-includes will allow /etc/apparmor.d/my-includes to + # be used by the yast UI and profiling tools as a source of #include + # files. + custom_includes = + + +[repository] + distro = ubuntu-intrepid + url = http://apparmor.test.opensuse.org/backend/api + preferred_user = ubuntu + +[qualifiers] + # things will be painfully broken if bash has a profile + /bin/bash = icnu + /bin/ksh = icnu + /bin/dash = icnu + + # these programs can't function if they're confined + /bin/mount = u + /etc/init.d/subdomain = u + /sbin/cardmgr = u + /sbin/subdomain_parser = u + /usr/sbin/genprof = u + /usr/sbin/logprof = u + /usr/lib/YaST2/servers_non_y2/ag_genprof = u + /usr/lib/YaST2/servers_non_y2/ag_logprof = u + + # these ones shouln't have their own profiles + /bin/awk = icn + /bin/cat = icn + /bin/chmod = icn + /bin/chown = icn + /bin/cp = icn + /bin/gawk = icn + /bin/grep = icn + /bin/gunzip = icn + /bin/gzip = icn + /bin/kill = icn + /bin/ln = icn + /bin/ls = icn + /bin/mkdir = icn + /bin/mv = icn + /bin/readlink = icn + /bin/rm = icn + /bin/sed = icn + /bin/touch = icn + /sbin/killall5 = icn + /usr/bin/find = icn + /usr/bin/killall = icn + /usr/bin/nice = icn + /usr/bin/perl = icn + /usr/bin/tr = icn + +[required_hats] + ^.+/apache(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT + ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI HANDLING_UNTRUSTED_INPUT + +[defaulthat] + ^.+/apache(|2|2-prefork)$ = DEFAULT_URI + ^.+/httpd(|2|2-prefork)$ = DEFAULT_URI + +[globs] + # /foo/bar/lib/libbaz.so -> /foo/bar/lib/lib* + /lib/lib[^\/]+so[^\/]*$ = /lib/lib*so* + + # strip kernel version numbers from kernel module accesses + ^/lib/modules/[^\/]+\/ = /lib/modules/*/ + + # strip pid numbers from /proc accesses + ^/proc/\d+/ = /proc/*/ + + # if it looks like a home directory, glob out the username + ^/home/[^\/]+ = /home/* + + # if they use any perl modules, grant access to all + ^/usr/lib/perl5/.+$ = /usr/lib/perl5/** + + # locale foo + ^/usr/lib/locale/.+$ = /usr/lib/locale/** + ^/usr/share/locale/.+$ = /usr/share/locale/** + + # timezone fun + ^/usr/share/zoneinfo/.+$ = /usr/share/zoneinfo/** + + # /foobar/fonts/baz -> /foobar/fonts/** + /fonts/.+$ = /fonts/** + + # turn /foo/bar/baz.8907234 into /foo/bar/baz.* + # BUGBUG - this one looked weird because it would suggest a glob for + # BUGBUG - libfoo.so.5.6.0 that looks like libfoo.so.5.6.* + # \.\d+$ = .* + + # some various /etc/security poo -- dunno about these ones... + ^/etc/security/_[^\/]+$ = /etc/security/* + ^/lib/security/pam_filter/[^\/]+$ = /lib/security/pam_filter/* + ^/lib/security/pam_[^\/]+\.so$ = /lib/security/pam_*.so + + ^/etc/pam.d/[^\/]+$ = /etc/pam.d/* + ^/etc/profile.d/[^\/]+\.sh$ = /etc/profile.d/*.sh + diff --git a/Testing/sbin.klogd b/Testing/sbin.klogd new file mode 100644 index 000000000..2563b9be8 --- /dev/null +++ b/Testing/sbin.klogd @@ -0,0 +1,35 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2002-2009 Novell/SUSE +# Copyright (C) 2010 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. +# +# ------------------------------------------------------------------ + +#include + +/sbin/klogd { + #include + + capability sys_admin, # for backward compatibility with kernel <= 2.6.37 + capability syslog, + + network inet stream, + + /boot/System.map* r, + @{PROC}/kmsg r, + @{PROC}/kallsyms r, + /dev/tty rw, + + /sbin/klogd rmix, + /var/log/boot.msg rwl, + /{,var/}run/klogd.pid krwl, + /{,var/}run/klogd/klogd.pid krwl, + /{,var/}run/klogd/kmsg r, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 6f418516d..4d2701d1c 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -10,6 +10,7 @@ class Test(unittest.TestCase): def testRank_Test(self): z = severity.Severity() + s = severity.Severity('severity.db') rank = s.rank('/usr/bin/whatis', 'x') self.assertEqual(rank, 5, 'Wrong rank') @@ -33,12 +34,19 @@ class Test(unittest.TestCase): self.assertEqual(s.rank('/etc/**', 'r') , 10, 'Invalid Rank') # Load all variables for /sbin/klogd and test them - s.load_variables('/etc/apparmor.d/sbin.klogd') + s.load_variables('sbin.klogd') + self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') + self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') + self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') + + s.unload_variables() + + s.load_variables('usr.sbin.dnsmasq') + self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') - #self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') #self.assertEqual(s.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') def testInvalid(self): diff --git a/Testing/usr.sbin.dnsmasq b/Testing/usr.sbin.dnsmasq new file mode 100644 index 000000000..d3d9615b9 --- /dev/null +++ b/Testing/usr.sbin.dnsmasq @@ -0,0 +1,60 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2009 John Dong +# Copyright (C) 2010 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. +# +# ------------------------------------------------------------------ + +@{TFTP_DIR}=/var/tftp /srv/tftpboot #comment + +#include # comment +/usr/sbin/dnsmasq { + #include + #include + + capability net_bind_service, + capability setgid, + capability setuid, + capability dac_override, + capability net_admin, # for DHCP server + capability net_raw, # for DHCP server ping checks + network inet raw, + + /etc/dnsmasq.conf r, + /etc/dnsmasq.d/ r, + /etc/dnsmasq.d/* r, + /etc/ethers r, + + /usr/sbin/dnsmasq mr, + + /{,var/}run/*dnsmasq*.pid w, + /{,var/}run/dnsmasq-forwarders.conf r, + /{,var/}run/dnsmasq/ r, + /{,var/}run/dnsmasq/* rw, + + /var/lib/misc/dnsmasq.leases rw, # Required only for DHCP server usage + + # for the read-only TFTP server + @{TFTP_DIR}/ r, + @{TFTP_DIR}/** r, + + # libvirt lease and hosts files for dnsmasq + /var/lib/libvirt/dnsmasq/ r, + /var/lib/libvirt/dnsmasq/*.leases rw, + /var/lib/libvirt/dnsmasq/*.hostsfile r, + + # libvirt pid files for dnsmasq + /{,var/}run/libvirt/network/ r, + /{,var/}run/libvirt/network/*.pid rw, + + # NetworkManager integration + /{,var/}run/nm-dns-dnsmasq.conf r, + /{,var/}run/sendsigs.omit.d/*dnsmasq.pid w, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/apparmor/aa.py b/apparmor/aa.py index 2f7a52a8c..704962bb4 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,4 @@ -#1321 + #382-430 #480-525 #global variable names corruption @@ -434,40 +434,30 @@ def create_new_profile(localfile): hashbang = head(localfile) if hashbang.startswith('#!'): interpreter = get_full_path(hashbang.lstrip('#!').strip()) - try: - local_profile[localfile]['allow']['path'][localfile]['mode'] |= str_to_mode('r') - except TypeError: - local_profile[localfile]['allow']['path'][localfile]['mode'] = str_to_mode('r') - try: - local_profile[localfile]['allow']['path'][localfile]['audit'] |= 0 - except TypeError: - local_profile[localfile]['allow']['path'][localfile]['audit'] = 0 - try: - local_profile[localfile]['allow']['path'][interpreter]['mode'] |= str_to_mode('ix') - except TypeError: - local_profile[localfile]['allow']['path'][interpreter]['mode'] = str_to_mode('ix') - try: - local_profile[localfile]['allow']['path'][interpreter]['audit'] |= 0 - except TypeError: - local_profile[localfile]['allow']['path'][interpreter]['audit'] = 0 + + local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r') + + local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', 0) + + local_profile[localfile]['allow']['path'][interpreter]['mode'] = local_profile[localfile]['allow']['path'][interpreter].get('mode', str_to_mode('ix')) | str_to_mode('ix') + + local_profile[localfile]['allow']['path'][interpreter]['audit'] = local_profile[localfile]['allow']['path'][interpreter].get('audit', 0) + if 'perl' in interpreter: - local_profile[localfile]['include']['abstractions/perl'] = 1 + local_profile[localfile]['include']['abstractions/perl'] = True elif 'python' in interpreter: - local_profile[localfile]['include']['abstractions/python'] = 1 + local_profile[localfile]['include']['abstractions/python'] = True elif 'ruby' in interpreter: - local_profile[localfile]['include']['abstractions/ruby'] = 1 - elif '/bin/bash' in interpreter or '/bin/dash' in interpreter or '/bin/sh' in interpreter: - local_profile[localfile]['include']['abstractions/ruby'] = 1 + local_profile[localfile]['include']['abstractions/ruby'] = True + elif interpreter in ['/bin/bash', '/bin/dash', '/bin/sh']: + local_profile[localfile]['include']['abstractions/bash'] = True handle_binfmt(local_profile[localfile], interpreter) else: - try: - local_profile[localfile]['allow']['path'][localfile]['mode'] |= str_to_mode('mr') - except TypeError: - local_profile[localfile]['allow']['path'][localfile]['mode'] = str_to_mode('mr') - try: - local_profile[localfile]['allow']['path'][localfile]['audit'] |= 0 - except TypeError: - local_profile[localfile]['allow']['path'][localfile] = 0 + + local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('mr')) | str_to_mode('mr') + + local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', 0) + handle_binfmt(local_profile[localfile], localfile) # Add required hats to the profile if they match the localfile for hatglob in cfg['required_hats'].keys(): @@ -487,6 +477,7 @@ def delete_profile(local_prof): os.remove(profile_file) if aa.get(local_prof, False): aa.pop(local_prof) + prof_unload(local_prof) def get_profile(prof_name): @@ -894,5 +885,183 @@ def build_x_functions(default, options, exec_toggle): def handle_children(profile, hat, root): entries = root[:] + regex_nullcomplain = re.compile('null(-complain)*-profile') for entry in entries: - \ No newline at end of file + if type(entry[0]) != str: + handle_children(profile, hat, entry) + else: + typ = entry.pop(0) + if typ == 'fork': + pid, p, h = entry[:3] + if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): + profile = p + hat = h + if hat: + profile_changes[pid] = profile + '//' + hat + else: + profile_changes[pid] = profile + elif type == 'unknown_hat': + pid, p, h, aamode, uhat = entry[:5] + if not regex_nullcomplain.search(p): + profile = p + if aa[profile].get(uhat, False): + hat = uhat + continue + new_p = update_repo_profile(aa[profile][profile]) + if new_p and UI_SelectUpdatedRepoProfile(profile, new_p) and aa[profile].get(uhat, False): + hat = uhat + continue + + default_hat = None + for hatglob in cfg.options('defaulthat'): + if re.search(hatglob, profile): + default_hat = cfg['defaulthat'][hatglob] + + context = profile + context = context + ' -> ^%s' % uhat + ans = transitions.get(context, 'XXXINVALIDXXX') + + while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']: + q = hasher() + q['headers'] = [] + q['headers'] += [gettext('Profile'), profile] + + if default_hat: + q['headers'] += [gettext('Default Hat'), default_hat] + + q['headers'] += [gettext('Requested Hat'), uhat] + + q['functions'] = [] + q['functions'].append('CMD_ADDHAT') + if default_hat: + q['functions'].append('CMD_USEDEFAULT') + q['functions'] += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] + + q['default'] = 'CMD_DENY' + if aamode == 'PERMITTING': + q['default'] = 'CMD_ADDHAT' + + seen_events += 1 + + ans = UI_PromptUser(q) + + transitions[context] = ans + + if ans == 'CMD_ADDHAT': + hat = uhat + aa[profile][hat]['flags'] = aa[profile][profile]['flags'] + elif ans == 'CMD_USEDEFAULT': + hat = default_hat + elif ans == 'CMD_DENY': + return None + + elif type == 'capability': + pid, p, h, prog, aamode, capability = entry[:6] + if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): + profile = p + hat = h + if not profile or not hat: + continue + prelog[aamode][profile][hat]['capability'][capability] = True + + elif type == 'path' or type == 'exec': + pid, p, h, prog, aamode, mode, detail, to_name = entry[:8] + + if not mode: + mode = 0 + if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): + profile = p + hat = h + if not profile or not hat or not detail: + continue + + domainchange = 'nochange' + if type == 'exec': + domainchange = 'change' + + # Escape special characters + detail = detail.replace('[', '\[') + detail = detail.replace(']', '\]') + detail = detail.replace('+', '\+') + detail = detail.replace('*', '\*') + detail = detail.replace('{', '\{') + detail = detail.replace('}', '\}') + + # Give Execute dialog if x access requested for something that's not a directory + # For directories force an 'ix' Path dialog + do_execute = False + exec_target = detail + + if mode & str_to_mode('x'): + if os.path.isdir(exec_target): + mode = mode & (~ALL_AA_EXEC_TYPE) + mode = mode | str_to_mode('ix') + else: + do_execute = True + + if mode & AA_MAY_LINK: + regex_link = re.compile('^from (.+) to (.+)$') + match = regex_link.search(detail) + if match: + path = match.groups()[0] + target = match.groups()[1] + + frommode = str_to_mode('lr') + if prelog[aamode][profile][hat]['path'].get(path, False): + frommode |= prelog[aamode][profile][hat]['path'][path] + prelog[aamode][profile][hat]['path'][path] = frommode + + tomode = str_to_mode('lr') + if prelog[aamode][profile][hat]['path'].get(target, False): + tomode |= prelog[aamode][profile][hat]['path'][target] + prelog[aamode][profile][hat]['path'][target] = tomode + else: + continue + elif mode: + path = detail + + if prelog[aamode][profile][hat]['path'].get(path, False): + mode |= prelog[aamode][profile][hat]['path'][path] + prelog[aamode][profile][hat]['path'][path] = mode + + if do_execute: + if profile_known_exec(aa[profile][hat], 'exec', exec_target): + continue + + p = update_repo_profile(aa[profile][profile]) + if to_name: + if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', to_name): + continue + else: + if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', exec_target): + continue + + context_new = profile + if profile != hat: + context_new = context_new + '^%s' % hat + context_new = context + ' ->%s' % exec_target + + ans_new = transitions.get(context_new, '') + combinedmode = False + combinedaudit = False + # Check Return Value Consistency + # Check if path matches any existing regexps in profile + cm, am , m = rematchfrag(aa[profile][hat], 'allow', exec_target) + if cm: + combinedmode |= cm + if am: + combinedaudit |= am + + if combinedmode & str_to_mode('x'): + nt_name = None + for entr in m: + if aa[profile][hat]['allow']['path'].get(entr, False): + nt_name = aa[profile][hat] + break + if toname and to_name != nt_name: + pass + elif nt_name: + to_name = nt_name + + + \ No newline at end of file diff --git a/apparmor/severity.py b/apparmor/severity.py index dd02bace1..8375937bc 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -55,7 +55,7 @@ class Severity: severity = int(severity) except ValueError as e: error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) - error(error_message) + #error(error_message) raise AppArmorException(error_message) # from None else: if severity not in range(0,11): @@ -184,16 +184,20 @@ class Severity: def load_variables(self, prof_path): """Loads the variables for the given profile""" + regex_include = re.compile('include <(\S*)>') if os.path.isfile(prof_path): with open_file_read(prof_path) as f_in: for line in f_in: line = line.strip() - # If any includes, load variables from them fitst - if '#include' in line: - new_path = line.split('<')[1].rstrip('>').strip() + # If any includes, load variables from them first + match = regex_include.search(line) + if match: + new_path = match.groups()[0] new_path = self.PROF_DIR + '/' + new_path self.load_variables(new_path) else: + if '#' in line: + line = line.split('#')[0].rstrip() # Expected format is @{Variable} = value1 value2 .. if line.startswith('@') and '=' in line: if '+=' in line: From d97f0c6b7d72d5b172f54db7ef4b51927639d4ce Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 23 Jul 2013 04:35:51 +0530 Subject: [PATCH 024/183] Code-base update --- Testing/usr.sbin.dnsmasq | 4 +- apparmor/aa.py | 422 ++++++++++++++++++++++++++++++++++++++- apparmor/config.py | 2 + apparmor/severity.py | 5 +- 4 files changed, 420 insertions(+), 13 deletions(-) diff --git a/Testing/usr.sbin.dnsmasq b/Testing/usr.sbin.dnsmasq index d3d9615b9..9fc3ed1ef 100644 --- a/Testing/usr.sbin.dnsmasq +++ b/Testing/usr.sbin.dnsmasq @@ -11,9 +11,9 @@ @{TFTP_DIR}=/var/tftp /srv/tftpboot #comment -#include # comment +#include # comment /usr/sbin/dnsmasq { - #include + #include #include capability net_bind_service, diff --git a/apparmor/aa.py b/apparmor/aa.py index 704962bb4..4b4f30b79 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,6 +1,7 @@ - +#2778 #382-430 #480-525 +# No old version logs, only 2.6 + supported #global variable names corruption from __future__ import with_statement import inspect @@ -177,8 +178,8 @@ def check_for_LD_XXX(file): if not os.path.isfile(file): return False size = os.stat(file).st_size - # Limit to checking files under 10k for the sake of speed - if size >10000: + # Limit to checking files under 100k for the sake of speed + if size >100000: return False with open_file_read(file) as f_in: for line in f_in: @@ -449,7 +450,7 @@ def create_new_profile(localfile): local_profile[localfile]['include']['abstractions/python'] = True elif 'ruby' in interpreter: local_profile[localfile]['include']['abstractions/ruby'] = True - elif interpreter in ['/bin/bash', '/bin/dash', '/bin/sh']: + elif re.search('/bin/(bash|dash|sh)', interpreter): local_profile[localfile]['include']['abstractions/bash'] = True handle_binfmt(local_profile[localfile], interpreter) else: @@ -612,7 +613,7 @@ def set_profile_flags(prof_filename, newflags): regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*(#*\S*)$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: - tempfile = tempfile.NamedTemporaryFile('w', prefix=prof_filename , delete=False, dir='/etc/apparmor.d/') + tempfile = tempfile.NamedTemporaryFile('w', prefix=prof_filename , suffix='~', delete=False, dir='/etc/apparmor.d/') shutil.copymode('/etc/apparmor.d/' + prof_filename, tempfile.name) with open_file_write(tempfile.name) as f_out: for line in f_in: @@ -885,7 +886,21 @@ def build_x_functions(default, options, exec_toggle): def handle_children(profile, hat, root): entries = root[:] - regex_nullcomplain = re.compile('null(-complain)*-profile') + pid = None + p = None + h = None + prog = None + aamode = None + mode = None + detail = None + to_name = None + uhat = None + capability = None + family = None + sock_type = None + protocol = None + regex_nullcomplain = re.compile('^null(-complain)*-profile$') + for entry in entries: if type(entry[0]) != str: handle_children(profile, hat, entry) @@ -1044,7 +1059,7 @@ def handle_children(profile, hat, root): ans_new = transitions.get(context_new, '') combinedmode = False combinedaudit = False - # Check Return Value Consistency + ## Check return Value Consistency # Check if path matches any existing regexps in profile cm, am , m = rematchfrag(aa[profile][hat], 'allow', exec_target) if cm: @@ -1058,10 +1073,399 @@ def handle_children(profile, hat, root): if aa[profile][hat]['allow']['path'].get(entr, False): nt_name = aa[profile][hat] break - if toname and to_name != nt_name: + if to_name and to_name != nt_name: + pass + elif nt_name: + to_name = nt_name + ## Check return value consistency + # Check if the includes from profile match + cm, am, m = match_prof_incs_to_path(aa[profile][hat], 'allow', exec_target) + if cm: + combinedmode |= cm + if am: + combinedaudit |= am + if combinedmode & str_to_mode('x'): + nt_name = None + for entr in m: + if aa[profile][hat]['allow']['path'][entry]['to']: + int_name = aa[profile][hat]['allow']['path'][entry]['to'] + break + if to_name and to_name != nt_name: pass elif nt_name: to_name = nt_name + # nx is not used in profiles but in log files. + # Log parsing methods will convert it to its profile form + # nx is internally cx/px/cix/pix + to_name + exec_mode = False + if contains(combinedmode, 'pix'): + if to_name: + ans = 'CMD_nix' + else: + ans = 'CMD_pix' + exec_mode = str_to_mode('pixr') + elif contains(combinedmode, 'cix'): + if to_name: + ans = 'CMD_nix' + else: + ans = 'CMD_cix' + exec_mode = str_to_mode('cixr') + elif contains(combinedmode, 'Pix'): + if to_name: + ans = 'CMD_nix_safe' + else: + ans = 'CMD_pix_safe' + exec_mode = str_to_mode('Pixr') + elif contains(combinedmode, 'Cix'): + if to_name: + ans = 'CMD_nix_safe' + else: + ans = 'CMD_cix_safe' + exec_mode = str_to_mode('Cixr') + elif contains(combinedmode, 'ix'): + ans = 'CMD_ix' + exec_mode = str_to_mode('ixr') + elif contains(combinedmode, 'px'): + if to_name: + ans = 'CMD_nx' + else: + ans = 'CMD_px' + exec_mode = str_to_mode('px') + elif contains(combinedmode, 'cx'): + if to_name: + ans = 'CMD_nx' + else: + ans = 'CMD_cx' + exec_mode = str_to_mode('cx') + elif contains(combinedmode, 'ux'): + ans = 'CMD_ux' + exec_mode = str_to_mode('ux') + elif contains(combinedmode, 'Px'): + if to_name: + ans = 'CMD_nx_safe' + else: + ans = 'CMD_px_safe' + exec_mode = str_to_mode('Px') + elif contains(combinedmode, 'Cx'): + if to_name: + ans = 'CMD_nx_safe' + else: + ans = 'CMD_cx_safe' + exec_mode = str_to_mode('Cx') + elif contains(combinedmode, 'Ux'): + ans = 'CMD_ux_safe' + exec_mode = str_to_mode('Ux') + else: + options = cfg['qualifiers'].get(exec_target, 'ipcnu') + if to_name: + fatal_error('%s has transition name but not transition mode' % entry) + + # If profiled program executes itself only 'ix' option + if exec_target == profile: + options = 'i' + + # Don't allow hats to cx? + options.replace('c', '') + # Add deny to options + options += 'd' + # Define the default option + default = None + if 'p' in options and os.path.exists(get_profile_filename(exec_target)): + default = 'CMD_px' + elif 'i' in options: + default = 'CMD_ix' + elif 'c' in options: + default = 'CMD_cx' + elif 'n' in options: + default = 'CMD_nx' + else: + default = 'DENY' + + # + parent_uses_ld_xxx = check_for_LD_XXX(profile) + + sev_db.unload_variables() + sev_db.load_variables(profile) + severity = sev_db.rank(exec_target, 'x') + + # Prompt portion starts + q = hasher() + q['headers'] = [] + q['headers'] += [gettext('Profile'), combine_name(profile, hat)] + if prog and prog != 'HINT': + q['headers'] += [gettext('Program'), prog] + + # to_name should not exist here since, transitioning is already handeled + q['headers'] += [gettext('Execute'), exec_target] + q['headers'] += [gettext('Severity'), severity] + + q['functions'] = [] + prompt = '\n%s\n' % context + exec_toggle = False + q['functions'].append(build_x_functions(default, options, exec_toggle)) + + options = '|'.join(options) + seen_events += 1 + regex_options = re.compile('^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$') + + while regex_options.search(ans): + ans = UI_PromptUser(q).strip() + if ans.startswith('CMD_EXEC_IX_'): + exec_toggle = not exec_toggle + q['functions'] = [] + q['functions'].append(build_x_functions(default, options, exec_toggle)) + ans = '' + continue + if ans == 'CMD_nx' or ans == 'CMD_nix': + arg = exec_target + ynans = 'n' + if profile == hat: + ynans = UI_YesNo(gettext('Are you specifying a transition to a local profile?'), 'n') + if ynans == 'y': + if ans == 'CMD_nx': + ans = 'CMD_cx' + else: + ans = 'CMD_cix' + else: + if ans == 'CMD_nx': + ans = 'CMD_px' + else: + ans = 'CMD_pix' + + to_name = UI_GetString(gettext('Enter profile name to transition to: '), arg) + + regex_optmode = re.compile('CMD_(px|cx|nx|pix|cix|nix)') + if ans == 'CMD_ix': + exec_mode = str_to_mode('ix') + elif regex_optmode.search(ans): + match = regex_optmode.search(ans).groups()[0] + exec_mode = str_to_match(match) + px_default = 'n' + px_msg = gettext('Should AppArmor sanitise the environment when\n' + + 'switching profiles?\n\n' + + 'Sanitising environment is more secure,\n' + + 'but some applications depend on the presence\n' + + 'of LD_PRELOAD or LD_LIBRARY_PATH.') + if parent_uses_ld_xxx: + px_msg = gettext('Should AppArmor sanitise the environment when\n' + + 'switching profiles?\n\n' + + 'Sanitising environment is more secure,\n' + + 'but this application appears to be using LD_PRELOAD\n' + + 'or LD_LIBRARY_PATH and sanitising the environment\n' + + 'could cause functionality problems.') + + ynans = UI_YesNo(px_msg, px_default) + if ynans == 'y': + # Disable the unsafe mode + exec_mode &= ~(AA_EXEC_UNSAFE | (AA_EXEC_UNSAFE << AA_OTHER_SHIFT)) + elif ans == 'CMD_ux': + exec_mode = str_to_mode('ux') + ynans = UI_YesNo(gettext('Launching processes in an unconfined state is a very\n' + + 'dangerous operation and can cause serious security holes.\n\n' + + 'Are you absolutely certain you wish to remove all\n' + + 'AppArmor protection when executing :') + '%s ?' % exec_target, 'n') + if ynans == 'y': + ynans = UI_YesNo(gettext('Should AppArmor sanitise the environment when\n' + + 'running this program unconfined?\n\n' + + 'Not sanitising the environment when unconfining\n' + + 'a program opens up significant security holes\n' + + 'and should be avoided if at all possible.'), 'y') + if yans == 'y': + # Disable the unsafe mode + exec_mode &= ~(AA_EXEC_UNSAFE | (AA_EXEC_UNSAFE << AA_OTHER_SHIFT)) + else: + ans = 'INVALID' + transitions[context] = ans + + regex_options = re.compile('CMD_(ix|px|cx|nx|pix|cix|nix)') + if regex_options.search(ans): + # For inherit we need r + if exec_mode & str_to_mode('i'): + exec_mode |= str_to_mode('r') + else: + if ans == 'CMD_DENY': + aa[profile][hat]['deny']['path'][exec_target]['mode'] = aa[profile][hat]['deny']['path'][exec_target].get('mode', str_to_mode('x')) | str_to_mode('x') + aa[profile][hat]['deny']['path'][exec_target]['audit'] = aa[profile][hat]['deny']['path'][exec_target].get('audit', 0) + changed[profile] = True + # Skip remaining events if they ask to deny exec + if domainchange == 'change': + return None + + if ans != 'CMD_DENY': + prelog['PERMITTING'][profile][hat]['path'][exec_target] = prelog['PERMITTING'][profile][hat]['path'].get(exec_target, exec_mode) | exec_mode + + log['PERMITTING'][profile] = hasher() + + aa[profile][hat]['allow']['path'][exec_target]['mode'] = aa[profile][hat]['allow']['path'][exec_target].get('mode', exec_mode) + + aa[profile][hat]['allow']['path'][exec_target]['audit'] = aa[profile][hat]['allow']['path'][exec_target].get('audit', 0) + + if to_name: + aa[profile][hat]['allow']['path'][exec_target]['to'] = to_name + + changed[profile] = True + + if exec_mode & str_to_mode('i'): + if 'perl' in exec_target: + aa[profile][hat]['include']['abstractions/perl'] = True + elif '/bin/bash' in exec_path or '/bin/sh' in exec_path: + aa[profile][hat]['include']['abstractions/bash'] = True + hashbang = head(exec_target) + if hashbang.startswith('#!'): + interpreter = hashbang[2:].strip() + interpreter = get_full_path(interpreter) + + aa[profile][hat]['path'][interpreter]['mode'] = aa[profile][hat]['path'][interpreter].get('mode', str_to_mode('ix')) | str_to_mode('ix') + + aa[profile][hat]['path'][interpreter]['audit'] = aa[profile][hat]['path'][interpreter].get('audit', 0) + + if 'perl' in interpreter: + aa[profile][hat]['include']['abstractions/perl'] = True + elif '/bin/bash' in interpreter or '/bin/sh' in interpreter: + aa[profile][hat]['include']['abstractions/bash'] = True - \ No newline at end of file + # Update tracking info based on kind of change + + if ans == 'CMD_ix': + if hat: + profile_changes[pid] = '%s//%s' %(profile, hat) + else: + profile_changes[pid] = '%s//' % profile + elif re.search('^CMD_(px|nx|pix|nix)', ans): + if to_name: + exec_target = to_name + if aamode == 'PERMITTING': + if domainchange == 'change': + profile = exec_target + hat = exec_target + profile_changes[pid] = '%s' % profile + + # Check profile exists for px + if not os.path.exists(get_profile_filename(exec_target)): + ynans = 'y' + if exec_mode & str_to_mode('i'): + ynans = UI_YesNo(gettext('A profile for ') + str(exec_target) + gettext(' doesnot exist.\nDo you want to create one?'), 'n') + if ynans == 'y': + helpers[exec_target] = 'enforce' + if to_name: + autodep_base('', exec_target) + else: + autodep_base(exec_target, '') + reload_base(exec_target) + elif ans.startswith('CMD_cx') or ans.startswith('CMD_cix'): + if to_name: + exec_target = to_name + if aamode == 'PERMITTING': + if domainchange == 'change': + profile_changes[pid] = '%s//%s' % (profile, exec_target) + + if not aa[profile].get(exec_target, False): + ynans = 'y' + if exec_mode & str_to_mode('i'): + ynans = UI_YesNo(gettext('A local profile for %s does not exit. Create one') % exec_target, 'n') + if ynans == 'y': + hat = exec_target + aa[profile][hat]['declared'] = False + aa[profile][hat]['profile'] = True + + if profile != hat: + aa[profile][hat]['flags'] = aa[profile][profile]['flags'] + + stub_profile = create_new_profile(hat) + + aa[profile][hat]['flags'] = 'complain' + + aa[profile][hat]['allow']['path'] = hasher() + if stub_profile[hat][hat]['allow'].get('path', False): + aa[profile][hat]['allow']['path'] = stub_profile[hat][hat]['allow']['path'] + + aa[profile][hat]['include'] = hasher() + if stub_profile[hat][hat].get('include', False): + aa[profile][hat]['include'] = stub_profile[hat][hat]['include'] + + aa[profile][hat]['allow']['netdomain'] = hasher() + + file_name = aa[profile][profile]['filename'] + filelist[file_name]['profiles'][profile][hat] = True + + elif ans.startswith('CMD_ux'): + profile_changes[pid] = 'unconfined' + if domainchange == 'change': + return None + + elif type == 'netdomain': + pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8] + + if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): + profile = p + hat = h + if not hat or not profile: + continue + if family and sock_type: + prelog[aamode][profile][hat]['netdomain'][family][sock_type] = True + + return None + +def add_to_tree(pid, parent, type, event): + if DEBUGGING: + debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (pid, type, event)) + + if not pid.get(pid, False): + profile, hat = event[:1] + if parent and pid.get(parent, False): + if not hat: + hat = 'null-complain-profile' + array_ref = ['fork', pid, profile, hat] + pid[parent].append(array_ref) + pid[pid] = array_ref + #else: + # array_ref = [] + # log.append(array_ref) + # pid[pid] = array_ref + pid[pid] += [type, pid, event] + +# Variables used by logparsing routines +LOG = None +next_log_entry = None +logmark = None +seenmark = None +#RE_LOG_v2_0_syslog = re.compile('SubDomain') +#RE_LOG_v2_1_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?(audit\([\d\.\:]+\):\s+)?type=150[1-6]') +RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=') +#RE_LOG_v2_0_audit = re.compile('type=(APPARMOR|UNKNOWN\[1500\]) msg=audit\([\d\.\:]+\):') +#RE_LOG_v2_1_audit = re.compile('type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))') +RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=') + +def prefetch_next_log_entry(): + if next_log_entry: + sys.stderr.out('A log entry already present: %s' % next_log_entry) + next_log_entry = LOG.readline() + while RE_LOG_v2_6_syslog.search(next_log_entry) or RE_LOG_v2_6_audit.search(next_log_entry) or re.search(logmark, next_log_entry): + next_log_entry = LOG.readline() + if not next_log_entry: + break + +def get_next_log_entry(): + # If no next log entry fetch it + if not next_log_entry: + prefetch_next_log_entry() + log_entry = next_log_entry + next_log_entry = None + return log_entry + +def peek_at_next_log_entry(): + # Take a peek at the next log entry + if not next_log_entry: + prefetch_next_log_entry() + return next_log_entry + +def throw_away_next_log_entry(): + next_log_entry = None + +def parse_log_record(record): + if DEBUGGING: + debug_logger.debug('parse_log_record: %s' % record) + + record_event = parse_event(record) + return record_event diff --git a/apparmor/config.py b/apparmor/config.py index 17cc15082..e3ffa6490 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -272,6 +272,8 @@ def py2_parser(filename): if os.path.exists(filename): with open_file_read(filename) as f_in: for line in f_in: + # The ini format allows for multi-line entries, with the subsequent + # entries being indented deeper hence simple lstrip() is not appropriate if line[:2] == ' ': line = line[2:] f_out.write(line) diff --git a/apparmor/severity.py b/apparmor/severity.py index 8375937bc..21aa87605 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -13,7 +13,7 @@ class Severity: self.severity['FILES'] = {} self.severity['REGEXPS'] = {} self.severity['DEFAULT_RANK'] = default_rank - # For variable expansions from /etc/apparmor.d + # For variable expansions for the profile self.severity['VARIABLES'] = dict() if not dbname: return None @@ -184,7 +184,7 @@ class Severity: def load_variables(self, prof_path): """Loads the variables for the given profile""" - regex_include = re.compile('include <(\S*)>') + regex_include = re.compile('^#?include\s*<(\S*)>') if os.path.isfile(prof_path): with open_file_read(prof_path) as f_in: for line in f_in: @@ -196,6 +196,7 @@ class Severity: new_path = self.PROF_DIR + '/' + new_path self.load_variables(new_path) else: + # Remove any comments if '#' in line: line = line.split('#')[0].rstrip() # Expected format is @{Variable} = value1 value2 .. From 60def060408b568a88ecf8d2454b43afbabf7007 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Wed, 24 Jul 2013 22:12:34 +0530 Subject: [PATCH 025/183] Code-base update --- apparmor/aa.py | 282 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 274 insertions(+), 8 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 4b4f30b79..a87e6b516 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,4 @@ -#2778 +#3389 #382-430 #480-525 # No old version logs, only 2.6 + supported @@ -11,6 +11,7 @@ import re import shutil import subprocess import sys +import time import traceback import atexit import tempfile @@ -156,7 +157,7 @@ def on_exit(): # Register the on_exit method with atexit atexit.register(on_exit) -def opt_type(operation): +def op_type(operation): """Returns the operation type if known, unkown otherwise""" operation_type = OPERATION_TYPES.get(operation, 'unknown') return operation_type @@ -1271,7 +1272,7 @@ def handle_children(profile, hat, root): 'Not sanitising the environment when unconfining\n' + 'a program opens up significant security holes\n' + 'and should be avoided if at all possible.'), 'y') - if yans == 'y': + if ynans == 'y': # Disable the unsafe mode exec_mode &= ~(AA_EXEC_UNSAFE | (AA_EXEC_UNSAFE << AA_OTHER_SHIFT)) else: @@ -1407,23 +1408,23 @@ def handle_children(profile, hat, root): return None -def add_to_tree(pid, parent, type, event): +def add_to_tree(loc_pid, parent, type, event): if DEBUGGING: debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (pid, type, event)) - if not pid.get(pid, False): + if not pid.get(loc_pid, False): profile, hat = event[:1] if parent and pid.get(parent, False): if not hat: hat = 'null-complain-profile' - array_ref = ['fork', pid, profile, hat] + array_ref = ['fork', loc_pid, profile, hat] pid[parent].append(array_ref) - pid[pid] = array_ref + pid[loc_pid] = array_ref #else: # array_ref = [] # log.append(array_ref) # pid[pid] = array_ref - pid[pid] += [type, pid, event] + pid[loc_pid] += [type, loc_pid, event] # Variables used by logparsing routines LOG = None @@ -1469,3 +1470,268 @@ def parse_log_record(record): record_event = parse_event(record) return record_event + +def add_event_to_tree(e): + aamode = e.get('aamode', 'UNKNOWN') + if e.get('type', False): + if re.search('(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)', e['type']): + aamode = 'AUDIT' + elif re.search('(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)', e['type']): + aamode = 'PERMITTING' + elif re.search('(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)', e['type']): + aamode = 'REJECTING' + elif re.search('(UNKNOWN\[1504\]|APPARMOR_HINT|1504)', e['type']): + aamode = 'HINT' + elif re.search('(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)', e['type']): + aamode = 'STATUS' + elif re.search('(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)', e['type']): + aamode = 'ERROR' + else: + aamode = 'UNKNOWN' + + if aamode in ['UNKNOWN', 'AUDIT', 'STATUS', 'ERROR']: + return None + + if 'profile_set' in e['operation']: + return None + + # Skip if AUDIT event was issued due to a change_hat in unconfined mode + if not e.get('profile', False): + return None + + # Convert new null profiles to old single level null profile + if '\\null-' in e['profile']: + e['profile'] = 'null-complain-profile' + + profile = e['profile'] + hat = None + + if '\\' in e['profile']: + profile, hat = e['profile'].split('\\') + + # Filter out change_hat events that aren't from learning + if e['operation'] == 'change_hat': + if aamode != 'HINT' and aamode != 'PERMITTING': + return None + profile = e['name'] + if '\\' in e['name']: + profile, hat = e['name'].split('\\') + + # prog is no longer passed around consistently + prog = 'HINT' + + if profile != 'null-complain-profile' and not profile_exists(profile): + return None + + if e['operation'] == 'exec': + if e.get('info', False) and e['info'] == 'mandatory profile missing': + add_to_tree(e['pid'], e['parent'], 'exec', + [profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']]) + elif e.get('name2', False) and '\\null-/' in e['name2']: + add_to_tree(e['pid'], e['parent'], 'exec', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + elif e.get('name', False): + add_to_tree(e['pid'], e['parent'], 'exec', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + else: + debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile']) + + elif 'file_' in e['operation']: + add_to_tree(e['pid'], e['parent'], 'path', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src', + 'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']: + add_to_tree(e['pid'], e['parent'], 'path', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + elif e['operation'] == 'capable': + add_to_tree(e['pid'], e['parent'], 'capability', + [profile, hat, prog, aamode, e['name'], '']) + elif e['operation'] == 'setattr' or 'xattr' in e['operation']: + add_to_tree(e['pid'], e['parent'], 'path', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + elif 'inode_' in e['operation']: + is_domain_change = False + if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING': + following = peek_at_next_log_entry() + if following: + entry = parse_log_record(following) + if entry and entry.get('info', False) == 'set profile': + is_domain_change = True + throw_away_next_log_entry() + + if is_domain_change: + add_to_tree(e['pid'], e['parent'], 'exec', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']]) + else: + add_to_tree(e['pid'], e['parent'], 'path', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + + elif e['operation'] == 'sysctl': + add_to_tree(e['pid'], e['parent'], 'path', + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + + elif e['operation'] == 'clone': + parent , child = e['pid'], e['task'] + if not parent: + parent = 'null-complain-profile' + if not hat: + hat = 'null-complain-profile' + arrayref = ['fork', child, profile, hat] + if pid.get(parent, False): + pid[parent] += [arrayref] + else: + log += [arrayref] + pid[child] = arrayref + + elif op_type(e['operation']) == 'net': + add_to_tree(e['pid'], e['parent'], 'netdomain', + [profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']]) + elif e['operation'] == 'change_hat': + add_to_tree(e['pid'], e['parent'], 'unknown_hat', + [profile, hat, aamode, hat]) + else: + debug_logger.debug('UNHANDLED: %s' % e) + +def read_log(logmark): + seenmark = True + if logmark: + seenmark = False + #last = None + #event_type = None + try: + log_open = open_file_read(filename) + except IOError: + raise AppArmorException('Can not read AppArmor logfile: ' + filename) + + with log_open as f_in: + for line in f_in: + line = line.strip() + debug_logger.debug('read_log: %s' % line) + if logmark in line: + seenmark = True + if not seenmark: + debug_logger.debug('read_log: seenmark = %s' % seenmark) + + event = parse_log_record(line) + if event: + add_event_to_tree(event) + logmark = '' + +def parse_event(msg): + """Parse the event from log into key value pairs""" + msg = msg.strip() + debug_logger.log('parse_event: %s' % msg) + event = LibAppArmor.parse_record(msg) + rmask = None + dmask = None + ev = dict() + ev['resource'] = event.info + ev['active_hat'] = event.active_hat + ev['aamode'] = event.event + ev['time'] = event.epoch + ev['operation'] = event.operation + ev['profile'] = event.profile + ev['name'] = event.name + ev['name2'] = event.name2 + ev['attr'] = event.attribute + ev['parent'] = event.parent + ev['pid'] = event.pid + ev['task'] = event.task + ev['info'] = event.info + dmask = event.denied_mask + rmask = event.requested_mask + ev['magic_token'] = event.magic_token + if ev['operation'] and op_type(ev['operation']) == 'net': + ev['family'] = event.net_family + ev['protocol'] = event.net_protocol + ev['sock_type'] = event.net_sock_type + LibAppArmor.free_record(event) + # Map c and d to w, logprof doesn't support c and d + if rmask: + rmask = rmask.replace('c', 'w') + rmask = rmask.replace('d', 'w') + if not validate_log_mode(hide_log_mode(rmask)): + fatal_error(gettext('Log contains unknown mode %s') % rmask) + if dmask: + dmask = dmask.replace('c', 'w') + dmask = dmask.replace('d', 'w') + if not validate_log_mode(hide_log_mode(dmask)): + fatal_error(gettext('Log contains unknown mode %s') % dmask) + + mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2']) + ev['denied_mask'] = mask + ev['name2'] = name + + mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2']) + ev['request_mask'] = mask + ev['name2'] = name + + if not ev['time']: + ev['time'] = int(time.time) + # Remove None keys + #for key in ev.keys(): + # if not ev[key] or not re.search('[\w]+', ev[key]): + # ev.pop(key) + if ev['aamode']: + # Convert aamode values to their counter-parts + mode_convertor = { + 0: 'UNKNOWN', + 1: 'ERROR', + 2: 'AUDITING', + 3: 'PERMITTING', + 4: 'REJECTING', + 5: 'HINT', + 6: 'STATUS' + } + try: + ev['aamode'] = mode_convertor(ev['aamode']) + except KeyError: + ev['aamode'] = None + + if ev['aamode']: + debug_logger.debug(ev) + return ev + else: + return None +# Repo related functions + +def UI_SelectUpdatedRepoProfile(profile, p): + # To-Do + return False + +def UI_repo_signup(): + # To-Do + return None, None + +def UI_ask_to_enable_repo(): + # To-Do + pass + +def UI_ask_to_upload_profiles(): + # To-Do + pass + +def UI_ask_mode_toggles(audit_toggle, owner_toggle, oldmode): + # To-Do + pass + +def parse_repo_profile(fqdbin, repo_url, profile): + # To-Do + pass + +def set_repo_info(profile_data, repo_url, username, iden): + # To-Do + pass + +def is_repo_profile(profile_data): + # To-Do + pass + +def get_repo_user_pass(): + # To-Do + pass + +def update_repo_profile(profile): + # To-Do + pass + From 5abbc86d37881487e7763dc1611ae468f5015b15 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 27 Jul 2013 15:28:12 +0530 Subject: [PATCH 026/183] Revision 24 edits and code update --- apparmor/aa.py | 581 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 555 insertions(+), 26 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index a87e6b516..020543343 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,4 @@ -#3389 +#4093 #382-430 #480-525 # No old version logs, only 2.6 + supported @@ -79,7 +79,7 @@ pid = None seen = dir() profile_changes = dict() prelog = dict() -log_dict = dict() +log_dict = hasher()#dict() changed = dict() created = [] helpers = dict() # Preserve this between passes # was our @@ -435,25 +435,27 @@ def create_new_profile(localfile): if os.path.isfile(localfile): hashbang = head(localfile) if hashbang.startswith('#!'): - interpreter = get_full_path(hashbang.lstrip('#!').strip()) + interpreter_path = get_full_path(hashbang.lstrip('#!').strip()) + + interpreter = re.sub('^(/usr)?/bin/[^/]', '', interpreter_path) local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r') local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', 0) - local_profile[localfile]['allow']['path'][interpreter]['mode'] = local_profile[localfile]['allow']['path'][interpreter].get('mode', str_to_mode('ix')) | str_to_mode('ix') + local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') - local_profile[localfile]['allow']['path'][interpreter]['audit'] = local_profile[localfile]['allow']['path'][interpreter].get('audit', 0) + local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', 0) - if 'perl' in interpreter: + if interpreter == 'perl': local_profile[localfile]['include']['abstractions/perl'] = True - elif 'python' in interpreter: + elif interpreter == 'python': local_profile[localfile]['include']['abstractions/python'] = True - elif 'ruby' in interpreter: + elif interpreter == 'ruby': local_profile[localfile]['include']['abstractions/ruby'] = True - elif re.search('/bin/(bash|dash|sh)', interpreter): + elif interpreter in ['bash', 'dash', 'sh']: local_profile[localfile]['include']['abstractions/bash'] = True - handle_binfmt(local_profile[localfile], interpreter) + handle_binfmt(local_profile[localfile], interpreter_path) else: local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('mr')) | str_to_mode('mr') @@ -669,12 +671,12 @@ def sync_profile(): UI_Important('WARNING: Error synchronizing profiles with the repository:\n%s\n' % ret) else: users_repo_profiles = ret - serialuze_opts['NO_FLAGS'] = True + serialize_opts['NO_FLAGS'] = True for prof in sorted(aa.keys()): if is_repo_profile([aa[prof][prof]]): repo_profiles.append(prof) if prof in created: - p_local = seralize_profile(aa[prof], prof, serialize_opts) + p_local = serialize_profile(aa[prof], prof, serialize_opts) if not users_repo_profiles.get(prof, False): new_profiles.append(prof) new_profiles.append(p_local) @@ -1162,9 +1164,9 @@ def handle_children(profile, hat, root): if to_name: fatal_error('%s has transition name but not transition mode' % entry) - # If profiled program executes itself only 'ix' option - if exec_target == profile: - options = 'i' + ### If profiled program executes itself only 'ix' option + ##if exec_target == profile: + ##options = 'i' # Don't allow hats to cx? options.replace('c', '') @@ -1296,7 +1298,7 @@ def handle_children(profile, hat, root): if ans != 'CMD_DENY': prelog['PERMITTING'][profile][hat]['path'][exec_target] = prelog['PERMITTING'][profile][hat]['path'].get(exec_target, exec_mode) | exec_mode - log['PERMITTING'][profile] = hasher() + log_dict['PERMITTING'][profile] = hasher() aa[profile][hat]['allow']['path'][exec_target]['mode'] = aa[profile][hat]['allow']['path'][exec_target].get('mode', exec_mode) @@ -1315,15 +1317,16 @@ def handle_children(profile, hat, root): hashbang = head(exec_target) if hashbang.startswith('#!'): interpreter = hashbang[2:].strip() - interpreter = get_full_path(interpreter) + interpreter_path = get_full_path(interpreter) + interpreter = re.sub('^(/usr)?/bin/[^/]', '', interpreter_path) - aa[profile][hat]['path'][interpreter]['mode'] = aa[profile][hat]['path'][interpreter].get('mode', str_to_mode('ix')) | str_to_mode('ix') + aa[profile][hat]['path'][interpreter_path]['mode'] = aa[profile][hat]['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') - aa[profile][hat]['path'][interpreter]['audit'] = aa[profile][hat]['path'][interpreter].get('audit', 0) + aa[profile][hat]['path'][interpreter_path]['audit'] = aa[profile][hat]['path'][interpreter_path].get('audit', 0) - if 'perl' in interpreter: + if interpreter == 'perl': aa[profile][hat]['include']['abstractions/perl'] = True - elif '/bin/bash' in interpreter or '/bin/sh' in interpreter: + elif interpreter in ['bash', 'dash', 'sh']: aa[profile][hat]['include']['abstractions/bash'] = True # Update tracking info based on kind of change @@ -1438,6 +1441,8 @@ RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\( #RE_LOG_v2_1_audit = re.compile('type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))') RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=') +LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') + def prefetch_next_log_entry(): if next_log_entry: sys.stderr.out('A log entry already present: %s' % next_log_entry) @@ -1646,14 +1651,14 @@ def parse_event(msg): ev['protocol'] = event.net_protocol ev['sock_type'] = event.net_sock_type LibAppArmor.free_record(event) - # Map c and d to w, logprof doesn't support c and d + # Map c (create) to a and d (delete) to w, logprof doesn't support c and d if rmask: - rmask = rmask.replace('c', 'w') + rmask = rmask.replace('c', 'a') rmask = rmask.replace('d', 'w') if not validate_log_mode(hide_log_mode(rmask)): fatal_error(gettext('Log contains unknown mode %s') % rmask) if dmask: - dmask = dmask.replace('c', 'w') + dmask = dmask.replace('c', 'a') dmask = dmask.replace('d', 'w') if not validate_log_mode(hide_log_mode(dmask)): fatal_error(gettext('Log contains unknown mode %s') % dmask) @@ -1693,7 +1698,18 @@ def parse_event(msg): return ev else: return None -# Repo related functions + +def hide_log_mode(mode): + mode = mode.replace('::', '') + return mode + +def validate_log_mode(mode): + if LOG_MODE_RE.search(mode): + return True + else: + return False + +##### Repo related functions def UI_SelectUpdatedRepoProfile(profile, p): # To-Do @@ -1733,5 +1749,518 @@ def get_repo_user_pass(): def update_repo_profile(profile): # To-Do - pass + return None + +def order_globs(globs, path): + """Returns the globs in sorted order, more specific behind""" + # To-Do + # ATM its lexicographic, should be done to allow better matches later + return sorted(globs) + +def ask_the_question(): + found = None + for aamode in sorted(log_dict.keys()): + # Describe the type of changes + if aamode == 'PERMITTING': + UI_Info(gettext('Complain-mode changes:')) + elif aamode == 'REJECTING': + UI_Info(gettext('Enforce-mode changes:')) + else: + # oops something screwed up + fatal_error(gettext('Invalid mode found: %s') % aamode) + + for profile in sorted(log_dict[aamode].keys()): + # Update the repo profiles + p = update_repo_profile(aa[profile][profile]) + if p: + UI_SelectUpdatedRepoProfile(profile, p) + + found += 1 + # Sorted list of hats with the profile name coming first + hats = filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys())) + if log_dict[aamode][profile].get(profile, False): + hats = [profile] + hats + + for hat in hats: + for capability in sorted(log_dict[aamode][profile][hat]['capability'].keys()): + # skip if capability already in profile + if profile_known_capability(aa[profile][hat], capability): + continue + # Load variables? Don't think so. + severity = sev_db.rank('CAP_%s' % capability) + default_option = 1 + options = [] + newincludes = match_cap_includes(aa[profile][hat], capability) + q = hasher() + + if newincludes: + options += map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes))) + + if options: + options.append('capability %s' % capability) + q['options'] = [options] + q['selected'] = default_option - 1 + + q['headers'] = [gettext('Profile'), combine_name(profile, hat)] + q['headers'] += [gettext('Capability'), capability] + q['headers'] += [gettext('Severity'), severity] + + audit_toggle = 0 + + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] + + # In complain mode: events default to allow + # In enforce mode: events default to deny + q['default'] = 'CMD_DENY' + if aamode == 'PERMITTING': + q['default'] = 'CMD_ALLOW' + + seen_events += 1 + + done = False + while not done: + ans, selected = UI_PromptUser(q) + if ans == 'CMD_AUDIT': + audit_toggle = not audit_toggle + audit = '' + if audit_toggle: + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', + 'CMD_ABORT', 'CMD_FINISHED'] + audit = 'audit' + else: + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] + + q['headers'] = [gettext('Profile'), combine_name(profile, hat), + gettext('Capability'), audit + capability, + gettext('Severity'), severity] + + if ans == 'CMD_ALLOW': + selection = options[selected] + match = re.search('^#include\s+<(.+)>$', selection) + if match: + deleted = False + inc = match.groups()[0] + deleted = delete_duplicates(aa[profile][hat], inc) + aa[profile][hat]['include'][inc] = True + + UI_Info(gettext('Adding %s to profile.') % selection) + if deleted: + UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + + aa[profile][hat]['allow']['capability'][capability]['set'] = True + aa[profile][hat]['allow']['capability'][capability]['audit'] = audit_toggle + + changed[profile] = True + + UI_Info(gettext('Adding capability %s to profile.'), capability) + done = True + + elif ans == 'CMD_DENY': + aa[profile][hat]['deny']['capability'][capability]['set'] = True + changed[profile] = True + + UI_Info(gettext('Denying capability %s to profile.') % capability) + done = True + else: + done = False + + # Process all the path entries. + for path in sorted(log_dict[aamode][profile][hat]['path'].keys()): + mode = log_dict[aamode][profile][hat]['path'][path] + # Lookup modes from profile + allow_mode = 0 + allow_audit = 0 + deny_mode = 0 + deny_audit = 0 + + fmode, famode, fm = rematch_frag(aa[profile][hat], 'allow', path) + if fmode: + allow_mode |= fmode + if famode: + allow_audit |= famode + + cm, cam, m = rematch_frag(aa[profile][hat], 'deny', path) + if cm: + deny_mode |= cm + if cam: + deny_audit |= cam + + imode, iamode, im = match_prof_incs_to_path(aa[profile][hat], 'allow', path) + if imode: + allow_mode |= imode + if iamode: + allow_audit |= iamode + + cm, cam, m = match_prof_incs_to_path(aa[profile][hat], 'deny', path) + if cm: + deny_mode |= cm + if cam: + deny_audit |= cam + + if deny_mode & AA_MAY_EXEC: + deny_mode |= ALL_AA_EXEC_TYPE + + # Mask off the denied modes + mode = mode & ~deny_mode + + # If we get an exec request from some kindof event that generates 'PERMITTING X' + # check if its already in allow_mode + # if not add ix permission + if mode & AA_MAY_EXEC: + # Remove all type access permission + mode = mode & ~ALL_AA_EXEC_TYPE + if not allow_mode & AA_MAY_EXEC: + mode |= str_to_mode('ix') + + # If we get an mmap request, check if we already have it in allow_mode + if mode & AA_EXEC_MMAP: + # ix implies m, so we don't need to add m if ix is present + if contains(allow_mode, 'ix'): + mode = mode & ~AA_EXEC_MMAP + + if not mode: + continue + + matches = [] + + if fmode: + matches.append(fm) + + if imode: + matches.append(im) + + if not mode_contains(allow_mode, mode): + default_option = 1 + options = [] + newincludes = [] + include_valid = False + + for incname in include.keys(): + include_valid = False + # If already present skip + if aa[profile][hat][incname]: + continue + + if cfg['settings']['custom_includes']: + for incn in cfg['settings']['custom_includes'].split(): + if incn in incname: + include_valid = True + + if 'abstraction' in incname: + include_valid = True + + if not include_valid: + continue + + cm, am, m = match_include_to_path(incname, 'allow', path) + + if cm and mode_contains(cm, mode): + dm = match_include_to_path(incname, 'deny', path) + # If the mode is denied + if not mode & dm: + if not filter(lambda s: '/**' not in s, m): + newincludes.append(incname) + # Add new includes to the options + if newincludes: + options += map(lambda s: '#include <%s>' % s, sorted(set(newincludes))) + # We should have literal the path in options list too + options.append(path) + # Add any the globs matching path from logprof + globs = globcommon(path) + if globs: + matches += globs + # Add any user entered matching globs + for user_glob in user_globs: + if matchliteral(user_glob, path): + matches.append(user_glob) + + matches = list(set(matches)) + if path in matches: + matches.remove(path) + + options += order_globs(matches, path) + default_option = len(options) + + sev_db.unload_variables() + sev_db.load_variables(profile) + severity = sev_db.rank(path, mode_to_str(mode)) + sev_db.unload_variables() + + audit_toggle = 0 + owner_toggle = cfg['settings']['default_owner_prompt'] + done = False + while not done: + q = hasher() + q['headers'] = [gettext('Profile'), combine_name(profile, hat), + gettext('Path'), path] + + if allow_mode: + mode |= allow_mode + tail = '' + s = '' + prompt_mode = None + if owner_toggle == 0: + prompt_mode = flatten_mode(mode) + tail = ' ' + gettext('(owner permissions off)') + elif owner_toggle == 1: + prompt_mode = mode + elif owner_toggle == 2: + prompt_mode = allow_mode | owner_flatten_mode(mode & ~allow_mode) + tail = ' ' + gettext('(force new perms to owner)') + else: + prompt_mode = owner_flatten_mode(mode) + tail = ' ' + gettext('(force all rule perms to owner)') + + if audit_toggle == 1: + s = mode_to_str_user(allow_mode) + if allow_mode: + s += ', ' + s += 'audit ' + mode_to_str_user(prompt_mode & ~allow_mode) + tail + elif audit_toggle == 2: + s = 'audit ' + mode_to_str_user(prompt_mode) + tail + else: + s = mode_to_str_user(prompt_mode) + tail + + q['headers'] += [gettext('Old Mode'), mode_to_str_user(allow_mode), + gettext('New Mode'), s] + + else: + s = '' + tail = '' + prompt_mode = None + if audit_toggle: + s = 'audit' + if owner_toggle == 0: + prompt_mode = flatten_mode(mode) + tail = ' ' + gettext('(owner permissions off)') + elif owner_toggle == 1: + prompt_mode = mode + else: + prompt_mode = owner_flatten_mode(mode) + tail = ' ' + gettext('(force perms to owner)') + + s = mode_to_str_user(prompt_mode) + q['headers'] += [gettext('Mode'), s] + + q['headers'] += [gettext('Severity'), severity] + q['options'] = options + q['selected'] = default_option - 1 + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_GLOB', + 'CMD_GLOBTEXT', 'CMD_NEW', 'CMD_ABORT', + 'CMD_FINISHED', 'CMD_OTHER'] + q['default'] = 'CMD_DENY' + if aamode == 'PERMITTING': + q['default'] = 'CMD_ALLOW' + + seen_events += 1 + + ans, selected = UI_PromptUser(q) + + if ans == 'CMD_OTHER': + audit_toggle, owner_toggle = UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode) + elif ans == 'CMD_USER_TOGGLE': + owner_toggle += 1 + if not allow_mode and owner_toggle == 2: + owner_toggle += 1 + if owner_toggle > 3: + owner_toggle = 0 + elif ans == 'CMD_ALLOW': + path = options[selected] + done = True + match = re.search('^#include\s+<(.+)>$', path) + if match: + inc = match.gropus()[0] + deleted = 0 + deleted = delete_duplicates(aa[profile][hat], inc) + aa[profile][hat]['include'][inc] = True + changed[profile] = True + UI_Info(gettext('Adding %s to profile.') % path) + if deleted: + UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + + else: + if aa[profile][hat]['allow']['path'][path].get('mode', False): + mode |= aa[profile][hat]['allow']['path'][path]['mode'] + deleted = 0 + for entry in aa[profile][hat]['allow']['path'].keys(): + if path == entry: + continue + + if matchregexp(path, entry): + if mode_contains(mode, aa[profile][hat]['allow']['path'][entry]['mode']): + aa[profile][hat]['allow']['path'].pop(entry) + deleted += 1 + + if owner_toggle == 0: + mode = flatten_mode(mode) + #elif owner_toggle == 1: + # mode = mode + elif owner_toggle == 2: + mode = allow_mode | owner_flatten_mode(mode & ~allow_mode) + elif owner_toggle == 3: + mode = owner_flatten_mode(mode) + + aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', 0) | mode + + tmpmode = 0 + if audit_toggle == 1: + tmpmode = mode & ~allow_mode + elif audit_toggle == 2: + tmpmode = mode + + aa[profile][hat]['allow']['path'][path]['audit'] = aa[profile][hat]['allow']['path'][path].get('audit', 0) | tmpmode + + changed[profile] = True + + UI_Info(gettext('Adding %s %s to profile') % (path, mode_to_str_user(mode))) + if deleted: + UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + + elif ans == 'CMD_DENY': + # Add new entry? + aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', 0) | (mode & ~allow_mode) + + aa[profile][hat]['deny']['path'][path]['audit'] = aa[profile][hat]['deny']['path'][path].get('audit', 0) + + changed[profile] = True + + done = True + + elif ans == 'CMD_NEW': + arg = options[selected] + if not arg.startswith('#include'): + ans = UI_GetString(gettext('Enter new path:'), arg) + if ans: + if not matchliteral(ans, path): + ynprompt = gettext('The specified path does not match this log entry:') + ynprompt += '\n\n ' + gettext('Log Entry') + ': %s' % path + ynprompt += '\n ' + gettext('Entered Path') + ': %s' % ans + ynprompt += gettext('Do you really want to use this path?') + '\n' + key = UI_YesNo(ynprompt, 'n') + if key == 'n': + continue + + user_globs.append(ans) + options.append(ans) + default_option = len(options) + + elif ans == 'CMD_GLOB': + newpath = options[selected].strip() + if not newpath.startswith('#include'): + if newpath[-1] == '/': + if newpath[-4:] == '/**/' or newpath[-3:] == '/*/': + # collapse one level to /**/ + newpath = re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) + else: + newpath = re.sub('/[^/]+/$', '/\*/', newpath) + else: + if newpath[-3:] == '/**' or newpath[-2:] == '/*': + newpath = re.sub('/[^/]+/\*{1,2}$', '/\*\*', newpath) + elif re.search('/\*\*[^/]+$', newpath): + newpath = re.sub('/\*\*[^/]+$', '/\*\*', newpath) + else: + newpath = re.sub('/[^/]+$', '/\*', newpath) + + if newpath not in options: + options.append(newpath) + default_option = len(options) + + elif ans == 'CMD_GLOBEXT': + newpath = options[selected].strip() + if not newpath.startswith('#include'): + match = re.search('/\*{1,2}(\.[^/]+)$', newpath) + if match: + newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/\*\*'+match.group()[0], newpath) + else: + match = re.search('(\.[^/]+)$') + newpath = re.sub('/[^/]+(\.[^/]+)$', '/\*'+match.groups()[0], newpath) + if newpath not in options: + options.append(newpath) + default_option = len(options) + + elif re.search('\d', ans): + default_option = ans + + # + for family in sorted(log_dict[aamode][profile][hat]['netdomain'].keys()): + # severity handling for net toggles goes here + for sock_type in sorted(log_dict[aaprofile][profile][hat]['netdomain'][family].keys()): + if profile_known_network(aa[profile][hat], family, sock_type): + continue + default_option = 1 + options = [] + newincludes = matchnetincludes(aa[profile][hat], family, sock_type) + q = hasher() + if newincludes: + options += map(lambda s: '#include <%s>'%s, sorted(set(newincludes))) + if options: + options.append('network %s %s' % (family, sock_type)) + q['options'] = options + q['selected'] = default_option - 1 + + q['headers'] = [gettext('Profile'), combine_name(profile, hat)] + q['headers'] += [gettext('Network Family'), family] + q['headers'] += [gettext('Socket Type'), sock_type] + + audit_toggle = 0 + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] + q['default'] = 'CMD_DENY' + + if aamode == 'PERMITTING': + q['default'] = 'CMD_ALLOW' + + seen_events += 1 + + done = False + while not done: + ans, selected = UI_PromptUser(q) + if ans.startswith('CMD_AUDIT'): + audit_toggle = not audit_toggle + audit = '' + if audit_toggle: + audit = 'audit' + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', + 'CMD_ABORT', 'CMD_FINISHED'] + else: + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] + q['headers'] = [gettext('Profile'), combine_name(profile, hat)] + q['headers'] += [gettext('Network Family'), audit + family] + q['headers'] += [gettext('Socket Type'), sock_type] + + elif ans == 'CMD_ALLOW': + selection = options[selected] + done = True + if re.search('#include\s+<.+>$', selection): + inc = re.search('#include\s+<(.+)>$', selection).groups()[0] + deleted = 0 + deleted = delete_duplicates(aa[profile][hat], inc) + + aa[profile][hat]['include'][inc] = True + + changed[profile] = True + + UI_Info(gettext('Adding %s to profile') % selection) + if deleted: + UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + + else: + aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle + aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True + + changed[profile] = True + + UI_Info(gettext('Adding network access %s %s to profile.' % (family, sock_type))) + + elif ans == 'CMD_DENY': + done = True + aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True + changed[profile] = True + UI_Info(gettext('Denying network access %s %s to profile') % (family, sock_type)) + + else: + done = False From bcceaa9c28166440e9c219f14f472aff3fe53177 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 27 Jul 2013 15:32:12 +0530 Subject: [PATCH 027/183] minor fix to regex from rev 26 --- apparmor/aa.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 020543343..034b549c3 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -437,7 +437,7 @@ def create_new_profile(localfile): if hashbang.startswith('#!'): interpreter_path = get_full_path(hashbang.lstrip('#!').strip()) - interpreter = re.sub('^(/usr)?/bin/[^/]', '', interpreter_path) + interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path) local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r') @@ -1318,7 +1318,7 @@ def handle_children(profile, hat, root): if hashbang.startswith('#!'): interpreter = hashbang[2:].strip() interpreter_path = get_full_path(interpreter) - interpreter = re.sub('^(/usr)?/bin/[^/]', '', interpreter_path) + interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path) aa[profile][hat]['path'][interpreter_path]['mode'] = aa[profile][hat]['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') From 375fc3b5bb09caf59fccba77ed4e6d6eff8372c0 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 28 Jul 2013 08:23:46 +0530 Subject: [PATCH 028/183] edits from review 26,27 and codebase update --- Testing/severity_test.py | 2 +- apparmor/aa.py | 821 ++++++++++++++++++++++++++++++++++++--- apparmor/ui.py | 3 +- apparmor/yasti.py | 10 +- 4 files changed, 775 insertions(+), 61 deletions(-) diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 4d2701d1c..77c625124 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -9,7 +9,7 @@ from apparmor.common import AppArmorException class Test(unittest.TestCase): def testRank_Test(self): - z = severity.Severity() + #z = severity.Severity() s = severity.Severity('severity.db') rank = s.rank('/usr/bin/whatis', 'x') diff --git a/apparmor/aa.py b/apparmor/aa.py index 034b549c3..3ee521dd2 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,4 @@ -#4093 +#4925 #382-430 #480-525 # No old version logs, only 2.6 + supported @@ -25,6 +25,7 @@ from apparmor.common import (AppArmorException, error, debug, msg, hasher, open_file_write) from apparmor.ui import * +from copy import deepcopy DEBUGGING = False debug_logger = None @@ -67,25 +68,26 @@ user_globs = [] ## Variables used under logprof ### Were our -t = dict() +t = hasher()#dict() transitions = dict() -aa = dict() # Profiles originally in sd, replace by aa -original_aa = dict() -extras = dict() # Inactive profiles from extras +aa = hasher() # Profiles originally in sd, replace by aa +original_aa = hasher() +extras = hasher() # Inactive profiles from extras ### end our log = [] pid = None -seen = dir() +seen = hasher()#dir() profile_changes = dict() -prelog = dict() +prelog = hasher() log_dict = hasher()#dict() changed = dict() created = [] +skip = hasher() helpers = dict() # Preserve this between passes # was our ### logprof ends -filelist = dict() # File level variables and stuff in config files +filelist = hasher() # File level variables and stuff in config files AA_MAY_EXEC = 1 AA_MAY_WRITE = 2 @@ -449,7 +451,7 @@ def create_new_profile(localfile): if interpreter == 'perl': local_profile[localfile]['include']['abstractions/perl'] = True - elif interpreter == 'python': + elif re.search('python()', interpreter): local_profile[localfile]['include']['abstractions/python'] = True elif interpreter == 'ruby': local_profile[localfile]['include']['abstractions/ruby'] = True @@ -547,12 +549,12 @@ def get_profile(prof_name): 'profile_type': p['profile_type'] }) ypath, yarg = GetDataFromYast() - else: - pager = get_pager() - proc = subprocess.Popen(pager, stdin=subprocess.PIPE) - proc.communicate('Profile submitted by %s:\n\n%s\n\n' % - (options[arg], p['profile'])) - proc.kill() + #else: + # pager = get_pager() + # proc = subprocess.Popen(pager, stdin=subprocess.PIPE) + # proc.communicate('Profile submitted by %s:\n\n%s\n\n' % + # (options[arg], p['profile'])) + # proc.kill() elif ans == 'CMD_USE_PROFILE': if p['profile_type'] == 'INACTIVE_LOCAL': profile_data = p['profile_data'] @@ -601,7 +603,7 @@ def autodep(bin_name, pname=''): profile_data = create_new_profile(pname) file = get_profile_filename(pname) attach_profile_data(aa, profile_data) - attach_profile_data(aa_original, profile_data) + attach_profile_data(original_aa, profile_data) if os.path.isfile(profile_dir + '/tunables/global'): if not filelist.get(file, False): filelist.file = hasher() @@ -612,8 +614,8 @@ def set_profile_flags(prof_filename, newflags): """Reads the old profile file and updates the flags accordingly""" regex_bin_flag = re.compile('^(\s*)(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+(flags=\(.+\)\s+)*\{\s*$/') regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') - a=re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') - regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*(#*\S*)$') + #a=re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') + #regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*(#*\S*)$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: tempfile = tempfile.NamedTemporaryFile('w', prefix=prof_filename , suffix='~', delete=False, dir='/etc/apparmor.d/') @@ -1064,7 +1066,7 @@ def handle_children(profile, hat, root): combinedaudit = False ## Check return Value Consistency # Check if path matches any existing regexps in profile - cm, am , m = rematchfrag(aa[profile][hat], 'allow', exec_target) + cm, am , m = rematch_frag(aa[profile][hat], 'allow', exec_target) if cm: combinedmode |= cm if am: @@ -1441,7 +1443,11 @@ RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\( #RE_LOG_v2_1_audit = re.compile('type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))') RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=') +MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N') LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') +PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') +PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') +PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x') def prefetch_next_log_entry(): if next_log_entry: @@ -1704,7 +1710,9 @@ def hide_log_mode(mode): return mode def validate_log_mode(mode): - if LOG_MODE_RE.search(mode): + pattern = '^(%s)+$' % LOG_MODE_RE.pattern + if re.search(pattern, mode): + #if LOG_MODE_RE.search(mode): return True else: return False @@ -1757,7 +1765,7 @@ def order_globs(globs, path): # ATM its lexicographic, should be done to allow better matches later return sorted(globs) -def ask_the_question(): +def ask_the_questions(): found = None for aamode in sorted(log_dict.keys()): # Describe the type of changes @@ -1808,7 +1816,7 @@ def ask_the_question(): audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED'] + 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] # In complain mode: events default to allow # In enforce mode: events default to deny @@ -1821,16 +1829,21 @@ def ask_the_question(): done = False while not done: ans, selected = UI_PromptUser(q) + # Ignore the log entry + if ans == 'CMD_IGNORE_ENTRY': + done = True + break + if ans == 'CMD_AUDIT': audit_toggle = not audit_toggle audit = '' if audit_toggle: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', - 'CMD_ABORT', 'CMD_FINISHED'] + 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] audit = 'audit' else: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED'] + 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] q['headers'] = [gettext('Profile'), combine_name(profile, hat), gettext('Capability'), audit + capability, @@ -1838,10 +1851,10 @@ def ask_the_question(): if ans == 'CMD_ALLOW': selection = options[selected] - match = re.search('^#include\s+<(.+)>$', selection) + match = re_match_include(selection) #re.search('^#include\s+<(.+)>$', selection) if match: deleted = False - inc = match.groups()[0] + inc = match #.groups()[0] deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True @@ -1914,11 +1927,13 @@ def ask_the_question(): if not allow_mode & AA_MAY_EXEC: mode |= str_to_mode('ix') - # If we get an mmap request, check if we already have it in allow_mode - if mode & AA_EXEC_MMAP: - # ix implies m, so we don't need to add m if ix is present - if contains(allow_mode, 'ix'): - mode = mode & ~AA_EXEC_MMAP + # m is not implied by ix + + ### If we get an mmap request, check if we already have it in allow_mode + ##if mode & AA_EXEC_MMAP: + ## # ix implies m, so we don't need to add m if ix is present + ## if contains(allow_mode, 'ix'): + ## mode = mode & ~AA_EXEC_MMAP if not mode: continue @@ -1945,10 +1960,10 @@ def ask_the_question(): if cfg['settings']['custom_includes']: for incn in cfg['settings']['custom_includes'].split(): - if incn in incname: + if incn == incname: include_valid = True - if 'abstraction' in incname: + if incname.startswith('abstractions/'): include_valid = True if not include_valid: @@ -1968,7 +1983,7 @@ def ask_the_question(): # We should have literal the path in options list too options.append(path) # Add any the globs matching path from logprof - globs = globcommon(path) + globs = glob_common(path) if globs: matches += globs # Add any user entered matching globs @@ -2049,7 +2064,7 @@ def ask_the_question(): q['selected'] = default_option - 1 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_GLOB', 'CMD_GLOBTEXT', 'CMD_NEW', 'CMD_ABORT', - 'CMD_FINISHED', 'CMD_OTHER'] + 'CMD_FINISHED', 'CMD_OTHER', 'CMD_IGNORE_ENTRY'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' @@ -2058,6 +2073,10 @@ def ask_the_question(): ans, selected = UI_PromptUser(q) + if ans == 'CMD_IGNORE_ENTRY': + done = True + break + if ans == 'CMD_OTHER': audit_toggle, owner_toggle = UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode) elif ans == 'CMD_USER_TOGGLE': @@ -2069,9 +2088,9 @@ def ask_the_question(): elif ans == 'CMD_ALLOW': path = options[selected] done = True - match = re.search('^#include\s+<(.+)>$', path) + match = re_match_include(path) #.search('^#include\s+<(.+)>$', path) if match: - inc = match.gropus()[0] + inc = match #.gropus()[0] deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True @@ -2130,7 +2149,7 @@ def ask_the_question(): elif ans == 'CMD_NEW': arg = options[selected] - if not arg.startswith('#include'): + if not re_match_include(arg): ans = UI_GetString(gettext('Enter new path:'), arg) if ans: if not matchliteral(ans, path): @@ -2148,20 +2167,28 @@ def ask_the_question(): elif ans == 'CMD_GLOB': newpath = options[selected].strip() - if not newpath.startswith('#include'): + if not re_match_include(newpath): if newpath[-1] == '/': if newpath[-4:] == '/**/' or newpath[-3:] == '/*/': - # collapse one level to /**/ - newpath = re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) + # /foo/**/ and /foo/*/ => /**/ + newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) #re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) + # /foo**/ => /**/ + elif re.search('/[^/]+\*\*/$', newpath): + newpath = re.sub('/[^/]+\*\*/$', '/**/', newpath) else: - newpath = re.sub('/[^/]+/$', '/\*/', newpath) + newpath = re.sub('/[^/]+/$', '/*/', newpath) else: + # /foo/** and /foo/* => /** if newpath[-3:] == '/**' or newpath[-2:] == '/*': - newpath = re.sub('/[^/]+/\*{1,2}$', '/\*\*', newpath) + newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) + # /**foo => /** elif re.search('/\*\*[^/]+$', newpath): - newpath = re.sub('/\*\*[^/]+$', '/\*\*', newpath) + newpath = re.sub('/\*\*[^/]+$', '/**', newpath) + # /foo** => /** + elif re.search('/[^/]+\*\*$', newpath): + newpath = re.sub('/[^/]+\*\*$', '/**', newpath) else: - newpath = re.sub('/[^/]+$', '/\*', newpath) + newpath = re.sub('/[^/]+$', '/*', newpath) if newpath not in options: options.append(newpath) @@ -2169,13 +2196,20 @@ def ask_the_question(): elif ans == 'CMD_GLOBEXT': newpath = options[selected].strip() - if not newpath.startswith('#include'): - match = re.search('/\*{1,2}(\.[^/]+)$', newpath) + if not re_match_include(newpath): + # match /**.ext and /*.ext + match = re.search('/\*{1,2}(\.[^/]+)$', newpath) if match: - newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/\*\*'+match.group()[0], newpath) + # /foo/**.ext and /foo/*.ext => /**.ext + newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.group()[0], newpath) + # /foo**.ext => /**.ext + elif re.search('/[^/]+\*\*\.[^/]+$'): + match = re.search('/[^/]+\*\*(\.[^/]+)$') + newpath = re.sub('/[^/]+\*\*\.[^/]+$', '/**'+match.groups()[0], newpath) else: + # /foo.ext => /*.ext match = re.search('(\.[^/]+)$') - newpath = re.sub('/[^/]+(\.[^/]+)$', '/\*'+match.groups()[0], newpath) + newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) if newpath not in options: options.append(newpath) default_option = len(options) @@ -2186,12 +2220,12 @@ def ask_the_question(): # for family in sorted(log_dict[aamode][profile][hat]['netdomain'].keys()): # severity handling for net toggles goes here - for sock_type in sorted(log_dict[aaprofile][profile][hat]['netdomain'][family].keys()): + for sock_type in sorted(log_dict[profile][profile][hat]['netdomain'][family].keys()): if profile_known_network(aa[profile][hat], family, sock_type): continue default_option = 1 options = [] - newincludes = matchnetincludes(aa[profile][hat], family, sock_type) + newincludes = match_net_includes(aa[profile][hat], family, sock_type) q = hasher() if newincludes: options += map(lambda s: '#include <%s>'%s, sorted(set(newincludes))) @@ -2206,7 +2240,7 @@ def ask_the_question(): audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED'] + 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': @@ -2217,6 +2251,10 @@ def ask_the_question(): done = False while not done: ans, selected = UI_PromptUser(q) + if ans == 'CMD_IGNORE_ENTRY': + done = True + break + if ans.startswith('CMD_AUDIT'): audit_toggle = not audit_toggle audit = '' @@ -2234,8 +2272,8 @@ def ask_the_question(): elif ans == 'CMD_ALLOW': selection = options[selected] done = True - if re.search('#include\s+<.+>$', selection): - inc = re.search('#include\s+<(.+)>$', selection).groups()[0] + if re_match_include(selection): #re.search('#include\s+<.+>$', selection): + inc = re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0] deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) @@ -2264,3 +2302,678 @@ def ask_the_question(): else: done = False +def delete_net_duplicates(netrules, incnetrules): + deleted = 0 + if incnetrules and netrules: + incnetglob = False + # Delete matching rules from abstractions + if incnetrules.get('all', False): + incnetglob = True + for fam in netrules.keys(): + if incnetglob or (type(incnetrules['rule'][fam]) != dict and incnetrules['rule'][fam] == 1): + if type(netrules['rule'][hash]) == dict: + deleted += len(netrules['rule'][fam].keys()) + else: + deleted += 1 + netrules['rule'].pop(fam) + elif netrules['rule'][fam] != 'HASH' and netrules['rule'][fam] == 1: + continue + else: + for socket_type in netrules['rule'][fam].keys(): + if incnetrules['rule'].get(fam, False): + netrules[fam].pop(socket_type) + deleted += 1 + return deleted + +def delete_cap_duplicates(profilecaps, inccaps): + deleted = 0 + if profilecaps and inccaps: + for capname in profilecaps.keys(): + if inccaps[capname].get('set', False) == 1: + profilecaps.pop(capname) + deleted += 1 + return deleted + +def delete_path_duplicates(profile, incname, allow): + deleted = 0 + + for entry in profile[allow]['path'].keys(): + if entry == '#include <%s>'%incname: + continue + cm, am, m = match_include_to_path(incname, allow, entry) + if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']): + profile[allow]['path'].pop(entry) + deleted += 1 + + return deleted + +def delete_duplicates(profile, incname): + deleted = 0 + # Allow rules covered by denied rules shouldn't be deleted + # only a subset allow rules may actually be denied + deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) + + deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) + + deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']) + + deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) + + deleted += delete_path_duplicates(profile, incname, 'allow') + deleted += delete_path_duplicates(profile, incname, 'deny') + + return deleted + +def match_net_include(incname, family, type): + includelist = incname[:] + checked = [] + name = None + if includelist: + name = includelist.pop(0) + while name: + checked.append(name) + if netrules_access_check(include[name][name]['allow']['netdomain'], family, type): + return True + + if include[name][name]['include'].keys() and name not in checked: + includelist += include[name][name]['include'].keys() + + if len(includelist): + name = includelist.pop(0) + else: + name = False + +def match_cap_includes(profile, cap): + newincludes = [] + includevalid = False + for incname in include.keys(): + includevalid = False + if profile['include'].get(incname, False): + continue + + if cfg['settings']['custom_includes']: + for incm in cfg['settings']['custom_includes'].split(): + if incm in incname: + includevalid = True + + if 'abstractions' in incname: + includevalid = True + + if not includevalid: + continue + if include[incname][incname]['allow']['capability'][cap].get('set', False) == 1: + newincludes.append(incname) + + return newincludes + +def re_match_include(path): + """Matches the path for include and returns the include path""" + regex_include = re.compile('^\s*#?include\s*<(\.*)>') + match = regex_include.search(path) + if match: + return match.groups()[0] + else: + return None + +def match_net_includes(profile, family, nettype): + newincludes = [] + includevalid = False + for incname in include.keys(): + includevalid = False + + if profile['include'].get(incname, False): + continue + + if cfg['settings']['custom_includes']: + for incm in cfg['settings']['custom_includes'].split(): + if incm == incname: + includevalid = True + + if incname.startswith('abstractions/'): + includevalid = True + + if includevalid and match_net_include(incname, family, type): + newincludes.append(incname) + + return newincludes + +def do_logprof_pass(logmark=''): + # set up variables for this pass + t = hasher() + transitions = hasher() + seen = hasher() + aa = hasher() + profile_changes = hasher() + prelog = hasher() + log = [] + log_dict = hasher() + changed = dict() + skip = hasher() + filelist = hasher() + + UI_Info(gettext('Reading log entries from %s.') %filename) + UI_Info(gettext('Updating AppArmor profiles in %s.') %profile_dir) + + read_profiles() + + if not sev_db: + sev_db = apparmor.severity(CONFDIR + '/severity', gettext('unknown')) + + ##if not repo_cf and cfg['repostory']['url']: + ## repo_cfg = read_config('repository.conf') + ## if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']: + ## UI_ask_to_enable_repo() + + read_log(logmark) + + for root in log: + handle_children('', '', root) + + for pid in sorted(profile_changes.keys()): + set_process(pid, profile_changes[pid]) + + collapse_log() + + ask_the_questions() + + if UI_mode == 'yast': + # To-Do + pass + + finishing = False + # Check for finished + save_profiles() + + ##if not repo_cfg['repository'].get('upload', False) or repo['repository']['upload'] == 'later': + ## UI_ask_to_upload_profiles() + ##if repo_enabled(): + ## if repo_cgf['repository']['upload'] == 'yes': + ## sync_profiles() + ## created = [] + + # If user selects 'Finish' then we want to exit logprof + if finishing: + return 'FINISHED' + else: + return 'NORMAL' + + +def save_profiles(): + # Ensure the changed profiles are actual active profiles + for prof_name in changed.keys(): + if not is_active_profile(prof_name): + changed.pop(prof_name) + + changed_list = sorted(changed.keys()) + + if changed_list: + + if UI_mode == 'yast': + # To-Do + selected_profiles = [] + profile_changes = dict() + for prof in changed_list: + oldprofile = serialize_profile(original_aa[prof], prof) + newprofile = serialize_profile(aa[prof], prof) + profile_changes[prof] = get_profile_diff(oldprofile, newprofile) + explanation = gettext('Select which profile changes you would like to save to the\nlocal profile set.') + title = gettext('Local profile changes') + SendDataToYast({ + 'type': 'dialog-select-profiles', + 'title': title, + 'explanation': explanation, + 'dialog_select': 'true', + 'get_changelog': 'false', + 'profiles': profile_changes + }) + ypath, yarg = GetDataFromYast() + if yarg['STATUS'] == 'cancel': + return None + else: + selected_profiles_ref = yarg['PROFILES'] + for profile_name in selected_profiles_ref: + writeprofile_ui_feedback(profile_name) + reload_base(profile_name) + + else: + q = hasher() + q['title'] = 'Changed Local Profiles' + q['headers'] = [] + q['explanation'] = gettext('The following local profiles were changed. Would you like to save them?') + q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] + q['default'] = 'CMD_VIEW_CHANGES' + q['options'] = changed + q['selected'] = 0 + p =None + ans = '' + arg = None + while ans != 'CMD_SAVE_CHANGES': + ans, arg = UI_PromptUser(q) + if ans == 'CMD_VIEW_CHANGES': + which = changed[arg] + oldprofile = serialize_profile(original_aa[which], which) + newprofile = serialize_profile(aa[which], which) + + display_changes(oldprofile, newprofile) + + for profile_name in changed_list: + writeprofile_ui_feedback(profile_name) + reload_base(profile_name) + +def get_pager(): + pass + +def generate_diff(oldprofile, newprofile): + oldtemp = tempfile.NamedTemporaryFile('wr', delete=False) + + oldtemp.write(oldprofile) + oldtemp.flush() + + newtemp = tempfile.NamedTemporaryFile('wr', delete=False) + newtemp.write(newprofile) + newtemp.flush() + + difftemp = tempfile.NamedTemporaryFile('wr', deleted=False) + + subprocess.call('diff -u %s %s > %s' %(oldtemp.name, newtemp.name, difftemp.name), shell=True) + + oldtemp.delete = True + oldtemp.close() + newtemp.delete = True + newtemp.close() + return difftemp + +def get_profile_diff(oldprofile, newprofile): + difftemp = generate_diff(oldprofile, newprofile) + diff = [] + with open_file_read(difftemp.name) as f_in: + for line in f_in: + if not (line.startswith('---') and line .startswith('+++') and re.search('^\@\@.*\@\@$', line)): + diff.append(line) + + difftemp.delete = True + difftemp.close() + return ''.join(diff) + +def display_changes(oldprofile, newprofile): + if UI_mode == 'yast': + UI_LongMessage(gettext('Profile Changes'), get_profile_diff(oldprofile, newprofile)) + else: + difftemp = generate_diff(oldprofile, newprofile) + subprocess.call('less %s' %difftemp.name, shell=True) + difftemp.delete = True + difftemp.close() + +def set_process(pid, profile): + # If process running don't do anything + if os.path.exists('/proc/%s/attr/current' % pid): + return None + process = None + try: + process = open_file_read('/proc/%s/attr/current') + except IOError: + return None + current = process.readline().strip() + process.close() + + if not re.search('null(-complain)*-profile', current): + return None + + stats = None + try: + stats = open_file_read('/proc/%s/stat' % pid) + except IOError: + return None + stat = stats.readline().strip() + stats.close() + + match = re.search('^\d+ \((\S+)\) ', stat) + if not match: + return None + + try: + process = open_file_write('/proc/%s/attr/current' % pid) + except IOError: + return None + process.write('setprofile %s' % profile) + process.close() + +def collapse_log(): + for aamode in prelog.keys(): + for profile in prelog[aamode].keys(): + for hat in prelog[aamode][profile].keys(): + + for path in prelog[aamode][profile][hat]['path'].keys(): + mode = prelog[aamode][profile][hat]['path'][path] + + combinedmode = 0 + # Is path in original profile? + if aa[profile][hat]['allow']['path'].get(path, False): + combinedmode |= aa[profile][hat]['allow']['path'][path] + + # Match path to regexps in profile + combinedmode |= rematch_frag(aa[profile][hat], 'allow', path) + + # Match path from includes + combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path) + + if not combinedmode or not mode_contains(combinedmode, mode): + if log[aamode][profile][hat]['path'].get(path, False): + mode |= log[aamode][profile][hat]['path'][path] + + log[aamode][profile][hat]['path'][path] = mode + + for capability in prelog[aamode][profile][hat]['capability'].keys(): + # If capability not already in profile + if not aa[profile][hat]['allow']['capability'][capability].get('set', False): + log[aamode][profile][hat]['capability'][capability] = True + + nd = prelog[aamode][profile][hat]['netdomain'] + for family in nd.keys(): + for sock_type in nd[family].keys(): + if not profile_known_network(aa[profile][hat], family, sock_type): + log[aamode][profile][hat]['netdomain'][family][sock_type] = True + +def profilemode(mode): + pass + +def commonprefix(old, new): + # T0-Do + # Weird regex + pass + +def commonsuffix(old, new): + # To-Do + # Weird regex + pass + +def spilt_log_mode(mode): + user = '' + other = '' + match = re.search('(.*?)::(.*)', mode) + if match: + user, other = match.groups() + else: + user = mode + other = mode + + return user, other + +def map_log_mode(mode): + return mode + +def validate_profile_mode(mode, allow, nt_name=None): + if allow == 'deny': + pattern = '^(%s)+$' % PROFILE_MODE_DENY_RE.pattern + if re.search(pattern, mode): + return True + else: + return False + + elif nt_name: + pattern = '^(%s)+$' % PROFILE_MODE_NT_RE.pattern + if re.search(pattern, mode): + return True + else: + return False + + else: + pattern = '^(%s)+$' % PROFILE_MODE_RE.pattern + if re.search(pattern, mode): + return True + else: + return False + +def sub_str_to_mode(string): + mode = 0 + if not string: + return mode + while str: + pattern = '(%s)' % MODE_MAP_RE.pattern + tmp = re.search(pattern, string).groups()[0] + re.sub(pattern, '', string) + + if tmp and MODE_HASH.get(tmp, False): + mode |= MODE_HASH[tmp] + else: + pass + + return mode + +def print_mode(mode): + user, other = split_mode(mode) + string = sub_mode_to_str(user) + '::' + sub_mode_to_str(other) + + return string + +def str_to_mode(string): + if not string: + return 0 + user, other = split_log_mode(string) + + if not user: + user = other + + mode = sub_str_to_mode(user) + mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT) + + return mode + +def log_str_to_mode(profile, string, nt_name): + mode = str_to_mode(string) + # If contains nx and nix + if contains(mode, 'Nx'): + # Transform to px, cx + match = re.search('(.+?)//(.+?)', nt_name) + if match: + lprofile, lhat = match.groups() + tmode = 0 + + if lprofile == profile: + if mode & AA_MAY_EXEC: + tmode = str_to_mode('Cx::') + if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): + tmode |= str_to_mode('Cx') + nt_name = lhat + else: + if mode & AA_MAY_EXEC: + tmode = str_to_mode('Px::') + if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): + tmode |= str_to_mode('Px') + nt_name = lhat + + mode = mode & ~str_to_mode('Nx') + mode |= tmode + + return mode, nt_name + +def split_mode(mode): + user = mode & AA_USER_MASK + other = (mode >> AA_OTHER_SHIFT) & AA_USER_MASK + + return user, other + +def is_user_mode(mode): + user, other = split_mode(mode) + + if user and not other: + return True + else: + return False + +def sub_mode_to_str(mode): + string = '' + # w(write) implies a(append) + if mode & AA_MAY_WRITE: + mode &= (~AA_MAY_APPEND) + + if mode & AA_EXEC_MMAP: + string += 'm' + if mode & AA_MAY_READ: + string += 'r' + if mode & AA_MAY_WRITE: + string += 'w' + if mode & AA_MAY_APPEND: + string += 'a' + if mode & AA_MAY_LINK: + string += 'l' + if mode & AA_MAY_LOCK: + string += 'k' + + # modes P and C must appear before I and U else invalid syntax + if mode & (AA_EXEC_PROFILE | AA_EXEC_NT): + if mode & AA_EXEC_UNSAFE: + string += 'p' + else: + string += 'P' + + if mode & AA_EXEC_CHILD: + if mode & AA_EXEC_UNSAFE: + string += 'c' + else: + string += 'C' + + if mode & AA_EXEC_UNCONFINED: + if mode & AA_EXEC_UNSAFE: + string += 'u' + else: + string += 'U' + + if mode & AA_EXEC_INHERIT: + string += 'i' + + if mode & AA_MAY_EXEC: + string += 'x' + + return string + +def flatten_mode(mode): + if not mode: + return 0 + + mode = (mode & AA_USER_MASK) | ((mode >> AA_OTHER_SHIFT) & AA_USER_MASK) + mode |= (mode << AA_OTHER_SHIFT) + + return mode + +def mode_to_str(mode): + mode = flatten_mode(mode) + return sub_mode_to_str(mode) + +def owner_flatten_mode(mode): + mode = flatten_mode(mode) &AA_USER_MASK + return mode + +def mode_to_str_user(mode): + user, other = split_mode(mode) + string = '' + + if not user: + user = 0 + if not other: + other = 0 + + if user & ~other: + if other: + string = sub_mode_to_str(other) + '+' + string += 'owner ' + sub_mode_to_str(user & ~other) + + elif is_user_mode(mode): + string = 'owner ' + sub_mode_to_str(user) + else: + string = sub_mode_to_str(flatten_mode(mode)) + + return string + +def mode_contains(mode, subset): + # w implies a + if mode & AA_MAY_WRITE: + mode |= AA_MAY_APPEND + if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT): + mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT) + + # ix does not imply m + + ### ix implies m + ##if mode & AA_EXEC_INHERIT: + ## mode |= AA_EXEC_MMAP + ##if mode & (AA_EXEC_INHERIT << AA_OTHER_SHIFT): + ## mode |= (AA_EXEC_MMAP << AA_OTHER_SHIFT) + + return (mode & subset) == subset + +def contains(mode, string): + return mode_contains(mode, str_to_mode(string)) + +# rpm backup files, dotfiles, emacs backup files should not be processed +# The skippable files type needs be synced with apparmor initscript +def is_skippable_file(path): + """Returns True if filename matches something to be skipped""" + if (re.search('(^|/)\.[^/]*$', path) or re.search('\.rpm(save|new)$', path) + or re.search('\.dpkg-(old|new)$', path) or re.search('\.swp$', path) + or path[-1] == '~' or path == 'README'): + return True + +def is_skippable_dir(path): + if path in ['disable', 'cache', 'force-complain', 'lxc']: + return True + return False + +def check_include_syntax(errors): + # To-Do + pass + +def check_profile_syntax(errors): + # To-Do + pass + +def read_profiles(): + try: + os.listdir(profile_dir) + except : + fatal_error('Can\'t read AppArmor profiles in %s' % profile_dir) + + for file in os.listdir(profile_dir): + if os.path.isfile(profile_dir + '/' + file): + if is_skippable_file(file): + continue + else: + read_profile(profile_dir + '/' + file, True) + +def read_inactive_profiles(): + if not os.path.exists(extra_profile_dir): + return None + try: + os.listdir(profile_dir) + except : + fatal_error('Can\'t read AppArmor profiles in %s' % extra_profile_dir) + + for file in os.listdir(profile_dir): + if os.path.isfile(extra_profile_dir + '/' + file): + if is_skippable_file(file): + continue + else: + read_profile(extra_profile_dir + '/' + file, False) + +def read_profile(file, active_profile): + data = None + try: + with open_file_read(file) as f_in: + data = f_in.readlines() + except IOError: + debug_logger.debug('read_profile: can\'t read %s - skipping' %file) + return None + + profile_data = parse_profile_data(data, file, 0) + if profile_data and active_profile: + attach_profile_data(aa, profile_data) + attach_profile_data(original_aa, profile_data) + elif profile_data: + attach_profile_data(extras, profile_data) + + +def attach_profile_data(profiles, profile_data): + # Make deep copy of data to avoid changes to + # arising due to mutables + for p in profile_data.keys(): + profiles[p] = deepcopy(profile_data[p]) diff --git a/apparmor/ui.py b/apparmor/ui.py index f84f58233..fb4e5a84d 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -225,7 +225,8 @@ CMDS = { 'CMD_NET_FAMILY': 'Allow Network Fa(m)ily', 'CMD_OVERWRITE': '(O)verwrite Profile', 'CMD_KEEP': '(K)eep Profile', - 'CMD_CONTINUE': '(C)ontinue' + 'CMD_CONTINUE': '(C)ontinue', + 'CMD_IGNORE_ENTRY': '(I)gnore Entry' } def UI_PromptUser(q): diff --git a/apparmor/yasti.py b/apparmor/yasti.py index 22d6e12c3..a9232febf 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -81,9 +81,9 @@ def ParseCommand(commands): ycp.y2warning('Superfluous command arguments ignored') return (command, path, argument) -def ParseTerm(input): +def ParseTerm(inp): regex_term = re.compile('^\s*`?(\w*)\s*') - term = regex_term.search(input) + term = regex_term.search(inp) ret = [] symbol = None if term: @@ -91,10 +91,10 @@ def ParseTerm(input): else: ycp.y2error('No term symbol') ret.append(symbol) - input = regex_term.sub('', input) - if not input.startswith('('): + inp = regex_term.sub('', inp) + if not inp.startswith('('): ycp.y2error('No term parantheses') - argref, err, rest = ParseYcpTermBody(input) + argref, err, rest = ParseYcpTermBody(inp) if err: ycp.y2error('%s (%s)' % (err, rest)) else: From 1af5f1f03faa12f2a64c9d255e8924835b87efa3 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 28 Jul 2013 08:29:59 +0530 Subject: [PATCH 029/183] python regex fix --- apparmor/aa.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 3ee521dd2..6974160f0 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -451,7 +451,7 @@ def create_new_profile(localfile): if interpreter == 'perl': local_profile[localfile]['include']['abstractions/perl'] = True - elif re.search('python()', interpreter): + elif re.search('python([23]|[23]\.[0-9])?$', interpreter): local_profile[localfile]['include']['abstractions/python'] = True elif interpreter == 'ruby': local_profile[localfile]['include']['abstractions/ruby'] = True @@ -2090,7 +2090,7 @@ def ask_the_questions(): done = True match = re_match_include(path) #.search('^#include\s+<(.+)>$', path) if match: - inc = match #.gropus()[0] + inc = match #.groups()[0] deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True From 928e4503c6be0c25a51365776af6f3c768990f3f Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 30 Jul 2013 20:13:08 +0530 Subject: [PATCH 030/183] Intermediate: codebase update with broken tests --- Testing/aa_test.py | 88 ++++++ apparmor/aa.py | 681 ++++++++++++++++++++++++++++++++++++++++----- apparmor/ui.py | 4 +- apparmor/yasti.py | 2 +- 4 files changed, 696 insertions(+), 79 deletions(-) create mode 100644 Testing/aa_test.py diff --git a/Testing/aa_test.py b/Testing/aa_test.py new file mode 100644 index 000000000..fdefbf456 --- /dev/null +++ b/Testing/aa_test.py @@ -0,0 +1,88 @@ +''' +Created on Jul 29, 2013 + +@author: kshitij +''' +import unittest +import sys + +sys.path.append('../') +import apparmor.aa +#from apparmor.aa import parse_event + +class Test(unittest.TestCase): + + + def test_parse_event(self): + event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_balmar" name=2F686F6D652F7777772F62616C6D61722E646174616E6F766F322E64652F68747470646F63732F6A6F6F6D6C612F696D616765732F6B75656368656E2F666F746F20322E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' + parsed_event = apparmor.aa.parse_event(event) + print(parsed_event) + + event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0' + parsed_event = apparmor.aa.parse_event(event) + print(parsed_event) + + event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=74657374207370616365 name="/home/jj/.bash_history" pid=17011 comm="bash" requested_mask="w" denied_mask="w" fsuid=0 ouid=1000' + parsed_event = apparmor.aa.parse_event(event) + print(parsed_event) + + + def test_modes_to_string(self): + self.assertEqual(apparmor.aa.mode_to_str(32270), 'rwPCUx') + + MODE_TEST = {'x': apparmor.aa.AA_MAY_EXEC, + 'w': apparmor.aa.AA_MAY_WRITE, + 'r': apparmor.aa.AA_MAY_READ, + 'a': apparmor.aa.AA_MAY_APPEND, + 'l': apparmor.aa.AA_MAY_LINK, + 'k': apparmor.aa.AA_MAY_LOCK, + 'm': apparmor.aa.AA_EXEC_MMAP, + 'i': apparmor.aa.AA_EXEC_INHERIT, + 'u': apparmor.aa.AA_EXEC_UNCONFINED + apparmor.aa.AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'U': apparmor.aa.AA_EXEC_UNCONFINED, + 'p': apparmor.aa.AA_EXEC_PROFILE + apparmor.aa.AA_EXEC_UNSAFE, # Profile + unsafe + 'P': apparmor.aa.AA_EXEC_PROFILE, + 'c': apparmor.aa.AA_EXEC_CHILD + apparmor.aa.AA_EXEC_UNSAFE, # Child + Unsafe + 'C': apparmor.aa.AA_EXEC_CHILD, + #'n': AA_EXEC_NT + AA_EXEC_UNSAFE, + #'N': AA_EXEC_NT + } + + while MODE_TEST: + string,mode = MODE_TEST.popitem() + self.assertEqual(apparmor.aa.mode_to_str(mode), string) + + mode = 2048 + self.assertEqual(apparmor.aa.mode_to_str(mode), 'C') + + def test_string_to_modes(self): + + #self.assertEqual(apparmor.aa.str_to_mode('wc'), 32270) + + MODE_TEST = {'x': apparmor.aa.AA_MAY_EXEC, + 'w': apparmor.aa.AA_MAY_WRITE, + 'r': apparmor.aa.AA_MAY_READ, + 'a': apparmor.aa.AA_MAY_APPEND, + 'l': apparmor.aa.AA_MAY_LINK, + 'k': apparmor.aa.AA_MAY_LOCK, + 'm': apparmor.aa.AA_EXEC_MMAP, + 'i': apparmor.aa.AA_EXEC_INHERIT, + 'u': apparmor.aa.AA_EXEC_UNCONFINED + apparmor.aa.AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'U': apparmor.aa.AA_EXEC_UNCONFINED, + 'p': apparmor.aa.AA_EXEC_PROFILE + apparmor.aa.AA_EXEC_UNSAFE, # Profile + unsafe + 'P': apparmor.aa.AA_EXEC_PROFILE, + 'c': apparmor.aa.AA_EXEC_CHILD + apparmor.aa.AA_EXEC_UNSAFE, # Child + Unsafe + 'C': apparmor.aa.AA_EXEC_CHILD, + #'n': AA_EXEC_NT + AA_EXEC_UNSAFE, + #'N': AA_EXEC_NT + } + + #while MODE_TEST: + # string,mode = MODE_TEST.popitem() + # self.assertEqual(apparmor.aa.str_to_mode(string), mode) + + #self.assertEqual(apparmor.aa.str_to_mode('C'), 2048) + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] + unittest.main() \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index 6974160f0..294553f0f 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,4 +1,4 @@ -#4925 +#5546 #382-430 #480-525 # No old version logs, only 2.6 + supported @@ -451,7 +451,7 @@ def create_new_profile(localfile): if interpreter == 'perl': local_profile[localfile]['include']['abstractions/perl'] = True - elif re.search('python([23]|[23]\.[0-9])?$', interpreter): + elif re.search('^python([23]|[23]\.[0-9]+)?$', interpreter): local_profile[localfile]['include']['abstractions/python'] = True elif interpreter == 'ruby': local_profile[localfile]['include']['abstractions/ruby'] = True @@ -1631,10 +1631,8 @@ def read_log(logmark): def parse_event(msg): """Parse the event from log into key value pairs""" msg = msg.strip() - debug_logger.log('parse_event: %s' % msg) + #debug_logger.log('parse_event: %s' % msg) event = LibAppArmor.parse_record(msg) - rmask = None - dmask = None ev = dict() ev['resource'] = event.info ev['active_hat'] = event.active_hat @@ -1668,8 +1666,9 @@ def parse_event(msg): dmask = dmask.replace('d', 'w') if not validate_log_mode(hide_log_mode(dmask)): fatal_error(gettext('Log contains unknown mode %s') % dmask) - + #print('parse_event:', ev['profile'], dmask, ev['name2']) mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2']) + ev['denied_mask'] = mask ev['name2'] = name @@ -1678,11 +1677,12 @@ def parse_event(msg): ev['name2'] = name if not ev['time']: - ev['time'] = int(time.time) + ev['time'] = int(time.time()) # Remove None keys #for key in ev.keys(): # if not ev[key] or not re.search('[\w]+', ev[key]): # ev.pop(key) + if ev['aamode']: # Convert aamode values to their counter-parts mode_convertor = { @@ -1695,12 +1695,12 @@ def parse_event(msg): 6: 'STATUS' } try: - ev['aamode'] = mode_convertor(ev['aamode']) + ev['aamode'] = mode_convertor[ev['aamode']] except KeyError: ev['aamode'] = None if ev['aamode']: - debug_logger.debug(ev) + #debug_logger.debug(ev) return ev else: return None @@ -1963,7 +1963,7 @@ def ask_the_questions(): if incn == incname: include_valid = True - if incname.startswith('abstractions/'): + if valid_abstraction(incname): include_valid = True if not include_valid: @@ -2172,20 +2172,23 @@ def ask_the_questions(): if newpath[-4:] == '/**/' or newpath[-3:] == '/*/': # /foo/**/ and /foo/*/ => /**/ newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) #re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) - # /foo**/ => /**/ - elif re.search('/[^/]+\*\*/$', newpath): - newpath = re.sub('/[^/]+\*\*/$', '/**/', newpath) + elif re.search('/[^/]+\*\*[^/]*/$', newpath): + # /foo**/ and /foo**bar/ => /**/ + newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath) + elif re.search('/\*\*[^/]+/$', newpath): + # /**bar/ => /**/ + newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath) else: newpath = re.sub('/[^/]+/$', '/*/', newpath) - else: - # /foo/** and /foo/* => /** + else: if newpath[-3:] == '/**' or newpath[-2:] == '/*': - newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) - # /**foo => /** - elif re.search('/\*\*[^/]+$', newpath): - newpath = re.sub('/\*\*[^/]+$', '/**', newpath) - # /foo** => /** + # /foo/** and /foo/* => /** + newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) + elif re.search('/[^/]*\*\*[^/]+$', newpath): + # /**foo and /foor**bar => /** + newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath) elif re.search('/[^/]+\*\*$', newpath): + # /foo** => /** newpath = re.sub('/[^/]+\*\*$', '/**', newpath) else: newpath = re.sub('/[^/]+$', '/*', newpath) @@ -2198,18 +2201,22 @@ def ask_the_questions(): newpath = options[selected].strip() if not re_match_include(newpath): # match /**.ext and /*.ext - match = re.search('/\*{1,2}(\.[^/]+)$', newpath) + match = re.search('/\*{1,2}(\.[^/]+)$', newpath) if match: # /foo/**.ext and /foo/*.ext => /**.ext newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.group()[0], newpath) - # /foo**.ext => /**.ext - elif re.search('/[^/]+\*\*\.[^/]+$'): - match = re.search('/[^/]+\*\*(\.[^/]+)$') - newpath = re.sub('/[^/]+\*\*\.[^/]+$', '/**'+match.groups()[0], newpath) + elif re.search('/[^/]+\*\*[^/]*\.[^/]+$'): + # /foo**.ext and /foo**bar.ext => /**.ext + match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$') + newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**'+match.groups()[0], newpath) + elif re.search('/\*\*[^/]+\.[^/]+$'): + # /**foo.ext => /**.ext + match = re.search('/\*\*[^/]+(\.[^/]+)$') + newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**'+match.groups()[0], newpath) else: - # /foo.ext => /*.ext match = re.search('(\.[^/]+)$') newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) + if newpath not in options: options.append(newpath) default_option = len(options) @@ -2382,57 +2389,45 @@ def match_net_include(incname, family, type): name = includelist.pop(0) else: name = False + + return False def match_cap_includes(profile, cap): newincludes = [] - includevalid = False for incname in include.keys(): - includevalid = False - if profile['include'].get(incname, False): - continue - - if cfg['settings']['custom_includes']: - for incm in cfg['settings']['custom_includes'].split(): - if incm in incname: - includevalid = True - - if 'abstractions' in incname: - includevalid = True - - if not includevalid: - continue - if include[incname][incname]['allow']['capability'][cap].get('set', False) == 1: + if valid_include(profile, incname) and include[incname][incname]['allow']['capability'][cap].get('set', False) == 1: newincludes.append(incname) return newincludes def re_match_include(path): """Matches the path for include and returns the include path""" - regex_include = re.compile('^\s*#?include\s*<(\.*)>') + regex_include = re.compile('^\s*#?include\s*<(\.*)\s*(#.*)?$>') match = regex_include.search(path) if match: return match.groups()[0] else: return None +def valid_include(profile, incname): + if profile['include'].get(incname, False): + return False + + if cfg['settings']['custom_includes']: + for incm in cfg['settings']['custom_includes'].split(): + if incm == incname: + return True + + if incname.startswith('abstractions/') and os.path.isfile(profile_dir + '/' + incname): + return True + + return False + def match_net_includes(profile, family, nettype): newincludes = [] - includevalid = False for incname in include.keys(): - includevalid = False - if profile['include'].get(incname, False): - continue - - if cfg['settings']['custom_includes']: - for incm in cfg['settings']['custom_includes'].split(): - if incm == incname: - includevalid = True - - if incname.startswith('abstractions/'): - includevalid = True - - if includevalid and match_net_include(incname, family, type): + if valid_include(profile, incname) and match_net_include(incname, family, type): newincludes.append(incname) return newincludes @@ -2564,22 +2559,20 @@ def get_pager(): pass def generate_diff(oldprofile, newprofile): - oldtemp = tempfile.NamedTemporaryFile('wr', delete=False) + oldtemp = tempfile.NamedTemporaryFile('wr') oldtemp.write(oldprofile) oldtemp.flush() - newtemp = tempfile.NamedTemporaryFile('wr', delete=False) + newtemp = tempfile.NamedTemporaryFile('wr') newtemp.write(newprofile) newtemp.flush() - difftemp = tempfile.NamedTemporaryFile('wr', deleted=False) + difftemp = tempfile.NamedTemporaryFile('wr', delete=False) - subprocess.call('diff -u %s %s > %s' %(oldtemp.name, newtemp.name, difftemp.name), shell=True) + subprocess.call('diff -u -p %s %s > %s' %(oldtemp.name, newtemp.name, difftemp.name), shell=True) - oldtemp.delete = True oldtemp.close() - newtemp.delete = True newtemp.close() return difftemp @@ -2588,7 +2581,7 @@ def get_profile_diff(oldprofile, newprofile): diff = [] with open_file_read(difftemp.name) as f_in: for line in f_in: - if not (line.startswith('---') and line .startswith('+++') and re.search('^\@\@.*\@\@$', line)): + if not (line.startswith('---') and line .startswith('+++') and line.startswith('@@')): diff.append(line) difftemp.delete = True @@ -2605,18 +2598,19 @@ def display_changes(oldprofile, newprofile): difftemp.close() def set_process(pid, profile): - # If process running don't do anything - if os.path.exists('/proc/%s/attr/current' % pid): + # If process not running don't do anything + if not os.path.exists('/proc/%s/attr/current' % pid): return None + process = None try: - process = open_file_read('/proc/%s/attr/current') + process = open_file_read('/proc/%s/attr/current' % pid) except IOError: return None current = process.readline().strip() process.close() - if not re.search('null(-complain)*-profile', current): + if not re.search('^null(-complain)*-profile$', current): return None stats = None @@ -2687,7 +2681,7 @@ def commonsuffix(old, new): # Weird regex pass -def spilt_log_mode(mode): +def split_log_mode(mode): user = '' other = '' match = re.search('(.*?)::(.*)', mode) @@ -2696,7 +2690,7 @@ def spilt_log_mode(mode): else: user = mode other = mode - + #print ('split_logmode:', user, mode) return user, other def map_log_mode(mode): @@ -2728,11 +2722,12 @@ def sub_str_to_mode(string): mode = 0 if not string: return mode - while str: + while string: pattern = '(%s)' % MODE_MAP_RE.pattern - tmp = re.search(pattern, string).groups()[0] - re.sub(pattern, '', string) - + tmp = re.search(pattern, string) + if tmp: + tmp = tmp.groups()[0] + string = re.sub(pattern, '', string) if tmp and MODE_HASH.get(tmp, False): mode |= MODE_HASH[tmp] else: @@ -2753,15 +2748,19 @@ def str_to_mode(string): if not user: user = other - + mode = sub_str_to_mode(user) + #print(string, mode) + #print(string, 'other', sub_str_to_mode(other)) mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT) - + #print (string, mode) + #print('str_to_mode:', mode) return mode def log_str_to_mode(profile, string, nt_name): mode = str_to_mode(string) # If contains nx and nix + #print (profile, string, nt_name) if contains(mode, 'Nx'): # Transform to px, cx match = re.search('(.+?)//(.+?)', nt_name) @@ -2977,3 +2976,533 @@ def attach_profile_data(profiles, profile_data): # arising due to mutables for p in profile_data.keys(): profiles[p] = deepcopy(profile_data[p]) + +def parse_profile_data(data, file, do_include): + profile_data = hasher() + profile = None + hat = None + in_contained_hat = None + repo_data = None + parsed_profiles = [] + initial_comment = '' + RE_PROFILE_START = re.compile('^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') + RE_PROFILE_END = re.compile('^\s*\}\s*(#.*)?$') + RE_PROFILE_CAP = re.compile('^\s*(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') + RE_PROFILE_SET_CAP = re.compile('^\s*set capability\s+(\S+)\s*,\s*(#.*)?$') + RE_PROFILE_LINK = re.compile('^\s*(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') + RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$') + RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') + RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$') + RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$') + RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\+?=\s*(.+?)\s*,?\s*(#.*)?$') + RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_PATH_ENTRY = re.compile('^\s*(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') + RE_PROFILE_NETWORK = re.compile('^\s*(audit\s+)?(deny\s+)?network(.*)\s*(#.*)?$') + RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$') + RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') + if do_include: + profile = file + hat = file + + for lineno, line in enumerate(data): + line = line.strip() + if not line: + continue + # Starting line of a profile + if RE_PROFILE_START.search(line): + matches = RE_PROFILE_START.search(line).groups() + + if profile: + if profile != hat or matches[3]: + raise AppArmorException('%s profile in %s contains syntax errors in line: %s.\n' % (profile, file, lineno+1)) + # Keep track of the start of a profile + if profile and profile == hat and matches[3]: + # local profile + hat = matches[3] + in_contained_hat = True + profile_data[profile][hat]['profile'] = True + else: + if matches[1]: + profile = matches[1] + else: + profile = matches[3] + profile, hat = profile.split('//')[:2] + in_contained_hat = False + if hat: + profile_data[profile][hat]['external'] = True + else: + hat = profile + + flags = matches[6] + + profile = strip_quotes(profile) + if hat: + hat = strip_quotes(hat) + # save profile name and filename + profile_data[profile][hat]['name'] = profile + profile_data[profile][hat]['filename'] = file + filelist[file]['profiles'][profile][hat] = True + + profile_data[profile][hat]['flags'] = flags + + profile_data[profile][hat]['allow']['netdomain'] = hasher() + profile_data[profile][hat]['allow']['path'] = hasher() + # Save the initial comment + if initial_comment: + profile_data[profile][hat]['initial_comment'] = initial_comment + + initial_comment = '' + + if repo_data: + profile_data[profile][profile]['repo']['url'] = repo_data['url'] + profile_data[profile][profile]['repo']['user'] = repo_data['user'] + + elif RE_PROFILE_END.search(line): + # If profile ends and we're not in one + if not profile: + raise AppArmorException('Syntax Error: Unexpected End of Profile reached in file: %s line: %s' % (file, lineno+1)) + + if in_contained_hat: + hat = profile + in_contained_hat = False + else: + parsed_profiles.append(profile) + profile = None + + initial_comment = '' + + elif RE_PROFILE_CAP.search(line): + matches = RE_PROFILE_CAP.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected capability entry found in file: %s line: %s' % (file, lineno+1)) + + audit = False + if matches[0]: + audit = True + + allow = 'allow' + if matches[1]: + allow = 'deny' + + capability = matches[2] + + profile_data[profile][hat][allow]['capability'][capability]['set'] = True + profile_data[profile][hat][allow]['capability'][capability]['audit'] = audit + + elif RE_PROFILE_SET_CAP.search(line): + matches = RE_PROFILE_SET_CAP.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected capability entry found in file: %s line: %s' % (file, lineno+1)) + + capability = matches[0] + profile_data[profile][hat]['set_capability'][capability] = True + + elif RE_PROFILE_LINK.ssearch(line): + matches = RE_PROFILE_LINK.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected link entry found in file: %s line: %s' % (file, lineno+1)) + + audit = False + if matches[0]: + audit = True + + allow = 'allow' + if matches[1]: + allow = 'deny' + + subset = matches[3] + link = strip_quotes(matches[6]) + value = strip_quotes(matches[7]) + profile_data[profile][hat][allow]['link'][link]['to'] = value + profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', 0) | AA_MAY_LINK + + if subset: + profile_data[profile][hat][allow]['link'][link]['mode'] |= AA_LINK_SUBSET + + if audit: + profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', 0) | AA_LINK_SUBSET + else: + profile_data[profile][hat][allow]['link'][link]['audit'] = 0 + + elif RE_PROFILE_CHANGE_PROFILE.search(line): + matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected change profile entry found in file: %s line: %s' % (file, lineno+1)) + + cp = strip_quotes(matches[0]) + profile_data[profile][hat]['changes_profile'][cp] = True + + elif RE_PROFILE_ALIAS.search(line): + matches = RE_PROFILE_ALIAS.search(line).groups() + + from_name = strip_quotes(matches[0]) + to_name = strip_quotes(matches[1]) + + if profile: + profile_data[profile][hat]['alias'][from_name] = to_name + else: + if not filelist.get(file, False): + filelist[file] = hasher() + filelist[file]['alias'][from_name] = to_name + + elif RE_PROFILE_RLIMIT.search(line): + matches = RE_PROFILE_RLIMIT.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected rlimit entry found in file: %s line: %s' % (file, lineno+1)) + + from_name = matches[0] + to_name = matches[1] + + profile_data[profile][hat]['rlimit'][from_name] = to_name + + elif RE_PROFILE_BOOLEAN.search(line, flags=re.IGNORECASE): + matches = RE_PROFILE_BOOLEAN.search(line, flags=re.IGNORECASE) + + if not profile: + raise AppArmorException('Syntax Error: Unexpected boolean definition found in file: %s line: %s' % (file, lineno+1)) + + bool_var = matches[0] + value = matches[1] + + profile_data[profile][hat]['lvar'][bool_var] = value + + elif RE_PROFILE_VARIABLE.search(line): + # variable additions += and = + matches = RE_PROFILE_VARIABLE.search(line) + + list_var = strip_quotes(matches[0]) + value = strip_quotes(matches[1]) + + if profile: + if not profile_data[profile][hat].get('lvar', False): + profile_data[profile][hat]['lvar'][list_var] = [] + store_list_var(profile_data[profile]['lvar'], list_var, value) + else: + if not filelist[file].get('lvar', False): + filelist[file]['lvar'][list_var] = [] + store_list_var(filelist[file]['lvar'], list_var, value) + + elif RE_PROFILE_CONDITIONAL.search(line): + # Conditional Boolean + pass + + elif RE_PROFILE_CONDITIONAL_VARIABLE.search(line): + # Conditional Variable defines + pass + + elif RE_PROFILE_CONDITIONAL_BOOLEAN.search(line): + # Conditional Boolean defined + pass + + elif RE_PROFILE_PATH_ENTRY.search(line): + matches = RE_PROFILE_PATH_ENTRY.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected path entry found in file: %s line: %s' % (file, lineno+1)) + + audit = False + if matches[0]: + audit = True + + allow = 'allow' + if matches[1]: + allow = 'deny' + + user = False + if matches[2]: + user = True + + path = matches[3].strip() + mode = matches[4] + nt_name = matches[6] + if nt_name: + nt_name = nt_name.strip() + + p_re = convert_regexp(path) + try: + re.compile(p_re) + except: + raise AppArmorException('Syntax Error: Invalid Regex %s in file: %s line: %s' % (path, file, lineno+1)) + + if not validate_profile_mode(mode, allow, nt_name): + raise AppArmorException('Invalid mode %s in file: %s line: %s' % (mode, file, lineno+1)) + + tmpmode = None + if user: + tmpmode = str_to_mode('%s::' % mode) + else: + tmpmode = str_to_mode(mode) + + profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', 0) | tmpmode + + if nt_name: + profile_data[profile][hat][allow]['path'][path]['to'] = nt_name + + if audit: + profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit') | tmpmode + else: + profile_data[profile][hat][allow]['path'][path]['audit'] = 0 + + elif re_match_include(line): + # Include files + include = re_match_include(line) + + if profile: + profile_data[profile][hat]['include'][include] = True + else: + if not filelist.get(file): + filelist[file] = hasher() + filelist[file]['include'][include] = True + # If include is a directory + if os.path.isdir(profile_dir + '/' + include): + for path in os.listdir(profile_dir + '/' + include): + path = path.strip() + if is_skippable_file(path): + continue + if os.path.isfile(profile_dir + '/' + include + '/' + path): + file_name = include + '/' + path + load_include(file_name) + else: + load_include(include) + + elif RE_PROFILE_NETWORK.search(line): + matches = RE_PROFILE_NETWORK.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected network entry found in file: %s line: %s' % (file, lineno+1)) + + audit = False + if matches[0]: + audit = True + allow = 'allow' + if matches[1]: + allow = 'deny' + network = matches[2] + + if re.search('\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$', network): + nmatch = re.search(network, '\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$').groups() + fam, typ = nmatch[:2] + profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = True + profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit + elif re.search('\s+(\S+)\s*,\s*(#.*)?$', network): + fam = re.search('\s+(\S+)\s*,\s*(#.*)?$', network).groups()[0] + profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True + profile_data[profile][hat][allow]['netdomain']['audit'][fam] = audit + else: + profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True + profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True + + elif RE_PROFILE_CHANGE_HAT.search(line): + matches = RE_PROFILE_CHANGE_HAT.search(line).groups() + + if not profile: + raise AppArmorException('Syntax Error: Unexpected change hat declaration found in file: %s line: %s' % (file, lineno+1)) + + hat = matches[0] + hat = strip_quotes(hat) + + if not profile_data[profile][hat].get('declared', False): + profile_data[profile][hat]['declared'] = True + + elif RE_PROFILE_HAT_DEF.search(line): + # An embedded hat syntax definition starts + matches = RE_PROFILE_HAT_DEF.search(line).groups() + if not profile: + raise AppArmorException('Syntax Error: Unexpected hat definition found in file: %s line: %s' % (file, lineno+1)) + + in_contained_hat = True + hat = matches[0] + hat = strip_quotes(hat) + flags = matches[3] + + profile_data[profile][hat]['flags'] = flags + profile_data[profile][hat]['declared'] = False + #profile_data[profile][hat]['allow']['path'] = hasher() + #profile_data[profile][hat]['allow']['netdomain'] = hasher() + + if initial_comment: + profile_data[profile][hat]['initial_comment'] = initial_comment + initial_comment = '' + + filelist[file]['profiles'][profile][hat] = True + + elif line[0] == '#': + # Handle initial comments + if not profile: + if line.startswith('# vim:syntax') or line.startswith('# Last Modified:'): + continue + line = line.split() + if line[1] == 'REPOSITORY:': + if len(line) == 3: + repo_data = {'neversubmit': True} + elif len(line) == 5: + repo_data = {'url': line[2], + 'user': line[3], + 'id': line[4]} + else: + initial_comment = line + '\n' + + else: + raise AppArmorException('Syntax Error: Unknown line found in file: %s line: %s' % (file, lineno+1)) + + # Below is not required I'd say + if not do_include: + for hatglob in cfg['required_hats'].keys(): + for parsed_prof in sorted(parsed_profiles): + if re.search(hatglob, parsed_prof): + for hat in cfg['required_hats'][hatglob].split(): + if not profile_data[parsed_prof].get(hat, False): + profile_data[parsed_prof][hat] = hasher() + + # End of file reached but we're stuck in a profile + if profile and not do_include: + raise AppArmorException('Syntax Error: Reached end of file %s while inside profile %s' % (file, profile)) + + return profile_data + +def separate_vars(vs): + data = [] + RE_VARS = re.compile('\s*((\".+?\")|([^\"]\S+))\s*(.*)$') + while RE_VARS.search(vs): + matches = RE_VARS.search(vs).groups() + data.append(strip_quotes(matches[0])) + vs = matches[3] + + return data + +def is_active_profile(pname): + if aa.get(pname, False): + return True + else: + return False + +def store_list_var(var, list_var, value): + vlist = separate_vars(value) + if var.get(list_var): + vlist += var[list_var] #vlist = (vlist, var[list_var]) + + vlist = list(set(vlist)) + var[list_var] = vlist + +def strip_quotes(data): + if data[0]+data[-1] == '""': + return data[1:-1] + +def quote_if_needed(data): + # quote data if it contains whitespace + if ' ' in data: + data = '"' + data + '"' + return data + +def escape(escape): + escape = strip_quotes(escape) + escape = re.sub('((?') + +def write_change_profile(prof_data, depth): + return write_single(prof_data, depth, '', 'change_profile', 'change_profile -> ', ',') + +def write_alias(prof_data, depth): + return write_pair(prof_data, depth, '', 'alias', 'alias ', ' -> ', ',', quote_if_needed) + +def write_rlimits(prof_data, depth): + return write_pair(prof_data, depth, '', 'rlimit', 'set rlimit ', ' <= ', ',', quote_if_needed) + +def var_transform(ref): + data = [] + for value in ref: + data.append(quote_if_needed(value)) + return ' '.join(data) + +def write_list_vars(prof_data, depth): + return write_pair(prof_data, depth, '', 'lvar', '', ' = ', '', var_transform) + +def write_cap_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + allowstr = '' + if allow == 'deny': + allowstr = 'deny' + + if prof_data[allow].get('capability', False): + for cap in sorted(prof_data[allow]['capability'].keys()): + audit = '' + if prof_data[allow]['capability'][cap].get('audit', False): + audit = 'audit' + if prof_data[allow]['capability'][cap].get('set', False): + data.append(pre + audit + allowstr + 'capability,') + data.append('') + + return data + +def write_capabilities(prof_data, depth): + data = write_single(prof_data, depth, '', 'set_capability', 'set capability ', ',') + data += write_cap_rules(prof_data, depth, 'deny') + data += write_cap_rules(prof_data, depth, 'allow') + return data + diff --git a/apparmor/ui.py b/apparmor/ui.py index fb4e5a84d..f065f567f 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -4,7 +4,7 @@ import gettext import locale import logging import os -from yasti import yastLog, SendDataToYast, GetDataFromYast +from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast from apparmor.common import readkey @@ -226,7 +226,7 @@ CMDS = { 'CMD_OVERWRITE': '(O)verwrite Profile', 'CMD_KEEP': '(K)eep Profile', 'CMD_CONTINUE': '(C)ontinue', - 'CMD_IGNORE_ENTRY': '(I)gnore Entry' + 'CMD_IGNORE_ENTRY': '(I)gnore' } def UI_PromptUser(q): diff --git a/apparmor/yasti.py b/apparmor/yasti.py index a9232febf..8eed768cf 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -1,5 +1,5 @@ import re -import ycp +#import ycp import os import sys import logging From 8f378e3ce240628c450ec7b437187b1d689605ce Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Wed, 31 Jul 2013 19:56:33 +0530 Subject: [PATCH 031/183] Intermediate codebase update and the test cases are still broken --- apparmor/aa.py | 686 +++++++++++++++++++++++++++++++++++++++++----- apparmor/ui.py | 160 ++++++++++- apparmor/yasti.py | 2 +- 3 files changed, 772 insertions(+), 76 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 294553f0f..80719560f 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,6 +1,7 @@ -#5546 +#6585 #382-430 #480-525 +#6414-6472 # No old version logs, only 2.6 + supported #global variable names corruption from __future__ import with_statement @@ -9,6 +10,7 @@ import logging import os import re import shutil +import stat import subprocess import sys import time @@ -148,8 +150,6 @@ OPERATION_TYPES = { 'sock_shutdown': 'net' } -ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'} - def on_exit(): """Shutdowns the logger and records exit if debugging enabled""" if DEBUGGING: @@ -164,16 +164,6 @@ def op_type(operation): operation_type = OPERATION_TYPES.get(operation, 'unknown') return operation_type -def getkey(): - key = readkey() - if key == '\x1B': - key = readkey() - if key == '[': - key = readkey() - if(ARROWS.get(key, False)): - key = ARROWS[key] - return key - def check_for_LD_XXX(file): """Returns True if specified program contains references to LD_PRELOAD or LD_LIBRARY_PATH to give the Px/Ux code better suggestions""" @@ -484,7 +474,16 @@ def delete_profile(local_prof): if aa.get(local_prof, False): aa.pop(local_prof) - prof_unload(local_prof) + #prof_unload(local_prof) + +def confirm_and_abort(): + ans = UI_YesNo(gettext('Are you sure you want to abandon this set of profile changes and exit?'), 'n') + if ans == 'y': + UI_Info(gettext('Abandoning all changes.')) + shutdown_yast() + for prof in created: + delete_profile(prof) + sys.exit(0) def get_profile(prof_name): profile_data = None @@ -583,7 +582,8 @@ def autodep(bin_name, pname=''): if not bin_name and pname.startswith('/'): bin_name = pname if not repo_cfg and not cfg['repository'].get('url', False): - repo_cfg = read_config('repository.conf') + repo_conf = apparmor.config.Config('shell') + repo_cfg = repo_conf.read_config('repository.conf') if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later': UI_ask_to_enable_repo() if bin_name: @@ -596,7 +596,7 @@ def autodep(bin_name, pname=''): if not bin_full: return None pname = bin_full - read_inactive_profile() + read_inactive_profiles() profile_data = get_profile(pname) # Create a new profile if no existing profile if not profile_data: @@ -718,7 +718,19 @@ def sync_profile(): submit_changed_profiles(changed_profiles) if new_profiles: submit_created_profiles(new_profiles) - + +def fetch_profile_by_id(url, id): + #To-Do + return None, None + +def fetch_profiles_by_name(url, distro, user): + #to-Do + return None, None + +def fetch_profiles_by_user(url, distro, user): + #to-Do + return None, None + def submit_created_profiles(new_profiles): #url = cfg['repository']['url'] if new_profiles: @@ -838,23 +850,11 @@ def console_select_and_upload_profiles(title, message, profiles_up): 'information is required to upload profiles to the repository.\n' + 'These changes could not be sent.\n') -def set_profile_local_only(profs): +def set_profiles_local_only(profs): for p in profs: aa[profs][profs]['repo']['neversubmit'] = True - writeback_ui_feedback(profs) + write_profile_ui_feedback(profs) -def confirm_and_abort(): - ans = UI_YesNo('Are you sure you want to abandon this set of profile changes and exit?', 'n') - if ans == 'y': - UI_Info('Abandoning all changes.') - shutdown_yast() - for prof in created: - delete_profile(prof) - sys.exit(0) - -def confirm_and_finish(): - sys.stdout.write('Finishing\n') - sys.exit(0) def build_x_functions(default, options, exec_toggle): ret_list = [] @@ -1066,7 +1066,7 @@ def handle_children(profile, hat, root): combinedaudit = False ## Check return Value Consistency # Check if path matches any existing regexps in profile - cm, am , m = rematch_frag(aa[profile][hat], 'allow', exec_target) + cm, am , m = rematchfrag(aa[profile][hat], 'allow', exec_target) if cm: combinedmode |= cm if am: @@ -1245,7 +1245,7 @@ def handle_children(profile, hat, root): exec_mode = str_to_mode('ix') elif regex_optmode.search(ans): match = regex_optmode.search(ans).groups()[0] - exec_mode = str_to_match(match) + exec_mode = str_to_mode(match) px_default = 'n' px_msg = gettext('Should AppArmor sanitise the environment when\n' + 'switching profiles?\n\n' + @@ -1312,10 +1312,10 @@ def handle_children(profile, hat, root): changed[profile] = True if exec_mode & str_to_mode('i'): - if 'perl' in exec_target: - aa[profile][hat]['include']['abstractions/perl'] = True - elif '/bin/bash' in exec_path or '/bin/sh' in exec_path: - aa[profile][hat]['include']['abstractions/bash'] = True + #if 'perl' in exec_target: + # aa[profile][hat]['include']['abstractions/perl'] = True + #elif '/bin/bash' in exec_target or '/bin/sh' in exec_target: + # aa[profile][hat]['include']['abstractions/bash'] = True hashbang = head(exec_target) if hashbang.startswith('#!'): interpreter = hashbang[2:].strip() @@ -1355,9 +1355,9 @@ def handle_children(profile, hat, root): if ynans == 'y': helpers[exec_target] = 'enforce' if to_name: - autodep_base('', exec_target) + autodep('', exec_target) else: - autodep_base(exec_target, '') + autodep(exec_target, '') reload_base(exec_target) elif ans.startswith('CMD_cx') or ans.startswith('CMD_cix'): if to_name: @@ -1754,6 +1754,12 @@ def is_repo_profile(profile_data): def get_repo_user_pass(): # To-Do pass +def get_preferred_user(repo_url): + # To-Do + pass +def repo_is_enabled(): + # To-Do + return False def update_repo_profile(profile): # To-Do @@ -1888,13 +1894,13 @@ def ask_the_questions(): deny_mode = 0 deny_audit = 0 - fmode, famode, fm = rematch_frag(aa[profile][hat], 'allow', path) + fmode, famode, fm = rematchfrag(aa[profile][hat], 'allow', path) if fmode: allow_mode |= fmode if famode: allow_audit |= famode - cm, cam, m = rematch_frag(aa[profile][hat], 'deny', path) + cm, cam, m = rematchfrag(aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: @@ -1958,13 +1964,7 @@ def ask_the_questions(): if aa[profile][hat][incname]: continue - if cfg['settings']['custom_includes']: - for incn in cfg['settings']['custom_includes'].split(): - if incn == incname: - include_valid = True - - if valid_abstraction(incname): - include_valid = True + include_valid = valid_include(profile, incname) if not include_valid: continue @@ -2527,7 +2527,7 @@ def save_profiles(): else: selected_profiles_ref = yarg['PROFILES'] for profile_name in selected_profiles_ref: - writeprofile_ui_feedback(profile_name) + write_profile_ui_feedback(profile_name) reload_base(profile_name) else: @@ -2552,7 +2552,7 @@ def save_profiles(): display_changes(oldprofile, newprofile) for profile_name in changed_list: - writeprofile_ui_feedback(profile_name) + write_profile_ui_feedback(profile_name) reload_base(profile_name) def get_pager(): @@ -2646,7 +2646,7 @@ def collapse_log(): combinedmode |= aa[profile][hat]['allow']['path'][path] # Match path to regexps in profile - combinedmode |= rematch_frag(aa[profile][hat], 'allow', path) + combinedmode |= rematchfrag(aa[profile][hat], 'allow', path) # Match path from includes combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path) @@ -3408,7 +3408,7 @@ def escape(escape): return '"%s"' % escape return escape -def write_header(profile_data, depth, name, embedded_hat, write_flags): +def write_header(prof_data, depth, name, embedded_hat, write_flags): pre = ' ' * depth data = [] name = quote_if_needed(name) @@ -3416,46 +3416,49 @@ def write_header(profile_data, depth, name, embedded_hat, write_flags): if (not embedded_hat and re.search('^[^\/]|^"[^\/]', name)) or (embedded_hat and re.search('^[^^]' ,name)): name = 'profile %s' % name - if write_flags and profile_data['flags']: - data.append('%s%s flags(%s) {' % (pre, name, profile_data['flags'])) + if write_flags and prof_data['flags']: + data.append('%s%s flags(%s) {' % (pre, name, prof_data['flags'])) else: data.append('%s%s {' % (pre, name)) return data -def write_single(profile_data, depth, allow, name, prefix, tail): +def write_single(prof_data, depth, allow, name, prefix, tail): pre = ' ' * depth data = [] - ref, allow = set_ref_allow(profile_data, allow) + ref, allow = set_ref_allow(prof_data, allow) if ref.get(name, False): for key in sorted(re[name].keys()): qkey = quote_if_needed(key) - data.append(pre + allow + prefix + qkey + tail) + data.append('%s%s%s%s%s' %(pre, allow, prefix, qkey, tail)) if ref[name].keys(): data.append('') return data -def set_ref_allow(profile_data, allow): - if allow: - if allow == 'deny': - return profile_data[allow], 'deny ' - else: - return profile_data[allow], '' +def set_allow_str(allow): + if allow == 'deny': + return 'deny ' else: - return profile_data, '' + return '' + +def set_ref_allow(prof_data, allow): + if allow: + return prof_data[allow], set_allow_str(allow) + else: + return prof_data, '' -def write_pair(profile_data, depth, allow, name, prefix, sep, tail, fn): +def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn): pre = ' ' * depth data = [] - ref, allow = set_ref_allow(profile_data, allow) + ref, allow = set_ref_allow(prof_data, allow) if ref.get(name, False): for key in sorted(re[name].keys()): value = fn(ref[name][key])#eval('%s(%s)' % (fn, ref[name][key])) - data.append(pre + allow + prefix + key + sep + value) + data.append('%s%s%s%s%s%s' %(pre, allow, prefix, key, sep, value)) if ref[name].keys(): data.append('') @@ -3485,9 +3488,7 @@ def write_list_vars(prof_data, depth): def write_cap_rules(prof_data, depth, allow): pre = ' ' * depth data = [] - allowstr = '' - if allow == 'deny': - allowstr = 'deny' + allowstr = set_allow_str(allow) if prof_data[allow].get('capability', False): for cap in sorted(prof_data[allow]['capability'].keys()): @@ -3495,7 +3496,7 @@ def write_cap_rules(prof_data, depth, allow): if prof_data[allow]['capability'][cap].get('audit', False): audit = 'audit' if prof_data[allow]['capability'][cap].get('set', False): - data.append(pre + audit + allowstr + 'capability,') + data.append('%s%s%scapability %s,' %(pre, audit, allowstr)) data.append('') return data @@ -3506,3 +3507,546 @@ def write_capabilities(prof_data, depth): data += write_cap_rules(prof_data, depth, 'allow') return data +def write_net_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + allowstr = set_allow_str(allow) + + if prof_data[allow].get('netdomain', False): + if prof_data[allow]['netdomain'].get('rule', False) == 'all': + if prof_data[allow]['netdomain']['audit'].get('all', False): + audit = 'audit ' + data.append('%s%snetwork,' %(pre, audit)) + else: + for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()): + if prof_data[allow]['netdomain']['rule'][fam] == True: + if prof_data[allow]['netdomain']['audit'][fam]: + audit = 'audit' + data.append('%s%s%snetwork %s' % (pre, audit, allowstr, fam)) + else: + for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()): + if prof_data[allow]['netdomain']['audit'][fam].get(typ, False): + audit = 'audit' + data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr,fam, typ)) + if prof_data[allow].get('netdomain', False): + data.append('') + + return data + +def write_netdomain(prof_data, depth): + data = write_net_rules(prof_data, depth, 'deny') + data += write_net_rules(prof_data, depth, 'allow') + return data + +def write_link_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + allowstr = set_allow_str(allow) + + if prof_data[allow].get('link', False): + for path in sorted(prof_data[allow]['link'].keys()): + to_name = prof_data[allow]['link'][path]['to'] + subset = '' + if prof_data[allow]['link'][path]['mode'] & AA_LINK_SUBSET: + subset = 'subset' + audit = '' + if prof_data[allow]['link'][path].get('audit', False): + audit = 'audit ' + path = quote_if_needed(path) + to_name = quote_if_needed(to_name) + data.append('%s%s%slink %s%s -> %s,' %(pre, audit, allowstr, subset, path, to_name)) + data.append('') + + return data + +def write_links(prof_data, depth): + data = write_link_rules(prof_data, depth, 'deny') + data += write_link_rules(prof_data, depth, 'allow') + + return data + +def write_path_rules(prof_data, depth, allow): + pre = ' ' * depth + data = [] + allowstr = set_allow_str(allow) + + if prof_data[allow].get('path', False): + for path in sorted(prof_data[allow]['path'].keys()): + mode = prof_data[allow]['path'][path]['mode'] + audit = prof_data[allow]['path'][path]['audit'] + tail = '' + if prof_data[allow]['path'][path].get('to', False): + tail = ' -> %s' % prof_data[allow]['path'][path]['to'] + user, other = split_mode(mode) + user_audit, other_audit = split_mode(audit) + + while user or other: + ownerstr = '' + tmpmode = 0 + tmpaudit = False + if user & ~other: + # if no other mode set + ownerstr = 'owner' + tmpmode = user & ~other + tmpaudit = user_audit + user = user & ~tmpmode + else: + if user_audit & ~other_audit & user: + ownerstr = 'owner ' + tmpaudit = user_audit & ~other_audit & user + tmpmode = user & tmpaudit + user = user & ~tmpmode + else: + ownerstr = '' + tmpmode = user | other + tmpaudit = user_audit | other_audit + user = user & ~tmpmode + other = other & ~tmpmode + + if tmpmode & tmpaudit: + modestr = mode_to_str(tmpmode & tmpaudit) + path = quote_if_needed(path) + data.append('%saudit %s%s%s %s%s,' %(pre, allowstr, ownerstr, path, modestr, tail)) + tmpmode = tmpmode & ~tmpaudit + + if tmpmode: + modestr = mode_to_str(tmpmode) + path = quote_if_needed(path) + data.append('%s%s%s%s %s%s,' %(pre, allowstr, ownerstr, path, modestr, tail)) + + data.append('') + return data + +def write_paths(prof_data, depth): + data = write_path_rules(prof_data, depth, 'deny') + data += write_path_rules(prof_data, depth, 'allow') + + return data + +def write_rules(prof_data, depth): + data = write_alias(prof_data, depth) + data += write_list_vars(prof_data, depth) + data += write_includes(prof_data, depth) + data += write_rlimits(prof_data, depth) + data += write_capabilities(prof_data, depth) + data += write_netdomain(prof_data, depth) + data += write_links(prof_data, depth) + data += write_paths(prof_data, depth) + data += write_change_profile(prof_data, depth) + + return data + +def write_piece(profile_data, depth, name, nhat, write_flags): + pre = ' ' * depth + data = [] + wname = None + inhat = False + if name == nhat: + wname = name + else: + wname = name + '//' + nhat + name = nhat + inhat = True + + data += write_header(profile_data[name], depth, wname, False, write_flags) + data += write_rules(profile_data[name], depth+1) + + pre2 = ' ' * (depth+1) + # External hat declarations + for hat in filter(lambda x: x != name, sorted(profile_data.keys())): + if profile_data[hat].get('declared', False): + data.append('%s^%s,' %(pre2, hat)) + + if not inhat: + # Embedded hats + for hat in filter(lambda x: x != name, sorted(profile_data.keys())): + if not profile_data[hat]['external'] and not profile_data[hat]['declared']: + data.append('') + if profile_data[hat]['profile']: + data += map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags)) + else: + data += map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags)) + + data += map(str, write_rules(profile_data[hat], depth+2)) + + data.append('%s}' %pre2) + + data.append('%s}' %pre) + + # External hats + for hat in filter(lambda x: x != name, sorted(profile_data.keys())): + if name == nhat and profile_data[hat].get('external', False): + data.append('') + data += map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags)) + data.append(' }') + + return data + +def serialize_profile(profile_data, name, options): + string = '' + include_metadata = False + include_flags = True + data= [] + + if options and type(options) == dict: + if options.get('METADATA', False): + include_metadata = True + if options.get('NO_FLAGS', False): + include_flags = False + + if include_metadata: + string = '# Last Modified: %s\n' %time.time() + + if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] + and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): + repo = profile_data[name]['repo'] + string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id']) + elif profile_data[name]['repo']['neversubmit']: + string += '# REPOSITORY: NEVERSUBMIT\n' + + if profile_data[name].get('initial_comment', False): + comment = profile_data[name]['initial_comment'] + comment.replace('\\n', '\n') + string += comment + '\n' + + filename = get_profile_filename(name) + if filelist.get(filename, False): + data += write_alias(filelist[filename], 0) + data += write_list_vars(filelist[filename], 0) + data += write_includes(filelist[filename], 0) + + data += write_piece(profile_data, 0, name, name, include_flags) + + string += '\n'.join(data) + + return string+'\n' + +def write_profile_ui_feedback(profile): + UI_Info(gettext('Writing updated profile for %s.') %profile) + write_profile(profile) + +def write_profile(profile): + filename = None + if aa[profile][profile].get('filename', False): + filename = aa[profile][profile]['filename'] + else: + filename = get_profile_filename(profile) + + newprof = tempfile.NamedTemporaryFile('rw', suffix='~' ,delete=False) + if os.path.exists(filename): + shutil.copymode(filename, newprof.name) + else: + #permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write + #os.chmod(newprof.name, permission_600) + pass + + serialize_options = {} + serialize_options['METADATA'] = True + + profile_string = serialize_profile(aa[profile], profile, serialize_options) + newprof.write(profile_string) + newprof.close() + + os.rename(newprof.name, filename) + + changed.pop(profile) + original_aa[profile] = deepcopy(aa[profile]) + +def matchliteral(aa_regexp, literal): + p_regexp = '^'+convert_regexp(aa_regexp)+'$' + match = False + try: + match = re.search(p_regexp, literal) + except: + return None + return match + +def profile_known_exec(profile, typ, exec_target): + if typ == 'exec': + cm = None + am = None + m = [] + + cm, am, m = rematchfrag(profile, 'deny', exec_target) + if cm & AA_MAY_EXEC: + return -1 + + cm, am, m = match_prof_incs_to_path(profile, 'deny', exec_target) + if cm & AA_MAY_EXEC: + return -1 + + cm, am, m = rematchfrag(profile, 'allow', exec_target) + if cm & AA_MAY_EXEC: + return 1 + + cm, am, m = match_prof_incs_to_path(profile, 'allow', exec_target) + if cm & AA_MAY_EXEC: + return 1 + + return 0 + +def profile_known_capability(profile, capname): + if profile['deny']['capability'][capname].get('set', False): + return -1 + + if profile['allow']['capability'][capname].get('set', False): + return 1 + + for incname in profile['include'].keys(): + if include[incname][incname]['deny']['capability'][capname].get('set', False): + return -1 + if include[incname][incname]['allow']['capability'][capname].get('set', False): + return 1 + + return 0 + +def profile_known_network(profile, family, sock_type): + if netrules_access_check(profile['deny']['netdomain'], family, sock_type): + return -1 + if netrules_access_check(profile['allow']['netdomain'], family, sock_type): + return 1 + + for incname in profile['include'].keys(): + if netrules_access_check(include[incname][incname]['deny']['netdomain'], family, sock_type): + return -1 + if netrules_access_check(include[incname][incname]['allow']['netdomain'], family, sock_type): + return 1 + + return 0 + +def netrules_access_check(netrules, family, sock_type): + if not netrules: + return 0 + all_net = False + all_net_family = False + net_family_sock = False + if netrules['rule'].get('all', False): + all_net = True + if netrules['rule'].get(family, False) == True: + all_net_family = True + if (netrules['rule'].get(family, False) and + type(netrules['rule'][family]) == dict and + netrules['rule'][family][sock_type]): + net_family_sock = True + + if all_net or all_net_family or net_family_sock: + return True + else: + return False + +def reload_base(bin_path): + if not check_for_apparmor(): + return None + + filename = get_profile_filename(bin_path) + + subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" %(filename, parser ,profile_dir), shell=True) + +def reload(bin_path): + bin_path = find_executable(bin_path) + if not bin: + return None + + return reload_base(bin_path) + +def get_include_data(filename): + data = [] + if os.path.exists(profile_dir + '/' + filename): + with open_file_read(profile_dir + '/' + filename) as f_in: + data = f_in.readlines() + else: + raise AppArmorException('File Not Found: %s' %filename) + return data + +def load_include(incname): + load_includeslist = [incname] + if include.get(incname, {}).get(incname, False): + return 0 + while load_includeslist: + incfile = load_includeslist.pop(0) + data = get_include_data(incfile) + incdata = parse_profile_data(data, incfile, True) + if incdata: + attach_profile_data(include, incdata) + + return 0 + +def rematchfrag(frag, allow, path): + combinedmode = 0 + combinedaudit = 0 + matches = [] + + for entry in frag[allow]['path'].keys(): + match = matchliteral(entry, path) + if match: + combinedmode |= frag[allow]['path'][entry]['mode'] + combinedaudit |= frag[allow]['path'][entry]['audit'] + matches.append(entry) + + return combinedmode, combinedaudit, matches + +def match_include_to_path(incname, allow, path): + combinedmode = 0 + combinedaudit = 0 + matches = [] + + includelist = [incname] + while includelist: + incfile = includelist.pop(0) + ret = load_include(incfile) + cm, am , m = rematchfrag(include[incfile][incfile], allow, path) + if cm: + combinedmode |= cm + combinedaudit |= am + matches += m + + if include[incfile][incfile][allow]['path'][path]: + combinedmode |= include[incfile][incfile][allow]['path'][path]['mode'] + combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit'] + + if include[incfile][incfile]['include'].keys(): + includelist + include[incfile][incfile]['include'].keys() + + return combinedmode, combinedaudit, matches + +def match_prof_incs_to_path(frag, allow, path): + combinedmode = 0 + combinedaudit = 0 + matches = [] + + includelist = list(frag['include'].keys()) + while includelist: + incname = includelist.pop(0) + cm, am, m = match_include_to_path(incname, allow, path) + if cm: + combinedmode |= cm + combinedaudit |= am + matches += m + + return combinedmode, combinedaudit, matches + +def suggest_incs_for_path(incname, path, allow): + combinedmode = 0 + combinedaudit = 0 + matches = [] + + includelist = [incname] + while includelist: + inc = includelist.pop(0) + cm, am , m = rematchfrag(include[inc][inc], 'allow', path) + if cm: + combinedmode |= cm + combinedaudit |= am + matches += m + + if include[inc][inc]['allow']['path'].get(path, False): + combinedmode |= include[inc][inc]['allow']['path'][path]['mode'] + combinedaudit |= include[inc][inc]['allow']['path'][path]['audit'] + + if include[inc][inc]['include'].keys(): + includelist += include[inc][inc]['include'].keys() + + return combinedmode, combinedaudit, matches + +def check_qualifiers(program): + if cfg['qualifiers'].get(program, False): + if cfg['qualifiers'][program] != 'p': + fatal_error(gettext('%s is currently marked as a program that should not have its own\n' + + 'profile. Usually, programs are marked this way if creating a profile for \n' + + 'them is likely to break the rest of the system. If you know what you\'re\n' + + 'doing and are certain you want to create a profile for this program, edit\n' + + 'the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.') %program) + +def get_subdirectories(current_dir): + """Returns a list of all directories directly inside given directory""" + if sys.version_info < (3,0): + return os.walk(current_dir).next()[1] + else: + return os.walk(current_dir).__next__()[1] + +def loadincludes(): + incdirs = get_subdirectories(profile_dir) + + for idir in incdirs: + if is_skippable_dir(idir): + continue + for dirpath, dirname, files in os.walk(profile_dir + '/' + idir): + if is_skippable_dir(dirpath): + continue + for fi in files: + if is_skippable_file(fi): + continue + else: + load_include(dirpath + '/' + fi) + +def glob_common(path): + globs = [] + + if re.search('[\d\.]+\.so$', path) or re.search('\.so\.[\d\.]+$', path): + libpath = path + libpath = re.sub('[\d\.]+\.so$', '*.so', libpath) + libpath = re.sub('\.so\.[\d\.]+$', '.so.*', libpath) + if libpath != path: + globs.append(libpath) + + for glob in cfg['globs']: + if re.search(glob, path): + globbedpath = path + globbedpath = re.sub(glob, cfg['globs'][glob]) + if globbedpath != path: + globs.append(globbedpath) + + return sorted(set(globs)) + +def combine_name(name1, name2): + if name1 == name2: + return name1 + else: + return '%s^%s' %(name1, name2) + +def split_name(name): + names = name.split('^') + if len(names) == 1: + return name, name + else: + return names[0], names[1] + +def matchregexp(new, old): + if re.search('\{.*(\,.*)*\}', old): + return None + + if re.search('\[.+\]', old) or re.search('\*', old) or re.search('\?', old): + + if re.search('\{.*\,.*\}', new): + pass + +######Initialisations###### + +conf = apparmor.config.Config('ini') +cfg = conf.read_config('logprof.conf') + +if cfg['settings'].get('default_owner_prompt', False): + cfg['settings']['default_owner_prompt'] = False + +profile_dir = conf.find_first_dir(cfg['settings']['profiledir']) or '/etc/apparmor.d' +if not os.path.isdir(profile_dir): + raise AppArmorException('Can\'t find AppArmor profiles' ) + +extra_profile_dir = conf.find_first_dir(cfg['settings']['inactive_profiledir']) or '/etc/apparmor/profiles/extras/' + +parser = conf.find_first_file(cfg['settings']['parser']) or '/sbin/apparmor_parser' +if not os.path.isfile(parser) or not os.access(parser, os.EX_OK): + raise AppArmorException('Can\'t find apparmor_parser') + +filename = conf.find_first_file(cfg['settings']['logfiles']) or '/var/log/syslog' +if not os.path.isfile(filename): + raise AppArmorException('Can\'t find system log.') + +ldd = conf.find_first_file(cfg['settings']['ldd']) or '/usr/bin/ldd' +if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK): + raise AppArmorException('Can\'t find ldd') + +logger = conf.find_first_file(cfg['settings']['logger']) or '/bin/logger' +if not os.path.isfile(logger) or not os.access(logger, os.EX_OK): + raise AppArmorException('Can\'t find logger') + \ No newline at end of file diff --git a/apparmor/ui.py b/apparmor/ui.py index f065f567f..25d71e5b5 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -4,9 +4,10 @@ import gettext import locale import logging import os +import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast -from apparmor.common import readkey +from apparmor.common import readkey, AppArmorException DEBUGGING = False debug_logger = None @@ -30,6 +31,18 @@ def init_localisation(): trans = gettext.NullTranslations() trans.install() +ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'} + +def getkey(): + key = readkey() + if key == '\x1B': + key = readkey() + if key == '[': + key = readkey() + if(ARROWS.get(key, False)): + key = ARROWS[key] + return key + def UI_Info(text): if DEBUGGING: debug_logger.info(text) @@ -71,7 +84,7 @@ def UI_YesNo(text, default): else: ans = default else: - SendDataTooYast({ + SendDataToYast({ 'type': 'dialog-yesno', 'question': text }) @@ -130,7 +143,7 @@ def UI_GetString(text, default): 'label': text, 'default': default }) - ypath, yarg = GetDatFromYast() + ypath, yarg = GetDataFromYast() string = yarg['string'] return string @@ -257,10 +270,149 @@ def UI_ShortMessage(title, message): }) ypath, yarg = GetDataFromYast() -def UI_longMessage(title, message): +def UI_LongMessage(title, message): SendDataToYast({ 'type': 'long-dialog-message', 'headline': title, 'message': message }) ypath, yarg = GetDataFromYast() + +def confirm_and_finish(): + sys.stdout.stdout('FINISHING\n') + sys.exit(0) + +def Text_PromptUser(question): + title = question['title'] + explanation = question['explanation'] + headers = question['headers'] + functions = question['functions'] + + default = question['default'] + options = question['options'] + selected = question.get('selected', False) or 0 + helptext = question['helptext'] + if helptext: + functions.append('CMD_HELP') + + menu_items = [] + keys = dict() + + for cmd in functions: + if not CMDS.get(cmd, False): + raise AppArmorException('PromptUser: %s %s' %(gettext('Unknown command'), cmd)) + + menutext = gettext(CMDS[cmd]) + + menuhotkey = re.search('\((\S)\)', menutext) + if not menuhotkey: + raise AppArmorException('PromptUser: %s \'%s\'' %(gettext('Invalid hotkey in'), menutext)) + + key = menuhotkey.groups()[0].lower() + # Duplicate hotkey + if keys.get(key, False): + raise AppArmorException('PromptUser: %s %s: %s' %(gettext('Duplicate hotkey for'), cmd, menutext)) + + keys[key] = cmd + + if default and default == cmd: + menutext = '[%s]' %menutext + + menu_items.append(menutext) + + default_key = 0 + if default and CMDS[default]: + defaulttext = gettext(CMDS[default]) + + defaulthotkey = re.search('\((\S)\)', defaulttext) + if not menuhotkey: + raise AppArmorException('PromptUser: %s \'%s\'' %(gettext('Invalid hotkey in default item'), defaulttext)) + + default_key = defaulthotkey.groups()[0].lower() + + if keys.get(default_key, False): + raise AppArmorException('PromptUser: %s %s' %(gettext('Invalid default'), default)) + + widest = 0 + header_copy = headers[:] + while header_copy: + header = header_copy.pop(0) + header_copy.pop(0) + if len(header) > widest: + widest = len(header) + widest += 1 + + formatstr = '%-' + widest + 's %s\n' + + function_regexp = '^(' + function_regexp += '|'.join(keys.keys()) + if options: + function_regexp += '|\d' + function_regexp += ')$' + + ans = 'XXXINVALIDXXX' + while not re.search(function_regexp, ans, flags=re.IGNORECASE): + + prompt = '\n' + if title: + prompt += '= %s =\n\n' %title + + if headers: + header_copy = headers[:] + while header_copy: + header = header_copy.pop(0) + value = header_copy.pop(0) + prompt += formatstr %(header+':', value) + prompt += '\n' + + if explanation: + prompt += explanation + '\n\n' + + if options: + for index, option in enumerate(options): + if selected == index: + format_option = ' [%s - %s]' + else: + format_option = ' %s - %s ' + prompt += format_option %(index+1, option) + prompt += '\n' + + prompt += ' / '.join(menu_items) + + sys.stdout.write(prompt+'\n') + + ans = readkey().lower() + + if ans: + if ans == 'up': + if options and selected > 0: + selected -= 1 + ans = 'XXXINVALIDXXX' + + elif ans == 'down': + if options and selected < len(options)-2: + selected += 1 + ans = 'XXXINVALIDXXX' + + elif keys.get(ans, False) == 'CMD_HELP': + sys.stdout.write('\n%s\n' %helptext) + ans = 'XXXINVALIDXXX' + + elif int(ans) == 10: + # If they hit return choose default option + ans = default_key + + elif options and re.search('^\d$', ans): + ans = int(ans) + if ans > 0 and ans < len(options): + selected = ans - 1 + ans = 'XXXINVALIDXXX' + + if keys.get(ans, False) == 'CMD_HELP': + sys.stdout.write('\n%s\n' %helptext) + ans = 'again' + + if keys.get(ans, False): + ans = keys[ans] + + return ans, selected diff --git a/apparmor/yasti.py b/apparmor/yasti.py index 8eed768cf..a9232febf 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -1,5 +1,5 @@ import re -#import ycp +import ycp import os import sys import logging From e78dd6e9bc4adf303351fa33d0488bc4436605af Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 1 Aug 2013 21:57:27 +0530 Subject: [PATCH 032/183] updated regex parser --- apparmor/aa.py | 61 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 80719560f..4e6659d07 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -236,22 +236,31 @@ def which(file): return None def convert_regexp(regexp): - ## To Do - #regex_escape = re.compile('(?/dev/null 2>&1" %(filename, parser ,profile_dir), shell=True) + subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" %(prof_filename, parser ,profile_dir), shell=True) def reload(bin_path): bin_path = find_executable(bin_path) From 68afe0f0e9fdc9176fce31967590cb4fe3852fbc Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 5 Aug 2013 18:55:34 +0530 Subject: [PATCH 033/183] Added some tests for common module and fixed a few minor bugs in regex parser --- Testing/aa_test.py | 2 + Testing/common_test.py | 77 +++++++++++++++++++++++++++++++++++++++ apparmor/aa.py | 83 +++++++++++++++++++----------------------- apparmor/common.py | 38 ++++++++++++++++++- apparmor/config.py | 1 + apparmor/severity.py | 40 ++++++++++---------- apparmor/yasti.py | 2 +- 7 files changed, 176 insertions(+), 67 deletions(-) create mode 100644 Testing/common_test.py diff --git a/Testing/aa_test.py b/Testing/aa_test.py index fdefbf456..b2826703e 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -8,6 +8,7 @@ import sys sys.path.append('../') import apparmor.aa + #from apparmor.aa import parse_event class Test(unittest.TestCase): @@ -83,6 +84,7 @@ class Test(unittest.TestCase): #self.assertEqual(apparmor.aa.str_to_mode('C'), 2048) + if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] unittest.main() \ No newline at end of file diff --git a/Testing/common_test.py b/Testing/common_test.py new file mode 100644 index 000000000..6d4c85dab --- /dev/null +++ b/Testing/common_test.py @@ -0,0 +1,77 @@ +import unittest +import re +import sys + +sys.path.append('../') +import apparmor.common + +class Test(unittest.TestCase): + + + def test_RegexParser(self): + regex_1 = '/foo/**/bar/' + parsed_regex_1 = apparmor.common.convert_regexp(regex_1) + compiled_regex_1 = re.compile(parsed_regex_1) + #print(parsed_regex_1) + self.assertEqual(bool(compiled_regex_1.search('/foo/user/tools/bar/')), True, 'Incorrectly Parsed regex') + self.assertEqual(bool(compiled_regex_1.search('/foo/apparmor/bar/')), True, 'Incorrectly Parsed regex') + + self.assertEqual(bool(compiled_regex_1.search('/foo/apparmor/bar')), False, 'Incorrectly Parsed regex') + + regex_2 = '/foo/*/bar/' + parsed_regex_2 = apparmor.common.convert_regexp(regex_2) + compiled_regex_2 = re.compile(parsed_regex_2) + #print(parsed_regex_2) + self.assertEqual(bool(compiled_regex_2.search('/foo/apparmor/bar/')), True, 'Incorrectly Parsed regex') + + self.assertEqual(bool(compiled_regex_2.search('/foo/apparmor/tools/bar/')), False, 'Incorrectly Parsed regex') + self.assertEqual(bool(compiled_regex_2.search('/foo/apparmor/bar')), False, 'Incorrectly Parsed regex') + + regex_3 = '/foo/{foo,bar,user,other}/bar/' + parsed_regex_3 = apparmor.common.convert_regexp(regex_3) + compiled_regex_3 = re.compile(parsed_regex_3) + #print(parsed_regex_3) + self.assertEqual(bool(compiled_regex_3.search('/foo/user/bar/')), True, 'Incorrectly Parsed regex') + self.assertEqual(bool(compiled_regex_3.search('/foo/bar/bar/')), True, 'Incorrectly Parsed regex') + + self.assertEqual(bool(compiled_regex_3.search('/foo/wrong/bar/')), False, 'Incorrectly Parsed regex') + + regex_4 = '/foo/user/ba?/' + parsed_regex_4 = apparmor.common.convert_regexp(regex_4) + compiled_regex_4 = re.compile(parsed_regex_4) + #print(parsed_regex_4) + + self.assertEqual(bool(compiled_regex_4.search('/foo/user/bar/')), True, 'Incorrectly Parsed regex') + + self.assertEqual(bool(compiled_regex_4.search('/foo/user/bar/apparmor/')), False, 'Incorrectly Parsed regex') + self.assertEqual(bool(compiled_regex_4.search('/foo/user/ba/')), False, 'Incorrectly Parsed regex') + + regex_5 = '/foo/user/bar/**' + parsed_regex_5 = apparmor.common.convert_regexp(regex_5) + compiled_regex_5 = re.compile(parsed_regex_5) + #print(parsed_regex_5) + + self.assertEqual(bool(compiled_regex_5.search('/foo/user/bar/apparmor')), True, 'Incorrectly Parsed regex') + self.assertEqual(bool(compiled_regex_5.search('/foo/user/bar/apparmor/tools')), True, 'Incorrectly Parsed regex') + + self.assertEqual(bool(compiled_regex_5.search('/foo/user/bar/')), False, 'Incorrectly Parsed regex') + + regex_6 = '/foo/user/bar/*' + parsed_regex_6 = apparmor.common.convert_regexp(regex_6) + compiled_regex_6 = re.compile(parsed_regex_6) + #print(parsed_regex_6) + + self.assertEqual(bool(compiled_regex_6.search('/foo/user/bar/apparmor')), True, 'Incorrectly Parsed regex') + + self.assertEqual(bool(compiled_regex_6.search('/foo/user/bar/apparmor/tools')), False, 'Incorrectly Parsed regex') + self.assertEqual(bool(compiled_regex_6.search('/foo/user/bar/')), False, 'Incorrectly Parsed regex') + + + def test_readkey(self): + print("Please press the Y button on the keyboard.") + self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!') + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.test_RegexParser'] + unittest.main() \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index 4e6659d07..6679b4e4d 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,6 +1,5 @@ #6585 #382-430 -#480-525 #6414-6472 # No old version logs, only 2.6 + supported #global variable names corruption @@ -24,7 +23,7 @@ import LibAppArmor from apparmor.common import (AppArmorException, error, debug, msg, open_file_read, readkey, valid_path, - hasher, open_file_write) + hasher, open_file_write, convert_regexp) from apparmor.ui import * from copy import deepcopy @@ -234,32 +233,6 @@ def which(file): if os.access(env_path, os.X_OK): return env_path return None - -def convert_regexp(regexp): - new_reg = re.sub(r'(? Date: Tue, 6 Aug 2013 01:53:28 +0530 Subject: [PATCH 034/183] Some code for logprof --- Testing/aa_test.py | 10 +++--- Tools/aa-logprof.py | 13 +++++++ apparmor/aa.py | 87 ++++++++++++++++++++++++++------------------- 3 files changed, 67 insertions(+), 43 deletions(-) create mode 100644 Tools/aa-logprof.py diff --git a/Testing/aa_test.py b/Testing/aa_test.py index b2826703e..d005b355e 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -1,8 +1,3 @@ -''' -Created on Jul 29, 2013 - -@author: kshitij -''' import unittest import sys @@ -13,7 +8,10 @@ import apparmor.aa class Test(unittest.TestCase): - + def test_loadinclude(self): + apparmor.aa.loadincludes() + + def test_parse_event(self): event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_balmar" name=2F686F6D652F7777772F62616C6D61722E646174616E6F766F322E64652F68747470646F63732F6A6F6F6D6C612F696D616765732F6B75656368656E2F666F746F20322E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' parsed_event = apparmor.aa.parse_event(event) diff --git a/Tools/aa-logprof.py b/Tools/aa-logprof.py new file mode 100644 index 000000000..d3be4ca8b --- /dev/null +++ b/Tools/aa-logprof.py @@ -0,0 +1,13 @@ +#!/usr/bin/python +import sys + +sys.path.append('../') +import apparmor.aa +import os +import argparse + +os.environb.putenv('PATH', '/bin/:/sbin/:/usr/bin/:/usr/sbin') + +apparmor.aa.loadincludes() + + diff --git a/apparmor/aa.py b/apparmor/aa.py index 6679b4e4d..0945b6f83 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2963,23 +2963,23 @@ def parse_profile_data(data, file, do_include): repo_data = None parsed_profiles = [] initial_comment = '' - RE_PROFILE_START = re.compile('^\s*(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') - RE_PROFILE_END = re.compile('^\s*\}\s*(#.*)?$') - RE_PROFILE_CAP = re.compile('^\s*(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') - RE_PROFILE_SET_CAP = re.compile('^\s*set capability\s+(\S+)\s*,\s*(#.*)?$') - RE_PROFILE_LINK = re.compile('^\s*(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') - RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$') - RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') - RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$') - RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$') - RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\+?=\s*(.+?)\s*,?\s*(#.*)?$') - RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_PATH_ENTRY = re.compile('^\s*(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') - RE_PROFILE_NETWORK = re.compile('^\s*(audit\s+)?(deny\s+)?network(.*)\s*(#.*)?$') - RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$') - RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') + RE_PROFILE_START = re.compile('^(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') + RE_PROFILE_END = re.compile('^\}\s*(#.*)?$') + RE_PROFILE_CAP = re.compile('^(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') + RE_PROFILE_SET_CAP = re.compile('^set capability\s+(\S+)\s*,\s*(#.*)?$') + RE_PROFILE_LINK = re.compile('^(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') + RE_PROFILE_CHANGE_PROFILE = re.compile('^change_profile\s+->\s*("??.+?"??),(#.*)?$') + RE_PROFILE_ALIAS = re.compile('^alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') + RE_PROFILE_RLIMIT = re.compile('^set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$') + RE_PROFILE_BOOLEAN = re.compile('^(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE) + RE_PROFILE_VARIABLE = re.compile('^(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.*)?$') + RE_PROFILE_CONDITIONAL = re.compile('^if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_PATH_ENTRY = re.compile('^(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') + RE_PROFILE_NETWORK = re.compile('^(audit\s+)?(deny\s+)?network(.*)\s*(#.*)?$') + RE_PROFILE_CHANGE_HAT = re.compile('^\^(\"??.+?\"??)\s*,\s*(#.*)?$') + RE_PROFILE_HAT_DEF = re.compile('^\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') if do_include: profile = file hat = file @@ -2993,7 +2993,8 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_START.search(line).groups() if profile: - if profile != hat or matches[3]: + #print(profile, hat) + if profile != hat or not matches[3]: raise AppArmorException('%s profile in %s contains syntax errors in line: %s.\n' % (profile, file, lineno+1)) # Keep track of the start of a profile if profile and profile == hat and matches[3]: @@ -3079,7 +3080,7 @@ def parse_profile_data(data, file, do_include): capability = matches[0] profile_data[profile][hat]['set_capability'][capability] = True - elif RE_PROFILE_LINK.ssearch(line): + elif RE_PROFILE_LINK.search(line): matches = RE_PROFILE_LINK.search(line).groups() if not profile: @@ -3140,8 +3141,8 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['rlimit'][from_name] = to_name - elif RE_PROFILE_BOOLEAN.search(line, flags=re.IGNORECASE): - matches = RE_PROFILE_BOOLEAN.search(line, flags=re.IGNORECASE) + elif RE_PROFILE_BOOLEAN.search(line): + matches = RE_PROFILE_BOOLEAN.search(line) if not profile: raise AppArmorException('Syntax Error: Unexpected boolean definition found in file: %s line: %s' % (file, lineno+1)) @@ -3153,19 +3154,20 @@ def parse_profile_data(data, file, do_include): elif RE_PROFILE_VARIABLE.search(line): # variable additions += and = - matches = RE_PROFILE_VARIABLE.search(line) + matches = RE_PROFILE_VARIABLE.search(line).groups() list_var = strip_quotes(matches[0]) - value = strip_quotes(matches[1]) + var_operation = matches[1] + value = strip_quotes(matches[2]) if profile: if not profile_data[profile][hat].get('lvar', False): profile_data[profile][hat]['lvar'][list_var] = [] - store_list_var(profile_data[profile]['lvar'], list_var, value) + store_list_var(profile_data[profile]['lvar'], list_var, value, var_operation) else: if not filelist[file].get('lvar', False): filelist[file]['lvar'][list_var] = [] - store_list_var(filelist[file]['lvar'], list_var, value) + store_list_var(filelist[file]['lvar'], list_var, value, var_operation) elif RE_PROFILE_CONDITIONAL.search(line): # Conditional Boolean @@ -3224,7 +3226,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat][allow]['path'][path]['to'] = nt_name if audit: - profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit') | tmpmode + profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit', 0) | tmpmode else: profile_data[profile][hat][allow]['path'][path]['audit'] = 0 @@ -3265,7 +3267,7 @@ def parse_profile_data(data, file, do_include): network = matches[2] if re.search('\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$', network): - nmatch = re.search(network, '\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$').groups() + nmatch = re.search('\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$', network).groups() fam, typ = nmatch[:2] profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = True profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit @@ -3347,12 +3349,14 @@ def parse_profile_data(data, file, do_include): def separate_vars(vs): data = [] + + #data = [i.strip('"') for i in vs.split()] RE_VARS = re.compile('\s*((\".+?\")|([^\"]\S+))\s*(.*)$') while RE_VARS.search(vs): matches = RE_VARS.search(vs).groups() data.append(strip_quotes(matches[0])) vs = matches[3] - + return data def is_active_profile(pname): @@ -3361,17 +3365,25 @@ def is_active_profile(pname): else: return False -def store_list_var(var, list_var, value): +def store_list_var(var, list_var, value, var_operation): vlist = separate_vars(value) - if var.get(list_var): - vlist += var[list_var] #vlist = (vlist, var[list_var]) - - vlist = list(set(vlist)) - var[list_var] = vlist + if var_operation == '=': + if not var.get(list_var, False): + var[list_var] = set(vlist) + else: + raise AppArmorException('An existing variable redefined') + else: + if var.get(list_var, False): + var[list_var] = set(var[list_var] + vlist) + else: + raise AppArmorException('An existing variable redefined') + def strip_quotes(data): if data[0]+data[-1] == '""': - return data[1:-1] + return data[1:-1] + else: + return data def quote_if_needed(data): # quote data if it contains whitespace @@ -3829,8 +3841,8 @@ def reload(bin_path): def get_include_data(filename): data = [] - if os.path.exists(profile_dir + '/' + filename): - with open_file_read(profile_dir + '/' + filename) as f_in: + if os.path.exists(filename): + with open_file_read(filename) as f_in: data = f_in.readlines() else: raise AppArmorException('File Not Found: %s' %filename) @@ -3844,6 +3856,7 @@ def load_include(incname): incfile = load_includeslist.pop(0) data = get_include_data(incfile) incdata = parse_profile_data(data, incfile, True) + print(incdata) if incdata: attach_profile_data(include, incdata) From d48c88428e36f232207eb17e7855fd4c53710638 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Wed, 7 Aug 2013 14:43:17 +0530 Subject: [PATCH 035/183] certain fixes --- Tools/aa-logprof.py | 11 ++- apparmor/__init__.py | 13 ++++ apparmor/aa.py | 163 ++++++++++++++++++++++--------------------- apparmor/ui.py | 24 ++----- 4 files changed, 115 insertions(+), 96 deletions(-) diff --git a/Tools/aa-logprof.py b/Tools/aa-logprof.py index d3be4ca8b..cd7107104 100644 --- a/Tools/aa-logprof.py +++ b/Tools/aa-logprof.py @@ -6,8 +6,17 @@ import apparmor.aa import os import argparse -os.environb.putenv('PATH', '/bin/:/sbin/:/usr/bin/:/usr/sbin') +if sys.version_info < (3,0): + os.environ['PATH'] = '/bin/:/sbin/:/usr/bin/:/usr/sbin' +else: + os.environb.putenv('PATH', '/bin/:/sbin/:/usr/bin/:/usr/sbin') + + + +logmark = '' apparmor.aa.loadincludes() +apparmor.aa.do_logprof_pass(logmark) + diff --git a/apparmor/__init__.py b/apparmor/__init__.py index c70a751df..813e6db3c 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -3,3 +3,16 @@ Created on Jun 27, 2013 @author: kshitij ''' +import gettext +import locale +def init_localisation(): + locale.setlocale(locale.LC_ALL, '') + #cur_locale = locale.getlocale() + filename = 'res/messages_%s.mo' % locale.getlocale()[0][0:2] + try: + trans = gettext.GNUTranslations(open( filename, 'rb')) + except IOError: + trans = gettext.NullTranslations() + trans.install() + +init_localisation() \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index 0945b6f83..76147107a 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -43,7 +43,7 @@ CONFDIR = '/etc/apparmor' running_under_genprof = False unimplemented_warning = False -# The database for severity +# The database for severity sev_db = None # The file to read log messages from ### Was our @@ -459,9 +459,9 @@ def delete_profile(local_prof): #prof_unload(local_prof) def confirm_and_abort(): - ans = UI_YesNo(gettext('Are you sure you want to abandon this set of profile changes and exit?'), 'n') + ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n') if ans == 'y': - UI_Info(gettext('Abandoning all changes.')) + UI_Info(_('Abandoning all changes.')) shutdown_yast() for prof in created: delete_profile(prof) @@ -930,12 +930,12 @@ def handle_children(profile, hat, root): while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']: q = hasher() q['headers'] = [] - q['headers'] += [gettext('Profile'), profile] + q['headers'] += [_('Profile'), profile] if default_hat: - q['headers'] += [gettext('Default Hat'), default_hat] + q['headers'] += [_('Default Hat'), default_hat] - q['headers'] += [gettext('Requested Hat'), uhat] + q['headers'] += [_('Requested Hat'), uhat] q['functions'] = [] q['functions'].append('CMD_ADDHAT') @@ -1183,13 +1183,13 @@ def handle_children(profile, hat, root): # Prompt portion starts q = hasher() q['headers'] = [] - q['headers'] += [gettext('Profile'), combine_name(profile, hat)] + q['headers'] += [_('Profile'), combine_name(profile, hat)] if prog and prog != 'HINT': - q['headers'] += [gettext('Program'), prog] + q['headers'] += [_('Program'), prog] # to_name should not exist here since, transitioning is already handeled - q['headers'] += [gettext('Execute'), exec_target] - q['headers'] += [gettext('Severity'), severity] + q['headers'] += [_('Execute'), exec_target] + q['headers'] += [_('Severity'), severity] q['functions'] = [] prompt = '\n%s\n' % context @@ -1212,7 +1212,7 @@ def handle_children(profile, hat, root): arg = exec_target ynans = 'n' if profile == hat: - ynans = UI_YesNo(gettext('Are you specifying a transition to a local profile?'), 'n') + ynans = UI_YesNo(_('Are you specifying a transition to a local profile?'), 'n') if ynans == 'y': if ans == 'CMD_nx': ans = 'CMD_cx' @@ -1224,7 +1224,7 @@ def handle_children(profile, hat, root): else: ans = 'CMD_pix' - to_name = UI_GetString(gettext('Enter profile name to transition to: '), arg) + to_name = UI_GetString(_('Enter profile name to transition to: '), arg) regex_optmode = re.compile('CMD_(px|cx|nx|pix|cix|nix)') if ans == 'CMD_ix': @@ -1233,13 +1233,13 @@ def handle_children(profile, hat, root): match = regex_optmode.search(ans).groups()[0] exec_mode = str_to_mode(match) px_default = 'n' - px_msg = gettext('Should AppArmor sanitise the environment when\n' + + px_msg = _('Should AppArmor sanitise the environment when\n' + 'switching profiles?\n\n' + 'Sanitising environment is more secure,\n' + 'but some applications depend on the presence\n' + 'of LD_PRELOAD or LD_LIBRARY_PATH.') if parent_uses_ld_xxx: - px_msg = gettext('Should AppArmor sanitise the environment when\n' + + px_msg = _('Should AppArmor sanitise the environment when\n' + 'switching profiles?\n\n' + 'Sanitising environment is more secure,\n' + 'but this application appears to be using LD_PRELOAD\n' + @@ -1252,12 +1252,12 @@ def handle_children(profile, hat, root): exec_mode &= ~(AA_EXEC_UNSAFE | (AA_EXEC_UNSAFE << AA_OTHER_SHIFT)) elif ans == 'CMD_ux': exec_mode = str_to_mode('ux') - ynans = UI_YesNo(gettext('Launching processes in an unconfined state is a very\n' + + ynans = UI_YesNo(_('Launching processes in an unconfined state is a very\n' + 'dangerous operation and can cause serious security holes.\n\n' + 'Are you absolutely certain you wish to remove all\n' + 'AppArmor protection when executing :') + '%s ?' % exec_target, 'n') if ynans == 'y': - ynans = UI_YesNo(gettext('Should AppArmor sanitise the environment when\n' + + ynans = UI_YesNo(_('Should AppArmor sanitise the environment when\n' + 'running this program unconfined?\n\n' + 'Not sanitising the environment when unconfining\n' + 'a program opens up significant security holes\n' + @@ -1337,7 +1337,7 @@ def handle_children(profile, hat, root): if not os.path.exists(get_profile_filename(exec_target)): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = UI_YesNo(gettext('A profile for ') + str(exec_target) + gettext(' doesnot exist.\nDo you want to create one?'), 'n') + ynans = UI_YesNo(_('A profile for ') + str(exec_target) + _(' doesnot exist.\nDo you want to create one?'), 'n') if ynans == 'y': helpers[exec_target] = 'enforce' if to_name: @@ -1355,7 +1355,7 @@ def handle_children(profile, hat, root): if not aa[profile].get(exec_target, False): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = UI_YesNo(gettext('A local profile for %s does not exit. Create one') % exec_target, 'n') + ynans = UI_YesNo(_('A local profile for %s does not exit. Create one') % exec_target, 'n') if ynans == 'y': hat = exec_target aa[profile][hat]['declared'] = False @@ -1596,6 +1596,7 @@ def read_log(logmark): #last = None #event_type = None try: + print(filename) log_open = open_file_read(filename) except IOError: raise AppArmorException('Can not read AppArmor logfile: ' + filename) @@ -1646,12 +1647,12 @@ def parse_event(msg): rmask = rmask.replace('c', 'a') rmask = rmask.replace('d', 'w') if not validate_log_mode(hide_log_mode(rmask)): - fatal_error(gettext('Log contains unknown mode %s') % rmask) + fatal_error(_('Log contains unknown mode %s') % rmask) if dmask: dmask = dmask.replace('c', 'a') dmask = dmask.replace('d', 'w') if not validate_log_mode(hide_log_mode(dmask)): - fatal_error(gettext('Log contains unknown mode %s') % dmask) + fatal_error(_('Log contains unknown mode %s') % dmask) #print('parse_event:', ev['profile'], dmask, ev['name2']) mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2']) @@ -1762,12 +1763,12 @@ def ask_the_questions(): for aamode in sorted(log_dict.keys()): # Describe the type of changes if aamode == 'PERMITTING': - UI_Info(gettext('Complain-mode changes:')) + UI_Info(_('Complain-mode changes:')) elif aamode == 'REJECTING': - UI_Info(gettext('Enforce-mode changes:')) + UI_Info(_('Enforce-mode changes:')) else: # oops something screwed up - fatal_error(gettext('Invalid mode found: %s') % aamode) + fatal_error(_('Invalid mode found: %s') % aamode) for profile in sorted(log_dict[aamode].keys()): # Update the repo profiles @@ -1801,9 +1802,9 @@ def ask_the_questions(): q['options'] = [options] q['selected'] = default_option - 1 - q['headers'] = [gettext('Profile'), combine_name(profile, hat)] - q['headers'] += [gettext('Capability'), capability] - q['headers'] += [gettext('Severity'), severity] + q['headers'] = [_('Profile'), combine_name(profile, hat)] + q['headers'] += [_('Capability'), capability] + q['headers'] += [_('Severity'), severity] audit_toggle = 0 @@ -1837,9 +1838,9 @@ def ask_the_questions(): q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] - q['headers'] = [gettext('Profile'), combine_name(profile, hat), - gettext('Capability'), audit + capability, - gettext('Severity'), severity] + q['headers'] = [_('Profile'), combine_name(profile, hat), + _('Capability'), audit + capability, + _('Severity'), severity] if ans == 'CMD_ALLOW': selection = options[selected] @@ -1850,23 +1851,23 @@ def ask_the_questions(): deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True - UI_Info(gettext('Adding %s to profile.') % selection) + UI_Info(_('Adding %s to profile.') % selection) if deleted: - UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + UI_Info(_('Deleted %s previous matching profile entries.') % deleted) aa[profile][hat]['allow']['capability'][capability]['set'] = True aa[profile][hat]['allow']['capability'][capability]['audit'] = audit_toggle changed[profile] = True - UI_Info(gettext('Adding capability %s to profile.'), capability) + UI_Info(_('Adding capability %s to profile.'), capability) done = True elif ans == 'CMD_DENY': aa[profile][hat]['deny']['capability'][capability]['set'] = True changed[profile] = True - UI_Info(gettext('Denying capability %s to profile.') % capability) + UI_Info(_('Denying capability %s to profile.') % capability) done = True else: done = False @@ -1996,8 +1997,8 @@ def ask_the_questions(): done = False while not done: q = hasher() - q['headers'] = [gettext('Profile'), combine_name(profile, hat), - gettext('Path'), path] + q['headers'] = [_('Profile'), combine_name(profile, hat), + _('Path'), path] if allow_mode: mode |= allow_mode @@ -2006,15 +2007,15 @@ def ask_the_questions(): prompt_mode = None if owner_toggle == 0: prompt_mode = flatten_mode(mode) - tail = ' ' + gettext('(owner permissions off)') + tail = ' ' + _('(owner permissions off)') elif owner_toggle == 1: prompt_mode = mode elif owner_toggle == 2: prompt_mode = allow_mode | owner_flatten_mode(mode & ~allow_mode) - tail = ' ' + gettext('(force new perms to owner)') + tail = ' ' + _('(force new perms to owner)') else: prompt_mode = owner_flatten_mode(mode) - tail = ' ' + gettext('(force all rule perms to owner)') + tail = ' ' + _('(force all rule perms to owner)') if audit_toggle == 1: s = mode_to_str_user(allow_mode) @@ -2026,8 +2027,8 @@ def ask_the_questions(): else: s = mode_to_str_user(prompt_mode) + tail - q['headers'] += [gettext('Old Mode'), mode_to_str_user(allow_mode), - gettext('New Mode'), s] + q['headers'] += [_('Old Mode'), mode_to_str_user(allow_mode), + _('New Mode'), s] else: s = '' @@ -2037,17 +2038,17 @@ def ask_the_questions(): s = 'audit' if owner_toggle == 0: prompt_mode = flatten_mode(mode) - tail = ' ' + gettext('(owner permissions off)') + tail = ' ' + _('(owner permissions off)') elif owner_toggle == 1: prompt_mode = mode else: prompt_mode = owner_flatten_mode(mode) - tail = ' ' + gettext('(force perms to owner)') + tail = ' ' + _('(force perms to owner)') s = mode_to_str_user(prompt_mode) - q['headers'] += [gettext('Mode'), s] + q['headers'] += [_('Mode'), s] - q['headers'] += [gettext('Severity'), severity] + q['headers'] += [_('Severity'), severity] q['options'] = options q['selected'] = default_option - 1 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_GLOB', @@ -2083,9 +2084,9 @@ def ask_the_questions(): deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True changed[profile] = True - UI_Info(gettext('Adding %s to profile.') % path) + UI_Info(_('Adding %s to profile.') % path) if deleted: - UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: if aa[profile][hat]['allow']['path'][path].get('mode', False): @@ -2121,9 +2122,9 @@ def ask_the_questions(): changed[profile] = True - UI_Info(gettext('Adding %s %s to profile') % (path, mode_to_str_user(mode))) + UI_Info(_('Adding %s %s to profile') % (path, mode_to_str_user(mode))) if deleted: - UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + UI_Info(_('Deleted %s previous matching profile entries.') % deleted) elif ans == 'CMD_DENY': # Add new entry? @@ -2138,13 +2139,13 @@ def ask_the_questions(): elif ans == 'CMD_NEW': arg = options[selected] if not re_match_include(arg): - ans = UI_GetString(gettext('Enter new path:'), arg) + ans = UI_GetString(_('Enter new path:'), arg) if ans: if not matchliteral(ans, path): - ynprompt = gettext('The specified path does not match this log entry:') - ynprompt += '\n\n ' + gettext('Log Entry') + ': %s' % path - ynprompt += '\n ' + gettext('Entered Path') + ': %s' % ans - ynprompt += gettext('Do you really want to use this path?') + '\n' + ynprompt = _('The specified path does not match this log entry:') + ynprompt += '\n\n ' + _('Log Entry') + ': %s' % path + ynprompt += '\n ' + _('Entered Path') + ': %s' % ans + ynprompt += _('Do you really want to use this path?') + '\n' key = UI_YesNo(ynprompt, 'n') if key == 'n': continue @@ -2229,9 +2230,9 @@ def ask_the_questions(): q['options'] = options q['selected'] = default_option - 1 - q['headers'] = [gettext('Profile'), combine_name(profile, hat)] - q['headers'] += [gettext('Network Family'), family] - q['headers'] += [gettext('Socket Type'), sock_type] + q['headers'] = [_('Profile'), combine_name(profile, hat)] + q['headers'] += [_('Network Family'), family] + q['headers'] += [_('Socket Type'), sock_type] audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', @@ -2260,9 +2261,9 @@ def ask_the_questions(): else: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] - q['headers'] = [gettext('Profile'), combine_name(profile, hat)] - q['headers'] += [gettext('Network Family'), audit + family] - q['headers'] += [gettext('Socket Type'), sock_type] + q['headers'] = [_('Profile'), combine_name(profile, hat)] + q['headers'] += [_('Network Family'), audit + family] + q['headers'] += [_('Socket Type'), sock_type] elif ans == 'CMD_ALLOW': selection = options[selected] @@ -2276,9 +2277,9 @@ def ask_the_questions(): changed[profile] = True - UI_Info(gettext('Adding %s to profile') % selection) + UI_Info(_('Adding %s to profile') % selection) if deleted: - UI_Info(gettext('Deleted %s previous matching profile entries.') % deleted) + UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle @@ -2286,13 +2287,13 @@ def ask_the_questions(): changed[profile] = True - UI_Info(gettext('Adding network access %s %s to profile.' % (family, sock_type))) + UI_Info(_('Adding network access %s %s to profile.' % (family, sock_type))) elif ans == 'CMD_DENY': done = True aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True changed[profile] = True - UI_Info(gettext('Denying network access %s %s to profile') % (family, sock_type)) + UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) else: done = False @@ -2420,7 +2421,7 @@ def match_net_includes(profile, family, nettype): return newincludes -def do_logprof_pass(logmark=''): +def do_logprof_pass(logmark='', sev_db=sev_db): # set up variables for this pass t = hasher() transitions = hasher() @@ -2434,13 +2435,13 @@ def do_logprof_pass(logmark=''): skip = hasher() filelist = hasher() - UI_Info(gettext('Reading log entries from %s.') %filename) - UI_Info(gettext('Updating AppArmor profiles in %s.') %profile_dir) + UI_Info(_('Reading log entries from %s.') %filename) + UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) read_profiles() if not sev_db: - sev_db = apparmor.severity(CONFDIR + '/severity', gettext('unknown')) + sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) ##if not repo_cf and cfg['repostory']['url']: ## repo_cfg = read_config('repository.conf') @@ -2499,8 +2500,8 @@ def save_profiles(): oldprofile = serialize_profile(original_aa[prof], prof) newprofile = serialize_profile(aa[prof], prof) profile_changes[prof] = get_profile_diff(oldprofile, newprofile) - explanation = gettext('Select which profile changes you would like to save to the\nlocal profile set.') - title = gettext('Local profile changes') + explanation = _('Select which profile changes you would like to save to the\nlocal profile set.') + title = _('Local profile changes') SendDataToYast({ 'type': 'dialog-select-profiles', 'title': title, @@ -2522,7 +2523,7 @@ def save_profiles(): q = hasher() q['title'] = 'Changed Local Profiles' q['headers'] = [] - q['explanation'] = gettext('The following local profiles were changed. Would you like to save them?') + q['explanation'] = _('The following local profiles were changed. Would you like to save them?') q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = changed @@ -2578,7 +2579,7 @@ def get_profile_diff(oldprofile, newprofile): def display_changes(oldprofile, newprofile): if UI_mode == 'yast': - UI_LongMessage(gettext('Profile Changes'), get_profile_diff(oldprofile, newprofile)) + UI_LongMessage(_('Profile Changes'), get_profile_diff(oldprofile, newprofile)) else: difftemp = generate_diff(oldprofile, newprofile) subprocess.call('less %s' %difftemp.name, shell=True) @@ -3007,7 +3008,11 @@ def parse_profile_data(data, file, do_include): profile = matches[1] else: profile = matches[3] - profile, hat = profile.split('//')[:2] + #print(profile) + if len(profile.split('//')) >= 2: + profile, hat = profile.split('//')[:2] + else: + hat = None in_contained_hat = False if hat: profile_data[profile][hat]['external'] = True @@ -3319,7 +3324,7 @@ def parse_profile_data(data, file, do_include): if line.startswith('# vim:syntax') or line.startswith('# Last Modified:'): continue line = line.split() - if line[1] == 'REPOSITORY:': + if len(line) > 1 and line[1] == 'REPOSITORY:': if len(line) == 3: repo_data = {'neversubmit': True} elif len(line) == 5: @@ -3327,7 +3332,7 @@ def parse_profile_data(data, file, do_include): 'user': line[3], 'id': line[4]} else: - initial_comment = line + '\n' + initial_comment = ' '.join(line) + '\n' else: raise AppArmorException('Syntax Error: Unknown line found in file: %s line: %s' % (file, lineno+1)) @@ -3371,7 +3376,9 @@ def store_list_var(var, list_var, value, var_operation): if not var.get(list_var, False): var[list_var] = set(vlist) else: - raise AppArmorException('An existing variable redefined') + print('Ignoring: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) + pass + #raise AppArmorException('An existing variable redefined') else: if var.get(list_var, False): var[list_var] = set(var[list_var] + vlist) @@ -3712,7 +3719,7 @@ def serialize_profile(profile_data, name, options): return string+'\n' def write_profile_ui_feedback(profile): - UI_Info(gettext('Writing updated profile for %s.') %profile) + UI_Info(_('Writing updated profile for %s.') %profile) write_profile(profile) def write_profile(profile): @@ -3856,7 +3863,7 @@ def load_include(incname): incfile = load_includeslist.pop(0) data = get_include_data(incfile) incdata = parse_profile_data(data, incfile, True) - print(incdata) + #print(incdata) if incdata: attach_profile_data(include, incdata) @@ -3942,7 +3949,7 @@ def suggest_incs_for_path(incname, path, allow): def check_qualifiers(program): if cfg['qualifiers'].get(program, False): if cfg['qualifiers'][program] != 'p': - fatal_error(gettext('%s is currently marked as a program that should not have its own\n' + + fatal_error(_('%s is currently marked as a program that should not have its own\n' + 'profile. Usually, programs are marked this way if creating a profile for \n' + 'them is likely to break the rest of the system. If you know what you\'re\n' + 'doing and are certain you want to create a profile for this program, edit\n' + diff --git a/apparmor/ui.py b/apparmor/ui.py index 25d71e5b5..155c36c44 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -21,16 +21,6 @@ if os.getenv('LOGPROF_DEBUG', False): # The operating mode: yast or text, text by default UI_mode = 'text' -def init_localisation(): - locale.setlocale(locale.LC_ALL, '') - #cur_locale = locale.getlocale() - filename = 'res/messages_%s.mo' % locale.getlocale()[0][0:2] - try: - trans = gettext.GNUTranslations(open( filename, 'rb')) - except IOError: - trans = gettext.NullTranslations() - trans.install() - ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'} def getkey(): @@ -300,18 +290,18 @@ def Text_PromptUser(question): for cmd in functions: if not CMDS.get(cmd, False): - raise AppArmorException('PromptUser: %s %s' %(gettext('Unknown command'), cmd)) + raise AppArmorException('PromptUser: %s %s' %(_('Unknown command'), cmd)) - menutext = gettext(CMDS[cmd]) + menutext = _(CMDS[cmd]) menuhotkey = re.search('\((\S)\)', menutext) if not menuhotkey: - raise AppArmorException('PromptUser: %s \'%s\'' %(gettext('Invalid hotkey in'), menutext)) + raise AppArmorException('PromptUser: %s \'%s\'' %(_('Invalid hotkey in'), menutext)) key = menuhotkey.groups()[0].lower() # Duplicate hotkey if keys.get(key, False): - raise AppArmorException('PromptUser: %s %s: %s' %(gettext('Duplicate hotkey for'), cmd, menutext)) + raise AppArmorException('PromptUser: %s %s: %s' %(_('Duplicate hotkey for'), cmd, menutext)) keys[key] = cmd @@ -322,16 +312,16 @@ def Text_PromptUser(question): default_key = 0 if default and CMDS[default]: - defaulttext = gettext(CMDS[default]) + defaulttext = _(CMDS[default]) defaulthotkey = re.search('\((\S)\)', defaulttext) if not menuhotkey: - raise AppArmorException('PromptUser: %s \'%s\'' %(gettext('Invalid hotkey in default item'), defaulttext)) + raise AppArmorException('PromptUser: %s \'%s\'' %(_('Invalid hotkey in default item'), defaulttext)) default_key = defaulthotkey.groups()[0].lower() if keys.get(default_key, False): - raise AppArmorException('PromptUser: %s %s' %(gettext('Invalid default'), default)) + raise AppArmorException('PromptUser: %s %s' %(_('Invalid default'), default)) widest = 0 header_copy = headers[:] From da49e6a3ee0bb09b380437396e75aef1304f5b20 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 8 Aug 2013 21:40:56 +0530 Subject: [PATCH 036/183] fixed allow --- apparmor/aa.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 76147107a..6757150f6 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2967,7 +2967,7 @@ def parse_profile_data(data, file, do_include): RE_PROFILE_START = re.compile('^(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') RE_PROFILE_END = re.compile('^\}\s*(#.*)?$') RE_PROFILE_CAP = re.compile('^(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') - RE_PROFILE_SET_CAP = re.compile('^set capability\s+(\S+)\s*,\s*(#.*)?$') + #RE_PROFILE_SET_CAP = re.compile('^set capability\s+(\S+)\s*,\s*(#.*)?$') RE_PROFILE_LINK = re.compile('^(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') RE_PROFILE_CHANGE_PROFILE = re.compile('^change_profile\s+->\s*("??.+?"??),(#.*)?$') RE_PROFILE_ALIAS = re.compile('^alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') @@ -3076,15 +3076,15 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat][allow]['capability'][capability]['set'] = True profile_data[profile][hat][allow]['capability'][capability]['audit'] = audit - elif RE_PROFILE_SET_CAP.search(line): - matches = RE_PROFILE_SET_CAP.search(line).groups() - - if not profile: - raise AppArmorException('Syntax Error: Unexpected capability entry found in file: %s line: %s' % (file, lineno+1)) - - capability = matches[0] - profile_data[profile][hat]['set_capability'][capability] = True - +# elif RE_PROFILE_SET_CAP.search(line): +# matches = RE_PROFILE_SET_CAP.search(line).groups() +# +# if not profile: +# raise AppArmorException('Syntax Error: Unexpected capability entry found in file: %s line: %s' % (file, lineno+1)) +# +# capability = matches[0] +# profile_data[profile][hat]['set_capability'][capability] = True +# elif RE_PROFILE_LINK.search(line): matches = RE_PROFILE_LINK.search(line).groups() @@ -3438,13 +3438,13 @@ def set_allow_str(allow): if allow == 'deny': return 'deny ' else: - return '' + return 'allow' def set_ref_allow(prof_data, allow): if allow: return prof_data[allow], set_allow_str(allow) else: - return prof_data, '' + return prof_data, 'allow' def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn): @@ -3499,8 +3499,8 @@ def write_cap_rules(prof_data, depth, allow): return data def write_capabilities(prof_data, depth): - data = write_single(prof_data, depth, '', 'set_capability', 'set capability ', ',') - data += write_cap_rules(prof_data, depth, 'deny') + #data = write_single(prof_data, depth, '', 'set_capability', 'set capability ', ',') + data = write_cap_rules(prof_data, depth, 'deny') data += write_cap_rules(prof_data, depth, 'allow') return data From 2d9f37be8744a7206a094aa8d115b39a64de91bf Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 9 Aug 2013 11:04:32 +0530 Subject: [PATCH 037/183] fixed debugglogger --- apparmor/aa.py | 47 +++++++++++++++++++--------------------------- apparmor/common.py | 20 ++++++++++++++++++++ apparmor/ui.py | 37 ++++++++++-------------------------- apparmor/yasti.py | 34 +++++++++++---------------------- 4 files changed, 60 insertions(+), 78 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 6757150f6..bbc850042 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -5,7 +5,6 @@ #global variable names corruption from __future__ import with_statement import inspect -import logging import os import re import shutil @@ -23,21 +22,13 @@ import LibAppArmor from apparmor.common import (AppArmorException, error, debug, msg, open_file_read, readkey, valid_path, - hasher, open_file_write, convert_regexp) + hasher, open_file_write, convert_regexp, DebugLogger) from apparmor.ui import * from copy import deepcopy -DEBUGGING = False -debug_logger = None - # Setup logging incase of debugging is enabled -if os.getenv('LOGPROF_DEBUG', False): - DEBUGGING = True - logprof_debug = '/var/log/apparmor/logprof.log' - logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) - debug_logger = logging.getLogger('logprof') - +debug_logger = DebugLogger('aa') CONFDIR = '/etc/apparmor' running_under_genprof = False @@ -151,9 +142,8 @@ OPERATION_TYPES = { def on_exit(): """Shutdowns the logger and records exit if debugging enabled""" - if DEBUGGING: - debug_logger.debug('Exiting..') - logging.shutdown() + debug_logger.debug('Exiting..') + debug_logger.shutdown() # Register the on_exit method with atexit atexit.register(on_exit) @@ -180,13 +170,12 @@ def check_for_LD_XXX(file): return found def fatal_error(message): - if DEBUGGING: - # Get the traceback to the message - tb_stack = traceback.format_list(traceback.extract_stack()) - tb_stack = ''.join(tb_stack) - # Append the traceback to message - message = message + '\n' + tb_stack - debug_logger.error(message) + # Get the traceback to the message + tb_stack = traceback.format_list(traceback.extract_stack()) + tb_stack = ''.join(tb_stack) + # Append the traceback to message + message = message + '\n' + tb_stack + debug_logger.error(message) caller = inspect.stack()[1][3] # If caller is SendDataToYast or GetDatFromYast simply exit @@ -444,8 +433,8 @@ def create_new_profile(localfile): local_profile[hat]['flags'] = 'complain' created.append(localfile) - if DEBUGGING: - debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) + + debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) return {localfile: local_profile} def delete_profile(local_prof): @@ -1400,8 +1389,7 @@ def handle_children(profile, hat, root): return None def add_to_tree(loc_pid, parent, type, event): - if DEBUGGING: - debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (pid, type, event)) + debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (pid, type, event)) if not pid.get(loc_pid, False): profile, hat = event[:1] @@ -1462,8 +1450,7 @@ def throw_away_next_log_entry(): next_log_entry = None def parse_log_record(record): - if DEBUGGING: - debug_logger.debug('parse_log_record: %s' % record) + debug_logger.debug('parse_log_record: %s' % record) record_event = parse_event(record) return record_event @@ -1618,7 +1605,11 @@ def read_log(logmark): def parse_event(msg): """Parse the event from log into key value pairs""" msg = msg.strip() - #debug_logger.log('parse_event: %s' % msg) + debug_logger.info('parse_event: %s' % msg) + #print(repr(msg)) + if sys.version_info < (3,0): + # parse_record fails with u'foo' style strings hence typecasting to string + msg = str(msg) event = LibAppArmor.parse_record(msg) ev = dict() ev['resource'] = event.info diff --git a/apparmor/common.py b/apparmor/common.py index 3565040be..ed4a8d10c 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -2,6 +2,7 @@ from __future__ import print_function import codecs import collections import glob +import logging import os import re import subprocess @@ -188,3 +189,22 @@ def convert_regexp(regexp): new_reg = new_reg + '$' return new_reg +class DebugLogger: + def __init__(self, module_name=__name__): + logging.basicConfig(filename='/var/log/apparmor/logprof.log') + self.logger = logging.getLogger(module_name) + self.debugging = True + if os.getenv('LOGPROF_DEBUG', False): + self.debugging = True + def error(self, msg): + if self.debugging: + self.logger.error(msg) + def info(self, msg): + if self.debugging: + self.logger.info(msg) + def debug(self, msg): + if self.debugging: + self.logger.debug(msg) + def shutdown(self): + logging.shutdown() + #logging.shutdown([self.logger]) \ No newline at end of file diff --git a/apparmor/ui.py b/apparmor/ui.py index 155c36c44..42acb7de3 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -1,22 +1,13 @@ # 1728 import sys -import gettext -import locale -import logging import os import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast -from apparmor.common import readkey, AppArmorException +from apparmor.common import readkey, AppArmorException, DebugLogger -DEBUGGING = False -debug_logger = None # Set up UI logger for separate messages from UI module -if os.getenv('LOGPROF_DEBUG', False): - DEBUGGING = True - logprof_debug = '/var/log/apparmor/logprof.log' - logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) - debug_logger = logging.getLogger('UI') +debug_logger = DebugLogger('UI') # The operating mode: yast or text, text by default UI_mode = 'text' @@ -34,16 +25,14 @@ def getkey(): return key def UI_Info(text): - if DEBUGGING: - debug_logger.info(text) + debug_logger.info(text) if UI_mode == 'text': sys.stdout.write(text + '\n') else: yastLog(text) def UI_Important(text): - if DEBUGGING: - debug_logger.debug(text) + debug_logger.debug(text) if UI_mode == 'text': sys.stdout.write('\n' + text + '\n') else: @@ -54,8 +43,7 @@ def UI_Important(text): path, yarg = GetDataFromYast() def UI_YesNo(text, default): - if DEBUGGING: - debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default)) + debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default)) ans = default if UI_mode == 'text': yes = '(Y)es' @@ -85,8 +73,7 @@ def UI_YesNo(text, default): return ans def UI_YesNoCancel(text, default): - if DEBUGGING: - debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default)) + debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default)) if UI_mode == 'text': yes = '(Y)es' @@ -121,8 +108,7 @@ def UI_YesNoCancel(text, default): return ans def UI_GetString(text, default): - if DEBUGGING: - debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default)) + debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default)) string = default if UI_mode == 'text': sys.stdout.write('\n' + text + '\n') @@ -138,8 +124,7 @@ def UI_GetString(text, default): return string def UI_GetFile(file): - if DEBUGGING: - debug_logger.debug('UI_GetFile: %s' % UI_mode) + debug_logger.debug('UI_GetFile: %s' % UI_mode) filename = None if UI_mode == 'text': sys.stdout.write(file['description'] + '\n') @@ -153,8 +138,7 @@ def UI_GetFile(file): return filename def UI_BusyStart(message): - if DEBUGGING: - debug_logger.debug('UI_BusyStart: %s' % UI_mode) + debug_logger.debug('UI_BusyStart: %s' % UI_mode) if UI_mode == 'text': UI_Info(message) else: @@ -165,8 +149,7 @@ def UI_BusyStart(message): ypath, yarg = GetDataFromYast() def UI_BusyStop(): - if DEBUGGING: - debug_logger.debug('UI_BusyStop: %s' % UI_mode) + debug_logger.debug('UI_BusyStop: %s' % UI_mode) if UI_mode != 'text': SendDataToYast({'type': 'dialog-busy-stop'}) ypath, yarg = GetDataFromYast() diff --git a/apparmor/yasti.py b/apparmor/yasti.py index 8eed768cf..2d022b3a2 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -2,17 +2,12 @@ import re #import ycp import os import sys -import logging -from apparmor.common import error -DEBUGGING = False -debug_logger = None +from apparmor.common import error, DebugLogger + # Set up UI logger for separate messages from YaST module -if os.getenv('LOGPROF_DEBUG', False): - DEBUGGING = True - logprof_debug = '/var/log/apparmor/logprof.log' - logging.basicConfig(filename=logprof_debug, level=logging.DEBUG) - debug_logger = logging.getLogger('YaST') +debug_logger = DebugLogger('YaST') + def setup_yast(): # To-Do @@ -26,35 +21,28 @@ def yastLog(text): ycp.y2milestone(text) def SendDataToYast(data): - if DEBUGGING: - debug_logger.info('SendDataToYast: Waiting for YCP command') + debug_logger.info('SendDataToYast: Waiting for YCP command') for line in sys.stdin: ycommand, ypath, yargument = ParseCommand(line) if ycommand and ycommand == 'Read': - if DEBUGGING: - debug_logger.info('SendDataToYast: Sending--%s' % data) + debug_logger.info('SendDataToYast: Sending--%s' % data) Return(data) return True else: - if DEBUGGING: - debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) + debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) error('SendDataToYast: didn\'t receive YCP command before connection died') def GetDataFromYast(): - if DEBUGGING: - debug_logger.inf('GetDataFromYast: Waiting for YCP command') + debug_logger.inf('GetDataFromYast: Waiting for YCP command') for line in sys.stdin: - if DEBUGGING: - debug_logger.info('GetDataFromYast: YCP: %s' % line) + debug_logger.info('GetDataFromYast: YCP: %s' % line) ycommand, ypath, yarg = ParseCommand(line) - if DEBUGGING: - debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg) + debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg) if ycommand and ycommand == 'Write': Return('true') return ypath, yarg else: - if DEBUGGING: - debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) + debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) error('GetDataFromYast: didn\'t receive YCP command before connection died') def ParseCommand(commands): From a9c594d5bc01d4502b3b5fbf509285c914f0bca4 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 9 Aug 2013 16:49:01 +0530 Subject: [PATCH 038/183] fixed test encoded data for log entries --- Testing/aa_test.py | 22 +++++++++++++------- apparmor/aa.py | 52 +++++++++++++++++++++++++--------------------- apparmor/common.py | 3 ++- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/Testing/aa_test.py b/Testing/aa_test.py index d005b355e..8bbeb63ea 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -13,17 +13,25 @@ class Test(unittest.TestCase): def test_parse_event(self): - event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_balmar" name=2F686F6D652F7777772F62616C6D61722E646174616E6F766F322E64652F68747470646F63732F6A6F6F6D6C612F696D616765732F6B75656368656E2F666F746F20322E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' + event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' parsed_event = apparmor.aa.parse_event(event) - print(parsed_event) + self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg', 'Incorrectly parsed/decoded name') + self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo', 'Incorrectly parsed/decode profile name') + self.assertEqual(parsed_event['aamode'], 'PERMITTING') - event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0' - parsed_event = apparmor.aa.parse_event(event) - print(parsed_event) + #print(parsed_event) - event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=74657374207370616365 name="/home/jj/.bash_history" pid=17011 comm="bash" requested_mask="w" denied_mask="w" fsuid=0 ouid=1000' + #event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0' + #parsed_event = apparmor.aa.parse_event(event) + #print(parsed_event) + + event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="w" denied_mask="w" fsuid=0 ouid=1000' parsed_event = apparmor.aa.parse_event(event) - print(parsed_event) + self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name') + self.assertEqual(parsed_event['profile'], 'foo bar', 'Incorrectly parsed/decode profile name') + self.assertEqual(parsed_event['aamode'], 'PERMITTING') + + #print(parsed_event) def test_modes_to_string(self): diff --git a/apparmor/aa.py b/apparmor/aa.py index bbc850042..6f467372e 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -21,7 +21,7 @@ import apparmor.severity import LibAppArmor from apparmor.common import (AppArmorException, error, debug, msg, - open_file_read, readkey, valid_path, + open_file_read, valid_path, hasher, open_file_write, convert_regexp, DebugLogger) from apparmor.ui import * @@ -2957,19 +2957,19 @@ def parse_profile_data(data, file, do_include): initial_comment = '' RE_PROFILE_START = re.compile('^(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') RE_PROFILE_END = re.compile('^\}\s*(#.*)?$') - RE_PROFILE_CAP = re.compile('^(audit\s+)?(deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') + RE_PROFILE_CAP = re.compile('^(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') #RE_PROFILE_SET_CAP = re.compile('^set capability\s+(\S+)\s*,\s*(#.*)?$') - RE_PROFILE_LINK = re.compile('^(audit\s+)?(deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') + RE_PROFILE_LINK = re.compile('^(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') RE_PROFILE_CHANGE_PROFILE = re.compile('^change_profile\s+->\s*("??.+?"??),(#.*)?$') RE_PROFILE_ALIAS = re.compile('^alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') - RE_PROFILE_RLIMIT = re.compile('^set\s+rlimit\s+(.+)\s+<=\s*(.+)\s*,(#.*)?$') - RE_PROFILE_BOOLEAN = re.compile('^(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE) + RE_PROFILE_RLIMIT = re.compile('^set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)\s*,(#.*)?$') + RE_PROFILE_BOOLEAN = re.compile('^(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE) RE_PROFILE_VARIABLE = re.compile('^(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.*)?$') - RE_PROFILE_CONDITIONAL = re.compile('^if\s+(not\s+)?(\$\{?[[:alpha:]][[:alnum:]_]*\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^if\s+(not\s+)?defined\s+(@\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^if\s+(not\s+)?defined\s+(\$\{?[[:alpha:]][[:alnum:]_]+\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_PATH_ENTRY = re.compile('^(audit\s+)?(deny\s+)?(owner\s+)?([\"\@\/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') - RE_PROFILE_NETWORK = re.compile('^(audit\s+)?(deny\s+)?network(.*)\s*(#.*)?$') + RE_PROFILE_CONDITIONAL = re.compile('^if\s+(not\s+)?(\$\{?\w*\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$') + RE_PROFILE_PATH_ENTRY = re.compile('^(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') + RE_PROFILE_NETWORK = re.compile('^(audit\s+)?(allow\s+|deny\s+)?network(.*)\s*(#.*)?$') RE_PROFILE_CHANGE_HAT = re.compile('^\^(\"??.+?\"??)\s*,\s*(#.*)?$') RE_PROFILE_HAT_DEF = re.compile('^\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') if do_include: @@ -3059,7 +3059,7 @@ def parse_profile_data(data, file, do_include): audit = True allow = 'allow' - if matches[1]: + if matches[1] and matches[1].strip() == 'deny': allow = 'deny' capability = matches[2] @@ -3087,7 +3087,7 @@ def parse_profile_data(data, file, do_include): audit = True allow = 'allow' - if matches[1]: + if matches[1] and matches[1].strip() == 'deny': allow = 'deny' subset = matches[3] @@ -3133,7 +3133,7 @@ def parse_profile_data(data, file, do_include): raise AppArmorException('Syntax Error: Unexpected rlimit entry found in file: %s line: %s' % (file, lineno+1)) from_name = matches[0] - to_name = matches[1] + to_name = matches[2] profile_data[profile][hat]['rlimit'][from_name] = to_name @@ -3188,7 +3188,7 @@ def parse_profile_data(data, file, do_include): audit = True allow = 'allow' - if matches[1]: + if matches[1] and matches[1].strip() == 'deny': allow = 'deny' user = False @@ -3258,17 +3258,18 @@ def parse_profile_data(data, file, do_include): if matches[0]: audit = True allow = 'allow' - if matches[1]: + if matches[1] and matches[1].strip() == 'deny': allow = 'deny' network = matches[2] - - if re.search('\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$', network): - nmatch = re.search('\s+(\S+)\s+(\S+)\s*,\s*(#.*)?$', network).groups() + RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$') + RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$') + if RE_NETWORK_FAMILY_TYPE.search(network): + nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() fam, typ = nmatch[:2] profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = True profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit - elif re.search('\s+(\S+)\s*,\s*(#.*)?$', network): - fam = re.search('\s+(\S+)\s*,\s*(#.*)?$', network).groups()[0] + elif RE_NETWORK_FAMILY.search(network): + fam = RE_NETWORK_FAMILY.search(network).groups()[0] profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True profile_data[profile][hat][allow]['netdomain']['audit'][fam] = audit else: @@ -3306,7 +3307,8 @@ def parse_profile_data(data, file, do_include): if initial_comment: profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' - + if filelist[file]['profiles'][profile].get(hat, False): + raise AppArmorException('Error: Multiple definitions for hat %s in profile %s.' %(hat, profile)) filelist[file]['profiles'][profile][hat] = True elif line[0] == '#': @@ -3339,11 +3341,12 @@ def parse_profile_data(data, file, do_include): # End of file reached but we're stuck in a profile if profile and not do_include: - raise AppArmorException('Syntax Error: Reached end of file %s while inside profile %s' % (file, profile)) + raise AppArmorException("Syntax Error: Missing '}' . Reached end of file %s while inside profile %s" % (file, profile)) return profile_data def separate_vars(vs): + """Returns a list of all the values for a variable""" data = [] #data = [i.strip('"') for i in vs.split()] @@ -3362,12 +3365,13 @@ def is_active_profile(pname): return False def store_list_var(var, list_var, value, var_operation): + """Store(add new variable or add values to variable) the variables encountered in the given list_var""" vlist = separate_vars(value) if var_operation == '=': if not var.get(list_var, False): var[list_var] = set(vlist) else: - print('Ignoring: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) + print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) pass #raise AppArmorException('An existing variable redefined') else: @@ -3401,7 +3405,7 @@ def write_header(prof_data, depth, name, embedded_hat, write_flags): data = [] name = quote_if_needed(name) - if (not embedded_hat and re.search('^[^\/]|^"[^\/]', name)) or (embedded_hat and re.search('^[^^]' ,name)): + if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]' ,name)): name = 'profile %s' % name if write_flags and prof_data['flags']: diff --git a/apparmor/common.py b/apparmor/common.py index ed4a8d10c..85d91404f 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -191,7 +191,8 @@ def convert_regexp(regexp): class DebugLogger: def __init__(self, module_name=__name__): - logging.basicConfig(filename='/var/log/apparmor/logprof.log') + #logging.basicConfig(filename='/var/log/apparmor/logprof.log') + logging.basicConfig(filename='/home/kshitij/logprof.log') self.logger = logging.getLogger(module_name) self.debugging = True if os.getenv('LOGPROF_DEBUG', False): From eacdddaf1294a5cdc6444d2b897a229d1156b3fd Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 10 Aug 2013 01:17:00 +0530 Subject: [PATCH 039/183] working logger --- apparmor/common.py | 7 ++++--- apparmor/ui.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apparmor/common.py b/apparmor/common.py index 85d91404f..842456ec1 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -191,16 +191,17 @@ def convert_regexp(regexp): class DebugLogger: def __init__(self, module_name=__name__): - #logging.basicConfig(filename='/var/log/apparmor/logprof.log') - logging.basicConfig(filename='/home/kshitij/logprof.log') + logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=logging.DEBUG, format='%(asctime)s - %(name)s - %(message)s\n') self.logger = logging.getLogger(module_name) - self.debugging = True + self.debugging = False if os.getenv('LOGPROF_DEBUG', False): self.debugging = True def error(self, msg): if self.debugging: + logging.error(msg) self.logger.error(msg) def info(self, msg): + logging.info(msg) if self.debugging: self.logger.info(msg) def debug(self, msg): diff --git a/apparmor/ui.py b/apparmor/ui.py index 42acb7de3..bb6088288 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -367,9 +367,9 @@ def Text_PromptUser(question): selected += 1 ans = 'XXXINVALIDXXX' - elif keys.get(ans, False) == 'CMD_HELP': - sys.stdout.write('\n%s\n' %helptext) - ans = 'XXXINVALIDXXX' +# elif keys.get(ans, False) == 'CMD_HELP': +# sys.stdout.write('\n%s\n' %helptext) +# ans = 'XXXINVALIDXXX' elif int(ans) == 10: # If they hit return choose default option From 3212422921472f68054ebc433d73e9fa4eb5ecf4 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 10 Aug 2013 12:46:22 +0530 Subject: [PATCH 040/183] fixes from rev 32..39 and fixed(?) flags=(..)thing --- Testing/common_test.py | 73 +++++++---------------------------------- Testing/config_test.py | 5 --- Testing/regex_tests.ini | 38 +++++++++++++++++++++ Tools/aa-logprof.py | 4 +-- apparmor/aa.py | 62 ++++++++++++++-------------------- apparmor/common.py | 28 +++++++++------- apparmor/severity.py | 19 ----------- apparmor/ui.py | 2 +- 8 files changed, 95 insertions(+), 136 deletions(-) create mode 100644 Testing/regex_tests.ini diff --git a/Testing/common_test.py b/Testing/common_test.py index 6d4c85dab..8042954ba 100644 --- a/Testing/common_test.py +++ b/Testing/common_test.py @@ -4,72 +4,23 @@ import sys sys.path.append('../') import apparmor.common +import apparmor.config class Test(unittest.TestCase): def test_RegexParser(self): - regex_1 = '/foo/**/bar/' - parsed_regex_1 = apparmor.common.convert_regexp(regex_1) - compiled_regex_1 = re.compile(parsed_regex_1) - #print(parsed_regex_1) - self.assertEqual(bool(compiled_regex_1.search('/foo/user/tools/bar/')), True, 'Incorrectly Parsed regex') - self.assertEqual(bool(compiled_regex_1.search('/foo/apparmor/bar/')), True, 'Incorrectly Parsed regex') - - self.assertEqual(bool(compiled_regex_1.search('/foo/apparmor/bar')), False, 'Incorrectly Parsed regex') - - regex_2 = '/foo/*/bar/' - parsed_regex_2 = apparmor.common.convert_regexp(regex_2) - compiled_regex_2 = re.compile(parsed_regex_2) - #print(parsed_regex_2) - self.assertEqual(bool(compiled_regex_2.search('/foo/apparmor/bar/')), True, 'Incorrectly Parsed regex') - - self.assertEqual(bool(compiled_regex_2.search('/foo/apparmor/tools/bar/')), False, 'Incorrectly Parsed regex') - self.assertEqual(bool(compiled_regex_2.search('/foo/apparmor/bar')), False, 'Incorrectly Parsed regex') - - regex_3 = '/foo/{foo,bar,user,other}/bar/' - parsed_regex_3 = apparmor.common.convert_regexp(regex_3) - compiled_regex_3 = re.compile(parsed_regex_3) - #print(parsed_regex_3) - self.assertEqual(bool(compiled_regex_3.search('/foo/user/bar/')), True, 'Incorrectly Parsed regex') - self.assertEqual(bool(compiled_regex_3.search('/foo/bar/bar/')), True, 'Incorrectly Parsed regex') - - self.assertEqual(bool(compiled_regex_3.search('/foo/wrong/bar/')), False, 'Incorrectly Parsed regex') - - regex_4 = '/foo/user/ba?/' - parsed_regex_4 = apparmor.common.convert_regexp(regex_4) - compiled_regex_4 = re.compile(parsed_regex_4) - #print(parsed_regex_4) - - self.assertEqual(bool(compiled_regex_4.search('/foo/user/bar/')), True, 'Incorrectly Parsed regex') - - self.assertEqual(bool(compiled_regex_4.search('/foo/user/bar/apparmor/')), False, 'Incorrectly Parsed regex') - self.assertEqual(bool(compiled_regex_4.search('/foo/user/ba/')), False, 'Incorrectly Parsed regex') - - regex_5 = '/foo/user/bar/**' - parsed_regex_5 = apparmor.common.convert_regexp(regex_5) - compiled_regex_5 = re.compile(parsed_regex_5) - #print(parsed_regex_5) - - self.assertEqual(bool(compiled_regex_5.search('/foo/user/bar/apparmor')), True, 'Incorrectly Parsed regex') - self.assertEqual(bool(compiled_regex_5.search('/foo/user/bar/apparmor/tools')), True, 'Incorrectly Parsed regex') - - self.assertEqual(bool(compiled_regex_5.search('/foo/user/bar/')), False, 'Incorrectly Parsed regex') - - regex_6 = '/foo/user/bar/*' - parsed_regex_6 = apparmor.common.convert_regexp(regex_6) - compiled_regex_6 = re.compile(parsed_regex_6) - #print(parsed_regex_6) - - self.assertEqual(bool(compiled_regex_6.search('/foo/user/bar/apparmor')), True, 'Incorrectly Parsed regex') - - self.assertEqual(bool(compiled_regex_6.search('/foo/user/bar/apparmor/tools')), False, 'Incorrectly Parsed regex') - self.assertEqual(bool(compiled_regex_6.search('/foo/user/bar/')), False, 'Incorrectly Parsed regex') - - - def test_readkey(self): - print("Please press the Y button on the keyboard.") - self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!') + tests=apparmor.config.Config('ini') + tests.CONF_DIR = '.' + regex_tests = tests.read_config('regex_tests.ini') + for regex in regex_tests.sections(): + parsed_regex = re.compile(apparmor.common.convert_regexp(regex)) + for regex_testcase in regex_tests.options(regex): + self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex') + + #def test_readkey(self): + # print("Please press the Y button on the keyboard.") + # self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!') if __name__ == "__main__": diff --git a/Testing/config_test.py b/Testing/config_test.py index 64dbd9bca..edeecd8cd 100644 --- a/Testing/config_test.py +++ b/Testing/config_test.py @@ -1,8 +1,3 @@ -''' -Created on Jul 18, 2013 - -@author: kshitij -''' import unittest import sys diff --git a/Testing/regex_tests.ini b/Testing/regex_tests.ini new file mode 100644 index 000000000..53094e48d --- /dev/null +++ b/Testing/regex_tests.ini @@ -0,0 +1,38 @@ +[/foo/**/bar/] + /foo/user/tools/bar/ = True + /foo/apparmor/bar/ = True + /foo/apparmor/bar = False + +[/foo/*/bar/] + /foo/apparmor/bar/ = True + /foo/apparmor/tools/bar/ = False + /foo/apparmor/bar = False + +[/foo/{foo,bar,user,other}/bar/] + /foo/user/bar/ = True + /foo/bar/bar/ = True + /foo/wrong/bar/ = False + +[/foo/{foo,bar,user,other}/test,ca}se/{aa,sd,nd}/bar/] + /foo/user/test,ca}se/aa/bar/ = True + /foo/bar/test,ca}se/sd/bar/ = True + /foo/wrong/user/bar/ = False + /foo/user/wrong/bar/ = False + /foo/wrong/aa/bar/ = False + +[/foo/user/ba?/] + /foo/user/bar/ = True + /foo/user/bar/apparmor/ = False + /foo/user/ba/ = False + /foo/user/ba// = False + +[/foo/user/bar/**] + /foo/user/bar/apparmor = True + /foo/user/bar/apparmor/tools = True + /foo/user/bar/ = False + +[/foo/user/bar/*] + /foo/user/bar/apparmor = True + /foo/user/bar/apparmor/tools = False + /foo/user/bar/ = False + /foo/user/bar/apparmor/ = False \ No newline at end of file diff --git a/Tools/aa-logprof.py b/Tools/aa-logprof.py index cd7107104..8b42bcb2e 100644 --- a/Tools/aa-logprof.py +++ b/Tools/aa-logprof.py @@ -7,9 +7,9 @@ import os import argparse if sys.version_info < (3,0): - os.environ['PATH'] = '/bin/:/sbin/:/usr/bin/:/usr/sbin' + os.environ['AAPATH'] = '/bin/:/sbin/:/usr/bin/:/usr/sbin' else: - os.environb.putenv('PATH', '/bin/:/sbin/:/usr/bin/:/usr/sbin') + os.environb.putenv('AAPATH', '/bin/:/sbin/:/usr/bin/:/usr/sbin') diff --git a/apparmor/aa.py b/apparmor/aa.py index 6f467372e..ecc02dfa0 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -67,7 +67,7 @@ original_aa = hasher() extras = hasher() # Inactive profiles from extras ### end our log = [] -pid = None +pid = dict() seen = hasher()#dir() profile_changes = dict() @@ -215,7 +215,7 @@ def check_for_apparmor(): def which(file): """Returns the executable fullpath for the file, None otherwise""" - env_dirs = os.getenv('PATH').split(':') + env_dirs = os.getenv('AAPATH').split(':') for env_dir in env_dirs: env_path = env_dir + '/' + file # Test if the path is executable or not @@ -264,7 +264,7 @@ def get_profile_filename(profile): profile = profile[1:] else: profile = "profile_" + profile - profile.replace('/', '.') + profile = profile.replace('/', '.') full_profilename = profile_dir + '/' + profile return full_profilename @@ -583,25 +583,26 @@ def autodep(bin_name, pname=''): def set_profile_flags(prof_filename, newflags): """Reads the old profile file and updates the flags accordingly""" - regex_bin_flag = re.compile('^(\s*)(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+(flags=\(.+\)\s+)*\{\s*$/') - regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') - #a=re.compile('^([a-z]*)\s+([A-Z]*)((\s+#\S*)*)\s*$') - #regex_hat_flag = re.compile('^(\s*\^\S+)\s+(flags=\(.+\)\s+)*\{\s*(#*\S*)$') + regex_bin_flag = re.compile('^(\s*)(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.*)\)\s+)?\{\s*(#.*)?$') + regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: tempfile = tempfile.NamedTemporaryFile('w', prefix=prof_filename , suffix='~', delete=False, dir='/etc/apparmor.d/') shutil.copymode('/etc/apparmor.d/' + prof_filename, tempfile.name) with open_file_write(tempfile.name) as f_out: for line in f_in: + comment = '' if '#' in line: comment = '#' + line.split('#', 1)[1].rstrip() - else: - comment = '' match = regex_bin_flag.search(line) if match: - space, binary, flags = match.groups() + matches = match.groups() + space = matches[0] + binary = matches[1] + flag = matches[6] + flags = matches[7] if newflags: - line = '%s%s flags=(%s) {%s\n' % (space, binary, newflags, comment) + line = '%s%s %s(%s) {%s\n' % (space, binary, flag, newflags, comment) else: line = '%s%s {%s\n' % (space, binary, comment) else: @@ -622,6 +623,7 @@ def profile_exists(program): return True # Check the disk for profile prof_path = get_profile_filename(program) + #print(prof_path) if os.path.isfile(prof_path): # Add to cache of profile existing_profiles[program] = True @@ -1326,7 +1328,7 @@ def handle_children(profile, hat, root): if not os.path.exists(get_profile_filename(exec_target)): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = UI_YesNo(_('A profile for ') + str(exec_target) + _(' doesnot exist.\nDo you want to create one?'), 'n') + ynans = UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') %exec_target, 'n') if ynans == 'y': helpers[exec_target] = 'enforce' if to_name: @@ -1389,10 +1391,9 @@ def handle_children(profile, hat, root): return None def add_to_tree(loc_pid, parent, type, event): - debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (pid, type, event)) - + debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (loc_pid, type, event)) if not pid.get(loc_pid, False): - profile, hat = event[:1] + profile, hat = event[:2] if parent and pid.get(parent, False): if not hat: hat = 'null-complain-profile' @@ -1403,18 +1404,14 @@ def add_to_tree(loc_pid, parent, type, event): # array_ref = [] # log.append(array_ref) # pid[pid] = array_ref - pid[loc_pid] += [type, loc_pid, event] + pid[loc_pid] = pid.get(loc_pid, []) + [type, loc_pid, event] # Variables used by logparsing routines LOG = None next_log_entry = None logmark = None seenmark = None -#RE_LOG_v2_0_syslog = re.compile('SubDomain') -#RE_LOG_v2_1_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?(audit\([\d\.\:]+\):\s+)?type=150[1-6]') RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=') -#RE_LOG_v2_0_audit = re.compile('type=(APPARMOR|UNKNOWN\[1500\]) msg=audit\([\d\.\:]+\):') -#RE_LOG_v2_1_audit = re.compile('type=(UNKNOWN\[150[1-6]\]|APPARMOR_(AUDIT|ALLOWED|DENIED|HINT|STATUS|ERROR))') RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=') MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N') @@ -1484,7 +1481,7 @@ def add_event_to_tree(e): return None # Convert new null profiles to old single level null profile - if '\\null-' in e['profile']: + if '//null-' in e['profile']: e['profile'] = 'null-complain-profile' profile = e['profile'] @@ -1503,7 +1500,7 @@ def add_event_to_tree(e): # prog is no longer passed around consistently prog = 'HINT' - + if profile != 'null-complain-profile' and not profile_exists(profile): return None @@ -1583,7 +1580,7 @@ def read_log(logmark): #last = None #event_type = None try: - print(filename) + #print(filename) log_open = open_file_read(filename) except IOError: raise AppArmorException('Can not read AppArmor logfile: ' + filename) @@ -1751,6 +1748,7 @@ def order_globs(globs, path): def ask_the_questions(): found = None + print(log_dict) for aamode in sorted(log_dict.keys()): # Describe the type of changes if aamode == 'PERMITTING': @@ -2955,10 +2953,9 @@ def parse_profile_data(data, file, do_include): repo_data = None parsed_profiles = [] initial_comment = '' - RE_PROFILE_START = re.compile('^(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') + RE_PROFILE_START = re.compile('^(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$') RE_PROFILE_END = re.compile('^\}\s*(#.*)?$') RE_PROFILE_CAP = re.compile('^(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') - #RE_PROFILE_SET_CAP = re.compile('^set capability\s+(\S+)\s*,\s*(#.*)?$') RE_PROFILE_LINK = re.compile('^(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') RE_PROFILE_CHANGE_PROFILE = re.compile('^change_profile\s+->\s*("??.+?"??),(#.*)?$') RE_PROFILE_ALIAS = re.compile('^alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') @@ -3066,16 +3063,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat][allow]['capability'][capability]['set'] = True profile_data[profile][hat][allow]['capability'][capability]['audit'] = audit - -# elif RE_PROFILE_SET_CAP.search(line): -# matches = RE_PROFILE_SET_CAP.search(line).groups() -# -# if not profile: -# raise AppArmorException('Syntax Error: Unexpected capability entry found in file: %s line: %s' % (file, lineno+1)) -# -# capability = matches[0] -# profile_data[profile][hat]['set_capability'][capability] = True -# + elif RE_PROFILE_LINK.search(line): matches = RE_PROFILE_LINK.search(line).groups() @@ -3373,12 +3361,12 @@ def store_list_var(var, list_var, value, var_operation): else: print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) pass - #raise AppArmorException('An existing variable redefined') + #raise AppArmorException('An existing variable redefined: %s' %list_var) else: if var.get(list_var, False): var[list_var] = set(var[list_var] + vlist) else: - raise AppArmorException('An existing variable redefined') + raise AppArmorException('An existing variable redefined: %s' %list_var) def strip_quotes(data): diff --git a/apparmor/common.py b/apparmor/common.py index 842456ec1..d0d9e100c 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -165,13 +165,12 @@ def convert_regexp(regexp): # new_reg = new_reg.replace('}', '}') # new_reg = new_reg.replace(',', '|') - while re.search('{.*,.*}', new_reg): - match = re.search('(.*){(.*),(.*)}(.*)', new_reg).groups() + while re.search('{[^}]*,[^}]*}', new_reg): + match = re.search('(.*){([^}]*)}(.*)', new_reg).groups() prev = match[0] - after = match[3] + after = match[2] p1 = match[1].replace(',','|') - p2 = match[2].replace(',','|') - new_reg = prev+'('+p1+'|'+p2+')'+after + new_reg = prev+'('+p1+')'+after new_reg = new_reg.replace('?', '[^/\000]') @@ -190,18 +189,25 @@ def convert_regexp(regexp): return new_reg class DebugLogger: - def __init__(self, module_name=__name__): - logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=logging.DEBUG, format='%(asctime)s - %(name)s - %(message)s\n') - self.logger = logging.getLogger(module_name) + def __init__(self, module_name=__name__): self.debugging = False + self.debug_level = logging.DEBUG if os.getenv('LOGPROF_DEBUG', False): - self.debugging = True + self.debugging = os.getenv('LOGPROF_DEBUG') + if self.debugging == 1: + debug_level = logging.ERROR + elif self.debug_level == 2: + debug_level = logging.INFO + + #logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') + logging.basicConfig(filename='/home/kshitij/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') + self.logger = logging.getLogger(module_name) + + def error(self, msg): if self.debugging: - logging.error(msg) self.logger.error(msg) def info(self, msg): - logging.info(msg) if self.debugging: self.logger.info(msg) def debug(self, msg): diff --git a/apparmor/severity.py b/apparmor/severity.py index eb4d87a6b..3783b9732 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -64,25 +64,6 @@ class Severity: else: raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) database.close() - -# def convert_regexp(self, path): -# """Returns the regex form of the path""" -# pattern_or = re.compile('{.*\,.*}') # The regex pattern for {a,b} -# internal_glob = '__KJHDKVZH_AAPROF_INTERNAL_GLOB_SVCUZDGZID__' -# regex = path -# for character in ['.', '+', '[', ']']: # Escape the regex symbols -# regex = regex.replace(character, "\%s" % character) -# # Convert the ** to regex -# regex = regex.replace('**', '.'+internal_glob) -# # Convert the * to regex -# regex = regex.replace('*', '[^/]'+internal_glob) -# # Convert {a,b} to (a|b) form -# if pattern_or.match(regex): -# for character, replacement in zip('{},', '()|'): -# regex = regex.replace(character, replacement) -# # Restore the * in the final regex -# regex = regex.replace(internal_glob, '*') -# return regex def handle_capability(self, resource): """Returns the severity of for the capability resource, default value if no match""" diff --git a/apparmor/ui.py b/apparmor/ui.py index bb6088288..7934d6380 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -4,7 +4,7 @@ import os import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast -from apparmor.common import readkey, AppArmorException, DebugLogger +from apparmor.common import readkey, AppArmorException, DebugLogger, msg # Set up UI logger for separate messages from UI module debug_logger = DebugLogger('UI') From 05e695c7d34207ab149b1b235eb2d795fe6d738e Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 11 Aug 2013 15:22:07 +0530 Subject: [PATCH 041/183] A commit before changing modes style --- Testing/aa_test.py | 6 +- Testing/common_test.py | 2 +- Tools/aa-logprof.py | 7 - Tools/out.out | 651 +++++++++++++++++++++++++++++++++++++++++ apparmor/aa.py | 411 +++++--------------------- apparmor/aamode.py | 258 ++++++++++++++++ apparmor/common.py | 13 +- apparmor/logparser.py | 379 ++++++++++++++++++++++++ apparmor/severity.py | 2 +- apparmor/ui.py | 24 +- 10 files changed, 1394 insertions(+), 359 deletions(-) create mode 100644 Tools/out.out create mode 100644 apparmor/aamode.py create mode 100644 apparmor/logparser.py diff --git a/Testing/aa_test.py b/Testing/aa_test.py index 8bbeb63ea..a18610fc6 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -3,6 +3,7 @@ import sys sys.path.append('../') import apparmor.aa +import apparmor.logparser #from apparmor.aa import parse_event @@ -13,8 +14,9 @@ class Test(unittest.TestCase): def test_parse_event(self): + event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' - parsed_event = apparmor.aa.parse_event(event) + parsed_event = apparmor.logparser.ReadLog.parse_event(apparmor.logparser.ReadLog, event) self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg', 'Incorrectly parsed/decoded name') self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo', 'Incorrectly parsed/decode profile name') self.assertEqual(parsed_event['aamode'], 'PERMITTING') @@ -26,7 +28,7 @@ class Test(unittest.TestCase): #print(parsed_event) event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="w" denied_mask="w" fsuid=0 ouid=1000' - parsed_event = apparmor.aa.parse_event(event) + parsed_event = apparmor.logparser.ReadLog.parse_event(apparmor.logparser.ReadLog, event) self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name') self.assertEqual(parsed_event['profile'], 'foo bar', 'Incorrectly parsed/decode profile name') self.assertEqual(parsed_event['aamode'], 'PERMITTING') diff --git a/Testing/common_test.py b/Testing/common_test.py index 8042954ba..1ff0b5a21 100644 --- a/Testing/common_test.py +++ b/Testing/common_test.py @@ -16,7 +16,7 @@ class Test(unittest.TestCase): for regex in regex_tests.sections(): parsed_regex = re.compile(apparmor.common.convert_regexp(regex)) for regex_testcase in regex_tests.options(regex): - self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex') + self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex: %s' %regex) #def test_readkey(self): # print("Please press the Y button on the keyboard.") diff --git a/Tools/aa-logprof.py b/Tools/aa-logprof.py index 8b42bcb2e..ebc3402aa 100644 --- a/Tools/aa-logprof.py +++ b/Tools/aa-logprof.py @@ -6,13 +6,6 @@ import apparmor.aa import os import argparse -if sys.version_info < (3,0): - os.environ['AAPATH'] = '/bin/:/sbin/:/usr/bin/:/usr/sbin' -else: - os.environb.putenv('AAPATH', '/bin/:/sbin/:/usr/bin/:/usr/sbin') - - - logmark = '' apparmor.aa.loadincludes() diff --git a/Tools/out.out b/Tools/out.out new file mode 100644 index 000000000..bd91bd41b --- /dev/null +++ b/Tools/out.out @@ -0,0 +1,651 @@ +Reading log entries from /var/log/messages. +Updating AppArmor profiles in /etc/apparmor.d. + ARRAY(0x1ac8bb8) ARRAY(0x1af2b80) ARRAY(0xbbe5f8) ARRAY(0x1b06848) ARRAY(0x1af2bb0) ARRAY(0x1af0a20) ARRAY(0x1b0ab68) ARRAY(0x1ab3918) ARRAY(0x1a865b0) ARRAY(0x1b753e8) ARRAY(0x1afc510) ARRAY(0x1abf788) + be dumper@event = ( + [ + 'path', + 660, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ); +$VAR2 = [ + [ + 'path', + 694, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR3 = [ + [ + 'path', + 659, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR4 = [ + [ + 'path', + 642, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR5 = [ + [ + 'path', + 676, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR6 = [ + [ + 'path', + 671, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR7 = [ + [ + 'path', + 667, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR8 = [ + [ + 'path', + 661, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR9 = [ + [ + 'path', + 684, + '/usr/sbin/nscd', + '/usr/sbin/nscd', + 'HINT', + 'REJECTING', + 65540, + '/proc/sys/vm/overcommit_memory', + '' + ] + ]; +$VAR10 = [ + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 1114180, + '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/usr/lib64/empathy/libempathy-3.6.3.so', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 1114180, + '/usr/lib64/empathy/libempathy-3.6.3.so', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/etc/ld.so.cache', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/usr/lib64/libdbus-glib-1.so.2.2.2', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 32770, + '/home/kshitij/.config/Empathy/geometry.ini.7YRV1W', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/home/kshitij/.config/Empathy/geometry.ini.7YRV1W', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/home/kshitij/.config/Empathy/geometry.ini.7YRV1W', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 32770, + '/home/kshitij/.config/Empathy/geometry.ini', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 98310, + '/run/user/1000/dconf/user', + '' + ], + [ + 'path', + 7664, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/home/kshitij/.config/dconf/user', + '' + ] + ]; +$VAR11 = [ + [ + 'path', + 7671, + 'null-complain-profile', + 'null-complain-profile', + 'HINT', + 'PERMITTING', + 65540, + '/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg', + '' + ], + [ + 'path', + 7671, + 'null-complain-profile', + 'null-complain-profile', + 'HINT', + 'PERMITTING', + 65540, + '/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg', + '' + ], + [ + 'path', + 7671, + 'null-complain-profile', + 'null-complain-profile', + 'HINT', + 'PERMITTING', + 65540, + '/usr/share/icons/gnome/scalable/actions/list-remove-symbolic.svg', + '' + ], + [ + 'path', + 7671, + 'null-complain-profile', + 'null-complain-profile', + 'HINT', + 'PERMITTING', + 65540, + '/usr/share/icons/gnome/scalable/actions/list-remove-symbolic.svg', + '' + ], + [ + 'path', + 7671, + 'null-complain-profile', + 'null-complain-profile', + 'HINT', + 'PERMITTING', + 65540, + '/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg', + '' + ] + ]; +$VAR12 = [ + [ + 'path', + 7753, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', + '' + ], + [ + 'path', + 7753, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 1114180, + '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', + '' + ], + [ + 'path', + 7753, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/usr/lib64/empathy/libempathy-3.6.3.so', + '' + ], + [ + 'path', + 7753, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 1114180, + '/usr/lib64/empathy/libempathy-3.6.3.so', + '' + ], + [ + 'path', + 7753, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/etc/ld.so.cache', + '' + ], + [ + 'path', + 7753, + '/usr/bin/empathy', + '/usr/bin/empathy', + 'HINT', + 'PERMITTING', + 65540, + '/usr/lib64/libdbus-glib-1.so.2.2.2', + '' + ] + ]; +\n end dumper ARRAY(0x1b65788) + Entry: ARRAY(0x1b65788) + ARRAY(0x1ac9188) + Entry: ARRAY(0x1ac9188) + ARRAY(0x1a98d98) + Entry: ARRAY(0x1a98d98) + ARRAY(0x1aae1c0) + Entry: ARRAY(0x1aae1c0) + ARRAY(0x1af09d8) + Entry: ARRAY(0x1af09d8) + ARRAY(0x1b6fa20) + Entry: ARRAY(0x1b6fa20) + ARRAY(0x1a9d920) + Entry: ARRAY(0x1a9d920) + ARRAY(0x1ae70b8) + Entry: ARRAY(0x1ae70b8) + ARRAY(0x1b0fac0) + Entry: ARRAY(0x1b0fac0) + ARRAY(0xbbe340) ARRAY(0x1b54130) ARRAY(0x1b18578) ARRAY(0x1b040e0) ARRAY(0x1b73600) ARRAY(0x1a9e298) ARRAY(0x1b0a370) ARRAY(0x1adfb70) ARRAY(0x1ac9008) ARRAY(0x1ac1aa0) ARRAY(0x1ab8428) ARRAY(0x1b12e90) ARRAY(0x1a89ab0) ARRAY(0x1aaa6d8) ARRAY(0x1b4d400) ARRAY(0x1b4d5c8) ARRAY(0x1adb230) ARRAY(0x1aadb60) ARRAY(0x1ab3d68) ARRAY(0x1b75fb8) ARRAY(0x1aaa588) ARRAY(0x1b4dcd0) ARRAY(0x1a96c48) ARRAY(0x1adf468) ARRAY(0x1b5ca10) ARRAY(0x1aa76e8) ARRAY(0x1ab8cf8) ARRAY(0x1b72df0) ARRAY(0x1ae9630) ARRAY(0x1a86a90) + Entry: ARRAY(0xbbe340) + Entry: ARRAY(0x1b54130) + Entry: ARRAY(0x1b18578) + Entry: ARRAY(0x1b040e0) + Entry: ARRAY(0x1b73600) + Entry: ARRAY(0x1a9e298) + Entry: ARRAY(0x1b0a370) + Entry: ARRAY(0x1adfb70) + Entry: ARRAY(0x1ac9008) + Entry: ARRAY(0x1ac1aa0) + Entry: ARRAY(0x1ab8428) + Entry: ARRAY(0x1b12e90) + Entry: ARRAY(0x1a89ab0) + Entry: ARRAY(0x1aaa6d8) + Entry: ARRAY(0x1b4d400) + Entry: ARRAY(0x1b4d5c8) + Entry: ARRAY(0x1adb230) + Entry: ARRAY(0x1aadb60) + Entry: ARRAY(0x1ab3d68) + Entry: ARRAY(0x1b75fb8) + Entry: ARRAY(0x1aaa588) + Entry: ARRAY(0x1b4dcd0) + Entry: ARRAY(0x1a96c48) + Entry: ARRAY(0x1adf468) + Entry: ARRAY(0x1b5ca10) + Entry: ARRAY(0x1aa76e8) + Entry: ARRAY(0x1ab8cf8) + Entry: ARRAY(0x1b72df0) + Entry: ARRAY(0x1ae9630) + Entry: ARRAY(0x1a86a90) + ARRAY(0x1afc540) ARRAY(0x1b15f38) ARRAY(0x1ab8b60) ARRAY(0x1abf7b8) ARRAY(0x1af6378) + Entry: ARRAY(0x1afc540) + Entry: ARRAY(0x1b15f38) + Entry: ARRAY(0x1ab8b60) + Entry: ARRAY(0x1abf7b8) + Entry: ARRAY(0x1af6378) + ARRAY(0x1b61898) ARRAY(0x1b12ec0) ARRAY(0x1af8bf0) ARRAY(0x1b0feb0) ARRAY(0x1b05ab0) ARRAY(0x1ab8470) + Entry: ARRAY(0x1b61898) + Entry: ARRAY(0x1b12ec0) + Entry: ARRAY(0x1af8bf0) + Entry: ARRAY(0x1b0feb0) + Entry: ARRAY(0x1b05ab0) + Entry: ARRAY(0x1ab8470) +Complain-mode changes: + +Profile: /usr/bin/empathy +Path: /etc/ld.so.cache +Mode: r +Severity: 1 + + 1 - #include + 2 - #include + 3 - #include + 4 - #include + 5 - \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index ecc02dfa0..a6195a915 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -17,6 +17,7 @@ import atexit import tempfile import apparmor.config +import apparmor.logparser import apparmor.severity import LibAppArmor @@ -61,7 +62,7 @@ user_globs = [] ## Variables used under logprof ### Were our t = hasher()#dict() -transitions = dict() +transitions = hasher() aa = hasher() # Profiles originally in sd, replace by aa original_aa = hasher() extras = hasher() # Inactive profiles from extras @@ -70,7 +71,7 @@ log = [] pid = dict() seen = hasher()#dir() -profile_changes = dict() +profile_changes = hasher() prelog = hasher() log_dict = hasher()#dict() changed = dict() @@ -215,7 +216,7 @@ def check_for_apparmor(): def which(file): """Returns the executable fullpath for the file, None otherwise""" - env_dirs = os.getenv('AAPATH').split(':') + env_dirs = os.getenv('PATH').split(':') for env_dir in env_dirs: env_path = env_dir + '/' + file # Test if the path is executable or not @@ -244,7 +245,7 @@ def get_full_path(original_path): return os.path.realpath(path) def find_executable(bin_path): - """Returns the full executable path for the binary given, None otherwise""" + """Returns the full executable path for the given, None otherwise""" full_bin = None if os.path.exists(bin_path): full_bin = get_full_path(bin_path) @@ -259,7 +260,9 @@ def find_executable(bin_path): def get_profile_filename(profile): """Returns the full profile name""" - if profile.startswith('/'): + if existing_profiles.get(profile, False): + return existing_profiles[profile] + elif profile.startswith('/'): # Remove leading / profile = profile[1:] else: @@ -370,14 +373,8 @@ def handle_binfmt(profile, path): library = glob_common(library) if not library: continue - try: - profile['allow']['path'][library]['mode'] |= str_to_mode('mr') - except TypeError: - profile['allow']['path'][library]['mode'] = str_to_mode('mr') - try: - profile['allow']['path'][library]['audit'] |= 0 - except TypeError: - profile['allow']['path'][library]['audit'] = 0 + profile['allow']['path'][library]['mode'] = profile['allow']['path'][library].get('mode', set()) | str_to_mode('mr') + profile['allow']['path'][library]['audit'] |= profile['allow']['path'][library].get('audit', set()) def get_inactive_profile(local_profile): if extras.get(local_profile, False): @@ -404,11 +401,11 @@ def create_new_profile(localfile): local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r') - local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', 0) + local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set()) local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') - local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', 0) + local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', set()) if interpreter == 'perl': local_profile[localfile]['include']['abstractions/perl'] = True @@ -619,6 +616,7 @@ def set_profile_flags(prof_filename, newflags): def profile_exists(program): """Returns True if profile exists, False otherwise""" # Check cache of profiles + if existing_profiles.get(program, False): return True # Check the disk for profile @@ -626,7 +624,7 @@ def profile_exists(program): #print(prof_path) if os.path.isfile(prof_path): # Add to cache of profile - existing_profiles[program] = True + existing_profiles[program] = prof_path return True return False @@ -897,7 +895,7 @@ def handle_children(profile, hat, root): profile_changes[pid] = profile + '//' + hat else: profile_changes[pid] = profile - elif type == 'unknown_hat': + elif typ == 'unknown_hat': pid, p, h, aamode, uhat = entry[:5] if not regex_nullcomplain.search(p): profile = p @@ -952,7 +950,7 @@ def handle_children(profile, hat, root): elif ans == 'CMD_DENY': return None - elif type == 'capability': + elif typ == 'capability': pid, p, h, prog, aamode, capability = entry[:6] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p @@ -961,9 +959,8 @@ def handle_children(profile, hat, root): continue prelog[aamode][profile][hat]['capability'][capability] = True - elif type == 'path' or type == 'exec': + elif typ == 'path' or typ == 'exec': pid, p, h, prog, aamode, mode, detail, to_name = entry[:8] - if not mode: mode = 0 if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): @@ -973,7 +970,7 @@ def handle_children(profile, hat, root): continue domainchange = 'nochange' - if type == 'exec': + if typ == 'exec': domainchange = 'change' # Escape special characters @@ -1346,7 +1343,7 @@ def handle_children(profile, hat, root): if not aa[profile].get(exec_target, False): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = UI_YesNo(_('A local profile for %s does not exit. Create one') % exec_target, 'n') + ynans = UI_YesNo(_('A local profile for %s does not exit. Create one?') % exec_target, 'n') if ynans == 'y': hat = exec_target aa[profile][hat]['declared'] = False @@ -1377,7 +1374,7 @@ def handle_children(profile, hat, root): if domainchange == 'change': return None - elif type == 'netdomain': + elif typ == 'netdomain': pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): @@ -1390,296 +1387,12 @@ def handle_children(profile, hat, root): return None -def add_to_tree(loc_pid, parent, type, event): - debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (loc_pid, type, event)) - if not pid.get(loc_pid, False): - profile, hat = event[:2] - if parent and pid.get(parent, False): - if not hat: - hat = 'null-complain-profile' - array_ref = ['fork', loc_pid, profile, hat] - pid[parent].append(array_ref) - pid[loc_pid] = array_ref - #else: - # array_ref = [] - # log.append(array_ref) - # pid[pid] = array_ref - pid[loc_pid] = pid.get(loc_pid, []) + [type, loc_pid, event] - -# Variables used by logparsing routines -LOG = None -next_log_entry = None -logmark = None -seenmark = None -RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=') -RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=') - MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N') LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x') -def prefetch_next_log_entry(): - if next_log_entry: - sys.stderr.out('A log entry already present: %s' % next_log_entry) - next_log_entry = LOG.readline() - while RE_LOG_v2_6_syslog.search(next_log_entry) or RE_LOG_v2_6_audit.search(next_log_entry) or re.search(logmark, next_log_entry): - next_log_entry = LOG.readline() - if not next_log_entry: - break - -def get_next_log_entry(): - # If no next log entry fetch it - if not next_log_entry: - prefetch_next_log_entry() - log_entry = next_log_entry - next_log_entry = None - return log_entry - -def peek_at_next_log_entry(): - # Take a peek at the next log entry - if not next_log_entry: - prefetch_next_log_entry() - return next_log_entry - -def throw_away_next_log_entry(): - next_log_entry = None - -def parse_log_record(record): - debug_logger.debug('parse_log_record: %s' % record) - - record_event = parse_event(record) - return record_event - -def add_event_to_tree(e): - aamode = e.get('aamode', 'UNKNOWN') - if e.get('type', False): - if re.search('(UNKNOWN\[1501\]|APPARMOR_AUDIT|1501)', e['type']): - aamode = 'AUDIT' - elif re.search('(UNKNOWN\[1502\]|APPARMOR_ALLOWED|1502)', e['type']): - aamode = 'PERMITTING' - elif re.search('(UNKNOWN\[1503\]|APPARMOR_DENIED|1503)', e['type']): - aamode = 'REJECTING' - elif re.search('(UNKNOWN\[1504\]|APPARMOR_HINT|1504)', e['type']): - aamode = 'HINT' - elif re.search('(UNKNOWN\[1505\]|APPARMOR_STATUS|1505)', e['type']): - aamode = 'STATUS' - elif re.search('(UNKNOWN\[1506\]|APPARMOR_ERROR|1506)', e['type']): - aamode = 'ERROR' - else: - aamode = 'UNKNOWN' - - if aamode in ['UNKNOWN', 'AUDIT', 'STATUS', 'ERROR']: - return None - - if 'profile_set' in e['operation']: - return None - - # Skip if AUDIT event was issued due to a change_hat in unconfined mode - if not e.get('profile', False): - return None - - # Convert new null profiles to old single level null profile - if '//null-' in e['profile']: - e['profile'] = 'null-complain-profile' - - profile = e['profile'] - hat = None - - if '\\' in e['profile']: - profile, hat = e['profile'].split('\\') - - # Filter out change_hat events that aren't from learning - if e['operation'] == 'change_hat': - if aamode != 'HINT' and aamode != 'PERMITTING': - return None - profile = e['name'] - if '\\' in e['name']: - profile, hat = e['name'].split('\\') - - # prog is no longer passed around consistently - prog = 'HINT' - - if profile != 'null-complain-profile' and not profile_exists(profile): - return None - - if e['operation'] == 'exec': - if e.get('info', False) and e['info'] == 'mandatory profile missing': - add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']]) - elif e.get('name2', False) and '\\null-/' in e['name2']: - add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - elif e.get('name', False): - add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - else: - debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile']) - - elif 'file_' in e['operation']: - add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src', - 'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']: - add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - elif e['operation'] == 'capable': - add_to_tree(e['pid'], e['parent'], 'capability', - [profile, hat, prog, aamode, e['name'], '']) - elif e['operation'] == 'setattr' or 'xattr' in e['operation']: - add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - elif 'inode_' in e['operation']: - is_domain_change = False - if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING': - following = peek_at_next_log_entry() - if following: - entry = parse_log_record(following) - if entry and entry.get('info', False) == 'set profile': - is_domain_change = True - throw_away_next_log_entry() - - if is_domain_change: - add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']]) - else: - add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - - elif e['operation'] == 'sysctl': - add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - - elif e['operation'] == 'clone': - parent , child = e['pid'], e['task'] - if not parent: - parent = 'null-complain-profile' - if not hat: - hat = 'null-complain-profile' - arrayref = ['fork', child, profile, hat] - if pid.get(parent, False): - pid[parent] += [arrayref] - else: - log += [arrayref] - pid[child] = arrayref - - elif op_type(e['operation']) == 'net': - add_to_tree(e['pid'], e['parent'], 'netdomain', - [profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']]) - elif e['operation'] == 'change_hat': - add_to_tree(e['pid'], e['parent'], 'unknown_hat', - [profile, hat, aamode, hat]) - else: - debug_logger.debug('UNHANDLED: %s' % e) - -def read_log(logmark): - seenmark = True - if logmark: - seenmark = False - #last = None - #event_type = None - try: - #print(filename) - log_open = open_file_read(filename) - except IOError: - raise AppArmorException('Can not read AppArmor logfile: ' + filename) - - with log_open as f_in: - for line in f_in: - line = line.strip() - debug_logger.debug('read_log: %s' % line) - if logmark in line: - seenmark = True - if not seenmark: - debug_logger.debug('read_log: seenmark = %s' % seenmark) - - event = parse_log_record(line) - if event: - add_event_to_tree(event) - logmark = '' - -def parse_event(msg): - """Parse the event from log into key value pairs""" - msg = msg.strip() - debug_logger.info('parse_event: %s' % msg) - #print(repr(msg)) - if sys.version_info < (3,0): - # parse_record fails with u'foo' style strings hence typecasting to string - msg = str(msg) - event = LibAppArmor.parse_record(msg) - ev = dict() - ev['resource'] = event.info - ev['active_hat'] = event.active_hat - ev['aamode'] = event.event - ev['time'] = event.epoch - ev['operation'] = event.operation - ev['profile'] = event.profile - ev['name'] = event.name - ev['name2'] = event.name2 - ev['attr'] = event.attribute - ev['parent'] = event.parent - ev['pid'] = event.pid - ev['task'] = event.task - ev['info'] = event.info - dmask = event.denied_mask - rmask = event.requested_mask - ev['magic_token'] = event.magic_token - if ev['operation'] and op_type(ev['operation']) == 'net': - ev['family'] = event.net_family - ev['protocol'] = event.net_protocol - ev['sock_type'] = event.net_sock_type - LibAppArmor.free_record(event) - # Map c (create) to a and d (delete) to w, logprof doesn't support c and d - if rmask: - rmask = rmask.replace('c', 'a') - rmask = rmask.replace('d', 'w') - if not validate_log_mode(hide_log_mode(rmask)): - fatal_error(_('Log contains unknown mode %s') % rmask) - if dmask: - dmask = dmask.replace('c', 'a') - dmask = dmask.replace('d', 'w') - if not validate_log_mode(hide_log_mode(dmask)): - fatal_error(_('Log contains unknown mode %s') % dmask) - #print('parse_event:', ev['profile'], dmask, ev['name2']) - mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2']) - - ev['denied_mask'] = mask - ev['name2'] = name - - mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2']) - ev['request_mask'] = mask - ev['name2'] = name - - if not ev['time']: - ev['time'] = int(time.time()) - # Remove None keys - #for key in ev.keys(): - # if not ev[key] or not re.search('[\w]+', ev[key]): - # ev.pop(key) - - if ev['aamode']: - # Convert aamode values to their counter-parts - mode_convertor = { - 0: 'UNKNOWN', - 1: 'ERROR', - 2: 'AUDITING', - 3: 'PERMITTING', - 4: 'REJECTING', - 5: 'HINT', - 6: 'STATUS' - } - try: - ev['aamode'] = mode_convertor[ev['aamode']] - except KeyError: - ev['aamode'] = None - - if ev['aamode']: - #debug_logger.debug(ev) - return ev - else: - return None - def hide_log_mode(mode): mode = mode.replace('::', '') return mode @@ -1747,8 +1460,8 @@ def order_globs(globs, path): return sorted(globs) def ask_the_questions(): - found = None - print(log_dict) + found = 0 + global seen_events for aamode in sorted(log_dict.keys()): # Describe the type of changes if aamode == 'PERMITTING': @@ -1939,14 +1652,15 @@ def ask_the_questions(): # If already present skip if aa[profile][hat][incname]: continue + if incname.startswith(profile_dir): + incname = incname.replace(profile_dir+'/', '', 1) - include_valid = valid_include(profile, incname) + include_valid = valid_include('', incname) if not include_valid: continue - cm, am, m = match_include_to_path(incname, 'allow', path) - + if 'base' in incname: print(cm,am,m,mode,mode_contains(cm, mode)) if cm and mode_contains(cm, mode): dm = match_include_to_path(incname, 'deny', path) # If the mode is denied @@ -2041,7 +1755,7 @@ def ask_the_questions(): q['options'] = options q['selected'] = default_option - 1 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_GLOB', - 'CMD_GLOBTEXT', 'CMD_NEW', 'CMD_ABORT', + 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT', 'CMD_FINISHED', 'CMD_OTHER', 'CMD_IGNORE_ENTRY'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': @@ -2183,16 +1897,16 @@ def ask_the_questions(): if match: # /foo/**.ext and /foo/*.ext => /**.ext newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.group()[0], newpath) - elif re.search('/[^/]+\*\*[^/]*\.[^/]+$'): + elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath): # /foo**.ext and /foo**bar.ext => /**.ext - match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$') + match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath) newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**'+match.groups()[0], newpath) - elif re.search('/\*\*[^/]+\.[^/]+$'): + elif re.search('/\*\*[^/]+\.[^/]+$', newpath): # /**foo.ext => /**.ext - match = re.search('/\*\*[^/]+(\.[^/]+)$') + match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath) newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**'+match.groups()[0], newpath) else: - match = re.search('(\.[^/]+)$') + match = re.search('(\.[^/]+)$', newpath) newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) if newpath not in options: @@ -2388,7 +2102,7 @@ def re_match_include(path): return None def valid_include(profile, incname): - if profile['include'].get(incname, False): + if profile and profile['include'].get(incname, False): return False if cfg['settings']['custom_includes']: @@ -2410,19 +2124,22 @@ def match_net_includes(profile, family, nettype): return newincludes -def do_logprof_pass(logmark='', sev_db=sev_db): +def do_logprof_pass(logmark='', pid=pid, existing_profiles=existing_profiles): # set up variables for this pass t = hasher() - transitions = hasher() +# transitions = hasher() seen = hasher() - aa = hasher() - profile_changes = hasher() - prelog = hasher() + global log log = [] - log_dict = hasher() - changed = dict() + global sev_db +# aa = hasher() +# profile_changes = hasher() +# prelog = hasher() +# log = [] +# log_dict = hasher() +# changed = dict() skip = hasher() - filelist = hasher() +# filelist = hasher() UI_Info(_('Reading log entries from %s.') %filename) UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) @@ -2431,17 +2148,21 @@ def do_logprof_pass(logmark='', sev_db=sev_db): if not sev_db: sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) - + #print(pid) + #print(existing_profiles) ##if not repo_cf and cfg['repostory']['url']: ## repo_cfg = read_config('repository.conf') ## if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']: ## UI_ask_to_enable_repo() - - read_log(logmark) + log_reader = apparmor.logparser.ReadLog(pid, filename, existing_profiles, profile_dir, log) + log = log_reader.read_log(logmark) + #read_log(logmark) for root in log: handle_children('', '', root) - + #for root in range(len(log)): + #log[root] = handle_children('', '', log[root]) + #print(log) for pid in sorted(profile_changes.keys()): set_process(pid, profile_changes[pid]) @@ -2624,27 +2345,27 @@ def collapse_log(): combinedmode |= aa[profile][hat]['allow']['path'][path] # Match path to regexps in profile - combinedmode |= rematchfrag(aa[profile][hat], 'allow', path) + combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0] # Match path from includes - combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path) + combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)[0] if not combinedmode or not mode_contains(combinedmode, mode): - if log[aamode][profile][hat]['path'].get(path, False): - mode |= log[aamode][profile][hat]['path'][path] + if log_dict[aamode][profile][hat]['path'].get(path, False): + mode |= log_dict[aamode][profile][hat]['path'][path] - log[aamode][profile][hat]['path'][path] = mode + log_dict[aamode][profile][hat]['path'][path] = mode for capability in prelog[aamode][profile][hat]['capability'].keys(): # If capability not already in profile if not aa[profile][hat]['allow']['capability'][capability].get('set', False): - log[aamode][profile][hat]['capability'][capability] = True + log_dict[aamode][profile][hat]['capability'][capability] = True nd = prelog[aamode][profile][hat]['netdomain'] for family in nd.keys(): for sock_type in nd[family].keys(): if not profile_known_network(aa[profile][hat], family, sock_type): - log[aamode][profile][hat]['netdomain'][family][sock_type] = True + log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True def profilemode(mode): pass @@ -2905,6 +2626,7 @@ def read_profiles(): if is_skippable_file(file): continue else: + #print('read %s' %file) read_profile(profile_dir + '/' + file, True) def read_inactive_profiles(): @@ -2932,6 +2654,7 @@ def read_profile(file, active_profile): return None profile_data = parse_profile_data(data, file, 0) + if profile_data and active_profile: attach_profile_data(aa, profile_data) attach_profile_data(original_aa, profile_data) @@ -2953,7 +2676,7 @@ def parse_profile_data(data, file, do_include): repo_data = None parsed_profiles = [] initial_comment = '' - RE_PROFILE_START = re.compile('^(("??\/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$') + RE_PROFILE_START = re.compile('^(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$') RE_PROFILE_END = re.compile('^\}\s*(#.*)?$') RE_PROFILE_CAP = re.compile('^(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') RE_PROFILE_LINK = re.compile('^(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') @@ -3006,6 +2729,8 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['external'] = True else: hat = profile + # Profile stored + existing_profiles[profile] = file flags = matches[6] @@ -3362,11 +3087,13 @@ def store_list_var(var, list_var, value, var_operation): print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) pass #raise AppArmorException('An existing variable redefined: %s' %list_var) - else: + elif var_operation == '+=': if var.get(list_var, False): var[list_var] = set(var[list_var] + vlist) else: - raise AppArmorException('An existing variable redefined: %s' %list_var) + raise AppArmorException('Values added to a non-existing variable: %s' %list_var) + else: + raise AppArmorException('Unknown variable operation: %s' %var_operation) def strip_quotes(data): @@ -3870,7 +3597,7 @@ def match_include_to_path(incname, allow, path): combinedmode = 0 combinedaudit = 0 matches = [] - + incname = profile_dir + '/' + incname includelist = [incname] while includelist: incfile = includelist.pop(0) diff --git a/apparmor/aamode.py b/apparmor/aamode.py new file mode 100644 index 000000000..7cd9ec133 --- /dev/null +++ b/apparmor/aamode.py @@ -0,0 +1,258 @@ +import re + +AA_MAY_EXEC = set('x') +AA_MAY_WRITE = set('w') +AA_MAY_READ = set('r') +AA_MAY_APPEND = set('a') +AA_MAY_LINK = set('l') +AA_MAY_LOCK = set('k') +AA_EXEC_MMAP = set('m') +AA_EXEC_UNSAFE = set('unsafe') +AA_EXEC_INHERIT = set('i') +AA_EXEC_UNCONFINED = set('U') +AA_EXEC_PROFILE = set('P') +AA_EXEC_CHILD = set('C') +AA_EXEC_NT = set('N') +AA_LINK_SUBSET = set('ls') +AA_OTHER_SHIFT = 14 +AA_USER_MASK = 16384 - 1 + +AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | + AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) + +MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, + 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, + 'r': AA_MAY_READ, 'R': AA_MAY_READ, + 'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND, + 'l': AA_MAY_LINK, 'L': AA_MAY_LINK, + 'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK, + 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, + 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, + 'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'U': AA_EXEC_UNCONFINED, + 'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe + 'P': AA_EXEC_PROFILE, + 'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe + 'C': AA_EXEC_CHILD, + 'n': AA_EXEC_NT + AA_EXEC_UNSAFE, + 'N': AA_EXEC_NT + } + +LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') +MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N') + +def str_to_mode(string): + if not string: + return set() + user, other = split_log_mode(string) + + if not user: + user = other + + mode = sub_str_to_mode(user) + #print(string, mode) + #print(string, 'other', sub_str_to_mode(other)) + mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT) + #print (string, mode) + #print('str_to_mode:', mode) + return mode + +def sub_str_to_mode(string): + mode = set() + if not string: + return mode + while string: + pattern = '(%s)' % MODE_MAP_RE.pattern + tmp = re.search(pattern, string) + if tmp: + tmp = tmp.groups()[0] + string = re.sub(pattern, '', string) + if tmp and MODE_HASH.get(tmp, False): + mode |= MODE_HASH[tmp] + else: + pass + + return mode + +def split_log_mode(mode): + user = '' + other = '' + match = re.search('(.*?)::(.*)', mode) + if match: + user, other = match.groups() + else: + user = mode + other = mode + #print ('split_logmode:', user, mode) + return user, other + +def mode_contains(mode, subset): + # w implies a + if mode & AA_MAY_WRITE: + mode |= AA_MAY_APPEND + if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT): + mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT) + + return (mode & subset) == subset + +def contains(mode, string): + return mode_contains(mode, str_to_mode(string)) + +def validate_log_mode(mode): + pattern = '^(%s)+$' % LOG_MODE_RE.pattern + if re.search(pattern, mode): + #if LOG_MODE_RE.search(mode): + return True + else: + return False + +def hide_log_mode(mode): + mode = mode.replace('::', '') + return mode + +AA_MAY_EXEC = 1 +AA_MAY_WRITE = 2 +AA_MAY_READ = 4 +AA_MAY_APPEND = 8 +AA_MAY_LINK = 16 +AA_MAY_LOCK = 32 +AA_EXEC_MMAP = 64 +AA_EXEC_UNSAFE = 128 +AA_EXEC_INHERIT = 256 +AA_EXEC_UNCONFINED = 512 +AA_EXEC_PROFILE = 1024 +AA_EXEC_CHILD = 2048 +AA_EXEC_NT = 4096 +AA_LINK_SUBSET = 8192 +AA_OTHER_SHIFT = 14 +AA_USER_MASK = 16384 - 1 + +AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | + AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) + +ALL_AA_EXEC_TYPE = AA_EXEC_TYPE # The same value + +# Modes and their values +MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, + 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, + 'r': AA_MAY_READ, 'R': AA_MAY_READ, + 'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND, + 'l': AA_MAY_LINK, 'L': AA_MAY_LINK, + 'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK, + 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, + 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, + 'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'U': AA_EXEC_UNCONFINED, + 'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe + 'P': AA_EXEC_PROFILE, + 'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe + 'C': AA_EXEC_CHILD, + 'n': AA_EXEC_NT + AA_EXEC_UNSAFE, + 'N': AA_EXEC_NT + } + +def log_str_to_mode(profile, string, nt_name): + mode = str_to_mode(string) + # If contains nx and nix + #print (profile, string, nt_name) + if contains(mode, 'Nx'): + # Transform to px, cx + match = re.search('(.+?)//(.+?)', nt_name) + if match: + lprofile, lhat = match.groups() + tmode = 0 + + if lprofile == profile: + if mode & AA_MAY_EXEC: + tmode = str_to_mode('Cx::') + if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): + tmode |= str_to_mode('Cx') + nt_name = lhat + else: + if mode & AA_MAY_EXEC: + tmode = str_to_mode('Px::') + if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): + tmode |= str_to_mode('Px') + nt_name = lhat + + mode = mode & ~str_to_mode('Nx') + mode |= tmode + + return mode, nt_name + +def hide_log_mode(mode): + mode = mode.replace('::', '') + return mode + +def validate_log_mode(mode): + pattern = '^(%s)+$' % LOG_MODE_RE.pattern + if re.search(pattern, mode): + #if LOG_MODE_RE.search(mode): + return True + else: + return False + +def str_to_mode(string): + if not string: + return 0 + user, other = split_log_mode(string) + + if not user: + user = other + + mode = sub_str_to_mode(user) + #print(string, mode) + #print(string, 'other', sub_str_to_mode(other)) + mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT) + #print (string, mode) + #print('str_to_mode:', mode) + return mode + +def mode_contains(mode, subset): + # w implies a + if mode & AA_MAY_WRITE: + mode |= AA_MAY_APPEND + if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT): + mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT) + + # ix does not imply m + + ### ix implies m + ##if mode & AA_EXEC_INHERIT: + ## mode |= AA_EXEC_MMAP + ##if mode & (AA_EXEC_INHERIT << AA_OTHER_SHIFT): + ## mode |= (AA_EXEC_MMAP << AA_OTHER_SHIFT) + + return (mode & subset) == subset + +def contains(mode, string): + return mode_contains(mode, str_to_mode(string)) + +def sub_str_to_mode(string): + mode = 0 + if not string: + return mode + while string: + pattern = '(%s)' % MODE_MAP_RE.pattern + tmp = re.search(pattern, string) + if tmp: + tmp = tmp.groups()[0] + string = re.sub(pattern, '', string) + if tmp and MODE_HASH.get(tmp, False): + mode |= MODE_HASH[tmp] + else: + pass + + return mode + +def split_log_mode(mode): + user = '' + other = '' + match = re.search('(.*?)::(.*)', mode) + if match: + user, other = match.groups() + else: + user = mode + other = mode + #print ('split_logmode:', user, mode) + return user, other \ No newline at end of file diff --git a/apparmor/common.py b/apparmor/common.py index d0d9e100c..b2d808b16 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -157,6 +157,7 @@ def hasher(): def convert_regexp(regexp): + regex_paren = re.compile('^(.*){([^}]*)}(.*)$') regexp = regexp.strip() new_reg = re.sub(r'(?') if os.path.isfile(prof_path): - with open_file_read(prof_path) as f_in: + with open(prof_path, 'r') as f_in: for line in f_in: line = line.strip() # If any includes, load variables from them first diff --git a/apparmor/ui.py b/apparmor/ui.py index 7934d6380..964cfc69f 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -235,6 +235,15 @@ def UI_PromptUser(q): cmd == 'XXXINVALIDXXX' return (cmd, arg) +def confirm_and_abort(): + ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n') + if ans == 'y': + UI_Info(_('Abandoning all changes.')) + #shutdown_yast() + #for prof in created: + # delete_profile(prof) + sys.exit(0) + def UI_ShortMessage(title, message): SendDataToYast({ 'type': 'short-dialog-message', @@ -303,7 +312,7 @@ def Text_PromptUser(question): default_key = defaulthotkey.groups()[0].lower() - if keys.get(default_key, False): + if not keys.get(default_key, False): raise AppArmorException('PromptUser: %s %s' %(_('Invalid default'), default)) widest = 0 @@ -315,7 +324,7 @@ def Text_PromptUser(question): widest = len(header) widest += 1 - formatstr = '%-' + widest + 's %s\n' + formatstr = '%-' + str(widest) + 's %s\n' function_regexp = '^(' function_regexp += '|'.join(keys.keys()) @@ -348,7 +357,7 @@ def Text_PromptUser(question): else: format_option = ' %s - %s ' prompt += format_option %(index+1, option) - prompt += '\n' + prompt += '\n' prompt += ' / '.join(menu_items) @@ -371,7 +380,7 @@ def Text_PromptUser(question): # sys.stdout.write('\n%s\n' %helptext) # ans = 'XXXINVALIDXXX' - elif int(ans) == 10: + elif is_number(ans) == 10: # If they hit return choose default option ans = default_key @@ -389,3 +398,10 @@ def Text_PromptUser(question): ans = keys[ans] return ans, selected + +def is_number(number): + try: + return int(number) + except: + return False + \ No newline at end of file From 4f4a8f61636e0e1181556fb517a944a17d1f86e8 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 11 Aug 2013 18:30:01 +0530 Subject: [PATCH 042/183] backup commit for modes --- Testing/aa_test.py | 88 +++++++-------- apparmor/aa.py | 79 ++++++------- apparmor/aamode.py | 274 +++++++++++++++++++++++---------------------- 3 files changed, 223 insertions(+), 218 deletions(-) diff --git a/Testing/aa_test.py b/Testing/aa_test.py index a18610fc6..8f82fde53 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -14,81 +14,77 @@ class Test(unittest.TestCase): def test_parse_event(self): - + parser = apparmor.logparser.ReadLog('', '', '', '', '') event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' - parsed_event = apparmor.logparser.ReadLog.parse_event(apparmor.logparser.ReadLog, event) + parsed_event = parser.parse_event(event) self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg', 'Incorrectly parsed/decoded name') self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo', 'Incorrectly parsed/decode profile name') self.assertEqual(parsed_event['aamode'], 'PERMITTING') - + self.assertEqual(parsed_event['request_mask'], set(['w', 'a', '::w', '::a'])) #print(parsed_event) #event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0' #parsed_event = apparmor.aa.parse_event(event) #print(parsed_event) - event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="w" denied_mask="w" fsuid=0 ouid=1000' - parsed_event = apparmor.logparser.ReadLog.parse_event(apparmor.logparser.ReadLog, event) + event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="rw" denied_mask="rw" fsuid=0 ouid=1000' + parsed_event = parser.parse_event(event) self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name') self.assertEqual(parsed_event['profile'], 'foo bar', 'Incorrectly parsed/decode profile name') self.assertEqual(parsed_event['aamode'], 'PERMITTING') - + self.assertEqual(parsed_event['request_mask'], set(['r', 'w', 'a','::r' , '::w', '::a'])) #print(parsed_event) def test_modes_to_string(self): - self.assertEqual(apparmor.aa.mode_to_str(32270), 'rwPCUx') - MODE_TEST = {'x': apparmor.aa.AA_MAY_EXEC, - 'w': apparmor.aa.AA_MAY_WRITE, - 'r': apparmor.aa.AA_MAY_READ, - 'a': apparmor.aa.AA_MAY_APPEND, - 'l': apparmor.aa.AA_MAY_LINK, - 'k': apparmor.aa.AA_MAY_LOCK, - 'm': apparmor.aa.AA_EXEC_MMAP, - 'i': apparmor.aa.AA_EXEC_INHERIT, - 'u': apparmor.aa.AA_EXEC_UNCONFINED + apparmor.aa.AA_EXEC_UNSAFE, # Unconfined + Unsafe - 'U': apparmor.aa.AA_EXEC_UNCONFINED, - 'p': apparmor.aa.AA_EXEC_PROFILE + apparmor.aa.AA_EXEC_UNSAFE, # Profile + unsafe - 'P': apparmor.aa.AA_EXEC_PROFILE, - 'c': apparmor.aa.AA_EXEC_CHILD + apparmor.aa.AA_EXEC_UNSAFE, # Child + Unsafe - 'C': apparmor.aa.AA_EXEC_CHILD, - #'n': AA_EXEC_NT + AA_EXEC_UNSAFE, - #'N': AA_EXEC_NT + MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, + 'w': apparmor.aamode.AA_MAY_WRITE, + 'r': apparmor.aamode.AA_MAY_READ, + 'a': apparmor.aamode.AA_MAY_APPEND, + 'l': apparmor.aamode.AA_MAY_LINK, + 'k': apparmor.aamode.AA_MAY_LOCK, + 'm': apparmor.aamode.AA_EXEC_MMAP, + 'i': apparmor.aamode.AA_EXEC_INHERIT, + 'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'U': apparmor.aamode.AA_EXEC_UNCONFINED, + 'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE, # Profile + unsafe + 'P': apparmor.aamode.AA_EXEC_PROFILE, + 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, # Child + Unsafe + 'C': apparmor.aamode.AA_EXEC_CHILD, + #'n': apparmor.aamode.AA_EXEC_NT | apparmor.aamode.AA_EXEC_UNSAFE, + #'N': apparmor.aamode.AA_EXEC_NT } - + #for i in MODE_TEST.keys(): + # print(i, MODE_TEST[i]) while MODE_TEST: string,mode = MODE_TEST.popitem() - self.assertEqual(apparmor.aa.mode_to_str(mode), string) - - mode = 2048 - self.assertEqual(apparmor.aa.mode_to_str(mode), 'C') + self.assertEqual(apparmor.aamode.mode_to_str(mode), string, 'mode is %s and string is %s'%(mode, string)) def test_string_to_modes(self): #self.assertEqual(apparmor.aa.str_to_mode('wc'), 32270) - - MODE_TEST = {'x': apparmor.aa.AA_MAY_EXEC, - 'w': apparmor.aa.AA_MAY_WRITE, - 'r': apparmor.aa.AA_MAY_READ, - 'a': apparmor.aa.AA_MAY_APPEND, - 'l': apparmor.aa.AA_MAY_LINK, - 'k': apparmor.aa.AA_MAY_LOCK, - 'm': apparmor.aa.AA_EXEC_MMAP, - 'i': apparmor.aa.AA_EXEC_INHERIT, - 'u': apparmor.aa.AA_EXEC_UNCONFINED + apparmor.aa.AA_EXEC_UNSAFE, # Unconfined + Unsafe - 'U': apparmor.aa.AA_EXEC_UNCONFINED, - 'p': apparmor.aa.AA_EXEC_PROFILE + apparmor.aa.AA_EXEC_UNSAFE, # Profile + unsafe - 'P': apparmor.aa.AA_EXEC_PROFILE, - 'c': apparmor.aa.AA_EXEC_CHILD + apparmor.aa.AA_EXEC_UNSAFE, # Child + Unsafe - 'C': apparmor.aa.AA_EXEC_CHILD, - #'n': AA_EXEC_NT + AA_EXEC_UNSAFE, - #'N': AA_EXEC_NT + MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, + 'w': apparmor.aamode.AA_MAY_WRITE, + 'r': apparmor.aamode.AA_MAY_READ, + 'a': apparmor.aamode.AA_MAY_APPEND, + 'l': apparmor.aamode.AA_MAY_LINK, + 'k': apparmor.aamode.AA_MAY_LOCK, + 'm': apparmor.aamode.AA_EXEC_MMAP, + 'i': apparmor.aamode.AA_EXEC_INHERIT, + 'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'U': apparmor.aamode.AA_EXEC_UNCONFINED, + 'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE, # Profile + unsafe + 'P': apparmor.aamode.AA_EXEC_PROFILE, + 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, # Child + Unsafe + 'C': apparmor.aamode.AA_EXEC_CHILD, + #'n': apparmor.aamode.AA_EXEC_NT | apparmor.aamode.AA_EXEC_UNSAFE, + #'N': apparmor.aamode.AA_EXEC_NT } #while MODE_TEST: # string,mode = MODE_TEST.popitem() - # self.assertEqual(apparmor.aa.str_to_mode(string), mode) + # self.assertEqual(apparmor.aamode.str_to_mode(string), mode) #self.assertEqual(apparmor.aa.str_to_mode('C'), 2048) diff --git a/apparmor/aa.py b/apparmor/aa.py index a6195a915..ec1b0797a 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -27,6 +27,7 @@ from apparmor.common import (AppArmorException, error, debug, msg, from apparmor.ui import * from copy import deepcopy +from apparmor.aamode import * # Setup logging incase of debugging is enabled debug_logger = DebugLogger('aa') @@ -420,7 +421,7 @@ def create_new_profile(localfile): local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('mr')) | str_to_mode('mr') - local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', 0) + local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set()) handle_binfmt(local_profile[localfile], localfile) # Add required hats to the profile if they match the localfile @@ -962,7 +963,7 @@ def handle_children(profile, hat, root): elif typ == 'path' or typ == 'exec': pid, p, h, prog, aamode, mode, detail, to_name = entry[:8] if not mode: - mode = 0 + mode = set() if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p hat = h @@ -988,7 +989,7 @@ def handle_children(profile, hat, root): if mode & str_to_mode('x'): if os.path.isdir(exec_target): - mode = mode & (~ALL_AA_EXEC_TYPE) + mode = mode - ALL_AA_EXEC_TYPE mode = mode | str_to_mode('ix') else: do_execute = True @@ -1036,8 +1037,8 @@ def handle_children(profile, hat, root): context_new = context + ' ->%s' % exec_target ans_new = transitions.get(context_new, '') - combinedmode = False - combinedaudit = False + combinedmode = set() + combinedaudit = set() ## Check return Value Consistency # Check if path matches any existing regexps in profile cm, am , m = rematchfrag(aa[profile][hat], 'allow', exec_target) @@ -1237,7 +1238,7 @@ def handle_children(profile, hat, root): ynans = UI_YesNo(px_msg, px_default) if ynans == 'y': # Disable the unsafe mode - exec_mode &= ~(AA_EXEC_UNSAFE | (AA_EXEC_UNSAFE << AA_OTHER_SHIFT)) + exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) elif ans == 'CMD_ux': exec_mode = str_to_mode('ux') ynans = UI_YesNo(_('Launching processes in an unconfined state is a very\n' + @@ -1252,7 +1253,7 @@ def handle_children(profile, hat, root): 'and should be avoided if at all possible.'), 'y') if ynans == 'y': # Disable the unsafe mode - exec_mode &= ~(AA_EXEC_UNSAFE | (AA_EXEC_UNSAFE << AA_OTHER_SHIFT)) + exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) else: ans = 'INVALID' transitions[context] = ans @@ -1265,7 +1266,7 @@ def handle_children(profile, hat, root): else: if ans == 'CMD_DENY': aa[profile][hat]['deny']['path'][exec_target]['mode'] = aa[profile][hat]['deny']['path'][exec_target].get('mode', str_to_mode('x')) | str_to_mode('x') - aa[profile][hat]['deny']['path'][exec_target]['audit'] = aa[profile][hat]['deny']['path'][exec_target].get('audit', 0) + aa[profile][hat]['deny']['path'][exec_target]['audit'] = aa[profile][hat]['deny']['path'][exec_target].get('audit', set()) changed[profile] = True # Skip remaining events if they ask to deny exec if domainchange == 'change': @@ -1278,7 +1279,7 @@ def handle_children(profile, hat, root): aa[profile][hat]['allow']['path'][exec_target]['mode'] = aa[profile][hat]['allow']['path'][exec_target].get('mode', exec_mode) - aa[profile][hat]['allow']['path'][exec_target]['audit'] = aa[profile][hat]['allow']['path'][exec_target].get('audit', 0) + aa[profile][hat]['allow']['path'][exec_target]['audit'] = aa[profile][hat]['allow']['path'][exec_target].get('audit', set()) if to_name: aa[profile][hat]['allow']['path'][exec_target]['to'] = to_name @@ -1298,7 +1299,7 @@ def handle_children(profile, hat, root): aa[profile][hat]['path'][interpreter_path]['mode'] = aa[profile][hat]['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') - aa[profile][hat]['path'][interpreter_path]['audit'] = aa[profile][hat]['path'][interpreter_path].get('audit', 0) + aa[profile][hat]['path'][interpreter_path]['audit'] = aa[profile][hat]['path'][interpreter_path].get('audit', set()) if interpreter == 'perl': aa[profile][hat]['include']['abstractions/perl'] = True @@ -1578,10 +1579,10 @@ def ask_the_questions(): for path in sorted(log_dict[aamode][profile][hat]['path'].keys()): mode = log_dict[aamode][profile][hat]['path'][path] # Lookup modes from profile - allow_mode = 0 - allow_audit = 0 - deny_mode = 0 - deny_audit = 0 + allow_mode = set() + allow_audit = set() + deny_mode = set() + deny_audit = set() fmode, famode, fm = rematchfrag(aa[profile][hat], 'allow', path) if fmode: @@ -1611,14 +1612,14 @@ def ask_the_questions(): deny_mode |= ALL_AA_EXEC_TYPE # Mask off the denied modes - mode = mode & ~deny_mode + mode = mode - deny_mode # If we get an exec request from some kindof event that generates 'PERMITTING X' # check if its already in allow_mode # if not add ix permission if mode & AA_MAY_EXEC: # Remove all type access permission - mode = mode & ~ALL_AA_EXEC_TYPE + mode = mode - ALL_AA_EXEC_TYPE if not allow_mode & AA_MAY_EXEC: mode |= str_to_mode('ix') @@ -1628,7 +1629,7 @@ def ask_the_questions(): ##if mode & AA_EXEC_MMAP: ## # ix implies m, so we don't need to add m if ix is present ## if contains(allow_mode, 'ix'): - ## mode = mode & ~AA_EXEC_MMAP + ## mode = mode - AA_EXEC_MMAP if not mode: continue @@ -1714,7 +1715,7 @@ def ask_the_questions(): elif owner_toggle == 1: prompt_mode = mode elif owner_toggle == 2: - prompt_mode = allow_mode | owner_flatten_mode(mode & ~allow_mode) + prompt_mode = allow_mode | owner_flatten_mode(mode - allow_mode) tail = ' ' + _('(force new perms to owner)') else: prompt_mode = owner_flatten_mode(mode) @@ -1724,7 +1725,7 @@ def ask_the_questions(): s = mode_to_str_user(allow_mode) if allow_mode: s += ', ' - s += 'audit ' + mode_to_str_user(prompt_mode & ~allow_mode) + tail + s += 'audit ' + mode_to_str_user(prompt_mode - allow_mode) + tail elif audit_toggle == 2: s = 'audit ' + mode_to_str_user(prompt_mode) + tail else: @@ -1809,19 +1810,19 @@ def ask_the_questions(): #elif owner_toggle == 1: # mode = mode elif owner_toggle == 2: - mode = allow_mode | owner_flatten_mode(mode & ~allow_mode) + mode = allow_mode | owner_flatten_mode(mode - allow_mode) elif owner_toggle == 3: mode = owner_flatten_mode(mode) - aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', 0) | mode + aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode tmpmode = 0 if audit_toggle == 1: - tmpmode = mode & ~allow_mode + tmpmode = mode- allow_mode elif audit_toggle == 2: tmpmode = mode - aa[profile][hat]['allow']['path'][path]['audit'] = aa[profile][hat]['allow']['path'][path].get('audit', 0) | tmpmode + aa[profile][hat]['allow']['path'][path]['audit'] = aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode changed[profile] = True @@ -1831,9 +1832,9 @@ def ask_the_questions(): elif ans == 'CMD_DENY': # Add new entry? - aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', 0) | (mode & ~allow_mode) + aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode) - aa[profile][hat]['deny']['path'][path]['audit'] = aa[profile][hat]['deny']['path'][path].get('audit', 0) + aa[profile][hat]['deny']['path'][path]['audit'] = aa[profile][hat]['deny']['path'][path].get('audit', set()) changed[profile] = True @@ -1896,7 +1897,7 @@ def ask_the_questions(): match = re.search('/\*{1,2}(\.[^/]+)$', newpath) if match: # /foo/**.ext and /foo/*.ext => /**.ext - newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.group()[0], newpath) + newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.groups()[0], newpath) elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath): # /foo**.ext and /foo**bar.ext => /**.ext match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath) @@ -2470,7 +2471,7 @@ def log_str_to_mode(profile, string, nt_name): tmode |= str_to_mode('Px') nt_name = lhat - mode = mode & ~str_to_mode('Nx') + mode = mode - str_to_mode('Nx') mode |= tmode return mode, nt_name @@ -2493,7 +2494,7 @@ def sub_mode_to_str(mode): string = '' # w(write) implies a(append) if mode & AA_MAY_WRITE: - mode &= (~AA_MAY_APPEND) + mode = mode - AA_MAY_APPEND if mode & AA_EXEC_MMAP: string += 'm' @@ -2561,10 +2562,10 @@ def mode_to_str_user(mode): if not other: other = 0 - if user & ~other: + if user - other: if other: string = sub_mode_to_str(other) + '+' - string += 'owner ' + sub_mode_to_str(user & ~other) + string += 'owner ' + sub_mode_to_str(user - other) elif is_user_mode(mode): string = 'owner ' + sub_mode_to_str(user) @@ -3291,30 +3292,30 @@ def write_path_rules(prof_data, depth, allow): ownerstr = '' tmpmode = 0 tmpaudit = False - if user & ~other: + if user - other: # if no other mode set ownerstr = 'owner' - tmpmode = user & ~other + tmpmode = user - other tmpaudit = user_audit - user = user & ~tmpmode + user = user - tmpmode else: - if user_audit & ~other_audit & user: + if user_audit - other_audit & user: ownerstr = 'owner ' - tmpaudit = user_audit & ~other_audit & user + tmpaudit = user_audit - other_audit & user tmpmode = user & tmpaudit - user = user & ~tmpmode + user = user - tmpmode else: ownerstr = '' tmpmode = user | other tmpaudit = user_audit | other_audit - user = user & ~tmpmode - other = other & ~tmpmode + user = user - tmpmode + other = other - tmpmode if tmpmode & tmpaudit: modestr = mode_to_str(tmpmode & tmpaudit) path = quote_if_needed(path) data.append('%saudit %s%s%s %s%s,' %(pre, allowstr, ownerstr, path, modestr, tail)) - tmpmode = tmpmode & ~tmpaudit + tmpmode = tmpmode - tmpaudit if tmpmode: modestr = mode_to_str(tmpmode) diff --git a/apparmor/aamode.py b/apparmor/aamode.py index 7cd9ec133..3142c7cc2 100644 --- a/apparmor/aamode.py +++ b/apparmor/aamode.py @@ -1,5 +1,18 @@ import re +def AA_OTHER(mode): + other = set() + for i in mode: + other.add('::%s'%i) + return other + +def AA_OTHER_REMOVE(mode): + other = set() + for i in mode: + if '::' in i: + other.add(i[2:]) + return other + AA_MAY_EXEC = set('x') AA_MAY_WRITE = set('w') AA_MAY_READ = set('r') @@ -7,15 +20,15 @@ AA_MAY_APPEND = set('a') AA_MAY_LINK = set('l') AA_MAY_LOCK = set('k') AA_EXEC_MMAP = set('m') -AA_EXEC_UNSAFE = set('unsafe') +AA_EXEC_UNSAFE = set('z') AA_EXEC_INHERIT = set('i') AA_EXEC_UNCONFINED = set('U') AA_EXEC_PROFILE = set('P') AA_EXEC_CHILD = set('C') AA_EXEC_NT = set('N') AA_LINK_SUBSET = set('ls') -AA_OTHER_SHIFT = 14 -AA_USER_MASK = 16384 - 1 +#AA_OTHER_SHIFT = 14 +#AA_USER_MASK = 16384 - 1 AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) @@ -28,31 +41,30 @@ MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, 'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK, 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, - 'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe + 'u': AA_EXEC_UNCONFINED | AA_EXEC_UNSAFE, # Unconfined + Unsafe 'U': AA_EXEC_UNCONFINED, - 'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe + 'p': AA_EXEC_PROFILE | AA_EXEC_UNSAFE, # Profile + unsafe 'P': AA_EXEC_PROFILE, - 'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe + 'c': AA_EXEC_CHILD | AA_EXEC_UNSAFE, # Child + Unsafe 'C': AA_EXEC_CHILD, - 'n': AA_EXEC_NT + AA_EXEC_UNSAFE, + 'n': AA_EXEC_NT | AA_EXEC_UNSAFE, 'N': AA_EXEC_NT } LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') -MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N') +MODE_MAP_RE = re.compile('(r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N)') def str_to_mode(string): if not string: return set() user, other = split_log_mode(string) - if not user: user = other mode = sub_str_to_mode(user) #print(string, mode) #print(string, 'other', sub_str_to_mode(other)) - mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT) + mode |= (AA_OTHER(sub_str_to_mode(other))) #print (string, mode) #print('str_to_mode:', mode) return mode @@ -62,11 +74,10 @@ def sub_str_to_mode(string): if not string: return mode while string: - pattern = '(%s)' % MODE_MAP_RE.pattern - tmp = re.search(pattern, string) + tmp = MODE_MAP_RE.search(string) if tmp: tmp = tmp.groups()[0] - string = re.sub(pattern, '', string) + string = MODE_MAP_RE.sub('', string, 1) if tmp and MODE_HASH.get(tmp, False): mode |= MODE_HASH[tmp] else: @@ -90,8 +101,8 @@ def mode_contains(mode, subset): # w implies a if mode & AA_MAY_WRITE: mode |= AA_MAY_APPEND - if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT): - mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT) + if mode & (AA_OTHER(AA_MAY_WRITE)): + mode |= (AA_OTHER(AA_MAY_APPEND)) return (mode & subset) == subset @@ -109,47 +120,121 @@ def validate_log_mode(mode): def hide_log_mode(mode): mode = mode.replace('::', '') return mode + +def map_log_mode(mode): + return mode + +def print_mode(mode): + user, other = split_mode(mode) + string = sub_mode_to_str(user) + '::' + sub_mode_to_str(other) -AA_MAY_EXEC = 1 -AA_MAY_WRITE = 2 -AA_MAY_READ = 4 -AA_MAY_APPEND = 8 -AA_MAY_LINK = 16 -AA_MAY_LOCK = 32 -AA_EXEC_MMAP = 64 -AA_EXEC_UNSAFE = 128 -AA_EXEC_INHERIT = 256 -AA_EXEC_UNCONFINED = 512 -AA_EXEC_PROFILE = 1024 -AA_EXEC_CHILD = 2048 -AA_EXEC_NT = 4096 -AA_LINK_SUBSET = 8192 -AA_OTHER_SHIFT = 14 -AA_USER_MASK = 16384 - 1 + return string -AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | - AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) +def sub_mode_to_str(mode): + string = '' + # w(write) implies a(append) + if mode & AA_MAY_WRITE: + mode = mode - AA_MAY_APPEND + #string = ''.join(mode) + + if mode & AA_EXEC_MMAP: + string += 'm' + if mode & AA_MAY_READ: + string += 'r' + if mode & AA_MAY_WRITE: + string += 'w' + if mode & AA_MAY_APPEND: + string += 'a' + if mode & AA_MAY_LINK: + string += 'l' + if mode & AA_MAY_LOCK: + string += 'k' + + # modes P and C must appear before I and U else invalid syntax + if mode & (AA_EXEC_PROFILE | AA_EXEC_NT): + if mode & AA_EXEC_UNSAFE: + string += 'p' + else: + string += 'P' + + if mode & AA_EXEC_CHILD: + if mode & AA_EXEC_UNSAFE: + string += 'c' + else: + string += 'C' + + if mode & AA_EXEC_UNCONFINED: + if mode & AA_EXEC_UNSAFE: + string += 'u' + else: + string += 'U' + + if mode & AA_EXEC_INHERIT: + string += 'i' + + if mode & AA_MAY_EXEC: + string += 'x' + + return string -ALL_AA_EXEC_TYPE = AA_EXEC_TYPE # The same value +def is_user_mode(mode): + user, other = split_mode(mode) + + if user and not other: + return True + else: + return False -# Modes and their values -MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, - 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, - 'r': AA_MAY_READ, 'R': AA_MAY_READ, - 'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND, - 'l': AA_MAY_LINK, 'L': AA_MAY_LINK, - 'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK, - 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, - 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, - 'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe - 'U': AA_EXEC_UNCONFINED, - 'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe - 'P': AA_EXEC_PROFILE, - 'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe - 'C': AA_EXEC_CHILD, - 'n': AA_EXEC_NT + AA_EXEC_UNSAFE, - 'N': AA_EXEC_NT - } +def profilemode(mode): + pass + +def split_mode(mode): + user = set() + for i in mode: + if not '::' in i: + user.add(i) + other = mode - user + other = AA_OTHER_REMOVE(other) + return user, other + +def mode_to_str(mode): + mode = flatten_mode(mode) + return sub_mode_to_str(mode) + +def flatten_mode(mode): + if not mode: + return set() + + user, other = split_mode(mode) + mode = user | other + mode |= (AA_OTHER(mode)) + + return mode + +def owner_flatten_mode(mode): + mode = flatten_mode(mode) + return mode + +def mode_to_str_user(mode): + user, other = split_mode(mode) + string = '' + + if not user: + user = set() + if not other: + other = set() + + if user - other: + if other: + string = sub_mode_to_str(other) + '+' + string += 'owner ' + sub_mode_to_str(user - other) + + elif is_user_mode(mode): + string = 'owner ' + sub_mode_to_str(user) + else: + string = sub_mode_to_str(flatten_mode(mode)) + + return string def log_str_to_mode(profile, string, nt_name): mode = str_to_mode(string) @@ -165,94 +250,17 @@ def log_str_to_mode(profile, string, nt_name): if lprofile == profile: if mode & AA_MAY_EXEC: tmode = str_to_mode('Cx::') - if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): + if mode & AA_OTHER(AA_MAY_EXEC): tmode |= str_to_mode('Cx') nt_name = lhat else: if mode & AA_MAY_EXEC: tmode = str_to_mode('Px::') - if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): + if mode & AA_OTHER(AA_MAY_EXEC): tmode |= str_to_mode('Px') nt_name = lhat - mode = mode & ~str_to_mode('Nx') + mode = mode - str_to_mode('Nx') mode |= tmode - return mode, nt_name - -def hide_log_mode(mode): - mode = mode.replace('::', '') - return mode - -def validate_log_mode(mode): - pattern = '^(%s)+$' % LOG_MODE_RE.pattern - if re.search(pattern, mode): - #if LOG_MODE_RE.search(mode): - return True - else: - return False - -def str_to_mode(string): - if not string: - return 0 - user, other = split_log_mode(string) - - if not user: - user = other - - mode = sub_str_to_mode(user) - #print(string, mode) - #print(string, 'other', sub_str_to_mode(other)) - mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT) - #print (string, mode) - #print('str_to_mode:', mode) - return mode - -def mode_contains(mode, subset): - # w implies a - if mode & AA_MAY_WRITE: - mode |= AA_MAY_APPEND - if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT): - mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT) - - # ix does not imply m - - ### ix implies m - ##if mode & AA_EXEC_INHERIT: - ## mode |= AA_EXEC_MMAP - ##if mode & (AA_EXEC_INHERIT << AA_OTHER_SHIFT): - ## mode |= (AA_EXEC_MMAP << AA_OTHER_SHIFT) - - return (mode & subset) == subset - -def contains(mode, string): - return mode_contains(mode, str_to_mode(string)) - -def sub_str_to_mode(string): - mode = 0 - if not string: - return mode - while string: - pattern = '(%s)' % MODE_MAP_RE.pattern - tmp = re.search(pattern, string) - if tmp: - tmp = tmp.groups()[0] - string = re.sub(pattern, '', string) - if tmp and MODE_HASH.get(tmp, False): - mode |= MODE_HASH[tmp] - else: - pass - - return mode - -def split_log_mode(mode): - user = '' - other = '' - match = re.search('(.*?)::(.*)', mode) - if match: - user, other = match.groups() - else: - user = mode - other = mode - #print ('split_logmode:', user, mode) - return user, other \ No newline at end of file + return mode, nt_name \ No newline at end of file From 5886faf63b9654bf6cf16fab450e01b64c3ab23c Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 11 Aug 2013 23:16:05 +0530 Subject: [PATCH 043/183] Working tool (seemingly to me), except the writing profile order needs to be fixed --- apparmor/aa.py | 377 +++++++-------------------------------------- apparmor/aamode.py | 2 + apparmor/ui.py | 6 +- 3 files changed, 59 insertions(+), 326 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index ec1b0797a..212db8c6c 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,6 +1,3 @@ -#6585 -#382-430 -#6414-6472 # No old version logs, only 2.6 + supported #global variable names corruption from __future__ import with_statement @@ -83,65 +80,6 @@ helpers = dict() # Preserve this between passes # was our filelist = hasher() # File level variables and stuff in config files -AA_MAY_EXEC = 1 -AA_MAY_WRITE = 2 -AA_MAY_READ = 4 -AA_MAY_APPEND = 8 -AA_MAY_LINK = 16 -AA_MAY_LOCK = 32 -AA_EXEC_MMAP = 64 -AA_EXEC_UNSAFE = 128 -AA_EXEC_INHERIT = 256 -AA_EXEC_UNCONFINED = 512 -AA_EXEC_PROFILE = 1024 -AA_EXEC_CHILD = 2048 -AA_EXEC_NT = 4096 -AA_LINK_SUBSET = 8192 -AA_OTHER_SHIFT = 14 -AA_USER_MASK = 16384 - 1 - -AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | - AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) - -ALL_AA_EXEC_TYPE = AA_EXEC_TYPE # The same value - -# Modes and their values -MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, - 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, - 'r': AA_MAY_READ, 'R': AA_MAY_READ, - 'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND, - 'l': AA_MAY_LINK, 'L': AA_MAY_LINK, - 'k': AA_MAY_LOCK, 'K': AA_MAY_LOCK, - 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, - 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, - 'u': AA_EXEC_UNCONFINED + AA_EXEC_UNSAFE, # Unconfined + Unsafe - 'U': AA_EXEC_UNCONFINED, - 'p': AA_EXEC_PROFILE + AA_EXEC_UNSAFE, # Profile + unsafe - 'P': AA_EXEC_PROFILE, - 'c': AA_EXEC_CHILD + AA_EXEC_UNSAFE, # Child + Unsafe - 'C': AA_EXEC_CHILD, - 'n': AA_EXEC_NT + AA_EXEC_UNSAFE, - 'N': AA_EXEC_NT - } - -# Used by netdomain to identify the operation types -OPERATION_TYPES = { - # New socket names - 'create': 'net', - 'post_create': 'net', - 'bind': 'net', - 'connect': 'net', - 'listen': 'net', - 'accept': 'net', - 'sendmsg': 'net', - 'recvmsg': 'net', - 'getsockname': 'net', - 'getpeername': 'net', - 'getsockopt': 'net', - 'setsockopt': 'net', - 'sock_shutdown': 'net' - } - def on_exit(): """Shutdowns the logger and records exit if debugging enabled""" debug_logger.debug('Exiting..') @@ -989,7 +927,7 @@ def handle_children(profile, hat, root): if mode & str_to_mode('x'): if os.path.isdir(exec_target): - mode = mode - ALL_AA_EXEC_TYPE + mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE mode = mode | str_to_mode('ix') else: do_execute = True @@ -1388,23 +1326,9 @@ def handle_children(profile, hat, root): return None -MODE_MAP_RE = re.compile('r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N') -LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x') - -def hide_log_mode(mode): - mode = mode.replace('::', '') - return mode - -def validate_log_mode(mode): - pattern = '^(%s)+$' % LOG_MODE_RE.pattern - if re.search(pattern, mode): - #if LOG_MODE_RE.search(mode): - return True - else: - return False ##### Repo related functions @@ -1609,7 +1533,7 @@ def ask_the_questions(): deny_audit |= cam if deny_mode & AA_MAY_EXEC: - deny_mode |= ALL_AA_EXEC_TYPE + deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE # Mask off the denied modes mode = mode - deny_mode @@ -1619,7 +1543,7 @@ def ask_the_questions(): # if not add ix permission if mode & AA_MAY_EXEC: # Remove all type access permission - mode = mode - ALL_AA_EXEC_TYPE + mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE if not allow_mode & AA_MAY_EXEC: mode |= str_to_mode('ix') @@ -1660,13 +1584,15 @@ def ask_the_questions(): if not include_valid: continue + cm, am, m = match_include_to_path(incname, 'allow', path) - if 'base' in incname: print(cm,am,m,mode,mode_contains(cm, mode)) + if cm and mode_contains(cm, mode): - dm = match_include_to_path(incname, 'deny', path) + + dm = match_include_to_path(incname, 'deny', path)[0] # If the mode is denied if not mode & dm: - if not filter(lambda s: '/**' not in s, m): + if not filter(lambda s: '/**' == s, m): newincludes.append(incname) # Add new includes to the options if newincludes: @@ -1816,7 +1742,7 @@ def ask_the_questions(): aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode - tmpmode = 0 + tmpmode = set() if audit_toggle == 1: tmpmode = mode- allow_mode elif audit_toggle == 2: @@ -2095,7 +2021,7 @@ def match_cap_includes(profile, cap): def re_match_include(path): """Matches the path for include and returns the include path""" - regex_include = re.compile('^\s*#?include\s*<(\.*)\s*(#.*)?$>') + regex_include = re.compile('^\s*#?include\s*<(.*)>\s*(#.*)?$') match = regex_include.search(path) if match: return match.groups()[0] @@ -2245,9 +2171,9 @@ def save_profiles(): while ans != 'CMD_SAVE_CHANGES': ans, arg = UI_PromptUser(q) if ans == 'CMD_VIEW_CHANGES': - which = changed[arg] - oldprofile = serialize_profile(original_aa[which], which) - newprofile = serialize_profile(aa[which], which) + which = changed.keys()[arg] + oldprofile = serialize_profile(original_aa[which], which, '') + newprofile = serialize_profile(aa[which], which, '') display_changes(oldprofile, newprofile) @@ -2340,7 +2266,7 @@ def collapse_log(): for path in prelog[aamode][profile][hat]['path'].keys(): mode = prelog[aamode][profile][hat]['path'][path] - combinedmode = 0 + combinedmode = set() # Is path in original profile? if aa[profile][hat]['allow']['path'].get(path, False): combinedmode |= aa[profile][hat]['allow']['path'][path] @@ -2349,6 +2275,7 @@ def collapse_log(): combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0] # Match path from includes + combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)[0] if not combinedmode or not mode_contains(combinedmode, mode): @@ -2368,23 +2295,6 @@ def collapse_log(): if not profile_known_network(aa[profile][hat], family, sock_type): log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True -def profilemode(mode): - pass - -def split_log_mode(mode): - user = '' - other = '' - match = re.search('(.*?)::(.*)', mode) - if match: - user, other = match.groups() - else: - user = mode - other = mode - #print ('split_logmode:', user, mode) - return user, other - -def map_log_mode(mode): - return mode def validate_profile_mode(mode, allow, nt_name=None): if allow == 'deny': @@ -2407,192 +2317,6 @@ def validate_profile_mode(mode, allow, nt_name=None): return True else: return False - -def sub_str_to_mode(string): - mode = 0 - if not string: - return mode - while string: - pattern = '(%s)' % MODE_MAP_RE.pattern - tmp = re.search(pattern, string) - if tmp: - tmp = tmp.groups()[0] - string = re.sub(pattern, '', string) - if tmp and MODE_HASH.get(tmp, False): - mode |= MODE_HASH[tmp] - else: - pass - - return mode - -def print_mode(mode): - user, other = split_mode(mode) - string = sub_mode_to_str(user) + '::' + sub_mode_to_str(other) - - return string - -def str_to_mode(string): - if not string: - return 0 - user, other = split_log_mode(string) - - if not user: - user = other - - mode = sub_str_to_mode(user) - #print(string, mode) - #print(string, 'other', sub_str_to_mode(other)) - mode |= (sub_str_to_mode(other) << AA_OTHER_SHIFT) - #print (string, mode) - #print('str_to_mode:', mode) - return mode - -def log_str_to_mode(profile, string, nt_name): - mode = str_to_mode(string) - # If contains nx and nix - #print (profile, string, nt_name) - if contains(mode, 'Nx'): - # Transform to px, cx - match = re.search('(.+?)//(.+?)', nt_name) - if match: - lprofile, lhat = match.groups() - tmode = 0 - - if lprofile == profile: - if mode & AA_MAY_EXEC: - tmode = str_to_mode('Cx::') - if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): - tmode |= str_to_mode('Cx') - nt_name = lhat - else: - if mode & AA_MAY_EXEC: - tmode = str_to_mode('Px::') - if mode & (AA_MAY_EXEC << AA_OTHER_SHIFT): - tmode |= str_to_mode('Px') - nt_name = lhat - - mode = mode - str_to_mode('Nx') - mode |= tmode - - return mode, nt_name - -def split_mode(mode): - user = mode & AA_USER_MASK - other = (mode >> AA_OTHER_SHIFT) & AA_USER_MASK - - return user, other - -def is_user_mode(mode): - user, other = split_mode(mode) - - if user and not other: - return True - else: - return False - -def sub_mode_to_str(mode): - string = '' - # w(write) implies a(append) - if mode & AA_MAY_WRITE: - mode = mode - AA_MAY_APPEND - - if mode & AA_EXEC_MMAP: - string += 'm' - if mode & AA_MAY_READ: - string += 'r' - if mode & AA_MAY_WRITE: - string += 'w' - if mode & AA_MAY_APPEND: - string += 'a' - if mode & AA_MAY_LINK: - string += 'l' - if mode & AA_MAY_LOCK: - string += 'k' - - # modes P and C must appear before I and U else invalid syntax - if mode & (AA_EXEC_PROFILE | AA_EXEC_NT): - if mode & AA_EXEC_UNSAFE: - string += 'p' - else: - string += 'P' - - if mode & AA_EXEC_CHILD: - if mode & AA_EXEC_UNSAFE: - string += 'c' - else: - string += 'C' - - if mode & AA_EXEC_UNCONFINED: - if mode & AA_EXEC_UNSAFE: - string += 'u' - else: - string += 'U' - - if mode & AA_EXEC_INHERIT: - string += 'i' - - if mode & AA_MAY_EXEC: - string += 'x' - - return string - -def flatten_mode(mode): - if not mode: - return 0 - - mode = (mode & AA_USER_MASK) | ((mode >> AA_OTHER_SHIFT) & AA_USER_MASK) - mode |= (mode << AA_OTHER_SHIFT) - - return mode - -def mode_to_str(mode): - mode = flatten_mode(mode) - return sub_mode_to_str(mode) - -def owner_flatten_mode(mode): - mode = flatten_mode(mode) &AA_USER_MASK - return mode - -def mode_to_str_user(mode): - user, other = split_mode(mode) - string = '' - - if not user: - user = 0 - if not other: - other = 0 - - if user - other: - if other: - string = sub_mode_to_str(other) + '+' - string += 'owner ' + sub_mode_to_str(user - other) - - elif is_user_mode(mode): - string = 'owner ' + sub_mode_to_str(user) - else: - string = sub_mode_to_str(flatten_mode(mode)) - - return string - -def mode_contains(mode, subset): - # w implies a - if mode & AA_MAY_WRITE: - mode |= AA_MAY_APPEND - if mode & (AA_MAY_WRITE << AA_OTHER_SHIFT): - mode |= (AA_MAY_APPEND << AA_OTHER_SHIFT) - - # ix does not imply m - - ### ix implies m - ##if mode & AA_EXEC_INHERIT: - ## mode |= AA_EXEC_MMAP - ##if mode & (AA_EXEC_INHERIT << AA_OTHER_SHIFT): - ## mode |= (AA_EXEC_MMAP << AA_OTHER_SHIFT) - - return (mode & subset) == subset - -def contains(mode, string): - return mode_contains(mode, str_to_mode(string)) # rpm backup files, dotfiles, emacs backup files should not be processed # The skippable files type needs be synced with apparmor initscript @@ -2696,11 +2420,11 @@ def parse_profile_data(data, file, do_include): if do_include: profile = file hat = file - for lineno, line in enumerate(data): line = line.strip() if not line: continue + # Starting line of a profile if RE_PROFILE_START.search(line): matches = RE_PROFILE_START.search(line).groups() @@ -2808,15 +2532,15 @@ def parse_profile_data(data, file, do_include): link = strip_quotes(matches[6]) value = strip_quotes(matches[7]) profile_data[profile][hat][allow]['link'][link]['to'] = value - profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', 0) | AA_MAY_LINK + profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', set()) | AA_MAY_LINK if subset: profile_data[profile][hat][allow]['link'][link]['mode'] |= AA_LINK_SUBSET if audit: - profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', 0) | AA_LINK_SUBSET + profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', set()) | AA_LINK_SUBSET else: - profile_data[profile][hat][allow]['link'][link]['audit'] = 0 + profile_data[profile][hat][allow]['link'][link]['audit'] = set() elif RE_PROFILE_CHANGE_PROFILE.search(line): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() @@ -2924,26 +2648,26 @@ def parse_profile_data(data, file, do_include): if not validate_profile_mode(mode, allow, nt_name): raise AppArmorException('Invalid mode %s in file: %s line: %s' % (mode, file, lineno+1)) - tmpmode = None + tmpmode = set() if user: tmpmode = str_to_mode('%s::' % mode) else: tmpmode = str_to_mode(mode) - profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', 0) | tmpmode + profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', set()) | tmpmode if nt_name: profile_data[profile][hat][allow]['path'][path]['to'] = nt_name if audit: - profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit', 0) | tmpmode + profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit', set()) | tmpmode else: - profile_data[profile][hat][allow]['path'][path]['audit'] = 0 + profile_data[profile][hat][allow]['path'][path]['audit'] = set() elif re_match_include(line): # Include files include = re_match_include(line) - + if profile: profile_data[profile][hat]['include'][include] = True else: @@ -2958,6 +2682,7 @@ def parse_profile_data(data, file, do_include): continue if os.path.isfile(profile_dir + '/' + include + '/' + path): file_name = include + '/' + path + file_name = file_name.replace(profile_dir+'/', '') load_include(file_name) else: load_include(include) @@ -3022,7 +2747,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' if filelist[file]['profiles'][profile].get(hat, False): - raise AppArmorException('Error: Multiple definitions for hat %s in profile %s.' %(hat, profile)) + pass#raise AppArmorException('Error: Multiple definitions for hat %s in profile %s.' %(hat, profile)) filelist[file]['profiles'][profile][hat] = True elif line[0] == '#': @@ -3125,7 +2850,7 @@ def write_header(prof_data, depth, name, embedded_hat, write_flags): name = 'profile %s' % name if write_flags and prof_data['flags']: - data.append('%s%s flags(%s) {' % (pre, name, prof_data['flags'])) + data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags'])) else: data.append('%s%s {' % (pre, name)) @@ -3137,7 +2862,7 @@ def write_single(prof_data, depth, allow, name, prefix, tail): ref, allow = set_ref_allow(prof_data, allow) if ref.get(name, False): - for key in sorted(re[name].keys()): + for key in sorted(ref[name].keys()): qkey = quote_if_needed(key) data.append('%s%s%s%s%s' %(pre, allow, prefix, qkey, tail)) if ref[name].keys(): @@ -3155,7 +2880,7 @@ def set_ref_allow(prof_data, allow): if allow: return prof_data[allow], set_allow_str(allow) else: - return prof_data, 'allow' + return prof_data, '' def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn): @@ -3559,6 +3284,7 @@ def reload(bin_path): def get_include_data(filename): data = [] + filename = profile_dir + '/' + filename if os.path.exists(filename): with open_file_read(filename) as f_in: data = f_in.readlines() @@ -3581,29 +3307,33 @@ def load_include(incname): return 0 def rematchfrag(frag, allow, path): - combinedmode = 0 - combinedaudit = 0 + combinedmode = set() + combinedaudit = set() matches = [] - + if not frag: + return combinedmode, combinedaudit, matches for entry in frag[allow]['path'].keys(): match = matchliteral(entry, path) if match: - combinedmode |= frag[allow]['path'][entry]['mode'] - combinedaudit |= frag[allow]['path'][entry]['audit'] + #print(frag[allow]['path'][entry]['mode']) + combinedmode |= frag[allow]['path'][entry].get('mode', set()) + combinedaudit |= frag[allow]['path'][entry].get('audit', set()) matches.append(entry) return combinedmode, combinedaudit, matches def match_include_to_path(incname, allow, path): - combinedmode = 0 - combinedaudit = 0 + combinedmode = set() + combinedaudit = set() matches = [] - incname = profile_dir + '/' + incname includelist = [incname] while includelist: - incfile = includelist.pop(0) + incfile = str(includelist.pop(0)) ret = load_include(incfile) - cm, am , m = rematchfrag(include[incfile][incfile], allow, path) + if not include.get(incfile,{}): + continue + cm, am , m = rematchfrag(include[incfile].get(incfile, {}), allow, path) + #print(incfile, cm, am, m) if cm: combinedmode |= cm combinedaudit |= am @@ -3614,13 +3344,13 @@ def match_include_to_path(incname, allow, path): combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit'] if include[incfile][incfile]['include'].keys(): - includelist + include[incfile][incfile]['include'].keys() - + includelist += include[incfile][incfile]['include'].keys() + return combinedmode, combinedaudit, matches def match_prof_incs_to_path(frag, allow, path): - combinedmode = 0 - combinedaudit = 0 + combinedmode = set() + combinedaudit = set() matches = [] includelist = list(frag['include'].keys()) @@ -3635,8 +3365,8 @@ def match_prof_incs_to_path(frag, allow, path): return combinedmode, combinedaudit, matches def suggest_incs_for_path(incname, path, allow): - combinedmode = 0 - combinedaudit = 0 + combinedmode = set() + combinedaudit = set() matches = [] includelist = [incname] @@ -3675,7 +3405,6 @@ def get_subdirectories(current_dir): def loadincludes(): incdirs = get_subdirectories(profile_dir) - for idir in incdirs: if is_skippable_dir(idir): continue @@ -3686,7 +3415,9 @@ def loadincludes(): if is_skippable_file(fi): continue else: - load_include(dirpath + '/' + fi) + fi = dirpath + '/' + fi + fi = fi.replace(profile_dir+'/', '', 1) + load_include(fi) def glob_common(path): globs = [] @@ -3701,7 +3432,7 @@ def glob_common(path): for glob in cfg['globs']: if re.search(glob, path): globbedpath = path - globbedpath = re.sub(glob, cfg['globs'][glob]) + globbedpath = re.sub(glob, cfg['globs'][glob], path) if globbedpath != path: globs.append(globbedpath) diff --git a/apparmor/aamode.py b/apparmor/aamode.py index 3142c7cc2..aa3067cd9 100644 --- a/apparmor/aamode.py +++ b/apparmor/aamode.py @@ -33,6 +33,8 @@ AA_LINK_SUBSET = set('ls') AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | AA_EXEC_UNCONFINED | AA_EXEC_PROFILE | AA_EXEC_CHILD | AA_EXEC_NT) +ALL_AA_EXEC_TYPE = AA_EXEC_TYPE + MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, 'r': AA_MAY_READ, 'R': AA_MAY_READ, diff --git a/apparmor/ui.py b/apparmor/ui.py index 964cfc69f..845eedc9a 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -272,7 +272,7 @@ def Text_PromptUser(question): default = question['default'] options = question['options'] - selected = question.get('selected', False) or 0 + selected = question.get('selected', 0) helptext = question['helptext'] if helptext: functions.append('CMD_HELP') @@ -363,7 +363,7 @@ def Text_PromptUser(question): sys.stdout.write(prompt+'\n') - ans = readkey().lower() + ans = getkey().lower() if ans: if ans == 'up': @@ -372,7 +372,7 @@ def Text_PromptUser(question): ans = 'XXXINVALIDXXX' elif ans == 'down': - if options and selected < len(options)-2: + if options and selected < len(options)-1: selected += 1 ans = 'XXXINVALIDXXX' From f12667c011829d3a406d5a71db9d2caeddcadd83 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 11 Aug 2013 23:16:35 +0530 Subject: [PATCH 044/183] Working tool (seemingly to me), except the writing profile order needs to be fixed --- apparmor/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apparmor/common.py b/apparmor/common.py index b2d808b16..a534d99dc 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -208,8 +208,8 @@ class DebugLogger: elif debug_level == 3: debug_level = logging.DEBUG - #logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') - logging.basicConfig(filename='/home/kshitij/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') + logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') + self.logger = logging.getLogger(module_name) From 396b504b5fc89474a3773611b6ed2b77cdb0cfee Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 11 Aug 2013 23:22:08 +0530 Subject: [PATCH 045/183] minor fix --- Tools/out.out | 651 -------------------------------------------------- 1 file changed, 651 deletions(-) delete mode 100644 Tools/out.out diff --git a/Tools/out.out b/Tools/out.out deleted file mode 100644 index bd91bd41b..000000000 --- a/Tools/out.out +++ /dev/null @@ -1,651 +0,0 @@ -Reading log entries from /var/log/messages. -Updating AppArmor profiles in /etc/apparmor.d. - ARRAY(0x1ac8bb8) ARRAY(0x1af2b80) ARRAY(0xbbe5f8) ARRAY(0x1b06848) ARRAY(0x1af2bb0) ARRAY(0x1af0a20) ARRAY(0x1b0ab68) ARRAY(0x1ab3918) ARRAY(0x1a865b0) ARRAY(0x1b753e8) ARRAY(0x1afc510) ARRAY(0x1abf788) - be dumper@event = ( - [ - 'path', - 660, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ); -$VAR2 = [ - [ - 'path', - 694, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR3 = [ - [ - 'path', - 659, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR4 = [ - [ - 'path', - 642, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR5 = [ - [ - 'path', - 676, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR6 = [ - [ - 'path', - 671, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR7 = [ - [ - 'path', - 667, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR8 = [ - [ - 'path', - 661, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR9 = [ - [ - 'path', - 684, - '/usr/sbin/nscd', - '/usr/sbin/nscd', - 'HINT', - 'REJECTING', - 65540, - '/proc/sys/vm/overcommit_memory', - '' - ] - ]; -$VAR10 = [ - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 1114180, - '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/usr/lib64/empathy/libempathy-3.6.3.so', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 1114180, - '/usr/lib64/empathy/libempathy-3.6.3.so', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/etc/ld.so.cache', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/usr/lib64/libdbus-glib-1.so.2.2.2', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 32770, - '/home/kshitij/.config/Empathy/geometry.ini.7YRV1W', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/home/kshitij/.config/Empathy/geometry.ini.7YRV1W', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/home/kshitij/.config/Empathy/geometry.ini.7YRV1W', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 32770, - '/home/kshitij/.config/Empathy/geometry.ini', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 98310, - '/run/user/1000/dconf/user', - '' - ], - [ - 'path', - 7664, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/home/kshitij/.config/dconf/user', - '' - ] - ]; -$VAR11 = [ - [ - 'path', - 7671, - 'null-complain-profile', - 'null-complain-profile', - 'HINT', - 'PERMITTING', - 65540, - '/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg', - '' - ], - [ - 'path', - 7671, - 'null-complain-profile', - 'null-complain-profile', - 'HINT', - 'PERMITTING', - 65540, - '/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg', - '' - ], - [ - 'path', - 7671, - 'null-complain-profile', - 'null-complain-profile', - 'HINT', - 'PERMITTING', - 65540, - '/usr/share/icons/gnome/scalable/actions/list-remove-symbolic.svg', - '' - ], - [ - 'path', - 7671, - 'null-complain-profile', - 'null-complain-profile', - 'HINT', - 'PERMITTING', - 65540, - '/usr/share/icons/gnome/scalable/actions/list-remove-symbolic.svg', - '' - ], - [ - 'path', - 7671, - 'null-complain-profile', - 'null-complain-profile', - 'HINT', - 'PERMITTING', - 65540, - '/usr/share/icons/gnome/scalable/actions/list-add-symbolic.svg', - '' - ] - ]; -$VAR12 = [ - [ - 'path', - 7753, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', - '' - ], - [ - 'path', - 7753, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 1114180, - '/usr/lib64/empathy/libempathy-gtk-3.6.3.so', - '' - ], - [ - 'path', - 7753, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/usr/lib64/empathy/libempathy-3.6.3.so', - '' - ], - [ - 'path', - 7753, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 1114180, - '/usr/lib64/empathy/libempathy-3.6.3.so', - '' - ], - [ - 'path', - 7753, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/etc/ld.so.cache', - '' - ], - [ - 'path', - 7753, - '/usr/bin/empathy', - '/usr/bin/empathy', - 'HINT', - 'PERMITTING', - 65540, - '/usr/lib64/libdbus-glib-1.so.2.2.2', - '' - ] - ]; -\n end dumper ARRAY(0x1b65788) - Entry: ARRAY(0x1b65788) - ARRAY(0x1ac9188) - Entry: ARRAY(0x1ac9188) - ARRAY(0x1a98d98) - Entry: ARRAY(0x1a98d98) - ARRAY(0x1aae1c0) - Entry: ARRAY(0x1aae1c0) - ARRAY(0x1af09d8) - Entry: ARRAY(0x1af09d8) - ARRAY(0x1b6fa20) - Entry: ARRAY(0x1b6fa20) - ARRAY(0x1a9d920) - Entry: ARRAY(0x1a9d920) - ARRAY(0x1ae70b8) - Entry: ARRAY(0x1ae70b8) - ARRAY(0x1b0fac0) - Entry: ARRAY(0x1b0fac0) - ARRAY(0xbbe340) ARRAY(0x1b54130) ARRAY(0x1b18578) ARRAY(0x1b040e0) ARRAY(0x1b73600) ARRAY(0x1a9e298) ARRAY(0x1b0a370) ARRAY(0x1adfb70) ARRAY(0x1ac9008) ARRAY(0x1ac1aa0) ARRAY(0x1ab8428) ARRAY(0x1b12e90) ARRAY(0x1a89ab0) ARRAY(0x1aaa6d8) ARRAY(0x1b4d400) ARRAY(0x1b4d5c8) ARRAY(0x1adb230) ARRAY(0x1aadb60) ARRAY(0x1ab3d68) ARRAY(0x1b75fb8) ARRAY(0x1aaa588) ARRAY(0x1b4dcd0) ARRAY(0x1a96c48) ARRAY(0x1adf468) ARRAY(0x1b5ca10) ARRAY(0x1aa76e8) ARRAY(0x1ab8cf8) ARRAY(0x1b72df0) ARRAY(0x1ae9630) ARRAY(0x1a86a90) - Entry: ARRAY(0xbbe340) - Entry: ARRAY(0x1b54130) - Entry: ARRAY(0x1b18578) - Entry: ARRAY(0x1b040e0) - Entry: ARRAY(0x1b73600) - Entry: ARRAY(0x1a9e298) - Entry: ARRAY(0x1b0a370) - Entry: ARRAY(0x1adfb70) - Entry: ARRAY(0x1ac9008) - Entry: ARRAY(0x1ac1aa0) - Entry: ARRAY(0x1ab8428) - Entry: ARRAY(0x1b12e90) - Entry: ARRAY(0x1a89ab0) - Entry: ARRAY(0x1aaa6d8) - Entry: ARRAY(0x1b4d400) - Entry: ARRAY(0x1b4d5c8) - Entry: ARRAY(0x1adb230) - Entry: ARRAY(0x1aadb60) - Entry: ARRAY(0x1ab3d68) - Entry: ARRAY(0x1b75fb8) - Entry: ARRAY(0x1aaa588) - Entry: ARRAY(0x1b4dcd0) - Entry: ARRAY(0x1a96c48) - Entry: ARRAY(0x1adf468) - Entry: ARRAY(0x1b5ca10) - Entry: ARRAY(0x1aa76e8) - Entry: ARRAY(0x1ab8cf8) - Entry: ARRAY(0x1b72df0) - Entry: ARRAY(0x1ae9630) - Entry: ARRAY(0x1a86a90) - ARRAY(0x1afc540) ARRAY(0x1b15f38) ARRAY(0x1ab8b60) ARRAY(0x1abf7b8) ARRAY(0x1af6378) - Entry: ARRAY(0x1afc540) - Entry: ARRAY(0x1b15f38) - Entry: ARRAY(0x1ab8b60) - Entry: ARRAY(0x1abf7b8) - Entry: ARRAY(0x1af6378) - ARRAY(0x1b61898) ARRAY(0x1b12ec0) ARRAY(0x1af8bf0) ARRAY(0x1b0feb0) ARRAY(0x1b05ab0) ARRAY(0x1ab8470) - Entry: ARRAY(0x1b61898) - Entry: ARRAY(0x1b12ec0) - Entry: ARRAY(0x1af8bf0) - Entry: ARRAY(0x1b0feb0) - Entry: ARRAY(0x1b05ab0) - Entry: ARRAY(0x1ab8470) -Complain-mode changes: - -Profile: /usr/bin/empathy -Path: /etc/ld.so.cache -Mode: r -Severity: 1 - - 1 - #include - 2 - #include - 3 - #include - 4 - #include - 5 - \ No newline at end of file From 2a1e419bf8243dca7f8ce3082a35cfe23c5ddfb1 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 13 Aug 2013 00:43:20 +0530 Subject: [PATCH 046/183] some fixes from review 41..45 and fixes for python3 compatibility --- apparmor/aa.py | 43 +++++++++++++++++++++++-------------------- apparmor/aamode.py | 9 ++++----- apparmor/common.py | 5 ----- apparmor/logparser.py | 10 +++++----- apparmor/severity.py | 2 +- apparmor/ui.py | 4 ++-- 6 files changed, 35 insertions(+), 38 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 212db8c6c..392490514 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -184,7 +184,7 @@ def get_full_path(original_path): return os.path.realpath(path) def find_executable(bin_path): - """Returns the full executable path for the given, None otherwise""" + """Returns the full executable path for the given executable, None otherwise""" full_bin = None if os.path.exists(bin_path): full_bin = get_full_path(bin_path) @@ -1405,7 +1405,7 @@ def ask_the_questions(): found += 1 # Sorted list of hats with the profile name coming first - hats = filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys())) + hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys()))) if log_dict[aamode][profile].get(profile, False): hats = [profile] + hats @@ -1588,11 +1588,10 @@ def ask_the_questions(): cm, am, m = match_include_to_path(incname, 'allow', path) if cm and mode_contains(cm, mode): - dm = match_include_to_path(incname, 'deny', path)[0] # If the mode is denied if not mode & dm: - if not filter(lambda s: '/**' == s, m): + if not list(filter(lambda s: '/**' == s, m)): newincludes.append(incname) # Add new includes to the options if newincludes: @@ -1616,7 +1615,7 @@ def ask_the_questions(): default_option = len(options) sev_db.unload_variables() - sev_db.load_variables(profile) + sev_db.load_variables(get_profile_filename(profile)) severity = sev_db.rank(path, mode_to_str(mode)) sev_db.unload_variables() @@ -2051,18 +2050,19 @@ def match_net_includes(profile, family, nettype): return newincludes -def do_logprof_pass(logmark='', pid=pid, existing_profiles=existing_profiles): +def do_logprof_pass(logmark='', pid=pid): # set up variables for this pass t = hasher() # transitions = hasher() seen = hasher() global log + global existing_profiles log = [] global sev_db # aa = hasher() # profile_changes = hasher() # prelog = hasher() -# log = [] + log = [] # log_dict = hasher() # changed = dict() skip = hasher() @@ -2071,7 +2071,7 @@ def do_logprof_pass(logmark='', pid=pid, existing_profiles=existing_profiles): UI_Info(_('Reading log entries from %s.') %filename) UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) - read_profiles() + read_profiles('nosub') if not sev_db: sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) @@ -2340,19 +2340,24 @@ def check_profile_syntax(errors): # To-Do pass -def read_profiles(): +def read_profiles(param=''): try: os.listdir(profile_dir) except : fatal_error('Can\'t read AppArmor profiles in %s' % profile_dir) - + for file in os.listdir(profile_dir): if os.path.isfile(profile_dir + '/' + file): if is_skippable_file(file): continue else: #print('read %s' %file) - read_profile(profile_dir + '/' + file, True) + if param == 'nosub': + #Already read all subdirectories in loadincludes + pass + else: + # Read profiles in sub directories + read_profile(profile_dir + '/' + file, True) def read_inactive_profiles(): if not os.path.exists(extra_profile_dir): @@ -2424,7 +2429,6 @@ def parse_profile_data(data, file, do_include): line = line.strip() if not line: continue - # Starting line of a profile if RE_PROFILE_START.search(line): matches = RE_PROFILE_START.search(line).groups() @@ -2466,7 +2470,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['name'] = profile profile_data[profile][hat]['filename'] = file filelist[file]['profiles'][profile][hat] = True - + profile_data[profile][hat]['flags'] = flags profile_data[profile][hat]['allow']['netdomain'] = hasher() @@ -2747,7 +2751,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' if filelist[file]['profiles'][profile].get(hat, False): - pass#raise AppArmorException('Error: Multiple definitions for hat %s in profile %s.' %(hat, profile)) + raise AppArmorException('Error: Multiple definitions for hat %s in profile %s.' %(hat, profile)) filelist[file]['profiles'][profile][hat] = True elif line[0] == '#': @@ -2810,9 +2814,8 @@ def store_list_var(var, list_var, value, var_operation): if not var.get(list_var, False): var[list_var] = set(vlist) else: - print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) - pass - #raise AppArmorException('An existing variable redefined: %s' %list_var) + #print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) + raise AppArmorException('An existing variable redefined: %s' %list_var) elif var_operation == '+=': if var.get(list_var, False): var[list_var] = set(var[list_var] + vlist) @@ -3086,13 +3089,13 @@ def write_piece(profile_data, depth, name, nhat, write_flags): pre2 = ' ' * (depth+1) # External hat declarations - for hat in filter(lambda x: x != name, sorted(profile_data.keys())): + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if profile_data[hat].get('declared', False): data.append('%s^%s,' %(pre2, hat)) if not inhat: # Embedded hats - for hat in filter(lambda x: x != name, sorted(profile_data.keys())): + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if not profile_data[hat]['external'] and not profile_data[hat]['declared']: data.append('') if profile_data[hat]['profile']: @@ -3107,7 +3110,7 @@ def write_piece(profile_data, depth, name, nhat, write_flags): data.append('%s}' %pre) # External hats - for hat in filter(lambda x: x != name, sorted(profile_data.keys())): + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if name == nhat and profile_data[hat].get('external', False): data.append('') data += map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags)) diff --git a/apparmor/aamode.py b/apparmor/aamode.py index aa3067cd9..b357a6a5f 100644 --- a/apparmor/aamode.py +++ b/apparmor/aamode.py @@ -20,13 +20,13 @@ AA_MAY_APPEND = set('a') AA_MAY_LINK = set('l') AA_MAY_LOCK = set('k') AA_EXEC_MMAP = set('m') -AA_EXEC_UNSAFE = set('z') +AA_EXEC_UNSAFE = set(['execunsafe']) AA_EXEC_INHERIT = set('i') AA_EXEC_UNCONFINED = set('U') AA_EXEC_PROFILE = set('P') AA_EXEC_CHILD = set('C') AA_EXEC_NT = set('N') -AA_LINK_SUBSET = set('ls') +AA_LINK_SUBSET = set(['linksubset']) #AA_OTHER_SHIFT = 14 #AA_USER_MASK = 16384 - 1 @@ -53,7 +53,7 @@ MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, 'N': AA_EXEC_NT } -LOG_MODE_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix') +LOG_MODE_RE = re.compile('(r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix)') MODE_MAP_RE = re.compile('(r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N)') def str_to_mode(string): @@ -112,8 +112,7 @@ def contains(mode, string): return mode_contains(mode, str_to_mode(string)) def validate_log_mode(mode): - pattern = '^(%s)+$' % LOG_MODE_RE.pattern - if re.search(pattern, mode): + if LOG_MODE_RE.search(mode): #if LOG_MODE_RE.search(mode): return True else: diff --git a/apparmor/common.py b/apparmor/common.py index a534d99dc..379e6b1d9 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -160,11 +160,6 @@ def convert_regexp(regexp): regex_paren = re.compile('^(.*){([^}]*)}(.*)$') regexp = regexp.strip() new_reg = re.sub(r'(?') if os.path.isfile(prof_path): - with open(prof_path, 'r') as f_in: + with open_file_read(prof_path) as f_in: for line in f_in: line = line.strip() # If any includes, load variables from them first diff --git a/apparmor/ui.py b/apparmor/ui.py index 845eedc9a..4bdfd2cff 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -56,7 +56,7 @@ def UI_YesNo(text, default): sys.stdout.write('\n[%s] / %s\n' % (yes, no)) else: sys.stdout.write('\n%s / [%s]\n' % (yes, no)) - ans = readkey() + ans = getkey()#readkey() if ans: ans = ans.lower() else: @@ -91,7 +91,7 @@ def UI_YesNoCancel(text, default): sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel)) else: sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel)) - ans = readkey() + ans = getkey()#readkey() if ans: ans = ans.lower() else: From 6ce67f3cbe32ff128bbd7df1d2711acd011b545b Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 13 Aug 2013 00:48:37 +0530 Subject: [PATCH 047/183] debugging level 0 fix --- apparmor/common.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apparmor/common.py b/apparmor/common.py index 379e6b1d9..6d3275c1e 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -194,14 +194,16 @@ class DebugLogger: self.debugging = int(self.debugging) except: self.debugging = False - if self.debugging not in range(1,4): + if self.debugging not in range(0,4): sys.stderr.out('Environment Variable: LOGPROF_DEBUG contains invalid value: %s' %os.getenv('LOGPROF_DEBUG')) + # self.debugging == 0 implies debugging = False if self.debugging == 1: - debug_level = logging.ERROR - elif self.debug_level == 2: - debug_level = logging.INFO - elif debug_level == 3: - debug_level = logging.DEBUG + self.debug_level = logging.ERROR + elif self.debugging == 2: + self.debug_level = logging.INFO + elif self.debugging == 3: + self.debug_level = logging.DEBUG + logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') From 457604014f1e9d15fa3757deab28b8d19e0f8463 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 17 Aug 2013 12:34:42 +0530 Subject: [PATCH 048/183] working commit prior to writer code alterations --- apparmor/aa.py | 22 ++++++++++++---------- apparmor/config.py | 2 ++ apparmor/logparser.py | 9 +++++---- apparmor/ui.py | 2 +- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 392490514..72814448b 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1422,7 +1422,7 @@ def ask_the_questions(): q = hasher() if newincludes: - options += map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes))) + options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) if options: options.append('capability %s' % capability) @@ -1470,8 +1470,10 @@ def ask_the_questions(): _('Severity'), severity] if ans == 'CMD_ALLOW': - selection = options[selected] - match = re_match_include(selection) #re.search('^#include\s+<(.+)>$', selection) + selection = '' + if options: + selection = options[selected] + match = re_match_include(selection) if match: deleted = False inc = match #.groups()[0] @@ -1595,7 +1597,7 @@ def ask_the_questions(): newincludes.append(incname) # Add new includes to the options if newincludes: - options += map(lambda s: '#include <%s>' % s, sorted(set(newincludes))) + options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes)))) # We should have literal the path in options list too options.append(path) # Add any the globs matching path from logprof @@ -1853,7 +1855,7 @@ def ask_the_questions(): newincludes = match_net_includes(aa[profile][hat], family, sock_type) q = hasher() if newincludes: - options += map(lambda s: '#include <%s>'%s, sorted(set(newincludes))) + options += list(map(lambda s: '#include <%s>'%s, sorted(set(newincludes)))) if options: options.append('network %s %s' % (family, sock_type)) q['options'] = options @@ -2171,7 +2173,7 @@ def save_profiles(): while ans != 'CMD_SAVE_CHANGES': ans, arg = UI_PromptUser(q) if ans == 'CMD_VIEW_CHANGES': - which = changed.keys()[arg] + which = list(changed.keys())[arg] oldprofile = serialize_profile(original_aa[which], which, '') newprofile = serialize_profile(aa[which], which, '') @@ -3099,11 +3101,11 @@ def write_piece(profile_data, depth, name, nhat, write_flags): if not profile_data[hat]['external'] and not profile_data[hat]['declared']: data.append('') if profile_data[hat]['profile']: - data += map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags)) + data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags))) else: - data += map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags)) + data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags))) - data += map(str, write_rules(profile_data[hat], depth+2)) + data += list(map(str, write_rules(profile_data[hat], depth+2))) data.append('%s}' %pre2) @@ -3113,7 +3115,7 @@ def write_piece(profile_data, depth, name, nhat, write_flags): for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if name == nhat and profile_data[hat].get('external', False): data.append('') - data += map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags)) + data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags))) data.append(' }') return data diff --git a/apparmor/config.py b/apparmor/config.py index 5961bab9a..e6ea8a5f6 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -277,6 +277,8 @@ def py2_parser(filename): # entries being indented deeper hence simple lstrip() is not appropriate if line[:2] == ' ': line = line[2:] + elif line[0] == '\t': + line = line[1:] f_out.write(line) f_out.flush() return tmp \ No newline at end of file diff --git a/apparmor/logparser.py b/apparmor/logparser.py index 4ddcc54a4..1b6ac3544 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -50,7 +50,7 @@ class ReadLog: if self.next_log_entry: sys.stderr.out('A log entry already present: %s' % self.next_log_entry) self.next_log_entry = self.LOG.readline() - while not (self.RE_LOG_v2_6_syslog.search(self.next_log_entry) or self.RE_LOG_v2_6_audit.search(self.next_log_entry)): #or re.search(self.logmark, self.next_log_entry)): + while not (self.RE_LOG_v2_6_syslog.search(self.next_log_entry) or self.RE_LOG_v2_6_audit.search(self.next_log_entry)) or (self.logmark and re.search(self.logmark, self.next_log_entry)): self.next_log_entry = self.LOG.readline() if not self.next_log_entry: break @@ -316,8 +316,9 @@ class ReadLog: self.debug_logger.debug('UNHANDLED: %s' % e) def read_log(self, logmark): + self.logmark = logmark seenmark = True - if logmark: + if self.logmark: seenmark = False #last = None #event_type = None @@ -331,7 +332,7 @@ class ReadLog: while line: line = line.strip() self.debug_logger.debug('read_log: %s' % line) - if logmark in line: + if self.logmark in line: seenmark = True self.debug_logger.debug('read_log: seenmark = %s' %seenmark) @@ -344,7 +345,7 @@ class ReadLog: self.add_event_to_tree(event) line = self.get_next_log_entry() self.LOG.close() - logmark = '' + self.logmark = '' return self.log def op_type(self, operation): diff --git a/apparmor/ui.py b/apparmor/ui.py index 4bdfd2cff..dd8d82555 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -261,7 +261,7 @@ def UI_LongMessage(title, message): ypath, yarg = GetDataFromYast() def confirm_and_finish(): - sys.stdout.stdout('FINISHING\n') + sys.stdout.write('FINISHING..\n') sys.exit(0) def Text_PromptUser(question): From ed28caeba6df877159c8618d0dfd41d8cf73959f Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 18 Aug 2013 14:13:46 +0530 Subject: [PATCH 049/183] first partially working iteration of new profile writer from old profile --- apparmor/aa.py | 486 +++++++++++++++++++++++++++++++++++---- apparmor/ui.py | 1 + apparmor/writeprofile.py | 42 ++++ 3 files changed, 479 insertions(+), 50 deletions(-) create mode 100644 apparmor/writeprofile.py diff --git a/apparmor/aa.py b/apparmor/aa.py index 72814448b..584a2fc3a 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -88,11 +88,6 @@ def on_exit(): # Register the on_exit method with atexit atexit.register(on_exit) -def op_type(operation): - """Returns the operation type if known, unkown otherwise""" - operation_type = OPERATION_TYPES.get(operation, 'unknown') - return operation_type - def check_for_LD_XXX(file): """Returns True if specified program contains references to LD_PRELOAD or LD_LIBRARY_PATH to give the Px/Ux code better suggestions""" @@ -2073,7 +2068,7 @@ def do_logprof_pass(logmark='', pid=pid): UI_Info(_('Reading log entries from %s.') %filename) UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) - read_profiles('nosub') + read_profiles() if not sev_db: sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) @@ -2163,7 +2158,7 @@ def save_profiles(): q['title'] = 'Changed Local Profiles' q['headers'] = [] q['explanation'] = _('The following local profiles were changed. Would you like to save them?') - q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] + q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = changed q['selected'] = 0 @@ -2173,6 +2168,17 @@ def save_profiles(): while ans != 'CMD_SAVE_CHANGES': ans, arg = UI_PromptUser(q) if ans == 'CMD_VIEW_CHANGES': + which = list(changed.keys())[arg] + oldprofile = None + if aa[which][which].get('filename', False): + oldprofile = aa[which][which]['filename'] + else: + oldprofile = get_profile_filename(which) + newprofile = serialize_profile_from_old_profile(aa[which], which, '') + + display_changes_with_comments(oldprofile, newprofile) + + elif ans == 'CMD_VIEW_CHANGES_CLEAN': which = list(changed.keys())[arg] oldprofile = serialize_profile(original_aa[which], which, '') newprofile = serialize_profile(aa[which], which, '') @@ -2187,16 +2193,16 @@ def get_pager(): pass def generate_diff(oldprofile, newprofile): - oldtemp = tempfile.NamedTemporaryFile('wr') + oldtemp = tempfile.NamedTemporaryFile('w') oldtemp.write(oldprofile) oldtemp.flush() - newtemp = tempfile.NamedTemporaryFile('wr') + newtemp = tempfile.NamedTemporaryFile('w') newtemp.write(newprofile) newtemp.flush() - difftemp = tempfile.NamedTemporaryFile('wr', delete=False) + difftemp = tempfile.NamedTemporaryFile('w', delete=False) subprocess.call('diff -u -p %s %s > %s' %(oldtemp.name, newtemp.name, difftemp.name), shell=True) @@ -2225,6 +2231,26 @@ def display_changes(oldprofile, newprofile): difftemp.delete = True difftemp.close() +def display_changes_with_comments(oldprofile, newprofile): + """Compare the new profile with the existing profile inclusive of all the comments""" + if not os.path.exists(oldprofile): + raise AppArmorException("Can't find existing profile %s to compare changes." %oldprofile) + if UI_mode == 'yast': + #To-Do + pass + else: + newtemp = tempfile.NamedTemporaryFile('w') + newtemp.write(newprofile) + newtemp.flush() + + difftemp = tempfile.NamedTemporaryFile('w') + + subprocess.call('diff -u -p %s %s > %s' %(oldprofile, newtemp.name, difftemp.name), shell=True) + + newtemp.close() + subprocess.call('less %s' %difftemp.name, shell=True) + difftemp.close() + def set_process(pid, profile): # If process not running don't do anything if not os.path.exists('/proc/%s/attr/current' % pid): @@ -2342,7 +2368,7 @@ def check_profile_syntax(errors): # To-Do pass -def read_profiles(param=''): +def read_profiles(): try: os.listdir(profile_dir) except : @@ -2353,13 +2379,7 @@ def read_profiles(param=''): if is_skippable_file(file): continue else: - #print('read %s' %file) - if param == 'nosub': - #Already read all subdirectories in loadincludes - pass - else: - # Read profiles in sub directories - read_profile(profile_dir + '/' + file, True) + read_profile(profile_dir + '/' + file, True) def read_inactive_profiles(): if not os.path.exists(extra_profile_dir): @@ -2400,6 +2420,26 @@ def attach_profile_data(profiles, profile_data): for p in profile_data.keys(): profiles[p] = deepcopy(profile_data[p]) +## Profile parsing regex +RE_PROFILE_START = re.compile('^\s*(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$') +RE_PROFILE_END = re.compile('^\s*\}\s*(#.*)?$') +RE_PROFILE_CAP = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') +RE_PROFILE_LINK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') +RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??),(#.*)?$') +RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') +RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)\s*,(#.*)?$') +RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE) +RE_PROFILE_VARIABLE = re.compile('^\s*(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.*)?$') +RE_PROFILE_CONDITIONAL = re.compile('^\s*if\s+(not\s+)?(\$\{?\w*\}?)\s*\{\s*(#.*)?$') +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_PATH_ENTRY = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') +RE_PROFILE_NETWORK = re.compile('^\s*(audit\s+)?(allow\s+|deny\s+)?network(.*)\s*(#.*)?$') +RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)\s*,\s*(#.*)?$') +RE_PROFILE_HAT_DEF = re.compile('^\s*\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') +RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$') +RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$') + def parse_profile_data(data, file, do_include): profile_data = hasher() profile = None @@ -2408,22 +2448,7 @@ def parse_profile_data(data, file, do_include): repo_data = None parsed_profiles = [] initial_comment = '' - RE_PROFILE_START = re.compile('^(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.+)\)\s+)?\{\s*(#.*)?$') - RE_PROFILE_END = re.compile('^\}\s*(#.*)?$') - RE_PROFILE_CAP = re.compile('^(audit\s+)?(allow\s+|deny\s+)?capability\s+(\S+)\s*,\s*(#.*)?$') - RE_PROFILE_LINK = re.compile('^(audit\s+)?(allow\s+|deny\s+)?link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)\s*,\s*(#.*)?$') - RE_PROFILE_CHANGE_PROFILE = re.compile('^change_profile\s+->\s*("??.+?"??),(#.*)?$') - RE_PROFILE_ALIAS = re.compile('^alias\s+("??.+?"??)\s+->\s*("??.+?"??)\s*,(#.*)?$') - RE_PROFILE_RLIMIT = re.compile('^set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)\s*,(#.*)?$') - RE_PROFILE_BOOLEAN = re.compile('^(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?\s*(#.*)?$', flags=re.IGNORECASE) - RE_PROFILE_VARIABLE = re.compile('^(@\{?\w+\}?)\s*(\+?=)\s*(@*.+?)\s*,?\s*(#.*)?$') - RE_PROFILE_CONDITIONAL = re.compile('^if\s+(not\s+)?(\$\{?\w*\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^if\s+(not\s+)?defined\s+(@\{?\w+\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$') - RE_PROFILE_PATH_ENTRY = re.compile('^(audit\s+)?(allow\s+|deny\s+)?(owner\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?\s*,\s*(#.*)?$') - RE_PROFILE_NETWORK = re.compile('^(audit\s+)?(allow\s+|deny\s+)?network(.*)\s*(#.*)?$') - RE_PROFILE_CHANGE_HAT = re.compile('^\^(\"??.+?\"??)\s*,\s*(#.*)?$') - RE_PROFILE_HAT_DEF = re.compile('^\^(\"??.+?\"??)\s+((flags=)?\((.+)\)\s+)*\{\s*(#.*)?$') + if do_include: profile = file hat = file @@ -2463,6 +2488,7 @@ def parse_profile_data(data, file, do_include): # Profile stored existing_profiles[profile] = file + flags = matches[6] profile = strip_quotes(profile) @@ -2672,26 +2698,28 @@ def parse_profile_data(data, file, do_include): elif re_match_include(line): # Include files - include = re_match_include(line) + include_name = re_match_include(line) if profile: - profile_data[profile][hat]['include'][include] = True + profile_data[profile][hat]['include'][include_name] = True else: if not filelist.get(file): filelist[file] = hasher() - filelist[file]['include'][include] = True + filelist[file]['include'][include_name] = True # If include is a directory - if os.path.isdir(profile_dir + '/' + include): - for path in os.listdir(profile_dir + '/' + include): + if os.path.isdir(profile_dir + '/' + include_name): + for path in os.listdir(profile_dir + '/' + include_name): path = path.strip() if is_skippable_file(path): continue - if os.path.isfile(profile_dir + '/' + include + '/' + path): - file_name = include + '/' + path + if os.path.isfile(profile_dir + '/' + include_name + '/' + path): + file_name = include_name + '/' + path file_name = file_name.replace(profile_dir+'/', '') - load_include(file_name) + if not include.get(file_name, False): + load_include(file_name) else: - load_include(include) + if not include.get(include_name, False): + load_include(include_name) elif RE_PROFILE_NETWORK.search(line): matches = RE_PROFILE_NETWORK.search(line).groups() @@ -2706,8 +2734,7 @@ def parse_profile_data(data, file, do_include): if matches[1] and matches[1].strip() == 'deny': allow = 'deny' network = matches[2] - RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$') - RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$') + if RE_NETWORK_FAMILY_TYPE.search(network): nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() fam, typ = nmatch[:2] @@ -2879,7 +2906,7 @@ def set_allow_str(allow): if allow == 'deny': return 'deny ' else: - return 'allow' + return 'allow ' def set_ref_allow(prof_data, allow): if allow: @@ -2934,7 +2961,7 @@ def write_cap_rules(prof_data, depth, allow): if prof_data[allow]['capability'][cap].get('audit', False): audit = 'audit' if prof_data[allow]['capability'][cap].get('set', False): - data.append('%s%s%scapability %s,' %(pre, audit, allowstr)) + data.append('%s%s%scapability %s,' %(pre, audit, allowstr, cap)) data.append('') return data @@ -2949,7 +2976,7 @@ def write_net_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) - + audit = '' if prof_data[allow].get('netdomain', False): if prof_data[allow]['netdomain'].get('rule', False) == 'all': if prof_data[allow]['netdomain']['audit'].get('all', False): @@ -3126,7 +3153,7 @@ def serialize_profile(profile_data, name, options): include_flags = True data= [] - if options and type(options) == dict: + if options:# and type(options) == dict: if options.get('METADATA', False): include_metadata = True if options.get('NO_FLAGS', False): @@ -3159,6 +3186,365 @@ def serialize_profile(profile_data, name, options): return string+'\n' +def serialize_profile_from_old_profile(profile_data, name, options): + data = [] + string = '' + include_metadata = False + include_flags = True + prof_filename = get_profile_filename(name) + + write_filelist = deepcopy(filelist[prof_filename]) + write_prof_data = deepcopy(profile_data) + + if options:# and type(options) == dict: + if options.get('METADATA', False): + include_metadata = True + if options.get('NO_FLAGS', False): + include_flags = False + + if include_metadata: + string = '# Last Modified: %s\n' %time.time() + + if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] + and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): + repo = profile_data[name]['repo'] + string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id']) + elif profile_data[name]['repo']['neversubmit']: + string += '# REPOSITORY: NEVERSUBMIT\n' + + + if not os.path.isfile(prof_filename): + raise AppArmorException("Can't find existing profile to modify") + with open_file_read(prof_filename) as f_in: + profile = None + hat = None + prof_correct = True + data.append('reading prof') + for line in f_in: + correct = True + line = line.rstrip('\n') + data.append(' ')#data.append('read: '+line) + if RE_PROFILE_START.search(line): + matches = RE_PROFILE_START.search(line).groups() + if profile and profile == hat and matches[3]: + hat = matches[3] + in_contained_hat = True + if write_prof_data[profile][hat]['profile']: + pass + else: + if matches[1]: + profile = matches[1] + else: + profile = matches[3] + if len(profile.split('//')) >= 2: + profile, hat = profile.split('//')[:2] + else: + hat = None + in_contained_hat = False + if hat and not write_prof_data[profile][hat]['external']: + correct = False + else: + hat = profile + data.append(str(correct)) + + flags = matches[6] + profile = strip_quotes(profile) + if hat: + hat = strip_quotes(hat) + + if not write_prof_data[hat]['name'] == profile: + correct = False + + if not write_filelist['profiles'][profile][hat] == True: + correct = False + + if not write_prof_data[hat]['flags'] == flags: + correct = False + + #Write the profile start + if correct: + data.append(line) + else: + # manipulate profile start + pass + + elif RE_PROFILE_END.search(line): + # DUMP REMAINDER OF PROFILE + if profile: + data.append(line) + + if in_contained_hat: + #Hat processed, remove it + hat = profile + in_contained_hat = False + else: + profile = None + + + elif RE_PROFILE_CAP.search(line): + matches = RE_PROFILE_CAP.search(line).groups() + audit = False + if matches[0]: + audit = matches[0] + + allow = 'allow' + if matches[1] and matches[1].strip() == 'deny': + allow = 'deny' + + capability = matches[2] + + if not write_prof_data[hat][allow]['capability'][capability].get('set', False): + correct = False + if not write_prof_data[hat][allow]['capability'][capability].get(audit, False) == audit: + correct = False + + if correct: + + write_prof_data[hat][allow]['capability'].pop(capability) + data.append(line) + #write_prof_data[hat][allow]['capability'][capability].pop(audit) + + #Remove this line + else: + # To-Do + pass + elif RE_PROFILE_LINK.search(line): + matches = RE_PROFILE_LINK.search(line).groups() + audit = False + if matches[0]: + audit = True + allow = 'allow' + if matches[1] and matches[1].strip() == 'deny': + allow = 'deny' + + subset = matches[3] + link = strip_quotes(matches[6]) + value = strip_quotes(matches[7]) + if not write_prof_data[hat][allow]['link'][link]['to'] == value: + correct = False + if not write_prof_data[hat][allow]['link'][link]['mode'] & AA_MAY_LINK: + correct = False + if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & AA_LINK_SUBSET: + correct = False + if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & AA_LINK_SUBSET: + correct = False + + if correct: + write_prof_data[hat][allow]['link'].pop(link) + data.append(line) + else: + # To-Do + pass + + elif RE_PROFILE_CHANGE_PROFILE.search(line): + matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() + cp = strip_quotes(matches[0]) + + if not write_prof_data[hat]['changes_profile'][cp] == True: + correct = False + + if correct: + write_prof_data[hat]['changes_profile'].pop(cp) + data.append(line) + else: + #To-Do + pass + + elif RE_PROFILE_ALIAS.search(line): + matches = RE_PROFILE_ALIAS.search(line).groups() + + from_name = strip_quotes(matches[0]) + to_name = strip_quotes(matches[1]) + + if profile: + if not write_prof_data[hat]['alias'][from_name] == to_name: + correct = False + else: + if not write_filelist['alias'][from_name] == to_name: + correct = False + + if correct: + data.append(line) + else: + #To-Do + pass + + elif RE_PROFILE_RLIMIT.search(line): + matches = RE_PROFILE_RLIMIT.search(line).groups() + + from_name = matches[0] + to_name = matches[2] + + if not write_prof_data[hat]['rlimit'][from_name] == to_name: + correct = False + + if correct: + data.append(line) + else: + #To-Do + pass + + elif RE_PROFILE_BOOLEAN.search(line): + matches = RE_PROFILE_BOOLEAN.search(line).groups() + bool_var = matches[0] + value = matches[1] + + if not write_prof_data[hat]['lvar'][bool_var] == value: + correct = False + + if correct: + data.append(line) + else: + #To-Do + pass + elif RE_PROFILE_VARIABLE.search(line): + matches = RE_PROFILE_VARIABLE.search(line).groups() + list_var = strip_quotes(matches[0]) + var_operation = matches[1] + value = strip_quotes(matches[2]) + var_set = hasher() + if profile: + store_list_var(var_set, list_var, value, var_operation) + if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False): + correct = False + else: + store_list_var(var_set, list_var, value, var_operation) + if not var_set[list_var] == write_filelist['lvar'].get(list_var, False): + correct = False + + if correct: + data.append(line) + else: + #To-Do + pass + + elif RE_PROFILE_PATH_ENTRY.search(line): + matches = RE_PROFILE_PATH_ENTRY.search(line).groups() + audit = False + if matches[0]: + audit = True + allow = 'allow' + if matches[1] and matches[1].split() == 'deny': + allow = 'deny' + + user = False + if matches[2]: + user = True + + path = matches[3].strip() + mode = matches[4] + nt_name = matches[6] + if nt_name: + nt_name = nt_name.strip() + + tmpmode = set() + if user: + tmpmode = str_to_mode('%s::' %mode) + else: + tmpmode = str_to_mode(mode) + + if not write_prof_data[hat][allow]['path'][path].get('mode', False) & tmpmode: + correct = False + + if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name: + correct = False + + if audit and not write_prof_data[hat][allow]['path'][path].get('audit', False) & tmpmode: + correct = False + + if correct: + write_prof_data[hat][allow]['path'].pop(path) + data.append(line) + else: + #To-Do + pass + + elif re_match_include(line): + include_name = re_match_include(line) + if profile: + if write_prof_data[hat]['include'].get(include_name, False): + write_prof_data[hat]['include'].pop(include_name) + data.append(line) + else: + if write_filelist['include'].get(include_name, False): + write_filelist['include'].pop(include_name) + data.append(line) + + elif RE_PROFILE_NETWORK.search(line): + matches = RE_PROFILE_NETWORK.search(line).groups() + audit = False + if matches[0]: + audit = True + allow = 'allow' + if matches[1] and matches[1].strip() == 'deny': + allow = 'deny' + network = matches[2] + if RE_NETWORK_FAMILY_TYPE.search(network): + nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() + fam, typ = nmatch[:2] + if write_prof_data[hat][allow]['netdomain']['rule'][fam][typ] and write_prof_data[hat][allow]['netdomain']['audit'][fam][typ] == audit: + write_prof_data[hat][allow]['netdomain']['rule'][fam].pop(typ) + write_prof_data[hat][allow]['netdomain']['audit'][fam].pop(typ) + data.append(line) + + elif RE_NETWORK_FAMILY.search(network): + fam = RE_NETWORK_FAMILY.search(network).groups()[0] + if write_prof_data[hat][allow]['netdomain']['rule'][fam] and write_prof_data[hat][allow]['netdomain']['audit'][fam] == audit: + write_prof_data[hat][allow]['netdomain']['rule'].pop(fam) + write_prof_data[hat][allow]['netdomain']['audit'].pop(fam) + data.append(line) + else: + if write_prof_data[hat][allow]['netdomain']['rule']['all'] and write_prof_data[hat][allow]['netdomain']['audit']['all'] == audit: + write_prof_data[hat][allow]['netdomain']['rule'].pop('all') + write_prof_data[hat][allow]['netdomain']['audit'].pop('all') + data.append(line) + + elif RE_PROFILE_CHANGE_HAT.search(line): + matches = RE_PROFILE_CHANGE_HAT.search(line).groups() + hat = matches[0] + hat = strip_quotes(hat) + if not write_prof_data[hat]['declared']: + correct = False + if correct: + data.append(line) + else: + #To-Do + pass + elif RE_PROFILE_HAT_DEF.search(line): + matches = RE_PROFILE_HAT_DEF.search(line).groups() + in_contained_hat = True + hat = matches[0] + hat = strip_quotes(hat) + flags = matches[3] + if not write_prof_data[hat]['flags'] == flags: + correct = False + if not write_prof_data[hat]['declared'] == False: + correct = False + if not write_filelist['profile'][profile][hat]: + correct = False + if correct: + data.append(line) + else: + #To-Do + pass + else: + if correct: + data.append(line) + else: + #To-Do + pass + data.append('prof done') + if write_filelist: + data += write_alias(write_filelist, 0) + data += write_list_vars(write_filelist, 0) + data += write_includes(write_filelist, 0) + data.append('from filelist over') + data += write_piece(write_prof_data, 0, name, name, include_flags) + + string += '\n'.join(data) + + return string+'\n' + def write_profile_ui_feedback(profile): UI_Info(_('Writing updated profile for %s.') %profile) write_profile(profile) @@ -3170,7 +3556,7 @@ def write_profile(profile): else: prof_filename = get_profile_filename(profile) - newprof = tempfile.NamedTemporaryFile('rw', suffix='~' ,delete=False) + newprof = tempfile.NamedTemporaryFile('w', suffix='~' ,delete=False) if os.path.exists(prof_filename): shutil.copymode(prof_filename, newprof.name) else: diff --git a/apparmor/ui.py b/apparmor/ui.py index dd8d82555..3c7d314d7 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -200,6 +200,7 @@ CMDS = { 'CMD_SAVE_CHANGES': '(S)ave Changes', 'CMD_UPLOAD_CHANGES': '(U)pload Changes', 'CMD_VIEW_CHANGES': '(V)iew Changes', + 'CMD_VIEW_CHANGES_CLEAN': 'View Changes b/w (C)lean profiles', 'CMD_VIEW': '(V)iew', 'CMD_ENABLE_REPO': '(E)nable Repository', 'CMD_DISABLE_REPO': '(D)isable Repository', diff --git a/apparmor/writeprofile.py b/apparmor/writeprofile.py new file mode 100644 index 000000000..56a25c81b --- /dev/null +++ b/apparmor/writeprofile.py @@ -0,0 +1,42 @@ + + + + +def serialize_profile(profile_data, name, options): + string = '' + include_metadata = False + include_flags = True + data= [] + + if options:# and type(options) == dict: + if options.get('METADATA', False): + include_metadata = True + if options.get('NO_FLAGS', False): + include_flags = False + + if include_metadata: + string = '# Last Modified: %s\n' %time.time() + + if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] + and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): + repo = profile_data[name]['repo'] + string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id']) + elif profile_data[name]['repo']['neversubmit']: + string += '# REPOSITORY: NEVERSUBMIT\n' + + if profile_data[name].get('initial_comment', False): + comment = profile_data[name]['initial_comment'] + comment.replace('\\n', '\n') + string += comment + '\n' + + prof_filename = get_profile_filename(name) + if filelist.get(prof_filename, False): + data += write_alias(filelist[prof_filename], 0) + data += write_list_vars(filelist[prof_filename], 0) + data += write_includes(filelist[prof_filename], 0) + + data += write_piece(profile_data, 0, name, name, include_flags) + + string += '\n'.join(data) + + return string+'\n' From 1fb521418d733cb30729226284683454a7dad9b3 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 19 Aug 2013 12:37:47 +0530 Subject: [PATCH 050/183] Semmingly working writer from old profile --- apparmor/aa.py | 209 +++++++++++++++++++++++++++++++++++---- apparmor/writeprofile.py | 72 ++++++++++++++ 2 files changed, 261 insertions(+), 20 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 584a2fc3a..dcfbef794 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1957,17 +1957,17 @@ def delete_cap_duplicates(profilecaps, inccaps): return deleted def delete_path_duplicates(profile, incname, allow): - deleted = 0 - + deleted = [] for entry in profile[allow]['path'].keys(): if entry == '#include <%s>'%incname: continue cm, am, m = match_include_to_path(incname, allow, entry) if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']): - profile[allow]['path'].pop(entry) - deleted += 1 + deleted.append(entry) - return deleted + for entry in deleted: + profile[allow]['path'].pop(entry) + return len(deleted) def delete_duplicates(profile, incname): deleted = 0 @@ -3112,7 +3112,6 @@ def write_piece(profile_data, depth, name, nhat, write_flags): wname = name + '//' + nhat name = nhat inhat = True - data += write_header(profile_data[name], depth, wname, False, write_flags) data += write_rules(profile_data[name], depth+1) @@ -3218,12 +3217,34 @@ def serialize_profile_from_old_profile(profile_data, name, options): with open_file_read(prof_filename) as f_in: profile = None hat = None + write_methods = {'alias': write_alias, + 'lvar': write_list_vars, + 'include': write_includes, + 'rlimit': write_rlimits, + 'capability': write_capabilities, + 'netdomain': write_netdomain, + 'link': write_links, + 'path': write_paths, + 'change_profile': write_change_profile, + } prof_correct = True - data.append('reading prof') + segments = { + 'alias': False, + 'lvar': False, + 'include': False, + 'rlimit': False, + 'capability': False, + 'netdomain': False, + 'link': False, + 'path': False, + 'change_profile': False, + 'include_local_started': False, + } + #data.append('reading prof') for line in f_in: correct = True line = line.rstrip('\n') - data.append(' ')#data.append('read: '+line) + #data.append(' ')#data.append('read: '+line) if RE_PROFILE_START.search(line): matches = RE_PROFILE_START.search(line).groups() if profile and profile == hat and matches[3]: @@ -3245,7 +3266,6 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False else: hat = profile - data.append(str(correct)) flags = matches[6] profile = strip_quotes(profile) @@ -3263,16 +3283,67 @@ def serialize_profile_from_old_profile(profile_data, name, options): #Write the profile start if correct: + if write_filelist: + data += write_alias(write_filelist, 0) + data += write_list_vars(write_filelist, 0) + data += write_includes(write_filelist, 0) data.append(line) else: - # manipulate profile start - pass + if write_prof_data[hat]['name'] == profile: + depth = len(line) - len(line.lstrip()) + data += write_header(write_prof_data[name], int(depth/2), name, False, include_flags) elif RE_PROFILE_END.search(line): # DUMP REMAINDER OF PROFILE if profile: + depth = len(line) - len(line.lstrip()) + if True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + + + data += write_alias(write_prof_data[name], depth) + data += write_list_vars(write_prof_data[name], depth) + data += write_includes(write_prof_data[name], depth) + data += write_rlimits(write_prof_data, depth) + data += write_capabilities(write_prof_data[name], depth) + data += write_netdomain(write_prof_data[name], depth) + data += write_links(write_prof_data[name], depth) + data += write_paths(write_prof_data[name], depth) + data += write_change_profile(write_prof_data[name], depth) + + write_prof_data.pop(name) + + #Append local includes data.append(line) + if not in_contained_hat: + # Embedded hats + depth = int((len(line) - len(line.lstrip()))/2) + pre2 = ' ' * (depth+1) + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): + if not profile_data[hat]['external'] and not profile_data[hat]['declared']: + data.append('') + if profile_data[hat]['profile']: + data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, include_flags))) + else: + data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, include_flags))) + + data += list(map(str, write_rules(profile_data[hat], depth+2))) + + data.append('%s}' %pre2) + + # External hats + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): + if profile_data[hat].get('external', False): + data.append('') + data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, name, include_flags))) + data.append(' }') + if in_contained_hat: #Hat processed, remove it hat = profile @@ -3299,9 +3370,17 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: - + if not segments['capability'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['capability'] = True write_prof_data[hat][allow]['capability'].pop(capability) data.append(line) + #write_prof_data[hat][allow]['capability'][capability].pop(audit) #Remove this line @@ -3330,6 +3409,14 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: + if not segments['link'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['link'] = True write_prof_data[hat][allow]['link'].pop(link) data.append(line) else: @@ -3344,7 +3431,15 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: - write_prof_data[hat]['changes_profile'].pop(cp) + if not segments['change_profile'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['change_profile'] = True + write_prof_data[hat]['change_profile'].pop(cp) data.append(line) else: #To-Do @@ -3364,6 +3459,18 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: + if not segments['alias'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['alias'] = True + if profile: + write_prof_data[hat]['alias'].pop(from_name) + else: + write_filelist['alias'].pop(from_name) data.append(line) else: #To-Do @@ -3379,6 +3486,15 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: + if not segments['rlimit'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['rlimit'] = True + write_prof_data[hat]['rlimit'].pop(from_name) data.append(line) else: #To-Do @@ -3393,6 +3509,15 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: + if not segments['lvar'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['lvar'] = True + write_prof_data[hat]['lvar'].pop(bool_var) data.append(line) else: #To-Do @@ -3413,6 +3538,18 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: + if not segments['lvar'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['lvar'] = True + if profile: + write_prof_data[hat]['lvar'].pop(list_var) + else: + write_filelist['lvar'].pop(list_var) data.append(line) else: #To-Do @@ -3453,6 +3590,14 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if correct: + if not segments['path'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['path'] = True write_prof_data[hat][allow]['path'].pop(path) data.append(line) else: @@ -3463,6 +3608,14 @@ def serialize_profile_from_old_profile(profile_data, name, options): include_name = re_match_include(line) if profile: if write_prof_data[hat]['include'].get(include_name, False): + if not segments['include'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['include'] = True write_prof_data[hat]['include'].pop(include_name) data.append(line) else: @@ -3486,6 +3639,8 @@ def serialize_profile_from_old_profile(profile_data, name, options): write_prof_data[hat][allow]['netdomain']['rule'][fam].pop(typ) write_prof_data[hat][allow]['netdomain']['audit'][fam].pop(typ) data.append(line) + else: + correct = False elif RE_NETWORK_FAMILY.search(network): fam = RE_NETWORK_FAMILY.search(network).groups()[0] @@ -3493,11 +3648,25 @@ def serialize_profile_from_old_profile(profile_data, name, options): write_prof_data[hat][allow]['netdomain']['rule'].pop(fam) write_prof_data[hat][allow]['netdomain']['audit'].pop(fam) data.append(line) + else: + correct = False else: if write_prof_data[hat][allow]['netdomain']['rule']['all'] and write_prof_data[hat][allow]['netdomain']['audit']['all'] == audit: write_prof_data[hat][allow]['netdomain']['rule'].pop('all') write_prof_data[hat][allow]['netdomain']['audit'].pop('all') data.append(line) + else: + correct = False + + if correct: + if not segments['netdomain'] and True in segments.values(): + for segs in list(filter(lambda x: segments[x], segments.keys())): + depth = len(line) - len(line.lstrip()) + data += write_methods[segs](write_prof_data[name], int(depth/2)) + segments[segs] = False + if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + segments['netdomain'] = True elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() @@ -3533,13 +3702,13 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: #To-Do pass - data.append('prof done') - if write_filelist: - data += write_alias(write_filelist, 0) - data += write_list_vars(write_filelist, 0) - data += write_includes(write_filelist, 0) - data.append('from filelist over') - data += write_piece(write_prof_data, 0, name, name, include_flags) +# data.append('prof done') +# if write_filelist: +# data += write_alias(write_filelist, 0) +# data += write_list_vars(write_filelist, 0) +# data += write_includes(write_filelist, 0) +# data.append('from filelist over') +# data += write_piece(write_prof_data, 0, name, name, include_flags) string += '\n'.join(data) diff --git a/apparmor/writeprofile.py b/apparmor/writeprofile.py index 56a25c81b..0e1a2dd94 100644 --- a/apparmor/writeprofile.py +++ b/apparmor/writeprofile.py @@ -1,5 +1,77 @@ +def write_header(prof_data, depth, name, embedded_hat, write_flags): + pre = ' ' * depth + data = [] + name = quote_if_needed(name) + + if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]' ,name)): + name = 'profile %s' % name + + if write_flags and prof_data['flags']: + data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags'])) + else: + data.append('%s%s {' % (pre, name)) + + return data +def write_rules(prof_data, depth): + data = write_alias(prof_data, depth) + data += write_list_vars(prof_data, depth) + data += write_includes(prof_data, depth) + data += write_rlimits(prof_data, depth) + data += write_capabilities(prof_data, depth) + data += write_netdomain(prof_data, depth) + data += write_links(prof_data, depth) + data += write_paths(prof_data, depth) + data += write_change_profile(prof_data, depth) + + return data +def write_piece(profile_data, depth, name, nhat, write_flags): + pre = ' ' * depth + data = [] + wname = None + inhat = False + if name == nhat: + wname = name + else: + wname = name + '//' + nhat + name = nhat + inhat = True + data += ['begin header'] + data += write_header(profile_data[name], depth, wname, False, write_flags) + data +=['end header'] + data += write_rules(profile_data[name], depth+1) + + pre2 = ' ' * (depth+1) + # External hat declarations + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): + if profile_data[hat].get('declared', False): + data.append('%s^%s,' %(pre2, hat)) + + if not inhat: + # Embedded hats + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): + if not profile_data[hat]['external'] and not profile_data[hat]['declared']: + data.append('') + if profile_data[hat]['profile']: + data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags))) + else: + data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags))) + + data += list(map(str, write_rules(profile_data[hat], depth+2))) + + data.append('%s}' %pre2) + + data.append('%s}' %pre) + + # External hats + for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): + if name == nhat and profile_data[hat].get('external', False): + data.append('') + data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags))) + data.append(' }') + + return data def serialize_profile(profile_data, name, options): From 5490dddbdadf525237b59aefe7cac7f9bf959746 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Wed, 21 Aug 2013 11:26:09 +0530 Subject: [PATCH 051/183] First set of tools in their alpha release, logprof and genprof are pre-bleeding edge so dont hurt yourself or worse your distro. --- Tools/aa-audit.py | 54 ++++++++++++++ Tools/aa-autodep.py | 53 ++++++++++++++ Tools/aa-complain.py | 55 +++++++++++++++ Tools/aa-disable.py | 67 ++++++++++++++++++ Tools/aa-enforce.py | 67 ++++++++++++++++++ Tools/aa-genprof.py | 148 +++++++++++++++++++++++++++++++++++++++ Tools/aa-logprof.py | 27 +++++-- Tools/aa-unconfined.py | 69 ++++++++++++++++++ apparmor/aa.py | 75 ++++++++++++++------ apparmor/config.py | 4 +- apparmor/logparser.py | 12 ++-- apparmor/severity.py | 91 ++++++++++++------------ apparmor/ui.py | 103 ++++++++++++++++----------- apparmor/writeprofile.py | 114 ------------------------------ 14 files changed, 703 insertions(+), 236 deletions(-) create mode 100644 Tools/aa-audit.py create mode 100644 Tools/aa-autodep.py create mode 100644 Tools/aa-complain.py create mode 100644 Tools/aa-disable.py create mode 100644 Tools/aa-enforce.py create mode 100644 Tools/aa-genprof.py create mode 100644 Tools/aa-unconfined.py diff --git a/Tools/aa-audit.py b/Tools/aa-audit.py new file mode 100644 index 000000000..c5e9b2380 --- /dev/null +++ b/Tools/aa-audit.py @@ -0,0 +1,54 @@ +#!/usr/bin/python +import sys +import os +import argparse + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='Switch the given programs to audit mode') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('program', type=str, nargs='+', help='name of program to hswitch to audit mode') +args = parser.parse_args() + +profiling = args.program +profiledir = args.d + +if profiledir: + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + +for p in profiling: + if not p: + continue + + program = None + if os.path.exists(p): + program = apparmor.get_full_path(p).strip() + else: + which = apparmor.which(p) + if which: + program = apparmor.get_full_path(which) + + if os.path.exists(program): + apparmor.read_profiles() + filename = apparmor.get_profile_filename(program) + + if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): + continue + + sys.stdout.write(_('Setting %s to audit mode.\n')%program) + + apparmor.set_profile_flags(filename, 'audit') + + cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + if cmd_info[0] != 0: + raise apparmor.AppArmorException(cmd_info[1]) + else: + if '/' not in p: + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + sys.exit(1) + +sys.exit(0) \ No newline at end of file diff --git a/Tools/aa-autodep.py b/Tools/aa-autodep.py new file mode 100644 index 000000000..9adbc429b --- /dev/null +++ b/Tools/aa-autodep.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +import sys +import os +import argparse + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='Disable the profile for the given programs') +parser.add_argument('--force', type=str, help='path to profiles') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('program', type=str, nargs='+', help='name of program to have profile disabled') +args = parser.parse_args() + +force = args.force +profiledir = args.d +profiling = args.program + +aa_mountpoint = apparmor.check_for_apparmor() + +if profiledir: + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + +for p in profiling: + if not p: + continue + + program = None + if os.path.exists(p): + program = apparmor.get_full_path(p).strip() + else: + which = apparmor.which(p) + if which: + program = apparmor.get_full_path(which) + + apparmor.check_qualifiers(program) + + if os.path.exists(program): + if os.path.exists(apparmor.get_profile_filename(program) and not force): + apparmor.UI_Info('Profile for %s already exists - skipping.'%program) + else: + apparmor.autodep(program) + if aa_mountpoint: + apparmor.reload(program) + else: + if '/' not in p: + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + sys.exit(1) + +sys.exit(0) diff --git a/Tools/aa-complain.py b/Tools/aa-complain.py new file mode 100644 index 000000000..b7ee90839 --- /dev/null +++ b/Tools/aa-complain.py @@ -0,0 +1,55 @@ +#!/usr/bin/python +import sys +import os +import argparse + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='Switch the given program to complain mode') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('program', type=str, nargs='+', help='name of program to switch to complain mode') +args = parser.parse_args() + +profiling = args.program +profiledir = args.d + +if profiledir: + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + +for p in profiling: + if not p: + continue + + program = None + if os.path.exists(p): + program = apparmor.get_full_path(p).strip() + else: + which = apparmor.which(p) + if which: + program = apparmor.get_full_path(which) + + if os.path.exists(program): + apparmor.read_profiles() + filename = apparmor.get_profile_filename(program) + + if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): + continue + + sys.stdout.write(_('Setting %s to complain mode.\n')%program) + + apparmor.set_profile_flags(filename, 'complain') + + cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + if cmd_info[0] != 0: + raise apparmor.AppArmorException(cmd_info[1]) + else: + if '/' not in p: + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + sys.exit(1) + +sys.exit(0) + diff --git a/Tools/aa-disable.py b/Tools/aa-disable.py new file mode 100644 index 000000000..1eaac4ad0 --- /dev/null +++ b/Tools/aa-disable.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +import sys +import os +import argparse + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='Disable the profile for the given programs') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('program', type=str, nargs='+', help='name of program to have profile disabled') +args = parser.parse_args() + +profiledir = args.d +profiling = args.program + +if profiledir: + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + +disabledir = apparmor.profile_dir+'/disable' +if not os.path.isdir(disabledir): + raise apparmor.AppArmorException("Can't find AppArmor disable directorys %s." %disabledir) + +for p in profiling: + if not p: + continue + + program = None + if os.path.exists(p): + program = apparmor.get_full_path(p).strip() + else: + which = apparmor.which(p) + if which: + program = apparmor.get_full_path(which) + + if os.path.exists(program): + apparmor.read_profiles() + filename = apparmor.get_profile_filename(program) + + if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): + continue + + bname = os.path.basename(filename) + if not bname: + apparmor.AppArmorException(_('Unable to find basename for %s.')%filename) + + sys.stdout.write(_('Disabling %s.\n')%program) + + link = '%s/%s'%(disabledir, bname) + if not os.path.exists(link): + try: + os.symlink(filename, link) + except: + raise apparmor.AppArmorException('Could not create %s symlink to %s.'%(link, filename)) + + cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + if cmd_info[0] != 0: + raise apparmor.AppArmorException(cmd_info[1]) + else: + if '/' not in p: + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + sys.exit(1) + +sys.exit(0) diff --git a/Tools/aa-enforce.py b/Tools/aa-enforce.py new file mode 100644 index 000000000..be0611659 --- /dev/null +++ b/Tools/aa-enforce.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +import sys +import os +import argparse + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='Switch the given program to enforce mode') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('program', type=str, nargs='+', help='name of program to switch to enforce mode') +args = parser.parse_args() + +profiledir = args.d +profiling = args.program + +if profiledir: + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + +for p in profiling: + if not p: + continue + + program = None + if os.path.exists(p): + program = apparmor.get_full_path(p).strip() + else: + which = apparmor.which(p) + if which: + program = apparmor.get_full_path(which) + + if os.path.exists(program): + apparmor.read_profiles() + filename = apparmor.get_profile_filename(program) + + if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): + continue + + sys.stdout.write(_('Setting %s to enforce mode.\n')%program) + + apparmor.set_profile_flags(filename, '') + + # Remove symlink from profile_dir/force-complain + complainlink = filename + complainlink = re.sub('^%s'%apparmor.profile_dir, '%s/force-complain'%apparmor.profile_dir, complainlink) + if os.path.exists(complainlink): + os.remove(complainlink) + + # remove symlink in profile_dir/disable + disablelink = filename + disablelink = re.sub('^%s'%apparmor.profile_dir, '%s/disable'%apparmor.profile_dir, disablelink) + if os.path.exists(disablelink): + os.remove(disablelink) + + cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + if cmd_info[0] != 0: + raise apparmor.AppArmorException(cmd_info[1]) + else: + if '/' not in p: + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + sys.exit(1) + +sys.exit(0) + diff --git a/Tools/aa-genprof.py b/Tools/aa-genprof.py new file mode 100644 index 000000000..db9733d82 --- /dev/null +++ b/Tools/aa-genprof.py @@ -0,0 +1,148 @@ +#!/usr/bin/python +import sys +import subprocess +import os +import re +import atexit +import argparse + +import apparmor.aa as apparmor + +def sysctl_read(path): + value = None + with open(path, 'r') as f_in: + value = int(f_in.readline()) + return value + +def sysctl_write(path, value): + if not value: + return + with open(path, 'w') as f_out: + f_out.write(str(value)) + +def last_audit_entry_time(): + out = subprocess.check_output(['tail', '-1', '/var/log/audit/audit.log'], shell=True) + logmark = None + if re.search('^*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out): + logmark = re.search('^*msg\=audit\((\d+\.\d+\:\d+).*\).*$', out).groups()[0] + else: + logmark = '' + return logmark + +def restore_ratelimit(): + sysctl_write(ratelimit_sysctl, ratelimit_saved) + +parser = argparse.ArgumentParser(description='Generate profile for the given program') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-f', type=str, help='path to logfile') +parser.add_argument('program', type=str, help='name of program to profile') +args = parser.parse_args() + +profiling = args.program +profiledir = args.d +filename = args.f + +aa_mountpoint = apparmor.check_for_apparmor() +if not aa_mountpoint: + raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.')) + +if profiledir: + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + + +# if not profiling: +# profiling = apparmor.UI_GetString(_('Please enter the program to profile: '), '') +# if profiling: +# profiling = profiling.strip() +# else: +# sys.exit(0) + +program = None +#if os.path.exists(apparmor.which(profiling.strip())): +if os.path.exists(profiling): + program = apparmor.get_full_path(profiling) +else: + if '/' not in profiling: + which = apparmor.which(profiling) + if which: + program = apparmor.get_full_path(which) + +if not program or not os.path.exists(program): + if '/' not in profiling: + raise apparmor.AppArmorException(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' in another window in order to find the fully-qualified path.") %(profiling, profiling)) + else: + raise apparmor.AppArmorException(_('%s does not exists, please double-check the path.') %profiling) + +# Check if the program has been marked as not allowed to have a profile +apparmor.check_qualifiers(program) + +apparmor.loadincludes() + +profile_filename = apparmor.get_profile_filename(program) +if os.path.exists(profile_filename): + apparmor.helpers[program] = apparmor.get_profile_flags(profile_filename) +else: + apparmor.autodep(program) + apparmor.helpers[program] = 'enforce' + +if apparmor.helpers[program] == 'enforce': + apparmor.complain(program) + apparmor.reload(program) + +# When reading from syslog, it is possible to hit the default kernel +# printk ratelimit. This will result in audit entries getting skipped, +# making profile generation inaccurate. When using genprof, disable +# the printk ratelimit, and restore it on exit. +ratelimit_sysctl = '/proc/sys/kernel/printk_ratelimit' +ratelimit_saved = sysctl_read(ratelimit_sysctl) +sysctl_write(ratelimit_sysctl, 0) + +atexit.register(restore_ratelimit) + +apparmor.UI_Info(_('\nBefore you begin, you may wish to check if a\nprofile already exists for the application you\nwish to confine. See the following wiki page for\nmore information:\nhttp://wiki.apparmor.net/index.php/Profiles')) + +apparmor.UI_Important(_('Please start the application to be profiled in\nanother window and exercise its functionality now.\n\nOnce completed, select the "Scan" option below in \norder to scan the system logs for AppArmor events. \n\nFor each AppArmor event, you will be given the \nopportunity to choose whether the access should be \nallowed or denied.')) + +syslog = True +logmark = '' +done_profiling = False + +if os.path.exists('/var/log/audit/audit.log'): + syslog = False + +passno = 0 +while not done_profiling: + if syslog: + logmark = subprocess.check_output(['date | md5sum'], shell=True) + logmark = logmark.decode('ascii').strip() + logmark = re.search('^([0-9a-f]+)', logmark).groups()[0] + t=subprocess.call("%s -p kern.warn 'GenProf: %s'"%(apparmor.logger, logmark), shell=True) + + else: + logmark = last_audit_entry_time() + + q=apparmor.hasher() + q['headers'] = [_('Profiling'), program] + q['functions'] = ['CMD_SCAN', 'CMD_FINISHED'] + q['default'] = 'CMD_SCAN' + ans, arg = apparmor.UI_PromptUser(q, 'noexit') + + if ans == 'CMD_SCAN': + lp_ret = apparmor.do_logprof_pass(logmark, passno) + passno += 1 + if lp_ret == 'FINISHED': + done_profiling = True + else: + done_profiling = True + +for p in sorted(apparmor.helpers.keys()): + if apparmor.helpers[p] == 'enforce': + enforce(p) + reload(p) + +apparmor.UI_Info(_('\nReloaded AppArmor profiles in enforce mode.')) +apparmor.UI_Info(_('\nPlease consider contributing your new profile!\nSee the following wiki page for more information:\nhttp://wiki.apparmor.net/index.php/Profiles\n')) +apparmor.UI_Info(_('Finished generating profile for %s.')%program) +sys.exit(0) diff --git a/Tools/aa-logprof.py b/Tools/aa-logprof.py index ebc3402aa..d8b73cd46 100644 --- a/Tools/aa-logprof.py +++ b/Tools/aa-logprof.py @@ -1,15 +1,30 @@ #!/usr/bin/python import sys - -sys.path.append('../') -import apparmor.aa +import apparmor.aa as apparmor import os import argparse -logmark = '' +parser = argparse.ArgumentParser(description='Process log entries to generate profiles') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-f', type=str, help='path to logfile') +parser.add_argument('-m', type=str, help='mark in the log to start processing after') +args = parser.parse_args() -apparmor.aa.loadincludes() +profiledir = args.d +filename = args.f +logmark = args.m or '' -apparmor.aa.do_logprof_pass(logmark) +aa_mountpoint = apparmor.check_for_apparmor() +if not aa_mountpoint: + raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.')) +if profiledir: + apparmor.profiledir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profiledir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) +apparmor.loadincludes() + +apparmor.do_logprof_pass(logmark) + +sys.exit(0) diff --git a/Tools/aa-unconfined.py b/Tools/aa-unconfined.py new file mode 100644 index 000000000..be39f711a --- /dev/null +++ b/Tools/aa-unconfined.py @@ -0,0 +1,69 @@ +#!/usr/bin/python +import sys +import os +import re +import argparse + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='') +parser.add_argument('--paranoid', type=str) +args = parser.parse_args() + +paranoid = args.paranoid + +aa_mountpoint = apparmor.check_for_apparmor() +if not aa_mountpoint: + raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.')) + +pids = [] +if paranoid: + pids = list(filter(lambda x: re.search('^\d+$', x), apparmor.get_subdirectories('/proc'))) +else: + regex_tcp_udp = re.compile('^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)') + output = apparmor.cmd(['netstat','-nlp'])[1].split('\n') + for line in output: + match = regex_tcp_udp.search(line) + if match: + pids.append(match.groups()[4]) +# We can safely remove duplicate pid's? +pids = list(map(lambda x: int(x), set(pids))) + +for pid in sorted(pids): + try: + prog = os.readlink('/proc/%s/exe'%pid) + except: + continue + attr = None + if os.path.exists('/proc/%s/attr/current'%pid): + with apparmor.open_file_read('/proc/%s/attr/current'%pid) as current: + for line in current: + if line.startswith('/') or line.startswith('null'): + attr = line.strip() + + cmdline = apparmor.cmd(['cat', '/proc/%s/cmdline'%pid])[1] + pname = cmdline.split('\0')[0] + if '/' in pname and pname != prog: + pname = '(%s)'%pname + else: + pname = '' + if not attr: + if re.search('^(/usr)?/bin/(python|perl|bash)', prog): + cmdline = re.sub('\0', ' ', cmdline) + cmdline = re.sub('\s+$', '', cmdline).strip() + sys.stdout.write(_('%s %s (%s) not confined\n')%(pid, prog, cmdline)) + else: + if pname and pname[-1] == ')': + pname += ' ' + sys.stdout.write(_('%s %s %snot confined\n')%(pid, prog, pname)) + else: + if re.search('^(/usr)?/bin/(python|perl|bash)', prog): + cmdline = re.sub('\0', ' ', cmdline) + cmdline = re.sub('\s+$', '', cmdline).strip() + sys.stdout.write(_("%s %s (%s) confined by '%s'\n")%(pid, prog, cmdline, attr)) + else: + if pname and pname[-1] == ')': + pname += ' ' + sys.stdout.write(_("%s %s %sconfined by '%s'\n")%(pid, prog, pname, attr)) + +sys.exit(0) \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index dcfbef794..92fab724d 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -18,7 +18,7 @@ import apparmor.logparser import apparmor.severity import LibAppArmor -from apparmor.common import (AppArmorException, error, debug, msg, +from apparmor.common import (AppArmorException, error, debug, msg, cmd, open_file_read, valid_path, hasher, open_file_write, convert_regexp, DebugLogger) @@ -228,7 +228,7 @@ def complain(path): set_profile_flags(prof_filename, 'complain') def enforce(path): - """Sets the profile to complain mode if it exists""" + """Sets the profile to enforce mode if it exists""" prof_filename, name = name_to_prof_filename(path) if not prof_filename : fatal_error("Can't find %s" % path) @@ -241,7 +241,9 @@ def head(file): if os.path.isfile(file): with open_file_read(file) as f_in: first = f_in.readline().rstrip() - return first + return first + else: + raise AppArmorException('Unable to read first line from: %s : File Not Found' %file) def get_output(params): """Returns the return code output by running the program with the args given in the list""" @@ -484,7 +486,7 @@ def autodep(bin_name, pname=''): if not bin_name and pname.startswith('/'): bin_name = pname if not repo_cfg and not cfg['repository'].get('url', False): - repo_conf = apparmor.config.Config('shell') + repo_conf = apparmor.config.Config('shell', CONFDIR) repo_cfg = repo_conf.read_config('repository.conf') if not repo_cfg.get('repository', False) or repo_cfg['repository']['enabled'] == 'later': UI_ask_to_enable_repo() @@ -511,6 +513,16 @@ def autodep(bin_name, pname=''): filelist.file = hasher() filelist[file][include]['tunables/global'] = True write_profile_ui_feedback(pname) + +def get_profile_flags(filename): + flags = 'enforce' + with open_file_read(filename) as f_in: + for line in f_in: + if RE_PROFILE_START.search(line): + flags = RE_PROFILE_START.search(line).groups()[6] + return flags + return flags + def set_profile_flags(prof_filename, newflags): """Reads the old profile file and updates the flags accordingly""" @@ -821,6 +833,7 @@ def handle_children(profile, hat, root): else: typ = entry.pop(0) if typ == 'fork': + # If type is fork then we (should) have pid, profile and hat pid, p, h = entry[:3] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p @@ -830,6 +843,7 @@ def handle_children(profile, hat, root): else: profile_changes[pid] = profile elif typ == 'unknown_hat': + # If hat is not known then we (should) have pid, profile, hat, mode and unknown hat in entry pid, p, h, aamode, uhat = entry[:5] if not regex_nullcomplain.search(p): profile = p @@ -882,9 +896,11 @@ def handle_children(profile, hat, root): elif ans == 'CMD_USEDEFAULT': hat = default_hat elif ans == 'CMD_DENY': + # As unknown hat is denied no entry for it should be made return None elif typ == 'capability': + # If capability then we (should) have pid, profile, hat, program, mode, capability pid, p, h, prog, aamode, capability = entry[:6] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p @@ -894,6 +910,7 @@ def handle_children(profile, hat, root): prelog[aamode][profile][hat]['capability'][capability] = True elif typ == 'path' or typ == 'exec': + # If path or exec then we (should) have pid, profile, hat, program, mode, details and to_name pid, p, h, prog, aamode, mode, detail, to_name = entry[:8] if not mode: mode = set() @@ -1086,6 +1103,7 @@ def handle_children(profile, hat, root): default = None if 'p' in options and os.path.exists(get_profile_filename(exec_target)): default = 'CMD_px' + sys.stdout.write('Target profile exists: %s\n' %get_profile_filename(exec_target)) elif 'i' in options: default = 'CMD_ix' elif 'c' in options: @@ -1309,6 +1327,7 @@ def handle_children(profile, hat, root): return None elif typ == 'netdomain': + # If netdomain we (should) have pid, profile, hat, program, mode, network family, socket type and protocol pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8] if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): @@ -1717,15 +1736,17 @@ def ask_the_questions(): else: if aa[profile][hat]['allow']['path'][path].get('mode', False): mode |= aa[profile][hat]['allow']['path'][path]['mode'] - deleted = 0 + deleted = [] for entry in aa[profile][hat]['allow']['path'].keys(): if path == entry: continue if matchregexp(path, entry): if mode_contains(mode, aa[profile][hat]['allow']['path'][entry]['mode']): - aa[profile][hat]['allow']['path'].pop(entry) - deleted += 1 + deleted.append(entry) + for entry in deleted: + aa[profile][hat]['allow']['path'].pop(entry) + deleted = len(deleted) if owner_toggle == 0: mode = flatten_mode(mode) @@ -1948,13 +1969,15 @@ def delete_net_duplicates(netrules, incnetrules): return deleted def delete_cap_duplicates(profilecaps, inccaps): - deleted = 0 + deleted = [] if profilecaps and inccaps: for capname in profilecaps.keys(): if inccaps[capname].get('set', False) == 1: - profilecaps.pop(capname) - deleted += 1 - return deleted + deleted.append(capname) + for capname in deleted: + profilecaps.pop(capname) + + return len(deleted) def delete_path_duplicates(profile, incname, allow): deleted = [] @@ -2047,7 +2070,7 @@ def match_net_includes(profile, family, nettype): return newincludes -def do_logprof_pass(logmark='', pid=pid): +def do_logprof_pass(logmark='', passno=0, pid=pid): # set up variables for this pass t = hasher() # transitions = hasher() @@ -2066,9 +2089,10 @@ def do_logprof_pass(logmark='', pid=pid): # filelist = hasher() UI_Info(_('Reading log entries from %s.') %filename) - UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) - read_profiles() + if not passno: + UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) + read_profiles() if not sev_db: sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) @@ -2158,7 +2182,7 @@ def save_profiles(): q['title'] = 'Changed Local Profiles' q['headers'] = [] q['explanation'] = _('The following local profiles were changed. Would you like to save them?') - q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT'] + q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_SAVE_SELECTED', 'CMD_VIEW_CHANGES', 'CMD_VIEW_CHANGES_CLEAN', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = changed q['selected'] = 0 @@ -2167,7 +2191,14 @@ def save_profiles(): arg = None while ans != 'CMD_SAVE_CHANGES': ans, arg = UI_PromptUser(q) - if ans == 'CMD_VIEW_CHANGES': + if ans == 'CMD_SAVE_SELECTED': + profile_name = list(changed.keys())[arg] + write_profile_ui_feedback(profile_name) + reload_base(profile_name) + changed.pop(profile_name) + #q['options'] = changed + + elif ans == 'CMD_VIEW_CHANGES': which = list(changed.keys())[arg] oldprofile = None if aa[which][which].get('filename', False): @@ -2485,9 +2516,8 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['external'] = True else: hat = profile - # Profile stored - existing_profiles[profile] = file - + # Profile stored + existing_profiles[profile] = file flags = matches[6] @@ -2959,7 +2989,7 @@ def write_cap_rules(prof_data, depth, allow): for cap in sorted(prof_data[allow]['capability'].keys()): audit = '' if prof_data[allow]['capability'][cap].get('audit', False): - audit = 'audit' + audit = 'audit ' if prof_data[allow]['capability'][cap].get('set', False): data.append('%s%s%scapability %s,' %(pre, audit, allowstr, cap)) data.append('') @@ -3837,7 +3867,7 @@ def reload_base(bin_path): def reload(bin_path): bin_path = find_executable(bin_path) - if not bin: + if not bin_path: return None return reload_base(bin_path) @@ -3955,6 +3985,7 @@ def check_qualifiers(program): 'them is likely to break the rest of the system. If you know what you\'re\n' + 'doing and are certain you want to create a profile for this program, edit\n' + 'the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.') %program) + return False def get_subdirectories(current_dir): """Returns a list of all directories directly inside given directory""" @@ -4048,7 +4079,7 @@ def matchregexp(new, old): ######Initialisations###### -conf = apparmor.config.Config('ini') +conf = apparmor.config.Config('ini', CONFDIR) cfg = conf.read_config('logprof.conf') #print(cfg['settings']) diff --git a/apparmor/config.py b/apparmor/config.py index e6ea8a5f6..ae8f70291 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -28,8 +28,8 @@ from apparmor.common import AppArmorException, warn, msg, open_file_read # REPO_CFG = None # SHELL_FILES = ['easyprof.conf', 'notify.conf', 'parser.conf', 'subdomain.conf'] class Config: - def __init__(self, conf_type): - self.CONF_DIR = '/etc/apparmor' + def __init__(self, conf_type, conf_dir='/etc/apparmor'): + self.CONF_DIR = conf_dir # The type of config file that'll be read and/or written if conf_type == 'shell' or conf_type == 'ini': self.conf_type = conf_type diff --git a/apparmor/logparser.py b/apparmor/logparser.py index 1b6ac3544..55a2757e8 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -50,7 +50,7 @@ class ReadLog: if self.next_log_entry: sys.stderr.out('A log entry already present: %s' % self.next_log_entry) self.next_log_entry = self.LOG.readline() - while not (self.RE_LOG_v2_6_syslog.search(self.next_log_entry) or self.RE_LOG_v2_6_audit.search(self.next_log_entry)) or (self.logmark and re.search(self.logmark, self.next_log_entry)): + while not self.RE_LOG_v2_6_syslog.search(self.next_log_entry) and not self.RE_LOG_v2_6_audit.search(self.next_log_entry) and not (self.logmark and self.logmark in self.next_log_entry): self.next_log_entry = self.LOG.readline() if not self.next_log_entry: break @@ -328,8 +328,11 @@ class ReadLog: except IOError: raise AppArmorException('Can not read AppArmor logfile: ' + self.filename) #LOG = open_file_read(log_open) - line = self.get_next_log_entry() + line = True while line: + line = self.get_next_log_entry() + if not line: + break line = line.strip() self.debug_logger.debug('read_log: %s' % line) if self.logmark in line: @@ -337,13 +340,12 @@ class ReadLog: self.debug_logger.debug('read_log: seenmark = %s' %seenmark) if not seenmark: - line = self.get_next_log_entry() - continue + continue + event = self.parse_log_record(line) #print(event) if event: self.add_event_to_tree(event) - line = self.get_next_log_entry() self.LOG.close() self.logmark = '' return self.log diff --git a/apparmor/severity.py b/apparmor/severity.py index 3783b9732..7294411a2 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -17,53 +17,50 @@ class Severity: self.severity['VARIABLES'] = dict() if not dbname: return None - try: - database = open_file_read(dbname)#open(dbname, 'r') - except IOError: - raise AppArmorException("Could not open severity database: %s" % dbname) - for lineno, line in enumerate(database, start=1): - line = line.strip() # or only rstrip and lstrip? - if line == '' or line.startswith('#') : - continue - if line.startswith('/'): - try: - path, read, write, execute = line.split() - read, write, execute = int(read), int(write), int(execute) - except ValueError: - raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) - else: - if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11): - raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) - path = path.lstrip('/') - if '*' not in path: - self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} + + with open_file_read(dbname) as database:#open(dbname, 'r') + for lineno, line in enumerate(database, start=1): + line = line.strip() # or only rstrip and lstrip? + if line == '' or line.startswith('#') : + continue + if line.startswith('/'): + try: + path, read, write, execute = line.split() + read, write, execute = int(read), int(write), int(execute) + except ValueError: + raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) else: - ptr = self.severity['REGEXPS'] - pieces = path.split('/') - for index, piece in enumerate(pieces): - if '*' in piece: - path = '/'.join(pieces[index:]) - regexp = convert_regexp(path) - ptr[regexp] = {'AA_RANK': {'r': read, 'w': write, 'x': execute}} - break - else: - ptr[piece] = ptr.get(piece, {}) - ptr = ptr[piece] - elif line.startswith('CAP_'): - try: - resource, severity = line.split() - severity = int(severity) - except ValueError as e: - error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) - #error(error_message) - raise AppArmorException(error_message) # from None + if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11): + raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) + path = path.lstrip('/') + if '*' not in path: + self.severity['FILES'][path] = {'r': read, 'w': write, 'x': execute} + else: + ptr = self.severity['REGEXPS'] + pieces = path.split('/') + for index, piece in enumerate(pieces): + if '*' in piece: + path = '/'.join(pieces[index:]) + regexp = convert_regexp(path) + ptr[regexp] = {'AA_RANK': {'r': read, 'w': write, 'x': execute}} + break + else: + ptr[piece] = ptr.get(piece, {}) + ptr = ptr[piece] + elif line.startswith('CAP_'): + try: + resource, severity = line.split() + severity = int(severity) + except ValueError as e: + error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) + #error(error_message) + raise AppArmorException(error_message) # from None + else: + if severity not in range(0,11): + raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) + self.severity['CAPABILITIES'][resource] = severity else: - if severity not in range(0,11): - raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) - self.severity['CAPABILITIES'][resource] = severity - else: - raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) - database.close() + raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) def handle_capability(self, resource): """Returns the severity of for the capability resource, default value if no match""" @@ -187,11 +184,11 @@ class Severity: try: self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()] except KeyError: - raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values" % line[0]) + raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values in file: %s" % (line[0], prof_path)) else: line = line.split('=') if line[0] in self.severity['VARIABLES'].keys(): - raise AppArmorException("Variable %s was previously declared" % line[0]) + raise AppArmorException("Variable %s was previously declared in file: %s" % (line[0], prof_path)) self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] def unload_variables(self): diff --git a/apparmor/ui.py b/apparmor/ui.py index 3c7d314d7..cf925e6e2 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -42,25 +42,43 @@ def UI_Important(text): }) path, yarg = GetDataFromYast() +def get_translated_hotkey(translated, cmsg=''): + msg = 'PromptUser: '+_('Invalid hotkey for') + if re.search('\((\S)\)', translated): + return re.search('\((\S)\)', translated).groups()[0] + else: + if cmsg: + raise AppArmorException(cmsg) + else: + raise AppArmorException('%s %s' %(msg, translated)) + def UI_YesNo(text, default): debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default)) - ans = default + default = default.lower() + ans = None if UI_mode == 'text': - yes = '(Y)es' - no = '(N)o' - usrmsg = 'PromptUser: Invalid hotkey for' - yeskey = 'y' - nokey = 'n' - sys.stdout.write('\n' + text + '\n') - if default == 'y': - sys.stdout.write('\n[%s] / %s\n' % (yes, no)) - else: - sys.stdout.write('\n%s / [%s]\n' % (yes, no)) - ans = getkey()#readkey() - if ans: - ans = ans.lower() - else: - ans = default + yes = _('(Y)es') + no = _('(N)o') + yeskey = get_translated_hotkey(yes).lower() + nokey = get_translated_hotkey(no).lower() + ans = 'XXXINVALIDXXX' + while ans not in ['y', 'n']: + sys.stdout.write('\n' + text + '\n') + if default == 'y': + sys.stdout.write('\n[%s] / %s\n' % (yes, no)) + else: + sys.stdout.write('\n%s / [%s]\n' % (yes, no)) + ans = getkey() + if ans: + # Get back to english from localised answer + ans = ans.lower() + if ans == yeskey: + ans = 'y' + else: + ans = 'n' + else: + ans = default + else: SendDataToYast({ 'type': 'dialog-yesno', @@ -74,16 +92,19 @@ def UI_YesNo(text, default): def UI_YesNoCancel(text, default): debug_logger.debug('UI_YesNoCancel: %s: %s %s' % (UI_mode, text, default)) - + default = default.lower() + ans = None if UI_mode == 'text': - yes = '(Y)es' - no = '(N)o' - cancel = '(C)ancel' - yeskey = 'y' - nokey = 'n' - cancelkey = 'c' + yes = _('(Y)es') + no = _('(N)o') + cancel = _('(C)ancel') + + yeskey = get_translated_hotkey(yes).lower() + nokey = get_translated_hotkey(no).lower() + cancelkey = get_translated_hotkey(cancel).lower() + ans = 'XXXINVALIDXXX' - while ans != 'c' and ans != 'n' and ans != 'y': + while ans not in ['c', 'n', 'y']: sys.stdout.write('\n' + text + '\n') if default == 'y': sys.stdout.write('\n[%s] / %s / %s\n' % (yes, no, cancel)) @@ -91,9 +112,16 @@ def UI_YesNoCancel(text, default): sys.stdout.write('\n%s / [%s] / %s\n' % (yes, no, cancel)) else: sys.stdout.write('\n%s / %s / [%s]\n' % (yes, no, cancel)) - ans = getkey()#readkey() + ans = getkey() if ans: + # Get back to english from localised answer ans = ans.lower() + if ans == yeskey: + ans = 'y' + elif ans == nokey: + ans = 'n' + elif ans== cancelkey: + ans= 'c' else: ans = default else: @@ -111,7 +139,7 @@ def UI_GetString(text, default): debug_logger.debug('UI_GetString: %s: %s %s' % (UI_mode, text, default)) string = default if UI_mode == 'text': - sys.stdout.write('\n' + text + '\n') + sys.stdout.write('\n' + text) string = sys.stdin.readline() else: SendDataToYast({ @@ -198,6 +226,7 @@ CMDS = { 'CMD_UPDATE_PROFILE': '(U)pdate Profile', 'CMD_IGNORE_UPDATE': '(I)gnore Update', 'CMD_SAVE_CHANGES': '(S)ave Changes', + 'CMD_SAVE_SELECTED': 'Save Selec(t)ed Profile', 'CMD_UPLOAD_CHANGES': '(U)pload Changes', 'CMD_VIEW_CHANGES': '(V)iew Changes', 'CMD_VIEW_CHANGES_CLEAN': 'View Changes b/w (C)lean profiles', @@ -216,7 +245,7 @@ CMDS = { 'CMD_IGNORE_ENTRY': '(I)gnore' } -def UI_PromptUser(q): +def UI_PromptUser(q, params=''): cmd = None arg = None if UI_mode == 'text': @@ -230,10 +259,11 @@ def UI_PromptUser(q): arg = yarg['selected'] if cmd == 'CMD_ABORT': confirm_and_abort() - cmd == 'XXXINVALIDXXX' + cmd = 'XXXINVALIDXXX' elif cmd == 'CMD_FINISHED': - confirm_and_finish() - cmd == 'XXXINVALIDXXX' + if not params: + confirm_and_finish() + cmd = 'XXXINVALIDXXX' return (cmd, arg) def confirm_and_abort(): @@ -287,11 +317,7 @@ def Text_PromptUser(question): menutext = _(CMDS[cmd]) - menuhotkey = re.search('\((\S)\)', menutext) - if not menuhotkey: - raise AppArmorException('PromptUser: %s \'%s\'' %(_('Invalid hotkey in'), menutext)) - - key = menuhotkey.groups()[0].lower() + key = get_translated_hotkey(menutext).lower() # Duplicate hotkey if keys.get(key, False): raise AppArmorException('PromptUser: %s %s: %s' %(_('Duplicate hotkey for'), cmd, menutext)) @@ -306,12 +332,9 @@ def Text_PromptUser(question): default_key = 0 if default and CMDS[default]: defaulttext = _(CMDS[default]) + defmsg = 'PromptUser: ' + _('Invalid hotkey in default item') - defaulthotkey = re.search('\((\S)\)', defaulttext) - if not menuhotkey: - raise AppArmorException('PromptUser: %s \'%s\'' %(_('Invalid hotkey in default item'), defaulttext)) - - default_key = defaulthotkey.groups()[0].lower() + default_key = get_translated_hotkey(defaulttext, defmsg).lower() if not keys.get(default_key, False): raise AppArmorException('PromptUser: %s %s' %(_('Invalid default'), default)) diff --git a/apparmor/writeprofile.py b/apparmor/writeprofile.py index 0e1a2dd94..e69de29bb 100644 --- a/apparmor/writeprofile.py +++ b/apparmor/writeprofile.py @@ -1,114 +0,0 @@ -def write_header(prof_data, depth, name, embedded_hat, write_flags): - pre = ' ' * depth - data = [] - name = quote_if_needed(name) - - if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]' ,name)): - name = 'profile %s' % name - - if write_flags and prof_data['flags']: - data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags'])) - else: - data.append('%s%s {' % (pre, name)) - - return data - -def write_rules(prof_data, depth): - data = write_alias(prof_data, depth) - data += write_list_vars(prof_data, depth) - data += write_includes(prof_data, depth) - data += write_rlimits(prof_data, depth) - data += write_capabilities(prof_data, depth) - data += write_netdomain(prof_data, depth) - data += write_links(prof_data, depth) - data += write_paths(prof_data, depth) - data += write_change_profile(prof_data, depth) - - return data - -def write_piece(profile_data, depth, name, nhat, write_flags): - pre = ' ' * depth - data = [] - wname = None - inhat = False - if name == nhat: - wname = name - else: - wname = name + '//' + nhat - name = nhat - inhat = True - data += ['begin header'] - data += write_header(profile_data[name], depth, wname, False, write_flags) - data +=['end header'] - data += write_rules(profile_data[name], depth+1) - - pre2 = ' ' * (depth+1) - # External hat declarations - for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): - if profile_data[hat].get('declared', False): - data.append('%s^%s,' %(pre2, hat)) - - if not inhat: - # Embedded hats - for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): - if not profile_data[hat]['external'] and not profile_data[hat]['declared']: - data.append('') - if profile_data[hat]['profile']: - data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags))) - else: - data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags))) - - data += list(map(str, write_rules(profile_data[hat], depth+2))) - - data.append('%s}' %pre2) - - data.append('%s}' %pre) - - # External hats - for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): - if name == nhat and profile_data[hat].get('external', False): - data.append('') - data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags))) - data.append(' }') - - return data - - -def serialize_profile(profile_data, name, options): - string = '' - include_metadata = False - include_flags = True - data= [] - - if options:# and type(options) == dict: - if options.get('METADATA', False): - include_metadata = True - if options.get('NO_FLAGS', False): - include_flags = False - - if include_metadata: - string = '# Last Modified: %s\n' %time.time() - - if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] - and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): - repo = profile_data[name]['repo'] - string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id']) - elif profile_data[name]['repo']['neversubmit']: - string += '# REPOSITORY: NEVERSUBMIT\n' - - if profile_data[name].get('initial_comment', False): - comment = profile_data[name]['initial_comment'] - comment.replace('\\n', '\n') - string += comment + '\n' - - prof_filename = get_profile_filename(name) - if filelist.get(prof_filename, False): - data += write_alias(filelist[prof_filename], 0) - data += write_list_vars(filelist[prof_filename], 0) - data += write_includes(filelist[prof_filename], 0) - - data += write_piece(profile_data, 0, name, name, include_flags) - - string += '\n'.join(data) - - return string+'\n' From 41b9aa112d432269e1faf46062e928e5c2fedce1 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 26 Aug 2013 00:23:59 +0530 Subject: [PATCH 052/183] Merged aa-audit, aa-autodep, aa-complain, aa-disable, aa-enforce to share the common code into a tools.py module. Added -r/--remove feature to aa-complain, aa-enforce, aa-audit and -r/--revert feature to aa-disable. Some other fixes from review 48..51 --- Tools/aa-audit.py | 51 +++------------- Tools/aa-autodep.py | 50 +++------------- Tools/aa-cleanprof.py | 53 +++++++++++++++++ Tools/aa-complain.py | 52 +++-------------- Tools/aa-disable.py | 63 +++----------------- Tools/aa-enforce.py | 64 +++------------------ Tools/aa-genprof.py | 23 +++----- Tools/aa-logprof.py | 12 ++-- Tools/aa-unconfined.py | 26 ++++----- apparmor/aa.py | 55 +++++++++++++++--- apparmor/tools.py | 128 +++++++++++++++++++++++++++++++++++++++++ apparmor/ui.py | 4 +- 12 files changed, 293 insertions(+), 288 deletions(-) create mode 100644 Tools/aa-cleanprof.py create mode 100644 apparmor/tools.py diff --git a/Tools/aa-audit.py b/Tools/aa-audit.py index c5e9b2380..5c048031b 100644 --- a/Tools/aa-audit.py +++ b/Tools/aa-audit.py @@ -1,54 +1,17 @@ #!/usr/bin/python -import sys -import os + import argparse -import apparmor.aa as apparmor +from apparmor.tools import * parser = argparse.ArgumentParser(description='Switch the given programs to audit mode') parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('program', type=str, nargs='+', help='name of program to hswitch to audit mode') +parser.add_argument('-r', '--remove', action='store_true', help='remove audit mode') +parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -profiling = args.program -profiledir = args.d +audit = aa_tools('audit', args) -if profiledir: - apparmor.profile_dir = apparmor.get_full_path(profiledir) - if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) +audit.check_profile_dir() -for p in profiling: - if not p: - continue - - program = None - if os.path.exists(p): - program = apparmor.get_full_path(p).strip() - else: - which = apparmor.which(p) - if which: - program = apparmor.get_full_path(which) - - if os.path.exists(program): - apparmor.read_profiles() - filename = apparmor.get_profile_filename(program) - - if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - continue - - sys.stdout.write(_('Setting %s to audit mode.\n')%program) - - apparmor.set_profile_flags(filename, 'audit') - - cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) - if cmd_info[0] != 0: - raise apparmor.AppArmorException(cmd_info[1]) - else: - if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) - else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) - sys.exit(1) - -sys.exit(0) \ No newline at end of file +audit.act() diff --git a/Tools/aa-autodep.py b/Tools/aa-autodep.py index 9adbc429b..3c03d096f 100644 --- a/Tools/aa-autodep.py +++ b/Tools/aa-autodep.py @@ -1,53 +1,17 @@ #!/usr/bin/python -import sys -import os + import argparse -import apparmor.aa as apparmor +from apparmor.tools import * -parser = argparse.ArgumentParser(description='Disable the profile for the given programs') +parser = argparse.ArgumentParser(description='') parser.add_argument('--force', type=str, help='path to profiles') parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('program', type=str, nargs='+', help='name of program to have profile disabled') +parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -force = args.force -profiledir = args.d -profiling = args.program +autodep = aa_tools('autodep', args) -aa_mountpoint = apparmor.check_for_apparmor() +autodep.check_profile_dir() -if profiledir: - apparmor.profile_dir = apparmor.get_full_path(profiledir) - if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) - -for p in profiling: - if not p: - continue - - program = None - if os.path.exists(p): - program = apparmor.get_full_path(p).strip() - else: - which = apparmor.which(p) - if which: - program = apparmor.get_full_path(which) - - apparmor.check_qualifiers(program) - - if os.path.exists(program): - if os.path.exists(apparmor.get_profile_filename(program) and not force): - apparmor.UI_Info('Profile for %s already exists - skipping.'%program) - else: - apparmor.autodep(program) - if aa_mountpoint: - apparmor.reload(program) - else: - if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) - else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) - sys.exit(1) - -sys.exit(0) +autodep.act() \ No newline at end of file diff --git a/Tools/aa-cleanprof.py b/Tools/aa-cleanprof.py new file mode 100644 index 000000000..54821813f --- /dev/null +++ b/Tools/aa-cleanprof.py @@ -0,0 +1,53 @@ +#!/usr/bin/python + +import sys +import os +import argparse + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='Cleanup the profiles for the given programs') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('program', type=str, nargs='+', help='name of program') +args = parser.parse_args() + +profiling = args.program +profiledir = args.d + +if profiledir: + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + +for p in profiling: + if not p: + continue + + program = None + if os.path.exists(p): + program = apparmor.get_full_path(p).strip() + else: + which = apparmor.which(p) + if which: + program = apparmor.get_full_path(which) + + if os.path.exists(program): + apparmor.read_profiles() + filename = apparmor.get_profile_filename(program) + + if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): + continue + + apparmor.UI_Info(_('Setting %s to complain mode.\n')%program) + + apparmor.set_profile_flags(filename, 'complain') + + cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + if cmd_info[0] != 0: + raise apparmor.AppArmorException(cmd_info[1]) + else: + if '/' not in p: + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + sys.exit(1) diff --git a/Tools/aa-complain.py b/Tools/aa-complain.py index b7ee90839..ca2f2c841 100644 --- a/Tools/aa-complain.py +++ b/Tools/aa-complain.py @@ -1,55 +1,17 @@ #!/usr/bin/python -import sys -import os + import argparse -import apparmor.aa as apparmor +from apparmor.tools import * parser = argparse.ArgumentParser(description='Switch the given program to complain mode') parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('program', type=str, nargs='+', help='name of program to switch to complain mode') +parser.add_argument('-r', '--remove', action='store_true', help='remove complain mode') +parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -profiling = args.program -profiledir = args.d +complain = aa_tools('complain', args) -if profiledir: - apparmor.profile_dir = apparmor.get_full_path(profiledir) - if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) +complain.check_profile_dir() -for p in profiling: - if not p: - continue - - program = None - if os.path.exists(p): - program = apparmor.get_full_path(p).strip() - else: - which = apparmor.which(p) - if which: - program = apparmor.get_full_path(which) - - if os.path.exists(program): - apparmor.read_profiles() - filename = apparmor.get_profile_filename(program) - - if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - continue - - sys.stdout.write(_('Setting %s to complain mode.\n')%program) - - apparmor.set_profile_flags(filename, 'complain') - - cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) - if cmd_info[0] != 0: - raise apparmor.AppArmorException(cmd_info[1]) - else: - if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) - else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) - sys.exit(1) - -sys.exit(0) - +complain.act() diff --git a/Tools/aa-disable.py b/Tools/aa-disable.py index 1eaac4ad0..5fc5dd201 100644 --- a/Tools/aa-disable.py +++ b/Tools/aa-disable.py @@ -1,67 +1,20 @@ #!/usr/bin/python -import sys -import os + import argparse +from apparmor.tools import * import apparmor.aa as apparmor parser = argparse.ArgumentParser(description='Disable the profile for the given programs') parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('program', type=str, nargs='+', help='name of program to have profile disabled') +parser.add_argument('-r', '--revert', action='store_true', help='enable the profile for the given programs') +parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -profiledir = args.d -profiling = args.program +disable = aa_tools('disable', args) -if profiledir: - apparmor.profile_dir = apparmor.get_full_path(profiledir) - if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) +disable.check_profile_dir() +disable.check_disable_dir() -disabledir = apparmor.profile_dir+'/disable' -if not os.path.isdir(disabledir): - raise apparmor.AppArmorException("Can't find AppArmor disable directorys %s." %disabledir) +disable.act() -for p in profiling: - if not p: - continue - - program = None - if os.path.exists(p): - program = apparmor.get_full_path(p).strip() - else: - which = apparmor.which(p) - if which: - program = apparmor.get_full_path(which) - - if os.path.exists(program): - apparmor.read_profiles() - filename = apparmor.get_profile_filename(program) - - if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - continue - - bname = os.path.basename(filename) - if not bname: - apparmor.AppArmorException(_('Unable to find basename for %s.')%filename) - - sys.stdout.write(_('Disabling %s.\n')%program) - - link = '%s/%s'%(disabledir, bname) - if not os.path.exists(link): - try: - os.symlink(filename, link) - except: - raise apparmor.AppArmorException('Could not create %s symlink to %s.'%(link, filename)) - - cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) - if cmd_info[0] != 0: - raise apparmor.AppArmorException(cmd_info[1]) - else: - if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) - else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) - sys.exit(1) - -sys.exit(0) diff --git a/Tools/aa-enforce.py b/Tools/aa-enforce.py index be0611659..33aed3895 100644 --- a/Tools/aa-enforce.py +++ b/Tools/aa-enforce.py @@ -1,67 +1,17 @@ #!/usr/bin/python -import sys -import os + import argparse -import apparmor.aa as apparmor +from apparmor.tools import * parser = argparse.ArgumentParser(description='Switch the given program to enforce mode') parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('program', type=str, nargs='+', help='name of program to switch to enforce mode') +parser.add_argument('-r', '--remove', action='store_true', help='remove enforce mode') +parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -profiledir = args.d -profiling = args.program +enforce = aa_tools('enforce', args) -if profiledir: - apparmor.profile_dir = apparmor.get_full_path(profiledir) - if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) +enforce.check_profile_dir() -for p in profiling: - if not p: - continue - - program = None - if os.path.exists(p): - program = apparmor.get_full_path(p).strip() - else: - which = apparmor.which(p) - if which: - program = apparmor.get_full_path(which) - - if os.path.exists(program): - apparmor.read_profiles() - filename = apparmor.get_profile_filename(program) - - if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - continue - - sys.stdout.write(_('Setting %s to enforce mode.\n')%program) - - apparmor.set_profile_flags(filename, '') - - # Remove symlink from profile_dir/force-complain - complainlink = filename - complainlink = re.sub('^%s'%apparmor.profile_dir, '%s/force-complain'%apparmor.profile_dir, complainlink) - if os.path.exists(complainlink): - os.remove(complainlink) - - # remove symlink in profile_dir/disable - disablelink = filename - disablelink = re.sub('^%s'%apparmor.profile_dir, '%s/disable'%apparmor.profile_dir, disablelink) - if os.path.exists(disablelink): - os.remove(disablelink) - - cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) - if cmd_info[0] != 0: - raise apparmor.AppArmorException(cmd_info[1]) - else: - if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) - else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) - sys.exit(1) - -sys.exit(0) - +enforce.act() diff --git a/Tools/aa-genprof.py b/Tools/aa-genprof.py index db9733d82..577252354 100644 --- a/Tools/aa-genprof.py +++ b/Tools/aa-genprof.py @@ -1,10 +1,11 @@ #!/usr/bin/python -import sys -import subprocess + +import argparse +import atexit import os import re -import atexit -import argparse +import subprocess +import sys import apparmor.aa as apparmor @@ -44,20 +45,12 @@ filename = args.f aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: - raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.')) + raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) if profiledir: apparmor.profile_dir = apparmor.get_full_path(profiledir) if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) - - -# if not profiling: -# profiling = apparmor.UI_GetString(_('Please enter the program to profile: '), '') -# if profiling: -# profiling = profiling.strip() -# else: -# sys.exit(0) + raise apparmor.AppArmorException("%s is not a directory." %profiledir) program = None #if os.path.exists(apparmor.which(profiling.strip())): @@ -71,7 +64,7 @@ else: if not program or not os.path.exists(program): if '/' not in profiling: - raise apparmor.AppArmorException(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' in another window in order to find the fully-qualified path.") %(profiling, profiling)) + raise apparmor.AppArmorException(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' in another window in order to find the fully-qualified path and use the full path as parameter.") %(profiling, profiling)) else: raise apparmor.AppArmorException(_('%s does not exists, please double-check the path.') %profiling) diff --git a/Tools/aa-logprof.py b/Tools/aa-logprof.py index d8b73cd46..40251ab57 100644 --- a/Tools/aa-logprof.py +++ b/Tools/aa-logprof.py @@ -1,8 +1,9 @@ #!/usr/bin/python -import sys -import apparmor.aa as apparmor -import os + import argparse +import os + +import apparmor.aa as apparmor parser = argparse.ArgumentParser(description='Process log entries to generate profiles') parser.add_argument('-d', type=str, help='path to profiles') @@ -16,15 +17,14 @@ logmark = args.m or '' aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: - raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.')) + raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) if profiledir: apparmor.profiledir = apparmor.get_full_path(profiledir) if not os.path.isdir(apparmor.profiledir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + raise apparmor.AppArmorException("%s is not a directory."%profiledir) apparmor.loadincludes() apparmor.do_logprof_pass(logmark) -sys.exit(0) diff --git a/Tools/aa-unconfined.py b/Tools/aa-unconfined.py index be39f711a..35a0cb7f2 100644 --- a/Tools/aa-unconfined.py +++ b/Tools/aa-unconfined.py @@ -1,20 +1,20 @@ #!/usr/bin/python -import sys + +import argparse import os import re -import argparse import apparmor.aa as apparmor parser = argparse.ArgumentParser(description='') -parser.add_argument('--paranoid', type=str) +parser.add_argument('--paranoid', action='store_true') args = parser.parse_args() paranoid = args.paranoid aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: - raise apparmor.AppArmorException(_('AppArmor seems to have not been started. Please enable AppArmor and try again.')) + raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) pids = [] if paranoid: @@ -48,22 +48,22 @@ for pid in sorted(pids): else: pname = '' if not attr: - if re.search('^(/usr)?/bin/(python|perl|bash)', prog): - cmdline = re.sub('\0', ' ', cmdline) + if re.search('^(/usr)?/bin/(python|perl|bash|sh)', prog): + cmdline = re.sub('\x00', ' ', cmdline) cmdline = re.sub('\s+$', '', cmdline).strip() - sys.stdout.write(_('%s %s (%s) not confined\n')%(pid, prog, cmdline)) + if 'perl' in cmdline: + print(cmdline) + apparmor.UI_Info(_('%s %s (%s) not confined\n')%(pid, prog, cmdline)) else: if pname and pname[-1] == ')': pname += ' ' - sys.stdout.write(_('%s %s %snot confined\n')%(pid, prog, pname)) + apparmor.UI_Info(_('%s %s %snot confined\n')%(pid, prog, pname)) else: - if re.search('^(/usr)?/bin/(python|perl|bash)', prog): + if re.search('^(/usr)?/bin/(python|perl|bash|sh)', prog): cmdline = re.sub('\0', ' ', cmdline) cmdline = re.sub('\s+$', '', cmdline).strip() - sys.stdout.write(_("%s %s (%s) confined by '%s'\n")%(pid, prog, cmdline, attr)) + apparmor.UI_Info(_("%s %s (%s) confined by '%s'\n")%(pid, prog, cmdline, attr)) else: if pname and pname[-1] == ')': pname += ' ' - sys.stdout.write(_("%s %s %sconfined by '%s'\n")%(pid, prog, pname, attr)) - -sys.exit(0) \ No newline at end of file + apparmor.UI_Info(_("%s %s %sconfined by '%s'\n")%(pid, prog, pname, attr)) \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index 92fab724d..72a916749 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -23,7 +23,9 @@ from apparmor.common import (AppArmorException, error, debug, msg, cmd, hasher, open_file_write, convert_regexp, DebugLogger) from apparmor.ui import * + from copy import deepcopy + from apparmor.aamode import * # Setup logging incase of debugging is enabled @@ -204,7 +206,7 @@ def get_profile_filename(profile): profile = profile.replace('/', '.') full_profilename = profile_dir + '/' + profile return full_profilename - + def name_to_prof_filename(prof_filename): """Returns the profile""" if prof_filename.startswith(profile_dir): @@ -225,15 +227,15 @@ def complain(path): if not prof_filename : fatal_error("Can't find %s" % path) UI_Info('Setting %s to complain mode.' % name) - set_profile_flags(prof_filename, 'complain') + change_profile_flags(prof_filename, 'complain') def enforce(path): """Sets the profile to enforce mode if it exists""" prof_filename, name = name_to_prof_filename(path) if not prof_filename : fatal_error("Can't find %s" % path) - UI_Info('Setting %s to enforce moode' % name) - set_profile_flags(prof_filename, '') + UI_Info('Setting %s to enforce mode' % name) + change_profile_flags(prof_filename, 'complain') def head(file): """Returns the first/head line of the file""" @@ -515,14 +517,45 @@ def autodep(bin_name, pname=''): write_profile_ui_feedback(pname) def get_profile_flags(filename): - flags = 'enforce' + # To-Do + # XXX If more than one profile in a file then second one is being ignored XXX + # Do we return flags for both or + flags = '' with open_file_read(filename) as f_in: for line in f_in: if RE_PROFILE_START.search(line): flags = RE_PROFILE_START.search(line).groups()[6] return flags - return flags + + raise AppArmorException(_('%s contains no profile')%filename) +def change_profile_flags(filename, flag, remove=False): + old_flags = get_profile_flags(filename) + newflags = [] + if old_flags != '': + # Flags maybe white-space and/or , separated + old_flags = old_flags.split(',') + + if type(old_flags) == type([]): + for i in old_flags: + newflags += i.split() + else: + newflags = old_flags.split() + #newflags = [lambda x:x.strip(), oldflags] + if flag in newflags: + if remove: + newflags.remove(flag) + else: + if not remove: + if flag == '': + if 'complain' in newflags: + newflags.remove('complain') + else: + newflags.append(flag) + + newflags = ','.join(newflags) + + set_profile_flags(filename, newflags) def set_profile_flags(prof_filename, newflags): """Reads the old profile file and updates the flags accordingly""" @@ -1774,6 +1807,7 @@ def ask_the_questions(): UI_Info(_('Deleted %s previous matching profile entries.') % deleted) elif ans == 'CMD_DENY': + path = options[selected].strip() # Add new entry? aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode) @@ -1832,7 +1866,9 @@ def ask_the_questions(): if newpath not in options: options.append(newpath) default_option = len(options) - + else: + default_option = options.index(newpath) + 1 + elif ans == 'CMD_GLOBEXT': newpath = options[selected].strip() if not re_match_include(newpath): @@ -1851,11 +1887,14 @@ def ask_the_questions(): newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**'+match.groups()[0], newpath) else: match = re.search('(\.[^/]+)$', newpath) - newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) + if match: + newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) if newpath not in options: options.append(newpath) default_option = len(options) + else: + default_option = options.index(newpath) + 1 elif re.search('\d', ans): default_option = ans diff --git a/apparmor/tools.py b/apparmor/tools.py new file mode 100644 index 000000000..36ee17941 --- /dev/null +++ b/apparmor/tools.py @@ -0,0 +1,128 @@ +import os +import re +import sys + +import apparmor.aa as apparmor + +class aa_tools: + def __init__(self, tool_name, args): + self.name = tool_name + self.profiledir = args.d + self.profiling = args.program + + if tool_name in ['audit', 'complain', 'enforce']: + self.remove = args.remove + elif tool_name == 'disable': + self.revert = args.revert + self.disabledir = apparmor.profile_dir+'/disable' + elif tool_name == 'autodep': + self.force = args.force + self.aa_mountpoint = apparmor.check_for_apparmor() + + def check_profile_dir(self): + if self.profiledir: + apparmor.profile_dir = apparmor.get_full_path(self.profiledir) + if not os.path.isdir(apparmor.profile_dir): + raise apparmor.AppArmorException("%s is not a directory." %self.profiledir) + + def check_disable_dir(self): + if not os.path.isdir(self.disabledir): + raise apparmor.AppArmorException("Can't find AppArmor disable directory %s." %self.disabledir) + + def act(self): + for p in self.profiling: + if not p: + continue + + program = None + if os.path.exists(p): + program = apparmor.get_full_path(p).strip() + else: + which = apparmor.which(p) + if which: + program = apparmor.get_full_path(which) + + if os.path.exists(program): + if self.name == 'autodep': + self.use_autodep(program) + + else: + apparmor.read_profiles() + filename = apparmor.get_profile_filename(program) + + if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): + continue + + if self.name == 'enforce': + apparmor.UI_Info(_('Setting %s to enforce mode.\n')%program) + apparmor.change_profile_flags(filename, '', self.remove) + #apparmor.set_profile_flags(filename, '') + self.remove_symlinks(filename) + + elif self.name == 'disable': + apparmor.UI_Info(_('Disabling %s.\n')%program) + if not self.revert: + self.disable_profile(filename) + else: + self.remove_disable_link(filename) + else: + apparmor.UI_Info(_('Setting %s to %s mode.\n')%(program, self.name)) + apparmor.change_profile_flags(filename, self.name, self.remove) + #apparmor.set_profile_flags(filename, self.name) + + cmd_info = apparmor.cmd(['cat', filename, '|', apparmor.parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + + if cmd_info[0] != 0: + raise apparmor.AppArmorException(cmd_info[1]) + + else: + if '/' not in p: + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + sys.exit(1) + + def use_autodep(self, program): + apparmor.check_qualifiers(program) + + if os.path.exists(apparmor.get_profile_filename(program) and not self.force): + apparmor.UI_Info('Profile for %s already exists - skipping.'%program) + else: + apparmor.autodep(program) + if self.aa_mountpoint: + apparmor.reload(program) + + def remove_symlinks(self, filename): + # Remove symlink from profile_dir/force-complain + complainlink = filename + complainlink = re.sub('^%s'%apparmor.profile_dir, '%s/force-complain'%apparmor.profile_dir, complainlink) + if os.path.exists(complainlink): + os.remove(complainlink) + + # remove symlink in profile_dir/disable + disablelink = filename + disablelink = re.sub('^%s'%apparmor.profile_dir, '%s/disable'%apparmor.profile_dir, disablelink) + if os.path.exists(disablelink): + os.remove(disablelink) + + def remove_disable_link(self, filename): + # Remove the file from disable dir + bname = os.path.basename(filename) + if not bname: + raise apparmor.AppArmorException(_('Unable to find basename for %s.')%filename) + + link = '%s/%s'%(self.disabledir, bname) + if os.path.exists(link): + os.remove(link) + + def disable_profile(self, filename): + bname = os.path.basename(filename) + if not bname: + raise apparmor.AppArmorException(_('Unable to find basename for %s.')%filename) + + link = '%s/%s'%(self.disabledir, bname) + if not os.path.exists(link): + try: + os.symlink(filename, link) + except: + raise apparmor.AppArmorException('Could not create %s symlink to %s.'%(link, filename)) \ No newline at end of file diff --git a/apparmor/ui.py b/apparmor/ui.py index cf925e6e2..2352a0e38 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -149,7 +149,7 @@ def UI_GetString(text, default): }) ypath, yarg = GetDataFromYast() string = yarg['string'] - return string + return string.strip() def UI_GetFile(file): debug_logger.debug('UI_GetFile: %s' % UI_mode) @@ -292,7 +292,7 @@ def UI_LongMessage(title, message): ypath, yarg = GetDataFromYast() def confirm_and_finish(): - sys.stdout.write('FINISHING..\n') + sys.stdout.write('FINISHING...\n') sys.exit(0) def Text_PromptUser(question): From 781ff9c3d9e3778296332151f3a9e1c827caa960 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 26 Aug 2013 00:41:15 +0530 Subject: [PATCH 053/183] aa-cleanprof tool --- Tools/aa-cleanprof.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Tools/aa-cleanprof.py b/Tools/aa-cleanprof.py index 54821813f..cbe2ead8d 100644 --- a/Tools/aa-cleanprof.py +++ b/Tools/aa-cleanprof.py @@ -1,6 +1,5 @@ #!/usr/bin/python -import sys import os import argparse @@ -17,9 +16,9 @@ profiledir = args.d if profiledir: apparmor.profile_dir = apparmor.get_full_path(profiledir) if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("Can't find AppArmor profiles in %s." %profiledir) + raise apparmor.AppArmorException("%s is not a directory."%profiledir) -for p in profiling: +for p in sorted(profiling): if not p: continue @@ -34,20 +33,9 @@ for p in profiling: if os.path.exists(program): apparmor.read_profiles() filename = apparmor.get_profile_filename(program) - - if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - continue - - apparmor.UI_Info(_('Setting %s to complain mode.\n')%program) - - apparmor.set_profile_flags(filename, 'complain') - - cmd_info = apparmor.cmd(['cat', filename, '|', parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) - if cmd_info[0] != 0: - raise apparmor.AppArmorException(cmd_info[1]) - else: - if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + if filename: + apparmor.write_profile_ui_feedback(program) + apparmor.reload_base(program) else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) - sys.exit(1) + raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) + From 27efe62a92454e95b8036300670694076b1366d9 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 30 Aug 2013 03:54:31 +0530 Subject: [PATCH 054/183] Fixes from review 52-53, merging cleanprof into apparmor/tools.py corrected enforce() and complain() to create/remove symlinks to force-complain/disable subdirs. Wrote some tests for globbing methods, segregated glob-path and glob-path-with-extension into methods in aa.py --- Testing/aa_test.py | 85 ++++++++++++++------ Testing/minitools_test.py | 28 +++++++ Testing/sbin.klogd | 35 -------- Testing/severity_test.py | 9 ++- Testing/usr.sbin.dnsmasq | 60 -------------- Tools/aa-audit.py | 2 - Tools/aa-autodep.py | 2 - Tools/aa-cleanprof.py | 33 +------- Tools/aa-complain.py | 2 - Tools/aa-disable.py | 4 - Tools/aa-enforce.py | 8 +- Tools/aa-mergeprof.py | 0 Tools/aa-unconfined.py | 8 +- apparmor/aa.py | 162 +++++++++++++++++++++++--------------- apparmor/tools.py | 104 ++++++++++++------------ 15 files changed, 256 insertions(+), 286 deletions(-) create mode 100644 Testing/minitools_test.py delete mode 100644 Testing/sbin.klogd delete mode 100644 Testing/usr.sbin.dnsmasq create mode 100644 Tools/aa-mergeprof.py diff --git a/Testing/aa_test.py b/Testing/aa_test.py index 8f82fde53..16b12f6c1 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -5,13 +5,68 @@ sys.path.append('../') import apparmor.aa import apparmor.logparser -#from apparmor.aa import parse_event - class Test(unittest.TestCase): + + def setUp(self): + self.MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, + 'w': apparmor.aamode.AA_MAY_WRITE, + 'r': apparmor.aamode.AA_MAY_READ, + 'a': apparmor.aamode.AA_MAY_APPEND, + 'l': apparmor.aamode.AA_MAY_LINK, + 'k': apparmor.aamode.AA_MAY_LOCK, + 'm': apparmor.aamode.AA_EXEC_MMAP, + 'i': apparmor.aamode.AA_EXEC_INHERIT, + 'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE, + 'U': apparmor.aamode.AA_EXEC_UNCONFINED, + 'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE, + 'P': apparmor.aamode.AA_EXEC_PROFILE, + 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, + 'C': apparmor.aamode.AA_EXEC_CHILD, + } def test_loadinclude(self): apparmor.aa.loadincludes() + def test_path_globs(self): + globs = { + '/foo/': '/*/', + '/foo': '/*', + '/b*': '/*', + '/*b': '/*', + '/*': '/*', + '/*/': '/*/', + '/*.foo/': '/*/', + '/**.foo/': '/**/', + '/foo/*/': '/**/', + '/usr/foo/*': '/usr/**', + '/usr/foo/**': '/usr/**', + '/usr/foo/bar**': '/usr/foo/**', + '/usr/foo/**barr': '/usr/foo/**', + '/usr/foo/**/bar': '/usr/foo/**/*', + '/usr/foo/**/*': '/usr/foo/**', + '/usr/foo/*/bar': '/usr/foo/*/*', + '/usr/foo/*/*': '/usr/foo/**', + '/usr/foo/*/**': '/usr/foo/**' + } + for path in globs.keys(): + self.assertEqual(apparmor.aa.glob_path(path), globs[path], 'Unexpected glob generated for path: %s'%path) + + def test_path_withext_globs(self): + globs = { + '/foo/bar': '/foo/bar', + '/foo/**/bar': '/foo/**/bar', + '/foo.bar': '/*.bar', + '/*.foo': '/*.foo' , + '/usr/*.bar': '/**.bar', + '/usr/**.bar': '/**.bar', + '/usr/foo**.bar': '/usr/**.bar', + '/usr/foo*.bar': '/usr/*.bar', + '/usr/**foo.bar': '/usr/**.bar', + '/usr/*foo.bar': '/usr/*.bar', + '/usr/foo.b*': '/usr/*.b*' + } + for path in globs.keys(): + self.assertEqual(apparmor.aa.glob_path_withext(path), globs[path], 'Unexpected glob generated for path: %s'%path) def test_parse_event(self): parser = apparmor.logparser.ReadLog('', '', '', '', '') @@ -38,27 +93,9 @@ class Test(unittest.TestCase): def test_modes_to_string(self): - MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, - 'w': apparmor.aamode.AA_MAY_WRITE, - 'r': apparmor.aamode.AA_MAY_READ, - 'a': apparmor.aamode.AA_MAY_APPEND, - 'l': apparmor.aamode.AA_MAY_LINK, - 'k': apparmor.aamode.AA_MAY_LOCK, - 'm': apparmor.aamode.AA_EXEC_MMAP, - 'i': apparmor.aamode.AA_EXEC_INHERIT, - 'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE, # Unconfined + Unsafe - 'U': apparmor.aamode.AA_EXEC_UNCONFINED, - 'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE, # Profile + unsafe - 'P': apparmor.aamode.AA_EXEC_PROFILE, - 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, # Child + Unsafe - 'C': apparmor.aamode.AA_EXEC_CHILD, - #'n': apparmor.aamode.AA_EXEC_NT | apparmor.aamode.AA_EXEC_UNSAFE, - #'N': apparmor.aamode.AA_EXEC_NT - } - #for i in MODE_TEST.keys(): - # print(i, MODE_TEST[i]) - while MODE_TEST: - string,mode = MODE_TEST.popitem() + + for string in self.MODE_TEST.keys(): + mode = self.MODE_TEST[string] self.assertEqual(apparmor.aamode.mode_to_str(mode), string, 'mode is %s and string is %s'%(mode, string)) def test_string_to_modes(self): @@ -78,8 +115,6 @@ class Test(unittest.TestCase): 'P': apparmor.aamode.AA_EXEC_PROFILE, 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, # Child + Unsafe 'C': apparmor.aamode.AA_EXEC_CHILD, - #'n': apparmor.aamode.AA_EXEC_NT | apparmor.aamode.AA_EXEC_UNSAFE, - #'N': apparmor.aamode.AA_EXEC_NT } #while MODE_TEST: diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py new file mode 100644 index 000000000..3bc023cd4 --- /dev/null +++ b/Testing/minitools_test.py @@ -0,0 +1,28 @@ +import unittest +import shutil + +class Test(unittest.TestCase): + + def setUp(self): + #copy the local profiles to the test directory + shutil.copytree('/etc/apparmor.d/', './profiles/') + + def test_audit(self): + pass + + def test_complain(self): + pass + + def test_enforce(self): + pass + + def test_disable(self): + pass + + def test_autodep(self): + pass + + +if __name__ == "__main__": + #import sys;sys.argv = ['', 'Test.testName'] + unittest.main() \ No newline at end of file diff --git a/Testing/sbin.klogd b/Testing/sbin.klogd deleted file mode 100644 index 2563b9be8..000000000 --- a/Testing/sbin.klogd +++ /dev/null @@ -1,35 +0,0 @@ -# ------------------------------------------------------------------ -# -# Copyright (C) 2002-2009 Novell/SUSE -# Copyright (C) 2010 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. -# -# ------------------------------------------------------------------ - -#include - -/sbin/klogd { - #include - - capability sys_admin, # for backward compatibility with kernel <= 2.6.37 - capability syslog, - - network inet stream, - - /boot/System.map* r, - @{PROC}/kmsg r, - @{PROC}/kallsyms r, - /dev/tty rw, - - /sbin/klogd rmix, - /var/log/boot.msg rwl, - /{,var/}run/klogd.pid krwl, - /{,var/}run/klogd/klogd.pid krwl, - /{,var/}run/klogd/kmsg r, - - # Site-specific additions and overrides. See local/README for details. - #include -} diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 77c625124..0c6163542 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -1,3 +1,4 @@ +import shutil import sys import unittest @@ -7,6 +8,10 @@ import apparmor.severity as severity from apparmor.common import AppArmorException class Test(unittest.TestCase): + + def setUp(self): + #copy the local profiles to the test directory + shutil.copytree('/etc/apparmor.d/', './profiles/') def testRank_Test(self): #z = severity.Severity() @@ -34,14 +39,14 @@ class Test(unittest.TestCase): self.assertEqual(s.rank('/etc/**', 'r') , 10, 'Invalid Rank') # Load all variables for /sbin/klogd and test them - s.load_variables('sbin.klogd') + s.load_variables('profiles/sbin.klogd') self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') s.unload_variables() - s.load_variables('usr.sbin.dnsmasq') + s.load_variables('profiles/usr.sbin.dnsmasq') self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') diff --git a/Testing/usr.sbin.dnsmasq b/Testing/usr.sbin.dnsmasq deleted file mode 100644 index 9fc3ed1ef..000000000 --- a/Testing/usr.sbin.dnsmasq +++ /dev/null @@ -1,60 +0,0 @@ -# ------------------------------------------------------------------ -# -# Copyright (C) 2009 John Dong -# Copyright (C) 2010 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. -# -# ------------------------------------------------------------------ - -@{TFTP_DIR}=/var/tftp /srv/tftpboot #comment - -#include # comment -/usr/sbin/dnsmasq { - #include - #include - - capability net_bind_service, - capability setgid, - capability setuid, - capability dac_override, - capability net_admin, # for DHCP server - capability net_raw, # for DHCP server ping checks - network inet raw, - - /etc/dnsmasq.conf r, - /etc/dnsmasq.d/ r, - /etc/dnsmasq.d/* r, - /etc/ethers r, - - /usr/sbin/dnsmasq mr, - - /{,var/}run/*dnsmasq*.pid w, - /{,var/}run/dnsmasq-forwarders.conf r, - /{,var/}run/dnsmasq/ r, - /{,var/}run/dnsmasq/* rw, - - /var/lib/misc/dnsmasq.leases rw, # Required only for DHCP server usage - - # for the read-only TFTP server - @{TFTP_DIR}/ r, - @{TFTP_DIR}/** r, - - # libvirt lease and hosts files for dnsmasq - /var/lib/libvirt/dnsmasq/ r, - /var/lib/libvirt/dnsmasq/*.leases rw, - /var/lib/libvirt/dnsmasq/*.hostsfile r, - - # libvirt pid files for dnsmasq - /{,var/}run/libvirt/network/ r, - /{,var/}run/libvirt/network/*.pid rw, - - # NetworkManager integration - /{,var/}run/nm-dns-dnsmasq.conf r, - /{,var/}run/sendsigs.omit.d/*dnsmasq.pid w, - - # Site-specific additions and overrides. See local/README for details. - #include -} diff --git a/Tools/aa-audit.py b/Tools/aa-audit.py index 5c048031b..5523e0c78 100644 --- a/Tools/aa-audit.py +++ b/Tools/aa-audit.py @@ -12,6 +12,4 @@ args = parser.parse_args() audit = aa_tools('audit', args) -audit.check_profile_dir() - audit.act() diff --git a/Tools/aa-autodep.py b/Tools/aa-autodep.py index 3c03d096f..7e51f9a59 100644 --- a/Tools/aa-autodep.py +++ b/Tools/aa-autodep.py @@ -12,6 +12,4 @@ args = parser.parse_args() autodep = aa_tools('autodep', args) -autodep.check_profile_dir() - autodep.act() \ No newline at end of file diff --git a/Tools/aa-cleanprof.py b/Tools/aa-cleanprof.py index cbe2ead8d..21aa9f85e 100644 --- a/Tools/aa-cleanprof.py +++ b/Tools/aa-cleanprof.py @@ -1,41 +1,14 @@ #!/usr/bin/python -import os import argparse -import apparmor.aa as apparmor +from apparmor.tools import * parser = argparse.ArgumentParser(description='Cleanup the profiles for the given programs') parser.add_argument('-d', type=str, help='path to profiles') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -profiling = args.program -profiledir = args.d +clean = aa_tools('cleanprof', args) -if profiledir: - apparmor.profile_dir = apparmor.get_full_path(profiledir) - if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("%s is not a directory."%profiledir) - -for p in sorted(profiling): - if not p: - continue - - program = None - if os.path.exists(p): - program = apparmor.get_full_path(p).strip() - else: - which = apparmor.which(p) - if which: - program = apparmor.get_full_path(which) - - if os.path.exists(program): - apparmor.read_profiles() - filename = apparmor.get_profile_filename(program) - if filename: - apparmor.write_profile_ui_feedback(program) - apparmor.reload_base(program) - else: - raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) - +clean.act() \ No newline at end of file diff --git a/Tools/aa-complain.py b/Tools/aa-complain.py index ca2f2c841..f6b3902ea 100644 --- a/Tools/aa-complain.py +++ b/Tools/aa-complain.py @@ -12,6 +12,4 @@ args = parser.parse_args() complain = aa_tools('complain', args) -complain.check_profile_dir() - complain.act() diff --git a/Tools/aa-disable.py b/Tools/aa-disable.py index 5fc5dd201..c6c1027f4 100644 --- a/Tools/aa-disable.py +++ b/Tools/aa-disable.py @@ -3,7 +3,6 @@ import argparse from apparmor.tools import * -import apparmor.aa as apparmor parser = argparse.ArgumentParser(description='Disable the profile for the given programs') parser.add_argument('-d', type=str, help='path to profiles') @@ -13,8 +12,5 @@ args = parser.parse_args() disable = aa_tools('disable', args) -disable.check_profile_dir() -disable.check_disable_dir() - disable.act() diff --git a/Tools/aa-enforce.py b/Tools/aa-enforce.py index 33aed3895..a5f68f660 100644 --- a/Tools/aa-enforce.py +++ b/Tools/aa-enforce.py @@ -6,12 +6,12 @@ from apparmor.tools import * parser = argparse.ArgumentParser(description='Switch the given program to enforce mode') parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('-r', '--remove', action='store_true', help='remove enforce mode') +parser.add_argument('-r', '--remove', action='store_true', help='switch to complain mode') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() +# Flipping the remove flag since complain = !enforce +args.remove = not args.remove -enforce = aa_tools('enforce', args) - -enforce.check_profile_dir() +enforce = aa_tools('complain', args) enforce.act() diff --git a/Tools/aa-mergeprof.py b/Tools/aa-mergeprof.py new file mode 100644 index 000000000..e69de29bb diff --git a/Tools/aa-unconfined.py b/Tools/aa-unconfined.py index 35a0cb7f2..8fb1f4a94 100644 --- a/Tools/aa-unconfined.py +++ b/Tools/aa-unconfined.py @@ -47,19 +47,19 @@ for pid in sorted(pids): pname = '(%s)'%pname else: pname = '' + regex_interpreter = re.compile('^(/usr)?/bin/(python|perl|bash|dash|sh)$') if not attr: - if re.search('^(/usr)?/bin/(python|perl|bash|sh)', prog): + if regex_interpreter.search(prog): cmdline = re.sub('\x00', ' ', cmdline) cmdline = re.sub('\s+$', '', cmdline).strip() - if 'perl' in cmdline: - print(cmdline) + apparmor.UI_Info(_('%s %s (%s) not confined\n')%(pid, prog, cmdline)) else: if pname and pname[-1] == ')': pname += ' ' apparmor.UI_Info(_('%s %s %snot confined\n')%(pid, prog, pname)) else: - if re.search('^(/usr)?/bin/(python|perl|bash|sh)', prog): + if regex_interpreter.search(prog): cmdline = re.sub('\0', ' ', cmdline) cmdline = re.sub('\s+$', '', cmdline).strip() apparmor.UI_Info(_("%s %s (%s) confined by '%s'\n")%(pid, prog, cmdline, attr)) diff --git a/apparmor/aa.py b/apparmor/aa.py index 72a916749..377a84232 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -226,16 +226,46 @@ def complain(path): prof_filename, name = name_to_prof_filename(path) if not prof_filename : fatal_error("Can't find %s" % path) - UI_Info('Setting %s to complain mode.' % name) - change_profile_flags(prof_filename, 'complain') - + set_complain(prof_filename, name) + def enforce(path): """Sets the profile to enforce mode if it exists""" prof_filename, name = name_to_prof_filename(path) if not prof_filename : fatal_error("Can't find %s" % path) - UI_Info('Setting %s to enforce mode' % name) - change_profile_flags(prof_filename, 'complain') + set_enforce(prof_filename, name) + +def set_complain(filename, program, ): + """Sets the profile to complain mode""" + UI_Info('Setting %s to complain mode.' % program) + create_symlink('force-complain', filename) + change_profile_flags(filename, 'complain', True) + +def set_enforce(filename, program): + """Sets the profile to enforce mode""" + UI_Info('Setting %s to enforce mode' % program) + delete_symlink('force-complain', filename) + delete_symlink('disable', filename) + change_profile_flags(filename, 'complain', False) + +def delete_symlink(subdir, filename): + path = filename + link = re.sub('^%s'%profile_dir, '%s/%s'%(profile_dir, subdir), path) + if link != path and os.path.islink(link): + os.remove(link) + +def create_symlink(subdir, filename): + path = filename + bname = os.path.basename(filename) + if not bname: + raise AppArmorException(_('Unable to find basename for %s.')%filename) + link = re.sub('^%s'%profile_dir, '%s/%s'%(profile_dir, subdir), path) + link = link + '/%s'%bname + if not os.path.exists(link): + try: + os.symlink(filename, link) + except: + raise AppArmorException('Could not create %s symlink to %s.'%(link, filename)) def head(file): """Returns the first/head line of the file""" @@ -323,13 +353,7 @@ def create_new_profile(localfile): local_profile = hasher() local_profile[localfile]['flags'] = 'complain' local_profile[localfile]['include']['abstractions/base'] = 1 - #local_profile = { - # localfile: { - # 'flags': 'complain', - # 'include': {'abstraction/base': 1}, - # 'allow': {'path': {}} - # } - # } + if os.path.isfile(localfile): hashbang = head(localfile) if hashbang.startswith('#!'): @@ -529,7 +553,7 @@ def get_profile_flags(filename): raise AppArmorException(_('%s contains no profile')%filename) -def change_profile_flags(filename, flag, remove=False): +def change_profile_flags(filename, flag, set_flag): old_flags = get_profile_flags(filename) newflags = [] if old_flags != '': @@ -542,16 +566,13 @@ def change_profile_flags(filename, flag, remove=False): else: newflags = old_flags.split() #newflags = [lambda x:x.strip(), oldflags] - if flag in newflags: - if remove: - newflags.remove(flag) + + if set_flag: + if flag not in newflags: + newflags.append(flag) else: - if not remove: - if flag == '': - if 'complain' in newflags: - newflags.remove('complain') - else: - newflags.append(flag) + if flag in newflags: + newflags.remove(flag) newflags = ','.join(newflags) @@ -1838,30 +1859,7 @@ def ask_the_questions(): elif ans == 'CMD_GLOB': newpath = options[selected].strip() if not re_match_include(newpath): - if newpath[-1] == '/': - if newpath[-4:] == '/**/' or newpath[-3:] == '/*/': - # /foo/**/ and /foo/*/ => /**/ - newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) #re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) - elif re.search('/[^/]+\*\*[^/]*/$', newpath): - # /foo**/ and /foo**bar/ => /**/ - newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath) - elif re.search('/\*\*[^/]+/$', newpath): - # /**bar/ => /**/ - newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath) - else: - newpath = re.sub('/[^/]+/$', '/*/', newpath) - else: - if newpath[-3:] == '/**' or newpath[-2:] == '/*': - # /foo/** and /foo/* => /** - newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) - elif re.search('/[^/]*\*\*[^/]+$', newpath): - # /**foo and /foor**bar => /** - newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath) - elif re.search('/[^/]+\*\*$', newpath): - # /foo** => /** - newpath = re.sub('/[^/]+\*\*$', '/**', newpath) - else: - newpath = re.sub('/[^/]+$', '/*', newpath) + newpath = glob_path(newpath) if newpath not in options: options.append(newpath) @@ -1872,23 +1870,7 @@ def ask_the_questions(): elif ans == 'CMD_GLOBEXT': newpath = options[selected].strip() if not re_match_include(newpath): - # match /**.ext and /*.ext - match = re.search('/\*{1,2}(\.[^/]+)$', newpath) - if match: - # /foo/**.ext and /foo/*.ext => /**.ext - newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.groups()[0], newpath) - elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath): - # /foo**.ext and /foo**bar.ext => /**.ext - match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath) - newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**'+match.groups()[0], newpath) - elif re.search('/\*\*[^/]+\.[^/]+$', newpath): - # /**foo.ext => /**.ext - match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath) - newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**'+match.groups()[0], newpath) - else: - match = re.search('(\.[^/]+)$', newpath) - if match: - newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) + newpath = glob_path_withext(newpath) if newpath not in options: options.append(newpath) @@ -1984,6 +1966,55 @@ def ask_the_questions(): else: done = False +def glob_path(newpath): + """Glob the given file path""" + if newpath[-1] == '/': + if newpath[-4:] == '/**/' or newpath[-3:] == '/*/': + # /foo/**/ and /foo/*/ => /**/ + newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) #re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) + elif re.search('/[^/]+\*\*[^/]*/$', newpath): + # /foo**/ and /foo**bar/ => /**/ + newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath) + elif re.search('/\*\*[^/]+/$', newpath): + # /**bar/ => /**/ + newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath) + else: + newpath = re.sub('/[^/]+/$', '/*/', newpath) + else: + if newpath[-3:] == '/**' or newpath[-2:] == '/*': + # /foo/** and /foo/* => /** + newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) + elif re.search('/[^/]*\*\*[^/]+$', newpath): + # /**foo and /foor**bar => /** + newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath) + elif re.search('/[^/]+\*\*$', newpath): + # /foo** => /** + newpath = re.sub('/[^/]+\*\*$', '/**', newpath) + else: + newpath = re.sub('/[^/]+$', '/*', newpath) + return newpath + +def glob_path_withext(newpath): + """Glob given file path with extension""" + # match /**.ext and /*.ext + match = re.search('/\*{1,2}(\.[^/]+)$', newpath) + if match: + # /foo/**.ext and /foo/*.ext => /**.ext + newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.groups()[0], newpath) + elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath): + # /foo**.ext and /foo**bar.ext => /**.ext + match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath) + newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**'+match.groups()[0], newpath) + elif re.search('/\*\*[^/]+\.[^/]+$', newpath): + # /**foo.ext => /**.ext + match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath) + newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**'+match.groups()[0], newpath) + else: + match = re.search('(\.[^/]+)$', newpath) + if match: + newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) + return newpath + def delete_net_duplicates(netrules, incnetrules): deleted = 0 if incnetrules and netrules: @@ -2426,7 +2457,7 @@ def is_skippable_file(path): return True def is_skippable_dir(path): - if path in ['disable', 'cache', 'force-complain', 'lxc']: + if re.search('(disable|cache|force-complain|lxc)', path): return True return False @@ -2768,6 +2799,9 @@ def parse_profile_data(data, file, do_include): elif re_match_include(line): # Include files include_name = re_match_include(line) + if include_name.startswith('local/'): + profile_data[profile][hat]['localinclude'][include_name] = True + if profile: profile_data[profile][hat]['include'][include_name] = True diff --git a/apparmor/tools.py b/apparmor/tools.py index 36ee17941..aa634b7dc 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -1,5 +1,4 @@ import os -import re import sys import apparmor.aa as apparmor @@ -9,15 +8,19 @@ class aa_tools: self.name = tool_name self.profiledir = args.d self.profiling = args.program + self.check_profile_dir() - if tool_name in ['audit', 'complain', 'enforce']: + if tool_name in ['audit', 'complain']: self.remove = args.remove elif tool_name == 'disable': self.revert = args.revert self.disabledir = apparmor.profile_dir+'/disable' + self.check_disable_dir() elif tool_name == 'autodep': self.force = args.force self.aa_mountpoint = apparmor.check_for_apparmor() + elif tool_name == 'cleanprof': + pass def check_profile_dir(self): if self.profiledir: @@ -41,46 +44,72 @@ class aa_tools: which = apparmor.which(p) if which: program = apparmor.get_full_path(which) - - if os.path.exists(program): + + if not os.path.exists(program): + apparmor.UI_Info(_('The given program cannot be found, please try with the fully qualified path name of the program: ')) + program = apparmor.UI_GetString('', '') + + apparmor.read_profiles() + + if program and apparmor.profile_exists(program):#os.path.exists(program): if self.name == 'autodep': self.use_autodep(program) + elif self.name == 'cleanprof': + self.clean_profile(program, p) + else: - apparmor.read_profiles() filename = apparmor.get_profile_filename(program) if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - continue - - if self.name == 'enforce': - apparmor.UI_Info(_('Setting %s to enforce mode.\n')%program) - apparmor.change_profile_flags(filename, '', self.remove) - #apparmor.set_profile_flags(filename, '') - self.remove_symlinks(filename) + apparmor.UI_Info(_('Profile for %s not found, skipping')%p) elif self.name == 'disable': - apparmor.UI_Info(_('Disabling %s.\n')%program) if not self.revert: + apparmor.UI_Info(_('Disabling %s.\n')%program) self.disable_profile(filename) else: - self.remove_disable_link(filename) - else: - apparmor.UI_Info(_('Setting %s to %s mode.\n')%(program, self.name)) - apparmor.change_profile_flags(filename, self.name, self.remove) - #apparmor.set_profile_flags(filename, self.name) + apparmor.UI_Info(_('Enabling %s.\n')%program) + self.enable_profile(filename) + + elif self.name == 'audit': + if not self.remove: + apparmor.UI_Info(_('Setting %s to audit mode.\n')%program) + else: + apparmor.UI_Info(_('Removing audit mode from %s.\n')%program) + apparmor.change_profile_flags(filename, 'audit', self.remove) - cmd_info = apparmor.cmd(['cat', filename, '|', apparmor.parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + elif self.name == 'complain': + if not self.remove: + apparmor.set_complain(filename, program) + pass + else: + apparmor.set_enforce(filename, program) + #apparmor.set_profile_flags(filename, self.name) + else: + # One simply does not walk in here! + raise apparmor.AppArmorException('Unknown tool.') + + cmd_info = apparmor.cmd([apparmor.parser, filename, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + #cmd_info = apparmor.cmd(['cat', filename, '|', apparmor.parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) if cmd_info[0] != 0: raise apparmor.AppArmorException(cmd_info[1]) else: if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.")%(p, p)) + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.\nPlease use the full path as parameter")%(p, p)) else: apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) sys.exit(1) + + def clean_profile(self, program, p): + filename = apparmor.get_profile_filename(program) + if filename: + apparmor.write_profile_ui_feedback(program) + apparmor.reload_base(program) + else: + raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) def use_autodep(self, program): apparmor.check_qualifiers(program) @@ -91,38 +120,9 @@ class aa_tools: apparmor.autodep(program) if self.aa_mountpoint: apparmor.reload(program) - - def remove_symlinks(self, filename): - # Remove symlink from profile_dir/force-complain - complainlink = filename - complainlink = re.sub('^%s'%apparmor.profile_dir, '%s/force-complain'%apparmor.profile_dir, complainlink) - if os.path.exists(complainlink): - os.remove(complainlink) - - # remove symlink in profile_dir/disable - disablelink = filename - disablelink = re.sub('^%s'%apparmor.profile_dir, '%s/disable'%apparmor.profile_dir, disablelink) - if os.path.exists(disablelink): - os.remove(disablelink) - def remove_disable_link(self, filename): - # Remove the file from disable dir - bname = os.path.basename(filename) - if not bname: - raise apparmor.AppArmorException(_('Unable to find basename for %s.')%filename) - - link = '%s/%s'%(self.disabledir, bname) - if os.path.exists(link): - os.remove(link) + def enable_profile(self, filename): + apparmor.delete_symlink('disable', filename) def disable_profile(self, filename): - bname = os.path.basename(filename) - if not bname: - raise apparmor.AppArmorException(_('Unable to find basename for %s.')%filename) - - link = '%s/%s'%(self.disabledir, bname) - if not os.path.exists(link): - try: - os.symlink(filename, link) - except: - raise apparmor.AppArmorException('Could not create %s symlink to %s.'%(link, filename)) \ No newline at end of file + apparmor.create_symlink('disable', filename) \ No newline at end of file From c7a74802abfc8e9b544f9f8ff7309a0e2d84e59b Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 31 Aug 2013 04:08:26 +0530 Subject: [PATCH 055/183] Tests for minitools aa-disable, aa-audit, aa-complain, aa-enforce added and some minor bugs fixed. Ran all existing test suites on python2 and python3 and tweaked a few things --- Testing/minitools_test.py | 106 ++++++++++++++++++++++++++++++++++---- Testing/severity_test.py | 11 ++-- apparmor/aa.py | 51 +++++++++++------- apparmor/tools.py | 11 ++-- 4 files changed, 139 insertions(+), 40 deletions(-) diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 3bc023cd4..3ab75ca57 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -1,28 +1,112 @@ -import unittest +import atexit +import os import shutil +import subprocess +import unittest + +import apparmor.aa as apparmor class Test(unittest.TestCase): - def setUp(self): - #copy the local profiles to the test directory - shutil.copytree('/etc/apparmor.d/', './profiles/') - def test_audit(self): - pass + #Set ntpd profile to audit mode and check if it was correctly set + subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Audit flag could not be set in profile %s'%local_profilename) + + #Remove audit mode from ntpd profile and check if it was correctly removed + subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles -r ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) + def test_complain(self): - pass + #Set ntpd profile to complain mode and check if it was correctly set + subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) + + #Set ntpd profile to enforce mode and check if it was correctly set + subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles -r ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) + self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) + + # Set audit flag and then complain flag in a profile + subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename) + + #Remove complain flag first i.e. set to enforce mode + subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles -r ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) + self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename) + + #Remove audit flag + subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles -r ntpd', shell=True) def test_enforce(self): - pass + #Set ntpd profile to complain mode and check if it was correctly set + subprocess.check_output('python ./../Tools/aa-enforce.py -d ./profiles -r ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) + + + #Set ntpd profile to enforce mode and check if it was correctly set + subprocess.check_output('python ./../Tools/aa-enforce.py -d ./profiles ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) + self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) + def test_disable(self): - pass + #Disable the ntpd profile and check if it was correctly disabled + subprocess.check_output('python ./../Tools/aa-disable.py -d ./profiles ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in disable'%local_profilename) + + #Enable the ntpd profile and check if it was correctly re-enabled + subprocess.check_output('python ./../Tools/aa-disable.py -d ./profiles -r ntpd', shell=True) + + local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove a symlink for %s from disable'%local_profilename) + def test_autodep(self): pass - + +def clean_profile_dir(): + #Wipe the local profiles from the test directory + shutil.rmtree('./profiles') if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file + + if os.path.exists('./profiles'): + shutil.rmtree('./profiles') + + #copy the local profiles to the test directory + shutil.copytree('/etc/apparmor.d', './profiles', symlinks=True) + + apparmor.profile_dir='./profiles' + + atexit.register(clean_profile_dir) + + unittest.main() diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 0c6163542..3613bc20b 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -1,3 +1,4 @@ +import os import shutil import sys import unittest @@ -11,11 +12,15 @@ class Test(unittest.TestCase): def setUp(self): #copy the local profiles to the test directory - shutil.copytree('/etc/apparmor.d/', './profiles/') + if os.path.exists('./profiles'): + shutil.rmtree('./profiles') + shutil.copytree('/etc/apparmor.d/', './profiles/', symlinks=True) + + def tearDown(self): + #Wipe the local profiles from the test directory + shutil.rmtree('./profiles') def testRank_Test(self): - #z = severity.Severity() - s = severity.Severity('severity.db') rank = s.rank('/usr/bin/whatis', 'x') self.assertEqual(rank, 5, 'Wrong rank') diff --git a/apparmor/aa.py b/apparmor/aa.py index 377a84232..6a0e069c4 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -152,13 +152,14 @@ def check_for_apparmor(): def which(file): """Returns the executable fullpath for the file, None otherwise""" - env_dirs = os.getenv('PATH').split(':') - for env_dir in env_dirs: - env_path = env_dir + '/' + file - # Test if the path is executable or not - if os.access(env_path, os.X_OK): - return env_path - return None + return shutil.which(file) +# env_dirs = os.getenv('PATH').split(':') +# for env_dir in env_dirs: +# env_path = env_dir + '/' + file +# # Test if the path is executable or not +# if os.access(env_path, os.X_OK): +# return env_path +# return None def get_full_path(original_path): """Return the full path after resolving any symlinks""" @@ -237,13 +238,13 @@ def enforce(path): def set_complain(filename, program, ): """Sets the profile to complain mode""" - UI_Info('Setting %s to complain mode.' % program) + UI_Info('Setting %s to complain mode.\n' % program) create_symlink('force-complain', filename) change_profile_flags(filename, 'complain', True) def set_enforce(filename, program): """Sets the profile to enforce mode""" - UI_Info('Setting %s to enforce mode' % program) + UI_Info('Setting %s to enforce mode.\n' % program) delete_symlink('force-complain', filename) delete_symlink('disable', filename) change_profile_flags(filename, 'complain', False) @@ -259,8 +260,16 @@ def create_symlink(subdir, filename): bname = os.path.basename(filename) if not bname: raise AppArmorException(_('Unable to find basename for %s.')%filename) + #print(filename) link = re.sub('^%s'%profile_dir, '%s/%s'%(profile_dir, subdir), path) - link = link + '/%s'%bname + #print(link) + #link = link + '/%s'%bname + #print(link) + symlink_dir=os.path.dirname(link) + if not os.path.exists(symlink_dir): + # If the symlink directory does not exist create it + os.makedirs(symlink_dir) + if not os.path.exists(link): try: os.symlink(filename, link) @@ -502,7 +511,7 @@ def activate_repo_profiles(url, profiles, complain): write_profile(pname) if complain: fname = get_profile_filename(pname) - set_profile_flags(fname, 'complain') + set_profile_flags(profile_dir + fname, 'complain') UI_Info('Setting %s to complain mode.' % pname) except Exception as e: sys.stderr.write("Error activating profiles: %s" % e) @@ -556,7 +565,7 @@ def get_profile_flags(filename): def change_profile_flags(filename, flag, set_flag): old_flags = get_profile_flags(filename) newflags = [] - if old_flags != '': + if old_flags: # Flags maybe white-space and/or , separated old_flags = old_flags.split(',') @@ -575,7 +584,7 @@ def change_profile_flags(filename, flag, set_flag): newflags.remove(flag) newflags = ','.join(newflags) - + set_profile_flags(filename, newflags) def set_profile_flags(prof_filename, newflags): @@ -584,19 +593,21 @@ def set_profile_flags(prof_filename, newflags): regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: - tempfile = tempfile.NamedTemporaryFile('w', prefix=prof_filename , suffix='~', delete=False, dir='/etc/apparmor.d/') - shutil.copymode('/etc/apparmor.d/' + prof_filename, tempfile.name) - with open_file_write(tempfile.name) as f_out: + temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename , suffix='~', delete=False, dir=profile_dir) + shutil.copymode(prof_filename, temp_file.name) + with open_file_write(temp_file.name) as f_out: for line in f_in: comment = '' if '#' in line: comment = '#' + line.split('#', 1)[1].rstrip() match = regex_bin_flag.search(line) - if match: + if not line.strip() or line.strip().startswith('#'): + pass + elif match: matches = match.groups() space = matches[0] binary = matches[1] - flag = matches[6] + flag = matches[6] or 'flags=' flags = matches[7] if newflags: line = '%s%s %s(%s) {%s\n' % (space, binary, flag, newflags, comment) @@ -605,13 +616,13 @@ def set_profile_flags(prof_filename, newflags): else: match = regex_hat_flag.search(line) if match: - hat, flags = match.groups() + hat, flags = match.groups()[:2] if newflags: line = '%s flags=(%s) {%s\n' % (hat, newflags, comment) else: line = '%s {%s\n' % (hat, comment) f_out.write(line) - os.rename(tempfile.name, prof_filename) + os.rename(temp_file.name, prof_filename) def profile_exists(program): """Returns True if profile exists, False otherwise""" diff --git a/apparmor/tools.py b/apparmor/tools.py index aa634b7dc..76448e881 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -45,12 +45,12 @@ class aa_tools: if which: program = apparmor.get_full_path(which) - if not os.path.exists(program): - apparmor.UI_Info(_('The given program cannot be found, please try with the fully qualified path name of the program: ')) - program = apparmor.UI_GetString('', '') + if not program or not os.path.exists(program): + program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') + apparmor.read_profiles() - + if program and apparmor.profile_exists(program):#os.path.exists(program): if self.name == 'autodep': self.use_autodep(program) @@ -77,12 +77,11 @@ class aa_tools: apparmor.UI_Info(_('Setting %s to audit mode.\n')%program) else: apparmor.UI_Info(_('Removing audit mode from %s.\n')%program) - apparmor.change_profile_flags(filename, 'audit', self.remove) + apparmor.change_profile_flags(filename, 'audit', not self.remove) elif self.name == 'complain': if not self.remove: apparmor.set_complain(filename, program) - pass else: apparmor.set_enforce(filename, program) #apparmor.set_profile_flags(filename, self.name) From bdc2677f7b27fdb21cce9d5531afe2cddb575104 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 31 Aug 2013 04:13:05 +0530 Subject: [PATCH 056/183] --- Tools/aa-audit.py | 4 ++-- Tools/aa-autodep.py | 4 ++-- Tools/aa-cleanprof.py | 4 ++-- Tools/aa-complain.py | 4 ++-- Tools/aa-disable.py | 4 ++-- Tools/aa-enforce.py | 4 ++-- Tools/aa-mergeprof.py | 2 ++ apparmor/aa.py | 4 ++-- 8 files changed, 16 insertions(+), 14 deletions(-) diff --git a/Tools/aa-audit.py b/Tools/aa-audit.py index 5523e0c78..91961f109 100644 --- a/Tools/aa-audit.py +++ b/Tools/aa-audit.py @@ -2,7 +2,7 @@ import argparse -from apparmor.tools import * +import apparmor.tools parser = argparse.ArgumentParser(description='Switch the given programs to audit mode') parser.add_argument('-d', type=str, help='path to profiles') @@ -10,6 +10,6 @@ parser.add_argument('-r', '--remove', action='store_true', help='remove audit mo parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -audit = aa_tools('audit', args) +audit = apparmor.tools.aa_tools('audit', args) audit.act() diff --git a/Tools/aa-autodep.py b/Tools/aa-autodep.py index 7e51f9a59..2619f1919 100644 --- a/Tools/aa-autodep.py +++ b/Tools/aa-autodep.py @@ -2,7 +2,7 @@ import argparse -from apparmor.tools import * +import apparmor.tools parser = argparse.ArgumentParser(description='') parser.add_argument('--force', type=str, help='path to profiles') @@ -10,6 +10,6 @@ parser.add_argument('-d', type=str, help='path to profiles') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -autodep = aa_tools('autodep', args) +autodep = apparmor.tools.aa_tools('autodep', args) autodep.act() \ No newline at end of file diff --git a/Tools/aa-cleanprof.py b/Tools/aa-cleanprof.py index 21aa9f85e..042f6cbc0 100644 --- a/Tools/aa-cleanprof.py +++ b/Tools/aa-cleanprof.py @@ -2,13 +2,13 @@ import argparse -from apparmor.tools import * +import apparmor.tools parser = argparse.ArgumentParser(description='Cleanup the profiles for the given programs') parser.add_argument('-d', type=str, help='path to profiles') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -clean = aa_tools('cleanprof', args) +clean = apparmor.tools.aa_tools('cleanprof', args) clean.act() \ No newline at end of file diff --git a/Tools/aa-complain.py b/Tools/aa-complain.py index f6b3902ea..940d97874 100644 --- a/Tools/aa-complain.py +++ b/Tools/aa-complain.py @@ -2,7 +2,7 @@ import argparse -from apparmor.tools import * +import apparmor.tools parser = argparse.ArgumentParser(description='Switch the given program to complain mode') parser.add_argument('-d', type=str, help='path to profiles') @@ -10,6 +10,6 @@ parser.add_argument('-r', '--remove', action='store_true', help='remove complain parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -complain = aa_tools('complain', args) +complain = apparmor.tools.aa_tools('complain', args) complain.act() diff --git a/Tools/aa-disable.py b/Tools/aa-disable.py index c6c1027f4..c27cd71a5 100644 --- a/Tools/aa-disable.py +++ b/Tools/aa-disable.py @@ -2,7 +2,7 @@ import argparse -from apparmor.tools import * +import apparmor.tools parser = argparse.ArgumentParser(description='Disable the profile for the given programs') parser.add_argument('-d', type=str, help='path to profiles') @@ -10,7 +10,7 @@ parser.add_argument('-r', '--revert', action='store_true', help='enable the prof parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() -disable = aa_tools('disable', args) +disable = apparmor.tools.aa_tools('disable', args) disable.act() diff --git a/Tools/aa-enforce.py b/Tools/aa-enforce.py index a5f68f660..a1a2f7976 100644 --- a/Tools/aa-enforce.py +++ b/Tools/aa-enforce.py @@ -2,7 +2,7 @@ import argparse -from apparmor.tools import * +import apparmor.tools parser = argparse.ArgumentParser(description='Switch the given program to enforce mode') parser.add_argument('-d', type=str, help='path to profiles') @@ -12,6 +12,6 @@ args = parser.parse_args() # Flipping the remove flag since complain = !enforce args.remove = not args.remove -enforce = aa_tools('complain', args) +enforce = apparmor.tools.aa_tools('complain', args) enforce.act() diff --git a/Tools/aa-mergeprof.py b/Tools/aa-mergeprof.py index e69de29bb..8d5063a78 100644 --- a/Tools/aa-mergeprof.py +++ b/Tools/aa-mergeprof.py @@ -0,0 +1,2 @@ +#!/usr/bin/python + diff --git a/apparmor/aa.py b/apparmor/aa.py index 6a0e069c4..821cfa303 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -18,14 +18,14 @@ import apparmor.logparser import apparmor.severity import LibAppArmor +from copy import deepcopy + from apparmor.common import (AppArmorException, error, debug, msg, cmd, open_file_read, valid_path, hasher, open_file_write, convert_regexp, DebugLogger) from apparmor.ui import * -from copy import deepcopy - from apparmor.aamode import * # Setup logging incase of debugging is enabled From 2ce5fd6267b325c0dd927d04f2e36dd22a214050 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 31 Aug 2013 17:48:40 +0530 Subject: [PATCH 057/183] Renamed tools to get rid of the .py extension and fixed the same in minitools_test --- Testing/minitools_test.py | 24 +++++++++++------------ Tools/{aa-audit.py => aa-audit} | 0 Tools/{aa-autodep.py => aa-autodep} | 0 Tools/{aa-cleanprof.py => aa-cleanprof} | 0 Tools/{aa-complain.py => aa-complain} | 0 Tools/{aa-disable.py => aa-disable} | 0 Tools/{aa-enforce.py => aa-enforce} | 0 Tools/{aa-genprof.py => aa-genprof} | 0 Tools/{aa-logprof.py => aa-logprof} | 0 Tools/{aa-mergeprof.py => aa-mergeprof} | 0 Tools/{aa-unconfined.py => aa-unconfined} | 0 11 files changed, 12 insertions(+), 12 deletions(-) rename Tools/{aa-audit.py => aa-audit} (100%) rename Tools/{aa-autodep.py => aa-autodep} (100%) rename Tools/{aa-cleanprof.py => aa-cleanprof} (100%) rename Tools/{aa-complain.py => aa-complain} (100%) rename Tools/{aa-disable.py => aa-disable} (100%) rename Tools/{aa-enforce.py => aa-enforce} (100%) rename Tools/{aa-genprof.py => aa-genprof} (100%) rename Tools/{aa-logprof.py => aa-logprof} (100%) rename Tools/{aa-mergeprof.py => aa-mergeprof} (100%) rename Tools/{aa-unconfined.py => aa-unconfined} (100%) diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 3ab75ca57..87decc94a 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -10,13 +10,13 @@ class Test(unittest.TestCase): def test_audit(self): #Set ntpd profile to audit mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-audit -d ./profiles ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Audit flag could not be set in profile %s'%local_profilename) #Remove audit mode from ntpd profile and check if it was correctly removed - subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles -r ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-audit -d ./profiles -r ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) @@ -24,14 +24,14 @@ class Test(unittest.TestCase): def test_complain(self): #Set ntpd profile to complain mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-complain -d ./profiles ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) #Set ntpd profile to enforce mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles -r ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-complain -d ./profiles -r ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) @@ -39,15 +39,15 @@ class Test(unittest.TestCase): self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) # Set audit flag and then complain flag in a profile - subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles ntpd', shell=True) - subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-audit -d ./profiles ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-complain -d ./profiles ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename) #Remove complain flag first i.e. set to enforce mode - subprocess.check_output('python ./../Tools/aa-complain.py -d ./profiles -r ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-complain -d ./profiles -r ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) @@ -55,11 +55,11 @@ class Test(unittest.TestCase): self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename) #Remove audit flag - subprocess.check_output('python ./../Tools/aa-audit.py -d ./profiles -r ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-audit -d ./profiles -r ntpd', shell=True) def test_enforce(self): #Set ntpd profile to complain mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-enforce.py -d ./profiles -r ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-enforce -d ./profiles -r ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) @@ -67,7 +67,7 @@ class Test(unittest.TestCase): #Set ntpd profile to enforce mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-enforce.py -d ./profiles ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-enforce -d ./profiles ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) @@ -77,13 +77,13 @@ class Test(unittest.TestCase): def test_disable(self): #Disable the ntpd profile and check if it was correctly disabled - subprocess.check_output('python ./../Tools/aa-disable.py -d ./profiles ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-disable -d ./profiles ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in disable'%local_profilename) #Enable the ntpd profile and check if it was correctly re-enabled - subprocess.check_output('python ./../Tools/aa-disable.py -d ./profiles -r ntpd', shell=True) + subprocess.check_output('python ./../Tools/aa-disable -d ./profiles -r ntpd', shell=True) local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove a symlink for %s from disable'%local_profilename) diff --git a/Tools/aa-audit.py b/Tools/aa-audit similarity index 100% rename from Tools/aa-audit.py rename to Tools/aa-audit diff --git a/Tools/aa-autodep.py b/Tools/aa-autodep similarity index 100% rename from Tools/aa-autodep.py rename to Tools/aa-autodep diff --git a/Tools/aa-cleanprof.py b/Tools/aa-cleanprof similarity index 100% rename from Tools/aa-cleanprof.py rename to Tools/aa-cleanprof diff --git a/Tools/aa-complain.py b/Tools/aa-complain similarity index 100% rename from Tools/aa-complain.py rename to Tools/aa-complain diff --git a/Tools/aa-disable.py b/Tools/aa-disable similarity index 100% rename from Tools/aa-disable.py rename to Tools/aa-disable diff --git a/Tools/aa-enforce.py b/Tools/aa-enforce similarity index 100% rename from Tools/aa-enforce.py rename to Tools/aa-enforce diff --git a/Tools/aa-genprof.py b/Tools/aa-genprof similarity index 100% rename from Tools/aa-genprof.py rename to Tools/aa-genprof diff --git a/Tools/aa-logprof.py b/Tools/aa-logprof similarity index 100% rename from Tools/aa-logprof.py rename to Tools/aa-logprof diff --git a/Tools/aa-mergeprof.py b/Tools/aa-mergeprof similarity index 100% rename from Tools/aa-mergeprof.py rename to Tools/aa-mergeprof diff --git a/Tools/aa-unconfined.py b/Tools/aa-unconfined similarity index 100% rename from Tools/aa-unconfined.py rename to Tools/aa-unconfined From 2763f0c0649f5e831d50168645eb64d35527e12d Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 12 Sep 2013 14:42:15 +0530 Subject: [PATCH 058/183] Updated __init_.py tested with de_DE and hi_IN translations using old apparmor-utils.mo file, not pushing remainder of files for their lack of beauty --- Testing/aa_test.py | 13 +- Testing/minitools_test.py | 51 ++-- Testing/runtests-py2.sh | 1 + Testing/runtests-py3.sh | 1 + Tools/aa-autodep | 2 +- Tools/aa-mergeprof | 512 ++++++++++++++++++++++++++++++++++++++ apparmor/__init__.py | 3 +- apparmor/aa.py | 27 +- apparmor/tools.py | 85 ++++++- 9 files changed, 650 insertions(+), 45 deletions(-) create mode 100644 Testing/runtests-py2.sh create mode 100644 Testing/runtests-py3.sh diff --git a/Testing/aa_test.py b/Testing/aa_test.py index 16b12f6c1..cfa38a212 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -41,12 +41,17 @@ class Test(unittest.TestCase): '/usr/foo/*': '/usr/**', '/usr/foo/**': '/usr/**', '/usr/foo/bar**': '/usr/foo/**', - '/usr/foo/**barr': '/usr/foo/**', + '/usr/foo/**bar': '/usr/foo/**', + '/usr/bin/foo**bar': '/usr/bin/**', '/usr/foo/**/bar': '/usr/foo/**/*', '/usr/foo/**/*': '/usr/foo/**', '/usr/foo/*/bar': '/usr/foo/*/*', + '/usr/bin/foo*bar': '/usr/bin/*', + '/usr/bin/*foo*': '/usr/bin/*', '/usr/foo/*/*': '/usr/foo/**', - '/usr/foo/*/**': '/usr/foo/**' + '/usr/foo/*/**': '/usr/foo/**', + '/**': '/**', + '/**/': '/**/' } for path in globs.keys(): self.assertEqual(apparmor.aa.glob_path(path), globs[path], 'Unexpected glob generated for path: %s'%path) @@ -61,9 +66,11 @@ class Test(unittest.TestCase): '/usr/**.bar': '/**.bar', '/usr/foo**.bar': '/usr/**.bar', '/usr/foo*.bar': '/usr/*.bar', + '/usr/fo*oo.bar': '/usr/*.bar', + '/usr/*foo*.bar': '/usr/*.bar', '/usr/**foo.bar': '/usr/**.bar', '/usr/*foo.bar': '/usr/*.bar', - '/usr/foo.b*': '/usr/*.b*' + '/usr/foo.b*': '/usr/*.b*' } for path in globs.keys(): self.assertEqual(apparmor.aa.glob_path_withext(path), globs[path], 'Unexpected glob generated for path: %s'%path) diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 87decc94a..1497f519a 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -2,74 +2,74 @@ import atexit import os import shutil import subprocess +import sys import unittest import apparmor.aa as apparmor -class Test(unittest.TestCase): +test_path = '/usr/sbin/ntpd' +local_profilename = None +python_interpreter = 'python' +if sys.version_info >= (3,0): + python_interpreter = 'python3' + +class Test(unittest.TestCase): + def test_audit(self): #Set ntpd profile to audit mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-audit -d ./profiles ntpd', shell=True) - - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) + subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles ntpd'%python_interpreter, shell=True) + local_profilename = apparmor.get_profile_filename(test_path) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Audit flag could not be set in profile %s'%local_profilename) - + #Remove audit mode from ntpd profile and check if it was correctly removed - subprocess.check_output('python ./../Tools/aa-audit -d ./profiles -r ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) def test_complain(self): #Set ntpd profile to complain mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-complain -d ./profiles ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) #Set ntpd profile to enforce mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-complain -d ./profiles -r ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) # Set audit flag and then complain flag in a profile - subprocess.check_output('python ./../Tools/aa-audit -d ./profiles ntpd', shell=True) - subprocess.check_output('python ./../Tools/aa-complain -d ./profiles ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename) #Remove complain flag first i.e. set to enforce mode - subprocess.check_output('python ./../Tools/aa-complain -d ./profiles -r ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename) #Remove audit flag - subprocess.check_output('python ./../Tools/aa-audit -d ./profiles -r ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r ntpd'%python_interpreter, shell=True) def test_enforce(self): #Set ntpd profile to complain mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-enforce -d ./profiles -r ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles -r ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) #Set ntpd profile to enforce mode and check if it was correctly set - subprocess.check_output('python ./../Tools/aa-enforce -d ./profiles ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) @@ -77,15 +77,13 @@ class Test(unittest.TestCase): def test_disable(self): #Disable the ntpd profile and check if it was correctly disabled - subprocess.check_output('python ./../Tools/aa-disable -d ./profiles ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in disable'%local_profilename) #Enable the ntpd profile and check if it was correctly re-enabled - subprocess.check_output('python ./../Tools/aa-disable -d ./profiles -r ntpd', shell=True) + subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles -r ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(apparmor.get_full_path(apparmor.which('ntpd'))) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove a symlink for %s from disable'%local_profilename) @@ -107,6 +105,9 @@ if __name__ == "__main__": apparmor.profile_dir='./profiles' + # Get the profile name for the test profile using current directory/path settings + local_profilename = apparmor.get_profile_filename(test_path) + atexit.register(clean_profile_dir) unittest.main() diff --git a/Testing/runtests-py2.sh b/Testing/runtests-py2.sh new file mode 100644 index 000000000..8c9f77014 --- /dev/null +++ b/Testing/runtests-py2.sh @@ -0,0 +1 @@ +for file in *.py ; do echo "running $file..." ; python $file; echo; done diff --git a/Testing/runtests-py3.sh b/Testing/runtests-py3.sh new file mode 100644 index 000000000..33645bb8b --- /dev/null +++ b/Testing/runtests-py3.sh @@ -0,0 +1 @@ +for file in *.py ; do echo "running $file..." ; python3 $file; echo; done diff --git a/Tools/aa-autodep b/Tools/aa-autodep index 2619f1919..8c0d80baa 100644 --- a/Tools/aa-autodep +++ b/Tools/aa-autodep @@ -5,7 +5,7 @@ import argparse import apparmor.tools parser = argparse.ArgumentParser(description='') -parser.add_argument('--force', type=str, help='path to profiles') +parser.add_argument('--force', type=str, help='override existing profile') parser.add_argument('-d', type=str, help='path to profiles') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 8d5063a78..b40c7c991 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -1,2 +1,514 @@ #!/usr/bin/python +import argparse +import sys + +import apparmor.aa as apparmor + +parser = argparse.ArgumentParser(description='Perform a 3way merge on the given profiles') +##parser.add_argument('profiles', type=str, nargs=3, help='MINE BASE OTHER') +parser.add_argument('mine', type=str, help='Your profile') +parser.add_argument('base', type=str, help='The base profile') +parser.add_argument('other', type=str, help='Other profile') +parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-auto', action='store_true', help='Automatically merge profiles, exits incase of *x conflicts') +args = parser.parse_args() + +profiles = [args.mine, args.base, args.other] + +if __name__ == '__main__': + main() + +print(profiles) + +def main(): + mergeprofiles = Merge(profiles) + +class Merge(object): + def __init__(self, profiles): + user, base, other = profiles + + #Read and parse base profile and save profile data, include data from it and reset them + apparmor.read_profile(base, True) + self.base_aa = apparmor.aa + self.base_filelist = apparmor.filelist + self.base_include = apparmor.include + reset() + + #Read and parse other profile and save profile data, include data from it and reset them + apparmor.read_profile(other, True) + self.other_aa = apparmor.aa + self.other_filelist = apparmor.filelist + self.other_include = apparmor.include + reset() + + #Read and parse user profile + apparmor.read_profile(profiles[0], True) + #user_aa = apparmor.aa + #user_filelist = apparmor.filelist + #user_include = apparmor.include + + def reset(): + apparmor.aa = apparmor.hasher() + apparmor.filelist = hasher() + apparmor.include = dict() + apparmor.existing_profiles = hasher() + apparmor.original_aa = hasher() + + def clear_common(self): + common_base_other() + remove_common('base') + remove_common('other') + + def common_base_other(self): + pass + + def remove_common(self, profile): + if prof1 == 'base': + + +# def intersect(ra, rb): +# """Given two ranges return the range where they intersect or None. +# +# >>> intersect((0, 10), (0, 6)) +# (0, 6) +# >>> intersect((0, 10), (5, 15)) +# (5, 10) +# >>> intersect((0, 10), (10, 15)) +# >>> intersect((0, 9), (10, 15)) +# >>> intersect((0, 9), (7, 15)) +# (7, 9) +# """ +# # preconditions: (ra[0] <= ra[1]) and (rb[0] <= rb[1]) +# +# sa = max(ra[0], rb[0]) +# sb = min(ra[1], rb[1]) +# if sa < sb: +# return sa, sb +# else: +# return None +# +# +# def compare_range(a, astart, aend, b, bstart, bend): +# """Compare a[astart:aend] == b[bstart:bend], without slicing. +# """ +# if (aend-astart) != (bend-bstart): +# return False +# for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)): +# if a[ia] != b[ib]: +# return False +# else: +# return True +# +# +# +# +# class Merge3(object): +# """3-way merge of texts. +# +# Given BASE, OTHER, THIS, tries to produce a combined text +# incorporating the changes from both BASE->OTHER and BASE->THIS. +# All three will typically be sequences of lines.""" +# +# def __init__(self, base, a, b, is_cherrypick=False, allow_objects=False): +# """Constructor. +# +# :param base: lines in BASE +# :param a: lines in A +# :param b: lines in B +# :param is_cherrypick: flag indicating if this merge is a cherrypick. +# When cherrypicking b => a, matches with b and base do not conflict. +# :param allow_objects: if True, do not require that base, a and b are +# plain Python strs. Also prevents BinaryFile from being raised. +# Lines can be any sequence of comparable and hashable Python +# objects. +# """ +# if not allow_objects: +# #textfile.check_text_lines(base) +# #textfile.check_text_lines(a) +# #textfile.check_text_lines(b) +# pass +# self.base = base +# self.a = a +# self.b = b +# self.is_cherrypick = is_cherrypick +# +# def merge_lines(self, +# name_a=None, +# name_b=None, +# name_base=None, +# start_marker='<<<<<<<', +# mid_marker='=======', +# end_marker='>>>>>>>', +# base_marker=None, +# reprocess=False): +# """Return merge in cvs-like form. +# """ +# newline = '\n' +# if len(self.a) > 0: +# if self.a[0].endswith('\r\n'): +# newline = '\r\n' +# elif self.a[0].endswith('\r'): +# newline = '\r' +# if base_marker and reprocess: +# raise errors.CantReprocessAndShowBase() +# if name_a: +# start_marker = start_marker + ' ' + name_a +# if name_b: +# end_marker = end_marker + ' ' + name_b +# if name_base and base_marker: +# base_marker = base_marker + ' ' + name_base +# merge_regions = self.merge_regions() +# if reprocess is True: +# merge_regions = self.reprocess_merge_regions(merge_regions) +# for t in merge_regions: +# what = t[0] +# if what == 'unchanged': +# for i in range(t[1], t[2]): +# yield self.base[i] +# elif what == 'a' or what == 'same': +# for i in range(t[1], t[2]): +# yield self.a[i] +# elif what == 'b': +# for i in range(t[1], t[2]): +# yield self.b[i] +# elif what == 'conflict': +# yield start_marker + newline +# for i in range(t[3], t[4]): +# yield self.a[i] +# if base_marker is not None: +# yield base_marker + newline +# for i in range(t[1], t[2]): +# yield self.base[i] +# yield mid_marker + newline +# for i in range(t[5], t[6]): +# yield self.b[i] +# yield end_marker + newline +# else: +# raise ValueError(what) +# +# def merge_annotated(self): +# """Return merge with conflicts, showing origin of lines. +# +# Most useful for debugging merge. +# """ +# for t in self.merge_regions(): +# what = t[0] +# if what == 'unchanged': +# for i in range(t[1], t[2]): +# yield 'u | ' + self.base[i] +# elif what == 'a' or what == 'same': +# for i in range(t[1], t[2]): +# yield what[0] + ' | ' + self.a[i] +# elif what == 'b': +# for i in range(t[1], t[2]): +# yield 'b | ' + self.b[i] +# elif what == 'conflict': +# yield '<<<<\n' +# for i in range(t[3], t[4]): +# yield 'A | ' + self.a[i] +# yield '----\n' +# for i in range(t[5], t[6]): +# yield 'B | ' + self.b[i] +# yield '>>>>\n' +# else: +# raise ValueError(what) +# +# def merge_groups(self): +# """Yield sequence of line groups. Each one is a tuple: +# +# 'unchanged', lines +# Lines unchanged from base +# +# 'a', lines +# Lines taken from a +# +# 'same', lines +# Lines taken from a (and equal to b) +# +# 'b', lines +# Lines taken from b +# +# 'conflict', base_lines, a_lines, b_lines +# Lines from base were changed to either a or b and conflict. +# """ +# for t in self.merge_regions(): +# what = t[0] +# if what == 'unchanged': +# yield what, self.base[t[1]:t[2]] +# elif what == 'a' or what == 'same': +# yield what, self.a[t[1]:t[2]] +# elif what == 'b': +# yield what, self.b[t[1]:t[2]] +# elif what == 'conflict': +# yield (what, +# self.base[t[1]:t[2]], +# self.a[t[3]:t[4]], +# self.b[t[5]:t[6]]) +# else: +# raise ValueError(what) +# +# def merge_regions(self): +# """Return sequences of matching and conflicting regions. +# +# This returns tuples, where the first value says what kind we +# have: +# +# 'unchanged', start, end +# Take a region of base[start:end] +# +# 'same', astart, aend +# b and a are different from base but give the same result +# +# 'a', start, end +# Non-clashing insertion from a[start:end] +# +# Method is as follows: +# +# The two sequences align only on regions which match the base +# and both descendents. These are found by doing a two-way diff +# of each one against the base, and then finding the +# intersections between those regions. These "sync regions" +# are by definition unchanged in both and easily dealt with. +# +# The regions in between can be in any of three cases: +# conflicted, or changed on only one side. +# """ +# +# # section a[0:ia] has been disposed of, etc +# iz = ia = ib = 0 +# +# for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions(): +# matchlen = zend - zmatch +# # invariants: +# # matchlen >= 0 +# # matchlen == (aend - amatch) +# # matchlen == (bend - bmatch) +# len_a = amatch - ia +# len_b = bmatch - ib +# len_base = zmatch - iz +# # invariants: +# # assert len_a >= 0 +# # assert len_b >= 0 +# # assert len_base >= 0 +# +# #print 'unmatched a=%d, b=%d' % (len_a, len_b) +# +# if len_a or len_b: +# # try to avoid actually slicing the lists +# same = compare_range(self.a, ia, amatch, +# self.b, ib, bmatch) +# +# if same: +# yield 'same', ia, amatch +# else: +# equal_a = compare_range(self.a, ia, amatch, +# self.base, iz, zmatch) +# equal_b = compare_range(self.b, ib, bmatch, +# self.base, iz, zmatch) +# if equal_a and not equal_b: +# yield 'b', ib, bmatch +# elif equal_b and not equal_a: +# yield 'a', ia, amatch +# elif not equal_a and not equal_b: +# if self.is_cherrypick: +# for node in self._refine_cherrypick_conflict( +# iz, zmatch, ia, amatch, +# ib, bmatch): +# yield node +# else: +# yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch +# else: +# raise AssertionError("can't handle a=b=base but unmatched") +# +# ia = amatch +# ib = bmatch +# iz = zmatch +# +# # if the same part of the base was deleted on both sides +# # that's OK, we can just skip it. +# +# if matchlen > 0: +# # invariants: +# # assert ia == amatch +# # assert ib == bmatch +# # assert iz == zmatch +# +# yield 'unchanged', zmatch, zend +# iz = zend +# ia = aend +# ib = bend +# +# def _refine_cherrypick_conflict(self, zstart, zend, astart, aend, bstart, bend): +# """When cherrypicking b => a, ignore matches with b and base.""" +# # Do not emit regions which match, only regions which do not match +# matches = patiencediff.PatienceSequenceMatcher(None, +# self.base[zstart:zend], self.b[bstart:bend]).get_matching_blocks() +# last_base_idx = 0 +# last_b_idx = 0 +# last_b_idx = 0 +# yielded_a = False +# for base_idx, b_idx, match_len in matches: +# conflict_z_len = base_idx - last_base_idx +# conflict_b_len = b_idx - last_b_idx +# if conflict_b_len == 0: # There are no lines in b which conflict, +# # so skip it +# pass +# else: +# if yielded_a: +# yield ('conflict', +# zstart + last_base_idx, zstart + base_idx, +# aend, aend, bstart + last_b_idx, bstart + b_idx) +# else: +# # The first conflict gets the a-range +# yielded_a = True +# yield ('conflict', zstart + last_base_idx, zstart + +# base_idx, +# astart, aend, bstart + last_b_idx, bstart + b_idx) +# last_base_idx = base_idx + match_len +# last_b_idx = b_idx + match_len +# if last_base_idx != zend - zstart or last_b_idx != bend - bstart: +# if yielded_a: +# yield ('conflict', zstart + last_base_idx, zstart + base_idx, +# aend, aend, bstart + last_b_idx, bstart + b_idx) +# else: +# # The first conflict gets the a-range +# yielded_a = True +# yield ('conflict', zstart + last_base_idx, zstart + base_idx, +# astart, aend, bstart + last_b_idx, bstart + b_idx) +# if not yielded_a: +# yield ('conflict', zstart, zend, astart, aend, bstart, bend) +# +# def reprocess_merge_regions(self, merge_regions): +# """Where there are conflict regions, remove the agreed lines. +# +# Lines where both A and B have made the same changes are +# eliminated. +# """ +# for region in merge_regions: +# if region[0] != "conflict": +# yield region +# continue +# type, iz, zmatch, ia, amatch, ib, bmatch = region +# a_region = self.a[ia:amatch] +# b_region = self.b[ib:bmatch] +# matches = patiencediff.PatienceSequenceMatcher( +# None, a_region, b_region).get_matching_blocks() +# next_a = ia +# next_b = ib +# for region_ia, region_ib, region_len in matches[:-1]: +# region_ia += ia +# region_ib += ib +# reg = self.mismatch_region(next_a, region_ia, next_b, +# region_ib) +# if reg is not None: +# yield reg +# yield 'same', region_ia, region_len+region_ia +# next_a = region_ia + region_len +# next_b = region_ib + region_len +# reg = self.mismatch_region(next_a, amatch, next_b, bmatch) +# if reg is not None: +# yield reg +# +# @staticmethod +# def mismatch_region(next_a, region_ia, next_b, region_ib): +# if next_a < region_ia or next_b < region_ib: +# return 'conflict', None, None, next_a, region_ia, next_b, region_ib +# +# def find_sync_regions(self): +# """Return a list of sync regions, where both descendents match the base. +# +# Generates a list of (base1, base2, a1, a2, b1, b2). There is +# always a zero-length sync region at the end of all the files. +# """ +# +# ia = ib = 0 +# amatches = patiencediff.PatienceSequenceMatcher( +# None, self.base, self.a).get_matching_blocks() +# bmatches = patiencediff.PatienceSequenceMatcher( +# None, self.base, self.b).get_matching_blocks() +# len_a = len(amatches) +# len_b = len(bmatches) +# +# sl = [] +# +# while ia < len_a and ib < len_b: +# abase, amatch, alen = amatches[ia] +# bbase, bmatch, blen = bmatches[ib] +# +# # there is an unconflicted block at i; how long does it +# # extend? until whichever one ends earlier. +# i = intersect((abase, abase+alen), (bbase, bbase+blen)) +# if i: +# intbase = i[0] +# intend = i[1] +# intlen = intend - intbase +# +# # found a match of base[i[0], i[1]]; this may be less than +# # the region that matches in either one +# # assert intlen <= alen +# # assert intlen <= blen +# # assert abase <= intbase +# # assert bbase <= intbase +# +# asub = amatch + (intbase - abase) +# bsub = bmatch + (intbase - bbase) +# aend = asub + intlen +# bend = bsub + intlen +# +# # assert self.base[intbase:intend] == self.a[asub:aend], \ +# # (self.base[intbase:intend], self.a[asub:aend]) +# # assert self.base[intbase:intend] == self.b[bsub:bend] +# +# sl.append((intbase, intend, +# asub, aend, +# bsub, bend)) +# # advance whichever one ends first in the base text +# if (abase + alen) < (bbase + blen): +# ia += 1 +# else: +# ib += 1 +# +# intbase = len(self.base) +# abase = len(self.a) +# bbase = len(self.b) +# sl.append((intbase, intbase, abase, abase, bbase, bbase)) +# +# return sl +# +# def find_unconflicted(self): +# """Return a list of ranges in base that are not conflicted.""" +# am = patiencediff.PatienceSequenceMatcher( +# None, self.base, self.a).get_matching_blocks() +# bm = patiencediff.PatienceSequenceMatcher( +# None, self.base, self.b).get_matching_blocks() +# +# unc = [] +# +# while am and bm: +# # there is an unconflicted block at i; how long does it +# # extend? until whichever one ends earlier. +# a1 = am[0][0] +# a2 = a1 + am[0][2] +# b1 = bm[0][0] +# b2 = b1 + bm[0][2] +# i = intersect((a1, a2), (b1, b2)) +# if i: +# unc.append(i) +# +# if a2 < b2: +# del am[0] +# else: +# del bm[0] +# +# return unc +# +# a = file(profiles[0], 'rt').readlines() +# base = file(profiles[1], 'rt').readlines() +# b = file(profiles[2], 'rt').readlines() +# +# m3 = Merge3(base, a, b) +# +# sys.stdout.write(m3.merge_annotated()) + + diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 813e6db3c..4743fa3fd 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -5,10 +5,11 @@ Created on Jun 27, 2013 ''' import gettext import locale + def init_localisation(): locale.setlocale(locale.LC_ALL, '') #cur_locale = locale.getlocale() - filename = 'res/messages_%s.mo' % locale.getlocale()[0][0:2] + filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0][0:2] try: trans = gettext.GNUTranslations(open( filename, 'rb')) except IOError: diff --git a/apparmor/aa.py b/apparmor/aa.py index 821cfa303..0091a1a79 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -152,14 +152,15 @@ def check_for_apparmor(): def which(file): """Returns the executable fullpath for the file, None otherwise""" - return shutil.which(file) -# env_dirs = os.getenv('PATH').split(':') -# for env_dir in env_dirs: -# env_path = env_dir + '/' + file -# # Test if the path is executable or not -# if os.access(env_path, os.X_OK): -# return env_path -# return None + if sys.version_info >= (3,3): + return shutil.which(file) + env_dirs = os.getenv('PATH').split(':') + for env_dir in env_dirs: + env_path = env_dir + '/' + file + # Test if the path is executable or not + if os.access(env_path, os.X_OK): + return env_path + return None def get_full_path(original_path): """Return the full path after resolving any symlinks""" @@ -3975,8 +3976,12 @@ def load_include(incname): data = get_include_data(incfile) incdata = parse_profile_data(data, incfile, True) #print(incdata) - if incdata: - attach_profile_data(include, incdata) + if not incdata: + # If include is empty, simply push in a placeholder for it + # because other profiles may mention them + incdata = hasher() + incdata[incname] = hasher() + attach_profile_data(include, incdata) return 0 @@ -4006,7 +4011,7 @@ def match_include_to_path(incname, allow, path): ret = load_include(incfile) if not include.get(incfile,{}): continue - cm, am , m = rematchfrag(include[incfile].get(incfile, {}), allow, path) + cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path) #print(incfile, cm, am, m) if cm: combinedmode |= cm diff --git a/apparmor/tools.py b/apparmor/tools.py index 76448e881..5135af630 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -45,10 +45,14 @@ class aa_tools: if which: program = apparmor.get_full_path(which) - if not program or not os.path.exists(program): - program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') + if (not program or not os.path.exists(program)): + if not program.startswith('/'): + program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') + else: + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%program) + sys.exit(1) - + #apparmor.loadincludes() apparmor.read_profiles() if program and apparmor.profile_exists(program):#os.path.exists(program): @@ -87,7 +91,7 @@ class aa_tools: #apparmor.set_profile_flags(filename, self.name) else: # One simply does not walk in here! - raise apparmor.AppArmorException('Unknown tool.') + raise apparmor.AppArmorException('Unknown tool: %s'%self.name) cmd_info = apparmor.cmd([apparmor.parser, filename, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) #cmd_info = apparmor.cmd(['cat', filename, '|', apparmor.parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) @@ -104,12 +108,85 @@ class aa_tools: def clean_profile(self, program, p): filename = apparmor.get_profile_filename(program) + self.delete_superluous_rules(program) if filename: apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) else: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) + + def delete_superluous_rules(self, program): + #print(filename, apparmor.aa.get(program, False)) + #print(apparmor.aa[program][program]['include']) + includes = apparmor.aa[program][program]['include'].keys() + allow_path_rules = list(apparmor.aa[program][program]['allow']['path'].keys()) + allow_net_rules = list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) + #b=set(allow_rules) + #print(allow_rules) + dele = 0 + #print(includes) + #Clean up superfluous rules from includes + #print(apparmor.include.keys()) + + for inc in includes: + #old=dele + if not apparmor.include.get(inc, False): + apparmor.load_include(inc) + dele+= apparmor.delete_duplicates(apparmor.aa[program][program], inc) + #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') + #if dele>old: + # print(inc) + + for rule in allow_path_rules: + pass + print(dele) + #allow_rules = [] + list(apparmor.aa[program][program]['allow']['path'].keys()) + #allow_rules += list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][program]['allow']['capability'].keys()) + #c=set(allow_rules) + #print(b.difference(c)) + sys.exit(0) + + def delete_path_duplicates(profile, incname, allow): + deleted = [] + for entry in profile[allow]['path'].keys(): + if entry == '#include <%s>'%incname: + continue + cm, am, m = match_include_to_path(incname, allow, entry) + if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']): + deleted.append(entry) + + for entry in deleted: + profile[allow]['path'].pop(entry) + return len(deleted) + + + def match_include_to_path(incname, allow, path): + combinedmode = set() + combinedaudit = set() + matches = [] + includelist = [incname] + while includelist: + incfile = str(includelist.pop(0)) + ret = load_include(incfile) + if not include.get(incfile,{}): + continue + cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path) + #print(incfile, cm, am, m) + if cm: + combinedmode |= cm + combinedaudit |= am + matches += m + + if include[incfile][incfile][allow]['path'][path]: + combinedmode |= include[incfile][incfile][allow]['path'][path]['mode'] + combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit'] + + if include[incfile][incfile]['include'].keys(): + includelist += include[incfile][incfile]['include'].keys() + + return combinedmode, combinedaudit, matches + def use_autodep(self, program): apparmor.check_qualifiers(program) From 9482ccdb74bb590af8bc3fc74fca495bc26a1225 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 17 Sep 2013 11:46:17 +0530 Subject: [PATCH 059/183] --- apparmor/aa.py | 66 ++++++++++++++++++++++++--------- apparmor/tools.py | 94 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 114 insertions(+), 46 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 0091a1a79..41aad11d8 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2078,16 +2078,42 @@ def delete_duplicates(profile, incname): deleted = 0 # Allow rules covered by denied rules shouldn't be deleted # only a subset allow rules may actually be denied - deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) +# deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) +# +# deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) +# +# deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']) +# +# deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) +# +# deleted += delete_path_duplicates(profile, incname, 'allow') +# deleted += delete_path_duplicates(profile, incname, 'deny') + + if include.get(incname, False): + deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) + + deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) + + deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']) + + deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) + + deleted += delete_path_duplicates(profile, incname, 'allow') + deleted += delete_path_duplicates(profile, incname, 'deny') + elif filelist.get(incname, False): + deleted += delete_net_duplicates(profile['allow']['netdomain'], filelist[incname][incname]['allow']['netdomain']) + + deleted += delete_net_duplicates(profile['deny']['netdomain'], filelist[incname][incname]['deny']['netdomain']) + + deleted += delete_cap_duplicates(profile['allow']['capability'], filelist[incname][incname]['allow']) + + deleted += delete_cap_duplicates(profile['deny']['capability'], filelist[incname][incname]['deny']['capability']) + + deleted += delete_path_duplicates(profile, incname, 'allow') + deleted += delete_path_duplicates(profile, incname, 'deny') - deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) - - deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']) - - deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) - - deleted += delete_path_duplicates(profile, incname, 'allow') - deleted += delete_path_duplicates(profile, incname, 'deny') + return deleted + return deleted @@ -3973,15 +3999,19 @@ def load_include(incname): return 0 while load_includeslist: incfile = load_includeslist.pop(0) - data = get_include_data(incfile) - incdata = parse_profile_data(data, incfile, True) - #print(incdata) - if not incdata: - # If include is empty, simply push in a placeholder for it - # because other profiles may mention them - incdata = hasher() - incdata[incname] = hasher() - attach_profile_data(include, incdata) + if os.path.isfile(profile_dir+'/'+incfile): + data = get_include_data(incfile) + incdata = parse_profile_data(data, incfile, True) + #print(incdata) + if not incdata: + # If include is empty, simply push in a placeholder for it + # because other profiles may mention them + incdata = hasher() + incdata[incname] = hasher() + attach_profile_data(include, incdata) + #If the include is a directory means include all subfiles + elif os.path.isdir(profile_dir+'/'+incfile): + load_includeslist += list(map(lambda x: incfile+'/'+x, os.listdir(profile_dir+'/'+incfile))) return 0 diff --git a/apparmor/tools.py b/apparmor/tools.py index 5135af630..a770ce8c4 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -108,44 +108,82 @@ class aa_tools: def clean_profile(self, program, p): filename = apparmor.get_profile_filename(program) - self.delete_superluous_rules(program) + self.delete_superfluous_rules(program, filename) if filename: apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) else: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) - def delete_superluous_rules(self, program): + def delete_superfluous_rules(self, program, filename): #print(filename, apparmor.aa.get(program, False)) #print(apparmor.aa[program][program]['include']) - includes = apparmor.aa[program][program]['include'].keys() - allow_path_rules = list(apparmor.aa[program][program]['allow']['path'].keys()) - allow_net_rules = list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) - #b=set(allow_rules) - #print(allow_rules) - dele = 0 - #print(includes) +# includes = apparmor.aa[program][program]['include'].keys() +# allow_path_rules = list(apparmor.aa[program][program]['allow']['path'].keys()) +# allow_net_rules = list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) +# #b=set(allow_rules) +# #print(allow_rules) +# dele = 0 +# #print(includes) +# +# #Clean up superfluous rules from includes +# #print(apparmor.include.keys()) +# +# for inc in includes: +# #old=dele +# if not apparmor.include.get(inc, False): +# apparmor.load_include(inc) +# dele+= apparmor.delete_duplicates(apparmor.aa[program][program], inc) +# #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') +# #if dele>old: +# # print(inc) +# +# for rule in allow_path_rules: +# pass +# print(dele) +# #allow_rules = [] + list(apparmor.aa[program][program]['allow']['path'].keys()) +# #allow_rules += list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][program]['allow']['capability'].keys()) +# #c=set(allow_rules) +# #print(b.difference(c)) +# sys.exit(0) - #Clean up superfluous rules from includes - #print(apparmor.include.keys()) - for inc in includes: - #old=dele - if not apparmor.include.get(inc, False): - apparmor.load_include(inc) - dele+= apparmor.delete_duplicates(apparmor.aa[program][program], inc) - #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') - #if dele>old: - # print(inc) - - for rule in allow_path_rules: - pass - print(dele) - #allow_rules = [] + list(apparmor.aa[program][program]['allow']['path'].keys()) - #allow_rules += list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][program]['allow']['capability'].keys()) - #c=set(allow_rules) - #print(b.difference(c)) - sys.exit(0) + #print(filename, apparmor.aa.get(program, False)) + #print(apparmor.aa[program][program]['include']) + #Process the profile of the program for + #Process every hat in the profile individually + file_includes = list(apparmor.filelist[filename]['include'].keys()) + print(file_includes) + for hat in apparmor.aa[program].keys(): + includes = list(apparmor.aa[program][hat]['include'].keys()) + file_includes + allow_path_rules = list(apparmor.aa[program][hat]['allow']['path'].keys()) + allow_net_rules = list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) + allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) + b=set(allow_rules) + #print(allow_rules) + dele = 0 + #print(includes) + + #Clean up superfluous rules from includes + #print(apparmor.include.keys()) + + for inc in includes: + old=dele + if not apparmor.include.get(inc, {}).get(inc,False): + apparmor.load_include(inc) + dele+= apparmor.delete_duplicates(apparmor.aa[program][hat], inc) + #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') + if dele>old: + print(inc) + + for rule in allow_path_rules: + pass + allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) + allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) + c=set(allow_rules) + print(b.difference(c)) + sys.exit(0) def delete_path_duplicates(profile, incname, allow): deleted = [] From a8a187828189437a23e0add58def0edaef2ad415 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 17 Sep 2013 14:03:58 +0530 Subject: [PATCH 060/183] added check for matching profile paths --- apparmor/tools.py | 101 ++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 61 deletions(-) diff --git a/apparmor/tools.py b/apparmor/tools.py index a770ce8c4..6df8eeb29 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -1,4 +1,5 @@ import os +import re import sys import apparmor.aa as apparmor @@ -116,86 +117,64 @@ class aa_tools: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) def delete_superfluous_rules(self, program, filename): - #print(filename, apparmor.aa.get(program, False)) - #print(apparmor.aa[program][program]['include']) -# includes = apparmor.aa[program][program]['include'].keys() -# allow_path_rules = list(apparmor.aa[program][program]['allow']['path'].keys()) -# allow_net_rules = list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) -# #b=set(allow_rules) -# #print(allow_rules) -# dele = 0 -# #print(includes) -# -# #Clean up superfluous rules from includes -# #print(apparmor.include.keys()) -# -# for inc in includes: -# #old=dele -# if not apparmor.include.get(inc, False): -# apparmor.load_include(inc) -# dele+= apparmor.delete_duplicates(apparmor.aa[program][program], inc) -# #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') -# #if dele>old: -# # print(inc) -# -# for rule in allow_path_rules: -# pass -# print(dele) -# #allow_rules = [] + list(apparmor.aa[program][program]['allow']['path'].keys()) -# #allow_rules += list(apparmor.aa[program][program]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][program]['allow']['capability'].keys()) -# #c=set(allow_rules) -# #print(b.difference(c)) -# sys.exit(0) - - - #print(filename, apparmor.aa.get(program, False)) - #print(apparmor.aa[program][program]['include']) - #Process the profile of the program for + #Process the profile of the program #Process every hat in the profile individually file_includes = list(apparmor.filelist[filename]['include'].keys()) print(file_includes) for hat in apparmor.aa[program].keys(): + #The combined list of includes from profile and the file includes = list(apparmor.aa[program][hat]['include'].keys()) + file_includes - allow_path_rules = list(apparmor.aa[program][hat]['allow']['path'].keys()) + allow_net_rules = list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) - allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) - allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) - b=set(allow_rules) + #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) + #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) + #b=set(allow_rules) #print(allow_rules) dele = 0 #print(includes) - #Clean up superfluous rules from includes - #print(apparmor.include.keys()) - + #Clean up superfluous rules from includes for inc in includes: - old=dele + #old=dele if not apparmor.include.get(inc, {}).get(inc,False): apparmor.load_include(inc) dele+= apparmor.delete_duplicates(apparmor.aa[program][hat], inc) #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') - if dele>old: - print(inc) - - for rule in allow_path_rules: - pass - allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) - allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) - c=set(allow_rules) - print(b.difference(c)) + #if dele>old: + # print(inc) + #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) + #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) + #c=set(allow_rules) + #print(b.difference(c)) + + dele += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'allow', True) + dele += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'deny', True) + + print(dele) sys.exit(0) - def delete_path_duplicates(profile, incname, allow): + def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): deleted = [] - for entry in profile[allow]['path'].keys(): - if entry == '#include <%s>'%incname: - continue - cm, am, m = match_include_to_path(incname, allow, entry) - if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']): - deleted.append(entry) - + #Check if any individual rule makes any rule superfluous + for rule in profile[allow]['path'].keys(): + for entry in profile_other[allow]['path'].keys(): + if rule == entry: + if not same_profile: + deleted.append(entry) + continue + if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): + continue + #Check if the rule implies entry + if apparmor.matchliteral(rule, entry): + #Check the modes + cm = profile[allow]['path'][rule]['mode'] + am = profile[allow]['path'][rule]['audit'] + #If modes of rule are a superset of rules implied by entry we can safely remove it + if apparmor.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.mode_contains(am, profile_other[allow]['path'][entry]['audit']): + deleted.append(entry) + #print(deleted) for entry in deleted: - profile[allow]['path'].pop(entry) + profile_other[allow]['path'].pop(entry) return len(deleted) From 3f9526c1ac4e80986295c508e5ecdafbad07a3ca Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 17 Sep 2013 22:30:48 +0530 Subject: [PATCH 061/183] seperated the code to check for duplicates into a separate module, will be using it to remove duplicates/superfluous rules/includes from base and other profiles in the aa-mergeprof --- Tools/aa-mergeprof | 18 ++-- apparmor/__init__.py | 14 ++- apparmor/aa.py | 4 +- apparmor/cleanprofile.py | 128 ++++++++++++++++++++++++++ apparmor/tools.py | 193 +++++++++++++++++++++------------------ 5 files changed, 252 insertions(+), 105 deletions(-) create mode 100644 apparmor/cleanprofile.py diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index b40c7c991..47dd61195 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -2,9 +2,10 @@ import argparse import sys +import cleanprof import apparmor.aa as apparmor - +import apparmor.cleanprofile as cleanprofile parser = argparse.ArgumentParser(description='Perform a 3way merge on the given profiles') ##parser.add_argument('profiles', type=str, nargs=3, help='MINE BASE OTHER') parser.add_argument('mine', type=str, help='Your profile') @@ -30,20 +31,23 @@ class Merge(object): #Read and parse base profile and save profile data, include data from it and reset them apparmor.read_profile(base, True) - self.base_aa = apparmor.aa - self.base_filelist = apparmor.filelist - self.base_include = apparmor.include + base = cleanprof.Prof() + #self.base_aa = apparmor.aa + #self.base_filelist = apparmor.filelist + #self.base_include = apparmor.include reset() #Read and parse other profile and save profile data, include data from it and reset them apparmor.read_profile(other, True) - self.other_aa = apparmor.aa - self.other_filelist = apparmor.filelist - self.other_include = apparmor.include + other = cleanprof.prof() + #self.other_aa = apparmor.aa + #self.other_filelist = apparmor.filelist + #self.other_include = apparmor.include reset() #Read and parse user profile apparmor.read_profile(profiles[0], True) + user = cleanprof.prof() #user_aa = apparmor.aa #user_filelist = apparmor.filelist #user_include = apparmor.include diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 4743fa3fd..78f0eab34 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -1,17 +1,15 @@ -''' -Created on Jun 27, 2013 - -@author: kshitij -''' import gettext import locale def init_localisation(): locale.setlocale(locale.LC_ALL, '') - #cur_locale = locale.getlocale() - filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0][0:2] + cur_locale = locale.getlocale() + filename = '' + #If a correct locale has been provided set filename else let a IOError be raised by '' path + if cur_locale[0]: + filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0][0:2] try: - trans = gettext.GNUTranslations(open( filename, 'rb')) + trans = gettext.GNUTranslations(open(filename, 'rb')) except IOError: trans = gettext.NullTranslations() trans.install() diff --git a/apparmor/aa.py b/apparmor/aa.py index 41aad11d8..d2bcc681e 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2036,12 +2036,12 @@ def delete_net_duplicates(netrules, incnetrules): incnetglob = True for fam in netrules.keys(): if incnetglob or (type(incnetrules['rule'][fam]) != dict and incnetrules['rule'][fam] == 1): - if type(netrules['rule'][hash]) == dict: + if type(netrules['rule'][fam]) == dict: deleted += len(netrules['rule'][fam].keys()) else: deleted += 1 netrules['rule'].pop(fam) - elif netrules['rule'][fam] != 'HASH' and netrules['rule'][fam] == 1: + elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam] == 1: continue else: for socket_type in netrules['rule'][fam].keys(): diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py new file mode 100644 index 000000000..36ec82552 --- /dev/null +++ b/apparmor/cleanprofile.py @@ -0,0 +1,128 @@ +import re +import sys + +import apparmor + +class Prof: + def __init__(self, filename): + self.aa = apparmor.aa.aa + self.filelist = apparmor.aa.filelist + self.include = apparmor.aa.include + self.filename = filename + +class CleanProf: + def __init__(self, same_file, profile, other): + #If same_file we're basically comparing the file against itself to check superfluous rules + self.same_file = same_file + self.profile = profile + self.other = profile + + def compare_profiles(self): + #Remove the duplicate file-level includes from other + other_file_includes = list(self.other.profile.filename['include'].keys()) + for rule in self.profile.filelist[self.profile.filename]: + if rule in other_file_includes: + self.other.other.filename['include'].pop(rule) + + for profile in self.profile.aa.keys(): + self.remove_duplicate_rules(profile) + + def remove_duplicate_rules(self, program): + #Process the profile of the program + #Process every hat in the profile individually + file_includes = list(self.profile.filelist[self.profile.filename]['include'].keys()) + #print(file_includes) + for hat in self.profile.aa[program].keys(): + #The combined list of includes from profile and the file + includes = list(self.profile.aa[program][hat]['include'].keys()) + file_includes + + allow_net_rules = list(self.profile.aa[program][hat]['allow']['netdomain']['rule'].keys()) + #allow_rules = [] + list(apparmor.aa.aa[program][hat]['allow']['path'].keys()) + #allow_rules += list(apparmor.aa.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa.aa[program][hat]['allow']['capability'].keys()) + #b=set(allow_rules) + #print(allow_rules) + deleted = 0 + #print(includes) + + #Clean up superfluous rules from includes in the other profile + for inc in includes: + #old=dele + if not self.profile.include.get(inc, {}).get(inc,False): + apparmor.aa.load_include(inc) + deleted += apparmor.aa.delete_duplicates(self.other.aa[program][hat], inc) + #dele+= apparmor.aa.delete_path_duplicates(apparmor.aa.aa[program][program], str(inc), 'allow') + #if dele>old: + # print(inc) + #allow_rules = [] + list(apparmor.aa.aa[program][hat]['allow']['path'].keys()) + #allow_rules += list(apparmor.aa.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa.aa.aa[program][hat]['allow']['capability'].keys()) + #c=set(allow_rules) + #print(b.difference(c)) + + #Clean the duplicates of caps in other profile + deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file) + deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['deny']['capability'], self.other.aa[program][hat]['deny']['capability'], self.same_file) + + #Clean the duplicates of path in other profile + deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file) + deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file) + + print(deleted) + sys.exit(0) + + def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): + deleted = [] + #Check if any individual rule makes any rule superfluous + for rule in profile[allow]['path'].keys(): + for entry in profile_other[allow]['path'].keys(): + if rule == entry: + if not same_profile: + deleted.append(entry) + continue + if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): + continue + #Check if the rule implies entry + if apparmor.aa.matchliteral(rule, entry): + #Check the modes + cm = profile[allow]['path'][rule]['mode'] + am = profile[allow]['path'][rule]['audit'] + #If modes of rule are a superset of rules implied by entry we can safely remove it + if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): + deleted.append(entry) + #print(deleted) + for entry in deleted: + profile_other[allow]['path'].pop(entry) + return len(deleted) + + def delete_cap_duplicates(self, profilecaps, profilecaps_other, same_profile=True): + deleted = [] + if profilecaps and profilecaps_other and not same_profile: + for capname in profilecaps.keys(): + if profilecaps_other[capname].get('set', False): + deleted.append(capname) + for capname in deleted: + profilecaps_other.pop(capname) + + return len(deleted) + + def delete_net_duplicates(self, netrules, netrules_other, same_profile=True): + deleted = 0 + if netrules_other and netrules: + netglob = False + # Delete matching rules from abstractions + if netrules.get('all', False): + netglob = True + for fam in netrules_other.keys(): + if netglob or (type(netrules_other['rule'][fam]) != dict and netrules_other['rule'][fam] == True): + if type(netrules['rule'][fam]) == dict: + deleted += len(netrules['rule'][fam].keys()) + else: + deleted += 1 + netrules['rule'].pop(fam) + elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam] == True: + continue + else: + for socket_type in netrules['rule'][fam].keys(): + if netrules_other['rule'].get(fam, False): + netrules[fam].pop(socket_type) + deleted += 1 + return deleted diff --git a/apparmor/tools.py b/apparmor/tools.py index 6df8eeb29..8460009eb 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -47,7 +47,7 @@ class aa_tools: program = apparmor.get_full_path(which) if (not program or not os.path.exists(program)): - if not program.startswith('/'): + if program and not program.startswith('/'): program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: apparmor.UI_Info(_("%s does not exist, please double-check the path.")%program) @@ -109,100 +109,117 @@ class aa_tools: def clean_profile(self, program, p): filename = apparmor.get_profile_filename(program) - self.delete_superfluous_rules(program, filename) + + import apparmor.cleanprofile as cleanprofile + prof = cleanprofile.Prof(filename) + cleanprof = cleanprofile.CleanProf(True, prof, prof) + cleanprof.remove_duplicate_rules(program) + + #self.delete_superfluous_rules(program, filename) if filename: apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) else: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) - def delete_superfluous_rules(self, program, filename): - #Process the profile of the program - #Process every hat in the profile individually - file_includes = list(apparmor.filelist[filename]['include'].keys()) - print(file_includes) - for hat in apparmor.aa[program].keys(): - #The combined list of includes from profile and the file - includes = list(apparmor.aa[program][hat]['include'].keys()) + file_includes +# def delete_superfluous_rules(self, program, filename): +# #Process the profile of the program +# #Process every hat in the profile individually +# file_includes = list(apparmor.filelist[filename]['include'].keys()) +# print(file_includes) +# for hat in apparmor.aa[program].keys(): +# #The combined list of includes from profile and the file +# includes = list(apparmor.aa[program][hat]['include'].keys()) + file_includes +# +# allow_net_rules = list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) +# #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) +# #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) +# #b=set(allow_rules) +# #print(allow_rules) +# deleted = 0 +# #print(includes) +# +# #Clean up superfluous rules from includes +# for inc in includes: +# #old=dele +# if not apparmor.include.get(inc, {}).get(inc,False): +# apparmor.load_include(inc) +# deleted += apparmor.delete_duplicates(apparmor.aa[program][hat], inc) +# #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') +# #if dele>old: +# # print(inc) +# #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) +# #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) +# #c=set(allow_rules) +# #print(b.difference(c)) +# +# deleted += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'allow', True) +# deleted += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'deny', True) +# +# deleted += self.delete_cap_duplicates(apparmor.aa[program][hat]['allow']['capability'], apparmor.aa[program][hat]['allow']['capability'], True) +# deleted += self.delete_cap_duplicates(apparmor.aa[program][hat]['deny']['capability'], apparmor.aa[program][hat]['deny']['capability'], True) +# +# print(deleted) +# sys.exit(0) +# +# def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): +# deleted = [] +# #Check if any individual rule makes any rule superfluous +# for rule in profile[allow]['path'].keys(): +# for entry in profile_other[allow]['path'].keys(): +# if rule == entry: +# if not same_profile: +# deleted.append(entry) +# continue +# if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): +# continue +# #Check if the rule implies entry +# if apparmor.matchliteral(rule, entry): +# #Check the modes +# cm = profile[allow]['path'][rule]['mode'] +# am = profile[allow]['path'][rule]['audit'] +# #If modes of rule are a superset of rules implied by entry we can safely remove it +# if apparmor.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.mode_contains(am, profile_other[allow]['path'][entry]['audit']): +# deleted.append(entry) +# #print(deleted) +# for entry in deleted: +# profile_other[allow]['path'].pop(entry) +# return len(deleted) +# +# def delete_cap_duplicates(self, profilecaps, profilecaps_other, same_profile=True): +# deleted = [] +# if profilecaps and profilecaps_other and not same_profile: +# for capname in profilecaps.keys(): +# if profilecaps_other[capname].get('set', False): +# deleted.append(capname) +# for capname in deleted: +# profilecaps_other.pop(capname) +# +# return len(deleted) +# +# def delete_net_duplicates(self, netrules, netrules_other, same_profile=True): +# deleted = 0 +# if netrules_other and netrules: +# netglob = False +# # Delete matching rules from abstractions +# if netrules.get('all', False): +# netglob = True +# for fam in netrules_other.keys(): +# if netglob or (type(netrules_other['rule'][fam]) != dict and netrules_other['rule'][fam] == True): +# if type(netrules['rule'][fam]) == dict: +# deleted += len(netrules['rule'][fam].keys()) +# else: +# deleted += 1 +# netrules['rule'].pop(fam) +# elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam] == True: +# continue +# else: +# for socket_type in netrules['rule'][fam].keys(): +# if netrules_other['rule'].get(fam, False): +# netrules[fam].pop(socket_type) +# deleted += 1 +# return deleted - allow_net_rules = list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) - #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) - #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) - #b=set(allow_rules) - #print(allow_rules) - dele = 0 - #print(includes) - - #Clean up superfluous rules from includes - for inc in includes: - #old=dele - if not apparmor.include.get(inc, {}).get(inc,False): - apparmor.load_include(inc) - dele+= apparmor.delete_duplicates(apparmor.aa[program][hat], inc) - #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') - #if dele>old: - # print(inc) - #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) - #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) - #c=set(allow_rules) - #print(b.difference(c)) - - dele += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'allow', True) - dele += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'deny', True) - - print(dele) - sys.exit(0) - - def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): - deleted = [] - #Check if any individual rule makes any rule superfluous - for rule in profile[allow]['path'].keys(): - for entry in profile_other[allow]['path'].keys(): - if rule == entry: - if not same_profile: - deleted.append(entry) - continue - if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): - continue - #Check if the rule implies entry - if apparmor.matchliteral(rule, entry): - #Check the modes - cm = profile[allow]['path'][rule]['mode'] - am = profile[allow]['path'][rule]['audit'] - #If modes of rule are a superset of rules implied by entry we can safely remove it - if apparmor.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.mode_contains(am, profile_other[allow]['path'][entry]['audit']): - deleted.append(entry) - #print(deleted) - for entry in deleted: - profile_other[allow]['path'].pop(entry) - return len(deleted) - - - def match_include_to_path(incname, allow, path): - combinedmode = set() - combinedaudit = set() - matches = [] - includelist = [incname] - while includelist: - incfile = str(includelist.pop(0)) - ret = load_include(incfile) - if not include.get(incfile,{}): - continue - cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path) - #print(incfile, cm, am, m) - if cm: - combinedmode |= cm - combinedaudit |= am - matches += m - - if include[incfile][incfile][allow]['path'][path]: - combinedmode |= include[incfile][incfile][allow]['path'][path]['mode'] - combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit'] - - if include[incfile][incfile]['include'].keys(): - includelist += include[incfile][incfile]['include'].keys() - - return combinedmode, combinedaudit, matches def use_autodep(self, program): apparmor.check_qualifiers(program) From e41a8aec0e68b7cc221d99ae4b9be4b8fde111d5 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 17 Sep 2013 22:37:13 +0530 Subject: [PATCH 062/183] --- apparmor/tools.py | 106 ++-------------------------------------------- 1 file changed, 3 insertions(+), 103 deletions(-) diff --git a/apparmor/tools.py b/apparmor/tools.py index 8460009eb..a01ab4f09 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -109,118 +109,18 @@ class aa_tools: def clean_profile(self, program, p): filename = apparmor.get_profile_filename(program) - + import apparmor.cleanprofile as cleanprofile prof = cleanprofile.Prof(filename) cleanprof = cleanprofile.CleanProf(True, prof, prof) cleanprof.remove_duplicate_rules(program) - - #self.delete_superfluous_rules(program, filename) + if filename: apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) else: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) - -# def delete_superfluous_rules(self, program, filename): -# #Process the profile of the program -# #Process every hat in the profile individually -# file_includes = list(apparmor.filelist[filename]['include'].keys()) -# print(file_includes) -# for hat in apparmor.aa[program].keys(): -# #The combined list of includes from profile and the file -# includes = list(apparmor.aa[program][hat]['include'].keys()) + file_includes -# -# allow_net_rules = list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) -# #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) -# #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) -# #b=set(allow_rules) -# #print(allow_rules) -# deleted = 0 -# #print(includes) -# -# #Clean up superfluous rules from includes -# for inc in includes: -# #old=dele -# if not apparmor.include.get(inc, {}).get(inc,False): -# apparmor.load_include(inc) -# deleted += apparmor.delete_duplicates(apparmor.aa[program][hat], inc) -# #dele+= apparmor.delete_path_duplicates(apparmor.aa[program][program], str(inc), 'allow') -# #if dele>old: -# # print(inc) -# #allow_rules = [] + list(apparmor.aa[program][hat]['allow']['path'].keys()) -# #allow_rules += list(apparmor.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa[program][hat]['allow']['capability'].keys()) -# #c=set(allow_rules) -# #print(b.difference(c)) -# -# deleted += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'allow', True) -# deleted += self.delete_path_duplicates(apparmor.aa[program][hat], apparmor.aa[program][hat], 'deny', True) -# -# deleted += self.delete_cap_duplicates(apparmor.aa[program][hat]['allow']['capability'], apparmor.aa[program][hat]['allow']['capability'], True) -# deleted += self.delete_cap_duplicates(apparmor.aa[program][hat]['deny']['capability'], apparmor.aa[program][hat]['deny']['capability'], True) -# -# print(deleted) -# sys.exit(0) -# -# def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): -# deleted = [] -# #Check if any individual rule makes any rule superfluous -# for rule in profile[allow]['path'].keys(): -# for entry in profile_other[allow]['path'].keys(): -# if rule == entry: -# if not same_profile: -# deleted.append(entry) -# continue -# if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): -# continue -# #Check if the rule implies entry -# if apparmor.matchliteral(rule, entry): -# #Check the modes -# cm = profile[allow]['path'][rule]['mode'] -# am = profile[allow]['path'][rule]['audit'] -# #If modes of rule are a superset of rules implied by entry we can safely remove it -# if apparmor.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.mode_contains(am, profile_other[allow]['path'][entry]['audit']): -# deleted.append(entry) -# #print(deleted) -# for entry in deleted: -# profile_other[allow]['path'].pop(entry) -# return len(deleted) -# -# def delete_cap_duplicates(self, profilecaps, profilecaps_other, same_profile=True): -# deleted = [] -# if profilecaps and profilecaps_other and not same_profile: -# for capname in profilecaps.keys(): -# if profilecaps_other[capname].get('set', False): -# deleted.append(capname) -# for capname in deleted: -# profilecaps_other.pop(capname) -# -# return len(deleted) -# -# def delete_net_duplicates(self, netrules, netrules_other, same_profile=True): -# deleted = 0 -# if netrules_other and netrules: -# netglob = False -# # Delete matching rules from abstractions -# if netrules.get('all', False): -# netglob = True -# for fam in netrules_other.keys(): -# if netglob or (type(netrules_other['rule'][fam]) != dict and netrules_other['rule'][fam] == True): -# if type(netrules['rule'][fam]) == dict: -# deleted += len(netrules['rule'][fam].keys()) -# else: -# deleted += 1 -# netrules['rule'].pop(fam) -# elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam] == True: -# continue -# else: -# for socket_type in netrules['rule'][fam].keys(): -# if netrules_other['rule'].get(fam, False): -# netrules[fam].pop(socket_type) -# deleted += 1 -# return deleted - - + def use_autodep(self, program): apparmor.check_qualifiers(program) From 3d0307a5a9731fb9598fe70c1b184d73bf03ba34 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 19 Sep 2013 10:32:19 +0530 Subject: [PATCH 063/183] Added manpages for the tools, fixes from rev 59..62, some fixes from rev 58 --- Tools/aa-audit | 2 +- Tools/aa-autodep | 2 +- Tools/aa-cleanprof | 2 +- Tools/aa-complain | 2 +- Tools/aa-disable | 2 +- Tools/aa-enforce | 2 +- Tools/aa-genprof | 4 +- Tools/aa-logprof | 4 +- Tools/aa-mergeprof | 8 +- Tools/aa-unconfined | 4 +- Tools/manpages/aa-audit.pod | 39 +++++++ Tools/manpages/aa-autodep.pod | 66 ++++++++++++ Tools/manpages/aa-cleanprof.pod | 34 ++++++ Tools/manpages/aa-complain.pod | 61 +++++++++++ Tools/manpages/aa-disable.pod | 62 +++++++++++ Tools/manpages/aa-enforce.pod | 65 ++++++++++++ Tools/manpages/aa-genprof.pod | 92 +++++++++++++++++ Tools/manpages/aa-logprof.pod | 171 +++++++++++++++++++++++++++++++ Tools/manpages/aa-mergeprof.pod | 33 ++++++ Tools/manpages/aa-unconfined.pod | 64 ++++++++++++ apparmor/__init__.py | 7 +- apparmor/aa.py | 38 +++---- apparmor/ui.py | 2 +- 23 files changed, 718 insertions(+), 48 deletions(-) create mode 100644 Tools/manpages/aa-audit.pod create mode 100644 Tools/manpages/aa-autodep.pod create mode 100644 Tools/manpages/aa-cleanprof.pod create mode 100644 Tools/manpages/aa-complain.pod create mode 100644 Tools/manpages/aa-disable.pod create mode 100644 Tools/manpages/aa-enforce.pod create mode 100644 Tools/manpages/aa-genprof.pod create mode 100644 Tools/manpages/aa-logprof.pod create mode 100644 Tools/manpages/aa-mergeprof.pod create mode 100644 Tools/manpages/aa-unconfined.pod diff --git a/Tools/aa-audit b/Tools/aa-audit index 91961f109..67699906b 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -5,7 +5,7 @@ import argparse import apparmor.tools parser = argparse.ArgumentParser(description='Switch the given programs to audit mode') -parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('-r', '--remove', action='store_true', help='remove audit mode') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() diff --git a/Tools/aa-autodep b/Tools/aa-autodep index 8c0d80baa..3677bf206 100644 --- a/Tools/aa-autodep +++ b/Tools/aa-autodep @@ -6,7 +6,7 @@ import apparmor.tools parser = argparse.ArgumentParser(description='') parser.add_argument('--force', type=str, help='override existing profile') -parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index 042f6cbc0..c75a1644e 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -5,7 +5,7 @@ import argparse import apparmor.tools parser = argparse.ArgumentParser(description='Cleanup the profiles for the given programs') -parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() diff --git a/Tools/aa-complain b/Tools/aa-complain index 940d97874..08d7333c4 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -5,7 +5,7 @@ import argparse import apparmor.tools parser = argparse.ArgumentParser(description='Switch the given program to complain mode') -parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('-r', '--remove', action='store_true', help='remove complain mode') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() diff --git a/Tools/aa-disable b/Tools/aa-disable index c27cd71a5..ed111eb8f 100644 --- a/Tools/aa-disable +++ b/Tools/aa-disable @@ -5,7 +5,7 @@ import argparse import apparmor.tools parser = argparse.ArgumentParser(description='Disable the profile for the given programs') -parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('-r', '--revert', action='store_true', help='enable the profile for the given programs') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() diff --git a/Tools/aa-enforce b/Tools/aa-enforce index a1a2f7976..c87dcd2b9 100644 --- a/Tools/aa-enforce +++ b/Tools/aa-enforce @@ -5,7 +5,7 @@ import argparse import apparmor.tools parser = argparse.ArgumentParser(description='Switch the given program to enforce mode') -parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('-r', '--remove', action='store_true', help='switch to complain mode') parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 577252354..0a573a4ef 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -34,8 +34,8 @@ def restore_ratelimit(): sysctl_write(ratelimit_sysctl, ratelimit_saved) parser = argparse.ArgumentParser(description='Generate profile for the given program') -parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('-f', type=str, help='path to logfile') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') +parser.add_argument('-f', '--file', type=str, help='path to logfile') parser.add_argument('program', type=str, help='name of program to profile') args = parser.parse_args() diff --git a/Tools/aa-logprof b/Tools/aa-logprof index 40251ab57..065cbe80c 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -6,8 +6,8 @@ import os import apparmor.aa as apparmor parser = argparse.ArgumentParser(description='Process log entries to generate profiles') -parser.add_argument('-d', type=str, help='path to profiles') -parser.add_argument('-f', type=str, help='path to logfile') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') +parser.add_argument('-f', '--file', type=str, help='path to logfile') parser.add_argument('-m', type=str, help='mark in the log to start processing after') args = parser.parse_args() diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 47dd61195..b23fd0258 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -2,16 +2,16 @@ import argparse import sys -import cleanprof import apparmor.aa as apparmor import apparmor.cleanprofile as cleanprofile + parser = argparse.ArgumentParser(description='Perform a 3way merge on the given profiles') ##parser.add_argument('profiles', type=str, nargs=3, help='MINE BASE OTHER') parser.add_argument('mine', type=str, help='Your profile') parser.add_argument('base', type=str, help='The base profile') parser.add_argument('other', type=str, help='Other profile') -parser.add_argument('-d', type=str, help='path to profiles') +parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('-auto', action='store_true', help='Automatically merge profiles, exits incase of *x conflicts') args = parser.parse_args() @@ -513,6 +513,4 @@ class Merge(object): # # m3 = Merge3(base, a, b) # -# sys.stdout.write(m3.merge_annotated()) - - +# sys.stdout.write(m3.merge_annotated()) \ No newline at end of file diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index 8fb1f4a94..cc9159f6d 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -6,8 +6,8 @@ import re import apparmor.aa as apparmor -parser = argparse.ArgumentParser(description='') -parser.add_argument('--paranoid', action='store_true') +parser = argparse.ArgumentParser(description='Lists unconfined processes having tcp or udp ports') +parser.add_argument('--paranoid', action='store_true', help='scan all processes from /proc') args = parser.parse_args() paranoid = args.paranoid diff --git a/Tools/manpages/aa-audit.pod b/Tools/manpages/aa-audit.pod new file mode 100644 index 000000000..d429a1559 --- /dev/null +++ b/Tools/manpages/aa-audit.pod @@ -0,0 +1,39 @@ +=pod + +=head1 NAME + +aa-audit - set a AppArmor security profile to I mode. + +=head1 SYNOPSIS + +BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-r>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +B<-r --remove> + + Removes the audit mode for the profile. + +=head1 DESCRIPTION + +B is used to set the audit mode for one or more profiles to audit. +In this mode security policy is enforced and all access (successes and failures) are logged to the system log. + +The I<--remove> option can be used to remove the audit mode for the profile. + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1), +aa_change_hat(2), and L. + +=cut diff --git a/Tools/manpages/aa-autodep.pod b/Tools/manpages/aa-autodep.pod new file mode 100644 index 000000000..d9930729d --- /dev/null +++ b/Tools/manpages/aa-autodep.pod @@ -0,0 +1,66 @@ +# This publication is intellectual property of Novell Inc. and Canonical +# Ltd. Its contents can be duplicated, either in part or in whole, provided +# that a copyright label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators +# shall be held liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. SUSE LINUX GmbH +# and Canonical Ltd. essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa-autodep - guess basic AppArmor profile requirements + +=head1 SYNOPSIS + +BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-f>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +B<-f --force> + + Overrides any existing AppArmor profile for the executable with the generated minimal AppArmor profile. + +=head1 DESCRIPTION + +B is used to generate a minimal AppArmor profile for a set of +executables. This program will generate a profile for binary executable +as well as interpreted script programs. At a minimum aa-autodep will provide +a base profile containing a base include directive which includes basic +profile entries needed by most programs. The profile is generated by +recursively calling ldd(1) on the executables listed on the command line. + +The I<--force> option will override any existing profile for the executable with +the newly generated minimal AppArmor profile. + +=head1 BUGS + +This program does not perform full static analysis of executables, so +the profiles generated are necessarily incomplete. If you find any bugs, +please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa-complain(1), aa-enforce(1), aa-disable(1), +aa_change_hat(2), and L. + +=cut diff --git a/Tools/manpages/aa-cleanprof.pod b/Tools/manpages/aa-cleanprof.pod new file mode 100644 index 000000000..88b5ea448 --- /dev/null +++ b/Tools/manpages/aa-cleanprof.pod @@ -0,0 +1,34 @@ +=pod + +=head1 NAME + +aa-cleanprof - clean an existing AppArmor security profile. + +=head1 SYNOPSIS + +BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +=head1 DESCRIPTION + +B is used to perform a cleanup on one or more profiles. +The tool removes any existing superfluous rules, reorders the rules to group +similar rules together and removes all comments. + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1), +aa_change_hat(2), and L. + +=cut diff --git a/Tools/manpages/aa-complain.pod b/Tools/manpages/aa-complain.pod new file mode 100644 index 000000000..e41dd886f --- /dev/null +++ b/Tools/manpages/aa-complain.pod @@ -0,0 +1,61 @@ +# This publication is intellectual property of Novell Inc. and Canonical +# Ltd. Its contents can be duplicated, either in part or in whole, provided +# that a copyright label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators +# shall be held liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. SUSE LINUX GmbH +# and Canonical Ltd. essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa-complain - set a AppArmor security profile to I mode. + +=head1 SYNOPSIS + +BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-r>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +B<-r --remove> + + Removes the complain mode for the profile. + +=head1 DESCRIPTION + +B is used to set the enforcement mode for one or more profiles to +complain. In this mode security policy is not enforced but rather access +violations are logged to the system log. + +The I<--remove> option can be used to remove the complain mode for the profile, +setting it to enforce mode by default. + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa-enforce(1), aa-disable(1), +aa_change_hat(2), and L. + +=cut diff --git a/Tools/manpages/aa-disable.pod b/Tools/manpages/aa-disable.pod new file mode 100644 index 000000000..7af82f847 --- /dev/null +++ b/Tools/manpages/aa-disable.pod @@ -0,0 +1,62 @@ +# This publication is intellectual property of Novell Inc. and Canonical +# Ltd. Its contents can be duplicated, either in part or in whole, provided +# that a copyright label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators +# shall be held liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. SUSE LINUX GmbH +# and Canonical Ltd. essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa-disable - disable an AppArmor security profile + +=head1 SYNOPSIS + +BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-r>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +B<-r --revert> + + Enables the profile and loads it. + +=head1 DESCRIPTION + +B is used to disable the enforcement mode for one or more +profiles. This command will unload the profile from the kernel and +prevent the profile from being loaded on AppArmor startup. The +I and I utilities may be used to to change this +behavior. + +The I<--revert> option can be used to enable the profile. + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), +aa_change_hat(2), and L. + +=cut diff --git a/Tools/manpages/aa-enforce.pod b/Tools/manpages/aa-enforce.pod new file mode 100644 index 000000000..9577288f1 --- /dev/null +++ b/Tools/manpages/aa-enforce.pod @@ -0,0 +1,65 @@ +# This publication is intellectual property of Novell Inc. and Canonical +# Ltd. Its contents can be duplicated, either in part or in whole, provided +# that a copyright label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators +# shall be held liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. SUSE LINUX GmbH +# and Canonical Ltd. essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa-enforce - set an AppArmor security profile to I mode from +being disabled or I mode. + +=head1 SYNOPSIS + +BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-r>]> + +=head1 OPTIONS + +B<-d --dir / path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +B<-r --remove> + + Removes the enforce mode for the profile. + +=head1 DESCRIPTION + +B is used to set the enforcement mode for one or more profiles +to I. This command is only relevant in conjunction with the +I utility which sets a profile to complain mode and the +I utility which unloads and disables a profile. The default +mode for a security policy is enforce and the I utility must +be run to change this behavior. + +The I<--remove> option can be used to remove the enforce mode for the profile, +setting it to complain mode. + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa-complain(1), aa-disable(1), +aa_change_hat(2), and L. + +=cut diff --git a/Tools/manpages/aa-genprof.pod b/Tools/manpages/aa-genprof.pod new file mode 100644 index 000000000..d2f2e38fa --- /dev/null +++ b/Tools/manpages/aa-genprof.pod @@ -0,0 +1,92 @@ +# This publication is intellectual property of Novell Inc. and Canonical +# Ltd. Its contents can be duplicated, either in part or in whole, provided +# that a copyright label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators +# shall be held liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. SUSE LINUX GmbH +# and Canonical Ltd. essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa-genprof - profile generation utility for AppArmor + +=head1 SYNOPSIS + +BexecutableE> [I<-d /path/to/profiles>] [I<-f /path/to/logfile>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +B<-f --file /path/to/logfile> + + Specifies the location of logfile. + Default locations are read from F. + Typical defaults are: + /var/log/audit/audit.log + /var/log/syslog + /var/log/messages + +=head1 DESCRIPTION + +When running aa-genprof, you must specify a program to profile. If the +specified program is not a fully-qualified path, aa-genprof will search $PATH +in order to find the program. + +If a profile does not exist for the program, aa-genprof will create one using +aa-autodep(1). + +Genprof will then: + + - set the profile to complain mode + + - write a mark to the system log + + - instruct the user to start the application to + be profiled in another window and exercise its functionality + +It then presents the user with two options, (S)can system log for entries +to add to profile and (F)inish. + +If the user selects (S)can or hits return, aa-genprof will parse +the complain mode logs and iterate through generated violations +using aa-logprof(1). + +After the user finishes selecting profile entries based on violations +that were detected during the program execution, aa-genprof will reload +the updated profiles in complain mode and again prompt the user for (S)can and +(D)one. This cycle can then be repeated as necessary until all application +functionality has been exercised without generating access violations. + +When the user eventually hits (F)inish, aa-genprof will set the main profile, +and any other profiles that were generated, into enforce mode and exit. + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa-enforce(1), aa-complain(1), aa-disable(1), +aa_change_hat(2), aa-logprof(1), logprof.conf(5), and +L. + +=cut diff --git a/Tools/manpages/aa-logprof.pod b/Tools/manpages/aa-logprof.pod new file mode 100644 index 000000000..a703a4061 --- /dev/null +++ b/Tools/manpages/aa-logprof.pod @@ -0,0 +1,171 @@ +# This publication is intellectual property of Novell Inc. and Canonical +# Ltd. Its contents can be duplicated, either in part or in whole, provided +# that a copyright label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators +# shall be held liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. SUSE LINUX GmbH +# and Canonical Ltd. essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa-logprof - utility program for managing AppArmor security profiles + +=head1 SYNOPSIS + +B] [I<-f /path/to/logfile>] [I<-m Emark in logfileE>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +B<-f --file /path/to/logfile> + + Specifies the location of logfile that contains AppArmor security events. + Default locations are read from F. + Typical defaults are: + /var/log/audit/audit.log + /var/log/syslog + /var/log/messages + +B< -m --logmark "mark"> + + aa-logprof will ignore all events in the system log before the + specified mark is seen. If the mark contains spaces, it must + be surrounded with quotes to work correctly. + +=head1 DESCRIPTION + +B is an interactive tool used to review AppArmor's +complain mode output and generate new entries for AppArmor security +profiles. + +Running aa-logprof will scan the log file and if there are new AppArmor +events that are not covered by the existing profile set, the user will +be prompted with suggested modifications to augment the profile. + +When aa-logprof exits profile changes are saved to disk. If AppArmor is +running, the updated profiles are reloaded and if any processes that +generated AppArmor events are still running in the null-complain-profile, +those processes are set to run under their proper profiles. + +=head2 Responding to AppArmor Events + +B will generate a list of suggested profile changes that +the user can choose from, or they can create their own, to modifiy the +permission set of the profile so that the generated access violation +will not re-occur. + +The user is then presented with info about the access including profile, +path, old mode if there was a previous entry in the profile for this path, +new mode, the suggestion list, and given these options: + + (A)llow, (D)eny, (N)ew, (G)lob last piece, (Q)uit + +If the AppArmor profile was in complain mode when the event was generated, +the default for this option is (A)llow, otherwise, it's (D)eny. + +The suggestion list is presented as a numbered list with includes +at the top, the literal path in the middle, and the suggested globs +at the bottom. If any globs are being suggested, the shortest glob +is the selected option, otherwise, the literal path is selected. +Picking includes from the list must be done manually. + +Hitting a numbered key will change the selected option to the +corresponding numbered entry in the list. + +If the user selects (N)ew, they'll be prompted to enter their own globbed +entry to match the path. If the user-entered glob does not match the +path for this event, they'll be informed and have the option to fix it. + +If the user selects (G)lob last piece then, taking the currently selected +option, aa-logprof will remove the last path element and replace it with /*. + +If the last path element already was /*, aa-logprof will go up a directory +level and replace it with /**. + +This new globbed entry is then added to the suggestion list and marked +as the selected option. + +So /usr/share/themes/foo/bar/baz.gif can be turned into +/usr/share/themes/** by hitting "g" three times. + +If the user selects (A)llow, aa-logprof will take the current selection +and add it to the profile, deleting other entries in the profile that +are matched by the new entry. + +Adding r access to /usr/share/themes/** would delete an entry for r +access to /usr/share/themes/foo/*.gif if it exists in the profile. + +If (Q)uit is selected at this point, aa-logprof will ignore all new pending +capability and path accesses. + +After all of the path accesses have been handled, logrof will write all +updated profiles to the disk and reload them if AppArmor is running. + +=head2 New Process (Execution) Events + +If there are unhandled x accesses generated by the execve(2) of a +new process, aa-logprof will display the parent profile and the target +program that's being executed and prompt the user to select and execute +modifier. These modifiers will allow a choice for the target to: have it's +own profile (px), inherit the parent's profile (ix), run unconstrained +(ux), or deny access for the target. See apparmor.d(5) for details. + +If there is a corresponding entry for the target in the qualifiers +section of /etc/apparmor/logprof.conf, the presented list will contain only the +allowed modes. + +The default option for this question is selected using this logic-- + + # if px mode is allowed and profile exists for the target + # px is default. + # else if ix mode is allowed + # ix is default + # else + # deny is default + +aa-logprof will never suggest "ux" as the default. + +=head2 ChangeHat Events + +If unknown aa_change_hat(2) events are found, the user is prompted to add a new +hat, if the events should go into the default hat for this profile based +on the corresponding entry in the defaulthat section of logprof.conf, +or if the following events that run under that hat should be denied +altogether. + +=head2 Capability Events + +If there are capability accesses, the user is shown each capability +access and asked if the capability should be allowed, denied, or if the +user wants to quit. See capability(7) for details. + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +klogd(8), auditd(8), apparmor(7), apparmor.d(5), aa_change_hat(2), +logprof.conf(5), aa-genprof(1), aa-enforce(1), aa-complain(1), +aa-disable(1), and L. + +=cut diff --git a/Tools/manpages/aa-mergeprof.pod b/Tools/manpages/aa-mergeprof.pod new file mode 100644 index 000000000..63cef3c80 --- /dev/null +++ b/Tools/manpages/aa-mergeprof.pod @@ -0,0 +1,33 @@ +=pod + +=head1 NAME + +aa-mergeprof - merge AppArmor security profiles. + +=head1 SYNOPSIS + +BmineE> IuserE> IotherE> [I<-d /path/to/profiles>]> + +=head1 OPTIONS + +B<-d --dir /path/to/profiles> + + Specifies where to look for the AppArmor security profile set. + Defaults to /etc/apparmor.d. + +=head1 DESCRIPTION + +B + +=head1 BUGS + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +apparmor(7), apparmor.d(5), aa_change_hat(2), aa-genprof(1), +aa-logprof(1), aa-enforce(1), aa-audit(1), aa-complain(1), +aa-disable(1), and L. + +=cut diff --git a/Tools/manpages/aa-unconfined.pod b/Tools/manpages/aa-unconfined.pod new file mode 100644 index 000000000..ca006056b --- /dev/null +++ b/Tools/manpages/aa-unconfined.pod @@ -0,0 +1,64 @@ +# This publication is intellectual property of Novell Inc. and Canonical +# Ltd. Its contents can be duplicated, either in part or in whole, provided +# that a copyright label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither SUSE LINUX GmbH, Canonical Ltd, the authors, nor the translators +# shall be held liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. SUSE LINUX GmbH +# and Canonical Ltd. essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa-unconfined - output a list of processes with tcp or udp ports that do +not have AppArmor profiles loaded + +=head1 SYNOPSIS + +B]> + +=head1 OPTIONS + +B<--paranoid> + + Displays all processes from F filesystem with tcp or udp ports that + do no have AppArmor profiles loaded. + +=head1 DESCRIPTION + +B will use netstat(8) to determine which processes have open +network sockets and do not have AppArmor profiles loaded into the kernel. + +=head1 BUGS + +B must be run as root to retrieve the process executable +link from the F filesystem. This program is susceptible to race +conditions of several flavours: an unlinked executable will be mishandled; +an executable started before a AppArmor profile is loaded will not +appear in the output, despite running without confinement; a process that dies +between the netstat(8) and further checks will be mishandled. This +program only lists processes using TCP and UDP. In short, this +program is unsuitable for forensics use and is provided only as an aid +to profiling all network-accessible processes in the lab. + +If you find any bugs, please report them at +L. + +=head1 SEE ALSO + +netstat(8), apparmor(7), apparmor.d(5), aa_change_hat(2), and +L. + +=cut diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 78f0eab34..741b464e6 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -3,11 +3,8 @@ import locale def init_localisation(): locale.setlocale(locale.LC_ALL, '') - cur_locale = locale.getlocale() - filename = '' - #If a correct locale has been provided set filename else let a IOError be raised by '' path - if cur_locale[0]: - filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0][0:2] + #If a correct locale has been provided set filename else let an IOError be raised + filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0] try: trans = gettext.GNUTranslations(open(filename, 'rb')) except IOError: diff --git a/apparmor/aa.py b/apparmor/aa.py index d2bcc681e..39791c0ba 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1515,8 +1515,8 @@ def ask_the_questions(): audit_toggle = 0 - q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] # In complain mode: events default to allow # In enforce mode: events default to deny @@ -1538,12 +1538,12 @@ def ask_the_questions(): audit_toggle = not audit_toggle audit = '' if audit_toggle: - q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', - 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_OFF', + 'CMD_ABORT', 'CMD_FINISHED'] audit = 'audit' else: - q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED', ] q['headers'] = [_('Profile'), combine_name(profile, hat), _('Capability'), audit + capability, @@ -1762,9 +1762,9 @@ def ask_the_questions(): q['headers'] += [_('Severity'), severity] q['options'] = options q['selected'] = default_option - 1 - q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_GLOB', + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT', - 'CMD_FINISHED', 'CMD_OTHER', 'CMD_IGNORE_ENTRY'] + 'CMD_FINISHED', 'CMD_OTHER'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' @@ -1915,8 +1915,8 @@ def ask_the_questions(): q['headers'] += [_('Socket Type'), sock_type] audit_toggle = 0 - q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED', 'CMD_IGNORE_ENTRY'] + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': @@ -2078,34 +2078,25 @@ def delete_duplicates(profile, incname): deleted = 0 # Allow rules covered by denied rules shouldn't be deleted # only a subset allow rules may actually be denied -# deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) -# -# deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) -# -# deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']) -# -# deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) -# -# deleted += delete_path_duplicates(profile, incname, 'allow') -# deleted += delete_path_duplicates(profile, incname, 'deny') if include.get(incname, False): deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) - deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']) + deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']['capability']) deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') + elif filelist.get(incname, False): deleted += delete_net_duplicates(profile['allow']['netdomain'], filelist[incname][incname]['allow']['netdomain']) deleted += delete_net_duplicates(profile['deny']['netdomain'], filelist[incname][incname]['deny']['netdomain']) - deleted += delete_cap_duplicates(profile['allow']['capability'], filelist[incname][incname]['allow']) + deleted += delete_cap_duplicates(profile['allow']['capability'], filelist[incname][incname]['allow']['capability']) deleted += delete_cap_duplicates(profile['deny']['capability'], filelist[incname][incname]['deny']['capability']) @@ -2114,9 +2105,6 @@ def delete_duplicates(profile, incname): return deleted - - return deleted - def match_net_include(incname, family, type): includelist = incname[:] checked = [] diff --git a/apparmor/ui.py b/apparmor/ui.py index 2352a0e38..aa1e3645e 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -410,7 +410,7 @@ def Text_PromptUser(question): elif options and re.search('^\d$', ans): ans = int(ans) - if ans > 0 and ans < len(options): + if ans > 0 and ans <= len(options): selected = ans - 1 ans = 'XXXINVALIDXXX' From b512123303e7a8e34e4b300e287b01b4fccdaf8c Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 19 Sep 2013 21:20:40 +0530 Subject: [PATCH 064/183] Finally added the translations pot file for the current codebase --- Translate/messages.pot | 646 +++++++++++++++++++++++++++++++++++++++++ apparmor/aa.py | 32 +- apparmor/tools.py | 2 +- apparmor/ui.py | 122 ++++---- 4 files changed, 714 insertions(+), 88 deletions(-) create mode 100644 Translate/messages.pot diff --git a/Translate/messages.pot b/Translate/messages.pot new file mode 100644 index 000000000..f73b67656 --- /dev/null +++ b/Translate/messages.pot @@ -0,0 +1,646 @@ +# Translations for AppArmor Profile Tools. +# Copyright (C) 2013 +# Kshitij Gupta , 2013. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"POT-Creation-Date: 2013-09-19 21:16+IST\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: ENCODING\n" +"Generated-By: pygettext.py 1.5\n" + + +#: ./../Tools/aa-genprof:48 ./../Tools/aa-logprof:20 +#: ./../Tools/aa-unconfined:17 +msgid "It seems AppArmor was not started. Please enable AppArmor and try again." +msgstr "" + +#: ./../Tools/aa-genprof:67 +msgid "Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' in another window in order to find the fully-qualified path and use the full path as parameter." +msgstr "" + +#: ./../Tools/aa-genprof:69 +msgid "%s does not exists, please double-check the path." +msgstr "" + +#: ./../Tools/aa-genprof:97 +msgid "" +"\n" +"Before you begin, you may wish to check if a\n" +"profile already exists for the application you\n" +"wish to confine. See the following wiki page for\n" +"more information:\n" +"http://wiki.apparmor.net/index.php/Profiles" +msgstr "" + +#: ./../Tools/aa-genprof:99 +msgid "" +"Please start the application to be profiled in\n" +"another window and exercise its functionality now.\n" +"\n" +"Once completed, select the \"Scan\" option below in \n" +"order to scan the system logs for AppArmor events. \n" +"\n" +"For each AppArmor event, you will be given the \n" +"opportunity to choose whether the access should be \n" +"allowed or denied." +msgstr "" + +#: ./../Tools/aa-genprof:120 +msgid "Profiling" +msgstr "" + +#: ./../Tools/aa-genprof:138 +msgid "" +"\n" +"Reloaded AppArmor profiles in enforce mode." +msgstr "" + +#: ./../Tools/aa-genprof:139 +msgid "" +"\n" +"Please consider contributing your new profile!\n" +"See the following wiki page for more information:\n" +"http://wiki.apparmor.net/index.php/Profiles\n" +msgstr "" + +#: ./../Tools/aa-genprof:140 +msgid "Finished generating profile for %s." +msgstr "" + +#: ./../Tools/aa-unconfined:56 +msgid "" +"%s %s (%s) not confined\n" +msgstr "" + +#: ./../Tools/aa-unconfined:60 +msgid "" +"%s %s %snot confined\n" +msgstr "" + +#: ./../Tools/aa-unconfined:65 +msgid "" +"%s %s (%s) confined by '%s'\n" +msgstr "" + +#: ./../Tools/aa-unconfined:69 +msgid "" +"%s %s %sconfined by '%s'\n" +msgstr "" + +#: aa.py:263 +msgid "Unable to find basename for %s." +msgstr "" + +#: aa.py:420 ui.py:270 +msgid "Are you sure you want to abandon this set of profile changes and exit?" +msgstr "" + +#: aa.py:422 ui.py:272 +msgid "Abandoning all changes." +msgstr "" + +#: aa.py:564 +msgid "%s contains no profile" +msgstr "" + +#: aa.py:936 aa.py:1192 aa.py:1496 aa.py:1532 aa.py:1695 aa.py:1897 aa.py:1928 +msgid "Profile" +msgstr "" + +#: aa.py:939 +msgid "Default Hat" +msgstr "" + +#: aa.py:941 +msgid "Requested Hat" +msgstr "" + +#: aa.py:1194 +msgid "Program" +msgstr "" + +#: aa.py:1197 +msgid "Execute" +msgstr "" + +#: aa.py:1198 aa.py:1498 aa.py:1534 aa.py:1746 +msgid "Severity" +msgstr "" + +#: aa.py:1221 +msgid "Are you specifying a transition to a local profile?" +msgstr "" + +#: aa.py:1233 +msgid "Enter profile name to transition to: " +msgstr "" + +#: aa.py:1242 +msgid "" +"Should AppArmor sanitise the environment when\n" +"switching profiles?\n" +"\n" +"Sanitising environment is more secure,\n" +"but some applications depend on the presence\n" +"of LD_PRELOAD or LD_LIBRARY_PATH." +msgstr "" + +#: aa.py:1244 +msgid "" +"Should AppArmor sanitise the environment when\n" +"switching profiles?\n" +"\n" +"Sanitising environment is more secure,\n" +"but this application appears to be using LD_PRELOAD\n" +"or LD_LIBRARY_PATH and sanitising the environment\n" +"could cause functionality problems." +msgstr "" + +#: aa.py:1252 +msgid "" +"Launching processes in an unconfined state is a very\n" +"dangerous operation and can cause serious security holes.\n" +"\n" +"Are you absolutely certain you wish to remove all\n" +"AppArmor protection when executing : %s ?" +msgstr "" + +#: aa.py:1254 +msgid "" +"Should AppArmor sanitise the environment when\n" +"running this program unconfined?\n" +"\n" +"Not sanitising the environment when unconfining\n" +"a program opens up significant security holes\n" +"and should be avoided if at all possible." +msgstr "" + +#: aa.py:1330 +msgid "" +"A profile for %s does not exist.\n" +"Do you want to create one?" +msgstr "" + +#: aa.py:1348 +msgid "A local profile for %s does not exit. Create one?" +msgstr "" + +#: aa.py:1457 +msgid "Complain-mode changes:" +msgstr "" + +#: aa.py:1459 +msgid "Enforce-mode changes:" +msgstr "" + +#: aa.py:1462 +msgid "Invalid mode found: %s" +msgstr "" + +#: aa.py:1497 aa.py:1533 +msgid "Capability" +msgstr "" + +#: aa.py:1547 aa.py:1782 +msgid "Adding %s to profile." +msgstr "" + +#: aa.py:1549 aa.py:1784 aa.py:1824 aa.py:1946 +msgid "Deleted %s previous matching profile entries." +msgstr "" + +#: aa.py:1556 +msgid "Adding capability %s to profile." +msgstr "" + +#: aa.py:1563 +msgid "Denying capability %s to profile." +msgstr "" + +#: aa.py:1696 +msgid "Path" +msgstr "" + +#: aa.py:1705 aa.py:1736 +msgid "(owner permissions off)" +msgstr "" + +#: aa.py:1710 +msgid "(force new perms to owner)" +msgstr "" + +#: aa.py:1713 +msgid "(force all rule perms to owner)" +msgstr "" + +#: aa.py:1725 +msgid "Old Mode" +msgstr "" + +#: aa.py:1726 +msgid "New Mode" +msgstr "" + +#: aa.py:1741 +msgid "(force perms to owner)" +msgstr "" + +#: aa.py:1744 +msgid "Mode" +msgstr "" + +#: aa.py:1822 +msgid "Adding %s %s to profile" +msgstr "" + +#: aa.py:1840 +msgid "Enter new path:" +msgstr "" + +#: aa.py:1843 +msgid "The specified path does not match this log entry:" +msgstr "" + +#: aa.py:1844 +msgid "Log Entry" +msgstr "" + +#: aa.py:1845 +msgid "Entered Path" +msgstr "" + +#: aa.py:1846 +msgid "Do you really want to use this path?" +msgstr "" + +#: aa.py:1898 aa.py:1929 +msgid "Network Family" +msgstr "" + +#: aa.py:1899 aa.py:1930 +msgid "Socket Type" +msgstr "" + +#: aa.py:1944 +msgid "Adding %s to profile" +msgstr "" + +#: aa.py:1954 +msgid "Adding network access %s %s to profile." +msgstr "" + +#: aa.py:1960 +msgid "Denying network access %s %s to profile" +msgstr "" + +#: aa.py:2171 +msgid "Reading log entries from %s." +msgstr "" + +#: aa.py:2174 +msgid "Updating AppArmor profiles in %s." +msgstr "" + +#: aa.py:2178 +msgid "unknown" +msgstr "" + +#: aa.py:2241 +msgid "" +"Select which profile changes you would like to save to the\n" +"local profile set." +msgstr "" + +#: aa.py:2242 +msgid "Local profile changes" +msgstr "" + +#: aa.py:2264 +msgid "The following local profiles were changed. Would you like to save them?" +msgstr "" + +#: aa.py:2338 +msgid "Profile Changes" +msgstr "" + +#: aa.py:3831 +msgid "Writing updated profile for %s." +msgstr "" + +#: aa.py:4074 +msgid "" +"%s is currently marked as a program that should not have its own\n" +"profile. Usually, programs are marked this way if creating a profile for \n" +"them is likely to break the rest of the system. If you know what you're\n" +"doing and are certain you want to create a profile for this program, edit\n" +"the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf." +msgstr "" + +#: logparser.py:117 logparser.py:122 +msgid "Log contains unknown mode %s" +msgstr "" + +#: tools.py:51 +msgid "The given program cannot be found, please try with the fully qualified path name of the program: " +msgstr "" + +#: tools.py:53 tools.py:107 +msgid "%s does not exist, please double-check the path." +msgstr "" + +#: tools.py:70 +msgid "Profile for %s not found, skipping" +msgstr "" + +#: tools.py:74 +msgid "" +"Disabling %s.\n" +msgstr "" + +#: tools.py:77 +msgid "" +"Enabling %s.\n" +msgstr "" + +#: tools.py:82 +msgid "" +"Setting %s to audit mode.\n" +msgstr "" + +#: tools.py:84 +msgid "" +"Removing audit mode from %s.\n" +msgstr "" + +#: tools.py:105 +msgid "" +"Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.\n" +"Please use the full path as parameter" +msgstr "" + +#: tools.py:122 +msgid "The profile for %s does not exists. Nothing to clean." +msgstr "" + +#: ui.py:46 +msgid "Invalid hotkey for" +msgstr "" + +#: ui.py:60 ui.py:98 ui.py:238 +msgid "(Y)es" +msgstr "" + +#: ui.py:61 ui.py:99 ui.py:239 +msgid "(N)o" +msgstr "" + +#: ui.py:100 +msgid "(C)ancel" +msgstr "" + +#: ui.py:186 +msgid "(A)llow" +msgstr "" + +#: ui.py:187 +msgid "(M)ore" +msgstr "" + +#: ui.py:188 +msgid "Audi(t)" +msgstr "" + +#: ui.py:189 +msgid "Audi(t) off" +msgstr "" + +#: ui.py:190 +msgid "Audit (A)ll" +msgstr "" + +#: ui.py:192 +msgid "(O)wner permissions on" +msgstr "" + +#: ui.py:193 +msgid "(O)wner permissions off" +msgstr "" + +#: ui.py:194 +msgid "(D)eny" +msgstr "" + +#: ui.py:195 +msgid "Abo(r)t" +msgstr "" + +#: ui.py:196 +msgid "(F)inish" +msgstr "" + +#: ui.py:197 +msgid "(I)nherit" +msgstr "" + +#: ui.py:198 +msgid "(P)rofile" +msgstr "" + +#: ui.py:199 +msgid "(P)rofile Clean Exec" +msgstr "" + +#: ui.py:200 +msgid "(C)hild" +msgstr "" + +#: ui.py:201 +msgid "(C)hild Clean Exec" +msgstr "" + +#: ui.py:202 +msgid "Named" +msgstr "" + +#: ui.py:203 +msgid "Named Clean Exec" +msgstr "" + +#: ui.py:204 +msgid "(U)nconfined" +msgstr "" + +#: ui.py:205 +msgid "(U)nconfined Clean Exec" +msgstr "" + +#: ui.py:206 +msgid "(P)rofile Inherit" +msgstr "" + +#: ui.py:207 +msgid "(P)rofile Inherit Clean Exec" +msgstr "" + +#: ui.py:208 +msgid "(C)hild Inherit" +msgstr "" + +#: ui.py:209 +msgid "(C)hild Inherit Clean Exec" +msgstr "" + +#: ui.py:210 +msgid "(N)amed Inherit" +msgstr "" + +#: ui.py:211 +msgid "(N)amed Inherit Clean Exec" +msgstr "" + +#: ui.py:212 +msgid "(X) ix On" +msgstr "" + +#: ui.py:213 +msgid "(X) ix Off" +msgstr "" + +#: ui.py:214 ui.py:228 +msgid "(S)ave Changes" +msgstr "" + +#: ui.py:215 +msgid "(C)ontinue Profiling" +msgstr "" + +#: ui.py:216 +msgid "(N)ew" +msgstr "" + +#: ui.py:217 +msgid "(G)lob" +msgstr "" + +#: ui.py:218 +msgid "Glob with (E)xtension" +msgstr "" + +#: ui.py:219 +msgid "(A)dd Requested Hat" +msgstr "" + +#: ui.py:220 +msgid "(U)se Default Hat" +msgstr "" + +#: ui.py:221 +msgid "(S)can system log for AppArmor events" +msgstr "" + +#: ui.py:222 +msgid "(H)elp" +msgstr "" + +#: ui.py:223 +msgid "(V)iew Profile" +msgstr "" + +#: ui.py:224 +msgid "(U)se Profile" +msgstr "" + +#: ui.py:225 +msgid "(C)reate New Profile" +msgstr "" + +#: ui.py:226 +msgid "(U)pdate Profile" +msgstr "" + +#: ui.py:227 +msgid "(I)gnore Update" +msgstr "" + +#: ui.py:229 +msgid "Save Selec(t)ed Profile" +msgstr "" + +#: ui.py:230 +msgid "(U)pload Changes" +msgstr "" + +#: ui.py:231 +msgid "(V)iew Changes" +msgstr "" + +#: ui.py:232 +msgid "View Changes b/w (C)lean profiles" +msgstr "" + +#: ui.py:233 +msgid "(V)iew" +msgstr "" + +#: ui.py:234 +msgid "(E)nable Repository" +msgstr "" + +#: ui.py:235 +msgid "(D)isable Repository" +msgstr "" + +#: ui.py:236 +msgid "(N)ever Ask Again" +msgstr "" + +#: ui.py:237 +msgid "Ask Me (L)ater" +msgstr "" + +#: ui.py:240 +msgid "Allow All (N)etwork" +msgstr "" + +#: ui.py:241 +msgid "Allow Network Fa(m)ily" +msgstr "" + +#: ui.py:242 +msgid "(O)verwrite Profile" +msgstr "" + +#: ui.py:243 +msgid "(K)eep Profile" +msgstr "" + +#: ui.py:244 +msgid "(C)ontinue" +msgstr "" + +#: ui.py:245 +msgid "(I)gnore" +msgstr "" + +#: ui.py:316 +msgid "Unknown command" +msgstr "" + +#: ui.py:323 +msgid "Duplicate hotkey for" +msgstr "" + +#: ui.py:335 +msgid "Invalid hotkey in default item" +msgstr "" + +#: ui.py:340 +msgid "Invalid default" +msgstr "" + diff --git a/apparmor/aa.py b/apparmor/aa.py index 39791c0ba..adb1c8282 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1239,18 +1239,9 @@ def handle_children(profile, hat, root): match = regex_optmode.search(ans).groups()[0] exec_mode = str_to_mode(match) px_default = 'n' - px_msg = _('Should AppArmor sanitise the environment when\n' + - 'switching profiles?\n\n' + - 'Sanitising environment is more secure,\n' + - 'but some applications depend on the presence\n' + - 'of LD_PRELOAD or LD_LIBRARY_PATH.') + px_msg = _("""Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.""") if parent_uses_ld_xxx: - px_msg = _('Should AppArmor sanitise the environment when\n' + - 'switching profiles?\n\n' + - 'Sanitising environment is more secure,\n' + - 'but this application appears to be using LD_PRELOAD\n' + - 'or LD_LIBRARY_PATH and sanitising the environment\n' + - 'could cause functionality problems.') + px_msg = _("""Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.""") ynans = UI_YesNo(px_msg, px_default) if ynans == 'y': @@ -1258,16 +1249,9 @@ def handle_children(profile, hat, root): exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) elif ans == 'CMD_ux': exec_mode = str_to_mode('ux') - ynans = UI_YesNo(_('Launching processes in an unconfined state is a very\n' + - 'dangerous operation and can cause serious security holes.\n\n' + - 'Are you absolutely certain you wish to remove all\n' + - 'AppArmor protection when executing :') + '%s ?' % exec_target, 'n') + ynans = UI_YesNo(_("""Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing : %s ?""") % exec_target, 'n') if ynans == 'y': - ynans = UI_YesNo(_('Should AppArmor sanitise the environment when\n' + - 'running this program unconfined?\n\n' + - 'Not sanitising the environment when unconfining\n' + - 'a program opens up significant security holes\n' + - 'and should be avoided if at all possible.'), 'y') + ynans = UI_YesNo(_("""Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."""), 'y') if ynans == 'y': # Disable the unsafe mode exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) @@ -1967,7 +1951,7 @@ def ask_the_questions(): changed[profile] = True - UI_Info(_('Adding network access %s %s to profile.' % (family, sock_type))) + UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) elif ans == 'CMD_DENY': done = True @@ -4087,11 +4071,7 @@ def suggest_incs_for_path(incname, path, allow): def check_qualifiers(program): if cfg['qualifiers'].get(program, False): if cfg['qualifiers'][program] != 'p': - fatal_error(_('%s is currently marked as a program that should not have its own\n' + - 'profile. Usually, programs are marked this way if creating a profile for \n' + - 'them is likely to break the rest of the system. If you know what you\'re\n' + - 'doing and are certain you want to create a profile for this program, edit\n' + - 'the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.') %program) + fatal_error(_("""%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.""") %program) return False def get_subdirectories(current_dir): diff --git a/apparmor/tools.py b/apparmor/tools.py index a01ab4f09..7d5df9ac5 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -7,7 +7,7 @@ import apparmor.aa as apparmor class aa_tools: def __init__(self, tool_name, args): self.name = tool_name - self.profiledir = args.d + self.profiledir = args.dir self.profiling = args.program self.check_profile_dir() diff --git a/apparmor/ui.py b/apparmor/ui.py index aa1e3645e..ced5b2c09 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -183,66 +183,66 @@ def UI_BusyStop(): ypath, yarg = GetDataFromYast() CMDS = { - 'CMD_ALLOW': '(A)llow', - 'CMD_OTHER': '(M)ore', - 'CMD_AUDIT_NEW': 'Audi(t)', - 'CMD_AUDIT_OFF': 'Audi(t) off', - 'CMD_AUDIT_FULL': 'Audit (A)ll', + 'CMD_ALLOW': _('(A)llow'), + 'CMD_OTHER': _('(M)ore'), + 'CMD_AUDIT_NEW': _('Audi(t)'), + 'CMD_AUDIT_OFF': _('Audi(t) off'), + 'CMD_AUDIT_FULL': _('Audit (A)ll'), #'CMD_OTHER': '(O)pts', - 'CMD_USER_ON': '(O)wner permissions on', - 'CMD_USER_OFF': '(O)wner permissions off', - 'CMD_DENY': '(D)eny', - 'CMD_ABORT': 'Abo(r)t', - 'CMD_FINISHED': '(F)inish', - 'CMD_ix': '(I)nherit', - 'CMD_px': '(P)rofile', - 'CMD_px_safe': '(P)rofile Clean Exec', - 'CMD_cx': '(C)hild', - 'CMD_cx_safe': '(C)hild Clean Exec', - 'CMD_nx': 'Named', - 'CMD_nx_safe': 'Named Clean Exec', - 'CMD_ux': '(U)nconfined', - 'CMD_ux_safe': '(U)nconfined Clean Exec', - 'CMD_pix': '(P)rofile Inherit', - 'CMD_pix_safe': '(P)rofile Inherit Clean Exec', - 'CMD_cix': '(C)hild Inherit', - 'CMD_cix_safe': '(C)hild Inherit Clean Exec', - 'CMD_nix': '(N)amed Inherit', - 'CMD_nix_safe': '(N)amed Inherit Clean Exec', - 'CMD_EXEC_IX_ON': '(X) ix On', - 'CMD_EXEC_IX_OFF': '(X) ix Off', - 'CMD_SAVE': '(S)ave Changes', - 'CMD_CONTINUE': '(C)ontinue Profiling', - 'CMD_NEW': '(N)ew', - 'CMD_GLOB': '(G)lob', - 'CMD_GLOBEXT': 'Glob with (E)xtension', - 'CMD_ADDHAT': '(A)dd Requested Hat', - 'CMD_USEDEFAULT': '(U)se Default Hat', - 'CMD_SCAN': '(S)can system log for AppArmor events', - 'CMD_HELP': '(H)elp', - 'CMD_VIEW_PROFILE': '(V)iew Profile', - 'CMD_USE_PROFILE': '(U)se Profile', - 'CMD_CREATE_PROFILE': '(C)reate New Profile', - 'CMD_UPDATE_PROFILE': '(U)pdate Profile', - 'CMD_IGNORE_UPDATE': '(I)gnore Update', - 'CMD_SAVE_CHANGES': '(S)ave Changes', - 'CMD_SAVE_SELECTED': 'Save Selec(t)ed Profile', - 'CMD_UPLOAD_CHANGES': '(U)pload Changes', - 'CMD_VIEW_CHANGES': '(V)iew Changes', - 'CMD_VIEW_CHANGES_CLEAN': 'View Changes b/w (C)lean profiles', - 'CMD_VIEW': '(V)iew', - 'CMD_ENABLE_REPO': '(E)nable Repository', - 'CMD_DISABLE_REPO': '(D)isable Repository', - 'CMD_ASK_NEVER': '(N)ever Ask Again', - 'CMD_ASK_LATER': 'Ask Me (L)ater', - 'CMD_YES': '(Y)es', - 'CMD_NO': '(N)o', - 'CMD_ALL_NET': 'Allow All (N)etwork', - 'CMD_NET_FAMILY': 'Allow Network Fa(m)ily', - 'CMD_OVERWRITE': '(O)verwrite Profile', - 'CMD_KEEP': '(K)eep Profile', - 'CMD_CONTINUE': '(C)ontinue', - 'CMD_IGNORE_ENTRY': '(I)gnore' + 'CMD_USER_ON': _('(O)wner permissions on'), + 'CMD_USER_OFF': _('(O)wner permissions off'), + 'CMD_DENY': _('(D)eny'), + 'CMD_ABORT': _('Abo(r)t'), + 'CMD_FINISHED': _('(F)inish'), + 'CMD_ix': _('(I)nherit'), + 'CMD_px': _('(P)rofile'), + 'CMD_px_safe': _('(P)rofile Clean Exec'), + 'CMD_cx': _('(C)hild'), + 'CMD_cx_safe': _('(C)hild Clean Exec'), + 'CMD_nx': _('Named'), + 'CMD_nx_safe': _('Named Clean Exec'), + 'CMD_ux': _('(U)nconfined'), + 'CMD_ux_safe': _('(U)nconfined Clean Exec'), + 'CMD_pix': _('(P)rofile Inherit'), + 'CMD_pix_safe': _('(P)rofile Inherit Clean Exec'), + 'CMD_cix': _('(C)hild Inherit'), + 'CMD_cix_safe': _('(C)hild Inherit Clean Exec'), + 'CMD_nix': _('(N)amed Inherit'), + 'CMD_nix_safe': _('(N)amed Inherit Clean Exec'), + 'CMD_EXEC_IX_ON': _('(X) ix On'), + 'CMD_EXEC_IX_OFF': _('(X) ix Off'), + 'CMD_SAVE': _('(S)ave Changes'), + 'CMD_CONTINUE': _('(C)ontinue Profiling'), + 'CMD_NEW': _('(N)ew'), + 'CMD_GLOB': _('(G)lob'), + 'CMD_GLOBEXT': _('Glob with (E)xtension'), + 'CMD_ADDHAT': _('(A)dd Requested Hat'), + 'CMD_USEDEFAULT': _('(U)se Default Hat'), + 'CMD_SCAN': _('(S)can system log for AppArmor events'), + 'CMD_HELP': _('(H)elp'), + 'CMD_VIEW_PROFILE': _('(V)iew Profile'), + 'CMD_USE_PROFILE': _('(U)se Profile'), + 'CMD_CREATE_PROFILE': _('(C)reate New Profile'), + 'CMD_UPDATE_PROFILE': _('(U)pdate Profile'), + 'CMD_IGNORE_UPDATE': _('(I)gnore Update'), + 'CMD_SAVE_CHANGES': _('(S)ave Changes'), + 'CMD_SAVE_SELECTED': _('Save Selec(t)ed Profile'), + 'CMD_UPLOAD_CHANGES': _('(U)pload Changes'), + 'CMD_VIEW_CHANGES': _('(V)iew Changes'), + 'CMD_VIEW_CHANGES_CLEAN': _('View Changes b/w (C)lean profiles'), + 'CMD_VIEW': _('(V)iew'), + 'CMD_ENABLE_REPO': _('(E)nable Repository'), + 'CMD_DISABLE_REPO': _('(D)isable Repository'), + 'CMD_ASK_NEVER': _('(N)ever Ask Again'), + 'CMD_ASK_LATER': _('Ask Me (L)ater'), + 'CMD_YES': _('(Y)es'), + 'CMD_NO': _('(N)o'), + 'CMD_ALL_NET': _('Allow All (N)etwork'), + 'CMD_NET_FAMILY': _('Allow Network Fa(m)ily'), + 'CMD_OVERWRITE': _('(O)verwrite Profile'), + 'CMD_KEEP': _('(K)eep Profile'), + 'CMD_CONTINUE': _('(C)ontinue'), + 'CMD_IGNORE_ENTRY': _('(I)gnore') } def UI_PromptUser(q, params=''): @@ -315,7 +315,7 @@ def Text_PromptUser(question): if not CMDS.get(cmd, False): raise AppArmorException('PromptUser: %s %s' %(_('Unknown command'), cmd)) - menutext = _(CMDS[cmd]) + menutext = CMDS[cmd] key = get_translated_hotkey(menutext).lower() # Duplicate hotkey @@ -331,7 +331,7 @@ def Text_PromptUser(question): default_key = 0 if default and CMDS[default]: - defaulttext = _(CMDS[default]) + defaulttext = CMDS[default] defmsg = 'PromptUser: ' + _('Invalid hotkey in default item') default_key = get_translated_hotkey(defaulttext, defmsg).lower() From 0b73862cfea3e164db0cac7212749191aee93cc3 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 20 Sep 2013 19:20:41 +0530 Subject: [PATCH 065/183] rev 63-64, fixes man pages, messages --- Tools/aa-genprof | 12 +- Tools/aa-logprof | 8 +- Tools/aa-unconfined | 4 +- Tools/manpages/aa-audit.pod | 6 +- Tools/manpages/aa-autodep.pod | 6 +- Tools/manpages/aa-cleanprof.pod | 7 +- Tools/manpages/aa-complain.pod | 10 +- Tools/manpages/aa-disable.pod | 10 +- Tools/manpages/aa-enforce.pod | 14 +- Tools/manpages/aa-genprof.pod | 4 +- Tools/manpages/aa-logprof.pod | 18 +-- Tools/manpages/aa-mergeprof.pod | 2 +- Tools/manpages/aa-unconfined.pod | 8 +- Translate/README | 10 ++ Translate/messages.pot | 212 ++++++++++++++++++++----------- apparmor/aa.py | 34 +++-- apparmor/tools.py | 2 +- apparmor/ui.py | 10 +- 18 files changed, 227 insertions(+), 150 deletions(-) create mode 100644 Translate/README diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 0a573a4ef..4acaade53 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -40,8 +40,8 @@ parser.add_argument('program', type=str, help='name of program to profile') args = parser.parse_args() profiling = args.program -profiledir = args.d -filename = args.f +profiledir = args.dir +filename = args.file aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: @@ -50,7 +50,7 @@ if not aa_mountpoint: if profiledir: apparmor.profile_dir = apparmor.get_full_path(profiledir) if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("%s is not a directory." %profiledir) + raise apparmor.AppArmorException(_("%s is not a directory.") %profiledir) program = None #if os.path.exists(apparmor.which(profiling.strip())): @@ -64,7 +64,7 @@ else: if not program or not os.path.exists(program): if '/' not in profiling: - raise apparmor.AppArmorException(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' in another window in order to find the fully-qualified path and use the full path as parameter.") %(profiling, profiling)) + raise apparmor.AppArmorException(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") %(profiling, profiling)) else: raise apparmor.AppArmorException(_('%s does not exists, please double-check the path.') %profiling) @@ -94,7 +94,7 @@ sysctl_write(ratelimit_sysctl, 0) atexit.register(restore_ratelimit) -apparmor.UI_Info(_('\nBefore you begin, you may wish to check if a\nprofile already exists for the application you\nwish to confine. See the following wiki page for\nmore information:\nhttp://wiki.apparmor.net/index.php/Profiles')) +apparmor.UI_Info(_('\nBefore you begin, you may wish to check if a\nprofile already exists for the application you\nwish to confine. See the following wiki page for\nmore information:')+'\nhttp://wiki.apparmor.net/index.php/Profiles') apparmor.UI_Important(_('Please start the application to be profiled in\nanother window and exercise its functionality now.\n\nOnce completed, select the "Scan" option below in \norder to scan the system logs for AppArmor events. \n\nFor each AppArmor event, you will be given the \nopportunity to choose whether the access should be \nallowed or denied.')) @@ -136,6 +136,6 @@ for p in sorted(apparmor.helpers.keys()): reload(p) apparmor.UI_Info(_('\nReloaded AppArmor profiles in enforce mode.')) -apparmor.UI_Info(_('\nPlease consider contributing your new profile!\nSee the following wiki page for more information:\nhttp://wiki.apparmor.net/index.php/Profiles\n')) +apparmor.UI_Info(_('\nPlease consider contributing your new profile!\nSee the following wiki page for more information:')+'\nhttp://wiki.apparmor.net/index.php/Profiles\n') apparmor.UI_Info(_('Finished generating profile for %s.')%program) sys.exit(0) diff --git a/Tools/aa-logprof b/Tools/aa-logprof index 065cbe80c..51fc387bc 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -8,12 +8,12 @@ import apparmor.aa as apparmor parser = argparse.ArgumentParser(description='Process log entries to generate profiles') parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('-f', '--file', type=str, help='path to logfile') -parser.add_argument('-m', type=str, help='mark in the log to start processing after') +parser.add_argument('-m', '--mark', type=str, help='mark in the log to start processing after') args = parser.parse_args() -profiledir = args.d -filename = args.f -logmark = args.m or '' +profiledir = args.dir +filename = args.file +logmark = args.mark or '' aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index cc9159f6d..989459d79 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -21,7 +21,9 @@ if paranoid: pids = list(filter(lambda x: re.search('^\d+$', x), apparmor.get_subdirectories('/proc'))) else: regex_tcp_udp = re.compile('^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)') - output = apparmor.cmd(['netstat','-nlp'])[1].split('\n') + import subprocess + output = subprocess.check_output('LANG=en netstat -nlp', shell=True).split('\n') + for line in output: match = regex_tcp_udp.search(line) if match: diff --git a/Tools/manpages/aa-audit.pod b/Tools/manpages/aa-audit.pod index d429a1559..9898782fc 100644 --- a/Tools/manpages/aa-audit.pod +++ b/Tools/manpages/aa-audit.pod @@ -2,7 +2,7 @@ =head1 NAME -aa-audit - set a AppArmor security profile to I mode. +aa-audit - set an AppArmor security profile to I mode. =head1 SYNOPSIS @@ -21,7 +21,7 @@ B<-r --remove> =head1 DESCRIPTION -B is used to set the audit mode for one or more profiles to audit. +B is used to set one or more profiles to audit mode. In this mode security policy is enforced and all access (successes and failures) are logged to the system log. The I<--remove> option can be used to remove the audit mode for the profile. @@ -29,7 +29,7 @@ The I<--remove> option can be used to remove the audit mode for the profile. =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-autodep.pod b/Tools/manpages/aa-autodep.pod index d9930729d..06ae2f2b5 100644 --- a/Tools/manpages/aa-autodep.pod +++ b/Tools/manpages/aa-autodep.pod @@ -37,7 +37,7 @@ B<-d --dir /path/to/profiles> B<-f --force> - Overrides any existing AppArmor profile for the executable with the generated minimal AppArmor profile. + Overwrites any existing AppArmor profile for the executable with the generated minimal AppArmor profile. =head1 DESCRIPTION @@ -48,7 +48,7 @@ a base profile containing a base include directive which includes basic profile entries needed by most programs. The profile is generated by recursively calling ldd(1) on the executables listed on the command line. -The I<--force> option will override any existing profile for the executable with +The I<--force> option will overwrite any existing profile for the executable with the newly generated minimal AppArmor profile. =head1 BUGS @@ -56,7 +56,7 @@ the newly generated minimal AppArmor profile. This program does not perform full static analysis of executables, so the profiles generated are necessarily incomplete. If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-cleanprof.pod b/Tools/manpages/aa-cleanprof.pod index 88b5ea448..d2485fd38 100644 --- a/Tools/manpages/aa-cleanprof.pod +++ b/Tools/manpages/aa-cleanprof.pod @@ -18,13 +18,14 @@ B<-d --dir /path/to/profiles> =head1 DESCRIPTION B is used to perform a cleanup on one or more profiles. -The tool removes any existing superfluous rules, reorders the rules to group -similar rules together and removes all comments. +The tool removes any existing superfluous rules (Rules that are covered +under an include or another rule),reorders the rules to group similar rules +together and removes all comments. =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-complain.pod b/Tools/manpages/aa-complain.pod index e41dd886f..cb4f81258 100644 --- a/Tools/manpages/aa-complain.pod +++ b/Tools/manpages/aa-complain.pod @@ -22,7 +22,7 @@ =head1 NAME -aa-complain - set a AppArmor security profile to I mode. +aa-complain - set an AppArmor security profile to I mode. =head1 SYNOPSIS @@ -41,9 +41,9 @@ B<-r --remove> =head1 DESCRIPTION -B is used to set the enforcement mode for one or more profiles to -complain. In this mode security policy is not enforced but rather access -violations are logged to the system log. +B is used to set one or more profiles to I mode. +In this mode security policy is not enforced but rather access violations +are logged to the system log. The I<--remove> option can be used to remove the complain mode for the profile, setting it to enforce mode by default. @@ -51,7 +51,7 @@ setting it to enforce mode by default. =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-disable.pod b/Tools/manpages/aa-disable.pod index 7af82f847..85c765329 100644 --- a/Tools/manpages/aa-disable.pod +++ b/Tools/manpages/aa-disable.pod @@ -41,11 +41,11 @@ B<-r --revert> =head1 DESCRIPTION -B is used to disable the enforcement mode for one or more -profiles. This command will unload the profile from the kernel and -prevent the profile from being loaded on AppArmor startup. The -I and I utilities may be used to to change this -behavior. +B is used to I one or more profiles. +This command will unload the profile from the kernel and prevent the +profile from being loaded on AppArmor startup. +The I and I utilities may be used to to change +this behavior. The I<--revert> option can be used to enable the profile. diff --git a/Tools/manpages/aa-enforce.pod b/Tools/manpages/aa-enforce.pod index 9577288f1..53aef579b 100644 --- a/Tools/manpages/aa-enforce.pod +++ b/Tools/manpages/aa-enforce.pod @@ -42,12 +42,12 @@ B<-r --remove> =head1 DESCRIPTION -B is used to set the enforcement mode for one or more profiles -to I. This command is only relevant in conjunction with the -I utility which sets a profile to complain mode and the -I utility which unloads and disables a profile. The default -mode for a security policy is enforce and the I utility must -be run to change this behavior. +B is used to set one or more profiles to I mode. +This command is only relevant in conjunction with the I utility +which sets a profile to complain mode and the I utility which +unloads and disables a profile. +The default mode for a security policy is enforce and the I +utility must be run to change this behavior. The I<--remove> option can be used to remove the enforce mode for the profile, setting it to complain mode. @@ -55,7 +55,7 @@ setting it to complain mode. =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-genprof.pod b/Tools/manpages/aa-genprof.pod index d2f2e38fa..c259408a1 100644 --- a/Tools/manpages/aa-genprof.pod +++ b/Tools/manpages/aa-genprof.pod @@ -72,7 +72,7 @@ using aa-logprof(1). After the user finishes selecting profile entries based on violations that were detected during the program execution, aa-genprof will reload the updated profiles in complain mode and again prompt the user for (S)can and -(D)one. This cycle can then be repeated as necessary until all application +(F)inish. This cycle can then be repeated as necessary until all application functionality has been exercised without generating access violations. When the user eventually hits (F)inish, aa-genprof will set the main profile, @@ -81,7 +81,7 @@ and any other profiles that were generated, into enforce mode and exit. =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-logprof.pod b/Tools/manpages/aa-logprof.pod index a703a4061..cb10d3b7d 100644 --- a/Tools/manpages/aa-logprof.pod +++ b/Tools/manpages/aa-logprof.pod @@ -22,7 +22,7 @@ =head1 NAME -aa-logprof - utility program for managing AppArmor security profiles +aa-logprof - utility for updating AppArmor security profiles =head1 SYNOPSIS @@ -52,9 +52,8 @@ B< -m --logmark "mark"> =head1 DESCRIPTION -B is an interactive tool used to review AppArmor's -complain mode output and generate new entries for AppArmor security -profiles. +B is an interactive tool used to review AppArmor generated +messages and update AppArmor security profiles. Running aa-logprof will scan the log file and if there are new AppArmor events that are not covered by the existing profile set, the user will @@ -76,11 +75,14 @@ The user is then presented with info about the access including profile, path, old mode if there was a previous entry in the profile for this path, new mode, the suggestion list, and given these options: - (A)llow, (D)eny, (N)ew, (G)lob last piece, (Q)uit + (A)llow, (D)eny, (I)gnore, (N)ew, (G)lob last piece, (Q)uit If the AppArmor profile was in complain mode when the event was generated, the default for this option is (A)llow, otherwise, it's (D)eny. +The (I)gnore allows user to ignore the event, without making any changes to +the AppArmor profile. + The suggestion list is presented as a numbered list with includes at the top, the literal path in the middle, and the suggested globs at the bottom. If any globs are being suggested, the shortest glob @@ -114,9 +116,9 @@ Adding r access to /usr/share/themes/** would delete an entry for r access to /usr/share/themes/foo/*.gif if it exists in the profile. If (Q)uit is selected at this point, aa-logprof will ignore all new pending -capability and path accesses. +accesses. -After all of the path accesses have been handled, logrof will write all +After all of the accesses have been handled, logrof will write all updated profiles to the disk and reload them if AppArmor is running. =head2 New Process (Execution) Events @@ -160,7 +162,7 @@ user wants to quit. See capability(7) for details. =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-mergeprof.pod b/Tools/manpages/aa-mergeprof.pod index 63cef3c80..272c04688 100644 --- a/Tools/manpages/aa-mergeprof.pod +++ b/Tools/manpages/aa-mergeprof.pod @@ -22,7 +22,7 @@ B =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Tools/manpages/aa-unconfined.pod b/Tools/manpages/aa-unconfined.pod index ca006056b..aaf8e4740 100644 --- a/Tools/manpages/aa-unconfined.pod +++ b/Tools/manpages/aa-unconfined.pod @@ -33,8 +33,8 @@ B]> B<--paranoid> - Displays all processes from F filesystem with tcp or udp ports that - do no have AppArmor profiles loaded. + Displays all processes from F filesystem with tcp or udp ports + that do not have AppArmor profiles loaded. =head1 DESCRIPTION @@ -46,7 +46,7 @@ network sockets and do not have AppArmor profiles loaded into the kernel. B must be run as root to retrieve the process executable link from the F filesystem. This program is susceptible to race conditions of several flavours: an unlinked executable will be mishandled; -an executable started before a AppArmor profile is loaded will not +an executable started before an AppArmor profile is loaded will not appear in the output, despite running without confinement; a process that dies between the netstat(8) and further checks will be mishandled. This program only lists processes using TCP and UDP. In short, this @@ -54,7 +54,7 @@ program is unsuitable for forensics use and is provided only as an aid to profiling all network-accessible processes in the lab. If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/Translate/README b/Translate/README new file mode 100644 index 000000000..357f8a1e4 --- /dev/null +++ b/Translate/README @@ -0,0 +1,10 @@ +GENERATING TRANSLATION MESSAGES + +To generate the messages.pot file: + +Navigate to apparmor/ and run the following command. +python pygettext.py aa.py aamode.py cleanprofile.py common.py config.py logparser.py severity.py tools.py ui.py writeprofile.py yasti.py ./../Tools/aa* + +It will generate the messages.pot file in apparmor/ + +You might need to provide the full path to pygettext.py from your python installation. It will typically be in the /path/to/python/libs/Tools/i18n/pygettext.py diff --git a/Translate/messages.pot b/Translate/messages.pot index f73b67656..0ff280c09 100644 --- a/Translate/messages.pot +++ b/Translate/messages.pot @@ -1,11 +1,11 @@ -# Translations for AppArmor Profile Tools. +# Messages from AppArmor Profile utils. # Copyright (C) 2013 -# Kshitij Gupta , 2013. +# Kshitij Gupta , 2013. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-09-19 21:16+IST\n" +"POT-Creation-Date: 2013-09-20 16:48+IST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -20,8 +20,16 @@ msgstr "" msgid "It seems AppArmor was not started. Please enable AppArmor and try again." msgstr "" -#: ./../Tools/aa-genprof:67 -msgid "Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' in another window in order to find the fully-qualified path and use the full path as parameter." +#: ./../Tools/aa-genprof:53 +msgid "%s is not a directory." +msgstr "" + +#: ./../Tools/aa-genprof:67 tools.py:105 +msgid "" +"Can't find %s in the system path list. If the name of the application\n" +"is correct, please run 'which %s' as a user with correct PATH\n" +"environment set up in order to find the fully-qualified path and\n" +"use the full path as parameter." msgstr "" #: ./../Tools/aa-genprof:69 @@ -34,8 +42,7 @@ msgid "" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" -"more information:\n" -"http://wiki.apparmor.net/index.php/Profiles" +"more information:" msgstr "" #: ./../Tools/aa-genprof:99 @@ -65,38 +72,59 @@ msgstr "" msgid "" "\n" "Please consider contributing your new profile!\n" -"See the following wiki page for more information:\n" -"http://wiki.apparmor.net/index.php/Profiles\n" +"See the following wiki page for more information:" msgstr "" #: ./../Tools/aa-genprof:140 msgid "Finished generating profile for %s." msgstr "" -#: ./../Tools/aa-unconfined:56 +#: ./../Tools/aa-unconfined:58 msgid "" "%s %s (%s) not confined\n" msgstr "" -#: ./../Tools/aa-unconfined:60 +#: ./../Tools/aa-unconfined:62 msgid "" "%s %s %snot confined\n" msgstr "" -#: ./../Tools/aa-unconfined:65 +#: ./../Tools/aa-unconfined:67 msgid "" "%s %s (%s) confined by '%s'\n" msgstr "" -#: ./../Tools/aa-unconfined:69 +#: ./../Tools/aa-unconfined:71 msgid "" "%s %s %sconfined by '%s'\n" msgstr "" +#: aa.py:242 aa.py:516 +msgid "Setting %s to complain mode." +msgstr "" + +#: aa.py:248 +msgid "Setting %s to enforce mode." +msgstr "" + #: aa.py:263 msgid "Unable to find basename for %s." msgstr "" +#: aa.py:278 +msgid "Could not create %s symlink to %s." +msgstr "" + +#: aa.py:288 +msgid "Unable to read first line from: %s : File Not Found" +msgstr "" + +#: aa.py:302 +msgid "" +"Unable to fork: %s\n" +"\t%s" +msgstr "" + #: aa.py:420 ui.py:270 msgid "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" @@ -105,43 +133,80 @@ msgstr "" msgid "Abandoning all changes." msgstr "" +#: aa.py:441 +msgid "WARNING: Error fetching profiles from the repository" +msgstr "" + +#: aa.py:518 +msgid "Error activating profiles: %s" +msgstr "" + #: aa.py:564 msgid "%s contains no profile" msgstr "" -#: aa.py:936 aa.py:1192 aa.py:1496 aa.py:1532 aa.py:1695 aa.py:1897 aa.py:1928 +#: aa.py:656 +msgid "" +"WARNING: Error synchronizing profiles with the repository:\n" +"%s\n" +msgstr "" + +#: aa.py:694 +msgid "" +"WARNING: Error synchronizing profiles with the repository\n" +"%s\n" +msgstr "" + +#: aa.py:816 +msgid "Changelog Entry: " +msgstr "" + +#: aa.py:836 +msgid "" +"Repository Error\n" +"Registration or Sigin was unsuccessful. User login\n" +"information is required to upload profiles to the repository.\n" +"These changes could not be sent.\n" +msgstr "" + +#: aa.py:934 aa.py:1190 aa.py:1494 aa.py:1530 aa.py:1693 aa.py:1895 aa.py:1926 msgid "Profile" msgstr "" -#: aa.py:939 +#: aa.py:937 msgid "Default Hat" msgstr "" -#: aa.py:941 +#: aa.py:939 msgid "Requested Hat" msgstr "" -#: aa.py:1194 +#: aa.py:1170 +msgid "" +"Target profile exists: %s\n" +msgstr "" + +#: aa.py:1192 msgid "Program" msgstr "" -#: aa.py:1197 +#: aa.py:1195 msgid "Execute" msgstr "" -#: aa.py:1198 aa.py:1498 aa.py:1534 aa.py:1746 +#: aa.py:1196 aa.py:1496 aa.py:1532 aa.py:1744 msgid "Severity" msgstr "" -#: aa.py:1221 +#: aa.py:1219 msgid "Are you specifying a transition to a local profile?" msgstr "" -#: aa.py:1233 +#: aa.py:1231 msgid "Enter profile name to transition to: " msgstr "" -#: aa.py:1242 +#: aa.py:1240 msgid "" "Should AppArmor sanitise the environment when\n" "switching profiles?\n" @@ -151,7 +216,7 @@ msgid "" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" -#: aa.py:1244 +#: aa.py:1242 msgid "" "Should AppArmor sanitise the environment when\n" "switching profiles?\n" @@ -162,16 +227,16 @@ msgid "" "could cause functionality problems." msgstr "" -#: aa.py:1252 +#: aa.py:1250 msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" "\n" "Are you absolutely certain you wish to remove all\n" -"AppArmor protection when executing : %s ?" +"AppArmor protection when executing %s ?" msgstr "" -#: aa.py:1254 +#: aa.py:1252 msgid "" "Should AppArmor sanitise the environment when\n" "running this program unconfined?\n" @@ -181,159 +246,159 @@ msgid "" "and should be avoided if at all possible." msgstr "" -#: aa.py:1330 +#: aa.py:1328 msgid "" "A profile for %s does not exist.\n" "Do you want to create one?" msgstr "" -#: aa.py:1348 +#: aa.py:1346 msgid "A local profile for %s does not exit. Create one?" msgstr "" -#: aa.py:1457 +#: aa.py:1455 msgid "Complain-mode changes:" msgstr "" -#: aa.py:1459 +#: aa.py:1457 msgid "Enforce-mode changes:" msgstr "" -#: aa.py:1462 +#: aa.py:1460 msgid "Invalid mode found: %s" msgstr "" -#: aa.py:1497 aa.py:1533 +#: aa.py:1495 aa.py:1531 msgid "Capability" msgstr "" -#: aa.py:1547 aa.py:1782 +#: aa.py:1545 aa.py:1780 msgid "Adding %s to profile." msgstr "" -#: aa.py:1549 aa.py:1784 aa.py:1824 aa.py:1946 +#: aa.py:1547 aa.py:1782 aa.py:1822 aa.py:1944 msgid "Deleted %s previous matching profile entries." msgstr "" -#: aa.py:1556 +#: aa.py:1554 msgid "Adding capability %s to profile." msgstr "" -#: aa.py:1563 +#: aa.py:1561 msgid "Denying capability %s to profile." msgstr "" -#: aa.py:1696 +#: aa.py:1694 msgid "Path" msgstr "" -#: aa.py:1705 aa.py:1736 +#: aa.py:1703 aa.py:1734 msgid "(owner permissions off)" msgstr "" -#: aa.py:1710 +#: aa.py:1708 msgid "(force new perms to owner)" msgstr "" -#: aa.py:1713 +#: aa.py:1711 msgid "(force all rule perms to owner)" msgstr "" -#: aa.py:1725 +#: aa.py:1723 msgid "Old Mode" msgstr "" -#: aa.py:1726 +#: aa.py:1724 msgid "New Mode" msgstr "" -#: aa.py:1741 +#: aa.py:1739 msgid "(force perms to owner)" msgstr "" -#: aa.py:1744 +#: aa.py:1742 msgid "Mode" msgstr "" -#: aa.py:1822 +#: aa.py:1820 msgid "Adding %s %s to profile" msgstr "" -#: aa.py:1840 +#: aa.py:1838 msgid "Enter new path:" msgstr "" -#: aa.py:1843 +#: aa.py:1841 msgid "The specified path does not match this log entry:" msgstr "" -#: aa.py:1844 +#: aa.py:1842 msgid "Log Entry" msgstr "" -#: aa.py:1845 +#: aa.py:1843 msgid "Entered Path" msgstr "" -#: aa.py:1846 +#: aa.py:1844 msgid "Do you really want to use this path?" msgstr "" -#: aa.py:1898 aa.py:1929 +#: aa.py:1896 aa.py:1927 msgid "Network Family" msgstr "" -#: aa.py:1899 aa.py:1930 +#: aa.py:1897 aa.py:1928 msgid "Socket Type" msgstr "" -#: aa.py:1944 +#: aa.py:1942 msgid "Adding %s to profile" msgstr "" -#: aa.py:1954 +#: aa.py:1952 msgid "Adding network access %s %s to profile." msgstr "" -#: aa.py:1960 +#: aa.py:1958 msgid "Denying network access %s %s to profile" msgstr "" -#: aa.py:2171 +#: aa.py:2169 msgid "Reading log entries from %s." msgstr "" -#: aa.py:2174 +#: aa.py:2172 msgid "Updating AppArmor profiles in %s." msgstr "" -#: aa.py:2178 +#: aa.py:2176 msgid "unknown" msgstr "" -#: aa.py:2241 +#: aa.py:2239 msgid "" "Select which profile changes you would like to save to the\n" "local profile set." msgstr "" -#: aa.py:2242 +#: aa.py:2240 msgid "Local profile changes" msgstr "" -#: aa.py:2264 +#: aa.py:2262 msgid "The following local profiles were changed. Would you like to save them?" msgstr "" -#: aa.py:2338 +#: aa.py:2336 msgid "Profile Changes" msgstr "" -#: aa.py:3831 +#: aa.py:3829 msgid "Writing updated profile for %s." msgstr "" -#: aa.py:4074 +#: aa.py:4072 msgid "" "%s is currently marked as a program that should not have its own\n" "profile. Usually, programs are marked this way if creating a profile for \n" @@ -378,12 +443,6 @@ msgid "" "Removing audit mode from %s.\n" msgstr "" -#: tools.py:105 -msgid "" -"Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.\n" -"Please use the full path as parameter" -msgstr "" - #: tools.py:122 msgid "The profile for %s does not exists. Nothing to clean." msgstr "" @@ -628,19 +687,24 @@ msgstr "" msgid "(I)gnore" msgstr "" +#: ui.py:295 +msgid "" +"FINISHING...\n" +msgstr "" + #: ui.py:316 -msgid "Unknown command" +msgid "PromptUser: Unknown command %s" msgstr "" #: ui.py:323 -msgid "Duplicate hotkey for" +msgid "PromptUser: Duplicate hotkey for %s: %s " msgstr "" #: ui.py:335 -msgid "Invalid hotkey in default item" +msgid "PromptUser: Invalid hotkey in default item" msgstr "" #: ui.py:340 -msgid "Invalid default" +msgid "PromptUser: Invalid default %s" msgstr "" diff --git a/apparmor/aa.py b/apparmor/aa.py index adb1c8282..c9e09209d 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -239,13 +239,13 @@ def enforce(path): def set_complain(filename, program, ): """Sets the profile to complain mode""" - UI_Info('Setting %s to complain mode.\n' % program) + UI_Info(_('Setting %s to complain mode.') % program) create_symlink('force-complain', filename) change_profile_flags(filename, 'complain', True) def set_enforce(filename, program): """Sets the profile to enforce mode""" - UI_Info('Setting %s to enforce mode.\n' % program) + UI_Info(_('Setting %s to enforce mode.') % program) delete_symlink('force-complain', filename) delete_symlink('disable', filename) change_profile_flags(filename, 'complain', False) @@ -275,7 +275,7 @@ def create_symlink(subdir, filename): try: os.symlink(filename, link) except: - raise AppArmorException('Could not create %s symlink to %s.'%(link, filename)) + raise AppArmorException(_('Could not create %s symlink to %s.')%(link, filename)) def head(file): """Returns the first/head line of the file""" @@ -285,7 +285,7 @@ def head(file): first = f_in.readline().rstrip() return first else: - raise AppArmorException('Unable to read first line from: %s : File Not Found' %file) + raise AppArmorException(_('Unable to read first line from: %s : File Not Found') %file) def get_output(params): """Returns the return code output by running the program with the args given in the list""" @@ -299,7 +299,7 @@ def get_output(params): # Get the output of the program output = subprocess.check_output(params) except OSError as e: - raise AppArmorException("Unable to fork: %s\n\t%s" %(program, str(e))) + raise AppArmorException(_("Unable to fork: %s\n\t%s") %(program, str(e))) # If exit-codes besides 0 except subprocess.CalledProcessError as e: output = e.output @@ -432,13 +432,13 @@ def get_profile(prof_name): local_profiles = [] profile_hash = hasher() if repo_is_enabled(): - UI_BusyStart('Coonecting to repository.....') + UI_BusyStart('Connecting to repository.....') status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name) UI_BusyStop() if status_ok: profile_hash = ret else: - UI_Important('WARNING: Error fetching profiles from the repository') + UI_Important(_('WARNING: Error fetching profiles from the repository')) inactive_profile = get_inactive_profile(prof_name) if inactive_profile: uname = 'Inactive local profile for %s' % prof_name @@ -513,9 +513,9 @@ def activate_repo_profiles(url, profiles, complain): if complain: fname = get_profile_filename(pname) set_profile_flags(profile_dir + fname, 'complain') - UI_Info('Setting %s to complain mode.' % pname) + UI_Info(_('Setting %s to complain mode.') % pname) except Exception as e: - sys.stderr.write("Error activating profiles: %s" % e) + sys.stderr.write(_("Error activating profiles: %s") % e) def autodep(bin_name, pname=''): bin_full = None @@ -653,7 +653,7 @@ def sync_profile(): if not status_ok: if not ret: ret = 'UNKNOWN ERROR' - UI_Important('WARNING: Error synchronizing profiles with the repository:\n%s\n' % ret) + UI_Important(_('WARNING: Error synchronizing profiles with the repository:\n%s\n') % ret) else: users_repo_profiles = ret serialize_opts['NO_FLAGS'] = True @@ -691,7 +691,7 @@ def sync_profile(): else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important('WARNING: Error synchronizing profiles witht he repository\n%s\n' % ret) + UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s\n') % ret) continue if p_repo != p_local: changed_profiles.append(prof) @@ -813,7 +813,7 @@ def console_select_and_upload_profiles(title, message, profiles_up): if ans == 'CMD_NEVER_ASK': set_profiles_local_only([i[0] for i in profs]) elif ans == 'CMD_UPLOAD_CHANGES': - changelog = UI_GetString('Changelog Entry: ', '') + changelog = UI_GetString(_('Changelog Entry: '), '') user, passw = get_repo_user_pass() if user and passw: for p_data in profs: @@ -831,11 +831,9 @@ def console_select_and_upload_profiles(title, message, profiles_up): else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important('WARNING: An error occured while uploading the profile %s\n%s\n' % (prof, ret)) + UI_Important('WARNING: An error occurred while uploading the profile %s\n%s\n' % (prof, ret)) else: - UI_Important('Repository Error\nRegistration or Sigin was unsuccessful. User login\n' + - 'information is required to upload profiles to the repository.\n' + - 'These changes could not be sent.\n') + UI_Important(_('Repository Error\nRegistration or Sigin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.\n')) def set_profiles_local_only(profs): for p in profs: @@ -1169,7 +1167,7 @@ def handle_children(profile, hat, root): default = None if 'p' in options and os.path.exists(get_profile_filename(exec_target)): default = 'CMD_px' - sys.stdout.write('Target profile exists: %s\n' %get_profile_filename(exec_target)) + sys.stdout.write(_('Target profile exists: %s\n') %get_profile_filename(exec_target)) elif 'i' in options: default = 'CMD_ix' elif 'c' in options: @@ -1249,7 +1247,7 @@ def handle_children(profile, hat, root): exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) elif ans == 'CMD_ux': exec_mode = str_to_mode('ux') - ynans = UI_YesNo(_("""Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing : %s ?""") % exec_target, 'n') + ynans = UI_YesNo(_("""Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?""") % exec_target, 'n') if ynans == 'y': ynans = UI_YesNo(_("""Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."""), 'y') if ynans == 'y': diff --git a/apparmor/tools.py b/apparmor/tools.py index 7d5df9ac5..b2a7ce5d4 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -102,7 +102,7 @@ class aa_tools: else: if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application is correct, please run 'which %s' as a user with correct PATH environment set up in order to find the fully-qualified path.\nPlease use the full path as parameter")%(p, p)) + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.")%(p, p)) else: apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) sys.exit(1) diff --git a/apparmor/ui.py b/apparmor/ui.py index ced5b2c09..9e89fafb2 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -292,7 +292,7 @@ def UI_LongMessage(title, message): ypath, yarg = GetDataFromYast() def confirm_and_finish(): - sys.stdout.write('FINISHING...\n') + sys.stdout.write(_('FINISHING...\n')) sys.exit(0) def Text_PromptUser(question): @@ -313,14 +313,14 @@ def Text_PromptUser(question): for cmd in functions: if not CMDS.get(cmd, False): - raise AppArmorException('PromptUser: %s %s' %(_('Unknown command'), cmd)) + raise AppArmorException(_('PromptUser: Unknown command %s') % cmd) menutext = CMDS[cmd] key = get_translated_hotkey(menutext).lower() # Duplicate hotkey if keys.get(key, False): - raise AppArmorException('PromptUser: %s %s: %s' %(_('Duplicate hotkey for'), cmd, menutext)) + raise AppArmorException(_('PromptUser: Duplicate hotkey for %s: %s ') % (cmd, menutext)) keys[key] = cmd @@ -332,12 +332,12 @@ def Text_PromptUser(question): default_key = 0 if default and CMDS[default]: defaulttext = CMDS[default] - defmsg = 'PromptUser: ' + _('Invalid hotkey in default item') + defmsg = _('PromptUser: Invalid hotkey in default item') default_key = get_translated_hotkey(defaulttext, defmsg).lower() if not keys.get(default_key, False): - raise AppArmorException('PromptUser: %s %s' %(_('Invalid default'), default)) + raise AppArmorException(_('PromptUser: Invalid default %s') % default) widest = 0 header_copy = headers[:] From 877f8253c30d28b7c7b8a4806a048314a841fecd Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 20 Sep 2013 21:21:35 +0530 Subject: [PATCH 066/183] fixed the explicit LANG in aa-unconfined to LANG=C --- Tools/aa-unconfined | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index 989459d79..30653d327 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -22,7 +22,7 @@ if paranoid: else: regex_tcp_udp = re.compile('^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)') import subprocess - output = subprocess.check_output('LANG=en netstat -nlp', shell=True).split('\n') + output = subprocess.check_output('LANG=C netstat -nlp', shell=True).split('\n') for line in output: match = regex_tcp_udp.search(line) From 1d3c2be0be30786dd236a193dc74e3b7afff2a52 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 21 Sep 2013 01:08:34 +0530 Subject: [PATCH 067/183] fixes from rev65 --- Tools/manpages/aa-cleanprof.pod | 4 +- Tools/manpages/aa-logprof.pod | 7 +- Translate/README | 6 +- Translate/messages.pot | 464 ++++++++++++++++++++------------ apparmor/aa.py | 105 ++++---- 5 files changed, 351 insertions(+), 235 deletions(-) diff --git a/Tools/manpages/aa-cleanprof.pod b/Tools/manpages/aa-cleanprof.pod index d2485fd38..6b2ef93f9 100644 --- a/Tools/manpages/aa-cleanprof.pod +++ b/Tools/manpages/aa-cleanprof.pod @@ -18,8 +18,8 @@ B<-d --dir /path/to/profiles> =head1 DESCRIPTION B is used to perform a cleanup on one or more profiles. -The tool removes any existing superfluous rules (Rules that are covered -under an include or another rule),reorders the rules to group similar rules +The tool removes any existing superfluous rules (rules that are covered +under an include or another rule), reorders the rules to group similar rules together and removes all comments. =head1 BUGS diff --git a/Tools/manpages/aa-logprof.pod b/Tools/manpages/aa-logprof.pod index cb10d3b7d..6219c12fc 100644 --- a/Tools/manpages/aa-logprof.pod +++ b/Tools/manpages/aa-logprof.pod @@ -80,8 +80,11 @@ new mode, the suggestion list, and given these options: If the AppArmor profile was in complain mode when the event was generated, the default for this option is (A)llow, otherwise, it's (D)eny. -The (I)gnore allows user to ignore the event, without making any changes to -the AppArmor profile. +The (D)eny option adds a "deny" rule to the AppArmor profile, which +silences logging. + +The (I)gnore option allows user to ignore the event, without making any +changes to the AppArmor profile. The suggestion list is presented as a numbered list with includes at the top, the literal path in the middle, and the suggested globs diff --git a/Translate/README b/Translate/README index 357f8a1e4..eade5e817 100644 --- a/Translate/README +++ b/Translate/README @@ -2,9 +2,9 @@ GENERATING TRANSLATION MESSAGES To generate the messages.pot file: -Navigate to apparmor/ and run the following command. -python pygettext.py aa.py aamode.py cleanprofile.py common.py config.py logparser.py severity.py tools.py ui.py writeprofile.py yasti.py ./../Tools/aa* +Run the following command in Translate. +python pygettext.py ../apparmor/*.py ../Tools/aa* -It will generate the messages.pot file in apparmor/ +It will generate the messages.pot file in the Translate directory. You might need to provide the full path to pygettext.py from your python installation. It will typically be in the /path/to/python/libs/Tools/i18n/pygettext.py diff --git a/Translate/messages.pot b/Translate/messages.pot index 0ff280c09..e57f581ef 100644 --- a/Translate/messages.pot +++ b/Translate/messages.pot @@ -1,11 +1,11 @@ -# Messages from AppArmor Profile utils. -# Copyright (C) 2013 -# Kshitij Gupta , 2013. +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-09-20 16:48+IST\n" +"POT-Creation-Date: 2013-09-21 01:07+IST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,16 +15,15 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" -#: ./../Tools/aa-genprof:48 ./../Tools/aa-logprof:20 -#: ./../Tools/aa-unconfined:17 +#: ../Tools/aa-genprof:48 ../Tools/aa-logprof:20 ../Tools/aa-unconfined:17 msgid "It seems AppArmor was not started. Please enable AppArmor and try again." msgstr "" -#: ./../Tools/aa-genprof:53 +#: ../Tools/aa-genprof:53 msgid "%s is not a directory." msgstr "" -#: ./../Tools/aa-genprof:67 tools.py:105 +#: ../Tools/aa-genprof:67 ../apparmor/tools.py:105 msgid "" "Can't find %s in the system path list. If the name of the application\n" "is correct, please run 'which %s' as a user with correct PATH\n" @@ -32,11 +31,11 @@ msgid "" "use the full path as parameter." msgstr "" -#: ./../Tools/aa-genprof:69 +#: ../Tools/aa-genprof:69 msgid "%s does not exists, please double-check the path." msgstr "" -#: ./../Tools/aa-genprof:97 +#: ../Tools/aa-genprof:97 msgid "" "\n" "Before you begin, you may wish to check if a\n" @@ -45,7 +44,7 @@ msgid "" "more information:" msgstr "" -#: ./../Tools/aa-genprof:99 +#: ../Tools/aa-genprof:99 msgid "" "Please start the application to be profiled in\n" "another window and exercise its functionality now.\n" @@ -58,155 +57,184 @@ msgid "" "allowed or denied." msgstr "" -#: ./../Tools/aa-genprof:120 +#: ../Tools/aa-genprof:120 msgid "Profiling" msgstr "" -#: ./../Tools/aa-genprof:138 +#: ../Tools/aa-genprof:138 msgid "" "\n" "Reloaded AppArmor profiles in enforce mode." msgstr "" -#: ./../Tools/aa-genprof:139 +#: ../Tools/aa-genprof:139 msgid "" "\n" "Please consider contributing your new profile!\n" "See the following wiki page for more information:" msgstr "" -#: ./../Tools/aa-genprof:140 +#: ../Tools/aa-genprof:140 msgid "Finished generating profile for %s." msgstr "" -#: ./../Tools/aa-unconfined:58 +#: ../Tools/aa-unconfined:58 msgid "" "%s %s (%s) not confined\n" msgstr "" -#: ./../Tools/aa-unconfined:62 +#: ../Tools/aa-unconfined:62 msgid "" "%s %s %snot confined\n" msgstr "" -#: ./../Tools/aa-unconfined:67 +#: ../Tools/aa-unconfined:67 msgid "" "%s %s (%s) confined by '%s'\n" msgstr "" -#: ./../Tools/aa-unconfined:71 +#: ../Tools/aa-unconfined:71 msgid "" "%s %s %sconfined by '%s'\n" msgstr "" -#: aa.py:242 aa.py:516 +#: ../apparmor/aa.py:174 +msgid "Followed too many links while resolving %s" +msgstr "" + +#: ../apparmor/aa.py:230 ../apparmor/aa.py:237 +msgid "Can't find %s" +msgstr "" + +#: ../apparmor/aa.py:242 ../apparmor/aa.py:521 msgid "Setting %s to complain mode." msgstr "" -#: aa.py:248 +#: ../apparmor/aa.py:248 msgid "Setting %s to enforce mode." msgstr "" -#: aa.py:263 +#: ../apparmor/aa.py:263 msgid "Unable to find basename for %s." msgstr "" -#: aa.py:278 +#: ../apparmor/aa.py:278 msgid "Could not create %s symlink to %s." msgstr "" -#: aa.py:288 -msgid "Unable to read first line from: %s : File Not Found" +#: ../apparmor/aa.py:291 +msgid "Unable to read first line from %s: File Not Found" msgstr "" -#: aa.py:302 +#: ../apparmor/aa.py:305 msgid "" "Unable to fork: %s\n" "\t%s" msgstr "" -#: aa.py:420 ui.py:270 +#: ../apparmor/aa.py:425 ../apparmor/ui.py:270 msgid "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" -#: aa.py:422 ui.py:272 +#: ../apparmor/aa.py:427 ../apparmor/ui.py:272 msgid "Abandoning all changes." msgstr "" -#: aa.py:441 +#: ../apparmor/aa.py:440 +msgid "Connecting to repository..." +msgstr "" + +#: ../apparmor/aa.py:446 msgid "WARNING: Error fetching profiles from the repository" msgstr "" -#: aa.py:518 +#: ../apparmor/aa.py:523 msgid "Error activating profiles: %s" msgstr "" -#: aa.py:564 +#: ../apparmor/aa.py:570 msgid "%s contains no profile" msgstr "" -#: aa.py:656 +#: ../apparmor/aa.py:662 msgid "" "WARNING: Error synchronizing profiles with the repository:\n" "%s\n" msgstr "" -#: aa.py:694 +#: ../apparmor/aa.py:700 msgid "" "WARNING: Error synchronizing profiles with the repository\n" -"%s\n" +"%s" msgstr "" -#: aa.py:816 +#: ../apparmor/aa.py:789 ../apparmor/aa.py:840 +msgid "" +"WARNING: An error occurred while uploading the profile %s\n" +"%s" +msgstr "" + +#: ../apparmor/aa.py:790 +msgid "Uploaded changes to repository." +msgstr "" + +#: ../apparmor/aa.py:822 msgid "Changelog Entry: " msgstr "" -#: aa.py:836 +#: ../apparmor/aa.py:842 msgid "" "Repository Error\n" -"Registration or Sigin was unsuccessful. User login\n" +"Registration or Signin was unsuccessful. User login\n" "information is required to upload profiles to the repository.\n" -"These changes could not be sent.\n" +"These changes could not be sent." msgstr "" -#: aa.py:934 aa.py:1190 aa.py:1494 aa.py:1530 aa.py:1693 aa.py:1895 aa.py:1926 +#: ../apparmor/aa.py:940 ../apparmor/aa.py:1196 ../apparmor/aa.py:1500 +#: ../apparmor/aa.py:1536 ../apparmor/aa.py:1699 ../apparmor/aa.py:1898 +#: ../apparmor/aa.py:1929 msgid "Profile" msgstr "" -#: aa.py:937 +#: ../apparmor/aa.py:943 msgid "Default Hat" msgstr "" -#: aa.py:939 +#: ../apparmor/aa.py:945 msgid "Requested Hat" msgstr "" -#: aa.py:1170 +#: ../apparmor/aa.py:1162 +msgid "%s has transition name but not transition mode" +msgstr "" + +#: ../apparmor/aa.py:1176 msgid "" "Target profile exists: %s\n" msgstr "" -#: aa.py:1192 +#: ../apparmor/aa.py:1198 msgid "Program" msgstr "" -#: aa.py:1195 +#: ../apparmor/aa.py:1201 msgid "Execute" msgstr "" -#: aa.py:1196 aa.py:1496 aa.py:1532 aa.py:1744 +#: ../apparmor/aa.py:1202 ../apparmor/aa.py:1502 ../apparmor/aa.py:1538 +#: ../apparmor/aa.py:1750 msgid "Severity" msgstr "" -#: aa.py:1219 +#: ../apparmor/aa.py:1225 msgid "Are you specifying a transition to a local profile?" msgstr "" -#: aa.py:1231 +#: ../apparmor/aa.py:1237 msgid "Enter profile name to transition to: " msgstr "" -#: aa.py:1240 +#: ../apparmor/aa.py:1246 msgid "" "Should AppArmor sanitise the environment when\n" "switching profiles?\n" @@ -216,7 +244,7 @@ msgid "" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" -#: aa.py:1242 +#: ../apparmor/aa.py:1248 msgid "" "Should AppArmor sanitise the environment when\n" "switching profiles?\n" @@ -227,7 +255,7 @@ msgid "" "could cause functionality problems." msgstr "" -#: aa.py:1250 +#: ../apparmor/aa.py:1256 msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" @@ -236,7 +264,7 @@ msgid "" "AppArmor protection when executing %s ?" msgstr "" -#: aa.py:1252 +#: ../apparmor/aa.py:1258 msgid "" "Should AppArmor sanitise the environment when\n" "running this program unconfined?\n" @@ -246,159 +274,241 @@ msgid "" "and should be avoided if at all possible." msgstr "" -#: aa.py:1328 +#: ../apparmor/aa.py:1334 ../apparmor/aa.py:1352 msgid "" "A profile for %s does not exist.\n" "Do you want to create one?" msgstr "" -#: aa.py:1346 -msgid "A local profile for %s does not exit. Create one?" -msgstr "" - -#: aa.py:1455 +#: ../apparmor/aa.py:1461 msgid "Complain-mode changes:" msgstr "" -#: aa.py:1457 +#: ../apparmor/aa.py:1463 msgid "Enforce-mode changes:" msgstr "" -#: aa.py:1460 +#: ../apparmor/aa.py:1466 msgid "Invalid mode found: %s" msgstr "" -#: aa.py:1495 aa.py:1531 +#: ../apparmor/aa.py:1501 ../apparmor/aa.py:1537 msgid "Capability" msgstr "" -#: aa.py:1545 aa.py:1780 +#: ../apparmor/aa.py:1551 ../apparmor/aa.py:1786 msgid "Adding %s to profile." msgstr "" -#: aa.py:1547 aa.py:1782 aa.py:1822 aa.py:1944 +#: ../apparmor/aa.py:1553 ../apparmor/aa.py:1788 ../apparmor/aa.py:1828 +#: ../apparmor/aa.py:1947 msgid "Deleted %s previous matching profile entries." msgstr "" -#: aa.py:1554 +#: ../apparmor/aa.py:1560 msgid "Adding capability %s to profile." msgstr "" -#: aa.py:1561 +#: ../apparmor/aa.py:1567 msgid "Denying capability %s to profile." msgstr "" -#: aa.py:1694 +#: ../apparmor/aa.py:1700 msgid "Path" msgstr "" -#: aa.py:1703 aa.py:1734 +#: ../apparmor/aa.py:1709 ../apparmor/aa.py:1740 msgid "(owner permissions off)" msgstr "" -#: aa.py:1708 +#: ../apparmor/aa.py:1714 msgid "(force new perms to owner)" msgstr "" -#: aa.py:1711 +#: ../apparmor/aa.py:1717 msgid "(force all rule perms to owner)" msgstr "" -#: aa.py:1723 +#: ../apparmor/aa.py:1729 msgid "Old Mode" msgstr "" -#: aa.py:1724 +#: ../apparmor/aa.py:1730 msgid "New Mode" msgstr "" -#: aa.py:1739 +#: ../apparmor/aa.py:1745 msgid "(force perms to owner)" msgstr "" -#: aa.py:1742 +#: ../apparmor/aa.py:1748 msgid "Mode" msgstr "" -#: aa.py:1820 +#: ../apparmor/aa.py:1826 msgid "Adding %s %s to profile" msgstr "" -#: aa.py:1838 -msgid "Enter new path:" +#: ../apparmor/aa.py:1844 +msgid "Enter new path: " msgstr "" -#: aa.py:1841 -msgid "The specified path does not match this log entry:" +#: ../apparmor/aa.py:1847 +msgid "" +"The specified path does not match this log entry:\n" +"\n" +" Log Entry: %s\n" +" Entered Path: %s\n" +"Do you really want to use this path?" msgstr "" -#: aa.py:1842 -msgid "Log Entry" -msgstr "" - -#: aa.py:1843 -msgid "Entered Path" -msgstr "" - -#: aa.py:1844 -msgid "Do you really want to use this path?" -msgstr "" - -#: aa.py:1896 aa.py:1927 +#: ../apparmor/aa.py:1899 ../apparmor/aa.py:1930 msgid "Network Family" msgstr "" -#: aa.py:1897 aa.py:1928 +#: ../apparmor/aa.py:1900 ../apparmor/aa.py:1931 msgid "Socket Type" msgstr "" -#: aa.py:1942 +#: ../apparmor/aa.py:1945 msgid "Adding %s to profile" msgstr "" -#: aa.py:1952 +#: ../apparmor/aa.py:1955 msgid "Adding network access %s %s to profile." msgstr "" -#: aa.py:1958 +#: ../apparmor/aa.py:1961 msgid "Denying network access %s %s to profile" msgstr "" -#: aa.py:2169 +#: ../apparmor/aa.py:2172 msgid "Reading log entries from %s." msgstr "" -#: aa.py:2172 +#: ../apparmor/aa.py:2175 msgid "Updating AppArmor profiles in %s." msgstr "" -#: aa.py:2176 +#: ../apparmor/aa.py:2179 msgid "unknown" msgstr "" -#: aa.py:2239 +#: ../apparmor/aa.py:2242 msgid "" "Select which profile changes you would like to save to the\n" "local profile set." msgstr "" -#: aa.py:2240 +#: ../apparmor/aa.py:2243 msgid "Local profile changes" msgstr "" -#: aa.py:2262 +#: ../apparmor/aa.py:2265 msgid "The following local profiles were changed. Would you like to save them?" msgstr "" -#: aa.py:2336 +#: ../apparmor/aa.py:2339 msgid "Profile Changes" msgstr "" -#: aa.py:3829 +#: ../apparmor/aa.py:2349 +msgid "Can't find existing profile %s to compare changes." +msgstr "" + +#: ../apparmor/aa.py:2487 ../apparmor/aa.py:2502 +msgid "Can't read AppArmor profiles in %s" +msgstr "" + +#: ../apparmor/aa.py:2578 +msgid "%s profile in %s contains syntax errors in line: %s." +msgstr "" + +#: ../apparmor/aa.py:2630 +msgid "Syntax Error: Unexpected End of Profile reached in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2645 +msgid "Syntax Error: Unexpected capability entry found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2664 +msgid "Syntax Error: Unexpected link entry found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2692 +msgid "Syntax Error: Unexpected change profile entry found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2714 +msgid "Syntax Error: Unexpected rlimit entry found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2725 +msgid "Syntax Error: Unexpected boolean definition found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2765 +msgid "Syntax Error: Unexpected path entry found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2789 +msgid "Syntax Error: Invalid Regex %s in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2792 +msgid "Invalid mode %s in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2842 +msgid "Syntax Error: Unexpected network entry found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2869 +msgid "Syntax Error: Unexpected change hat declaration found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2881 +msgid "Syntax Error: Unexpected hat definition found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2897 +msgid "Error: Multiple definitions for hat %s in profile %s." +msgstr "" + +#: ../apparmor/aa.py:2917 +msgid "Syntax Error: Unknown line found in file: %s line: %s" +msgstr "" + +#: ../apparmor/aa.py:2930 +msgid "Syntax Error: Missing '}' . Reached end of file %s while inside profile %s" +msgstr "" + +#: ../apparmor/aa.py:2961 +msgid "An existing variable redefined: %s" +msgstr "" + +#: ../apparmor/aa.py:2966 +msgid "Values added to a non-existing variable: %s" +msgstr "" + +#: ../apparmor/aa.py:2968 +msgid "Unknown variable operation: %s" +msgstr "" + +#: ../apparmor/aa.py:3330 +msgid "Can't find existing profile to modify" +msgstr "" + +#: ../apparmor/aa.py:3832 msgid "Writing updated profile for %s." msgstr "" -#: aa.py:4072 +#: ../apparmor/aa.py:3966 +msgid "File Not Found: %s" +msgstr "" + +#: ../apparmor/aa.py:4075 msgid "" "%s is currently marked as a program that should not have its own\n" "profile. Usually, programs are marked this way if creating a profile for \n" @@ -407,304 +517,304 @@ msgid "" "the corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf." msgstr "" -#: logparser.py:117 logparser.py:122 +#: ../apparmor/logparser.py:117 ../apparmor/logparser.py:122 msgid "Log contains unknown mode %s" msgstr "" -#: tools.py:51 +#: ../apparmor/tools.py:51 msgid "The given program cannot be found, please try with the fully qualified path name of the program: " msgstr "" -#: tools.py:53 tools.py:107 +#: ../apparmor/tools.py:53 ../apparmor/tools.py:107 msgid "%s does not exist, please double-check the path." msgstr "" -#: tools.py:70 +#: ../apparmor/tools.py:70 msgid "Profile for %s not found, skipping" msgstr "" -#: tools.py:74 +#: ../apparmor/tools.py:74 msgid "" "Disabling %s.\n" msgstr "" -#: tools.py:77 +#: ../apparmor/tools.py:77 msgid "" "Enabling %s.\n" msgstr "" -#: tools.py:82 +#: ../apparmor/tools.py:82 msgid "" "Setting %s to audit mode.\n" msgstr "" -#: tools.py:84 +#: ../apparmor/tools.py:84 msgid "" "Removing audit mode from %s.\n" msgstr "" -#: tools.py:122 +#: ../apparmor/tools.py:122 msgid "The profile for %s does not exists. Nothing to clean." msgstr "" -#: ui.py:46 +#: ../apparmor/ui.py:46 msgid "Invalid hotkey for" msgstr "" -#: ui.py:60 ui.py:98 ui.py:238 +#: ../apparmor/ui.py:60 ../apparmor/ui.py:98 ../apparmor/ui.py:238 msgid "(Y)es" msgstr "" -#: ui.py:61 ui.py:99 ui.py:239 +#: ../apparmor/ui.py:61 ../apparmor/ui.py:99 ../apparmor/ui.py:239 msgid "(N)o" msgstr "" -#: ui.py:100 +#: ../apparmor/ui.py:100 msgid "(C)ancel" msgstr "" -#: ui.py:186 +#: ../apparmor/ui.py:186 msgid "(A)llow" msgstr "" -#: ui.py:187 +#: ../apparmor/ui.py:187 msgid "(M)ore" msgstr "" -#: ui.py:188 +#: ../apparmor/ui.py:188 msgid "Audi(t)" msgstr "" -#: ui.py:189 +#: ../apparmor/ui.py:189 msgid "Audi(t) off" msgstr "" -#: ui.py:190 +#: ../apparmor/ui.py:190 msgid "Audit (A)ll" msgstr "" -#: ui.py:192 +#: ../apparmor/ui.py:192 msgid "(O)wner permissions on" msgstr "" -#: ui.py:193 +#: ../apparmor/ui.py:193 msgid "(O)wner permissions off" msgstr "" -#: ui.py:194 +#: ../apparmor/ui.py:194 msgid "(D)eny" msgstr "" -#: ui.py:195 +#: ../apparmor/ui.py:195 msgid "Abo(r)t" msgstr "" -#: ui.py:196 +#: ../apparmor/ui.py:196 msgid "(F)inish" msgstr "" -#: ui.py:197 +#: ../apparmor/ui.py:197 msgid "(I)nherit" msgstr "" -#: ui.py:198 +#: ../apparmor/ui.py:198 msgid "(P)rofile" msgstr "" -#: ui.py:199 +#: ../apparmor/ui.py:199 msgid "(P)rofile Clean Exec" msgstr "" -#: ui.py:200 +#: ../apparmor/ui.py:200 msgid "(C)hild" msgstr "" -#: ui.py:201 +#: ../apparmor/ui.py:201 msgid "(C)hild Clean Exec" msgstr "" -#: ui.py:202 +#: ../apparmor/ui.py:202 msgid "Named" msgstr "" -#: ui.py:203 +#: ../apparmor/ui.py:203 msgid "Named Clean Exec" msgstr "" -#: ui.py:204 +#: ../apparmor/ui.py:204 msgid "(U)nconfined" msgstr "" -#: ui.py:205 +#: ../apparmor/ui.py:205 msgid "(U)nconfined Clean Exec" msgstr "" -#: ui.py:206 +#: ../apparmor/ui.py:206 msgid "(P)rofile Inherit" msgstr "" -#: ui.py:207 +#: ../apparmor/ui.py:207 msgid "(P)rofile Inherit Clean Exec" msgstr "" -#: ui.py:208 +#: ../apparmor/ui.py:208 msgid "(C)hild Inherit" msgstr "" -#: ui.py:209 +#: ../apparmor/ui.py:209 msgid "(C)hild Inherit Clean Exec" msgstr "" -#: ui.py:210 +#: ../apparmor/ui.py:210 msgid "(N)amed Inherit" msgstr "" -#: ui.py:211 +#: ../apparmor/ui.py:211 msgid "(N)amed Inherit Clean Exec" msgstr "" -#: ui.py:212 +#: ../apparmor/ui.py:212 msgid "(X) ix On" msgstr "" -#: ui.py:213 +#: ../apparmor/ui.py:213 msgid "(X) ix Off" msgstr "" -#: ui.py:214 ui.py:228 +#: ../apparmor/ui.py:214 ../apparmor/ui.py:228 msgid "(S)ave Changes" msgstr "" -#: ui.py:215 +#: ../apparmor/ui.py:215 msgid "(C)ontinue Profiling" msgstr "" -#: ui.py:216 +#: ../apparmor/ui.py:216 msgid "(N)ew" msgstr "" -#: ui.py:217 +#: ../apparmor/ui.py:217 msgid "(G)lob" msgstr "" -#: ui.py:218 +#: ../apparmor/ui.py:218 msgid "Glob with (E)xtension" msgstr "" -#: ui.py:219 +#: ../apparmor/ui.py:219 msgid "(A)dd Requested Hat" msgstr "" -#: ui.py:220 +#: ../apparmor/ui.py:220 msgid "(U)se Default Hat" msgstr "" -#: ui.py:221 +#: ../apparmor/ui.py:221 msgid "(S)can system log for AppArmor events" msgstr "" -#: ui.py:222 +#: ../apparmor/ui.py:222 msgid "(H)elp" msgstr "" -#: ui.py:223 +#: ../apparmor/ui.py:223 msgid "(V)iew Profile" msgstr "" -#: ui.py:224 +#: ../apparmor/ui.py:224 msgid "(U)se Profile" msgstr "" -#: ui.py:225 +#: ../apparmor/ui.py:225 msgid "(C)reate New Profile" msgstr "" -#: ui.py:226 +#: ../apparmor/ui.py:226 msgid "(U)pdate Profile" msgstr "" -#: ui.py:227 +#: ../apparmor/ui.py:227 msgid "(I)gnore Update" msgstr "" -#: ui.py:229 +#: ../apparmor/ui.py:229 msgid "Save Selec(t)ed Profile" msgstr "" -#: ui.py:230 +#: ../apparmor/ui.py:230 msgid "(U)pload Changes" msgstr "" -#: ui.py:231 +#: ../apparmor/ui.py:231 msgid "(V)iew Changes" msgstr "" -#: ui.py:232 +#: ../apparmor/ui.py:232 msgid "View Changes b/w (C)lean profiles" msgstr "" -#: ui.py:233 +#: ../apparmor/ui.py:233 msgid "(V)iew" msgstr "" -#: ui.py:234 +#: ../apparmor/ui.py:234 msgid "(E)nable Repository" msgstr "" -#: ui.py:235 +#: ../apparmor/ui.py:235 msgid "(D)isable Repository" msgstr "" -#: ui.py:236 +#: ../apparmor/ui.py:236 msgid "(N)ever Ask Again" msgstr "" -#: ui.py:237 +#: ../apparmor/ui.py:237 msgid "Ask Me (L)ater" msgstr "" -#: ui.py:240 +#: ../apparmor/ui.py:240 msgid "Allow All (N)etwork" msgstr "" -#: ui.py:241 +#: ../apparmor/ui.py:241 msgid "Allow Network Fa(m)ily" msgstr "" -#: ui.py:242 +#: ../apparmor/ui.py:242 msgid "(O)verwrite Profile" msgstr "" -#: ui.py:243 +#: ../apparmor/ui.py:243 msgid "(K)eep Profile" msgstr "" -#: ui.py:244 +#: ../apparmor/ui.py:244 msgid "(C)ontinue" msgstr "" -#: ui.py:245 +#: ../apparmor/ui.py:245 msgid "(I)gnore" msgstr "" -#: ui.py:295 +#: ../apparmor/ui.py:295 msgid "" "FINISHING...\n" msgstr "" -#: ui.py:316 +#: ../apparmor/ui.py:316 msgid "PromptUser: Unknown command %s" msgstr "" -#: ui.py:323 +#: ../apparmor/ui.py:323 msgid "PromptUser: Duplicate hotkey for %s: %s " msgstr "" -#: ui.py:335 +#: ../apparmor/ui.py:335 msgid "PromptUser: Invalid hotkey in default item" msgstr "" -#: ui.py:340 +#: ../apparmor/ui.py:340 msgid "PromptUser: Invalid default %s" msgstr "" diff --git a/apparmor/aa.py b/apparmor/aa.py index c9e09209d..5ee3f748a 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -171,7 +171,7 @@ def get_full_path(original_path): while os.path.islink(path): link_count += 1 if link_count > 64: - fatal_error("Followed too many links while resolving %s" % (original_path)) + fatal_error(_("Followed too many links while resolving %s") % (original_path)) direc, file = os.path.split(path) link = os.readlink(path) # If the link an absolute path @@ -227,14 +227,14 @@ def complain(path): """Sets the profile to complain mode if it exists""" prof_filename, name = name_to_prof_filename(path) if not prof_filename : - fatal_error("Can't find %s" % path) + fatal_error(_("Can't find %s") % path) set_complain(prof_filename, name) def enforce(path): """Sets the profile to enforce mode if it exists""" prof_filename, name = name_to_prof_filename(path) if not prof_filename : - fatal_error("Can't find %s" % path) + fatal_error(_("Can't find %s") % path) set_enforce(prof_filename, name) def set_complain(filename, program, ): @@ -282,10 +282,13 @@ def head(file): first = '' if os.path.isfile(file): with open_file_read(file) as f_in: - first = f_in.readline().rstrip() + try: + first = f_in.readline().rstrip() + except UnicodeDecodeError: + pass return first else: - raise AppArmorException(_('Unable to read first line from: %s : File Not Found') %file) + raise AppArmorException(_('Unable to read first line from %s: File Not Found') %file) def get_output(params): """Returns the return code output by running the program with the args given in the list""" @@ -340,10 +343,12 @@ def handle_binfmt(profile, path): """Modifies the profile to add the requirements""" reqs_processed = dict() reqs = get_reqs(path) + print(reqs) while reqs: library = reqs.pop() if not reqs_processed.get(library, False): - reqs.append(get_reqs(library)) + if get_reqs(library): + reqs += get_reqs(library) reqs_processed[library] = True combined_mode = match_prof_incs_to_path(profile, 'allow', library) if combined_mode: @@ -432,7 +437,7 @@ def get_profile(prof_name): local_profiles = [] profile_hash = hasher() if repo_is_enabled(): - UI_BusyStart('Connecting to repository.....') + UI_BusyStart(_('Connecting to repository...')) status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name) UI_BusyStop() if status_ok: @@ -519,6 +524,7 @@ def activate_repo_profiles(url, profiles, complain): def autodep(bin_name, pname=''): bin_full = None + global repo_cfg if not bin_name and pname.startswith('/'): bin_name = pname if not repo_cfg and not cfg['repository'].get('url', False): @@ -546,8 +552,8 @@ def autodep(bin_name, pname=''): attach_profile_data(original_aa, profile_data) if os.path.isfile(profile_dir + '/tunables/global'): if not filelist.get(file, False): - filelist.file = hasher() - filelist[file][include]['tunables/global'] = True + filelist[file] = hasher() + filelist[file]['include']['tunables/global'] = True write_profile_ui_feedback(pname) def get_profile_flags(filename): @@ -691,7 +697,7 @@ def sync_profile(): else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s\n') % ret) + UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s') % ret) continue if p_repo != p_local: changed_profiles.append(prof) @@ -780,8 +786,8 @@ def yast_select_and_upload_profiles(title, message, profiles_up): else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important('WARNING: An error occured while uploading the profile %s\n%s\n' % (p, ret)) - UI_Info('Uploaded changes to repository.') + UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (p, ret)) + UI_Info(_('Uploaded changes to repository.')) if yarg.get('NEVER_ASK_AGAIN'): unselected_profiles = [] for p in profs: @@ -831,9 +837,9 @@ def console_select_and_upload_profiles(title, message, profiles_up): else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important('WARNING: An error occurred while uploading the profile %s\n%s\n' % (prof, ret)) + UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (prof, ret)) else: - UI_Important(_('Repository Error\nRegistration or Sigin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.\n')) + UI_Important(_('Repository Error\nRegistration or Signin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.')) def set_profiles_local_only(profs): for p in profs: @@ -1153,7 +1159,7 @@ def handle_children(profile, hat, root): else: options = cfg['qualifiers'].get(exec_target, 'ipcnu') if to_name: - fatal_error('%s has transition name but not transition mode' % entry) + fatal_error(_('%s has transition name but not transition mode') % entry) ### If profiled program executes itself only 'ix' option ##if exec_target == profile: @@ -1237,9 +1243,9 @@ def handle_children(profile, hat, root): match = regex_optmode.search(ans).groups()[0] exec_mode = str_to_mode(match) px_default = 'n' - px_msg = _("""Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.""") + px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.") if parent_uses_ld_xxx: - px_msg = _("""Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.""") + px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.") ynans = UI_YesNo(px_msg, px_default) if ynans == 'y': @@ -1247,9 +1253,9 @@ def handle_children(profile, hat, root): exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) elif ans == 'CMD_ux': exec_mode = str_to_mode('ux') - ynans = UI_YesNo(_("""Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?""") % exec_target, 'n') + ynans = UI_YesNo(_("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?") % exec_target, 'n') if ynans == 'y': - ynans = UI_YesNo(_("""Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."""), 'y') + ynans = UI_YesNo(_("Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), 'y') if ynans == 'y': # Disable the unsafe mode exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) @@ -1343,7 +1349,7 @@ def handle_children(profile, hat, root): if not aa[profile].get(exec_target, False): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = UI_YesNo(_('A local profile for %s does not exit. Create one?') % exec_target, 'n') + ynans = UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n') if ynans == 'y': hat = exec_target aa[profile][hat]['declared'] = False @@ -1835,13 +1841,10 @@ def ask_the_questions(): elif ans == 'CMD_NEW': arg = options[selected] if not re_match_include(arg): - ans = UI_GetString(_('Enter new path:'), arg) + ans = UI_GetString(_('Enter new path: '), arg) if ans: if not matchliteral(ans, path): - ynprompt = _('The specified path does not match this log entry:') - ynprompt += '\n\n ' + _('Log Entry') + ': %s' % path - ynprompt += '\n ' + _('Entered Path') + ': %s' % ans - ynprompt += _('Do you really want to use this path?') + '\n' + ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path,ans) key = UI_YesNo(ynprompt, 'n') if key == 'n': continue @@ -2343,7 +2346,7 @@ def display_changes(oldprofile, newprofile): def display_changes_with_comments(oldprofile, newprofile): """Compare the new profile with the existing profile inclusive of all the comments""" if not os.path.exists(oldprofile): - raise AppArmorException("Can't find existing profile %s to compare changes." %oldprofile) + raise AppArmorException(_("Can't find existing profile %s to compare changes.") %oldprofile) if UI_mode == 'yast': #To-Do pass @@ -2481,7 +2484,7 @@ def read_profiles(): try: os.listdir(profile_dir) except : - fatal_error('Can\'t read AppArmor profiles in %s' % profile_dir) + fatal_error(_("Can't read AppArmor profiles in %s") % profile_dir) for file in os.listdir(profile_dir): if os.path.isfile(profile_dir + '/' + file): @@ -2496,7 +2499,7 @@ def read_inactive_profiles(): try: os.listdir(profile_dir) except : - fatal_error('Can\'t read AppArmor profiles in %s' % extra_profile_dir) + fatal_error(_("Can't read AppArmor profiles in %s") % extra_profile_dir) for file in os.listdir(profile_dir): if os.path.isfile(extra_profile_dir + '/' + file): @@ -2511,7 +2514,7 @@ def read_profile(file, active_profile): with open_file_read(file) as f_in: data = f_in.readlines() except IOError: - debug_logger.debug('read_profile: can\'t read %s - skipping' %file) + debug_logger.debug("read_profile: can't read %s - skipping" %file) return None profile_data = parse_profile_data(data, file, 0) @@ -2572,7 +2575,7 @@ def parse_profile_data(data, file, do_include): if profile: #print(profile, hat) if profile != hat or not matches[3]: - raise AppArmorException('%s profile in %s contains syntax errors in line: %s.\n' % (profile, file, lineno+1)) + raise AppArmorException(_('%s profile in %s contains syntax errors in line: %s.') % (profile, file, lineno+1)) # Keep track of the start of a profile if profile and profile == hat and matches[3]: # local profile @@ -2624,7 +2627,7 @@ def parse_profile_data(data, file, do_include): elif RE_PROFILE_END.search(line): # If profile ends and we're not in one if not profile: - raise AppArmorException('Syntax Error: Unexpected End of Profile reached in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %s line: %s') % (file, lineno+1)) if in_contained_hat: hat = profile @@ -2639,7 +2642,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_CAP.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected capability entry found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %s line: %s') % (file, lineno+1)) audit = False if matches[0]: @@ -2658,7 +2661,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_LINK.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected link entry found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected link entry found in file: %s line: %s') % (file, lineno+1)) audit = False if matches[0]: @@ -2686,7 +2689,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected change profile entry found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %s line: %s') % (file, lineno+1)) cp = strip_quotes(matches[0]) profile_data[profile][hat]['changes_profile'][cp] = True @@ -2708,7 +2711,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_RLIMIT.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected rlimit entry found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %s line: %s') % (file, lineno+1)) from_name = matches[0] to_name = matches[2] @@ -2719,7 +2722,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_BOOLEAN.search(line) if not profile: - raise AppArmorException('Syntax Error: Unexpected boolean definition found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %s line: %s') % (file, lineno+1)) bool_var = matches[0] value = matches[1] @@ -2759,7 +2762,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_PATH_ENTRY.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected path entry found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %s line: %s') % (file, lineno+1)) audit = False if matches[0]: @@ -2783,10 +2786,10 @@ def parse_profile_data(data, file, do_include): try: re.compile(p_re) except: - raise AppArmorException('Syntax Error: Invalid Regex %s in file: %s line: %s' % (path, file, lineno+1)) + raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno+1)) if not validate_profile_mode(mode, allow, nt_name): - raise AppArmorException('Invalid mode %s in file: %s line: %s' % (mode, file, lineno+1)) + raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno+1)) tmpmode = set() if user: @@ -2836,7 +2839,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_NETWORK.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected network entry found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %s line: %s') % (file, lineno+1)) audit = False if matches[0]: @@ -2863,7 +2866,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected change hat declaration found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %s line: %s') % (file, lineno+1)) hat = matches[0] hat = strip_quotes(hat) @@ -2875,7 +2878,7 @@ def parse_profile_data(data, file, do_include): # An embedded hat syntax definition starts matches = RE_PROFILE_HAT_DEF.search(line).groups() if not profile: - raise AppArmorException('Syntax Error: Unexpected hat definition found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %s line: %s') % (file, lineno+1)) in_contained_hat = True hat = matches[0] @@ -2891,7 +2894,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' if filelist[file]['profiles'][profile].get(hat, False): - raise AppArmorException('Error: Multiple definitions for hat %s in profile %s.' %(hat, profile)) + raise AppArmorException(_('Error: Multiple definitions for hat %s in profile %s.') %(hat, profile)) filelist[file]['profiles'][profile][hat] = True elif line[0] == '#': @@ -2911,7 +2914,7 @@ def parse_profile_data(data, file, do_include): initial_comment = ' '.join(line) + '\n' else: - raise AppArmorException('Syntax Error: Unknown line found in file: %s line: %s' % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unknown line found in file: %s line: %s') % (file, lineno+1)) # Below is not required I'd say if not do_include: @@ -2924,7 +2927,7 @@ def parse_profile_data(data, file, do_include): # End of file reached but we're stuck in a profile if profile and not do_include: - raise AppArmorException("Syntax Error: Missing '}' . Reached end of file %s while inside profile %s" % (file, profile)) + raise AppArmorException(_("Syntax Error: Missing '}' . Reached end of file %s while inside profile %s") % (file, profile)) return profile_data @@ -2955,14 +2958,14 @@ def store_list_var(var, list_var, value, var_operation): var[list_var] = set(vlist) else: #print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) - raise AppArmorException('An existing variable redefined: %s' %list_var) + raise AppArmorException(_('An existing variable redefined: %s') %list_var) elif var_operation == '+=': if var.get(list_var, False): var[list_var] = set(var[list_var] + vlist) else: - raise AppArmorException('Values added to a non-existing variable: %s' %list_var) + raise AppArmorException(_('Values added to a non-existing variable: %s') %list_var) else: - raise AppArmorException('Unknown variable operation: %s' %var_operation) + raise AppArmorException(_('Unknown variable operation: %s') %var_operation) def strip_quotes(data): @@ -3324,7 +3327,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not os.path.isfile(prof_filename): - raise AppArmorException("Can't find existing profile to modify") + raise AppArmorException(_("Can't find existing profile to modify")) with open_file_read(prof_filename) as f_in: profile = None hat = None @@ -3960,7 +3963,7 @@ def get_include_data(filename): with open_file_read(filename) as f_in: data = f_in.readlines() else: - raise AppArmorException('File Not Found: %s' %filename) + raise AppArmorException(_('File Not Found: %s') %filename) return data def load_include(incname): @@ -4069,7 +4072,7 @@ def suggest_incs_for_path(incname, path, allow): def check_qualifiers(program): if cfg['qualifiers'].get(program, False): if cfg['qualifiers'][program] != 'p': - fatal_error(_("""%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.""") %program) + fatal_error(_("%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") %program) return False def get_subdirectories(current_dir): From 72f9a80c7686525dd7c75addae79dd28bc65a834 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 21 Sep 2013 12:36:51 +0530 Subject: [PATCH 068/183] Fixed flag reader and writer to be able to set unset flag for a specific target program also fixed tests for mini tools to be independent of existence of ntpd --- Testing/minitools_test.py | 51 +++++++++++++++++++-------------------- Tools/aa-complain | 2 +- apparmor/aa.py | 33 ++++++++++++++----------- apparmor/tools.py | 20 ++++++++------- 4 files changed, 56 insertions(+), 50 deletions(-) diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 1497f519a..37d93326d 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -7,8 +7,10 @@ import unittest import apparmor.aa as apparmor +# Path for the program test_path = '/usr/sbin/ntpd' -local_profilename = None +# Path for the target file containing profile +local_profilename = './profiles/usr.sbin.ntpd' python_interpreter = 'python' if sys.version_info >= (3,0): @@ -18,71 +20,71 @@ class Test(unittest.TestCase): def test_audit(self): #Set ntpd profile to audit mode and check if it was correctly set - subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles ntpd'%python_interpreter, shell=True) - local_profilename = apparmor.get_profile_filename(test_path) - self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Audit flag could not be set in profile %s'%local_profilename) + str(subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True)) + + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Audit flag could not be set in profile %s'%local_profilename) #Remove audit mode from ntpd profile and check if it was correctly removed - subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) - self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) def test_complain(self): #Set ntpd profile to complain mode and check if it was correctly set - subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) - self.assertEqual(apparmor.get_profile_flags(local_profilename), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) #Set ntpd profile to enforce mode and check if it was correctly set - subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) - self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) # Set audit flag and then complain flag in a profile - subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles ntpd'%python_interpreter, shell=True) - subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) - self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename) #Remove complain flag first i.e. set to enforce mode - subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) - self.assertEqual(apparmor.get_profile_flags(local_profilename), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename) #Remove audit flag - subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) def test_enforce(self): #Set ntpd profile to complain mode and check if it was correctly set - subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles -r ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) - self.assertEqual(apparmor.get_profile_flags(local_profilename), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) #Set ntpd profile to enforce mode and check if it was correctly set - subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) - self.assertEqual(apparmor.get_profile_flags(local_profilename), None, 'Complain flag could not be removed in profile %s'%local_profilename) + self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) def test_disable(self): #Disable the ntpd profile and check if it was correctly disabled - subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in disable'%local_profilename) #Enable the ntpd profile and check if it was correctly re-enabled - subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles -r ntpd'%python_interpreter, shell=True) + subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove a symlink for %s from disable'%local_profilename) @@ -104,10 +106,7 @@ if __name__ == "__main__": shutil.copytree('/etc/apparmor.d', './profiles', symlinks=True) apparmor.profile_dir='./profiles' - - # Get the profile name for the test profile using current directory/path settings - local_profilename = apparmor.get_profile_filename(test_path) - + atexit.register(clean_profile_dir) unittest.main() diff --git a/Tools/aa-complain b/Tools/aa-complain index 08d7333c4..05211eeef 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -11,5 +11,5 @@ parser.add_argument('program', type=str, nargs='+', help='name of program') args = parser.parse_args() complain = apparmor.tools.aa_tools('complain', args) - +print(args) complain.act() diff --git a/apparmor/aa.py b/apparmor/aa.py index 5ee3f748a..7210b29cb 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -237,18 +237,18 @@ def enforce(path): fatal_error(_("Can't find %s") % path) set_enforce(prof_filename, name) -def set_complain(filename, program, ): +def set_complain(filename, program): """Sets the profile to complain mode""" UI_Info(_('Setting %s to complain mode.') % program) create_symlink('force-complain', filename) - change_profile_flags(filename, 'complain', True) + change_profile_flags(filename, program, 'complain', True) def set_enforce(filename, program): """Sets the profile to enforce mode""" UI_Info(_('Setting %s to enforce mode.') % program) delete_symlink('force-complain', filename) delete_symlink('disable', filename) - change_profile_flags(filename, 'complain', False) + change_profile_flags(filename, program, 'complain', False) def delete_symlink(subdir, filename): path = filename @@ -556,7 +556,7 @@ def autodep(bin_name, pname=''): filelist[file]['include']['tunables/global'] = True write_profile_ui_feedback(pname) -def get_profile_flags(filename): +def get_profile_flags(filename, program): # To-Do # XXX If more than one profile in a file then second one is being ignored XXX # Do we return flags for both or @@ -564,13 +564,17 @@ def get_profile_flags(filename): with open_file_read(filename) as f_in: for line in f_in: if RE_PROFILE_START.search(line): - flags = RE_PROFILE_START.search(line).groups()[6] - return flags + matches = RE_PROFILE_START.search(line).groups() + profile = matches[1] or matches[3] + flags = matches[6] + if profile == program: + return flags raise AppArmorException(_('%s contains no profile')%filename) -def change_profile_flags(filename, flag, set_flag): - old_flags = get_profile_flags(filename) +def change_profile_flags(filename, program, flag, set_flag): + old_flags = get_profile_flags(filename, program) + print(old_flags) newflags = [] if old_flags: # Flags maybe white-space and/or , separated @@ -592,9 +596,9 @@ def change_profile_flags(filename, flag, set_flag): newflags = ','.join(newflags) - set_profile_flags(filename, newflags) + set_profile_flags(filename, program, newflags) -def set_profile_flags(prof_filename, newflags): +def set_profile_flags(prof_filename, program, newflags): """Reads the old profile file and updates the flags accordingly""" regex_bin_flag = re.compile('^(\s*)(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.*)\)\s+)?\{\s*(#.*)?$') regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$') @@ -616,10 +620,11 @@ def set_profile_flags(prof_filename, newflags): binary = matches[1] flag = matches[6] or 'flags=' flags = matches[7] - if newflags: - line = '%s%s %s(%s) {%s\n' % (space, binary, flag, newflags, comment) - else: - line = '%s%s {%s\n' % (space, binary, comment) + if binary == program: + if newflags: + line = '%s%s %s(%s) {%s\n' % (space, binary, flag, newflags, comment) + else: + line = '%s%s {%s\n' % (space, binary, comment) else: match = regex_hat_flag.search(line) if match: diff --git a/apparmor/tools.py b/apparmor/tools.py index b2a7ce5d4..e04f41af1 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -46,16 +46,18 @@ class aa_tools: if which: program = apparmor.get_full_path(which) - if (not program or not os.path.exists(program)): + apparmor.read_profiles() + #If program does not exists on the system but its profile does + if not program and apparmor.profile_exists(p): + program = p + + if not program or not(os.path.exists(program) or apparmor.profile_exists(program)): if program and not program.startswith('/'): program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: apparmor.UI_Info(_("%s does not exist, please double-check the path.")%program) sys.exit(1) - #apparmor.loadincludes() - apparmor.read_profiles() - if program and apparmor.profile_exists(program):#os.path.exists(program): if self.name == 'autodep': self.use_autodep(program) @@ -71,18 +73,18 @@ class aa_tools: elif self.name == 'disable': if not self.revert: - apparmor.UI_Info(_('Disabling %s.\n')%program) + apparmor.UI_Info(_('Disabling %s.')%program) self.disable_profile(filename) else: - apparmor.UI_Info(_('Enabling %s.\n')%program) + apparmor.UI_Info(_('Enabling %s.')%program) self.enable_profile(filename) elif self.name == 'audit': if not self.remove: - apparmor.UI_Info(_('Setting %s to audit mode.\n')%program) + apparmor.UI_Info(_('Setting %s to audit mode.')%program) else: - apparmor.UI_Info(_('Removing audit mode from %s.\n')%program) - apparmor.change_profile_flags(filename, 'audit', not self.remove) + apparmor.UI_Info(_('Removing audit mode from %s.')%program) + apparmor.change_profile_flags(filename, program, 'audit', not self.remove) elif self.name == 'complain': if not self.remove: From e44863e9080ee998f1d1a69794cf885ec071cf66 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 21 Sep 2013 18:50:00 +0530 Subject: [PATCH 069/183] Fixes from rev58, working on the general concerns will push it soon --- apparmor/aa.py | 9 ++++----- apparmor/tools.py | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 7210b29cb..0868699cb 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -343,7 +343,6 @@ def handle_binfmt(profile, path): """Modifies the profile to add the requirements""" reqs_processed = dict() reqs = get_reqs(path) - print(reqs) while reqs: library = reqs.pop() if not reqs_processed.get(library, False): @@ -1423,7 +1422,7 @@ def UI_ask_to_upload_profiles(): def UI_ask_mode_toggles(audit_toggle, owner_toggle, oldmode): # To-Do - pass + return (audit_toggle, owner_toggle) def parse_repo_profile(fqdbin, repo_url, profile): # To-Do @@ -3699,13 +3698,13 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: tmpmode = str_to_mode(mode) - if not write_prof_data[hat][allow]['path'][path].get('mode', False) & tmpmode: + if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode: correct = False if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name: correct = False - if audit and not write_prof_data[hat][allow]['path'][path].get('audit', False) & tmpmode: + if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode: correct = False if correct: @@ -3844,7 +3843,7 @@ def write_profile(profile): else: prof_filename = get_profile_filename(profile) - newprof = tempfile.NamedTemporaryFile('w', suffix='~' ,delete=False) + newprof = tempfile.NamedTemporaryFile('w', suffix='~' ,delete=False, dir=profile_dir) if os.path.exists(prof_filename): shutil.copymode(prof_filename, newprof.name) else: diff --git a/apparmor/tools.py b/apparmor/tools.py index e04f41af1..3f946518c 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -1,5 +1,4 @@ import os -import re import sys import apparmor.aa as apparmor From 61ed67f27bc0cbaac17d0d50cbe732032e358b3e Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 22 Sep 2013 15:01:34 +0530 Subject: [PATCH 070/183] So that closes the first proper version of aa-cleanprof with testcases added, fixed profile writer to work on multiple profiles at once, please use the view clean changes option in logprof and genprof, the comment preserver version needs tweaking that version wont be written anyways. Plus a few other changes --- Testing/cleanprof_test.in | 16 ++++++++ Testing/cleanprof_test.out | 15 +++++++ Testing/minitools_test.py | 18 +++++++++ Tools/aa-cleanprof | 1 + Tools/manpages/aa-cleanprof.pod | 8 +++- Tools/manpages/aa-disable.pod | 2 +- apparmor/aa.py | 44 +++++++++++++++------ apparmor/cleanprofile.py | 70 ++++++++++++++++----------------- apparmor/tools.py | 36 ++++++++++++++--- 9 files changed, 151 insertions(+), 59 deletions(-) create mode 100644 Testing/cleanprof_test.in create mode 100644 Testing/cleanprof_test.out diff --git a/Testing/cleanprof_test.in b/Testing/cleanprof_test.in new file mode 100644 index 000000000..77f95dd09 --- /dev/null +++ b/Testing/cleanprof_test.in @@ -0,0 +1,16 @@ +# A simple test comment which will persist +#include + +/usr/bin/a/simple/cleanprof/test/profile { + # Just for the heck of it, this comment wont see the day of light + allow /home/*/** r, + allow /home/foo/bar r, + allow /home/foo/** w, +} + +/usr/bin/other/cleanprof/test/profile { + # This one shouldn't be affected by the processing + # However this comment will be wiped, need to change that + allow /home/*/** rw, + allow /home/foo/bar r, +} \ No newline at end of file diff --git a/Testing/cleanprof_test.out b/Testing/cleanprof_test.out new file mode 100644 index 000000000..3dc375a1b --- /dev/null +++ b/Testing/cleanprof_test.out @@ -0,0 +1,15 @@ +#include + +# A simple test comment which will persist + + +/usr/bin/a/simple/cleanprof/test/profile { + allow /home/*/** r, + allow /home/foo/** w, + +} +/usr/bin/other/cleanprof/test/profile { + allow /home/*/** rw, + allow /home/foo/bar r, + +} diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 37d93326d..cdc8d75f6 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -4,6 +4,7 @@ import shutil import subprocess import sys import unittest +import filecmp import apparmor.aa as apparmor @@ -92,6 +93,22 @@ class Test(unittest.TestCase): def test_autodep(self): pass + def test_cleanprof(self): + input_file = 'cleanprof_test.in' + output_file = 'cleanprof_test.out' + #We position the local testfile + shutil.copy('./%s'%input_file, './profiles') + #Our silly test program whose profile we wish to clean + cleanprof_test = '/usr/bin/a/simple/cleanprof/test/profile' + + subprocess.check_output('%s ./../Tools/aa-cleanprof -d ./profiles -s %s' % (python_interpreter, cleanprof_test), shell=True) + + #Strip off the first line (#modified line) + subprocess.check_output('sed -i 1d ./profiles/%s'%(input_file), shell=True) + + self.assertEqual(filecmp.cmp('./profiles/%s'%input_file, './%s'%output_file, False), True, 'Failed to cleanup profile properly') + + def clean_profile_dir(): #Wipe the local profiles from the test directory shutil.rmtree('./profiles') @@ -103,6 +120,7 @@ if __name__ == "__main__": shutil.rmtree('./profiles') #copy the local profiles to the test directory + #Should be the set of cleanprofile shutil.copytree('/etc/apparmor.d', './profiles', symlinks=True) apparmor.profile_dir='./profiles' diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index c75a1644e..4b9753af2 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -7,6 +7,7 @@ import apparmor.tools parser = argparse.ArgumentParser(description='Cleanup the profiles for the given programs') parser.add_argument('-d', '--dir', type=str, help='path to profiles') parser.add_argument('program', type=str, nargs='+', help='name of program') +parser.add_argument('-s', '--silent', action='store_true', help='Silently over-write with cleanprofile') args = parser.parse_args() clean = apparmor.tools.aa_tools('cleanprof', args) diff --git a/Tools/manpages/aa-cleanprof.pod b/Tools/manpages/aa-cleanprof.pod index 6b2ef93f9..8d1ad30ec 100644 --- a/Tools/manpages/aa-cleanprof.pod +++ b/Tools/manpages/aa-cleanprof.pod @@ -6,7 +6,7 @@ aa-cleanprof - clean an existing AppArmor security profile. =head1 SYNOPSIS -BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>]> +BexecutableE> [IexecutableE> ...] [I<-d /path/to/profiles>] [I<-s>]> =head1 OPTIONS @@ -14,13 +14,17 @@ B<-d --dir /path/to/profiles> Specifies where to look for the AppArmor security profile set. Defaults to /etc/apparmor.d. + +B<-s --silent> + + Silently over-writes the profile without user prompt. =head1 DESCRIPTION B is used to perform a cleanup on one or more profiles. The tool removes any existing superfluous rules (rules that are covered under an include or another rule), reorders the rules to group similar rules -together and removes all comments. +together and removes all comments from the file. =head1 BUGS diff --git a/Tools/manpages/aa-disable.pod b/Tools/manpages/aa-disable.pod index 85c765329..3f665df50 100644 --- a/Tools/manpages/aa-disable.pod +++ b/Tools/manpages/aa-disable.pod @@ -52,7 +52,7 @@ The I<--revert> option can be used to enable the profile. =head1 BUGS If you find any bugs, please report them at -L. +L. =head1 SEE ALSO diff --git a/apparmor/aa.py b/apparmor/aa.py index 0868699cb..07d3d66e4 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2024,13 +2024,13 @@ def delete_net_duplicates(netrules, incnetrules): if incnetrules.get('all', False): incnetglob = True for fam in netrules.keys(): - if incnetglob or (type(incnetrules['rule'][fam]) != dict and incnetrules['rule'][fam] == 1): + if incnetglob or (type(incnetrules['rule'][fam]) != dict and incnetrules['rule'][fam]): if type(netrules['rule'][fam]) == dict: deleted += len(netrules['rule'][fam].keys()) else: deleted += 1 netrules['rule'].pop(fam) - elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam] == 1: + elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam]: continue else: for socket_type in netrules['rule'][fam].keys(): @@ -2275,12 +2275,14 @@ def save_profiles(): ans = '' arg = None while ans != 'CMD_SAVE_CHANGES': + if not changed: + return ans, arg = UI_PromptUser(q) if ans == 'CMD_SAVE_SELECTED': profile_name = list(changed.keys())[arg] write_profile_ui_feedback(profile_name) reload_base(profile_name) - changed.pop(profile_name) + #changed.pop(profile_name) #q['options'] = changed elif ans == 'CMD_VIEW_CHANGES': @@ -2413,7 +2415,7 @@ def collapse_log(): combinedmode = set() # Is path in original profile? if aa[profile][hat]['allow']['path'].get(path, False): - combinedmode |= aa[profile][hat]['allow']['path'][path] + combinedmode |= aa[profile][hat]['allow']['path'][path]['mode'] # Match path to regexps in profile combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0] @@ -2856,8 +2858,11 @@ def parse_profile_data(data, file, do_include): if RE_NETWORK_FAMILY_TYPE.search(network): nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() fam, typ = nmatch[:2] - profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = True - profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit + #Simply ignore any type subrules if family has True (seperately for allow and deny) + #This will lead to those type specific rules being lost when written + if not profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False): + profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = True + profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit elif RE_NETWORK_FAMILY.search(network): fam = RE_NETWORK_FAMILY.search(network).groups()[0] profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True @@ -3169,7 +3174,7 @@ def write_path_rules(prof_data, depth, allow): tmpaudit = False if user - other: # if no other mode set - ownerstr = 'owner' + ownerstr = 'owner ' tmpmode = user - other tmpaudit = user_audit user = user - tmpmode @@ -3286,18 +3291,31 @@ def serialize_profile(profile_data, name, options): elif profile_data[name]['repo']['neversubmit']: string += '# REPOSITORY: NEVERSUBMIT\n' - if profile_data[name].get('initial_comment', False): - comment = profile_data[name]['initial_comment'] - comment.replace('\\n', '\n') - string += comment + '\n' +# if profile_data[name].get('initial_comment', False): +# comment = profile_data[name]['initial_comment'] +# comment.replace('\\n', '\n') +# string += comment + '\n' prof_filename = get_profile_filename(name) if filelist.get(prof_filename, False): data += write_alias(filelist[prof_filename], 0) data += write_list_vars(filelist[prof_filename], 0) data += write_includes(filelist[prof_filename], 0) - - data += write_piece(profile_data, 0, name, name, include_flags) + + #Here should be all the profiles from the files added write after global/common stuff + for prof in sorted(filelist[prof_filename]['profiles'].keys()): + if prof != name: + if original_aa[prof][prof].get('initial_comment', False): + comment = profile_data[name]['initial_comment'] + comment.replace('\\n', '\n') + data += [comment + '\n'] + data += write_piece(original_aa[prof], 0, prof, prof, include_flags) + else: + if profile_data[name].get('initial_comment', False): + comment = profile_data[name]['initial_comment'] + comment.replace('\\n', '\n') + data += [comment + '\n'] + data += write_piece(profile_data, 0, name, name, include_flags) string += '\n'.join(data) diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py index 36ec82552..fe162beb6 100644 --- a/apparmor/cleanprofile.py +++ b/apparmor/cleanprofile.py @@ -1,5 +1,5 @@ import re -import sys +import copy import apparmor @@ -31,32 +31,16 @@ class CleanProf: #Process the profile of the program #Process every hat in the profile individually file_includes = list(self.profile.filelist[self.profile.filename]['include'].keys()) - #print(file_includes) + deleted = 0 for hat in self.profile.aa[program].keys(): #The combined list of includes from profile and the file includes = list(self.profile.aa[program][hat]['include'].keys()) + file_includes - - allow_net_rules = list(self.profile.aa[program][hat]['allow']['netdomain']['rule'].keys()) - #allow_rules = [] + list(apparmor.aa.aa[program][hat]['allow']['path'].keys()) - #allow_rules += list(apparmor.aa.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa.aa[program][hat]['allow']['capability'].keys()) - #b=set(allow_rules) - #print(allow_rules) - deleted = 0 - #print(includes) #Clean up superfluous rules from includes in the other profile for inc in includes: - #old=dele if not self.profile.include.get(inc, {}).get(inc,False): apparmor.aa.load_include(inc) deleted += apparmor.aa.delete_duplicates(self.other.aa[program][hat], inc) - #dele+= apparmor.aa.delete_path_duplicates(apparmor.aa.aa[program][program], str(inc), 'allow') - #if dele>old: - # print(inc) - #allow_rules = [] + list(apparmor.aa.aa[program][hat]['allow']['path'].keys()) - #allow_rules += list(apparmor.aa.aa[program][hat]['allow']['netdomain']['rule'].keys()) + list(apparmor.aa.aa.aa[program][hat]['allow']['capability'].keys()) - #c=set(allow_rules) - #print(b.difference(c)) #Clean the duplicates of caps in other profile deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file) @@ -65,9 +49,12 @@ class CleanProf: #Clean the duplicates of path in other profile deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file) deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file) - - print(deleted) - sys.exit(0) + + #Clean the duplicates of net rules in other profile + deleted += self.delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file) + deleted += self.delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file) + + return deleted def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): deleted = [] @@ -88,9 +75,10 @@ class CleanProf: #If modes of rule are a superset of rules implied by entry we can safely remove it if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): deleted.append(entry) - #print(deleted) + for entry in deleted: profile_other[allow]['path'].pop(entry) + return len(deleted) def delete_cap_duplicates(self, profilecaps, profilecaps_other, same_profile=True): @@ -106,23 +94,31 @@ class CleanProf: def delete_net_duplicates(self, netrules, netrules_other, same_profile=True): deleted = 0 + copy_netrules_other = copy.deepcopy(netrules_other) if netrules_other and netrules: netglob = False - # Delete matching rules from abstractions + # Delete matching rules if netrules.get('all', False): netglob = True - for fam in netrules_other.keys(): - if netglob or (type(netrules_other['rule'][fam]) != dict and netrules_other['rule'][fam] == True): - if type(netrules['rule'][fam]) == dict: - deleted += len(netrules['rule'][fam].keys()) - else: - deleted += 1 - netrules['rule'].pop(fam) - elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam] == True: - continue - else: - for socket_type in netrules['rule'][fam].keys(): - if netrules_other['rule'].get(fam, False): - netrules[fam].pop(socket_type) + #Iterate over a copy of the rules in the other profile + for fam in copy_netrules_other.keys(): + if netglob or (type(netrules['rule'][fam]) != dict and netrules['rule'][fam]): + if not same_profile: + if type(netrules_other['rule'][fam] == dict): + deleted += len(netrules['rule'][fam].keys()) + else: deleted += 1 - return deleted + netrules_other['rule'].pop(fam) + elif type(netrules_other['rule'][fam]) != dict and netrules_other['rule'][fam]: + if type(netrules['rule'][fam]) != dict and netrules['rule'][fam]: + if not same_profile: + netrules_other['rule'].pop(fam) + deleted += 1 + else: + for sock_type in netrules_other['rule'][fam].keys(): + if netrules['rule'].get(fam, False): + if netrules['rule'][fam].get(sock_type, False): + if not same_profile: + netrules_other['rule'][fam].pop(sock_type) + deleted += 1 + return deleted \ No newline at end of file diff --git a/apparmor/tools.py b/apparmor/tools.py index 3f946518c..444dbac3b 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -9,7 +9,7 @@ class aa_tools: self.profiledir = args.dir self.profiling = args.program self.check_profile_dir() - + self.silent = None if tool_name in ['audit', 'complain']: self.remove = args.remove elif tool_name == 'disable': @@ -20,7 +20,7 @@ class aa_tools: self.force = args.force self.aa_mountpoint = apparmor.check_for_apparmor() elif tool_name == 'cleanprof': - pass + self.silent = args.silent def check_profile_dir(self): if self.profiledir: @@ -114,11 +114,35 @@ class aa_tools: import apparmor.cleanprofile as cleanprofile prof = cleanprofile.Prof(filename) cleanprof = cleanprofile.CleanProf(True, prof, prof) - cleanprof.remove_duplicate_rules(program) - + deleted = cleanprof.remove_duplicate_rules(program) + apparmor.UI_Info(_("\nDeleted %s rules.") % deleted) + apparmor.changed[program] = True + if filename: - apparmor.write_profile_ui_feedback(program) - apparmor.reload_base(program) + if not self.silent: + q = apparmor.hasher() + q['title'] = 'Changed Local Profiles' + q['headers'] = [] + q['explanation'] = _('The following local profiles were changed. Would you like to save them?') + q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] + q['default'] = 'CMD_VIEW_CHANGES' + q['options'] = [] + q['selected'] = 0 + p =None + ans = '' + arg = None + while ans != 'CMD_SAVE_CHANGES': + ans, arg = apparmor.UI_PromptUser(q) + if ans == 'CMD_SAVE_CHANGES': + apparmor.write_profile_ui_feedback(program) + apparmor.reload_base(program) + elif ans == 'CMD_VIEW_CHANGES': + #oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '') + newprofile = apparmor.serialize_profile(apparmor.aa[program], program, '') + apparmor.display_changes_with_comments(filename, newprofile) + else: + apparmor.write_profile_ui_feedback(program) + apparmor.reload_base(program) else: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) From 2c19d7f3da126eb68e8887855231b391e2d54ff2 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 22 Sep 2013 15:08:30 +0530 Subject: [PATCH 071/183] added a little tiny abstraction redundancy in profile in test case --- Testing/cleanprof_test.in | 3 +++ Testing/cleanprof_test.out | 2 ++ apparmor/aa.py | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Testing/cleanprof_test.in b/Testing/cleanprof_test.in index 77f95dd09..200652577 100644 --- a/Testing/cleanprof_test.in +++ b/Testing/cleanprof_test.in @@ -3,6 +3,9 @@ /usr/bin/a/simple/cleanprof/test/profile { # Just for the heck of it, this comment wont see the day of light + #include + #Below rule comes from abstractions/base + allow /usr/share/X11/locale/** r, allow /home/*/** r, allow /home/foo/bar r, allow /home/foo/** w, diff --git a/Testing/cleanprof_test.out b/Testing/cleanprof_test.out index 3dc375a1b..34c1b5453 100644 --- a/Testing/cleanprof_test.out +++ b/Testing/cleanprof_test.out @@ -4,6 +4,8 @@ /usr/bin/a/simple/cleanprof/test/profile { + #include + allow /home/*/** r, allow /home/foo/** w, diff --git a/apparmor/aa.py b/apparmor/aa.py index 07d3d66e4..79b132002 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -3044,7 +3044,7 @@ def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn): ref, allow = set_ref_allow(prof_data, allow) if ref.get(name, False): - for key in sorted(re[name].keys()): + for key in sorted(ref[name].keys()): value = fn(ref[name][key])#eval('%s(%s)' % (fn, ref[name][key])) data.append('%s%s%s%s%s%s' %(pre, allow, prefix, key, sep, value)) if ref[name].keys(): From 86e7c22196e7ffe262d672cfe72a2f48b1811ed7 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 22 Sep 2013 15:25:20 +0530 Subject: [PATCH 072/183] Added help messages to translate strings and a few other minor fixes --- Tools/aa-audit | 8 +- Tools/aa-autodep | 8 +- Tools/aa-cleanprof | 8 +- Tools/aa-complain | 8 +- Tools/aa-disable | 8 +- Tools/aa-enforce | 8 +- Tools/aa-genprof | 8 +- Tools/aa-logprof | 8 +- Tools/aa-mergeprof | 12 +- Tools/aa-unconfined | 4 +- Translate/messages.pot | 321 +++++++++++++++++++++++++++-------------- 11 files changed, 255 insertions(+), 146 deletions(-) mode change 100644 => 100755 Translate/messages.pot diff --git a/Tools/aa-audit b/Tools/aa-audit index 67699906b..29446f800 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -4,10 +4,10 @@ import argparse import apparmor.tools -parser = argparse.ArgumentParser(description='Switch the given programs to audit mode') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('-r', '--remove', action='store_true', help='remove audit mode') -parser.add_argument('program', type=str, nargs='+', help='name of program') +parser = argparse.ArgumentParser(description=_('Switch the given programs to audit mode')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit mode')) +parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() audit = apparmor.tools.aa_tools('audit', args) diff --git a/Tools/aa-autodep b/Tools/aa-autodep index 3677bf206..41a73ac32 100644 --- a/Tools/aa-autodep +++ b/Tools/aa-autodep @@ -4,10 +4,10 @@ import argparse import apparmor.tools -parser = argparse.ArgumentParser(description='') -parser.add_argument('--force', type=str, help='override existing profile') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('program', type=str, nargs='+', help='name of program') +parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements')) +parser.add_argument('--force', type=str, help=_('overwrite existing profile')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() autodep = apparmor.tools.aa_tools('autodep', args) diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index 4b9753af2..8f3d1fb1f 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -4,10 +4,10 @@ import argparse import apparmor.tools -parser = argparse.ArgumentParser(description='Cleanup the profiles for the given programs') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('program', type=str, nargs='+', help='name of program') -parser.add_argument('-s', '--silent', action='store_true', help='Silently over-write with cleanprofile') +parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('program', type=str, nargs='+', help=_('name of program')) +parser.add_argument('-s', '--silent', action='store_true', help=_('Silently over-write with a clean profile')) args = parser.parse_args() clean = apparmor.tools.aa_tools('cleanprof', args) diff --git a/Tools/aa-complain b/Tools/aa-complain index 05211eeef..578957830 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -4,10 +4,10 @@ import argparse import apparmor.tools -parser = argparse.ArgumentParser(description='Switch the given program to complain mode') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('-r', '--remove', action='store_true', help='remove complain mode') -parser.add_argument('program', type=str, nargs='+', help='name of program') +parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('-r', '--remove', action='store_true', help=_('remove complain mode')) +parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() complain = apparmor.tools.aa_tools('complain', args) diff --git a/Tools/aa-disable b/Tools/aa-disable index ed111eb8f..91df90220 100644 --- a/Tools/aa-disable +++ b/Tools/aa-disable @@ -4,10 +4,10 @@ import argparse import apparmor.tools -parser = argparse.ArgumentParser(description='Disable the profile for the given programs') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('-r', '--revert', action='store_true', help='enable the profile for the given programs') -parser.add_argument('program', type=str, nargs='+', help='name of program') +parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('-r', '--revert', action='store_true', help=_('enable the profile for the given programs')) +parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() disable = apparmor.tools.aa_tools('disable', args) diff --git a/Tools/aa-enforce b/Tools/aa-enforce index c87dcd2b9..c480f76a3 100644 --- a/Tools/aa-enforce +++ b/Tools/aa-enforce @@ -4,10 +4,10 @@ import argparse import apparmor.tools -parser = argparse.ArgumentParser(description='Switch the given program to enforce mode') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('-r', '--remove', action='store_true', help='switch to complain mode') -parser.add_argument('program', type=str, nargs='+', help='name of program') +parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('-r', '--remove', action='store_true', help=_('switch to complain mode')) +parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() # Flipping the remove flag since complain = !enforce args.remove = not args.remove diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 4acaade53..3037a42e2 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -33,10 +33,10 @@ def last_audit_entry_time(): def restore_ratelimit(): sysctl_write(ratelimit_sysctl, ratelimit_saved) -parser = argparse.ArgumentParser(description='Generate profile for the given program') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('-f', '--file', type=str, help='path to logfile') -parser.add_argument('program', type=str, help='name of program to profile') +parser = argparse.ArgumentParser(description=_('Generate profile for the given program')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('-f', '--file', type=str, help=_('path to logfile')) +parser.add_argument('program', type=str, help=_('name of program to profile')) args = parser.parse_args() profiling = args.program diff --git a/Tools/aa-logprof b/Tools/aa-logprof index 51fc387bc..abdf1fec8 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -5,10 +5,10 @@ import os import apparmor.aa as apparmor -parser = argparse.ArgumentParser(description='Process log entries to generate profiles') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('-f', '--file', type=str, help='path to logfile') -parser.add_argument('-m', '--mark', type=str, help='mark in the log to start processing after') +parser = argparse.ArgumentParser(description=_('Process log entries to generate profiles')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('-f', '--file', type=str, help=_('path to logfile')) +parser.add_argument('-m', '--mark', type=str, help=_('mark in the log to start processing after')) args = parser.parse_args() profiledir = args.dir diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index b23fd0258..c54955d65 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -6,13 +6,13 @@ import sys import apparmor.aa as apparmor import apparmor.cleanprofile as cleanprofile -parser = argparse.ArgumentParser(description='Perform a 3way merge on the given profiles') +parser = argparse.ArgumentParser(description=_('Perform a 3way merge on the given profiles')) ##parser.add_argument('profiles', type=str, nargs=3, help='MINE BASE OTHER') -parser.add_argument('mine', type=str, help='Your profile') -parser.add_argument('base', type=str, help='The base profile') -parser.add_argument('other', type=str, help='Other profile') -parser.add_argument('-d', '--dir', type=str, help='path to profiles') -parser.add_argument('-auto', action='store_true', help='Automatically merge profiles, exits incase of *x conflicts') +parser.add_argument('mine', type=str, help=_('your profile')) +parser.add_argument('base', type=str, help=_('base profile')) +parser.add_argument('other', type=str, help=_('other profile')) +parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) +parser.add_argument('-auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts')) args = parser.parse_args() profiles = [args.mine, args.base, args.other] diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index 30653d327..33a5fe208 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -6,8 +6,8 @@ import re import apparmor.aa as apparmor -parser = argparse.ArgumentParser(description='Lists unconfined processes having tcp or udp ports') -parser.add_argument('--paranoid', action='store_true', help='scan all processes from /proc') +parser = argparse.ArgumentParser(description=_('Lists unconfined processes having tcp or udp ports')) +parser.add_argument('--paranoid', action='store_true', help=_('scan all processes from /proc')) args = parser.parse_args() paranoid = args.paranoid diff --git a/Translate/messages.pot b/Translate/messages.pot old mode 100644 new mode 100755 index e57f581ef..0c13a8e39 --- a/Translate/messages.pot +++ b/Translate/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-09-21 01:07+IST\n" +"POT-Creation-Date: 2013-09-22 15:23+IST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,6 +15,77 @@ msgstr "" "Generated-By: pygettext.py 1.5\n" +#: ../Tools/aa-audit:7 +msgid "Switch the given programs to audit mode" +msgstr "" + +#: ../Tools/aa-audit:8 ../Tools/aa-autodep:9 ../Tools/aa-cleanprof:8 +#: ../Tools/aa-complain:8 ../Tools/aa-disable:8 ../Tools/aa-enforce:8 +#: ../Tools/aa-genprof:37 ../Tools/aa-logprof:9 ../Tools/aa-mergeprof:14 +msgid "path to profiles" +msgstr "" + +#: ../Tools/aa-audit:9 +msgid "remove audit mode" +msgstr "" + +#: ../Tools/aa-audit:10 ../Tools/aa-autodep:10 ../Tools/aa-cleanprof:9 +#: ../Tools/aa-complain:10 ../Tools/aa-disable:10 ../Tools/aa-enforce:10 +msgid "name of program" +msgstr "" + +#: ../Tools/aa-autodep:7 +msgid "Generate a basic AppArmor profile by guessing requirements" +msgstr "" + +#: ../Tools/aa-autodep:8 +msgid "overwrite existing profile" +msgstr "" + +#: ../Tools/aa-cleanprof:7 +msgid "Cleanup the profiles for the given programs" +msgstr "" + +#: ../Tools/aa-cleanprof:10 +msgid "Silently over-write with a clean profile" +msgstr "" + +#: ../Tools/aa-complain:7 +msgid "Switch the given program to complain mode" +msgstr "" + +#: ../Tools/aa-complain:9 +msgid "remove complain mode" +msgstr "" + +#: ../Tools/aa-disable:7 +msgid "Disable the profile for the given programs" +msgstr "" + +#: ../Tools/aa-disable:9 +msgid "enable the profile for the given programs" +msgstr "" + +#: ../Tools/aa-enforce:7 +msgid "Switch the given program to enforce mode" +msgstr "" + +#: ../Tools/aa-enforce:9 +msgid "switch to complain mode" +msgstr "" + +#: ../Tools/aa-genprof:36 +msgid "Generate profile for the given program" +msgstr "" + +#: ../Tools/aa-genprof:38 ../Tools/aa-logprof:10 +msgid "path to logfile" +msgstr "" + +#: ../Tools/aa-genprof:39 +msgid "name of program to profile" +msgstr "" + #: ../Tools/aa-genprof:48 ../Tools/aa-logprof:20 ../Tools/aa-unconfined:17 msgid "It seems AppArmor was not started. Please enable AppArmor and try again." msgstr "" @@ -23,7 +94,7 @@ msgstr "" msgid "%s is not a directory." msgstr "" -#: ../Tools/aa-genprof:67 ../apparmor/tools.py:105 +#: ../Tools/aa-genprof:67 ../apparmor/tools.py:106 msgid "" "Can't find %s in the system path list. If the name of the application\n" "is correct, please run 'which %s' as a user with correct PATH\n" @@ -78,6 +149,42 @@ msgstr "" msgid "Finished generating profile for %s." msgstr "" +#: ../Tools/aa-logprof:8 +msgid "Process log entries to generate profiles" +msgstr "" + +#: ../Tools/aa-logprof:11 +msgid "mark in the log to start processing after" +msgstr "" + +#: ../Tools/aa-mergeprof:9 +msgid "Perform a 3way merge on the given profiles" +msgstr "" + +#: ../Tools/aa-mergeprof:11 +msgid "your profile" +msgstr "" + +#: ../Tools/aa-mergeprof:12 +msgid "base profile" +msgstr "" + +#: ../Tools/aa-mergeprof:13 +msgid "other profile" +msgstr "" + +#: ../Tools/aa-mergeprof:15 +msgid "Automatically merge profiles, exits incase of *x conflicts" +msgstr "" + +#: ../Tools/aa-unconfined:9 +msgid "Lists unconfined processes having tcp or udp ports" +msgstr "" + +#: ../Tools/aa-unconfined:10 +msgid "scan all processes from /proc" +msgstr "" + #: ../Tools/aa-unconfined:58 msgid "" "%s %s (%s) not confined\n" @@ -106,7 +213,7 @@ msgstr "" msgid "Can't find %s" msgstr "" -#: ../apparmor/aa.py:242 ../apparmor/aa.py:521 +#: ../apparmor/aa.py:242 ../apparmor/aa.py:520 msgid "Setting %s to complain mode." msgstr "" @@ -132,57 +239,57 @@ msgid "" "\t%s" msgstr "" -#: ../apparmor/aa.py:425 ../apparmor/ui.py:270 +#: ../apparmor/aa.py:424 ../apparmor/ui.py:270 msgid "Are you sure you want to abandon this set of profile changes and exit?" msgstr "" -#: ../apparmor/aa.py:427 ../apparmor/ui.py:272 +#: ../apparmor/aa.py:426 ../apparmor/ui.py:272 msgid "Abandoning all changes." msgstr "" -#: ../apparmor/aa.py:440 +#: ../apparmor/aa.py:439 msgid "Connecting to repository..." msgstr "" -#: ../apparmor/aa.py:446 +#: ../apparmor/aa.py:445 msgid "WARNING: Error fetching profiles from the repository" msgstr "" -#: ../apparmor/aa.py:523 +#: ../apparmor/aa.py:522 msgid "Error activating profiles: %s" msgstr "" -#: ../apparmor/aa.py:570 +#: ../apparmor/aa.py:572 msgid "%s contains no profile" msgstr "" -#: ../apparmor/aa.py:662 +#: ../apparmor/aa.py:666 msgid "" "WARNING: Error synchronizing profiles with the repository:\n" "%s\n" msgstr "" -#: ../apparmor/aa.py:700 +#: ../apparmor/aa.py:704 msgid "" "WARNING: Error synchronizing profiles with the repository\n" "%s" msgstr "" -#: ../apparmor/aa.py:789 ../apparmor/aa.py:840 +#: ../apparmor/aa.py:793 ../apparmor/aa.py:844 msgid "" "WARNING: An error occurred while uploading the profile %s\n" "%s" msgstr "" -#: ../apparmor/aa.py:790 +#: ../apparmor/aa.py:794 msgid "Uploaded changes to repository." msgstr "" -#: ../apparmor/aa.py:822 +#: ../apparmor/aa.py:826 msgid "Changelog Entry: " msgstr "" -#: ../apparmor/aa.py:842 +#: ../apparmor/aa.py:846 msgid "" "Repository Error\n" "Registration or Signin was unsuccessful. User login\n" @@ -190,51 +297,51 @@ msgid "" "These changes could not be sent." msgstr "" -#: ../apparmor/aa.py:940 ../apparmor/aa.py:1196 ../apparmor/aa.py:1500 -#: ../apparmor/aa.py:1536 ../apparmor/aa.py:1699 ../apparmor/aa.py:1898 -#: ../apparmor/aa.py:1929 +#: ../apparmor/aa.py:944 ../apparmor/aa.py:1200 ../apparmor/aa.py:1504 +#: ../apparmor/aa.py:1540 ../apparmor/aa.py:1703 ../apparmor/aa.py:1902 +#: ../apparmor/aa.py:1933 msgid "Profile" msgstr "" -#: ../apparmor/aa.py:943 +#: ../apparmor/aa.py:947 msgid "Default Hat" msgstr "" -#: ../apparmor/aa.py:945 +#: ../apparmor/aa.py:949 msgid "Requested Hat" msgstr "" -#: ../apparmor/aa.py:1162 +#: ../apparmor/aa.py:1166 msgid "%s has transition name but not transition mode" msgstr "" -#: ../apparmor/aa.py:1176 +#: ../apparmor/aa.py:1180 msgid "" "Target profile exists: %s\n" msgstr "" -#: ../apparmor/aa.py:1198 +#: ../apparmor/aa.py:1202 msgid "Program" msgstr "" -#: ../apparmor/aa.py:1201 +#: ../apparmor/aa.py:1205 msgid "Execute" msgstr "" -#: ../apparmor/aa.py:1202 ../apparmor/aa.py:1502 ../apparmor/aa.py:1538 -#: ../apparmor/aa.py:1750 +#: ../apparmor/aa.py:1206 ../apparmor/aa.py:1506 ../apparmor/aa.py:1542 +#: ../apparmor/aa.py:1754 msgid "Severity" msgstr "" -#: ../apparmor/aa.py:1225 +#: ../apparmor/aa.py:1229 msgid "Are you specifying a transition to a local profile?" msgstr "" -#: ../apparmor/aa.py:1237 +#: ../apparmor/aa.py:1241 msgid "Enter profile name to transition to: " msgstr "" -#: ../apparmor/aa.py:1246 +#: ../apparmor/aa.py:1250 msgid "" "Should AppArmor sanitise the environment when\n" "switching profiles?\n" @@ -244,7 +351,7 @@ msgid "" "of LD_PRELOAD or LD_LIBRARY_PATH." msgstr "" -#: ../apparmor/aa.py:1248 +#: ../apparmor/aa.py:1252 msgid "" "Should AppArmor sanitise the environment when\n" "switching profiles?\n" @@ -255,7 +362,7 @@ msgid "" "could cause functionality problems." msgstr "" -#: ../apparmor/aa.py:1256 +#: ../apparmor/aa.py:1260 msgid "" "Launching processes in an unconfined state is a very\n" "dangerous operation and can cause serious security holes.\n" @@ -264,7 +371,7 @@ msgid "" "AppArmor protection when executing %s ?" msgstr "" -#: ../apparmor/aa.py:1258 +#: ../apparmor/aa.py:1262 msgid "" "Should AppArmor sanitise the environment when\n" "running this program unconfined?\n" @@ -274,86 +381,86 @@ msgid "" "and should be avoided if at all possible." msgstr "" -#: ../apparmor/aa.py:1334 ../apparmor/aa.py:1352 +#: ../apparmor/aa.py:1338 ../apparmor/aa.py:1356 msgid "" "A profile for %s does not exist.\n" "Do you want to create one?" msgstr "" -#: ../apparmor/aa.py:1461 +#: ../apparmor/aa.py:1465 msgid "Complain-mode changes:" msgstr "" -#: ../apparmor/aa.py:1463 +#: ../apparmor/aa.py:1467 msgid "Enforce-mode changes:" msgstr "" -#: ../apparmor/aa.py:1466 +#: ../apparmor/aa.py:1470 msgid "Invalid mode found: %s" msgstr "" -#: ../apparmor/aa.py:1501 ../apparmor/aa.py:1537 +#: ../apparmor/aa.py:1505 ../apparmor/aa.py:1541 msgid "Capability" msgstr "" -#: ../apparmor/aa.py:1551 ../apparmor/aa.py:1786 +#: ../apparmor/aa.py:1555 ../apparmor/aa.py:1790 msgid "Adding %s to profile." msgstr "" -#: ../apparmor/aa.py:1553 ../apparmor/aa.py:1788 ../apparmor/aa.py:1828 -#: ../apparmor/aa.py:1947 +#: ../apparmor/aa.py:1557 ../apparmor/aa.py:1792 ../apparmor/aa.py:1832 +#: ../apparmor/aa.py:1951 msgid "Deleted %s previous matching profile entries." msgstr "" -#: ../apparmor/aa.py:1560 +#: ../apparmor/aa.py:1564 msgid "Adding capability %s to profile." msgstr "" -#: ../apparmor/aa.py:1567 +#: ../apparmor/aa.py:1571 msgid "Denying capability %s to profile." msgstr "" -#: ../apparmor/aa.py:1700 +#: ../apparmor/aa.py:1704 msgid "Path" msgstr "" -#: ../apparmor/aa.py:1709 ../apparmor/aa.py:1740 +#: ../apparmor/aa.py:1713 ../apparmor/aa.py:1744 msgid "(owner permissions off)" msgstr "" -#: ../apparmor/aa.py:1714 +#: ../apparmor/aa.py:1718 msgid "(force new perms to owner)" msgstr "" -#: ../apparmor/aa.py:1717 +#: ../apparmor/aa.py:1721 msgid "(force all rule perms to owner)" msgstr "" -#: ../apparmor/aa.py:1729 +#: ../apparmor/aa.py:1733 msgid "Old Mode" msgstr "" -#: ../apparmor/aa.py:1730 +#: ../apparmor/aa.py:1734 msgid "New Mode" msgstr "" -#: ../apparmor/aa.py:1745 +#: ../apparmor/aa.py:1749 msgid "(force perms to owner)" msgstr "" -#: ../apparmor/aa.py:1748 +#: ../apparmor/aa.py:1752 msgid "Mode" msgstr "" -#: ../apparmor/aa.py:1826 +#: ../apparmor/aa.py:1830 msgid "Adding %s %s to profile" msgstr "" -#: ../apparmor/aa.py:1844 +#: ../apparmor/aa.py:1848 msgid "Enter new path: " msgstr "" -#: ../apparmor/aa.py:1847 +#: ../apparmor/aa.py:1851 msgid "" "The specified path does not match this log entry:\n" "\n" @@ -362,153 +469,153 @@ msgid "" "Do you really want to use this path?" msgstr "" -#: ../apparmor/aa.py:1899 ../apparmor/aa.py:1930 +#: ../apparmor/aa.py:1903 ../apparmor/aa.py:1934 msgid "Network Family" msgstr "" -#: ../apparmor/aa.py:1900 ../apparmor/aa.py:1931 +#: ../apparmor/aa.py:1904 ../apparmor/aa.py:1935 msgid "Socket Type" msgstr "" -#: ../apparmor/aa.py:1945 +#: ../apparmor/aa.py:1949 msgid "Adding %s to profile" msgstr "" -#: ../apparmor/aa.py:1955 +#: ../apparmor/aa.py:1959 msgid "Adding network access %s %s to profile." msgstr "" -#: ../apparmor/aa.py:1961 +#: ../apparmor/aa.py:1965 msgid "Denying network access %s %s to profile" msgstr "" -#: ../apparmor/aa.py:2172 +#: ../apparmor/aa.py:2176 msgid "Reading log entries from %s." msgstr "" -#: ../apparmor/aa.py:2175 +#: ../apparmor/aa.py:2179 msgid "Updating AppArmor profiles in %s." msgstr "" -#: ../apparmor/aa.py:2179 +#: ../apparmor/aa.py:2183 msgid "unknown" msgstr "" -#: ../apparmor/aa.py:2242 +#: ../apparmor/aa.py:2246 msgid "" "Select which profile changes you would like to save to the\n" "local profile set." msgstr "" -#: ../apparmor/aa.py:2243 +#: ../apparmor/aa.py:2247 msgid "Local profile changes" msgstr "" -#: ../apparmor/aa.py:2265 +#: ../apparmor/aa.py:2269 ../apparmor/tools.py:126 msgid "The following local profiles were changed. Would you like to save them?" msgstr "" -#: ../apparmor/aa.py:2339 +#: ../apparmor/aa.py:2345 msgid "Profile Changes" msgstr "" -#: ../apparmor/aa.py:2349 +#: ../apparmor/aa.py:2355 msgid "Can't find existing profile %s to compare changes." msgstr "" -#: ../apparmor/aa.py:2487 ../apparmor/aa.py:2502 +#: ../apparmor/aa.py:2493 ../apparmor/aa.py:2508 msgid "Can't read AppArmor profiles in %s" msgstr "" -#: ../apparmor/aa.py:2578 +#: ../apparmor/aa.py:2584 msgid "%s profile in %s contains syntax errors in line: %s." msgstr "" -#: ../apparmor/aa.py:2630 +#: ../apparmor/aa.py:2636 msgid "Syntax Error: Unexpected End of Profile reached in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2645 +#: ../apparmor/aa.py:2651 msgid "Syntax Error: Unexpected capability entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2664 +#: ../apparmor/aa.py:2670 msgid "Syntax Error: Unexpected link entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2692 +#: ../apparmor/aa.py:2698 msgid "Syntax Error: Unexpected change profile entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2714 +#: ../apparmor/aa.py:2720 msgid "Syntax Error: Unexpected rlimit entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2725 +#: ../apparmor/aa.py:2731 msgid "Syntax Error: Unexpected boolean definition found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2765 +#: ../apparmor/aa.py:2771 msgid "Syntax Error: Unexpected path entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2789 +#: ../apparmor/aa.py:2795 msgid "Syntax Error: Invalid Regex %s in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2792 +#: ../apparmor/aa.py:2798 msgid "Invalid mode %s in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2842 +#: ../apparmor/aa.py:2848 msgid "Syntax Error: Unexpected network entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2869 +#: ../apparmor/aa.py:2878 msgid "Syntax Error: Unexpected change hat declaration found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2881 +#: ../apparmor/aa.py:2890 msgid "Syntax Error: Unexpected hat definition found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2897 +#: ../apparmor/aa.py:2906 msgid "Error: Multiple definitions for hat %s in profile %s." msgstr "" -#: ../apparmor/aa.py:2917 +#: ../apparmor/aa.py:2926 msgid "Syntax Error: Unknown line found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2930 +#: ../apparmor/aa.py:2939 msgid "Syntax Error: Missing '}' . Reached end of file %s while inside profile %s" msgstr "" -#: ../apparmor/aa.py:2961 +#: ../apparmor/aa.py:2970 msgid "An existing variable redefined: %s" msgstr "" -#: ../apparmor/aa.py:2966 +#: ../apparmor/aa.py:2975 msgid "Values added to a non-existing variable: %s" msgstr "" -#: ../apparmor/aa.py:2968 +#: ../apparmor/aa.py:2977 msgid "Unknown variable operation: %s" msgstr "" -#: ../apparmor/aa.py:3330 +#: ../apparmor/aa.py:3352 msgid "Can't find existing profile to modify" msgstr "" -#: ../apparmor/aa.py:3832 +#: ../apparmor/aa.py:3854 msgid "Writing updated profile for %s." msgstr "" -#: ../apparmor/aa.py:3966 +#: ../apparmor/aa.py:3988 msgid "File Not Found: %s" msgstr "" -#: ../apparmor/aa.py:4075 +#: ../apparmor/aa.py:4097 msgid "" "%s is currently marked as a program that should not have its own\n" "profile. Usually, programs are marked this way if creating a profile for \n" @@ -521,39 +628,41 @@ msgstr "" msgid "Log contains unknown mode %s" msgstr "" -#: ../apparmor/tools.py:51 +#: ../apparmor/tools.py:55 msgid "The given program cannot be found, please try with the fully qualified path name of the program: " msgstr "" -#: ../apparmor/tools.py:53 ../apparmor/tools.py:107 +#: ../apparmor/tools.py:57 ../apparmor/tools.py:108 msgid "%s does not exist, please double-check the path." msgstr "" -#: ../apparmor/tools.py:70 +#: ../apparmor/tools.py:71 msgid "Profile for %s not found, skipping" msgstr "" -#: ../apparmor/tools.py:74 -msgid "" -"Disabling %s.\n" +#: ../apparmor/tools.py:75 +msgid "Disabling %s." msgstr "" -#: ../apparmor/tools.py:77 -msgid "" -"Enabling %s.\n" +#: ../apparmor/tools.py:78 +msgid "Enabling %s." msgstr "" -#: ../apparmor/tools.py:82 -msgid "" -"Setting %s to audit mode.\n" +#: ../apparmor/tools.py:83 +msgid "Setting %s to audit mode." msgstr "" -#: ../apparmor/tools.py:84 -msgid "" -"Removing audit mode from %s.\n" +#: ../apparmor/tools.py:85 +msgid "Removing audit mode from %s." msgstr "" -#: ../apparmor/tools.py:122 +#: ../apparmor/tools.py:118 +msgid "" +"\n" +"Deleted %s rules." +msgstr "" + +#: ../apparmor/tools.py:147 msgid "The profile for %s does not exists. Nothing to clean." msgstr "" From 4debd1ea79f1fea970794684b67aaf7286792105 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 22 Sep 2013 22:51:30 +0530 Subject: [PATCH 073/183] Only ran sed -i s/ *// in ./apparmor/*.py , ./Tools/aa* and ./Testing/*.py no other changes, should ignore this commit unless it broke something --- Testing/aa_test.py | 36 +- Testing/common_test.py | 4 +- Testing/config_test.py | 10 +- Testing/minitools_test.py | 50 +- Testing/severity_test.py | 30 +- Tools/aa-genprof | 4 +- Tools/aa-mergeprof | 140 +++--- Tools/aa-unconfined | 4 +- apparmor/__init__.py | 2 +- apparmor/aa.py | 991 +++++++++++++++++++------------------- apparmor/aamode.py | 46 +- apparmor/cleanprofile.py | 30 +- apparmor/common.py | 24 +- apparmor/config.py | 44 +- apparmor/logparser.py | 74 +-- apparmor/severity.py | 26 +- apparmor/tools.py | 50 +- apparmor/ui.py | 75 ++- apparmor/yasti.py | 4 +- 19 files changed, 821 insertions(+), 823 deletions(-) diff --git a/Testing/aa_test.py b/Testing/aa_test.py index cfa38a212..75653699f 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -6,9 +6,9 @@ import apparmor.aa import apparmor.logparser class Test(unittest.TestCase): - + def setUp(self): - self.MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, + self.MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, 'w': apparmor.aamode.AA_MAY_WRITE, 'r': apparmor.aamode.AA_MAY_READ, 'a': apparmor.aamode.AA_MAY_APPEND, @@ -26,7 +26,7 @@ class Test(unittest.TestCase): def test_loadinclude(self): apparmor.aa.loadincludes() - + def test_path_globs(self): globs = { '/foo/': '/*/', @@ -47,15 +47,15 @@ class Test(unittest.TestCase): '/usr/foo/**/*': '/usr/foo/**', '/usr/foo/*/bar': '/usr/foo/*/*', '/usr/bin/foo*bar': '/usr/bin/*', - '/usr/bin/*foo*': '/usr/bin/*', + '/usr/bin/*foo*': '/usr/bin/*', '/usr/foo/*/*': '/usr/foo/**', '/usr/foo/*/**': '/usr/foo/**', '/**': '/**', - '/**/': '/**/' + '/**/': '/**/' } for path in globs.keys(): self.assertEqual(apparmor.aa.glob_path(path), globs[path], 'Unexpected glob generated for path: %s'%path) - + def test_path_withext_globs(self): globs = { '/foo/bar': '/foo/bar', @@ -70,11 +70,11 @@ class Test(unittest.TestCase): '/usr/*foo*.bar': '/usr/*.bar', '/usr/**foo.bar': '/usr/**.bar', '/usr/*foo.bar': '/usr/*.bar', - '/usr/foo.b*': '/usr/*.b*' + '/usr/foo.b*': '/usr/*.b*' } for path in globs.keys(): self.assertEqual(apparmor.aa.glob_path_withext(path), globs[path], 'Unexpected glob generated for path: %s'%path) - + def test_parse_event(self): parser = apparmor.logparser.ReadLog('', '', '', '', '') event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30' @@ -84,11 +84,11 @@ class Test(unittest.TestCase): self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], set(['w', 'a', '::w', '::a'])) #print(parsed_event) - + #event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0' #parsed_event = apparmor.aa.parse_event(event) #print(parsed_event) - + event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="rw" denied_mask="rw" fsuid=0 ouid=1000' parsed_event = parser.parse_event(event) self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name') @@ -96,19 +96,19 @@ class Test(unittest.TestCase): self.assertEqual(parsed_event['aamode'], 'PERMITTING') self.assertEqual(parsed_event['request_mask'], set(['r', 'w', 'a','::r' , '::w', '::a'])) #print(parsed_event) - - + + def test_modes_to_string(self): - - + + for string in self.MODE_TEST.keys(): mode = self.MODE_TEST[string] self.assertEqual(apparmor.aamode.mode_to_str(mode), string, 'mode is %s and string is %s'%(mode, string)) - + def test_string_to_modes(self): #self.assertEqual(apparmor.aa.str_to_mode('wc'), 32270) - MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, + MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, 'w': apparmor.aamode.AA_MAY_WRITE, 'r': apparmor.aamode.AA_MAY_READ, 'a': apparmor.aamode.AA_MAY_APPEND, @@ -123,11 +123,11 @@ class Test(unittest.TestCase): 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, # Child + Unsafe 'C': apparmor.aamode.AA_EXEC_CHILD, } - + #while MODE_TEST: # string,mode = MODE_TEST.popitem() # self.assertEqual(apparmor.aamode.str_to_mode(string), mode) - + #self.assertEqual(apparmor.aa.str_to_mode('C'), 2048) diff --git a/Testing/common_test.py b/Testing/common_test.py index 1ff0b5a21..4a69b906d 100644 --- a/Testing/common_test.py +++ b/Testing/common_test.py @@ -17,11 +17,11 @@ class Test(unittest.TestCase): parsed_regex = re.compile(apparmor.common.convert_regexp(regex)) for regex_testcase in regex_tests.options(regex): self.assertEqual(bool(parsed_regex.search(regex_testcase)), eval(regex_tests[regex][regex_testcase]), 'Incorrectly Parsed regex: %s' %regex) - + #def test_readkey(self): # print("Please press the Y button on the keyboard.") # self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!') - + if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.test_RegexParser'] diff --git a/Testing/config_test.py b/Testing/config_test.py index edeecd8cd..324582271 100644 --- a/Testing/config_test.py +++ b/Testing/config_test.py @@ -14,11 +14,11 @@ class Test(unittest.TestCase): logprof_sections = ['settings', 'repository', 'qualifiers', 'required_hats', 'defaulthat', 'globs'] logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes'] logprof_settings_parser = '/sbin/apparmor_parser /sbin/subdomain_parser' - + self.assertEqual(conf.sections(), logprof_sections) self.assertEqual(conf.options('settings'), logprof_sections_options) self.assertEqual(conf['settings']['parser'], logprof_settings_parser) - + def test_ShellConfig(self): shell_config = config.Config('shell') shell_config.CONF_DIR = '.' @@ -26,12 +26,12 @@ class Test(unittest.TestCase): easyprof_sections = ['POLICYGROUPS_DIR', 'TEMPLATES_DIR'] easyprof_Policygroup = '/usr/share/apparmor/easyprof/policygroups' easyprof_Templates = '/usr/share/apparmor/easyprof/templates' - + self.assertEqual(sorted(list(conf[''].keys())), sorted(easyprof_sections)) self.assertEqual(conf['']['POLICYGROUPS_DIR'], easyprof_Policygroup) self.assertEqual(conf['']['TEMPLATES_DIR'], easyprof_Templates) - - + + if __name__ == "__main__": diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index cdc8d75f6..36eaf5064 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -18,81 +18,81 @@ if sys.version_info >= (3,0): python_interpreter = 'python3' class Test(unittest.TestCase): - + def test_audit(self): #Set ntpd profile to audit mode and check if it was correctly set str(subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True)) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Audit flag could not be set in profile %s'%local_profilename) - + #Remove audit mode from ntpd profile and check if it was correctly removed subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) - + def test_complain(self): #Set ntpd profile to complain mode and check if it was correctly set subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) - + #Set ntpd profile to enforce mode and check if it was correctly set subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) - + # Set audit flag and then complain flag in a profile subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles %s'%(python_interpreter, test_path), shell=True) subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit,complain', 'Complain flag could not be set in profile %s'%local_profilename) - + #Remove complain flag first i.e. set to enforce mode subprocess.check_output('%s ./../Tools/aa-complain -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'audit', 'Complain flag could not be removed in profile %s'%local_profilename) - + #Remove audit flag subprocess.check_output('%s ./../Tools/aa-audit -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) - + def test_enforce(self): #Set ntpd profile to complain mode and check if it was correctly set subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) - + self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in force-complain'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), 'complain', 'Complain flag could not be set in profile %s'%local_profilename) - - + + #Set ntpd profile to enforce mode and check if it was correctly set subprocess.check_output('%s ./../Tools/aa-enforce -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/force-complain/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from force-complain'%local_profilename) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove symlink for %s from disable'%local_profilename) self.assertEqual(apparmor.get_profile_flags(local_profilename, test_path), None, 'Complain flag could not be removed in profile %s'%local_profilename) - - + + def test_disable(self): #Disable the ntpd profile and check if it was correctly disabled subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), True, 'Failed to create a symlink for %s in disable'%local_profilename) - + #Enable the ntpd profile and check if it was correctly re-enabled subprocess.check_output('%s ./../Tools/aa-disable -d ./profiles -r %s'%(python_interpreter, test_path), shell=True) self.assertEqual(os.path.islink('./profiles/disable/%s'%os.path.basename(local_profilename)), False, 'Failed to remove a symlink for %s from disable'%local_profilename) - - + + def test_autodep(self): pass - + def test_cleanprof(self): input_file = 'cleanprof_test.in' output_file = 'cleanprof_test.out' @@ -100,31 +100,31 @@ class Test(unittest.TestCase): shutil.copy('./%s'%input_file, './profiles') #Our silly test program whose profile we wish to clean cleanprof_test = '/usr/bin/a/simple/cleanprof/test/profile' - + subprocess.check_output('%s ./../Tools/aa-cleanprof -d ./profiles -s %s' % (python_interpreter, cleanprof_test), shell=True) - + #Strip off the first line (#modified line) subprocess.check_output('sed -i 1d ./profiles/%s'%(input_file), shell=True) - + self.assertEqual(filecmp.cmp('./profiles/%s'%input_file, './%s'%output_file, False), True, 'Failed to cleanup profile properly') - - + + def clean_profile_dir(): #Wipe the local profiles from the test directory shutil.rmtree('./profiles') if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] - + if os.path.exists('./profiles'): shutil.rmtree('./profiles') #copy the local profiles to the test directory #Should be the set of cleanprofile shutil.copytree('/etc/apparmor.d', './profiles', symlinks=True) - + apparmor.profile_dir='./profiles' atexit.register(clean_profile_dir) - + unittest.main() diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 3613bc20b..15de54e61 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -8,18 +8,18 @@ sys.path.append('../') import apparmor.severity as severity from apparmor.common import AppArmorException -class Test(unittest.TestCase): - +class Test(unittest.TestCase): + def setUp(self): #copy the local profiles to the test directory if os.path.exists('./profiles'): shutil.rmtree('./profiles') shutil.copytree('/etc/apparmor.d/', './profiles/', symlinks=True) - + def tearDown(self): #Wipe the local profiles from the test directory shutil.rmtree('./profiles') - + def testRank_Test(self): s = severity.Severity('severity.db') rank = s.rank('/usr/bin/whatis', 'x') @@ -42,35 +42,35 @@ class Test(unittest.TestCase): self.assertEqual(rank, 9, 'Wrong rank') self.assertEqual(s.rank('/etc/apparmor/**', 'r') , 6, 'Invalid Rank') self.assertEqual(s.rank('/etc/**', 'r') , 10, 'Invalid Rank') - + # Load all variables for /sbin/klogd and test them s.load_variables('profiles/sbin.klogd') self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') - + s.unload_variables() - + s.load_variables('profiles/usr.sbin.dnsmasq') self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') - + #self.assertEqual(s.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') - + def testInvalid(self): s = severity.Severity('severity.db') - rank = s.rank('/dev/doublehit', 'i') - self.assertEqual(rank, 10, 'Wrong') + rank = s.rank('/dev/doublehit', 'i') + self.assertEqual(rank, 10, 'Wrong') try: - broken = severity.Severity('severity_broken.db') + broken = severity.Severity('severity_broken.db') except AppArmorException: pass rank = s.rank('CAP_UNKOWN') - rank = s.rank('CAP_K*') - - + rank = s.rank('CAP_K*') + + if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 3037a42e2..27e90fd58 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -115,13 +115,13 @@ while not done_profiling: else: logmark = last_audit_entry_time() - + q=apparmor.hasher() q['headers'] = [_('Profiling'), program] q['functions'] = ['CMD_SCAN', 'CMD_FINISHED'] q['default'] = 'CMD_SCAN' ans, arg = apparmor.UI_PromptUser(q, 'noexit') - + if ans == 'CMD_SCAN': lp_ret = apparmor.do_logprof_pass(logmark, passno) passno += 1 diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index c54955d65..8168fb154 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -19,16 +19,16 @@ profiles = [args.mine, args.base, args.other] if __name__ == '__main__': main() - + print(profiles) def main(): mergeprofiles = Merge(profiles) - + class Merge(object): def __init__(self, profiles): user, base, other = profiles - + #Read and parse base profile and save profile data, include data from it and reset them apparmor.read_profile(base, True) base = cleanprof.Prof() @@ -36,7 +36,7 @@ class Merge(object): #self.base_filelist = apparmor.filelist #self.base_include = apparmor.include reset() - + #Read and parse other profile and save profile data, include data from it and reset them apparmor.read_profile(other, True) other = cleanprof.prof() @@ -44,36 +44,36 @@ class Merge(object): #self.other_filelist = apparmor.filelist #self.other_include = apparmor.include reset() - + #Read and parse user profile apparmor.read_profile(profiles[0], True) user = cleanprof.prof() #user_aa = apparmor.aa #user_filelist = apparmor.filelist #user_include = apparmor.include - + def reset(): apparmor.aa = apparmor.hasher() apparmor.filelist = hasher() apparmor.include = dict() apparmor.existing_profiles = hasher() apparmor.original_aa = hasher() - + def clear_common(self): common_base_other() remove_common('base') remove_common('other') - + def common_base_other(self): pass - + def remove_common(self, profile): if prof1 == 'base': # def intersect(ra, rb): # """Given two ranges return the range where they intersect or None. -# +# # >>> intersect((0, 10), (0, 6)) # (0, 6) # >>> intersect((0, 10), (5, 15)) @@ -84,15 +84,15 @@ class Merge(object): # (7, 9) # """ # # preconditions: (ra[0] <= ra[1]) and (rb[0] <= rb[1]) -# +# # sa = max(ra[0], rb[0]) # sb = min(ra[1], rb[1]) # if sa < sb: # return sa, sb # else: # return None -# -# +# +# # def compare_range(a, astart, aend, b, bstart, bend): # """Compare a[astart:aend] == b[bstart:bend], without slicing. # """ @@ -103,20 +103,20 @@ class Merge(object): # return False # else: # return True -# -# -# -# +# +# +# +# # class Merge3(object): # """3-way merge of texts. -# +# # Given BASE, OTHER, THIS, tries to produce a combined text # incorporating the changes from both BASE->OTHER and BASE->THIS. # All three will typically be sequences of lines.""" -# +# # def __init__(self, base, a, b, is_cherrypick=False, allow_objects=False): # """Constructor. -# +# # :param base: lines in BASE # :param a: lines in A # :param b: lines in B @@ -136,7 +136,7 @@ class Merge(object): # self.a = a # self.b = b # self.is_cherrypick = is_cherrypick -# +# # def merge_lines(self, # name_a=None, # name_b=None, @@ -190,10 +190,10 @@ class Merge(object): # yield end_marker + newline # else: # raise ValueError(what) -# +# # def merge_annotated(self): # """Return merge with conflicts, showing origin of lines. -# +# # Most useful for debugging merge. # """ # for t in self.merge_regions(): @@ -217,22 +217,22 @@ class Merge(object): # yield '>>>>\n' # else: # raise ValueError(what) -# +# # def merge_groups(self): # """Yield sequence of line groups. Each one is a tuple: -# +# # 'unchanged', lines # Lines unchanged from base -# +# # 'a', lines # Lines taken from a -# +# # 'same', lines # Lines taken from a (and equal to b) -# +# # 'b', lines # Lines taken from b -# +# # 'conflict', base_lines, a_lines, b_lines # Lines from base were changed to either a or b and conflict. # """ @@ -251,37 +251,37 @@ class Merge(object): # self.b[t[5]:t[6]]) # else: # raise ValueError(what) -# +# # def merge_regions(self): # """Return sequences of matching and conflicting regions. -# +# # This returns tuples, where the first value says what kind we # have: -# +# # 'unchanged', start, end # Take a region of base[start:end] -# +# # 'same', astart, aend # b and a are different from base but give the same result -# +# # 'a', start, end # Non-clashing insertion from a[start:end] -# +# # Method is as follows: -# +# # The two sequences align only on regions which match the base # and both descendents. These are found by doing a two-way diff # of each one against the base, and then finding the # intersections between those regions. These "sync regions" # are by definition unchanged in both and easily dealt with. -# +# # The regions in between can be in any of three cases: # conflicted, or changed on only one side. # """ -# +# # # section a[0:ia] has been disposed of, etc # iz = ia = ib = 0 -# +# # for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions(): # matchlen = zend - zmatch # # invariants: @@ -295,14 +295,14 @@ class Merge(object): # # assert len_a >= 0 # # assert len_b >= 0 # # assert len_base >= 0 -# +# # #print 'unmatched a=%d, b=%d' % (len_a, len_b) -# +# # if len_a or len_b: # # try to avoid actually slicing the lists # same = compare_range(self.a, ia, amatch, # self.b, ib, bmatch) -# +# # if same: # yield 'same', ia, amatch # else: @@ -324,25 +324,25 @@ class Merge(object): # yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch # else: # raise AssertionError("can't handle a=b=base but unmatched") -# +# # ia = amatch # ib = bmatch # iz = zmatch -# +# # # if the same part of the base was deleted on both sides # # that's OK, we can just skip it. -# +# # if matchlen > 0: # # invariants: # # assert ia == amatch # # assert ib == bmatch # # assert iz == zmatch -# +# # yield 'unchanged', zmatch, zend # iz = zend # ia = aend # ib = bend -# +# # def _refine_cherrypick_conflict(self, zstart, zend, astart, aend, bstart, bend): # """When cherrypicking b => a, ignore matches with b and base.""" # # Do not emit regions which match, only regions which do not match @@ -382,10 +382,10 @@ class Merge(object): # astart, aend, bstart + last_b_idx, bstart + b_idx) # if not yielded_a: # yield ('conflict', zstart, zend, astart, aend, bstart, bend) -# +# # def reprocess_merge_regions(self, merge_regions): # """Where there are conflict regions, remove the agreed lines. -# +# # Lines where both A and B have made the same changes are # eliminated. # """ @@ -413,19 +413,19 @@ class Merge(object): # reg = self.mismatch_region(next_a, amatch, next_b, bmatch) # if reg is not None: # yield reg -# +# # @staticmethod # def mismatch_region(next_a, region_ia, next_b, region_ib): # if next_a < region_ia or next_b < region_ib: # return 'conflict', None, None, next_a, region_ia, next_b, region_ib -# +# # def find_sync_regions(self): # """Return a list of sync regions, where both descendents match the base. -# +# # Generates a list of (base1, base2, a1, a2, b1, b2). There is # always a zero-length sync region at the end of all the files. # """ -# +# # ia = ib = 0 # amatches = patiencediff.PatienceSequenceMatcher( # None, self.base, self.a).get_matching_blocks() @@ -433,13 +433,13 @@ class Merge(object): # None, self.base, self.b).get_matching_blocks() # len_a = len(amatches) # len_b = len(bmatches) -# +# # sl = [] -# +# # while ia < len_a and ib < len_b: # abase, amatch, alen = amatches[ia] # bbase, bmatch, blen = bmatches[ib] -# +# # # there is an unconflicted block at i; how long does it # # extend? until whichever one ends earlier. # i = intersect((abase, abase+alen), (bbase, bbase+blen)) @@ -447,23 +447,23 @@ class Merge(object): # intbase = i[0] # intend = i[1] # intlen = intend - intbase -# +# # # found a match of base[i[0], i[1]]; this may be less than # # the region that matches in either one # # assert intlen <= alen # # assert intlen <= blen # # assert abase <= intbase # # assert bbase <= intbase -# +# # asub = amatch + (intbase - abase) # bsub = bmatch + (intbase - bbase) # aend = asub + intlen # bend = bsub + intlen -# +# # # assert self.base[intbase:intend] == self.a[asub:aend], \ # # (self.base[intbase:intend], self.a[asub:aend]) # # assert self.base[intbase:intend] == self.b[bsub:bend] -# +# # sl.append((intbase, intend, # asub, aend, # bsub, bend)) @@ -472,23 +472,23 @@ class Merge(object): # ia += 1 # else: # ib += 1 -# +# # intbase = len(self.base) # abase = len(self.a) # bbase = len(self.b) # sl.append((intbase, intbase, abase, abase, bbase, bbase)) -# +# # return sl -# +# # def find_unconflicted(self): # """Return a list of ranges in base that are not conflicted.""" # am = patiencediff.PatienceSequenceMatcher( # None, self.base, self.a).get_matching_blocks() # bm = patiencediff.PatienceSequenceMatcher( # None, self.base, self.b).get_matching_blocks() -# +# # unc = [] -# +# # while am and bm: # # there is an unconflicted block at i; how long does it # # extend? until whichever one ends earlier. @@ -499,18 +499,18 @@ class Merge(object): # i = intersect((a1, a2), (b1, b2)) # if i: # unc.append(i) -# +# # if a2 < b2: # del am[0] # else: # del bm[0] -# +# # return unc -# +# # a = file(profiles[0], 'rt').readlines() # base = file(profiles[1], 'rt').readlines() # b = file(profiles[2], 'rt').readlines() -# +# # m3 = Merge3(base, a, b) -# +# # sys.stdout.write(m3.merge_annotated()) \ No newline at end of file diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index 33a5fe208..131fb622a 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -23,7 +23,7 @@ else: regex_tcp_udp = re.compile('^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)') import subprocess output = subprocess.check_output('LANG=C netstat -nlp', shell=True).split('\n') - + for line in output: match = regex_tcp_udp.search(line) if match: @@ -42,7 +42,7 @@ for pid in sorted(pids): for line in current: if line.startswith('/') or line.startswith('null'): attr = line.strip() - + cmdline = apparmor.cmd(['cat', '/proc/%s/cmdline'%pid])[1] pname = cmdline.split('\0')[0] if '/' in pname and pname != prog: diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 741b464e6..8588e00fd 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -10,5 +10,5 @@ def init_localisation(): except IOError: trans = gettext.NullTranslations() trans.install() - + init_localisation() \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index 79b132002..493998367 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -35,7 +35,7 @@ CONFDIR = '/etc/apparmor' running_under_genprof = False unimplemented_warning = False -# The database for severity +# The database for severity sev_db = None # The file to read log messages from ### Was our @@ -61,7 +61,7 @@ user_globs = [] ## Variables used under logprof ### Were our -t = hasher()#dict() +t = hasher()#dict() transitions = hasher() aa = hasher() # Profiles originally in sd, replace by aa original_aa = hasher() @@ -86,12 +86,12 @@ def on_exit(): """Shutdowns the logger and records exit if debugging enabled""" debug_logger.debug('Exiting..') debug_logger.shutdown() - + # Register the on_exit method with atexit atexit.register(on_exit) def check_for_LD_XXX(file): - """Returns True if specified program contains references to LD_PRELOAD or + """Returns True if specified program contains references to LD_PRELOAD or LD_LIBRARY_PATH to give the Px/Ux code better suggestions""" found = False if not os.path.isfile(file): @@ -114,16 +114,16 @@ def fatal_error(message): message = message + '\n' + tb_stack debug_logger.error(message) caller = inspect.stack()[1][3] - + # If caller is SendDataToYast or GetDatFromYast simply exit if caller == 'SendDataToYast' or caller== 'GetDatFromYast': sys.exit(1) - + # Else tell user what happened UI_Important(message) shutdown_yast() sys.exit(1) - + def check_for_apparmor(): """Finds and returns the mointpoint for apparmor None otherwise""" filesystem = '/proc/filesystems' @@ -144,7 +144,7 @@ def check_for_apparmor(): if match: mountpoint = match.groups()[0] + '/apparmor' if valid_path(mountpoint): - aa_mountpoint = mountpoint + aa_mountpoint = mountpoint # Check if apparmor is actually mounted there if not valid_path(aa_mountpoint + '/profiles'): aa_mountpoint = None @@ -161,7 +161,7 @@ def which(file): if os.access(env_path, os.X_OK): return env_path return None - + def get_full_path(original_path): """Return the full path after resolving any symlinks""" path = original_path @@ -208,7 +208,7 @@ def get_profile_filename(profile): profile = profile.replace('/', '.') full_profilename = profile_dir + '/' + profile return full_profilename - + def name_to_prof_filename(prof_filename): """Returns the profile""" if prof_filename.startswith(profile_dir): @@ -236,7 +236,7 @@ def enforce(path): if not prof_filename : fatal_error(_("Can't find %s") % path) set_enforce(prof_filename, name) - + def set_complain(filename, program): """Sets the profile to complain mode""" UI_Info(_('Setting %s to complain mode.') % program) @@ -270,13 +270,13 @@ def create_symlink(subdir, filename): if not os.path.exists(symlink_dir): # If the symlink directory does not exist create it os.makedirs(symlink_dir) - + if not os.path.exists(link): try: os.symlink(filename, link) except: raise AppArmorException(_('Could not create %s symlink to %s.')%(link, filename)) - + def head(file): """Returns the first/head line of the file""" first = '' @@ -313,9 +313,9 @@ def get_output(params): output = output.decode('utf-8').split('\n') # Remove the extra empty string caused due to \n if present if len(output) > 1: - output.pop() - return (ret, output) - + output.pop() + return (ret, output) + def get_reqs(file): """Returns a list of paths from ldd output""" pattern1 = re.compile('^\s*\S+ => (\/\S+)') @@ -357,7 +357,7 @@ def handle_binfmt(profile, path): continue profile['allow']['path'][library]['mode'] = profile['allow']['path'][library].get('mode', set()) | str_to_mode('mr') profile['allow']['path'][library]['audit'] |= profile['allow']['path'][library].get('audit', set()) - + def get_inactive_profile(local_profile): if extras.get(local_profile, False): return {local_profile: extras[local_profile]} @@ -366,21 +366,21 @@ def get_inactive_profile(local_profile): def create_new_profile(localfile): local_profile = hasher() local_profile[localfile]['flags'] = 'complain' - local_profile[localfile]['include']['abstractions/base'] = 1 - + local_profile[localfile]['include']['abstractions/base'] = 1 + if os.path.isfile(localfile): hashbang = head(localfile) if hashbang.startswith('#!'): interpreter_path = get_full_path(hashbang.lstrip('#!').strip()) - + interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path) - + local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('r')) | str_to_mode('r') - + local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set()) - - local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') - + + local_profile[localfile]['allow']['path'][interpreter_path]['mode'] = local_profile[localfile]['allow']['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') + local_profile[localfile]['allow']['path'][interpreter_path]['audit'] = local_profile[localfile]['allow']['path'][interpreter_path].get('audit', set()) if interpreter == 'perl': @@ -393,23 +393,23 @@ def create_new_profile(localfile): local_profile[localfile]['include']['abstractions/bash'] = True handle_binfmt(local_profile[localfile], interpreter_path) else: - + local_profile[localfile]['allow']['path'][localfile]['mode'] = local_profile[localfile]['allow']['path'][localfile].get('mode', str_to_mode('mr')) | str_to_mode('mr') - + local_profile[localfile]['allow']['path'][localfile]['audit'] = local_profile[localfile]['allow']['path'][localfile].get('audit', set()) handle_binfmt(local_profile[localfile], localfile) - # Add required hats to the profile if they match the localfile + # Add required hats to the profile if they match the localfile for hatglob in cfg['required_hats'].keys(): if re.search(hatglob, localfile): for hat in sorted(cfg['required_hats'][hatglob].split()): local_profile[hat]['flags'] = 'complain' - + created.append(localfile) debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) return {localfile: local_profile} - + def delete_profile(local_prof): """Deletes the specified file from the disk and remove it from our list""" profile_file = get_profile_filename(local_prof) @@ -417,7 +417,7 @@ def delete_profile(local_prof): os.remove(profile_file) if aa.get(local_prof, False): aa.pop(local_prof) - + #prof_unload(local_prof) def confirm_and_abort(): @@ -428,7 +428,7 @@ def confirm_and_abort(): for prof in created: delete_profile(prof) sys.exit(0) - + def get_profile(prof_name): profile_data = None distro = cfg['repository']['distro'] @@ -459,17 +459,17 @@ def get_profile(prof_name): tmp_list = [] preferred_present = False preferred_user = cfg['repository'].get('preferred_user', 'NOVELL') - + for p in profile_hash.keys(): if profile_hash[p]['username'] == preferred_user: preferred_present = True else: tmp_list.append(profile_hash[p]['username']) - + if preferred_present: options.append(preferred_user) options += tmp_list - + q = dict() q['headers'] = ['Profile', prof_name] q['functions'] = ['CMD_VIEW_PROFILE', 'CMD_USE_PROFILE', 'CMD_CREATE_PROFILE', @@ -477,7 +477,7 @@ def get_profile(prof_name): q['default'] = "CMD_VIEW_PROFILE" q['options'] = options q['selected'] = 0 - + ans = '' while 'CMD_USE_PROFILE' not in ans and 'CMD_CREATE_PROFILE' not in ans: ans, arg = UI_PromptUser(q) @@ -495,7 +495,7 @@ def get_profile(prof_name): #else: # pager = get_pager() # proc = subprocess.Popen(pager, stdin=subprocess.PIPE) - # proc.communicate('Profile submitted by %s:\n\n%s\n\n' % + # proc.communicate('Profile submitted by %s:\n\n%s\n\n' % # (options[arg], p['profile'])) # proc.kill() elif ans == 'CMD_USE_PROFILE': @@ -558,7 +558,7 @@ def autodep(bin_name, pname=''): def get_profile_flags(filename, program): # To-Do # XXX If more than one profile in a file then second one is being ignored XXX - # Do we return flags for both or + # Do we return flags for both or flags = '' with open_file_read(filename) as f_in: for line in f_in: @@ -570,7 +570,7 @@ def get_profile_flags(filename, program): return flags raise AppArmorException(_('%s contains no profile')%filename) - + def change_profile_flags(filename, program, flag, set_flag): old_flags = get_profile_flags(filename, program) print(old_flags) @@ -585,7 +585,7 @@ def change_profile_flags(filename, program, flag, set_flag): else: newflags = old_flags.split() #newflags = [lambda x:x.strip(), oldflags] - + if set_flag: if flag not in newflags: newflags.append(flag) @@ -595,8 +595,8 @@ def change_profile_flags(filename, program, flag, set_flag): newflags = ','.join(newflags) - set_profile_flags(filename, program, newflags) - + set_profile_flags(filename, program, newflags) + def set_profile_flags(prof_filename, program, newflags): """Reads the old profile file and updates the flags accordingly""" regex_bin_flag = re.compile('^(\s*)(("??/.+?"??)|(profile\s+("??.+?"??)))\s+((flags=)?\((.*)\)\s+)?\{\s*(#.*)?$') @@ -638,7 +638,7 @@ def set_profile_flags(prof_filename, program, newflags): def profile_exists(program): """Returns True if profile exists, False otherwise""" # Check cache of profiles - + if existing_profiles.get(program, False): return True # Check the disk for profile @@ -829,7 +829,7 @@ def console_select_and_upload_profiles(title, message, profiles_up): for p_data in profs: prof = p_data[0] prof_string = p_data[1] - status_ok, ret = upload_profile(url, user, passw, + status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'], prof, prof_string, changelog ) if status_ok: @@ -900,7 +900,7 @@ def handle_children(profile, hat, root): sock_type = None protocol = None regex_nullcomplain = re.compile('^null(-complain)*-profile$') - + for entry in entries: if type(entry[0]) != str: handle_children(profile, hat, entry) @@ -928,42 +928,42 @@ def handle_children(profile, hat, root): if new_p and UI_SelectUpdatedRepoProfile(profile, new_p) and aa[profile].get(uhat, False): hat = uhat continue - + default_hat = None for hatglob in cfg.options('defaulthat'): if re.search(hatglob, profile): default_hat = cfg['defaulthat'][hatglob] - + context = profile context = context + ' -> ^%s' % uhat ans = transitions.get(context, 'XXXINVALIDXXX') - + while ans not in ['CMD_ADDHAT', 'CMD_USEDEFAULT', 'CMD_DENY']: q = hasher() q['headers'] = [] q['headers'] += [_('Profile'), profile] - + if default_hat: q['headers'] += [_('Default Hat'), default_hat] - + q['headers'] += [_('Requested Hat'), uhat] - + q['functions'] = [] q['functions'].append('CMD_ADDHAT') if default_hat: q['functions'].append('CMD_USEDEFAULT') q['functions'] += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] - + q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ADDHAT' - + seen_events += 1 - + ans = UI_PromptUser(q) - + transitions[context] = ans - + if ans == 'CMD_ADDHAT': hat = uhat aa[profile][hat]['flags'] = aa[profile][profile]['flags'] @@ -972,7 +972,7 @@ def handle_children(profile, hat, root): elif ans == 'CMD_DENY': # As unknown hat is denied no entry for it should be made return None - + elif typ == 'capability': # If capability then we (should) have pid, profile, hat, program, mode, capability pid, p, h, prog, aamode, capability = entry[:6] @@ -982,7 +982,7 @@ def handle_children(profile, hat, root): if not profile or not hat: continue prelog[aamode][profile][hat]['capability'][capability] = True - + elif typ == 'path' or typ == 'exec': # If path or exec then we (should) have pid, profile, hat, program, mode, details and to_name pid, p, h, prog, aamode, mode, detail, to_name = entry[:8] @@ -993,7 +993,7 @@ def handle_children(profile, hat, root): hat = h if not profile or not hat or not detail: continue - + domainchange = 'nochange' if typ == 'exec': domainchange = 'change' @@ -1005,48 +1005,48 @@ def handle_children(profile, hat, root): detail = detail.replace('*', '\*') detail = detail.replace('{', '\{') detail = detail.replace('}', '\}') - - # Give Execute dialog if x access requested for something that's not a directory + + # Give Execute dialog if x access requested for something that's not a directory # For directories force an 'ix' Path dialog do_execute = False exec_target = detail - + if mode & str_to_mode('x'): if os.path.isdir(exec_target): mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE mode = mode | str_to_mode('ix') else: do_execute = True - + if mode & AA_MAY_LINK: regex_link = re.compile('^from (.+) to (.+)$') match = regex_link.search(detail) if match: path = match.groups()[0] target = match.groups()[1] - + frommode = str_to_mode('lr') if prelog[aamode][profile][hat]['path'].get(path, False): frommode |= prelog[aamode][profile][hat]['path'][path] prelog[aamode][profile][hat]['path'][path] = frommode - + tomode = str_to_mode('lr') if prelog[aamode][profile][hat]['path'].get(target, False): tomode |= prelog[aamode][profile][hat]['path'][target] - prelog[aamode][profile][hat]['path'][target] = tomode + prelog[aamode][profile][hat]['path'][target] = tomode else: continue elif mode: path = detail - + if prelog[aamode][profile][hat]['path'].get(path, False): mode |= prelog[aamode][profile][hat]['path'][path] prelog[aamode][profile][hat]['path'][path] = mode - + if do_execute: if profile_known_exec(aa[profile][hat], 'exec', exec_target): continue - + p = update_repo_profile(aa[profile][profile]) if to_name: if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', to_name): @@ -1054,12 +1054,12 @@ def handle_children(profile, hat, root): else: if UI_SelectUpdatedRepoProfile(profile, p) and profile_known_exec(aa[profile][hat], 'exec', exec_target): continue - + context_new = profile if profile != hat: context_new = context_new + '^%s' % hat context_new = context + ' ->%s' % exec_target - + ans_new = transitions.get(context_new, '') combinedmode = set() combinedaudit = set() @@ -1070,7 +1070,7 @@ def handle_children(profile, hat, root): combinedmode |= cm if am: combinedaudit |= am - + if combinedmode & str_to_mode('x'): nt_name = None for entr in m: @@ -1098,7 +1098,7 @@ def handle_children(profile, hat, root): pass elif nt_name: to_name = nt_name - + # nx is not used in profiles but in log files. # Log parsing methods will convert it to its profile form # nx is internally cx/px/cix/pix + to_name @@ -1164,11 +1164,11 @@ def handle_children(profile, hat, root): options = cfg['qualifiers'].get(exec_target, 'ipcnu') if to_name: fatal_error(_('%s has transition name but not transition mode') % entry) - + ### If profiled program executes itself only 'ix' option ##if exec_target == profile: ##options = 'i' - + # Don't allow hats to cx? options.replace('c', '') # Add deny to options @@ -1186,34 +1186,34 @@ def handle_children(profile, hat, root): default = 'CMD_nx' else: default = 'DENY' - - # + + # parent_uses_ld_xxx = check_for_LD_XXX(profile) - + sev_db.unload_variables() sev_db.load_variables(profile) severity = sev_db.rank(exec_target, 'x') - + # Prompt portion starts q = hasher() q['headers'] = [] q['headers'] += [_('Profile'), combine_name(profile, hat)] if prog and prog != 'HINT': q['headers'] += [_('Program'), prog] - + # to_name should not exist here since, transitioning is already handeled q['headers'] += [_('Execute'), exec_target] q['headers'] += [_('Severity'), severity] - + q['functions'] = [] prompt = '\n%s\n' % context exec_toggle = False - q['functions'].append(build_x_functions(default, options, exec_toggle)) - + q['functions'].append(build_x_functions(default, options, exec_toggle)) + options = '|'.join(options) seen_events += 1 regex_options = re.compile('^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$') - + while regex_options.search(ans): ans = UI_PromptUser(q).strip() if ans.startswith('CMD_EXEC_IX_'): @@ -1237,9 +1237,9 @@ def handle_children(profile, hat, root): ans = 'CMD_px' else: ans = 'CMD_pix' - + to_name = UI_GetString(_('Enter profile name to transition to: '), arg) - + regex_optmode = re.compile('CMD_(px|cx|nx|pix|cix|nix)') if ans == 'CMD_ix': exec_mode = str_to_mode('ix') @@ -1250,7 +1250,7 @@ def handle_children(profile, hat, root): px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut some applications depend on the presence\nof LD_PRELOAD or LD_LIBRARY_PATH.") if parent_uses_ld_xxx: px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.") - + ynans = UI_YesNo(px_msg, px_default) if ynans == 'y': # Disable the unsafe mode @@ -1266,7 +1266,7 @@ def handle_children(profile, hat, root): else: ans = 'INVALID' transitions[context] = ans - + regex_options = re.compile('CMD_(ix|px|cx|nx|pix|cix|nix)') if regex_options.search(ans): # For inherit we need r @@ -1280,21 +1280,21 @@ def handle_children(profile, hat, root): # Skip remaining events if they ask to deny exec if domainchange == 'change': return None - + if ans != 'CMD_DENY': prelog['PERMITTING'][profile][hat]['path'][exec_target] = prelog['PERMITTING'][profile][hat]['path'].get(exec_target, exec_mode) | exec_mode - + log_dict['PERMITTING'][profile] = hasher() - + aa[profile][hat]['allow']['path'][exec_target]['mode'] = aa[profile][hat]['allow']['path'][exec_target].get('mode', exec_mode) - + aa[profile][hat]['allow']['path'][exec_target]['audit'] = aa[profile][hat]['allow']['path'][exec_target].get('audit', set()) - + if to_name: aa[profile][hat]['allow']['path'][exec_target]['to'] = to_name - + changed[profile] = True - + if exec_mode & str_to_mode('i'): #if 'perl' in exec_target: # aa[profile][hat]['include']['abstractions/perl'] = True @@ -1305,18 +1305,18 @@ def handle_children(profile, hat, root): interpreter = hashbang[2:].strip() interpreter_path = get_full_path(interpreter) interpreter = re.sub('^(/usr)?/bin/', '', interpreter_path) - + aa[profile][hat]['path'][interpreter_path]['mode'] = aa[profile][hat]['path'][interpreter_path].get('mode', str_to_mode('ix')) | str_to_mode('ix') - + aa[profile][hat]['path'][interpreter_path]['audit'] = aa[profile][hat]['path'][interpreter_path].get('audit', set()) - + if interpreter == 'perl': aa[profile][hat]['include']['abstractions/perl'] = True elif interpreter in ['bash', 'dash', 'sh']: aa[profile][hat]['include']['abstractions/bash'] = True - + # Update tracking info based on kind of change - + if ans == 'CMD_ix': if hat: profile_changes[pid] = '%s//%s' %(profile, hat) @@ -1330,7 +1330,7 @@ def handle_children(profile, hat, root): profile = exec_target hat = exec_target profile_changes[pid] = '%s' % profile - + # Check profile exists for px if not os.path.exists(get_profile_filename(exec_target)): ynans = 'y' @@ -1349,7 +1349,7 @@ def handle_children(profile, hat, root): if aamode == 'PERMITTING': if domainchange == 'change': profile_changes[pid] = '%s//%s' % (profile, exec_target) - + if not aa[profile].get(exec_target, False): ynans = 'y' if exec_mode & str_to_mode('i'): @@ -1358,36 +1358,36 @@ def handle_children(profile, hat, root): hat = exec_target aa[profile][hat]['declared'] = False aa[profile][hat]['profile'] = True - + if profile != hat: aa[profile][hat]['flags'] = aa[profile][profile]['flags'] - + stub_profile = create_new_profile(hat) - + aa[profile][hat]['flags'] = 'complain' - + aa[profile][hat]['allow']['path'] = hasher() if stub_profile[hat][hat]['allow'].get('path', False): aa[profile][hat]['allow']['path'] = stub_profile[hat][hat]['allow']['path'] - + aa[profile][hat]['include'] = hasher() if stub_profile[hat][hat].get('include', False): aa[profile][hat]['include'] = stub_profile[hat][hat]['include'] - + aa[profile][hat]['allow']['netdomain'] = hasher() - + file_name = aa[profile][profile]['filename'] filelist[file_name]['profiles'][profile][hat] = True - + elif ans.startswith('CMD_ux'): profile_changes[pid] = 'unconfined' if domainchange == 'change': return None - + elif typ == 'netdomain': # If netdomain we (should) have pid, profile, hat, program, mode, network family, socket type and protocol pid, p, h, prog, aamode, family, sock_type, protocol = entry[:8] - + if not regex_nullcomplain.search(p) and not regex_nullcomplain.search(h): profile = p hat = h @@ -1395,13 +1395,13 @@ def handle_children(profile, hat, root): continue if family and sock_type: prelog[aamode][profile][hat]['netdomain'][family][sock_type] = True - + return None PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x') - + ##### Repo related functions def UI_SelectUpdatedRepoProfile(profile, p): @@ -1468,19 +1468,19 @@ def ask_the_questions(): else: # oops something screwed up fatal_error(_('Invalid mode found: %s') % aamode) - + for profile in sorted(log_dict[aamode].keys()): # Update the repo profiles p = update_repo_profile(aa[profile][profile]) if p: UI_SelectUpdatedRepoProfile(profile, p) - + found += 1 # Sorted list of hats with the profile name coming first hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys()))) if log_dict[aamode][profile].get(profile, False): hats = [profile] + hats - + for hat in hats: for capability in sorted(log_dict[aamode][profile][hat]['capability'].keys()): # skip if capability already in profile @@ -1492,32 +1492,32 @@ def ask_the_questions(): options = [] newincludes = match_cap_includes(aa[profile][hat], capability) q = hasher() - + if newincludes: options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) - + if options: options.append('capability %s' % capability) q['options'] = [options] q['selected'] = default_option - 1 - + q['headers'] = [_('Profile'), combine_name(profile, hat)] q['headers'] += [_('Capability'), capability] q['headers'] += [_('Severity'), severity] - + audit_toggle = 0 - + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] - + # In complain mode: events default to allow # In enforce mode: events default to deny q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' - + seen_events += 1 - + done = False while not done: ans, selected = UI_PromptUser(q) @@ -1525,7 +1525,7 @@ def ask_the_questions(): if ans == 'CMD_IGNORE_ENTRY': done = True break - + if ans == 'CMD_AUDIT': audit_toggle = not audit_toggle audit = '' @@ -1536,43 +1536,43 @@ def ask_the_questions(): else: q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED', ] - + q['headers'] = [_('Profile'), combine_name(profile, hat), _('Capability'), audit + capability, _('Severity'), severity] - + if ans == 'CMD_ALLOW': selection = '' if options: selection = options[selected] - match = re_match_include(selection) + match = re_match_include(selection) if match: deleted = False inc = match #.groups()[0] deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True - + UI_Info(_('Adding %s to profile.') % selection) if deleted: UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + aa[profile][hat]['allow']['capability'][capability]['set'] = True aa[profile][hat]['allow']['capability'][capability]['audit'] = audit_toggle - + changed[profile] = True - + UI_Info(_('Adding capability %s to profile.'), capability) done = True - + elif ans == 'CMD_DENY': aa[profile][hat]['deny']['capability'][capability]['set'] = True changed[profile] = True - + UI_Info(_('Denying capability %s to profile.') % capability) done = True else: done = False - + # Process all the path entries. for path in sorted(log_dict[aamode][profile][hat]['path'].keys()): mode = log_dict[aamode][profile][hat]['path'][path] @@ -1581,37 +1581,37 @@ def ask_the_questions(): allow_audit = set() deny_mode = set() deny_audit = set() - + fmode, famode, fm = rematchfrag(aa[profile][hat], 'allow', path) if fmode: allow_mode |= fmode if famode: allow_audit |= famode - + cm, cam, m = rematchfrag(aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam - + imode, iamode, im = match_prof_incs_to_path(aa[profile][hat], 'allow', path) if imode: allow_mode |= imode if iamode: allow_audit |= iamode - + cm, cam, m = match_prof_incs_to_path(aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam - + if deny_mode & AA_MAY_EXEC: deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE - + # Mask off the denied modes mode = mode - deny_mode - + # If we get an exec request from some kindof event that generates 'PERMITTING X' # check if its already in allow_mode # if not add ix permission @@ -1620,32 +1620,32 @@ def ask_the_questions(): mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE if not allow_mode & AA_MAY_EXEC: mode |= str_to_mode('ix') - + # m is not implied by ix - + ### If we get an mmap request, check if we already have it in allow_mode ##if mode & AA_EXEC_MMAP: ## # ix implies m, so we don't need to add m if ix is present ## if contains(allow_mode, 'ix'): ## mode = mode - AA_EXEC_MMAP - + if not mode: continue - + matches = [] - + if fmode: matches.append(fm) - + if imode: matches.append(im) - + if not mode_contains(allow_mode, mode): default_option = 1 options = [] newincludes = [] include_valid = False - + for incname in include.keys(): include_valid = False # If already present skip @@ -1653,12 +1653,12 @@ def ask_the_questions(): continue if incname.startswith(profile_dir): incname = incname.replace(profile_dir+'/', '', 1) - + include_valid = valid_include('', incname) - + if not include_valid: continue - + cm, am, m = match_include_to_path(incname, 'allow', path) if cm and mode_contains(cm, mode): @@ -1680,19 +1680,19 @@ def ask_the_questions(): for user_glob in user_globs: if matchliteral(user_glob, path): matches.append(user_glob) - + matches = list(set(matches)) if path in matches: matches.remove(path) - + options += order_globs(matches, path) default_option = len(options) - + sev_db.unload_variables() sev_db.load_variables(get_profile_filename(profile)) severity = sev_db.rank(path, mode_to_str(mode)) sev_db.unload_variables() - + audit_toggle = 0 owner_toggle = 0 if cfg['settings']['default_owner_prompt']: @@ -1702,7 +1702,7 @@ def ask_the_questions(): q = hasher() q['headers'] = [_('Profile'), combine_name(profile, hat), _('Path'), path] - + if allow_mode: mode |= allow_mode tail = '' @@ -1719,7 +1719,7 @@ def ask_the_questions(): else: prompt_mode = owner_flatten_mode(mode) tail = ' ' + _('(force all rule perms to owner)') - + if audit_toggle == 1: s = mode_to_str_user(allow_mode) if allow_mode: @@ -1729,10 +1729,10 @@ def ask_the_questions(): s = 'audit ' + mode_to_str_user(prompt_mode) + tail else: s = mode_to_str_user(prompt_mode) + tail - - q['headers'] += [_('Old Mode'), mode_to_str_user(allow_mode), + + q['headers'] += [_('Old Mode'), mode_to_str_user(allow_mode), _('New Mode'), s] - + else: s = '' tail = '' @@ -1747,10 +1747,10 @@ def ask_the_questions(): else: prompt_mode = owner_flatten_mode(mode) tail = ' ' + _('(force perms to owner)') - + s = mode_to_str_user(prompt_mode) q['headers'] += [_('Mode'), s] - + q['headers'] += [_('Severity'), severity] q['options'] = options q['selected'] = default_option - 1 @@ -1760,15 +1760,15 @@ def ask_the_questions(): q['default'] = 'CMD_DENY' if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' - + seen_events += 1 - + ans, selected = UI_PromptUser(q) - + if ans == 'CMD_IGNORE_ENTRY': done = True break - + if ans == 'CMD_OTHER': audit_toggle, owner_toggle = UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode) elif ans == 'CMD_USER_TOGGLE': @@ -1790,7 +1790,7 @@ def ask_the_questions(): UI_Info(_('Adding %s to profile.') % path) if deleted: UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + else: if aa[profile][hat]['allow']['path'][path].get('mode', False): mode |= aa[profile][hat]['allow']['path'][path]['mode'] @@ -1798,14 +1798,14 @@ def ask_the_questions(): for entry in aa[profile][hat]['allow']['path'].keys(): if path == entry: continue - + if matchregexp(path, entry): if mode_contains(mode, aa[profile][hat]['allow']['path'][entry]['mode']): deleted.append(entry) for entry in deleted: aa[profile][hat]['allow']['path'].pop(entry) deleted = len(deleted) - + if owner_toggle == 0: mode = flatten_mode(mode) #elif owner_toggle == 1: @@ -1814,34 +1814,34 @@ def ask_the_questions(): mode = allow_mode | owner_flatten_mode(mode - allow_mode) elif owner_toggle == 3: mode = owner_flatten_mode(mode) - + aa[profile][hat]['allow']['path'][path]['mode'] = aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode - + tmpmode = set() if audit_toggle == 1: tmpmode = mode- allow_mode elif audit_toggle == 2: - tmpmode = mode - + tmpmode = mode + aa[profile][hat]['allow']['path'][path]['audit'] = aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode - + changed[profile] = True - + UI_Info(_('Adding %s %s to profile') % (path, mode_to_str_user(mode))) if deleted: UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + elif ans == 'CMD_DENY': path = options[selected].strip() # Add new entry? aa[profile][hat]['deny']['path'][path]['mode'] = aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode) - + aa[profile][hat]['deny']['path'][path]['audit'] = aa[profile][hat]['deny']['path'][path].get('audit', set()) - + changed[profile] = True - + done = True - + elif ans == 'CMD_NEW': arg = options[selected] if not re_match_include(arg): @@ -1852,36 +1852,36 @@ def ask_the_questions(): key = UI_YesNo(ynprompt, 'n') if key == 'n': continue - + user_globs.append(ans) options.append(ans) default_option = len(options) - + elif ans == 'CMD_GLOB': newpath = options[selected].strip() if not re_match_include(newpath): newpath = glob_path(newpath) - + if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 - + elif ans == 'CMD_GLOBEXT': newpath = options[selected].strip() if not re_match_include(newpath): newpath = glob_path_withext(newpath) - + if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 - + elif re.search('\d', ans): default_option = ans - + # for family in sorted(log_dict[aamode][profile][hat]['netdomain'].keys()): # severity handling for net toggles goes here @@ -1898,28 +1898,28 @@ def ask_the_questions(): options.append('network %s %s' % (family, sock_type)) q['options'] = options q['selected'] = default_option - 1 - + q['headers'] = [_('Profile'), combine_name(profile, hat)] q['headers'] += [_('Network Family'), family] q['headers'] += [_('Socket Type'), sock_type] - + audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_DENY' - + if aamode == 'PERMITTING': q['default'] = 'CMD_ALLOW' - + seen_events += 1 - + done = False while not done: ans, selected = UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True break - + if ans.startswith('CMD_AUDIT'): audit_toggle = not audit_toggle audit = '' @@ -1933,7 +1933,7 @@ def ask_the_questions(): q['headers'] = [_('Profile'), combine_name(profile, hat)] q['headers'] += [_('Network Family'), audit + family] q['headers'] += [_('Socket Type'), sock_type] - + elif ans == 'CMD_ALLOW': selection = options[selected] done = True @@ -1941,29 +1941,29 @@ def ask_the_questions(): inc = re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0] deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) - + aa[profile][hat]['include'][inc] = True - + changed[profile] = True - + UI_Info(_('Adding %s to profile') % selection) if deleted: UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + else: aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True - + changed[profile] = True - + UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) - + elif ans == 'CMD_DENY': done = True aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True changed[profile] = True UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) - + else: done = False @@ -1981,13 +1981,13 @@ def glob_path(newpath): newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath) else: newpath = re.sub('/[^/]+/$', '/*/', newpath) - else: + else: if newpath[-3:] == '/**' or newpath[-2:] == '/*': # /foo/** and /foo/* => /** - newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) + newpath = re.sub('/[^/]+/\*{1,2}$', '/**', newpath) elif re.search('/[^/]*\*\*[^/]+$', newpath): # /**foo and /foor**bar => /** - newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath) + newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath) elif re.search('/[^/]+\*\*$', newpath): # /foo** => /** newpath = re.sub('/[^/]+\*\*$', '/**', newpath) @@ -2014,7 +2014,7 @@ def glob_path_withext(newpath): match = re.search('(\.[^/]+)$', newpath) if match: newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) - return newpath + return newpath def delete_net_duplicates(netrules, incnetrules): deleted = 0 @@ -2047,7 +2047,7 @@ def delete_cap_duplicates(profilecaps, inccaps): deleted.append(capname) for capname in deleted: profilecaps.pop(capname) - + return len(deleted) def delete_path_duplicates(profile, incname, allow): @@ -2058,7 +2058,7 @@ def delete_path_duplicates(profile, incname, allow): cm, am, m = match_include_to_path(incname, allow, entry) if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']): deleted.append(entry) - + for entry in deleted: profile[allow]['path'].pop(entry) return len(deleted) @@ -2070,28 +2070,28 @@ def delete_duplicates(profile, incname): if include.get(incname, False): deleted += delete_net_duplicates(profile['allow']['netdomain'], include[incname][incname]['allow']['netdomain']) - + deleted += delete_net_duplicates(profile['deny']['netdomain'], include[incname][incname]['deny']['netdomain']) - + deleted += delete_cap_duplicates(profile['allow']['capability'], include[incname][incname]['allow']['capability']) - + deleted += delete_cap_duplicates(profile['deny']['capability'], include[incname][incname]['deny']['capability']) - + deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') - + elif filelist.get(incname, False): deleted += delete_net_duplicates(profile['allow']['netdomain'], filelist[incname][incname]['allow']['netdomain']) - + deleted += delete_net_duplicates(profile['deny']['netdomain'], filelist[incname][incname]['deny']['netdomain']) - + deleted += delete_cap_duplicates(profile['allow']['capability'], filelist[incname][incname]['allow']['capability']) - + deleted += delete_cap_duplicates(profile['deny']['capability'], filelist[incname][incname]['deny']['capability']) - + deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') - + return deleted def match_net_include(incname, family, type): @@ -2104,23 +2104,23 @@ def match_net_include(incname, family, type): checked.append(name) if netrules_access_check(include[name][name]['allow']['netdomain'], family, type): return True - + if include[name][name]['include'].keys() and name not in checked: includelist += include[name][name]['include'].keys() - + if len(includelist): name = includelist.pop(0) else: name = False - + return False def match_cap_includes(profile, cap): newincludes = [] for incname in include.keys(): if valid_include(profile, incname) and include[incname][incname]['allow']['capability'][cap].get('set', False) == 1: - newincludes.append(incname) - + newincludes.append(incname) + return newincludes def re_match_include(path): @@ -2143,16 +2143,16 @@ def valid_include(profile, incname): if incname.startswith('abstractions/') and os.path.isfile(profile_dir + '/' + incname): return True - + return False - + def match_net_includes(profile, family, nettype): newincludes = [] for incname in include.keys(): - + if valid_include(profile, incname) and match_net_include(incname, family, type): newincludes.append(incname) - + return newincludes def do_logprof_pass(logmark='', passno=0, pid=pid): @@ -2172,13 +2172,13 @@ def do_logprof_pass(logmark='', passno=0, pid=pid): # changed = dict() skip = hasher() # filelist = hasher() - + UI_Info(_('Reading log entries from %s.') %filename) - + if not passno: UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) read_profiles() - + if not sev_db: sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown')) #print(pid) @@ -2190,51 +2190,51 @@ def do_logprof_pass(logmark='', passno=0, pid=pid): log_reader = apparmor.logparser.ReadLog(pid, filename, existing_profiles, profile_dir, log) log = log_reader.read_log(logmark) #read_log(logmark) - + for root in log: handle_children('', '', root) #for root in range(len(log)): #log[root] = handle_children('', '', log[root]) - #print(log) + #print(log) for pid in sorted(profile_changes.keys()): set_process(pid, profile_changes[pid]) - + collapse_log() - + ask_the_questions() - + if UI_mode == 'yast': # To-Do pass - + finishing = False # Check for finished save_profiles() - + ##if not repo_cfg['repository'].get('upload', False) or repo['repository']['upload'] == 'later': ## UI_ask_to_upload_profiles() ##if repo_enabled(): ## if repo_cgf['repository']['upload'] == 'yes': ## sync_profiles() ## created = [] - + # If user selects 'Finish' then we want to exit logprof if finishing: return 'FINISHED' else: return 'NORMAL' - + def save_profiles(): # Ensure the changed profiles are actual active profiles for prof_name in changed.keys(): if not is_active_profile(prof_name): changed.pop(prof_name) - + changed_list = sorted(changed.keys()) - + if changed_list: - + if UI_mode == 'yast': # To-Do selected_profiles = [] @@ -2261,7 +2261,7 @@ def save_profiles(): for profile_name in selected_profiles_ref: write_profile_ui_feedback(profile_name) reload_base(profile_name) - + else: q = hasher() q['title'] = 'Changed Local Profiles' @@ -2284,7 +2284,7 @@ def save_profiles(): reload_base(profile_name) #changed.pop(profile_name) #q['options'] = changed - + elif ans == 'CMD_VIEW_CHANGES': which = list(changed.keys())[arg] oldprofile = None @@ -2293,16 +2293,16 @@ def save_profiles(): else: oldprofile = get_profile_filename(which) newprofile = serialize_profile_from_old_profile(aa[which], which, '') - + display_changes_with_comments(oldprofile, newprofile) - + elif ans == 'CMD_VIEW_CHANGES_CLEAN': which = list(changed.keys())[arg] oldprofile = serialize_profile(original_aa[which], which, '') newprofile = serialize_profile(aa[which], which, '') - + display_changes(oldprofile, newprofile) - + for profile_name in changed_list: write_profile_ui_feedback(profile_name) reload_base(profile_name) @@ -2312,30 +2312,30 @@ def get_pager(): def generate_diff(oldprofile, newprofile): oldtemp = tempfile.NamedTemporaryFile('w') - + oldtemp.write(oldprofile) oldtemp.flush() - + newtemp = tempfile.NamedTemporaryFile('w') newtemp.write(newprofile) newtemp.flush() - + difftemp = tempfile.NamedTemporaryFile('w', delete=False) - + subprocess.call('diff -u -p %s %s > %s' %(oldtemp.name, newtemp.name, difftemp.name), shell=True) - + oldtemp.close() newtemp.close() return difftemp def get_profile_diff(oldprofile, newprofile): - difftemp = generate_diff(oldprofile, newprofile) - diff = [] + difftemp = generate_diff(oldprofile, newprofile) + diff = [] with open_file_read(difftemp.name) as f_in: for line in f_in: if not (line.startswith('---') and line .startswith('+++') and line.startswith('@@')): diff.append(line) - + difftemp.delete = True difftemp.close() return ''.join(diff) @@ -2360,11 +2360,11 @@ def display_changes_with_comments(oldprofile, newprofile): newtemp = tempfile.NamedTemporaryFile('w') newtemp.write(newprofile) newtemp.flush() - + difftemp = tempfile.NamedTemporaryFile('w') - + subprocess.call('diff -u -p %s %s > %s' %(oldprofile, newtemp.name, difftemp.name), shell=True) - + newtemp.close() subprocess.call('less %s' %difftemp.name, shell=True) difftemp.close() @@ -2373,7 +2373,7 @@ def set_process(pid, profile): # If process not running don't do anything if not os.path.exists('/proc/%s/attr/current' % pid): return None - + process = None try: process = open_file_read('/proc/%s/attr/current' % pid) @@ -2381,10 +2381,10 @@ def set_process(pid, profile): return None current = process.readline().strip() process.close() - + if not re.search('^null(-complain)*-profile$', current): return None - + stats = None try: stats = open_file_read('/proc/%s/stat' % pid) @@ -2392,11 +2392,11 @@ def set_process(pid, profile): return None stat = stats.readline().strip() stats.close() - + match = re.search('^\d+ \((\S+)\) ', stat) if not match: return None - + try: process = open_file_write('/proc/%s/attr/current' % pid) except IOError: @@ -2408,33 +2408,33 @@ def collapse_log(): for aamode in prelog.keys(): for profile in prelog[aamode].keys(): for hat in prelog[aamode][profile].keys(): - + for path in prelog[aamode][profile][hat]['path'].keys(): mode = prelog[aamode][profile][hat]['path'][path] - + combinedmode = set() # Is path in original profile? if aa[profile][hat]['allow']['path'].get(path, False): combinedmode |= aa[profile][hat]['allow']['path'][path]['mode'] - + # Match path to regexps in profile combinedmode |= rematchfrag(aa[profile][hat], 'allow', path)[0] - + # Match path from includes combinedmode |= match_prof_incs_to_path(aa[profile][hat], 'allow', path)[0] - + if not combinedmode or not mode_contains(combinedmode, mode): if log_dict[aamode][profile][hat]['path'].get(path, False): mode |= log_dict[aamode][profile][hat]['path'][path] - + log_dict[aamode][profile][hat]['path'][path] = mode - + for capability in prelog[aamode][profile][hat]['capability'].keys(): # If capability not already in profile if not aa[profile][hat]['allow']['capability'][capability].get('set', False): log_dict[aamode][profile][hat]['capability'][capability] = True - + nd = prelog[aamode][profile][hat]['netdomain'] for family in nd.keys(): for sock_type in nd[family].keys(): @@ -2449,14 +2449,14 @@ def validate_profile_mode(mode, allow, nt_name=None): return True else: return False - + elif nt_name: pattern = '^(%s)+$' % PROFILE_MODE_NT_RE.pattern if re.search(pattern, mode): return True else: return False - + else: pattern = '^(%s)+$' % PROFILE_MODE_RE.pattern if re.search(pattern, mode): @@ -2506,7 +2506,7 @@ def read_inactive_profiles(): os.listdir(profile_dir) except : fatal_error(_("Can't read AppArmor profiles in %s") % extra_profile_dir) - + for file in os.listdir(profile_dir): if os.path.isfile(extra_profile_dir + '/' + file): if is_skippable_file(file): @@ -2522,18 +2522,18 @@ def read_profile(file, active_profile): except IOError: debug_logger.debug("read_profile: can't read %s - skipping" %file) return None - + profile_data = parse_profile_data(data, file, 0) - + if profile_data and active_profile: attach_profile_data(aa, profile_data) attach_profile_data(original_aa, profile_data) elif profile_data: attach_profile_data(extras, profile_data) - + def attach_profile_data(profiles, profile_data): - # Make deep copy of data to avoid changes to + # Make deep copy of data to avoid changes to # arising due to mutables for p in profile_data.keys(): profiles[p] = deepcopy(profile_data[p]) @@ -2577,7 +2577,7 @@ def parse_profile_data(data, file, do_include): # Starting line of a profile if RE_PROFILE_START.search(line): matches = RE_PROFILE_START.search(line).groups() - + if profile: #print(profile, hat) if profile != hat or not matches[3]: @@ -2605,9 +2605,9 @@ def parse_profile_data(data, file, do_include): hat = profile # Profile stored existing_profiles[profile] = file - + flags = matches[6] - + profile = strip_quotes(profile) if hat: hat = strip_quotes(hat) @@ -2617,132 +2617,132 @@ def parse_profile_data(data, file, do_include): filelist[file]['profiles'][profile][hat] = True profile_data[profile][hat]['flags'] = flags - + profile_data[profile][hat]['allow']['netdomain'] = hasher() profile_data[profile][hat]['allow']['path'] = hasher() # Save the initial comment if initial_comment: profile_data[profile][hat]['initial_comment'] = initial_comment - + initial_comment = '' - + if repo_data: profile_data[profile][profile]['repo']['url'] = repo_data['url'] profile_data[profile][profile]['repo']['user'] = repo_data['user'] - + elif RE_PROFILE_END.search(line): # If profile ends and we're not in one if not profile: raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %s line: %s') % (file, lineno+1)) - + if in_contained_hat: hat = profile in_contained_hat = False else: parsed_profiles.append(profile) profile = None - + initial_comment = '' - + elif RE_PROFILE_CAP.search(line): matches = RE_PROFILE_CAP.search(line).groups() - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %s line: %s') % (file, lineno+1)) - + audit = False if matches[0]: audit = True - + allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' - + capability = matches[2] - + profile_data[profile][hat][allow]['capability'][capability]['set'] = True profile_data[profile][hat][allow]['capability'][capability]['audit'] = audit - + elif RE_PROFILE_LINK.search(line): matches = RE_PROFILE_LINK.search(line).groups() - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected link entry found in file: %s line: %s') % (file, lineno+1)) - + audit = False if matches[0]: audit = True - + allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' - + subset = matches[3] link = strip_quotes(matches[6]) value = strip_quotes(matches[7]) profile_data[profile][hat][allow]['link'][link]['to'] = value profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', set()) | AA_MAY_LINK - + if subset: profile_data[profile][hat][allow]['link'][link]['mode'] |= AA_LINK_SUBSET - + if audit: profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', set()) | AA_LINK_SUBSET else: profile_data[profile][hat][allow]['link'][link]['audit'] = set() - + elif RE_PROFILE_CHANGE_PROFILE.search(line): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %s line: %s') % (file, lineno+1)) - + cp = strip_quotes(matches[0]) profile_data[profile][hat]['changes_profile'][cp] = True - + elif RE_PROFILE_ALIAS.search(line): matches = RE_PROFILE_ALIAS.search(line).groups() - + from_name = strip_quotes(matches[0]) to_name = strip_quotes(matches[1]) - + if profile: profile_data[profile][hat]['alias'][from_name] = to_name else: if not filelist.get(file, False): filelist[file] = hasher() filelist[file]['alias'][from_name] = to_name - + elif RE_PROFILE_RLIMIT.search(line): matches = RE_PROFILE_RLIMIT.search(line).groups() - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %s line: %s') % (file, lineno+1)) - + from_name = matches[0] to_name = matches[2] - + profile_data[profile][hat]['rlimit'][from_name] = to_name - + elif RE_PROFILE_BOOLEAN.search(line): matches = RE_PROFILE_BOOLEAN.search(line) - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %s line: %s') % (file, lineno+1)) - + bool_var = matches[0] value = matches[1] - + profile_data[profile][hat]['lvar'][bool_var] = value - + elif RE_PROFILE_VARIABLE.search(line): - # variable additions += and = + # variable additions += and = matches = RE_PROFILE_VARIABLE.search(line).groups() - + list_var = strip_quotes(matches[0]) var_operation = matches[1] value = strip_quotes(matches[2]) - + if profile: if not profile_data[profile][hat].get('lvar', False): profile_data[profile][hat]['lvar'][list_var] = [] @@ -2751,74 +2751,74 @@ def parse_profile_data(data, file, do_include): if not filelist[file].get('lvar', False): filelist[file]['lvar'][list_var] = [] store_list_var(filelist[file]['lvar'], list_var, value, var_operation) - + elif RE_PROFILE_CONDITIONAL.search(line): # Conditional Boolean pass - + elif RE_PROFILE_CONDITIONAL_VARIABLE.search(line): # Conditional Variable defines pass - + elif RE_PROFILE_CONDITIONAL_BOOLEAN.search(line): # Conditional Boolean defined pass - + elif RE_PROFILE_PATH_ENTRY.search(line): matches = RE_PROFILE_PATH_ENTRY.search(line).groups() - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %s line: %s') % (file, lineno+1)) - + audit = False if matches[0]: audit = True - + allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' - + user = False if matches[2]: user = True - + path = matches[3].strip() mode = matches[4] nt_name = matches[6] if nt_name: nt_name = nt_name.strip() - + p_re = convert_regexp(path) try: re.compile(p_re) except: raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno+1)) - + if not validate_profile_mode(mode, allow, nt_name): raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno+1)) - + tmpmode = set() if user: tmpmode = str_to_mode('%s::' % mode) else: tmpmode = str_to_mode(mode) - + profile_data[profile][hat][allow]['path'][path]['mode'] = profile_data[profile][hat][allow]['path'][path].get('mode', set()) | tmpmode - + if nt_name: profile_data[profile][hat][allow]['path'][path]['to'] = nt_name - + if audit: profile_data[profile][hat][allow]['path'][path]['audit'] = profile_data[profile][hat][allow]['path'][path].get('audit', set()) | tmpmode else: profile_data[profile][hat][allow]['path'][path]['audit'] = set() - + elif re_match_include(line): # Include files include_name = re_match_include(line) if include_name.startswith('local/'): profile_data[profile][hat]['localinclude'][include_name] = True - + if profile: profile_data[profile][hat]['include'][include_name] = True @@ -2840,13 +2840,13 @@ def parse_profile_data(data, file, do_include): else: if not include.get(include_name, False): load_include(include_name) - + elif RE_PROFILE_NETWORK.search(line): matches = RE_PROFILE_NETWORK.search(line).groups() - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %s line: %s') % (file, lineno+1)) - + audit = False if matches[0]: audit = True @@ -2870,42 +2870,42 @@ def parse_profile_data(data, file, do_include): else: profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True - + elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() - + if not profile: raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %s line: %s') % (file, lineno+1)) - + hat = matches[0] hat = strip_quotes(hat) - + if not profile_data[profile][hat].get('declared', False): profile_data[profile][hat]['declared'] = True - + elif RE_PROFILE_HAT_DEF.search(line): # An embedded hat syntax definition starts matches = RE_PROFILE_HAT_DEF.search(line).groups() if not profile: raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %s line: %s') % (file, lineno+1)) - + in_contained_hat = True hat = matches[0] hat = strip_quotes(hat) flags = matches[3] - + profile_data[profile][hat]['flags'] = flags profile_data[profile][hat]['declared'] = False #profile_data[profile][hat]['allow']['path'] = hasher() #profile_data[profile][hat]['allow']['netdomain'] = hasher() - + if initial_comment: profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' if filelist[file]['profiles'][profile].get(hat, False): raise AppArmorException(_('Error: Multiple definitions for hat %s in profile %s.') %(hat, profile)) filelist[file]['profiles'][profile][hat] = True - + elif line[0] == '#': # Handle initial comments if not profile: @@ -2921,10 +2921,10 @@ def parse_profile_data(data, file, do_include): 'id': line[4]} else: initial_comment = ' '.join(line) + '\n' - + else: raise AppArmorException(_('Syntax Error: Unknown line found in file: %s line: %s') % (file, lineno+1)) - + # Below is not required I'd say if not do_include: for hatglob in cfg['required_hats'].keys(): @@ -2933,11 +2933,11 @@ def parse_profile_data(data, file, do_include): for hat in cfg['required_hats'][hatglob].split(): if not profile_data[parsed_prof].get(hat, False): profile_data[parsed_prof][hat] = hasher() - - # End of file reached but we're stuck in a profile + + # End of file reached but we're stuck in a profile if profile and not do_include: raise AppArmorException(_("Syntax Error: Missing '}' . Reached end of file %s while inside profile %s") % (file, profile)) - + return profile_data def separate_vars(vs): @@ -2962,7 +2962,7 @@ def is_active_profile(pname): def store_list_var(var, list_var, value, var_operation): """Store(add new variable or add values to variable) the variables encountered in the given list_var""" vlist = separate_vars(value) - if var_operation == '=': + if var_operation == '=': if not var.get(list_var, False): var[list_var] = set(vlist) else: @@ -2982,7 +2982,7 @@ def strip_quotes(data): return data[1:-1] else: return data - + def quote_if_needed(data): # quote data if it contains whitespace if ' ' in data: @@ -3000,29 +3000,29 @@ def write_header(prof_data, depth, name, embedded_hat, write_flags): pre = ' ' * depth data = [] name = quote_if_needed(name) - + if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]' ,name)): name = 'profile %s' % name - + if write_flags and prof_data['flags']: data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags'])) else: data.append('%s%s {' % (pre, name)) - + return data def write_single(prof_data, depth, allow, name, prefix, tail): pre = ' ' * depth data = [] ref, allow = set_ref_allow(prof_data, allow) - + if ref.get(name, False): for key in sorted(ref[name].keys()): qkey = quote_if_needed(key) data.append('%s%s%s%s%s' %(pre, allow, prefix, qkey, tail)) if ref[name].keys(): data.append('') - + return data def set_allow_str(allow): @@ -3042,14 +3042,14 @@ def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn): pre = ' ' * depth data = [] ref, allow = set_ref_allow(prof_data, allow) - + if ref.get(name, False): for key in sorted(ref[name].keys()): value = fn(ref[name][key])#eval('%s(%s)' % (fn, ref[name][key])) data.append('%s%s%s%s%s%s' %(pre, allow, prefix, key, sep, value)) if ref[name].keys(): data.append('') - + return data def write_includes(prof_data, depth): @@ -3077,7 +3077,7 @@ def write_cap_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) - + if prof_data[allow].get('capability', False): for cap in sorted(prof_data[allow]['capability'].keys()): audit = '' @@ -3086,7 +3086,7 @@ def write_cap_rules(prof_data, depth, allow): if prof_data[allow]['capability'][cap].get('set', False): data.append('%s%s%scapability %s,' %(pre, audit, allowstr, cap)) data.append('') - + return data def write_capabilities(prof_data, depth): @@ -3118,7 +3118,7 @@ def write_net_rules(prof_data, depth, allow): data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr,fam, typ)) if prof_data[allow].get('netdomain', False): data.append('') - + return data def write_netdomain(prof_data, depth): @@ -3130,7 +3130,7 @@ def write_link_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) - + if prof_data[allow].get('link', False): for path in sorted(prof_data[allow]['link'].keys()): to_name = prof_data[allow]['link'][path]['to'] @@ -3144,20 +3144,20 @@ def write_link_rules(prof_data, depth, allow): to_name = quote_if_needed(to_name) data.append('%s%s%slink %s%s -> %s,' %(pre, audit, allowstr, subset, path, to_name)) data.append('') - + return data def write_links(prof_data, depth): data = write_link_rules(prof_data, depth, 'deny') data += write_link_rules(prof_data, depth, 'allow') - + return data def write_path_rules(prof_data, depth, allow): pre = ' ' * depth data = [] allowstr = set_allow_str(allow) - + if prof_data[allow].get('path', False): for path in sorted(prof_data[allow]['path'].keys()): mode = prof_data[allow]['path'][path]['mode'] @@ -3167,13 +3167,13 @@ def write_path_rules(prof_data, depth, allow): tail = ' -> %s' % prof_data[allow]['path'][path]['to'] user, other = split_mode(mode) user_audit, other_audit = split_mode(audit) - + while user or other: ownerstr = '' tmpmode = 0 tmpaudit = False if user - other: - # if no other mode set + # if no other mode set ownerstr = 'owner ' tmpmode = user - other tmpaudit = user_audit @@ -3190,25 +3190,25 @@ def write_path_rules(prof_data, depth, allow): tmpaudit = user_audit | other_audit user = user - tmpmode other = other - tmpmode - + if tmpmode & tmpaudit: modestr = mode_to_str(tmpmode & tmpaudit) path = quote_if_needed(path) data.append('%saudit %s%s%s %s%s,' %(pre, allowstr, ownerstr, path, modestr, tail)) tmpmode = tmpmode - tmpaudit - + if tmpmode: modestr = mode_to_str(tmpmode) path = quote_if_needed(path) data.append('%s%s%s%s %s%s,' %(pre, allowstr, ownerstr, path, modestr, tail)) - + data.append('') return data def write_paths(prof_data, depth): data = write_path_rules(prof_data, depth, 'deny') data += write_path_rules(prof_data, depth, 'allow') - + return data def write_rules(prof_data, depth): @@ -3221,7 +3221,7 @@ def write_rules(prof_data, depth): data += write_links(prof_data, depth) data += write_paths(prof_data, depth) data += write_change_profile(prof_data, depth) - + return data def write_piece(profile_data, depth, name, nhat, write_flags): @@ -3237,13 +3237,13 @@ def write_piece(profile_data, depth, name, nhat, write_flags): inhat = True data += write_header(profile_data[name], depth, wname, False, write_flags) data += write_rules(profile_data[name], depth+1) - + pre2 = ' ' * (depth+1) # External hat declarations for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if profile_data[hat].get('declared', False): data.append('%s^%s,' %(pre2, hat)) - + if not inhat: # Embedded hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): @@ -3253,20 +3253,20 @@ def write_piece(profile_data, depth, name, nhat, write_flags): data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags))) else: data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags))) - + data += list(map(str, write_rules(profile_data[hat], depth+2))) - + data.append('%s}' %pre2) - + data.append('%s}' %pre) - + # External hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if name == nhat and profile_data[hat].get('external', False): data.append('') data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags))) data.append(' }') - + return data def serialize_profile(profile_data, name, options): @@ -3274,28 +3274,28 @@ def serialize_profile(profile_data, name, options): include_metadata = False include_flags = True data= [] - + if options:# and type(options) == dict: if options.get('METADATA', False): include_metadata = True if options.get('NO_FLAGS', False): include_flags = False - + if include_metadata: string = '# Last Modified: %s\n' %time.time() - + if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): repo = profile_data[name]['repo'] string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id']) elif profile_data[name]['repo']['neversubmit']: string += '# REPOSITORY: NEVERSUBMIT\n' - + # if profile_data[name].get('initial_comment', False): # comment = profile_data[name]['initial_comment'] # comment.replace('\\n', '\n') # string += comment + '\n' - + prof_filename = get_profile_filename(name) if filelist.get(prof_filename, False): data += write_alias(filelist[prof_filename], 0) @@ -3316,9 +3316,9 @@ def serialize_profile(profile_data, name, options): comment.replace('\\n', '\n') data += [comment + '\n'] data += write_piece(profile_data, 0, name, name, include_flags) - + string += '\n'.join(data) - + return string+'\n' def serialize_profile_from_old_profile(profile_data, name, options): @@ -3327,19 +3327,19 @@ def serialize_profile_from_old_profile(profile_data, name, options): include_metadata = False include_flags = True prof_filename = get_profile_filename(name) - + write_filelist = deepcopy(filelist[prof_filename]) write_prof_data = deepcopy(profile_data) - + if options:# and type(options) == dict: if options.get('METADATA', False): include_metadata = True if options.get('NO_FLAGS', False): include_flags = False - + if include_metadata: string = '# Last Modified: %s\n' %time.time() - + if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): repo = profile_data[name]['repo'] @@ -3347,7 +3347,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): elif profile_data[name]['repo']['neversubmit']: string += '# REPOSITORY: NEVERSUBMIT\n' - + if not os.path.isfile(prof_filename): raise AppArmorException(_("Can't find existing profile to modify")) with open_file_read(prof_filename) as f_in: @@ -3402,18 +3402,18 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False else: hat = profile - + flags = matches[6] profile = strip_quotes(profile) if hat: hat = strip_quotes(hat) - + if not write_prof_data[hat]['name'] == profile: correct = False if not write_filelist['profiles'][profile][hat] == True: correct = False - + if not write_prof_data[hat]['flags'] == flags: correct = False @@ -3428,19 +3428,19 @@ def serialize_profile_from_old_profile(profile_data, name, options): if write_prof_data[hat]['name'] == profile: depth = len(line) - len(line.lstrip()) data += write_header(write_prof_data[name], int(depth/2), name, False, include_flags) - + elif RE_PROFILE_END.search(line): # DUMP REMAINDER OF PROFILE if profile: depth = len(line) - len(line.lstrip()) if True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): - + data += write_methods[segs](write_prof_data[name], int(depth/2)) segments[segs] = False if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) - + data += write_alias(write_prof_data[name], depth) data += write_list_vars(write_prof_data[name], depth) @@ -3451,12 +3451,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): data += write_links(write_prof_data[name], depth) data += write_paths(write_prof_data[name], depth) data += write_change_profile(write_prof_data[name], depth) - + write_prof_data.pop(name) - + #Append local includes data.append(line) - + if not in_contained_hat: # Embedded hats depth = int((len(line) - len(line.lstrip()))/2) @@ -3468,18 +3468,18 @@ def serialize_profile_from_old_profile(profile_data, name, options): data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, include_flags))) else: data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, include_flags))) - + data += list(map(str, write_rules(profile_data[hat], depth+2))) - + data.append('%s}' %pre2) - + # External hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if profile_data[hat].get('external', False): data.append('') data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, name, include_flags))) data.append(' }') - + if in_contained_hat: #Hat processed, remove it hat = profile @@ -3487,24 +3487,24 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: profile = None - + elif RE_PROFILE_CAP.search(line): matches = RE_PROFILE_CAP.search(line).groups() audit = False if matches[0]: audit = matches[0] - + allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' - + capability = matches[2] - + if not write_prof_data[hat][allow]['capability'][capability].get('set', False): correct = False if not write_prof_data[hat][allow]['capability'][capability].get(audit, False) == audit: correct = False - + if correct: if not segments['capability'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3516,9 +3516,9 @@ def serialize_profile_from_old_profile(profile_data, name, options): segments['capability'] = True write_prof_data[hat][allow]['capability'].pop(capability) data.append(line) - + #write_prof_data[hat][allow]['capability'][capability].pop(audit) - + #Remove this line else: # To-Do @@ -3531,7 +3531,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): allow = 'allow' if matches[1] and matches[1].strip() == 'deny': allow = 'deny' - + subset = matches[3] link = strip_quotes(matches[6]) value = strip_quotes(matches[7]) @@ -3543,7 +3543,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): correct = False if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & AA_LINK_SUBSET: correct = False - + if correct: if not segments['link'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3558,14 +3558,14 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: # To-Do pass - + elif RE_PROFILE_CHANGE_PROFILE.search(line): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() cp = strip_quotes(matches[0]) - + if not write_prof_data[hat]['changes_profile'][cp] == True: correct = False - + if correct: if not segments['change_profile'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3580,20 +3580,20 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: #To-Do pass - + elif RE_PROFILE_ALIAS.search(line): matches = RE_PROFILE_ALIAS.search(line).groups() - + from_name = strip_quotes(matches[0]) to_name = strip_quotes(matches[1]) - + if profile: if not write_prof_data[hat]['alias'][from_name] == to_name: correct = False else: if not write_filelist['alias'][from_name] == to_name: correct = False - + if correct: if not segments['alias'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3611,16 +3611,16 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: #To-Do pass - + elif RE_PROFILE_RLIMIT.search(line): matches = RE_PROFILE_RLIMIT.search(line).groups() - + from_name = matches[0] to_name = matches[2] - + if not write_prof_data[hat]['rlimit'][from_name] == to_name: correct = False - + if correct: if not segments['rlimit'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3635,15 +3635,15 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: #To-Do pass - + elif RE_PROFILE_BOOLEAN.search(line): matches = RE_PROFILE_BOOLEAN.search(line).groups() bool_var = matches[0] value = matches[1] - + if not write_prof_data[hat]['lvar'][bool_var] == value: correct = False - + if correct: if not segments['lvar'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3672,7 +3672,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): store_list_var(var_set, list_var, value, var_operation) if not var_set[list_var] == write_filelist['lvar'].get(list_var, False): correct = False - + if correct: if not segments['lvar'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3690,7 +3690,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: #To-Do pass - + elif RE_PROFILE_PATH_ENTRY.search(line): matches = RE_PROFILE_PATH_ENTRY.search(line).groups() audit = False @@ -3699,32 +3699,32 @@ def serialize_profile_from_old_profile(profile_data, name, options): allow = 'allow' if matches[1] and matches[1].split() == 'deny': allow = 'deny' - + user = False if matches[2]: user = True - + path = matches[3].strip() mode = matches[4] nt_name = matches[6] if nt_name: nt_name = nt_name.strip() - + tmpmode = set() if user: tmpmode = str_to_mode('%s::' %mode) else: tmpmode = str_to_mode(mode) - + if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode: correct = False - + if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name: correct = False - + if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode: correct = False - + if correct: if not segments['path'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3739,7 +3739,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: #To-Do pass - + elif re_match_include(line): include_name = re_match_include(line) if profile: @@ -3777,7 +3777,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): data.append(line) else: correct = False - + elif RE_NETWORK_FAMILY.search(network): fam = RE_NETWORK_FAMILY.search(network).groups()[0] if write_prof_data[hat][allow]['netdomain']['rule'][fam] and write_prof_data[hat][allow]['netdomain']['audit'][fam] == audit: @@ -3793,7 +3793,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): data.append(line) else: correct = False - + if correct: if not segments['netdomain'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): @@ -3816,7 +3816,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): #To-Do pass elif RE_PROFILE_HAT_DEF.search(line): - matches = RE_PROFILE_HAT_DEF.search(line).groups() + matches = RE_PROFILE_HAT_DEF.search(line).groups() in_contained_hat = True hat = matches[0] hat = strip_quotes(hat) @@ -3845,22 +3845,22 @@ def serialize_profile_from_old_profile(profile_data, name, options): # data += write_includes(write_filelist, 0) # data.append('from filelist over') # data += write_piece(write_prof_data, 0, name, name, include_flags) - + string += '\n'.join(data) - + return string+'\n' def write_profile_ui_feedback(profile): UI_Info(_('Writing updated profile for %s.') %profile) write_profile(profile) - + def write_profile(profile): prof_filename = None if aa[profile][profile].get('filename', False): prof_filename = aa[profile][profile]['filename'] else: prof_filename = get_profile_filename(profile) - + newprof = tempfile.NamedTemporaryFile('w', suffix='~' ,delete=False, dir=profile_dir) if os.path.exists(prof_filename): shutil.copymode(prof_filename, newprof.name) @@ -3868,19 +3868,19 @@ def write_profile(profile): #permission_600 = stat.S_IRUSR | stat.S_IWUSR # Owner read and write #os.chmod(newprof.name, permission_600) pass - + serialize_options = {} serialize_options['METADATA'] = True - + profile_string = serialize_profile(aa[profile], profile, serialize_options) newprof.write(profile_string) newprof.close() - + os.rename(newprof.name, prof_filename) - + changed.pop(profile) original_aa[profile] = deepcopy(aa[profile]) - + def matchliteral(aa_regexp, literal): p_regexp = '^'+convert_regexp(aa_regexp)+'$' match = False @@ -3895,38 +3895,38 @@ def profile_known_exec(profile, typ, exec_target): cm = None am = None m = [] - + cm, am, m = rematchfrag(profile, 'deny', exec_target) if cm & AA_MAY_EXEC: return -1 - + cm, am, m = match_prof_incs_to_path(profile, 'deny', exec_target) if cm & AA_MAY_EXEC: return -1 - + cm, am, m = rematchfrag(profile, 'allow', exec_target) if cm & AA_MAY_EXEC: return 1 - + cm, am, m = match_prof_incs_to_path(profile, 'allow', exec_target) if cm & AA_MAY_EXEC: return 1 - + return 0 def profile_known_capability(profile, capname): if profile['deny']['capability'][capname].get('set', False): return -1 - + if profile['allow']['capability'][capname].get('set', False): return 1 - + for incname in profile['include'].keys(): if include[incname][incname]['deny']['capability'][capname].get('set', False): return -1 if include[incname][incname]['allow']['capability'][capname].get('set', False): return 1 - + return 0 def profile_known_network(profile, family, sock_type): @@ -3934,13 +3934,13 @@ def profile_known_network(profile, family, sock_type): return -1 if netrules_access_check(profile['allow']['netdomain'], family, sock_type): return 1 - + for incname in profile['include'].keys(): if netrules_access_check(include[incname][incname]['deny']['netdomain'], family, sock_type): return -1 if netrules_access_check(include[incname][incname]['allow']['netdomain'], family, sock_type): return 1 - + return 0 def netrules_access_check(netrules, family, sock_type): @@ -3957,25 +3957,25 @@ def netrules_access_check(netrules, family, sock_type): type(netrules['rule'][family]) == dict and netrules['rule'][family][sock_type]): net_family_sock = True - + if all_net or all_net_family or net_family_sock: return True else: return False - + def reload_base(bin_path): if not check_for_apparmor(): return None - + prof_filename = get_profile_filename(bin_path) - + subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" %(prof_filename, parser ,profile_dir), shell=True) - + def reload(bin_path): bin_path = find_executable(bin_path) if not bin_path: return None - + return reload_base(bin_path) def get_include_data(filename): @@ -3999,7 +3999,7 @@ def load_include(incname): incdata = parse_profile_data(data, incfile, True) #print(incdata) if not incdata: - # If include is empty, simply push in a placeholder for it + # If include is empty, simply push in a placeholder for it # because other profiles may mention them incdata = hasher() incdata[incname] = hasher() @@ -4007,7 +4007,7 @@ def load_include(incname): #If the include is a directory means include all subfiles elif os.path.isdir(profile_dir+'/'+incfile): load_includeslist += list(map(lambda x: incfile+'/'+x, os.listdir(profile_dir+'/'+incfile))) - + return 0 def rematchfrag(frag, allow, path): @@ -4023,7 +4023,7 @@ def rematchfrag(frag, allow, path): combinedmode |= frag[allow]['path'][entry].get('mode', set()) combinedaudit |= frag[allow]['path'][entry].get('audit', set()) matches.append(entry) - + return combinedmode, combinedaudit, matches def match_include_to_path(incname, allow, path): @@ -4042,21 +4042,21 @@ def match_include_to_path(incname, allow, path): combinedmode |= cm combinedaudit |= am matches += m - + if include[incfile][incfile][allow]['path'][path]: combinedmode |= include[incfile][incfile][allow]['path'][path]['mode'] combinedaudit |= include[incfile][incfile][allow]['path'][path]['audit'] - + if include[incfile][incfile]['include'].keys(): includelist += include[incfile][incfile]['include'].keys() - + return combinedmode, combinedaudit, matches def match_prof_incs_to_path(frag, allow, path): combinedmode = set() combinedaudit = set() matches = [] - + includelist = list(frag['include'].keys()) while includelist: incname = includelist.pop(0) @@ -4065,14 +4065,14 @@ def match_prof_incs_to_path(frag, allow, path): combinedmode |= cm combinedaudit |= am matches += m - + return combinedmode, combinedaudit, matches def suggest_incs_for_path(incname, path, allow): combinedmode = set() combinedaudit = set() matches = [] - + includelist = [incname] while includelist: inc = includelist.pop(0) @@ -4081,14 +4081,14 @@ def suggest_incs_for_path(incname, path, allow): combinedmode |= cm combinedaudit |= am matches += m - + if include[inc][inc]['allow']['path'].get(path, False): combinedmode |= include[inc][inc]['allow']['path'][path]['mode'] combinedaudit |= include[inc][inc]['allow']['path'][path]['audit'] - + if include[inc][inc]['include'].keys(): includelist += include[inc][inc]['include'].keys() - + return combinedmode, combinedaudit, matches def check_qualifiers(program): @@ -4103,7 +4103,7 @@ def get_subdirectories(current_dir): return os.walk(current_dir).next()[1] else: return os.walk(current_dir).__next__()[1] - + def loadincludes(): incdirs = get_subdirectories(profile_dir) for idir in incdirs: @@ -4119,24 +4119,24 @@ def loadincludes(): fi = dirpath + '/' + fi fi = fi.replace(profile_dir+'/', '', 1) load_include(fi) - + def glob_common(path): globs = [] - + if re.search('[\d\.]+\.so$', path) or re.search('\.so\.[\d\.]+$', path): libpath = path libpath = re.sub('[\d\.]+\.so$', '*.so', libpath) libpath = re.sub('\.so\.[\d\.]+$', '.so.*', libpath) if libpath != path: globs.append(libpath) - + for glob in cfg['globs']: if re.search(glob, path): globbedpath = path globbedpath = re.sub(glob, cfg['globs'][glob], path) if globbedpath != path: globs.append(globbedpath) - + return sorted(set(globs)) def combine_name(name1, name2): @@ -4165,28 +4165,28 @@ def commonsuffix(new, old): def matchregexp(new, old): if re.search('\{.*(\,.*)*\}', old): return None - + # if re.search('\[.+\]', old) or re.search('\*', old) or re.search('\?', old): -# +# # new_reg = convert_regexp(new) # old_reg = convert_regexp(old) -# +# # pref = commonprefix(new, old) # if pref: # if convert_regexp('(*,**)$') in pref: # pref = pref.replace(convert_regexp('(*,**)$'), '') # new = new.replace(pref, '', 1) # old = old.replace(pref, '', 1) -# +# # suff = commonsuffix(new, old) # if suffix: # pass new_reg = convert_regexp(new) if re.search(new_reg, old): return True - + return None - + ######Initialisations###### conf = apparmor.config.Config('ini', CONFDIR) @@ -4218,4 +4218,3 @@ if not os.path.isfile(ldd) or not os.access(ldd, os.EX_OK): logger = conf.find_first_file(cfg['settings']['logger']) or '/bin/logger' if not os.path.isfile(logger) or not os.access(logger, os.EX_OK): raise AppArmorException('Can\'t find logger') - \ No newline at end of file diff --git a/apparmor/aamode.py b/apparmor/aamode.py index b357a6a5f..d02e3ac88 100644 --- a/apparmor/aamode.py +++ b/apparmor/aamode.py @@ -35,7 +35,7 @@ AA_EXEC_TYPE = (AA_MAY_EXEC | AA_EXEC_UNSAFE | AA_EXEC_INHERIT | ALL_AA_EXEC_TYPE = AA_EXEC_TYPE -MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, +MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, 'w': AA_MAY_WRITE, 'W': AA_MAY_WRITE, 'r': AA_MAY_READ, 'R': AA_MAY_READ, 'a': AA_MAY_APPEND, 'A': AA_MAY_APPEND, @@ -84,7 +84,7 @@ def sub_str_to_mode(string): mode |= MODE_HASH[tmp] else: pass - + return mode def split_log_mode(mode): @@ -102,10 +102,10 @@ def split_log_mode(mode): def mode_contains(mode, subset): # w implies a if mode & AA_MAY_WRITE: - mode |= AA_MAY_APPEND + mode |= AA_MAY_APPEND if mode & (AA_OTHER(AA_MAY_WRITE)): mode |= (AA_OTHER(AA_MAY_APPEND)) - + return (mode & subset) == subset def contains(mode, string): @@ -128,7 +128,7 @@ def map_log_mode(mode): def print_mode(mode): user, other = split_mode(mode) string = sub_mode_to_str(user) + '::' + sub_mode_to_str(other) - + return string def sub_mode_to_str(mode): @@ -137,7 +137,7 @@ def sub_mode_to_str(mode): if mode & AA_MAY_WRITE: mode = mode - AA_MAY_APPEND #string = ''.join(mode) - + if mode & AA_EXEC_MMAP: string += 'm' if mode & AA_MAY_READ: @@ -150,37 +150,37 @@ def sub_mode_to_str(mode): string += 'l' if mode & AA_MAY_LOCK: string += 'k' - + # modes P and C must appear before I and U else invalid syntax if mode & (AA_EXEC_PROFILE | AA_EXEC_NT): if mode & AA_EXEC_UNSAFE: string += 'p' else: string += 'P' - + if mode & AA_EXEC_CHILD: if mode & AA_EXEC_UNSAFE: string += 'c' else: string += 'C' - + if mode & AA_EXEC_UNCONFINED: if mode & AA_EXEC_UNSAFE: string += 'u' else: string += 'U' - + if mode & AA_EXEC_INHERIT: string += 'i' - + if mode & AA_MAY_EXEC: string += 'x' - + return string def is_user_mode(mode): user, other = split_mode(mode) - + if user and not other: return True else: @@ -195,7 +195,7 @@ def split_mode(mode): if not '::' in i: user.add(i) other = mode - user - other = AA_OTHER_REMOVE(other) + other = AA_OTHER_REMOVE(other) return user, other def mode_to_str(mode): @@ -205,11 +205,11 @@ def mode_to_str(mode): def flatten_mode(mode): if not mode: return set() - + user, other = split_mode(mode) mode = user | other mode |= (AA_OTHER(mode)) - + return mode def owner_flatten_mode(mode): @@ -219,22 +219,22 @@ def owner_flatten_mode(mode): def mode_to_str_user(mode): user, other = split_mode(mode) string = '' - + if not user: user = set() if not other: other = set() - + if user - other: if other: string = sub_mode_to_str(other) + '+' string += 'owner ' + sub_mode_to_str(user - other) - + elif is_user_mode(mode): string = 'owner ' + sub_mode_to_str(user) else: string = sub_mode_to_str(flatten_mode(mode)) - + return string def log_str_to_mode(profile, string, nt_name): @@ -247,7 +247,7 @@ def log_str_to_mode(profile, string, nt_name): if match: lprofile, lhat = match.groups() tmode = 0 - + if lprofile == profile: if mode & AA_MAY_EXEC: tmode = str_to_mode('Cx::') @@ -260,8 +260,8 @@ def log_str_to_mode(profile, string, nt_name): if mode & AA_OTHER(AA_MAY_EXEC): tmode |= str_to_mode('Px') nt_name = lhat - + mode = mode - str_to_mode('Nx') mode |= tmode - + return mode, nt_name \ No newline at end of file diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py index fe162beb6..0770881d0 100644 --- a/apparmor/cleanprofile.py +++ b/apparmor/cleanprofile.py @@ -9,24 +9,24 @@ class Prof: self.filelist = apparmor.aa.filelist self.include = apparmor.aa.include self.filename = filename - + class CleanProf: def __init__(self, same_file, profile, other): #If same_file we're basically comparing the file against itself to check superfluous rules self.same_file = same_file self.profile = profile self.other = profile - + def compare_profiles(self): #Remove the duplicate file-level includes from other other_file_includes = list(self.other.profile.filename['include'].keys()) for rule in self.profile.filelist[self.profile.filename]: if rule in other_file_includes: self.other.other.filename['include'].pop(rule) - + for profile in self.profile.aa.keys(): self.remove_duplicate_rules(profile) - + def remove_duplicate_rules(self, program): #Process the profile of the program #Process every hat in the profile individually @@ -35,25 +35,25 @@ class CleanProf: for hat in self.profile.aa[program].keys(): #The combined list of includes from profile and the file includes = list(self.profile.aa[program][hat]['include'].keys()) + file_includes - - #Clean up superfluous rules from includes in the other profile + + #Clean up superfluous rules from includes in the other profile for inc in includes: if not self.profile.include.get(inc, {}).get(inc,False): apparmor.aa.load_include(inc) deleted += apparmor.aa.delete_duplicates(self.other.aa[program][hat], inc) - + #Clean the duplicates of caps in other profile - deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file) + deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file) deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['deny']['capability'], self.other.aa[program][hat]['deny']['capability'], self.same_file) #Clean the duplicates of path in other profile deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file) deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file) - + #Clean the duplicates of net rules in other profile deleted += self.delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file) deleted += self.delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file) - + return deleted def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): @@ -73,14 +73,14 @@ class CleanProf: cm = profile[allow]['path'][rule]['mode'] am = profile[allow]['path'][rule]['audit'] #If modes of rule are a superset of rules implied by entry we can safely remove it - if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): + if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): deleted.append(entry) for entry in deleted: profile_other[allow]['path'].pop(entry) return len(deleted) - + def delete_cap_duplicates(self, profilecaps, profilecaps_other, same_profile=True): deleted = [] if profilecaps and profilecaps_other and not same_profile: @@ -89,9 +89,9 @@ class CleanProf: deleted.append(capname) for capname in deleted: profilecaps_other.pop(capname) - + return len(deleted) - + def delete_net_duplicates(self, netrules, netrules_other, same_profile=True): deleted = 0 copy_netrules_other = copy.deepcopy(netrules_other) @@ -108,7 +108,7 @@ class CleanProf: deleted += len(netrules['rule'][fam].keys()) else: deleted += 1 - netrules_other['rule'].pop(fam) + netrules_other['rule'].pop(fam) elif type(netrules_other['rule'][fam]) != dict and netrules_other['rule'][fam]: if type(netrules['rule'][fam]) != dict and netrules['rule'][fam]: if not same_profile: diff --git a/apparmor/common.py b/apparmor/common.py index 6d3275c1e..2836d3846 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -147,7 +147,7 @@ def readkey(): ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - + return ch def hasher(): @@ -160,20 +160,20 @@ def convert_regexp(regexp): regex_paren = re.compile('^(.*){([^}]*)}(.*)$') regexp = regexp.strip() new_reg = re.sub(r'(? (3,0): - config.read(filepath) + config.read(filepath) else: try: config.read(filepath) @@ -72,7 +72,7 @@ class Config: config.read(tmp_filepath.name) ##config.__get__() return config - + def write_config(self, filename, config): """Writes the given config to the specified file""" filepath = self.CONF_DIR + '/' + filename @@ -96,8 +96,8 @@ class Config: else: # Replace the target config file with the temporary file os.rename(config_file.name, filepath) - - + + def find_first_file(self, file_list): """Returns name of first matching file None otherwise""" filename = None @@ -106,7 +106,7 @@ class Config: filename = file break return filename - + def find_first_dir(self, dir_list): """Returns name of first matching directory None otherwise""" dirname = None @@ -116,7 +116,7 @@ class Config: dirname = direc break return dirname - + def read_shell(self, filepath): """Reads the shell type conf files and returns config[''][option]=value""" config = {'': dict()} @@ -134,7 +134,7 @@ class Config: value = None config[''][option] = value return config - + def write_shell(self, filepath, f_out, config): """Writes the config object in shell file format""" # All the options in the file @@ -153,8 +153,8 @@ class Config: comment = value.split('#', 1)[1] comment = '#'+comment else: - comment = '' - # If option exists in the new config file + comment = '' + # If option exists in the new config file if option in options: # If value is different if value != config[''][option]: @@ -174,7 +174,7 @@ class Config: # If option type option = result[0] value = None - # If option exists in the new config file + # If option exists in the new config file if option in options: # If its no longer option type if config[''][option] != None: @@ -182,7 +182,7 @@ class Config: line = option + '=' + value + '\n' f_out.write(line) # Remove from remaining options list - options.remove(option) + options.remove(option) else: # If its empty or comment copy as it is f_out.write(line) @@ -197,7 +197,7 @@ class Config: else: line = option + '=' + value + '\n' f_out.write(line) - + def write_configparser(self, filepath, f_out, config): """Writes/updates the given file with given config object""" # All the sections in the file @@ -229,21 +229,21 @@ class Config: f_out.write(line) else: # disable writing until next valid section - write = False - # If write enabled + write = False + # If write enabled elif write: value = shlex.split(line, True) # If the line is empty or a comment if not value: f_out.write(line) else: - option, value = line.split('=', 1) + option, value = line.split('=', 1) try: # split any inline comments value, comment = value.split('#', 1) comment = '#' + comment except ValueError: - comment = '' + comment = '' if option.strip() in options: if config[section][option.strip()] != value.strip(): value = value.replace(value, config[section][option.strip()]) @@ -264,7 +264,7 @@ class Config: options = config.options(section) for option in options: line = ' ' + option + ' = ' + config[section][option] + '\n' - f_out.write(line) + f_out.write(line) def py2_parser(filename): """Returns the de-dented ini file from the new format ini""" diff --git a/apparmor/logparser.py b/apparmor/logparser.py index 55a2757e8..e0232d735 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -3,7 +3,7 @@ import re import sys import time import LibAppArmor -from apparmor.common import (AppArmorException, error, debug, msg, +from apparmor.common import (AppArmorException, error, debug, msg, open_file_read, valid_path, hasher, open_file_write, convert_regexp, DebugLogger) @@ -45,7 +45,7 @@ class ReadLog: self.logmark = '' self.seenmark = None self.next_log_entry = None - + def prefetch_next_log_entry(self): if self.next_log_entry: sys.stderr.out('A log entry already present: %s' % self.next_log_entry) @@ -54,7 +54,7 @@ class ReadLog: self.next_log_entry = self.LOG.readline() if not self.next_log_entry: break - + def get_next_log_entry(self): # If no next log entry fetch it if not self.next_log_entry: @@ -62,22 +62,22 @@ class ReadLog: log_entry = self.next_log_entry self.next_log_entry = None return log_entry - + def peek_at_next_log_entry(self): # Take a peek at the next log entry if not self.next_log_entry: self.prefetch_next_log_entry() return self.next_log_entry - + def throw_away_next_log_entry(self): self.next_log_entry = None - + def parse_log_record(self, record): self.debug_logger.debug('parse_log_record: %s' % record) - + record_event = self.parse_event(record) return record_event - + def parse_event(self, msg): """Parse the event from log into key value pairs""" msg = msg.strip() @@ -122,21 +122,21 @@ class ReadLog: raise AppArmorException(_('Log contains unknown mode %s') % dmask) #print('parse_event:', ev['profile'], dmask, ev['name2']) mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2']) - + ev['denied_mask'] = mask ev['name2'] = name - + mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2']) ev['request_mask'] = mask ev['name2'] = name - + if not ev['time']: ev['time'] = int(time.time()) # Remove None keys #for key in ev.keys(): # if not ev[key] or not re.search('[\w]+', ev[key]): # ev.pop(key) - + if ev['aamode']: # Convert aamode values to their counter-parts mode_convertor = { @@ -152,13 +152,13 @@ class ReadLog: ev['aamode'] = mode_convertor[ev['aamode']] except KeyError: ev['aamode'] = None - + if ev['aamode']: #debug_logger.debug(ev) return ev else: return None - + def add_to_tree(self, loc_pid, parent, type, event): self.debug_logger.info('add_to_tree: pid [%s] type [%s] event [%s]' % (loc_pid, type, event)) if not self.pid.get(loc_pid, False): @@ -200,27 +200,27 @@ class ReadLog: aamode = 'ERROR' else: aamode = 'UNKNOWN' - + if aamode in ['UNKNOWN', 'AUDIT', 'STATUS', 'ERROR']: return None - + if 'profile_set' in e['operation']: return None # Skip if AUDIT event was issued due to a change_hat in unconfined mode if not e.get('profile', False): - return None - + return None + # Convert new null profiles to old single level null profile if '//null-' in e['profile']: e['profile'] = 'null-complain-profile' - + profile = e['profile'] hat = None - + if '//' in e['profile']: profile, hat = e['profile'].split('//')[:2] - + # Filter out change_hat events that aren't from learning if e['operation'] == 'change_hat': if aamode != 'HINT' and aamode != 'PERMITTING': @@ -229,18 +229,18 @@ class ReadLog: #hat = None if '//' in e['name']: profile, hat = e['name'].split('//')[:2] - + if not hat: hat = profile # prog is no longer passed around consistently prog = 'HINT' - + if profile != 'null-complain-profile' and not self.profile_exists(profile): return None if e['operation'] == 'exec': if e.get('info', False) and e['info'] == 'mandatory profile missing': - self.add_to_tree(e['pid'], e['parent'], 'exec', + self.add_to_tree(e['pid'], e['parent'], 'exec', [profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']]) elif e.get('name2', False) and '\\null-/' in e['name2']: self.add_to_tree(e['pid'], e['parent'], 'exec', @@ -250,11 +250,11 @@ class ReadLog: [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) else: self.debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile']) - + elif 'file_' in e['operation']: self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src', + elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src', 'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']: #print(e['operation'], e['name']) self.add_to_tree(e['pid'], e['parent'], 'path', @@ -274,18 +274,18 @@ class ReadLog: if entry and entry.get('info', False) == 'set profile': is_domain_change = True self.throw_away_next_log_entry() - + if is_domain_change: self.add_to_tree(e['pid'], e['parent'], 'exec', [profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']]) else: self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - + elif e['operation'] == 'sysctl': self.add_to_tree(e['pid'], e['parent'], 'path', [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) - + elif e['operation'] == 'clone': parent , child = e['pid'], e['task'] if not parent: @@ -305,7 +305,7 @@ class ReadLog: # else: # self.log += [arrayref] # self.pid[child] = arrayref - + elif self.op_type(e['operation']) == 'net': self.add_to_tree(e['pid'], e['parent'], 'netdomain', [profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']]) @@ -314,7 +314,7 @@ class ReadLog: [profile, hat, aamode, hat]) else: self.debug_logger.debug('UNHANDLED: %s' % e) - + def read_log(self, logmark): self.logmark = logmark seenmark = True @@ -337,11 +337,11 @@ class ReadLog: self.debug_logger.debug('read_log: %s' % line) if self.logmark in line: seenmark = True - + self.debug_logger.debug('read_log: seenmark = %s' %seenmark) if not seenmark: continue - + event = self.parse_log_record(line) #print(event) if event: @@ -349,12 +349,12 @@ class ReadLog: self.LOG.close() self.logmark = '' return self.log - + def op_type(self, operation): """Returns the operation type if known, unkown otherwise""" operation_type = self.OPERATION_TYPES.get(operation, 'unknown') return operation_type - + def profile_exists(self, program): """Returns True if profile exists, False otherwise""" # Check cache of profiles @@ -368,8 +368,8 @@ class ReadLog: self.existing_profiles[program] = prof_path return True return False - - + + def get_profile_filename(self, profile): """Returns the full profile name""" if profile.startswith('/'): diff --git a/apparmor/severity.py b/apparmor/severity.py index 7294411a2..673951e90 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -17,7 +17,7 @@ class Severity: self.severity['VARIABLES'] = dict() if not dbname: return None - + with open_file_read(dbname) as database:#open(dbname, 'r') for lineno, line in enumerate(database, start=1): line = line.strip() # or only rstrip and lstrip? @@ -61,7 +61,7 @@ class Severity: self.severity['CAPABILITIES'][resource] = severity else: raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) - + def handle_capability(self, resource): """Returns the severity of for the capability resource, default value if no match""" if resource in self.severity['CAPABILITIES'].keys(): @@ -69,8 +69,8 @@ class Severity: # raise ValueError("unexpected capability rank input: %s"%resource) warn("unknown capability: %s" % resource) return self.severity['DEFAULT_RANK'] - - + + def check_subtree(self, tree, mode, sev, segments): """Returns the max severity from the regex tree""" if len(segments) == 0: @@ -89,13 +89,13 @@ class Severity: if '*' in chunk: # Match rest of the path if re.search("^"+chunk, path): - # Find max rank + # Find max rank if "AA_RANK" in tree[chunk].keys(): for m in mode: if sev == None or tree[chunk]["AA_RANK"].get(m, -1) > sev: sev = tree[chunk]["AA_RANK"].get(m, None) return sev - + def handle_file(self, resource, mode): """Returns the severity for the file, default value if no match found""" resource = resource[1:] # remove initial / from path @@ -115,7 +115,7 @@ class Severity: return self.severity['DEFAULT_RANK'] else: return sev - + def rank(self, resource, mode=None): """Returns the rank for the resource file/capability""" if '@' in resource: # path contains variable @@ -126,7 +126,7 @@ class Severity: return self.handle_capability(resource) else: raise AppArmorException("Unexpected rank input: %s" % resource) - + def handle_variable_rank(self, resource, mode): """Returns the max possible rank for file resources containing variables""" regex_variable = re.compile('@{([^{.]*)}') @@ -138,13 +138,13 @@ class Severity: for replacement in self.severity['VARIABLES'][variable]: resource_replaced = self.variable_replace(variable, replacement, resource) rank_new = self.handle_variable_rank(resource_replaced, mode) - #rank_new = self.handle_variable_rank(resource.replace('@{'+variable+'}', replacement), mode) + #rank_new = self.handle_variable_rank(resource.replace('@{'+variable+'}', replacement), mode) if rank == None or rank_new > rank: rank = rank_new return rank else: return self.handle_file(resource, mode) - + def variable_replace(self, variable, replacement, resource): """Returns the expanded path for the passed variable""" leading = False @@ -159,7 +159,7 @@ class Severity: if replacement[-1] == '/' and replacement[-2:] !='//' and trailing: replacement = replacement[:-1] return resource.replace(variable, replacement) - + def load_variables(self, prof_path): """Loads the variables for the given profile""" regex_include = re.compile('^#?include\s*<(\S*)>') @@ -172,7 +172,7 @@ class Severity: if match: new_path = match.groups()[0] new_path = self.PROF_DIR + '/' + new_path - self.load_variables(new_path) + self.load_variables(new_path) else: # Remove any comments if '#' in line: @@ -190,7 +190,7 @@ class Severity: if line[0] in self.severity['VARIABLES'].keys(): raise AppArmorException("Variable %s was previously declared in file: %s" % (line[0], prof_path)) self.severity['VARIABLES'][line[0]] = [i.strip('"') for i in line[1].split()] - + def unload_variables(self): """Clears all loaded variables""" self.severity['VARIABLES'] = dict() diff --git a/apparmor/tools.py b/apparmor/tools.py index 444dbac3b..dc3894574 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -2,7 +2,7 @@ import os import sys import apparmor.aa as apparmor - + class aa_tools: def __init__(self, tool_name, args): self.name = tool_name @@ -21,22 +21,22 @@ class aa_tools: self.aa_mountpoint = apparmor.check_for_apparmor() elif tool_name == 'cleanprof': self.silent = args.silent - + def check_profile_dir(self): if self.profiledir: apparmor.profile_dir = apparmor.get_full_path(self.profiledir) if not os.path.isdir(apparmor.profile_dir): raise apparmor.AppArmorException("%s is not a directory." %self.profiledir) - + def check_disable_dir(self): if not os.path.isdir(self.disabledir): raise apparmor.AppArmorException("Can't find AppArmor disable directory %s." %self.disabledir) - + def act(self): for p in self.profiling: if not p: continue - + program = None if os.path.exists(p): program = apparmor.get_full_path(p).strip() @@ -44,32 +44,32 @@ class aa_tools: which = apparmor.which(p) if which: program = apparmor.get_full_path(which) - + apparmor.read_profiles() #If program does not exists on the system but its profile does if not program and apparmor.profile_exists(p): program = p - + if not program or not(os.path.exists(program) or apparmor.profile_exists(program)): if program and not program.startswith('/'): program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: apparmor.UI_Info(_("%s does not exist, please double-check the path.")%program) sys.exit(1) - + if program and apparmor.profile_exists(program):#os.path.exists(program): if self.name == 'autodep': self.use_autodep(program) - + elif self.name == 'cleanprof': self.clean_profile(program, p) - + else: filename = apparmor.get_profile_filename(program) - + if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): apparmor.UI_Info(_('Profile for %s not found, skipping')%p) - + elif self.name == 'disable': if not self.revert: apparmor.UI_Info(_('Disabling %s.')%program) @@ -77,14 +77,14 @@ class aa_tools: else: apparmor.UI_Info(_('Enabling %s.')%program) self.enable_profile(filename) - + elif self.name == 'audit': if not self.remove: apparmor.UI_Info(_('Setting %s to audit mode.')%program) else: apparmor.UI_Info(_('Removing audit mode from %s.')%program) apparmor.change_profile_flags(filename, program, 'audit', not self.remove) - + elif self.name == 'complain': if not self.remove: apparmor.set_complain(filename, program) @@ -94,20 +94,20 @@ class aa_tools: else: # One simply does not walk in here! raise apparmor.AppArmorException('Unknown tool: %s'%self.name) - + cmd_info = apparmor.cmd([apparmor.parser, filename, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) #cmd_info = apparmor.cmd(['cat', filename, '|', apparmor.parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) - + if cmd_info[0] != 0: raise apparmor.AppArmorException(cmd_info[1]) - + else: if '/' not in p: apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.")%(p, p)) else: apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) sys.exit(1) - + def clean_profile(self, program, p): filename = apparmor.get_profile_filename(program) @@ -117,7 +117,7 @@ class aa_tools: deleted = cleanprof.remove_duplicate_rules(program) apparmor.UI_Info(_("\nDeleted %s rules.") % deleted) apparmor.changed[program] = True - + if filename: if not self.silent: q = apparmor.hasher() @@ -145,19 +145,19 @@ class aa_tools: apparmor.reload_base(program) else: raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) - + def use_autodep(self, program): - apparmor.check_qualifiers(program) - + apparmor.check_qualifiers(program) + if os.path.exists(apparmor.get_profile_filename(program) and not self.force): apparmor.UI_Info('Profile for %s already exists - skipping.'%program) else: apparmor.autodep(program) if self.aa_mountpoint: apparmor.reload(program) - - def enable_profile(self, filename): + + def enable_profile(self, filename): apparmor.delete_symlink('disable', filename) - + def disable_profile(self, filename): apparmor.create_symlink('disable', filename) \ No newline at end of file diff --git a/apparmor/ui.py b/apparmor/ui.py index 9e89fafb2..747510c7f 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -50,7 +50,7 @@ def get_translated_hotkey(translated, cmsg=''): if cmsg: raise AppArmorException(cmsg) else: - raise AppArmorException('%s %s' %(msg, translated)) + raise AppArmorException('%s %s' %(msg, translated)) def UI_YesNo(text, default): debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default)) @@ -78,7 +78,7 @@ def UI_YesNo(text, default): ans = 'n' else: ans = default - + else: SendDataToYast({ 'type': 'dialog-yesno', @@ -98,11 +98,11 @@ def UI_YesNoCancel(text, default): yes = _('(Y)es') no = _('(N)o') cancel = _('(C)ancel') - + yeskey = get_translated_hotkey(yes).lower() nokey = get_translated_hotkey(no).lower() cancelkey = get_translated_hotkey(cancel).lower() - + ans = 'XXXINVALIDXXX' while ans not in ['c', 'n', 'y']: sys.stdout.write('\n' + text + '\n') @@ -300,45 +300,45 @@ def Text_PromptUser(question): explanation = question['explanation'] headers = question['headers'] functions = question['functions'] - + default = question['default'] options = question['options'] selected = question.get('selected', 0) helptext = question['helptext'] if helptext: functions.append('CMD_HELP') - + menu_items = [] keys = dict() - + for cmd in functions: if not CMDS.get(cmd, False): raise AppArmorException(_('PromptUser: Unknown command %s') % cmd) - + menutext = CMDS[cmd] - + key = get_translated_hotkey(menutext).lower() # Duplicate hotkey - if keys.get(key, False): - raise AppArmorException(_('PromptUser: Duplicate hotkey for %s: %s ') % (cmd, menutext)) - + if keys.get(key, False): + raise AppArmorException(_('PromptUser: Duplicate hotkey for %s: %s ') % (cmd, menutext)) + keys[key] = cmd - + if default and default == cmd: menutext = '[%s]' %menutext - + menu_items.append(menutext) - + default_key = 0 if default and CMDS[default]: defaulttext = CMDS[default] defmsg = _('PromptUser: Invalid hotkey in default item') - + default_key = get_translated_hotkey(defaulttext, defmsg).lower() - - if not keys.get(default_key, False): + + if not keys.get(default_key, False): raise AppArmorException(_('PromptUser: Invalid default %s') % default) - + widest = 0 header_copy = headers[:] while header_copy: @@ -347,22 +347,22 @@ def Text_PromptUser(question): if len(header) > widest: widest = len(header) widest += 1 - + formatstr = '%-' + str(widest) + 's %s\n' - + function_regexp = '^(' function_regexp += '|'.join(keys.keys()) if options: function_regexp += '|\d' function_regexp += ')$' - + ans = 'XXXINVALIDXXX' while not re.search(function_regexp, ans, flags=re.IGNORECASE): - + prompt = '\n' if title: prompt += '= %s =\n\n' %title - + if headers: header_copy = headers[:] while header_copy: @@ -370,10 +370,10 @@ def Text_PromptUser(question): value = header_copy.pop(0) prompt += formatstr %(header+':', value) prompt += '\n' - + if explanation: prompt += explanation + '\n\n' - + if options: for index, option in enumerate(options): if selected == index: @@ -382,45 +382,45 @@ def Text_PromptUser(question): format_option = ' %s - %s ' prompt += format_option %(index+1, option) prompt += '\n' - + prompt += ' / '.join(menu_items) - + sys.stdout.write(prompt+'\n') - + ans = getkey().lower() - + if ans: if ans == 'up': if options and selected > 0: selected -= 1 ans = 'XXXINVALIDXXX' - + elif ans == 'down': if options and selected < len(options)-1: selected += 1 ans = 'XXXINVALIDXXX' - + # elif keys.get(ans, False) == 'CMD_HELP': # sys.stdout.write('\n%s\n' %helptext) # ans = 'XXXINVALIDXXX' - + elif is_number(ans) == 10: # If they hit return choose default option ans = default_key - + elif options and re.search('^\d$', ans): ans = int(ans) if ans > 0 and ans <= len(options): selected = ans - 1 ans = 'XXXINVALIDXXX' - + if keys.get(ans, False) == 'CMD_HELP': sys.stdout.write('\n%s\n' %helptext) ans = 'again' - + if keys.get(ans, False): ans = keys[ans] - + return ans, selected def is_number(number): @@ -428,4 +428,3 @@ def is_number(number): return int(number) except: return False - \ No newline at end of file diff --git a/apparmor/yasti.py b/apparmor/yasti.py index 2d022b3a2..a3ab1af2a 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -11,7 +11,7 @@ debug_logger = DebugLogger('YaST') def setup_yast(): # To-Do - pass + pass def shutdown_yast(): # To-Do @@ -30,7 +30,7 @@ def SendDataToYast(data): return True else: debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) - error('SendDataToYast: didn\'t receive YCP command before connection died') + error('SendDataToYast: didn\'t receive YCP command before connection died') def GetDataFromYast(): debug_logger.inf('GetDataFromYast: Waiting for YCP command') From 93d59eb6eb98e5f68982e291a0adcd3f16ab1a13 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 22 Sep 2013 23:49:19 +0530 Subject: [PATCH 074/183] Fixes from rev70..72 --- Tools/aa-cleanprof | 2 +- Tools/aa-logprof | 4 ++-- Tools/aa-mergeprof | 2 +- Tools/manpages/aa-cleanprof.pod | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index 8f3d1fb1f..c90f61ab7 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -7,7 +7,7 @@ import apparmor.tools parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) -parser.add_argument('-s', '--silent', action='store_true', help=_('Silently over-write with a clean profile')) +parser.add_argument('-s', '--silent', action='store_true', help=_('Silently overwrite with a clean profile')) args = parser.parse_args() clean = apparmor.tools.aa_tools('cleanprof', args) diff --git a/Tools/aa-logprof b/Tools/aa-logprof index abdf1fec8..cf5d6e60e 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -20,8 +20,8 @@ if not aa_mountpoint: raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) if profiledir: - apparmor.profiledir = apparmor.get_full_path(profiledir) - if not os.path.isdir(apparmor.profiledir): + apparmor.profile_dir = apparmor.get_full_path(profiledir) + if not os.path.isdir(apparmor.profile_dir): raise apparmor.AppArmorException("%s is not a directory."%profiledir) apparmor.loadincludes() diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 8168fb154..e8dcd3e67 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -12,7 +12,7 @@ parser.add_argument('mine', type=str, help=_('your profile')) parser.add_argument('base', type=str, help=_('base profile')) parser.add_argument('other', type=str, help=_('other profile')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) -parser.add_argument('-auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts')) +parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts')) args = parser.parse_args() profiles = [args.mine, args.base, args.other] diff --git a/Tools/manpages/aa-cleanprof.pod b/Tools/manpages/aa-cleanprof.pod index 8d1ad30ec..1651b5a55 100644 --- a/Tools/manpages/aa-cleanprof.pod +++ b/Tools/manpages/aa-cleanprof.pod @@ -17,7 +17,7 @@ B<-d --dir /path/to/profiles> B<-s --silent> - Silently over-writes the profile without user prompt. + Silently overwrites the profile without user prompt. =head1 DESCRIPTION From 0b0aeeda291a73a249a9a5a21818783d277d3f85 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 23 Sep 2013 02:14:11 +0530 Subject: [PATCH 075/183] Fixed the netrule persistence issue in cleanprof, some elementary work for mergeprof --- Tools/aa-mergeprof | 56 ++++++++++++++++++++-------------------- apparmor/aa.py | 5 ++-- apparmor/cleanprofile.py | 35 ++++++++++++++++++------- 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index e8dcd3e67..f4961daa6 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -7,7 +7,6 @@ import apparmor.aa as apparmor import apparmor.cleanprofile as cleanprofile parser = argparse.ArgumentParser(description=_('Perform a 3way merge on the given profiles')) -##parser.add_argument('profiles', type=str, nargs=3, help='MINE BASE OTHER') parser.add_argument('mine', type=str, help=_('your profile')) parser.add_argument('base', type=str, help=_('base profile')) parser.add_argument('other', type=str, help=_('other profile')) @@ -17,13 +16,12 @@ args = parser.parse_args() profiles = [args.mine, args.base, args.other] -if __name__ == '__main__': - main() - print(profiles) def main(): mergeprofiles = Merge(profiles) + #Get rid of common/superfluous stuff + mergeprofiles.clear_common() class Merge(object): def __init__(self, profiles): @@ -31,45 +29,47 @@ class Merge(object): #Read and parse base profile and save profile data, include data from it and reset them apparmor.read_profile(base, True) - base = cleanprof.Prof() - #self.base_aa = apparmor.aa - #self.base_filelist = apparmor.filelist - #self.base_include = apparmor.include - reset() + base = cleanprofile.Prof(base) + + self.reset() #Read and parse other profile and save profile data, include data from it and reset them apparmor.read_profile(other, True) - other = cleanprof.prof() - #self.other_aa = apparmor.aa - #self.other_filelist = apparmor.filelist - #self.other_include = apparmor.include - reset() + other = cleanprofile.Prof(other) + + self.reset() #Read and parse user profile apparmor.read_profile(profiles[0], True) - user = cleanprof.prof() - #user_aa = apparmor.aa - #user_filelist = apparmor.filelist - #user_include = apparmor.include + user = cleanprofile.Prof(user) - def reset(): + def reset(self): apparmor.aa = apparmor.hasher() - apparmor.filelist = hasher() + apparmor.filelist = apparmor.hasher() apparmor.include = dict() - apparmor.existing_profiles = hasher() - apparmor.original_aa = hasher() + apparmor.existing_profiles = apparmor.hasher() + apparmor.original_aa = apparmor.hasher() def clear_common(self): - common_base_other() - remove_common('base') - remove_common('other') + deleted = 0 + #Remove off the parts in other profile which are common/superfluous from user profile + user_other = cleanprofile.CleanProf(False, user, other) + deleted += user_other.compare_profiles() + + #Remove off the parts in base profile which are common/superfluous from user profile + user_base = cleanprofile.CleanProf(False, user, base) + deleted += user_base.compare_profiles() + + #Remove off the parts in other profile which are common/superfluous from base profile + base_other = cleanprofile.CleanProf(False, base, other) + deleted += user_base.compare_profiles() - def common_base_other(self): + def ask_the_questions(self): pass - def remove_common(self, profile): - if prof1 == 'base': +if __name__ == '__main__': + main() # def intersect(ra, rb): # """Given two ranges return the range where they intersect or None. diff --git a/apparmor/aa.py b/apparmor/aa.py index 493998367..cdff2d575 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2018,12 +2018,13 @@ def glob_path_withext(newpath): def delete_net_duplicates(netrules, incnetrules): deleted = 0 + copy_netrules = deepcopy(netrules) if incnetrules and netrules: incnetglob = False # Delete matching rules from abstractions if incnetrules.get('all', False): incnetglob = True - for fam in netrules.keys(): + for fam in copy_netrules['rule'].keys(): if incnetglob or (type(incnetrules['rule'][fam]) != dict and incnetrules['rule'][fam]): if type(netrules['rule'][fam]) == dict: deleted += len(netrules['rule'][fam].keys()) @@ -2035,7 +2036,7 @@ def delete_net_duplicates(netrules, incnetrules): else: for socket_type in netrules['rule'][fam].keys(): if incnetrules['rule'].get(fam, False): - netrules[fam].pop(socket_type) + netrules['rule'][fam].pop(socket_type) deleted += 1 return deleted diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py index 0770881d0..b26f661d6 100644 --- a/apparmor/cleanprofile.py +++ b/apparmor/cleanprofile.py @@ -15,17 +15,21 @@ class CleanProf: #If same_file we're basically comparing the file against itself to check superfluous rules self.same_file = same_file self.profile = profile - self.other = profile + self.other = other def compare_profiles(self): + deleted = 0 + other_file_includes = list(self.other.filelist[self.profile.filename]['include'].keys()) + #Remove the duplicate file-level includes from other - other_file_includes = list(self.other.profile.filename['include'].keys()) - for rule in self.profile.filelist[self.profile.filename]: + for rule in self.profile.filelist[self.profile.filename]['include'].keys(): if rule in other_file_includes: - self.other.other.filename['include'].pop(rule) + self.other.filelist['include'].pop(rule) for profile in self.profile.aa.keys(): - self.remove_duplicate_rules(profile) + deleted += self.remove_duplicate_rules(profile) + + return deleted def remove_duplicate_rules(self, program): #Process the profile of the program @@ -36,6 +40,12 @@ class CleanProf: #The combined list of includes from profile and the file includes = list(self.profile.aa[program][hat]['include'].keys()) + file_includes + #If different files remove duplicate includes in the other profile + if not self.same_file: + for inc in includes: + if self.other.aa[program][hat]['include'].get(inc, False): + self.other.aa[program][hat]['include'].pop(inc) + deleted += 1 #Clean up superfluous rules from includes in the other profile for inc in includes: if not self.profile.include.get(inc, {}).get(inc,False): @@ -62,8 +72,13 @@ class CleanProf: for rule in profile[allow]['path'].keys(): for entry in profile_other[allow]['path'].keys(): if rule == entry: - if not same_profile: - deleted.append(entry) + #Check the modes + cm = profile[allow]['path'][rule]['mode'] + am = profile[allow]['path'][rule]['audit'] + #If modes of rule are a superset of rules implied by entry we can safely remove it + if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): + if not same_profile: + deleted.append(entry) continue if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): continue @@ -101,11 +116,11 @@ class CleanProf: if netrules.get('all', False): netglob = True #Iterate over a copy of the rules in the other profile - for fam in copy_netrules_other.keys(): + for fam in copy_netrules_other['rule'].keys(): if netglob or (type(netrules['rule'][fam]) != dict and netrules['rule'][fam]): if not same_profile: - if type(netrules_other['rule'][fam] == dict): - deleted += len(netrules['rule'][fam].keys()) + if type(netrules_other['rule'][fam]) == dict: + deleted += len(netrules_other['rule'][fam].keys()) else: deleted += 1 netrules_other['rule'].pop(fam) From 381ff97efa0266c812e7aaae4431596fd5300c8e Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 23 Sep 2013 03:47:15 +0530 Subject: [PATCH 076/183] fix for the delete count --- Tools/aa-mergeprof | 12 ++++++------ apparmor/aa.py | 17 +++++++++-------- apparmor/cleanprofile.py | 9 +++++---- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index f4961daa6..101538221 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -29,19 +29,19 @@ class Merge(object): #Read and parse base profile and save profile data, include data from it and reset them apparmor.read_profile(base, True) - base = cleanprofile.Prof(base) + self.base = cleanprofile.Prof(base) self.reset() #Read and parse other profile and save profile data, include data from it and reset them apparmor.read_profile(other, True) - other = cleanprofile.Prof(other) + self.other = cleanprofile.Prof(other) self.reset() #Read and parse user profile apparmor.read_profile(profiles[0], True) - user = cleanprofile.Prof(user) + self.user = cleanprofile.Prof(user) def reset(self): apparmor.aa = apparmor.hasher() @@ -53,15 +53,15 @@ class Merge(object): def clear_common(self): deleted = 0 #Remove off the parts in other profile which are common/superfluous from user profile - user_other = cleanprofile.CleanProf(False, user, other) + user_other = cleanprofile.CleanProf(False, self.user, self.other) deleted += user_other.compare_profiles() #Remove off the parts in base profile which are common/superfluous from user profile - user_base = cleanprofile.CleanProf(False, user, base) + user_base = cleanprofile.CleanProf(False, self.user, self.base) deleted += user_base.compare_profiles() #Remove off the parts in other profile which are common/superfluous from base profile - base_other = cleanprofile.CleanProf(False, base, other) + base_other = cleanprofile.CleanProf(False, self.base, self.other) deleted += user_base.compare_profiles() def ask_the_questions(self): diff --git a/apparmor/aa.py b/apparmor/aa.py index cdff2d575..6a5dd8715 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2018,6 +2018,7 @@ def glob_path_withext(newpath): def delete_net_duplicates(netrules, incnetrules): deleted = 0 + hasher_obj = hasher() copy_netrules = deepcopy(netrules) if incnetrules and netrules: incnetglob = False @@ -2025,13 +2026,13 @@ def delete_net_duplicates(netrules, incnetrules): if incnetrules.get('all', False): incnetglob = True for fam in copy_netrules['rule'].keys(): - if incnetglob or (type(incnetrules['rule'][fam]) != dict and incnetrules['rule'][fam]): - if type(netrules['rule'][fam]) == dict: + if incnetglob or (type(incnetrules['rule'][fam]) != type(hasher_obj) and incnetrules['rule'][fam]): + if type(netrules['rule'][fam]) == type(hasher_obj): deleted += len(netrules['rule'][fam].keys()) else: deleted += 1 netrules['rule'].pop(fam) - elif type(netrules['rule'][fam]) != dict and netrules['rule'][fam]: + elif type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: continue else: for socket_type in netrules['rule'][fam].keys(): @@ -2859,11 +2860,11 @@ def parse_profile_data(data, file, do_include): if RE_NETWORK_FAMILY_TYPE.search(network): nmatch = RE_NETWORK_FAMILY_TYPE.search(network).groups() fam, typ = nmatch[:2] - #Simply ignore any type subrules if family has True (seperately for allow and deny) - #This will lead to those type specific rules being lost when written - if not profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False): - profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = True - profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit + ##Simply ignore any type subrules if family has True (seperately for allow and deny) + ##This will lead to those type specific rules being lost when written + #if type(profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False)) == dict: + profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = 1 + profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit elif RE_NETWORK_FAMILY.search(network): fam = RE_NETWORK_FAMILY.search(network).groups()[0] profile_data[profile][hat][allow]['netdomain']['rule'][fam] = True diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py index b26f661d6..41b503108 100644 --- a/apparmor/cleanprofile.py +++ b/apparmor/cleanprofile.py @@ -109,6 +109,7 @@ class CleanProf: def delete_net_duplicates(self, netrules, netrules_other, same_profile=True): deleted = 0 + hasher_obj = apparmor.aa.hasher() copy_netrules_other = copy.deepcopy(netrules_other) if netrules_other and netrules: netglob = False @@ -117,15 +118,15 @@ class CleanProf: netglob = True #Iterate over a copy of the rules in the other profile for fam in copy_netrules_other['rule'].keys(): - if netglob or (type(netrules['rule'][fam]) != dict and netrules['rule'][fam]): + if netglob or (type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]): if not same_profile: - if type(netrules_other['rule'][fam]) == dict: + if type(netrules_other['rule'][fam]) == type(hasher_obj): deleted += len(netrules_other['rule'][fam].keys()) else: deleted += 1 netrules_other['rule'].pop(fam) - elif type(netrules_other['rule'][fam]) != dict and netrules_other['rule'][fam]: - if type(netrules['rule'][fam]) != dict and netrules['rule'][fam]: + elif type(netrules_other['rule'][fam]) != type(hasher_obj) and netrules_other['rule'][fam]: + if type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: if not same_profile: netrules_other['rule'].pop(fam) deleted += 1 From 37529a4cd1a47639ae42c68674f46a56aac2a203 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 23 Sep 2013 19:32:25 +0530 Subject: [PATCH 077/183] Added first version of aa-mergeprof, does not include the check for conflicting ix rules yet --- Tools/aa-mergeprof | 1020 +++++++++++++++++++++----------------- apparmor/aa.py | 2 +- apparmor/cleanprofile.py | 4 +- apparmor/tools.py | 2 +- 4 files changed, 567 insertions(+), 461 deletions(-) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 101538221..ff9956fd1 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -3,7 +3,9 @@ import argparse import sys -import apparmor.aa as apparmor +import apparmor.aa +import apparmor.aamode +import apparmor.severity import apparmor.cleanprofile as cleanprofile parser = argparse.ArgumentParser(description=_('Perform a 3way merge on the given profiles')) @@ -16,39 +18,70 @@ args = parser.parse_args() profiles = [args.mine, args.base, args.other] -print(profiles) - def main(): mergeprofiles = Merge(profiles) #Get rid of common/superfluous stuff mergeprofiles.clear_common() + + if not args.auto: + mergeprofiles.ask_the_questions('other') + + mergeprofiles.clear_common() + print("base") + mergeprofiles.ask_the_questions('base') + + q = apparmor.aa.hasher() + q['title'] = 'Changed Local Profiles' + q['headers'] = [] + q['explanation'] = _('The following local profiles were changed. Would you like to save them?') + q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] + q['default'] = 'CMD_VIEW_CHANGES' + q['options'] = [] + q['selected'] = 0 + p =None + ans = '' + arg = None + programs = list(mergeprofiles.user.aa.keys()) + program = programs[0] + while ans != 'CMD_SAVE_CHANGES': + ans, arg = apparmor.aa.UI_PromptUser(q) + if ans == 'CMD_SAVE_CHANGES': + apparmor.aa.write_profile_ui_feedback(program) + apparmor.aa.reload_base(program) + elif ans == 'CMD_VIEW_CHANGES': + for program in programs: + apparmor.aa.original_aa[program] = apparmor.aa.deepcopy(apparmor.aa.aa[program]) + #oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '') + newprofile = apparmor.aa.serialize_profile(mergeprofiles.user.aa[program], program, '') + apparmor.aa.display_changes_with_comments(mergeprofiles.user.filename, newprofile) + class Merge(object): def __init__(self, profiles): user, base, other = profiles #Read and parse base profile and save profile data, include data from it and reset them - apparmor.read_profile(base, True) + apparmor.aa.read_profile(base, True) self.base = cleanprofile.Prof(base) self.reset() #Read and parse other profile and save profile data, include data from it and reset them - apparmor.read_profile(other, True) + apparmor.aa.read_profile(other, True) self.other = cleanprofile.Prof(other) self.reset() #Read and parse user profile - apparmor.read_profile(profiles[0], True) + apparmor.aa.read_profile(profiles[0], True) self.user = cleanprofile.Prof(user) def reset(self): - apparmor.aa = apparmor.hasher() - apparmor.filelist = apparmor.hasher() - apparmor.include = dict() - apparmor.existing_profiles = apparmor.hasher() - apparmor.original_aa = apparmor.hasher() + apparmor.aa.aa = apparmor.aa.hasher() + apparmor.aa.filelist = apparmor.aa.hasher() + apparmor.aa.include = dict() + apparmor.aa.existing_profiles = apparmor.aa.hasher() + apparmor.aa.original_aa = apparmor.aa.hasher() def clear_common(self): deleted = 0 @@ -64,453 +97,526 @@ class Merge(object): base_other = cleanprofile.CleanProf(False, self.base, self.other) deleted += user_base.compare_profiles() - def ask_the_questions(self): - pass + def ask_the_questions(self, other): + if other == 'other': + other = self.other + else: + other = self.base + #print(other.aa) + + #Add the file-wide includes from the other profile to the user profile + done = False + options = list(map(lambda inc: '#include <%s>' %inc, sorted(other.filelist[other.filename]['include'].keys()))) + q = apparmor.aa.hasher() + q['options'] = options + default_option = 1 + q['selected'] = default_option - 1 + q['headers'] = [_('File includes'), _('Select the ones you wish to add')] + q['functions'] = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] + q['default'] = 'CMD_ALLOW' + while not done and options: + ans, selected = apparmor.aa.UI_PromptUser(q) + if ans == 'CMD_IGNORE_ENTRY': + done = True + elif ans == 'CMD_ALLOW': + selection = options[selected] + inc = apparmor.aa.re_match_include(selection) + self.user.filelist[self.user.filename]['include'][inc] = True + options.pop(selected) + apparmor.aa.UI_Info(_('Adding %s to the file.') % selection) + + + sev_db = apparmor.aa.sev_db + if not sev_db: + sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown')) + for profile in sorted(other.aa.keys()): + for hat in sorted(other.aa[profile].keys()): + #Add the includes from the other profile to the user profile + done = False + options = list(map(lambda inc: '#include <%s>' %inc, sorted(other.aa[profile][hat]['include'].keys()))) + q = apparmor.aa.hasher() + q['options'] = options + default_option = 1 + q['selected'] = default_option - 1 + q['headers'] = [_('File includes'), _('Select the ones you wish to add')] + q['functions'] = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] + q['default'] = 'CMD_ALLOW' + while not done and options: + ans, selected = apparmor.aa.UI_PromptUser(q) + if ans == 'CMD_IGNORE_ENTRY': + done = True + elif ans == 'CMD_ALLOW': + selection = options[selected] + inc = apparmor.aa.re_match_include(selection) + deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) + self.user.aa[profile][hat]['include'][inc] = True + options.pop(selected) + apparmor.aa.UI_Info(_('Adding %s to the file.') % selection) + if deleted: + apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + + #Add the capabilities + for allow in ['allow', 'deny']: + if other.aa[profile][hat].get(allow, False): + continue + for capability in sorted(other.aa[profile][hat][allow]['capability'].keys()): + severity = sev_db.rank('CAP_%s' % capability) + default_option = 1 + options = [] + newincludes = apparmor.aa.match_cap_includes(self.user.aa[profile][hat], capability) + q = apparmor.aa.hasher() + if newincludes: + options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) + + if options: + options.append('capability %s' % capability) + q['options'] = [options] + q['selected'] = default_option - 1 + + q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] + q['headers'] += [_('Capability'), capability] + q['headers'] += [_('Severity'), severity] + + audit_toggle = 0 + + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] + + q['default'] = 'CMD_ALLOW' + + done = False + while not done: + ans, selected = apparmor.aa.UI_PromptUser(q) + # Ignore the log entry + if ans == 'CMD_IGNORE_ENTRY': + done = True + break + + if ans == 'CMD_ALLOW': + selection = '' + if options: + selection = options[selected] + match = apparmor.aa.re_match_include(selection) + if match: + deleted = False + inc = match + deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) + self.user.aa[profile][hat]['include'][inc] = True + + apparmor.aa.UI_Info(_('Adding %s to profile.') % selection) + if deleted: + apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + + self.user.aa[profile][hat]['allow']['capability'][capability]['set'] = True + self.user.aa[profile][hat]['allow']['capability'][capability]['audit'] = other.aa[profile][hat]['allow']['capability'][capability]['audit'] + + apparmor.aa.changed[profile] = True + + apparmor.aa.UI_Info(_('Adding capability %s to profile.'), capability) + done = True + + elif ans == 'CMD_DENY': + self.user.aa[profile][hat]['deny']['capability'][capability]['set'] = True + apparmor.aa.changed[profile] = True + + apparmor.aa.UI_Info(_('Denying capability %s to profile.') % capability) + done = True + else: + done = False + + # Process all the path entries. + for allow in ['allow', 'deny']: + for path in sorted(other.aa[profile][hat][allow]['path'].keys()): + #print(path, other.aa[profile][hat][allow]['path'][path]) + mode = other.aa[profile][hat][allow]['path'][path]['mode'] + # Lookup modes from profile + allow_mode = set() + allow_audit = set() + deny_mode = set() + deny_audit = set() + + fmode, famode, fm = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'allow', path) + if fmode: + allow_mode |= fmode + if famode: + allow_audit |= famode + + cm, cam, m = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'deny', path) + if cm: + deny_mode |= cm + if cam: + deny_audit |= cam + + imode, iamode, im = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'allow', path) + if imode: + allow_mode |= imode + if iamode: + allow_audit |= iamode + + cm, cam, m = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'deny', path) + if cm: + deny_mode |= cm + if cam: + deny_audit |= cam + + if deny_mode & apparmor.aa.AA_MAY_EXEC: + deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE + + # Mask off the denied modes + mode = mode - deny_mode + + # If we get an exec request from some kindof event that generates 'PERMITTING X' + # check if its already in allow_mode + # if not add ix permission + if mode & apparmor.aa.AA_MAY_EXEC: + # Remove all type access permission + mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE + if not allow_mode & apparmor.aa.AA_MAY_EXEC: + mode |= apparmor.aa.str_to_mode('ix') + + # m is not implied by ix + + ### If we get an mmap request, check if we already have it in allow_mode + ##if mode & AA_EXEC_MMAP: + ## # ix implies m, so we don't need to add m if ix is present + ## if contains(allow_mode, 'ix'): + ## mode = mode - AA_EXEC_MMAP + + if not mode: + continue + + matches = [] + + if fmode: + apparmor.aa.matches.append(fm) + + if imode: + apparmor.aa.matches.append(im) + + if not apparmor.aa.mode_contains(allow_mode, mode): + default_option = 1 + options = [] + newincludes = [] + include_valid = False + + for incname in apparmor.aa.include.keys(): + include_valid = False + # If already present skip + if self.user.aa[profile][hat][incname]: + continue + if incname.startswith(apparmor.aa.profile_dir): + incname = incname.replace(apparmor.aa.profile_dir+'/', '', 1) + + include_valid = apparmor.aa.valid_include('', incname) + + if not include_valid: + continue + + cm, am, m = apparmor.aa.match_include_to_path(incname, 'allow', path) + + if cm and apparmor.aa.mode_contains(cm, mode): + dm = apparmor.aa.match_include_to_path(incname, 'deny', path)[0] + # If the mode is denied + if not mode & dm: + if not list(filter(lambda s: '/**' == s, m)): + newincludes.append(incname) + # Add new includes to the options + if newincludes: + options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes)))) + # We should have literal the path in options list too + options.append(path) + # Add any the globs matching path from logprof + globs = apparmor.aa.glob_common(path) + if globs: + matches += globs + # Add any user entered matching globs + for user_glob in apparmor.aa.user_globs: + if apparmor.aa.matchliteral(user_glob, path): + matches.append(user_glob) + + matches = list(set(matches)) + if path in matches: + matches.remove(path) + + options += apparmor.aa.order_globs(matches, path) + default_option = len(options) + + sev_db.unload_variables() + sev_db.load_variables(apparmor.aa.get_profile_filename(profile)) + severity = sev_db.rank(path, apparmor.aa.mode_to_str(mode)) + sev_db.unload_variables() + + audit_toggle = 0 + owner_toggle = 0 + if apparmor.aa.cfg['settings']['default_owner_prompt']: + owner_toggle = apparmor.aa.cfg['settings']['default_owner_prompt'] + done = False + while not done: + q = apparmor.aa.hasher() + q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat), + _('Path'), path] + + if allow_mode: + mode |= allow_mode + tail = '' + s = '' + prompt_mode = None + if owner_toggle == 0: + prompt_mode = apparmor.aa.flatten_mode(mode) + tail = ' ' + _('(owner permissions off)') + elif owner_toggle == 1: + prompt_mode = mode + elif owner_toggle == 2: + prompt_mode = allow_mode | apparmor.aa.owner_flatten_mode(mode - allow_mode) + tail = ' ' + _('(force new perms to owner)') + else: + prompt_mode = apparmor.aa.owner_flatten_mode(mode) + tail = ' ' + _('(force all rule perms to owner)') + + if audit_toggle == 1: + s = apparmor.aa.mode_to_str_user(allow_mode) + if allow_mode: + s += ', ' + s += 'audit ' + apparmor.aa.mode_to_str_user(prompt_mode - allow_mode) + tail + elif audit_toggle == 2: + s = 'audit ' + apparmor.aa.mode_to_str_user(prompt_mode) + tail + else: + s = apparmor.aa.mode_to_str_user(prompt_mode) + tail + + q['headers'] += [_('Old Mode'), apparmor.aa.mode_to_str_user(allow_mode), + _('New Mode'), s] + + else: + s = '' + tail = '' + prompt_mode = None + if audit_toggle: + s = 'audit' + if owner_toggle == 0: + prompt_mode = apparmor.aa.flatten_mode(mode) + tail = ' ' + _('(owner permissions off)') + elif owner_toggle == 1: + prompt_mode = mode + else: + prompt_mode = apparmor.aa.owner_flatten_mode(mode) + tail = ' ' + _('(force perms to owner)') + + s = apparmor.aa.mode_to_str_user(prompt_mode) + q['headers'] += [_('Mode'), s] + + q['headers'] += [_('Severity'), severity] + q['options'] = options + q['selected'] = default_option - 1 + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', + 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT', + 'CMD_FINISHED', 'CMD_OTHER'] + + q['default'] = 'CMD_ALLOW' + + + ans, selected = apparmor.aa.UI_PromptUser(q) + + if ans == 'CMD_IGNORE_ENTRY': + done = True + break + + if ans == 'CMD_OTHER': + audit_toggle, owner_toggle = apparmor.aa.UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode) + elif ans == 'CMD_USER_TOGGLE': + owner_toggle += 1 + if not allow_mode and owner_toggle == 2: + owner_toggle += 1 + if owner_toggle > 3: + owner_toggle = 0 + elif ans == 'CMD_ALLOW': + path = options[selected] + done = True + match = apparmor.aa.re_match_include(path) + if match: + inc = match + deleted = 0 + deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc) + self.user.aa[profile][hat]['include'][inc] = True + apparmor.aa.changed[profile] = True + apparmor.aa.UI_Info(_('Adding %s to profile.') % path) + if deleted: + apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + + else: + if self.user.aa[profile][hat]['allow']['path'][path].get('mode', False): + mode |= self.user.aa[profile][hat]['allow']['path'][path]['mode'] + deleted = [] + for entry in self.user.aa[profile][hat]['allow']['path'].keys(): + if path == entry: + continue + + if apparmor.aa.matchregexp(path, entry): + if apparmor.aa.mode_contains(mode, self.user.aa[profile][hat]['allow']['path'][entry]['mode']): + deleted.append(entry) + for entry in deleted: + self.user.aa[profile][hat]['allow']['path'].pop(entry) + deleted = len(deleted) + + if owner_toggle == 0: + mode = apparmor.aa.flatten_mode(mode) + #elif owner_toggle == 1: + # mode = mode + elif owner_toggle == 2: + mode = allow_mode | apparmor.aa.owner_flatten_mode(mode - allow_mode) + elif owner_toggle == 3: + mode = apparmor.aa.owner_flatten_mode(mode) + + self.user.aa[profile][hat]['allow']['path'][path]['mode'] = self.user.aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode + + tmpmode = set() + if audit_toggle == 1: + tmpmode = mode- allow_mode + elif audit_toggle == 2: + tmpmode = mode + + self.user.aa[profile][hat]['allow']['path'][path]['audit'] = self.user.aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode + + apparmor.aa.changed[profile] = True + + apparmor.aa.UI_Info(_('Adding %s %s to profile') % (path, apparmor.aa.mode_to_str_user(mode))) + if deleted: + apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + + elif ans == 'CMD_DENY': + path = options[selected].strip() + # Add new entry? + self.user.aa[profile][hat]['deny']['path'][path]['mode'] = self.user.aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode) + + self.user.aa[profile][hat]['deny']['path'][path]['audit'] = self.user.aa[profile][hat]['deny']['path'][path].get('audit', set()) + + apparmor.aa.changed[profile] = True + + done = True + + elif ans == 'CMD_NEW': + arg = options[selected] + if not apparmor.aa.re_match_include(arg): + ans = apparmor.aa.UI_GetString(_('Enter new path: '), arg) +# if ans: +# if not matchliteral(ans, path): +# ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path,ans) +# key = apparmor.aa.UI_YesNo(ynprompt, 'n') +# if key == 'n': +# continue + apparmor.aa.user_globs.append(ans) + options.append(ans) + default_option = len(options) + + elif ans == 'CMD_GLOB': + newpath = options[selected].strip() + if not apparmor.aa.re_match_include(newpath): + newpath = apparmor.aa.glob_path(newpath) + + if newpath not in options: + options.append(newpath) + default_option = len(options) + else: + default_option = options.index(newpath) + 1 + + elif ans == 'CMD_GLOBEXT': + newpath = options[selected].strip() + if not apparmor.aa.re_match_include(newpath): + newpath = apparmor.aa.glob_path_withext(newpath) + + if newpath not in options: + options.append(newpath) + default_option = len(options) + else: + default_option = options.index(newpath) + 1 + + elif re.search('\d', ans): + default_option = ans + + # + for allow in ['allow', 'deny']: + for family in sorted(other.aa[profile][hat][allow]['netdomain']['rule'].keys()): + # severity handling for net toggles goes here + print(family) + for sock_type in sorted(other.aa[profile][hat][allow]['netdomain']['rule'][family].keys()): + if apparmor.aa.profile_known_network(self.user.aa[profile][hat], family, sock_type): + continue + default_option = 1 + options = [] + newincludes = apparmor.aa.match_net_includes(self.user.aa[profile][hat], family, sock_type) + q = apparmor.aa.hasher() + if newincludes: + options += list(map(lambda s: '#include <%s>'%s, sorted(set(newincludes)))) + if True:#options: + options.append('network %s %s' % (family, sock_type)) + q['options'] = options + q['selected'] = default_option - 1 + + q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] + q['headers'] += [_('Network Family'), family] + q['headers'] += [_('Socket Type'), sock_type] + + audit_toggle = 0 + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] + + q['default'] = 'CMD_ALLOW' + + done = False + while not done: + ans, selected = apparmor.aa.UI_PromptUser(q) + if ans == 'CMD_IGNORE_ENTRY': + done = True + break + + if ans.startswith('CMD_AUDIT'): + audit_toggle = not audit_toggle + audit = '' + if audit_toggle: + audit = 'audit' + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', + 'CMD_ABORT', 'CMD_FINISHED'] + else: + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', + 'CMD_ABORT', 'CMD_FINISHED'] + q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] + q['headers'] += [_('Network Family'), audit + family] + q['headers'] += [_('Socket Type'), sock_type] + + elif ans == 'CMD_ALLOW': + #print(options, selected) + selection = options[selected] + done = True + if apparmor.aa.re_match_include(selection): #re.search('#include\s+<.+>$', selection): + inc = apparmor.aa.re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0] + deleted = 0 + deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) + + self.user.aa[profile][hat]['include'][inc] = True + + apparmor.aa.changed[profile] = True + + apparmor.aa.UI_Info(_('Adding %s to profile') % selection) + if deleted: + apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + + else: + self.user.aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle + self.user.aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True + + apparmor.aa.changed[profile] = True + + apparmor.aa.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) + + elif ans == 'CMD_DENY': + done = True + self.user.aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True + apparmor.aa.changed[profile] = True + apparmor.aa.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) + + else: + done = False + + if __name__ == '__main__': main() - -# def intersect(ra, rb): -# """Given two ranges return the range where they intersect or None. -# -# >>> intersect((0, 10), (0, 6)) -# (0, 6) -# >>> intersect((0, 10), (5, 15)) -# (5, 10) -# >>> intersect((0, 10), (10, 15)) -# >>> intersect((0, 9), (10, 15)) -# >>> intersect((0, 9), (7, 15)) -# (7, 9) -# """ -# # preconditions: (ra[0] <= ra[1]) and (rb[0] <= rb[1]) -# -# sa = max(ra[0], rb[0]) -# sb = min(ra[1], rb[1]) -# if sa < sb: -# return sa, sb -# else: -# return None -# -# -# def compare_range(a, astart, aend, b, bstart, bend): -# """Compare a[astart:aend] == b[bstart:bend], without slicing. -# """ -# if (aend-astart) != (bend-bstart): -# return False -# for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)): -# if a[ia] != b[ib]: -# return False -# else: -# return True -# -# -# -# -# class Merge3(object): -# """3-way merge of texts. -# -# Given BASE, OTHER, THIS, tries to produce a combined text -# incorporating the changes from both BASE->OTHER and BASE->THIS. -# All three will typically be sequences of lines.""" -# -# def __init__(self, base, a, b, is_cherrypick=False, allow_objects=False): -# """Constructor. -# -# :param base: lines in BASE -# :param a: lines in A -# :param b: lines in B -# :param is_cherrypick: flag indicating if this merge is a cherrypick. -# When cherrypicking b => a, matches with b and base do not conflict. -# :param allow_objects: if True, do not require that base, a and b are -# plain Python strs. Also prevents BinaryFile from being raised. -# Lines can be any sequence of comparable and hashable Python -# objects. -# """ -# if not allow_objects: -# #textfile.check_text_lines(base) -# #textfile.check_text_lines(a) -# #textfile.check_text_lines(b) -# pass -# self.base = base -# self.a = a -# self.b = b -# self.is_cherrypick = is_cherrypick -# -# def merge_lines(self, -# name_a=None, -# name_b=None, -# name_base=None, -# start_marker='<<<<<<<', -# mid_marker='=======', -# end_marker='>>>>>>>', -# base_marker=None, -# reprocess=False): -# """Return merge in cvs-like form. -# """ -# newline = '\n' -# if len(self.a) > 0: -# if self.a[0].endswith('\r\n'): -# newline = '\r\n' -# elif self.a[0].endswith('\r'): -# newline = '\r' -# if base_marker and reprocess: -# raise errors.CantReprocessAndShowBase() -# if name_a: -# start_marker = start_marker + ' ' + name_a -# if name_b: -# end_marker = end_marker + ' ' + name_b -# if name_base and base_marker: -# base_marker = base_marker + ' ' + name_base -# merge_regions = self.merge_regions() -# if reprocess is True: -# merge_regions = self.reprocess_merge_regions(merge_regions) -# for t in merge_regions: -# what = t[0] -# if what == 'unchanged': -# for i in range(t[1], t[2]): -# yield self.base[i] -# elif what == 'a' or what == 'same': -# for i in range(t[1], t[2]): -# yield self.a[i] -# elif what == 'b': -# for i in range(t[1], t[2]): -# yield self.b[i] -# elif what == 'conflict': -# yield start_marker + newline -# for i in range(t[3], t[4]): -# yield self.a[i] -# if base_marker is not None: -# yield base_marker + newline -# for i in range(t[1], t[2]): -# yield self.base[i] -# yield mid_marker + newline -# for i in range(t[5], t[6]): -# yield self.b[i] -# yield end_marker + newline -# else: -# raise ValueError(what) -# -# def merge_annotated(self): -# """Return merge with conflicts, showing origin of lines. -# -# Most useful for debugging merge. -# """ -# for t in self.merge_regions(): -# what = t[0] -# if what == 'unchanged': -# for i in range(t[1], t[2]): -# yield 'u | ' + self.base[i] -# elif what == 'a' or what == 'same': -# for i in range(t[1], t[2]): -# yield what[0] + ' | ' + self.a[i] -# elif what == 'b': -# for i in range(t[1], t[2]): -# yield 'b | ' + self.b[i] -# elif what == 'conflict': -# yield '<<<<\n' -# for i in range(t[3], t[4]): -# yield 'A | ' + self.a[i] -# yield '----\n' -# for i in range(t[5], t[6]): -# yield 'B | ' + self.b[i] -# yield '>>>>\n' -# else: -# raise ValueError(what) -# -# def merge_groups(self): -# """Yield sequence of line groups. Each one is a tuple: -# -# 'unchanged', lines -# Lines unchanged from base -# -# 'a', lines -# Lines taken from a -# -# 'same', lines -# Lines taken from a (and equal to b) -# -# 'b', lines -# Lines taken from b -# -# 'conflict', base_lines, a_lines, b_lines -# Lines from base were changed to either a or b and conflict. -# """ -# for t in self.merge_regions(): -# what = t[0] -# if what == 'unchanged': -# yield what, self.base[t[1]:t[2]] -# elif what == 'a' or what == 'same': -# yield what, self.a[t[1]:t[2]] -# elif what == 'b': -# yield what, self.b[t[1]:t[2]] -# elif what == 'conflict': -# yield (what, -# self.base[t[1]:t[2]], -# self.a[t[3]:t[4]], -# self.b[t[5]:t[6]]) -# else: -# raise ValueError(what) -# -# def merge_regions(self): -# """Return sequences of matching and conflicting regions. -# -# This returns tuples, where the first value says what kind we -# have: -# -# 'unchanged', start, end -# Take a region of base[start:end] -# -# 'same', astart, aend -# b and a are different from base but give the same result -# -# 'a', start, end -# Non-clashing insertion from a[start:end] -# -# Method is as follows: -# -# The two sequences align only on regions which match the base -# and both descendents. These are found by doing a two-way diff -# of each one against the base, and then finding the -# intersections between those regions. These "sync regions" -# are by definition unchanged in both and easily dealt with. -# -# The regions in between can be in any of three cases: -# conflicted, or changed on only one side. -# """ -# -# # section a[0:ia] has been disposed of, etc -# iz = ia = ib = 0 -# -# for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions(): -# matchlen = zend - zmatch -# # invariants: -# # matchlen >= 0 -# # matchlen == (aend - amatch) -# # matchlen == (bend - bmatch) -# len_a = amatch - ia -# len_b = bmatch - ib -# len_base = zmatch - iz -# # invariants: -# # assert len_a >= 0 -# # assert len_b >= 0 -# # assert len_base >= 0 -# -# #print 'unmatched a=%d, b=%d' % (len_a, len_b) -# -# if len_a or len_b: -# # try to avoid actually slicing the lists -# same = compare_range(self.a, ia, amatch, -# self.b, ib, bmatch) -# -# if same: -# yield 'same', ia, amatch -# else: -# equal_a = compare_range(self.a, ia, amatch, -# self.base, iz, zmatch) -# equal_b = compare_range(self.b, ib, bmatch, -# self.base, iz, zmatch) -# if equal_a and not equal_b: -# yield 'b', ib, bmatch -# elif equal_b and not equal_a: -# yield 'a', ia, amatch -# elif not equal_a and not equal_b: -# if self.is_cherrypick: -# for node in self._refine_cherrypick_conflict( -# iz, zmatch, ia, amatch, -# ib, bmatch): -# yield node -# else: -# yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch -# else: -# raise AssertionError("can't handle a=b=base but unmatched") -# -# ia = amatch -# ib = bmatch -# iz = zmatch -# -# # if the same part of the base was deleted on both sides -# # that's OK, we can just skip it. -# -# if matchlen > 0: -# # invariants: -# # assert ia == amatch -# # assert ib == bmatch -# # assert iz == zmatch -# -# yield 'unchanged', zmatch, zend -# iz = zend -# ia = aend -# ib = bend -# -# def _refine_cherrypick_conflict(self, zstart, zend, astart, aend, bstart, bend): -# """When cherrypicking b => a, ignore matches with b and base.""" -# # Do not emit regions which match, only regions which do not match -# matches = patiencediff.PatienceSequenceMatcher(None, -# self.base[zstart:zend], self.b[bstart:bend]).get_matching_blocks() -# last_base_idx = 0 -# last_b_idx = 0 -# last_b_idx = 0 -# yielded_a = False -# for base_idx, b_idx, match_len in matches: -# conflict_z_len = base_idx - last_base_idx -# conflict_b_len = b_idx - last_b_idx -# if conflict_b_len == 0: # There are no lines in b which conflict, -# # so skip it -# pass -# else: -# if yielded_a: -# yield ('conflict', -# zstart + last_base_idx, zstart + base_idx, -# aend, aend, bstart + last_b_idx, bstart + b_idx) -# else: -# # The first conflict gets the a-range -# yielded_a = True -# yield ('conflict', zstart + last_base_idx, zstart + -# base_idx, -# astart, aend, bstart + last_b_idx, bstart + b_idx) -# last_base_idx = base_idx + match_len -# last_b_idx = b_idx + match_len -# if last_base_idx != zend - zstart or last_b_idx != bend - bstart: -# if yielded_a: -# yield ('conflict', zstart + last_base_idx, zstart + base_idx, -# aend, aend, bstart + last_b_idx, bstart + b_idx) -# else: -# # The first conflict gets the a-range -# yielded_a = True -# yield ('conflict', zstart + last_base_idx, zstart + base_idx, -# astart, aend, bstart + last_b_idx, bstart + b_idx) -# if not yielded_a: -# yield ('conflict', zstart, zend, astart, aend, bstart, bend) -# -# def reprocess_merge_regions(self, merge_regions): -# """Where there are conflict regions, remove the agreed lines. -# -# Lines where both A and B have made the same changes are -# eliminated. -# """ -# for region in merge_regions: -# if region[0] != "conflict": -# yield region -# continue -# type, iz, zmatch, ia, amatch, ib, bmatch = region -# a_region = self.a[ia:amatch] -# b_region = self.b[ib:bmatch] -# matches = patiencediff.PatienceSequenceMatcher( -# None, a_region, b_region).get_matching_blocks() -# next_a = ia -# next_b = ib -# for region_ia, region_ib, region_len in matches[:-1]: -# region_ia += ia -# region_ib += ib -# reg = self.mismatch_region(next_a, region_ia, next_b, -# region_ib) -# if reg is not None: -# yield reg -# yield 'same', region_ia, region_len+region_ia -# next_a = region_ia + region_len -# next_b = region_ib + region_len -# reg = self.mismatch_region(next_a, amatch, next_b, bmatch) -# if reg is not None: -# yield reg -# -# @staticmethod -# def mismatch_region(next_a, region_ia, next_b, region_ib): -# if next_a < region_ia or next_b < region_ib: -# return 'conflict', None, None, next_a, region_ia, next_b, region_ib -# -# def find_sync_regions(self): -# """Return a list of sync regions, where both descendents match the base. -# -# Generates a list of (base1, base2, a1, a2, b1, b2). There is -# always a zero-length sync region at the end of all the files. -# """ -# -# ia = ib = 0 -# amatches = patiencediff.PatienceSequenceMatcher( -# None, self.base, self.a).get_matching_blocks() -# bmatches = patiencediff.PatienceSequenceMatcher( -# None, self.base, self.b).get_matching_blocks() -# len_a = len(amatches) -# len_b = len(bmatches) -# -# sl = [] -# -# while ia < len_a and ib < len_b: -# abase, amatch, alen = amatches[ia] -# bbase, bmatch, blen = bmatches[ib] -# -# # there is an unconflicted block at i; how long does it -# # extend? until whichever one ends earlier. -# i = intersect((abase, abase+alen), (bbase, bbase+blen)) -# if i: -# intbase = i[0] -# intend = i[1] -# intlen = intend - intbase -# -# # found a match of base[i[0], i[1]]; this may be less than -# # the region that matches in either one -# # assert intlen <= alen -# # assert intlen <= blen -# # assert abase <= intbase -# # assert bbase <= intbase -# -# asub = amatch + (intbase - abase) -# bsub = bmatch + (intbase - bbase) -# aend = asub + intlen -# bend = bsub + intlen -# -# # assert self.base[intbase:intend] == self.a[asub:aend], \ -# # (self.base[intbase:intend], self.a[asub:aend]) -# # assert self.base[intbase:intend] == self.b[bsub:bend] -# -# sl.append((intbase, intend, -# asub, aend, -# bsub, bend)) -# # advance whichever one ends first in the base text -# if (abase + alen) < (bbase + blen): -# ia += 1 -# else: -# ib += 1 -# -# intbase = len(self.base) -# abase = len(self.a) -# bbase = len(self.b) -# sl.append((intbase, intbase, abase, abase, bbase, bbase)) -# -# return sl -# -# def find_unconflicted(self): -# """Return a list of ranges in base that are not conflicted.""" -# am = patiencediff.PatienceSequenceMatcher( -# None, self.base, self.a).get_matching_blocks() -# bm = patiencediff.PatienceSequenceMatcher( -# None, self.base, self.b).get_matching_blocks() -# -# unc = [] -# -# while am and bm: -# # there is an unconflicted block at i; how long does it -# # extend? until whichever one ends earlier. -# a1 = am[0][0] -# a2 = a1 + am[0][2] -# b1 = bm[0][0] -# b2 = b1 + bm[0][2] -# i = intersect((a1, a2), (b1, b2)) -# if i: -# unc.append(i) -# -# if a2 < b2: -# del am[0] -# else: -# del bm[0] -# -# return unc -# -# a = file(profiles[0], 'rt').readlines() -# base = file(profiles[1], 'rt').readlines() -# b = file(profiles[2], 'rt').readlines() -# -# m3 = Merge3(base, a, b) -# -# sys.stdout.write(m3.merge_annotated()) \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index 6a5dd8715..119e68ce6 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2097,7 +2097,7 @@ def delete_duplicates(profile, incname): return deleted def match_net_include(incname, family, type): - includelist = incname[:] + includelist = [incname] checked = [] name = None if includelist: diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py index 41b503108..0d7c64be7 100644 --- a/apparmor/cleanprofile.py +++ b/apparmor/cleanprofile.py @@ -19,12 +19,12 @@ class CleanProf: def compare_profiles(self): deleted = 0 - other_file_includes = list(self.other.filelist[self.profile.filename]['include'].keys()) + other_file_includes = list(self.other.filelist[self.other.filename]['include'].keys()) #Remove the duplicate file-level includes from other for rule in self.profile.filelist[self.profile.filename]['include'].keys(): if rule in other_file_includes: - self.other.filelist['include'].pop(rule) + self.other.filelist[self.other.filename]['include'].pop(rule) for profile in self.profile.aa.keys(): deleted += self.remove_duplicate_rules(profile) diff --git a/apparmor/tools.py b/apparmor/tools.py index dc3894574..41e44c3a7 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -123,7 +123,7 @@ class aa_tools: q = apparmor.hasher() q['title'] = 'Changed Local Profiles' q['headers'] = [] - q['explanation'] = _('The following local profiles were changed. Would you like to save them?') + q['explanation'] = _('The local profile for %s in file %s was changed. Would you like to save it?') %(program, filename) q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = [] From 6f46a777ca501647f74c1e6d75b5336c9071faaa Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 23 Sep 2013 20:09:09 +0530 Subject: [PATCH 078/183] updated messages.pot --- Translate/messages.pot | 299 ++++++++++++++++++++++------------------- 1 file changed, 161 insertions(+), 138 deletions(-) diff --git a/Translate/messages.pot b/Translate/messages.pot index 0c13a8e39..ce3df5153 100755 --- a/Translate/messages.pot +++ b/Translate/messages.pot @@ -5,7 +5,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-09-22 15:23+IST\n" +"POT-Creation-Date: 2013-09-23 19:39+IST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -21,7 +21,7 @@ msgstr "" #: ../Tools/aa-audit:8 ../Tools/aa-autodep:9 ../Tools/aa-cleanprof:8 #: ../Tools/aa-complain:8 ../Tools/aa-disable:8 ../Tools/aa-enforce:8 -#: ../Tools/aa-genprof:37 ../Tools/aa-logprof:9 ../Tools/aa-mergeprof:14 +#: ../Tools/aa-genprof:37 ../Tools/aa-logprof:9 ../Tools/aa-mergeprof:15 msgid "path to profiles" msgstr "" @@ -47,7 +47,7 @@ msgid "Cleanup the profiles for the given programs" msgstr "" #: ../Tools/aa-cleanprof:10 -msgid "Silently over-write with a clean profile" +msgid "Silently overwrite with a clean profile" msgstr "" #: ../Tools/aa-complain:7 @@ -157,26 +157,145 @@ msgstr "" msgid "mark in the log to start processing after" msgstr "" -#: ../Tools/aa-mergeprof:9 +#: ../Tools/aa-mergeprof:11 msgid "Perform a 3way merge on the given profiles" msgstr "" -#: ../Tools/aa-mergeprof:11 +#: ../Tools/aa-mergeprof:12 msgid "your profile" msgstr "" -#: ../Tools/aa-mergeprof:12 +#: ../Tools/aa-mergeprof:13 msgid "base profile" msgstr "" -#: ../Tools/aa-mergeprof:13 +#: ../Tools/aa-mergeprof:14 msgid "other profile" msgstr "" -#: ../Tools/aa-mergeprof:15 +#: ../Tools/aa-mergeprof:16 msgid "Automatically merge profiles, exits incase of *x conflicts" msgstr "" +#: ../Tools/aa-mergeprof:36 ../apparmor/aa.py:2271 +msgid "The following local profiles were changed. Would you like to save them?" +msgstr "" + +#: ../Tools/aa-mergeprof:114 ../Tools/aa-mergeprof:141 +msgid "File includes" +msgstr "" + +#: ../Tools/aa-mergeprof:114 ../Tools/aa-mergeprof:141 +msgid "Select the ones you wish to add" +msgstr "" + +#: ../Tools/aa-mergeprof:126 ../Tools/aa-mergeprof:154 +msgid "Adding %s to the file." +msgstr "" + +#: ../Tools/aa-mergeprof:131 ../apparmor/aa.py:2185 +msgid "unknown" +msgstr "" + +#: ../Tools/aa-mergeprof:156 ../Tools/aa-mergeprof:207 +#: ../Tools/aa-mergeprof:442 ../Tools/aa-mergeprof:482 +#: ../Tools/aa-mergeprof:599 ../apparmor/aa.py:1557 ../apparmor/aa.py:1792 +#: ../apparmor/aa.py:1832 ../apparmor/aa.py:1951 +msgid "Deleted %s previous matching profile entries." +msgstr "" + +#: ../Tools/aa-mergeprof:176 ../Tools/aa-mergeprof:355 +#: ../Tools/aa-mergeprof:553 ../Tools/aa-mergeprof:580 ../apparmor/aa.py:944 +#: ../apparmor/aa.py:1200 ../apparmor/aa.py:1504 ../apparmor/aa.py:1540 +#: ../apparmor/aa.py:1703 ../apparmor/aa.py:1902 ../apparmor/aa.py:1933 +msgid "Profile" +msgstr "" + +#: ../Tools/aa-mergeprof:177 ../apparmor/aa.py:1505 ../apparmor/aa.py:1541 +msgid "Capability" +msgstr "" + +#: ../Tools/aa-mergeprof:178 ../Tools/aa-mergeprof:406 ../apparmor/aa.py:1206 +#: ../apparmor/aa.py:1506 ../apparmor/aa.py:1542 ../apparmor/aa.py:1754 +msgid "Severity" +msgstr "" + +#: ../Tools/aa-mergeprof:205 ../Tools/aa-mergeprof:440 ../apparmor/aa.py:1555 +#: ../apparmor/aa.py:1790 +msgid "Adding %s to profile." +msgstr "" + +#: ../Tools/aa-mergeprof:214 ../apparmor/aa.py:1564 +msgid "Adding capability %s to profile." +msgstr "" + +#: ../Tools/aa-mergeprof:221 ../apparmor/aa.py:1571 +msgid "Denying capability %s to profile." +msgstr "" + +#: ../Tools/aa-mergeprof:356 ../apparmor/aa.py:1704 +msgid "Path" +msgstr "" + +#: ../Tools/aa-mergeprof:365 ../Tools/aa-mergeprof:396 ../apparmor/aa.py:1713 +#: ../apparmor/aa.py:1744 +msgid "(owner permissions off)" +msgstr "" + +#: ../Tools/aa-mergeprof:370 ../apparmor/aa.py:1718 +msgid "(force new perms to owner)" +msgstr "" + +#: ../Tools/aa-mergeprof:373 ../apparmor/aa.py:1721 +msgid "(force all rule perms to owner)" +msgstr "" + +#: ../Tools/aa-mergeprof:385 ../apparmor/aa.py:1733 +msgid "Old Mode" +msgstr "" + +#: ../Tools/aa-mergeprof:386 ../apparmor/aa.py:1734 +msgid "New Mode" +msgstr "" + +#: ../Tools/aa-mergeprof:401 ../apparmor/aa.py:1749 +msgid "(force perms to owner)" +msgstr "" + +#: ../Tools/aa-mergeprof:404 ../apparmor/aa.py:1752 +msgid "Mode" +msgstr "" + +#: ../Tools/aa-mergeprof:480 ../apparmor/aa.py:1830 +msgid "Adding %s %s to profile" +msgstr "" + +#: ../Tools/aa-mergeprof:498 ../apparmor/aa.py:1848 +msgid "Enter new path: " +msgstr "" + +#: ../Tools/aa-mergeprof:554 ../Tools/aa-mergeprof:581 ../apparmor/aa.py:1903 +#: ../apparmor/aa.py:1934 +msgid "Network Family" +msgstr "" + +#: ../Tools/aa-mergeprof:555 ../Tools/aa-mergeprof:582 ../apparmor/aa.py:1904 +#: ../apparmor/aa.py:1935 +msgid "Socket Type" +msgstr "" + +#: ../Tools/aa-mergeprof:597 ../apparmor/aa.py:1949 +msgid "Adding %s to profile" +msgstr "" + +#: ../Tools/aa-mergeprof:607 ../apparmor/aa.py:1959 +msgid "Adding network access %s %s to profile." +msgstr "" + +#: ../Tools/aa-mergeprof:613 ../apparmor/aa.py:1965 +msgid "Denying network access %s %s to profile" +msgstr "" + #: ../Tools/aa-unconfined:9 msgid "Lists unconfined processes having tcp or udp ports" msgstr "" @@ -297,12 +416,6 @@ msgid "" "These changes could not be sent." msgstr "" -#: ../apparmor/aa.py:944 ../apparmor/aa.py:1200 ../apparmor/aa.py:1504 -#: ../apparmor/aa.py:1540 ../apparmor/aa.py:1703 ../apparmor/aa.py:1902 -#: ../apparmor/aa.py:1933 -msgid "Profile" -msgstr "" - #: ../apparmor/aa.py:947 msgid "Default Hat" msgstr "" @@ -328,11 +441,6 @@ msgstr "" msgid "Execute" msgstr "" -#: ../apparmor/aa.py:1206 ../apparmor/aa.py:1506 ../apparmor/aa.py:1542 -#: ../apparmor/aa.py:1754 -msgid "Severity" -msgstr "" - #: ../apparmor/aa.py:1229 msgid "Are you specifying a transition to a local profile?" msgstr "" @@ -399,67 +507,6 @@ msgstr "" msgid "Invalid mode found: %s" msgstr "" -#: ../apparmor/aa.py:1505 ../apparmor/aa.py:1541 -msgid "Capability" -msgstr "" - -#: ../apparmor/aa.py:1555 ../apparmor/aa.py:1790 -msgid "Adding %s to profile." -msgstr "" - -#: ../apparmor/aa.py:1557 ../apparmor/aa.py:1792 ../apparmor/aa.py:1832 -#: ../apparmor/aa.py:1951 -msgid "Deleted %s previous matching profile entries." -msgstr "" - -#: ../apparmor/aa.py:1564 -msgid "Adding capability %s to profile." -msgstr "" - -#: ../apparmor/aa.py:1571 -msgid "Denying capability %s to profile." -msgstr "" - -#: ../apparmor/aa.py:1704 -msgid "Path" -msgstr "" - -#: ../apparmor/aa.py:1713 ../apparmor/aa.py:1744 -msgid "(owner permissions off)" -msgstr "" - -#: ../apparmor/aa.py:1718 -msgid "(force new perms to owner)" -msgstr "" - -#: ../apparmor/aa.py:1721 -msgid "(force all rule perms to owner)" -msgstr "" - -#: ../apparmor/aa.py:1733 -msgid "Old Mode" -msgstr "" - -#: ../apparmor/aa.py:1734 -msgid "New Mode" -msgstr "" - -#: ../apparmor/aa.py:1749 -msgid "(force perms to owner)" -msgstr "" - -#: ../apparmor/aa.py:1752 -msgid "Mode" -msgstr "" - -#: ../apparmor/aa.py:1830 -msgid "Adding %s %s to profile" -msgstr "" - -#: ../apparmor/aa.py:1848 -msgid "Enter new path: " -msgstr "" - #: ../apparmor/aa.py:1851 msgid "" "The specified path does not match this log entry:\n" @@ -469,153 +516,125 @@ msgid "" "Do you really want to use this path?" msgstr "" -#: ../apparmor/aa.py:1903 ../apparmor/aa.py:1934 -msgid "Network Family" -msgstr "" - -#: ../apparmor/aa.py:1904 ../apparmor/aa.py:1935 -msgid "Socket Type" -msgstr "" - -#: ../apparmor/aa.py:1949 -msgid "Adding %s to profile" -msgstr "" - -#: ../apparmor/aa.py:1959 -msgid "Adding network access %s %s to profile." -msgstr "" - -#: ../apparmor/aa.py:1965 -msgid "Denying network access %s %s to profile" -msgstr "" - -#: ../apparmor/aa.py:2176 +#: ../apparmor/aa.py:2178 msgid "Reading log entries from %s." msgstr "" -#: ../apparmor/aa.py:2179 +#: ../apparmor/aa.py:2181 msgid "Updating AppArmor profiles in %s." msgstr "" -#: ../apparmor/aa.py:2183 -msgid "unknown" -msgstr "" - -#: ../apparmor/aa.py:2246 +#: ../apparmor/aa.py:2248 msgid "" "Select which profile changes you would like to save to the\n" "local profile set." msgstr "" -#: ../apparmor/aa.py:2247 +#: ../apparmor/aa.py:2249 msgid "Local profile changes" msgstr "" -#: ../apparmor/aa.py:2269 ../apparmor/tools.py:126 -msgid "The following local profiles were changed. Would you like to save them?" -msgstr "" - -#: ../apparmor/aa.py:2345 +#: ../apparmor/aa.py:2347 msgid "Profile Changes" msgstr "" -#: ../apparmor/aa.py:2355 +#: ../apparmor/aa.py:2357 msgid "Can't find existing profile %s to compare changes." msgstr "" -#: ../apparmor/aa.py:2493 ../apparmor/aa.py:2508 +#: ../apparmor/aa.py:2495 ../apparmor/aa.py:2510 msgid "Can't read AppArmor profiles in %s" msgstr "" -#: ../apparmor/aa.py:2584 +#: ../apparmor/aa.py:2586 msgid "%s profile in %s contains syntax errors in line: %s." msgstr "" -#: ../apparmor/aa.py:2636 +#: ../apparmor/aa.py:2638 msgid "Syntax Error: Unexpected End of Profile reached in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2651 +#: ../apparmor/aa.py:2653 msgid "Syntax Error: Unexpected capability entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2670 +#: ../apparmor/aa.py:2672 msgid "Syntax Error: Unexpected link entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2698 +#: ../apparmor/aa.py:2700 msgid "Syntax Error: Unexpected change profile entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2720 +#: ../apparmor/aa.py:2722 msgid "Syntax Error: Unexpected rlimit entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2731 +#: ../apparmor/aa.py:2733 msgid "Syntax Error: Unexpected boolean definition found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2771 +#: ../apparmor/aa.py:2773 msgid "Syntax Error: Unexpected path entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2795 +#: ../apparmor/aa.py:2797 msgid "Syntax Error: Invalid Regex %s in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2798 +#: ../apparmor/aa.py:2800 msgid "Invalid mode %s in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2848 +#: ../apparmor/aa.py:2850 msgid "Syntax Error: Unexpected network entry found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2878 +#: ../apparmor/aa.py:2880 msgid "Syntax Error: Unexpected change hat declaration found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2890 +#: ../apparmor/aa.py:2892 msgid "Syntax Error: Unexpected hat definition found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2906 +#: ../apparmor/aa.py:2908 msgid "Error: Multiple definitions for hat %s in profile %s." msgstr "" -#: ../apparmor/aa.py:2926 +#: ../apparmor/aa.py:2928 msgid "Syntax Error: Unknown line found in file: %s line: %s" msgstr "" -#: ../apparmor/aa.py:2939 +#: ../apparmor/aa.py:2941 msgid "Syntax Error: Missing '}' . Reached end of file %s while inside profile %s" msgstr "" -#: ../apparmor/aa.py:2970 +#: ../apparmor/aa.py:2972 msgid "An existing variable redefined: %s" msgstr "" -#: ../apparmor/aa.py:2975 +#: ../apparmor/aa.py:2977 msgid "Values added to a non-existing variable: %s" msgstr "" -#: ../apparmor/aa.py:2977 +#: ../apparmor/aa.py:2979 msgid "Unknown variable operation: %s" msgstr "" -#: ../apparmor/aa.py:3352 +#: ../apparmor/aa.py:3354 msgid "Can't find existing profile to modify" msgstr "" -#: ../apparmor/aa.py:3854 +#: ../apparmor/aa.py:3856 msgid "Writing updated profile for %s." msgstr "" -#: ../apparmor/aa.py:3988 +#: ../apparmor/aa.py:3990 msgid "File Not Found: %s" msgstr "" -#: ../apparmor/aa.py:4097 +#: ../apparmor/aa.py:4099 msgid "" "%s is currently marked as a program that should not have its own\n" "profile. Usually, programs are marked this way if creating a profile for \n" @@ -662,6 +681,10 @@ msgid "" "Deleted %s rules." msgstr "" +#: ../apparmor/tools.py:126 +msgid "The local profile for %s in file %s was changed. Would you like to save it?" +msgstr "" + #: ../apparmor/tools.py:147 msgid "The profile for %s does not exists. Nothing to clean." msgstr "" From 24f3b67b5608df8a37304d4dc9a756b6ca4a98c8 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 23 Sep 2013 21:00:36 +0530 Subject: [PATCH 079/183] --- Tools/aa-genprof | 2 +- apparmor/aa.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 27e90fd58..b46e03e6b 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -75,7 +75,7 @@ apparmor.loadincludes() profile_filename = apparmor.get_profile_filename(program) if os.path.exists(profile_filename): - apparmor.helpers[program] = apparmor.get_profile_flags(profile_filename) + apparmor.helpers[program] = apparmor.get_profile_flags(profile_filename, program) else: apparmor.autodep(program) apparmor.helpers[program] = 'enforce' diff --git a/apparmor/aa.py b/apparmor/aa.py index 119e68ce6..e47c7a5ea 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1466,7 +1466,7 @@ def ask_the_questions(): elif aamode == 'REJECTING': UI_Info(_('Enforce-mode changes:')) else: - # oops something screwed up + # This is so wrong! fatal_error(_('Invalid mode found: %s') % aamode) for profile in sorted(log_dict[aamode].keys()): @@ -2035,7 +2035,7 @@ def delete_net_duplicates(netrules, incnetrules): elif type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: continue else: - for socket_type in netrules['rule'][fam].keys(): + for socket_type in copy_netrules['rule'][fam].keys(): if incnetrules['rule'].get(fam, False): netrules['rule'][fam].pop(socket_type) deleted += 1 @@ -3030,8 +3030,12 @@ def write_single(prof_data, depth, allow, name, prefix, tail): def set_allow_str(allow): if allow == 'deny': return 'deny ' - else: + elif allow == 'allow': return 'allow ' + elif allow == '': + return '' + else: + raise AppArmorException(_("Invalid allow string: %(allow)s")) def set_ref_allow(prof_data, allow): if allow: From a8a19da607f91d239cc3b346355fba8edd686046 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 23 Sep 2013 23:05:25 +0530 Subject: [PATCH 080/183] Fixes netrule deletion for includes --- Tools/aa-mergeprof | 68 +++++++++++++++++++++++++++++++++++++++++++++- apparmor/aa.py | 9 ++++-- apparmor/tools.py | 1 - 3 files changed, 73 insertions(+), 5 deletions(-) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index ff9956fd1..461cf9a79 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -97,6 +97,36 @@ class Merge(object): base_other = cleanprofile.CleanProf(False, self.base, self.other) deleted += user_base.compare_profiles() + def conflict_mode(self, profile, hat, allow, path, mode, new_mode, old_mode): + conflict_modes = set('uUpPcCiIxX') + conflict_x= (old_mode | mode) & conflict_modes + if conflict_x: + #We may have conflicting x modes + if conflict_x & set('x'): + conflict_x.remove('x') + if conflict_x & set('X'): + conflict_x.remove('X') + if len(conflict_x) > 1: + q = apparmor.aa.hasher() + q['headers'] = [_('Path'), path] + q['headers'] += [_('Select the appropriate mode'), ''] + options = [] + options.append('%s: %s' %(mode, path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode((old_mode | new_mode) - (old_mode & conflict_x))))) + options.append('%s: %s' %(mode, path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode((old_mode | new_mode) - (new_mode & conflict_x))))) + q['options'] = options + q['functions'] = ['CMD_ALLOW', 'CMD_ABORT'] + done = False + while not done: + ans, selected = apparmor.aa.UI_PromptUser(q) + if ans == 'CMD_ALLOW': + if selection == 0: + self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (old_mode & conflict_x) + elif selection == 1: + self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (new_mode & conflict_x) + else: + raise apparmor.aa.AppArmorException(_('Unknown selection')) + done = True + def ask_the_questions(self, other): if other == 'other': other = self.other @@ -228,6 +258,40 @@ class Merge(object): for path in sorted(other.aa[profile][hat][allow]['path'].keys()): #print(path, other.aa[profile][hat][allow]['path'][path]) mode = other.aa[profile][hat][allow]['path'][path]['mode'] + + self.conflict_mode(profile, hat, allow, path, 'mode', other.aa[profile][hat][allow]['path'][path]['mode'], self.user.aa[profile][hat][allow][path]['mode']) + self.conflict_mode(profile, hat, allow, path, 'audit', other.aa[profile][hat][allow]['path'][path]['audit'], self.user.aa[profile][hat][allow][path]['audit']) +# conflict_modes = set('uUpPcCiIxX') +# conflict_x= (old_mode | mode) & conflict_modes +# if conflict_x: +# #We may have conflicting x modes +# if conflict_x & set('x'): +# conflict_x.remove('x') +# if conflict_x & set('X'): +# conflict_x.remove('X') +# if len(a) > 1: +# q = apparmor.aa.hasher() +# q['headers'] = [_('Path'), path] +# q['headers'] += [_('Select the appropriate mode'), ''] +# options = [] +# options.append('mode: %s' %(path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode(mode)))) +# options.append('mode: %s' %(path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode(old_mode)))) +# q['options'] = options +# q['functions'] = ['CMD_ALLOW', 'CMD_ABORT'] +# done = False +# while not done: +# ans, selected = apparmor.aa.UI_PromptUser(q) +# if ans == 'CMD_ALLOW': +# if selection == 0: +# self.user.aa[profile][hat][allow][path]['mode'] = mode +# elif selection == 1: +# self.user.aa[profile][hat][allow][path]['mode'] = old_mode +# mode = old_mode +# else: +# raise apparmor.aa.AppArmorException(_('Unknown selection')) +# done = True + + # Lookup modes from profile allow_mode = set() allow_audit = set() @@ -465,7 +529,9 @@ class Merge(object): elif owner_toggle == 3: mode = apparmor.aa.owner_flatten_mode(mode) - self.user.aa[profile][hat]['allow']['path'][path]['mode'] = self.user.aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode + if not self.user.aa[profile][hat]['allow'].get(path, False): + self.user.aa[profile][hat]['allow']['path'][path]['mode'] = self.user.aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode + tmpmode = set() if audit_toggle == 1: diff --git a/apparmor/aa.py b/apparmor/aa.py index e47c7a5ea..e443c84ce 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -2036,7 +2036,7 @@ def delete_net_duplicates(netrules, incnetrules): continue else: for socket_type in copy_netrules['rule'][fam].keys(): - if incnetrules['rule'].get(fam, False): + if incnetrules['rule'][fam].get(socket_type, False): netrules['rule'][fam].pop(socket_type) deleted += 1 return deleted @@ -3288,7 +3288,7 @@ def serialize_profile(profile_data, name, options): include_flags = False if include_metadata: - string = '# Last Modified: %s\n' %time.time() + string = '# Last Modified: %s\n' %time.asctime() if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): @@ -3344,7 +3344,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): include_flags = False if include_metadata: - string = '# Last Modified: %s\n' %time.time() + string = '# Last Modified: %s\n' %time.asctime() if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): @@ -3356,6 +3356,9 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not os.path.isfile(prof_filename): raise AppArmorException(_("Can't find existing profile to modify")) + + profiles_list = filelist[prof_filename].keys() + with open_file_read(prof_filename) as f_in: profile = None hat = None diff --git a/apparmor/tools.py b/apparmor/tools.py index 41e44c3a7..252255579 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -110,7 +110,6 @@ class aa_tools: def clean_profile(self, program, p): filename = apparmor.get_profile_filename(program) - import apparmor.cleanprofile as cleanprofile prof = cleanprofile.Prof(filename) cleanprof = cleanprofile.CleanProf(True, prof, prof) From 63efd5d96a62a7c7a8bac52c55ed2580aa8663c0 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Mon, 23 Sep 2013 23:56:28 +0530 Subject: [PATCH 081/183] added handler for conflicting *x access --- Tools/aa-mergeprof | 66 ++++++++++++++++------------------------------ apparmor/aa.py | 4 +-- 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 461cf9a79..07c3a8caa 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -27,7 +27,7 @@ def main(): mergeprofiles.ask_the_questions('other') mergeprofiles.clear_common() - print("base") + mergeprofiles.ask_the_questions('base') q = apparmor.aa.hasher() @@ -98,8 +98,12 @@ class Merge(object): deleted += user_base.compare_profiles() def conflict_mode(self, profile, hat, allow, path, mode, new_mode, old_mode): + m = new_mode + o = old_mode + new_mode = apparmor.aa.flatten_mode(new_mode) + old_mode = apparmor.aa.flatten_mode(old_mode) conflict_modes = set('uUpPcCiIxX') - conflict_x= (old_mode | mode) & conflict_modes + conflict_x= (old_mode | new_mode) & conflict_modes if conflict_x: #We may have conflicting x modes if conflict_x & set('x'): @@ -111,18 +115,20 @@ class Merge(object): q['headers'] = [_('Path'), path] q['headers'] += [_('Select the appropriate mode'), ''] options = [] - options.append('%s: %s' %(mode, path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode((old_mode | new_mode) - (old_mode & conflict_x))))) - options.append('%s: %s' %(mode, path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode((old_mode | new_mode) - (new_mode & conflict_x))))) + options.append('%s: %s' %(mode, apparmor.aa.mode_to_str_user(new_mode)))# - (old_mode & conflict_x)))) + options.append('%s: %s' %(mode, apparmor.aa.mode_to_str_user(old_mode)))#(old_mode | new_mode) - (new_mode & conflict_x)))) q['options'] = options q['functions'] = ['CMD_ALLOW', 'CMD_ABORT'] done = False while not done: ans, selected = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_ALLOW': - if selection == 0: - self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (old_mode & conflict_x) - elif selection == 1: - self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (new_mode & conflict_x) + if selected == 0: + self.user.aa[profile][hat][allow]['path'][path][mode] = m#apparmor.aa.owner_flatten_mode(new_mode)#(old_mode | new_mode) - (old_mode & conflict_x) + return m + elif selected == 1: + return o + pass#self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (new_mode & conflict_x) else: raise apparmor.aa.AppArmorException(_('Unknown selection')) done = True @@ -259,39 +265,11 @@ class Merge(object): #print(path, other.aa[profile][hat][allow]['path'][path]) mode = other.aa[profile][hat][allow]['path'][path]['mode'] - self.conflict_mode(profile, hat, allow, path, 'mode', other.aa[profile][hat][allow]['path'][path]['mode'], self.user.aa[profile][hat][allow][path]['mode']) - self.conflict_mode(profile, hat, allow, path, 'audit', other.aa[profile][hat][allow]['path'][path]['audit'], self.user.aa[profile][hat][allow][path]['audit']) -# conflict_modes = set('uUpPcCiIxX') -# conflict_x= (old_mode | mode) & conflict_modes -# if conflict_x: -# #We may have conflicting x modes -# if conflict_x & set('x'): -# conflict_x.remove('x') -# if conflict_x & set('X'): -# conflict_x.remove('X') -# if len(a) > 1: -# q = apparmor.aa.hasher() -# q['headers'] = [_('Path'), path] -# q['headers'] += [_('Select the appropriate mode'), ''] -# options = [] -# options.append('mode: %s' %(path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode(mode)))) -# options.append('mode: %s' %(path, apparmor.aa.mode_to_str_user(apparmor.aa.flatten_mode(old_mode)))) -# q['options'] = options -# q['functions'] = ['CMD_ALLOW', 'CMD_ABORT'] -# done = False -# while not done: -# ans, selected = apparmor.aa.UI_PromptUser(q) -# if ans == 'CMD_ALLOW': -# if selection == 0: -# self.user.aa[profile][hat][allow][path]['mode'] = mode -# elif selection == 1: -# self.user.aa[profile][hat][allow][path]['mode'] = old_mode -# mode = old_mode -# else: -# raise apparmor.aa.AppArmorException(_('Unknown selection')) -# done = True - - + if self.user.aa[profile][hat][allow]['path'].get(path, False): + mode = self.conflict_mode(profile, hat, allow, path, 'mode', other.aa[profile][hat][allow]['path'][path]['mode'], self.user.aa[profile][hat][allow]['path'][path]['mode']) + self.conflict_mode(profile, hat, allow, path, 'audit', other.aa[profile][hat][allow]['path'][path]['audit'], self.user.aa[profile][hat][allow]['path'][path]['audit']) + apparmor.aa.changed[profile] = True + continue # Lookup modes from profile allow_mode = set() allow_audit = set() @@ -351,10 +329,10 @@ class Merge(object): matches = [] if fmode: - apparmor.aa.matches.append(fm) + matches += fm if imode: - apparmor.aa.matches.append(im) + matches += im if not apparmor.aa.mode_contains(allow_mode, mode): default_option = 1 @@ -601,7 +579,7 @@ class Merge(object): for allow in ['allow', 'deny']: for family in sorted(other.aa[profile][hat][allow]['netdomain']['rule'].keys()): # severity handling for net toggles goes here - print(family) + for sock_type in sorted(other.aa[profile][hat][allow]['netdomain']['rule'][family].keys()): if apparmor.aa.profile_known_network(self.user.aa[profile][hat], family, sock_type): continue diff --git a/apparmor/aa.py b/apparmor/aa.py index e443c84ce..e721565e0 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1635,10 +1635,10 @@ def ask_the_questions(): matches = [] if fmode: - matches.append(fm) + matches.append += fm if imode: - matches.append(im) + matches.append += im if not mode_contains(allow_mode, mode): default_option = 1 From 72e0aac5517f7c1a96a85af1fc9495c5b39d1599 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 24 Sep 2013 00:02:26 +0530 Subject: [PATCH 082/183] Final push for GSoC 2013 (hopefully) --- Translate/messages.pot | 96 ++++++++++++++++++++++++------------------ apparmor/aa.py | 1 - 2 files changed, 54 insertions(+), 43 deletions(-) diff --git a/Translate/messages.pot b/Translate/messages.pot index ce3df5153..e38a598db 100755 --- a/Translate/messages.pot +++ b/Translate/messages.pot @@ -1,11 +1,11 @@ # SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ORGANIZATION -# FIRST AUTHOR , YEAR. +# Copyright (C) 2013 +# Kshitij Gupta , 2013. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2013-09-23 19:39+IST\n" +"POT-Creation-Date: 2013-09-23 23:59+IST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -181,118 +181,126 @@ msgstr "" msgid "The following local profiles were changed. Would you like to save them?" msgstr "" -#: ../Tools/aa-mergeprof:114 ../Tools/aa-mergeprof:141 +#: ../Tools/aa-mergeprof:115 ../Tools/aa-mergeprof:398 ../apparmor/aa.py:1704 +msgid "Path" +msgstr "" + +#: ../Tools/aa-mergeprof:116 +msgid "Select the appropriate mode" +msgstr "" + +#: ../Tools/aa-mergeprof:133 +msgid "Unknown selection" +msgstr "" + +#: ../Tools/aa-mergeprof:150 ../Tools/aa-mergeprof:177 msgid "File includes" msgstr "" -#: ../Tools/aa-mergeprof:114 ../Tools/aa-mergeprof:141 +#: ../Tools/aa-mergeprof:150 ../Tools/aa-mergeprof:177 msgid "Select the ones you wish to add" msgstr "" -#: ../Tools/aa-mergeprof:126 ../Tools/aa-mergeprof:154 +#: ../Tools/aa-mergeprof:162 ../Tools/aa-mergeprof:190 msgid "Adding %s to the file." msgstr "" -#: ../Tools/aa-mergeprof:131 ../apparmor/aa.py:2185 +#: ../Tools/aa-mergeprof:167 ../apparmor/aa.py:2185 msgid "unknown" msgstr "" -#: ../Tools/aa-mergeprof:156 ../Tools/aa-mergeprof:207 -#: ../Tools/aa-mergeprof:442 ../Tools/aa-mergeprof:482 -#: ../Tools/aa-mergeprof:599 ../apparmor/aa.py:1557 ../apparmor/aa.py:1792 +#: ../Tools/aa-mergeprof:192 ../Tools/aa-mergeprof:243 +#: ../Tools/aa-mergeprof:484 ../Tools/aa-mergeprof:526 +#: ../Tools/aa-mergeprof:643 ../apparmor/aa.py:1557 ../apparmor/aa.py:1792 #: ../apparmor/aa.py:1832 ../apparmor/aa.py:1951 msgid "Deleted %s previous matching profile entries." msgstr "" -#: ../Tools/aa-mergeprof:176 ../Tools/aa-mergeprof:355 -#: ../Tools/aa-mergeprof:553 ../Tools/aa-mergeprof:580 ../apparmor/aa.py:944 +#: ../Tools/aa-mergeprof:212 ../Tools/aa-mergeprof:397 +#: ../Tools/aa-mergeprof:597 ../Tools/aa-mergeprof:624 ../apparmor/aa.py:944 #: ../apparmor/aa.py:1200 ../apparmor/aa.py:1504 ../apparmor/aa.py:1540 #: ../apparmor/aa.py:1703 ../apparmor/aa.py:1902 ../apparmor/aa.py:1933 msgid "Profile" msgstr "" -#: ../Tools/aa-mergeprof:177 ../apparmor/aa.py:1505 ../apparmor/aa.py:1541 +#: ../Tools/aa-mergeprof:213 ../apparmor/aa.py:1505 ../apparmor/aa.py:1541 msgid "Capability" msgstr "" -#: ../Tools/aa-mergeprof:178 ../Tools/aa-mergeprof:406 ../apparmor/aa.py:1206 +#: ../Tools/aa-mergeprof:214 ../Tools/aa-mergeprof:448 ../apparmor/aa.py:1206 #: ../apparmor/aa.py:1506 ../apparmor/aa.py:1542 ../apparmor/aa.py:1754 msgid "Severity" msgstr "" -#: ../Tools/aa-mergeprof:205 ../Tools/aa-mergeprof:440 ../apparmor/aa.py:1555 +#: ../Tools/aa-mergeprof:241 ../Tools/aa-mergeprof:482 ../apparmor/aa.py:1555 #: ../apparmor/aa.py:1790 msgid "Adding %s to profile." msgstr "" -#: ../Tools/aa-mergeprof:214 ../apparmor/aa.py:1564 +#: ../Tools/aa-mergeprof:250 ../apparmor/aa.py:1564 msgid "Adding capability %s to profile." msgstr "" -#: ../Tools/aa-mergeprof:221 ../apparmor/aa.py:1571 +#: ../Tools/aa-mergeprof:257 ../apparmor/aa.py:1571 msgid "Denying capability %s to profile." msgstr "" -#: ../Tools/aa-mergeprof:356 ../apparmor/aa.py:1704 -msgid "Path" -msgstr "" - -#: ../Tools/aa-mergeprof:365 ../Tools/aa-mergeprof:396 ../apparmor/aa.py:1713 +#: ../Tools/aa-mergeprof:407 ../Tools/aa-mergeprof:438 ../apparmor/aa.py:1713 #: ../apparmor/aa.py:1744 msgid "(owner permissions off)" msgstr "" -#: ../Tools/aa-mergeprof:370 ../apparmor/aa.py:1718 +#: ../Tools/aa-mergeprof:412 ../apparmor/aa.py:1718 msgid "(force new perms to owner)" msgstr "" -#: ../Tools/aa-mergeprof:373 ../apparmor/aa.py:1721 +#: ../Tools/aa-mergeprof:415 ../apparmor/aa.py:1721 msgid "(force all rule perms to owner)" msgstr "" -#: ../Tools/aa-mergeprof:385 ../apparmor/aa.py:1733 +#: ../Tools/aa-mergeprof:427 ../apparmor/aa.py:1733 msgid "Old Mode" msgstr "" -#: ../Tools/aa-mergeprof:386 ../apparmor/aa.py:1734 +#: ../Tools/aa-mergeprof:428 ../apparmor/aa.py:1734 msgid "New Mode" msgstr "" -#: ../Tools/aa-mergeprof:401 ../apparmor/aa.py:1749 +#: ../Tools/aa-mergeprof:443 ../apparmor/aa.py:1749 msgid "(force perms to owner)" msgstr "" -#: ../Tools/aa-mergeprof:404 ../apparmor/aa.py:1752 +#: ../Tools/aa-mergeprof:446 ../apparmor/aa.py:1752 msgid "Mode" msgstr "" -#: ../Tools/aa-mergeprof:480 ../apparmor/aa.py:1830 +#: ../Tools/aa-mergeprof:524 ../apparmor/aa.py:1830 msgid "Adding %s %s to profile" msgstr "" -#: ../Tools/aa-mergeprof:498 ../apparmor/aa.py:1848 +#: ../Tools/aa-mergeprof:542 ../apparmor/aa.py:1848 msgid "Enter new path: " msgstr "" -#: ../Tools/aa-mergeprof:554 ../Tools/aa-mergeprof:581 ../apparmor/aa.py:1903 +#: ../Tools/aa-mergeprof:598 ../Tools/aa-mergeprof:625 ../apparmor/aa.py:1903 #: ../apparmor/aa.py:1934 msgid "Network Family" msgstr "" -#: ../Tools/aa-mergeprof:555 ../Tools/aa-mergeprof:582 ../apparmor/aa.py:1904 +#: ../Tools/aa-mergeprof:599 ../Tools/aa-mergeprof:626 ../apparmor/aa.py:1904 #: ../apparmor/aa.py:1935 msgid "Socket Type" msgstr "" -#: ../Tools/aa-mergeprof:597 ../apparmor/aa.py:1949 +#: ../Tools/aa-mergeprof:641 ../apparmor/aa.py:1949 msgid "Adding %s to profile" msgstr "" -#: ../Tools/aa-mergeprof:607 ../apparmor/aa.py:1959 +#: ../Tools/aa-mergeprof:651 ../apparmor/aa.py:1959 msgid "Adding network access %s %s to profile." msgstr "" -#: ../Tools/aa-mergeprof:613 ../apparmor/aa.py:1965 +#: ../Tools/aa-mergeprof:657 ../apparmor/aa.py:1965 msgid "Denying network access %s %s to profile" msgstr "" @@ -622,19 +630,23 @@ msgstr "" msgid "Unknown variable operation: %s" msgstr "" -#: ../apparmor/aa.py:3354 +#: ../apparmor/aa.py:3038 +msgid "Invalid allow string: %(allow)s" +msgstr "" + +#: ../apparmor/aa.py:3358 msgid "Can't find existing profile to modify" msgstr "" -#: ../apparmor/aa.py:3856 +#: ../apparmor/aa.py:3863 msgid "Writing updated profile for %s." msgstr "" -#: ../apparmor/aa.py:3990 +#: ../apparmor/aa.py:3997 msgid "File Not Found: %s" msgstr "" -#: ../apparmor/aa.py:4099 +#: ../apparmor/aa.py:4106 msgid "" "%s is currently marked as a program that should not have its own\n" "profile. Usually, programs are marked this way if creating a profile for \n" @@ -675,17 +687,17 @@ msgstr "" msgid "Removing audit mode from %s." msgstr "" -#: ../apparmor/tools.py:118 +#: ../apparmor/tools.py:117 msgid "" "\n" "Deleted %s rules." msgstr "" -#: ../apparmor/tools.py:126 +#: ../apparmor/tools.py:125 msgid "The local profile for %s in file %s was changed. Would you like to save it?" msgstr "" -#: ../apparmor/tools.py:147 +#: ../apparmor/tools.py:146 msgid "The profile for %s does not exists. Nothing to clean." msgstr "" diff --git a/apparmor/aa.py b/apparmor/aa.py index e721565e0..9e50430b9 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,5 +1,4 @@ # No old version logs, only 2.6 + supported -#global variable names corruption from __future__ import with_statement import inspect import os From be6338863801cabfe79578f99f206492c7478147 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 24 Sep 2013 00:21:47 +0530 Subject: [PATCH 083/183] remove the allow prefix from rules --- apparmor/aa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 9e50430b9..0f69f28d5 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -3030,7 +3030,7 @@ def set_allow_str(allow): if allow == 'deny': return 'deny ' elif allow == 'allow': - return 'allow ' + return '' elif allow == '': return '' else: From 7cccd1fae506fe11bfcd1b1df626e2f299efa23b Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 24 Sep 2013 00:34:09 +0530 Subject: [PATCH 084/183] fixed test for cleanprof --- Testing/cleanprof_test.out | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Testing/cleanprof_test.out b/Testing/cleanprof_test.out index 34c1b5453..db102c135 100644 --- a/Testing/cleanprof_test.out +++ b/Testing/cleanprof_test.out @@ -6,12 +6,12 @@ /usr/bin/a/simple/cleanprof/test/profile { #include - allow /home/*/** r, - allow /home/foo/** w, + /home/*/** r, + /home/foo/** w, } /usr/bin/other/cleanprof/test/profile { - allow /home/*/** rw, - allow /home/foo/bar r, + /home/*/** rw, + /home/foo/bar r, } From 173d8fca001d535a101b2e9c5954eddc1c16464c Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Thu, 26 Sep 2013 18:41:41 +0530 Subject: [PATCH 085/183] Fixes the TypeError associated with Python3 in calling netstat in aa-unconfined --- Tools/aa-unconfined | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index 131fb622a..cd915a48a 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -3,6 +3,7 @@ import argparse import os import re +import sys import apparmor.aa as apparmor @@ -22,7 +23,11 @@ if paranoid: else: regex_tcp_udp = re.compile('^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)') import subprocess - output = subprocess.check_output('LANG=C netstat -nlp', shell=True).split('\n') + if sys.version_info < (3,0): + output = subprocess.check_output('LANG=C netstat -nlp', shell=True).split('\n') + else: + #Python3 needs to translate a stream of bytes to string with specified encoding + output = str(subprocess.check_output('LANG=C netstat -nlp', shell=True), encoding='utf8').split('\n') for line in output: match = regex_tcp_udp.search(line) From 4f8c524839d03ff2d1d5609cd1d5e10a81726446 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 28 Sep 2013 20:43:06 +0530 Subject: [PATCH 086/183] Added license headers --- Testing/aa_test.py | 13 +++++++++++++ Testing/common_test.py | 13 +++++++++++++ Testing/config_test.py | 13 +++++++++++++ Testing/minitools_test.py | 13 +++++++++++++ Testing/regex_tests.ini | 13 +++++++++++++ Testing/severity_test.py | 13 +++++++++++++ Tools/aa-audit | 14 +++++++++++++- Tools/aa-autodep | 14 +++++++++++++- Tools/aa-cleanprof | 14 +++++++++++++- Tools/aa-complain | 14 +++++++++++++- Tools/aa-disable | 14 +++++++++++++- Tools/aa-enforce | 14 +++++++++++++- Tools/aa-genprof | 14 +++++++++++++- Tools/aa-logprof | 14 +++++++++++++- Tools/aa-mergeprof | 14 +++++++++++++- Tools/aa-unconfined | 14 +++++++++++++- Translate/messages.pot | 6 +++--- apparmor/__init__.py | 10 ++++++++++ apparmor/aa.py | 13 +++++++++++++ apparmor/aamode.py | 13 +++++++++++++ apparmor/cleanprofile.py | 13 +++++++++++++ apparmor/common.py | 10 ++++++++++ apparmor/config.py | 13 +++++++++++++ apparmor/logparser.py | 13 +++++++++++++ apparmor/severity.py | 13 +++++++++++++ apparmor/tools.py | 13 +++++++++++++ apparmor/ui.py | 14 +++++++++++++- apparmor/yasti.py | 13 +++++++++++++ 28 files changed, 348 insertions(+), 14 deletions(-) diff --git a/Testing/aa_test.py b/Testing/aa_test.py index 75653699f..34808f812 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 import sys diff --git a/Testing/common_test.py b/Testing/common_test.py index 4a69b906d..aaf1c744e 100644 --- a/Testing/common_test.py +++ b/Testing/common_test.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 import re import sys diff --git a/Testing/config_test.py b/Testing/config_test.py index 324582271..adf633e88 100644 --- a/Testing/config_test.py +++ b/Testing/config_test.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 import sys diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 36eaf5064..129e0688e 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 atexit import os import shutil diff --git a/Testing/regex_tests.ini b/Testing/regex_tests.ini index 53094e48d..99c954054 100644 --- a/Testing/regex_tests.ini +++ b/Testing/regex_tests.ini @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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. +# +# ---------------------------------------------------------------------- [/foo/**/bar/] /foo/user/tools/bar/ = True /foo/apparmor/bar/ = True diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 15de54e61..7856fe511 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 os import shutil import sys diff --git a/Tools/aa-audit b/Tools/aa-audit index 29446f800..ea5a0e1ac 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import apparmor.tools diff --git a/Tools/aa-autodep b/Tools/aa-autodep index 41a73ac32..bb468ffcf 100644 --- a/Tools/aa-autodep +++ b/Tools/aa-autodep @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import apparmor.tools diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index c90f61ab7..da5904264 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import apparmor.tools diff --git a/Tools/aa-complain b/Tools/aa-complain index 578957830..7c9596e6c 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import apparmor.tools diff --git a/Tools/aa-disable b/Tools/aa-disable index 91df90220..96ab16f5e 100644 --- a/Tools/aa-disable +++ b/Tools/aa-disable @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import apparmor.tools diff --git a/Tools/aa-enforce b/Tools/aa-enforce index c480f76a3..223dfee11 100644 --- a/Tools/aa-enforce +++ b/Tools/aa-enforce @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import apparmor.tools diff --git a/Tools/aa-genprof b/Tools/aa-genprof index b46e03e6b..ddcc81ac7 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import atexit import os diff --git a/Tools/aa-logprof b/Tools/aa-logprof index cf5d6e60e..5294467ed 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import os diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 07c3a8caa..3a08605bc 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import sys diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index cd915a48a..64a06377f 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -1,5 +1,17 @@ #!/usr/bin/python - +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 argparse import os import re diff --git a/Translate/messages.pot b/Translate/messages.pot index e38a598db..68a4dcc7d 100755 --- a/Translate/messages.pot +++ b/Translate/messages.pot @@ -1,5 +1,5 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) 2013 +# Translation Messages for AppArmor Profile Tools +# Copyright (C) 2013 Kshitij Gupta # Kshitij Gupta , 2013. # msgid "" @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: 2013-09-23 23:59+IST\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" +"Last-Translator: Kshitij Gupta \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 8588e00fd..133016751 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -1,3 +1,13 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2011-2012 Canonical Ltd. +# Copyright (C) 22013 Kshitij Gupta +# +# 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 gettext import locale diff --git a/apparmor/aa.py b/apparmor/aa.py index 0f69f28d5..13bd9ea77 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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. +# +# ---------------------------------------------------------------------- # No old version logs, only 2.6 + supported from __future__ import with_statement import inspect diff --git a/apparmor/aamode.py b/apparmor/aamode.py index d02e3ac88..9d4ded620 100644 --- a/apparmor/aamode.py +++ b/apparmor/aamode.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 def AA_OTHER(mode): diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py index 0d7c64be7..bfe83abc6 100644 --- a/apparmor/cleanprofile.py +++ b/apparmor/cleanprofile.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 import copy diff --git a/apparmor/common.py b/apparmor/common.py index 2836d3846..235e8814a 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -1,3 +1,13 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2012 Canonical Ltd. +# Copyright (C) 2013 Kshitij Gupta +# +# 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. +# +# ------------------------------------------------------------------ from __future__ import print_function import codecs import collections diff --git a/apparmor/config.py b/apparmor/config.py index bd5144f15..14cba5102 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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. +# +# ---------------------------------------------------------------------- from __future__ import with_statement import os import shlex diff --git a/apparmor/logparser.py b/apparmor/logparser.py index e0232d735..1db358ceb 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 os import re import sys diff --git a/apparmor/severity.py b/apparmor/severity.py index 673951e90..a1b6ad6b7 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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. +# +# ---------------------------------------------------------------------- from __future__ import with_statement import os import re diff --git a/apparmor/tools.py b/apparmor/tools.py index 252255579..3c4c77f46 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 os import sys diff --git a/apparmor/ui.py b/apparmor/ui.py index 747510c7f..486eef0a1 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -1,4 +1,16 @@ -# 1728 +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 sys import os import re diff --git a/apparmor/yasti.py b/apparmor/yasti.py index a3ab1af2a..93aaf8083 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -1,3 +1,16 @@ +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# +# 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 #import ycp import os From f3e549e772013b0b8bcf1ac84d42cca6db8822b2 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 28 Sep 2013 20:47:45 +0530 Subject: [PATCH 087/183] fixed 22013 to 2013 in __init__.py license --- apparmor/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 133016751..68c263bba 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -1,7 +1,7 @@ # ------------------------------------------------------------------ # # Copyright (C) 2011-2012 Canonical Ltd. -# Copyright (C) 22013 Kshitij Gupta +# Copyright (C) 2013 Kshitij Gupta # # This program is free software; you can redistribute it and/or # modify it under the terms of version 2 of the GNU General Public From 9bbf089634037d9e377c3e07fbbc8014443aeaa4 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 1 Oct 2013 01:30:50 +0530 Subject: [PATCH 088/183] some fixed bugs --- apparmor/aa.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 13bd9ea77..b56b0e928 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -911,6 +911,7 @@ def handle_children(profile, hat, root): family = None sock_type = None protocol = None + global seen_events regex_nullcomplain = re.compile('^null(-complain)*-profile$') for entry in entries: @@ -1070,7 +1071,7 @@ def handle_children(profile, hat, root): context_new = profile if profile != hat: context_new = context_new + '^%s' % hat - context_new = context + ' ->%s' % exec_target + context_new = context_new + ' -> %s' % exec_target ans_new = transitions.get(context_new, '') combinedmode = set() @@ -1218,20 +1219,21 @@ def handle_children(profile, hat, root): q['headers'] += [_('Severity'), severity] q['functions'] = [] - prompt = '\n%s\n' % context + prompt = '\n%s\n' % context_new exec_toggle = False - q['functions'].append(build_x_functions(default, options, exec_toggle)) + q['functions'] += build_x_functions(default, options, exec_toggle) options = '|'.join(options) seen_events += 1 regex_options = re.compile('^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$') - while regex_options.search(ans): - ans = UI_PromptUser(q).strip() + ans = '' + while not regex_options.search(ans): + ans = UI_PromptUser(q)[0].strip() if ans.startswith('CMD_EXEC_IX_'): exec_toggle = not exec_toggle q['functions'] = [] - q['functions'].append(build_x_functions(default, options, exec_toggle)) + q['functions'] += build_x_functions(default, options, exec_toggle) ans = '' continue if ans == 'CMD_nx' or ans == 'CMD_nix': @@ -1277,7 +1279,7 @@ def handle_children(profile, hat, root): exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) else: ans = 'INVALID' - transitions[context] = ans + transitions[context_new] = ans regex_options = re.compile('CMD_(ix|px|cx|nx|pix|cix|nix)') if regex_options.search(ans): @@ -1573,7 +1575,7 @@ def ask_the_questions(): changed[profile] = True - UI_Info(_('Adding capability %s to profile.'), capability) + UI_Info(_('Adding capability %s to profile.') % capability) done = True elif ans == 'CMD_DENY': @@ -1647,10 +1649,10 @@ def ask_the_questions(): matches = [] if fmode: - matches.append += fm + matches += fm if imode: - matches.append += im + matches += im if not mode_contains(allow_mode, mode): default_option = 1 From aa0a24a0f1c591975f5b483bfe7437e432d08ede Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 22 Oct 2013 03:06:23 +0530 Subject: [PATCH 089/183] Fixes the application level translations, the module level translation in __init__.py become reduntant though as app level covers them. Besides added the feature to allow use of arrow keys for UI_YesNo. Added README.md to store the list of known bugs. --- README.md | 4 ++++ Testing/minitools_test.py | 11 +++++++++++ Tools/aa-audit | 3 +++ Tools/aa-autodep | 3 +++ Tools/aa-cleanprof | 3 +++ Tools/aa-complain | 3 +++ Tools/aa-disable | 3 +++ Tools/aa-enforce | 3 +++ Tools/aa-genprof | 3 +++ Tools/aa-logprof | 4 ++++ Tools/aa-mergeprof | 3 +++ Tools/aa-unconfined | 17 +++++++++++++++++ apparmor/__init__.py | 5 +++-- apparmor/aa.py | 3 ++- apparmor/common.py | 7 +++++++ apparmor/ui.py | 14 ++++++++++---- 16 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..8de45fada --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Known Bugs: +Will allow multiple letters in the () due to translation/unicode issues with regexing the key. +User input will probably bug out in a different locale. +Moving the arrow keys to select or de-select option in yes and no doesn't work. diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 129e0688e..51013e528 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -105,6 +105,17 @@ class Test(unittest.TestCase): def test_autodep(self): pass + + def test_unconfined(self): + output = subprocess.check_output('%s ./../Tools/aa-unconfined'%python_interpreter, shell=True) + + output_force = subprocess.check_output('%s ./../Tools/aa-unconfined --paranoid'%python_interpreter, shell=True) + + self.assertIsNot(output, '', 'Failed to run aa-unconfined') + + self.assertIsNot(output_force, '', 'Failed to run aa-unconfined in paranoid mode') + + def test_cleanprof(self): input_file = 'cleanprof_test.in' diff --git a/Tools/aa-audit b/Tools/aa-audit index ea5a0e1ac..e947231ee 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -14,6 +14,9 @@ # ---------------------------------------------------------------------- import argparse +from apparmor.common import init_translations +init_translations() + import apparmor.tools parser = argparse.ArgumentParser(description=_('Switch the given programs to audit mode')) diff --git a/Tools/aa-autodep b/Tools/aa-autodep index bb468ffcf..8f789db14 100644 --- a/Tools/aa-autodep +++ b/Tools/aa-autodep @@ -14,6 +14,9 @@ # ---------------------------------------------------------------------- import argparse +from apparmor.common import init_translations +init_translations() + import apparmor.tools parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements')) diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index da5904264..cc70f0aec 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -14,6 +14,9 @@ # ---------------------------------------------------------------------- import argparse +from apparmor.common import init_translations +init_translations() + import apparmor.tools parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs')) diff --git a/Tools/aa-complain b/Tools/aa-complain index 7c9596e6c..e0a622bb9 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -14,6 +14,9 @@ # ---------------------------------------------------------------------- import argparse +from apparmor.common import init_translations +init_translations() + import apparmor.tools parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode')) diff --git a/Tools/aa-disable b/Tools/aa-disable index 96ab16f5e..67dd46226 100644 --- a/Tools/aa-disable +++ b/Tools/aa-disable @@ -14,6 +14,9 @@ # ---------------------------------------------------------------------- import argparse +from apparmor.common import init_translations +init_translations() + import apparmor.tools parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs')) diff --git a/Tools/aa-enforce b/Tools/aa-enforce index 223dfee11..8d427f155 100644 --- a/Tools/aa-enforce +++ b/Tools/aa-enforce @@ -14,6 +14,9 @@ # ---------------------------------------------------------------------- import argparse +from apparmor.common import init_translations +init_translations() + import apparmor.tools parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode')) diff --git a/Tools/aa-genprof b/Tools/aa-genprof index ddcc81ac7..201cf833f 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -19,6 +19,9 @@ import re import subprocess import sys +from apparmor.common import init_translations +init_translations() + import apparmor.aa as apparmor def sysctl_read(path): diff --git a/Tools/aa-logprof b/Tools/aa-logprof index 5294467ed..d3d591dac 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -15,6 +15,9 @@ import argparse import os +from apparmor.common import init_translations +init_translations() + import apparmor.aa as apparmor parser = argparse.ArgumentParser(description=_('Process log entries to generate profiles')) @@ -27,6 +30,7 @@ profiledir = args.dir filename = args.file logmark = args.mark or '' + aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 3a08605bc..255c96dea 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -15,6 +15,9 @@ import argparse import sys +from apparmor.common import init_translations +init_translations() + import apparmor.aa import apparmor.aamode import apparmor.severity diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index 64a06377f..e7ed5fb1b 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -13,12 +13,29 @@ # # ---------------------------------------------------------------------- import argparse +import gettext +import locale import os import re import sys +from apparmor.common import init_translations +init_translations() + import apparmor.aa as apparmor +#gettext.bindtextdomain('apparmor-utils', '/usr/share/locale/')#/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0]) +#gettext.textdomain('apparmor-utils') +#_ = gettext.gettext +#gettext.translation('apparmor-utils','./Trans/') +#gettext.install('apparmor-utils') +#print(os.path.join('/usr/share/locale', locale.getlocale()[0], 'LC_MESSAGES', '%s.mo' % +# 'apparmor-utils')) +#_ = gettext.translation('apparmor-utils', '/usr/share/locale', [locale.getlocale()[0]]).gettext + +#gettext.find + + parser = argparse.ArgumentParser(description=_('Lists unconfined processes having tcp or udp ports')) parser.add_argument('--paranoid', action='store_true', help=_('scan all processes from /proc')) args = parser.parse_args() diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 68c263bba..22f860868 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -10,15 +10,16 @@ # ------------------------------------------------------------------ import gettext import locale - + def init_localisation(): locale.setlocale(locale.LC_ALL, '') #If a correct locale has been provided set filename else let an IOError be raised filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0] try: trans = gettext.GNUTranslations(open(filename, 'rb')) + print("Locale installed for %s"%locale.getlocale()[0]) except IOError: trans = gettext.NullTranslations() trans.install() - + init_localisation() \ No newline at end of file diff --git a/apparmor/aa.py b/apparmor/aa.py index b56b0e928..b71effbea 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -13,6 +13,7 @@ # ---------------------------------------------------------------------- # No old version logs, only 2.6 + supported from __future__ import with_statement +import codecs import inspect import os import re @@ -112,7 +113,7 @@ def check_for_LD_XXX(file): # Limit to checking files under 100k for the sake of speed if size >100000: return False - with open_file_read(file) as f_in: + with codecs.open(file, 'r', encoding='ascii') as f_in: for line in f_in: if 'LD_PRELOAD' in line or 'LD_LIBRARY_PATH' in line: found = True diff --git a/apparmor/common.py b/apparmor/common.py index 235e8814a..8214b3798 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -11,6 +11,7 @@ from __future__ import print_function import codecs import collections +import gettext import glob import logging import os @@ -165,6 +166,12 @@ def hasher(): # Creates a dictionary for any depth and returns empty dictionary otherwise return collections.defaultdict(hasher) +def init_translations(domain='apparmor-utils'): + """Installs the translations for the given domain, defaults to apparmor-utils domain""" + #Setup Translation + gettext.translation(domain, fallback=True) + gettext.install(domain) + def convert_regexp(regexp): regex_paren = re.compile('^(.*){([^}]*)}(.*)$') diff --git a/apparmor/ui.py b/apparmor/ui.py index 486eef0a1..5bc08368a 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -34,7 +34,7 @@ def getkey(): key = readkey() if(ARROWS.get(key, False)): key = ARROWS[key] - return key + return key.strip() def UI_Info(text): debug_logger.info(text) @@ -56,8 +56,10 @@ def UI_Important(text): def get_translated_hotkey(translated, cmsg=''): msg = 'PromptUser: '+_('Invalid hotkey for') - if re.search('\((\S)\)', translated): - return re.search('\((\S)\)', translated).groups()[0] + + # Originally (\S) was used but with translations it would not work :( + if re.search('\((\S*)\)', translated, re.LOCALE): + return re.search('\((\S*)\)', translated, re.LOCALE).groups()[0] else: if cmsg: raise AppArmorException(cmsg) @@ -86,8 +88,12 @@ def UI_YesNo(text, default): ans = ans.lower() if ans == yeskey: ans = 'y' - else: + elif ans == nokey: ans = 'n' + elif ans == 'left': + default = 'y' + elif ans == 'right': + default = 'n' else: ans = default From eb61520753284d781eeeb44a4357b58facc37545 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Tue, 22 Oct 2013 03:09:31 +0530 Subject: [PATCH 090/183] Added left right arrow use to UI_YesNoCancel --- apparmor/ui.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/apparmor/ui.py b/apparmor/ui.py index 5bc08368a..e1655eae2 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -138,8 +138,18 @@ def UI_YesNoCancel(text, default): ans = 'y' elif ans == nokey: ans = 'n' - elif ans== cancelkey: + elif ans == cancelkey: ans= 'c' + elif ans == 'left': + if default == 'n': + default = 'y' + elif default == 'c': + default = 'n' + elif ans == 'right': + if default == 'y': + default = 'n' + elif default == 'n': + default = 'c' else: ans = default else: From 42ea5f4f6744df15484befcfa0ffbab5a2a37475 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Fri, 20 Dec 2013 03:12:58 +0530 Subject: [PATCH 091/183] Added read from custom logfile feature and some other older changes I sadly dont remember --- README.md | 1 - Testing/aa_test.py | 2 +- Testing/common_test.py | 4 +-- Testing/config_test.py | 2 +- Testing/minitools_test.py | 15 +++++---- Testing/severity_test.py | 58 +++++++++++++++++----------------- Tools/aa-genprof | 6 ++++ Tools/aa-logprof | 5 +++ Tools/aa-unconfined | 66 +++++++++++++++------------------------ apparmor/__init__.py | 7 ++--- apparmor/aa.py | 3 +- apparmor/common.py | 4 +-- 12 files changed, 83 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 8de45fada..f7b0f4827 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ Known Bugs: Will allow multiple letters in the () due to translation/unicode issues with regexing the key. User input will probably bug out in a different locale. -Moving the arrow keys to select or de-select option in yes and no doesn't work. diff --git a/Testing/aa_test.py b/Testing/aa_test.py index 34808f812..e8849efa0 100644 --- a/Testing/aa_test.py +++ b/Testing/aa_test.py @@ -146,4 +146,4 @@ class Test(unittest.TestCase): if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file + unittest.main() diff --git a/Testing/common_test.py b/Testing/common_test.py index aaf1c744e..1bb9c66bb 100644 --- a/Testing/common_test.py +++ b/Testing/common_test.py @@ -23,7 +23,7 @@ class Test(unittest.TestCase): def test_RegexParser(self): - tests=apparmor.config.Config('ini') + tests = apparmor.config.Config('ini') tests.CONF_DIR = '.' regex_tests = tests.read_config('regex_tests.ini') for regex in regex_tests.sections(): @@ -38,4 +38,4 @@ class Test(unittest.TestCase): if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.test_RegexParser'] - unittest.main() \ No newline at end of file + unittest.main() diff --git a/Testing/config_test.py b/Testing/config_test.py index adf633e88..2d5575542 100644 --- a/Testing/config_test.py +++ b/Testing/config_test.py @@ -49,4 +49,4 @@ class Test(unittest.TestCase): if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testConfig'] - unittest.main() \ No newline at end of file + unittest.main() diff --git a/Testing/minitools_test.py b/Testing/minitools_test.py index 51013e528..2699ffe5a 100644 --- a/Testing/minitools_test.py +++ b/Testing/minitools_test.py @@ -27,7 +27,7 @@ test_path = '/usr/sbin/ntpd' local_profilename = './profiles/usr.sbin.ntpd' python_interpreter = 'python' -if sys.version_info >= (3,0): +if sys.version_info >= (3, 0): python_interpreter = 'python3' class Test(unittest.TestCase): @@ -105,17 +105,16 @@ class Test(unittest.TestCase): def test_autodep(self): pass - + def test_unconfined(self): output = subprocess.check_output('%s ./../Tools/aa-unconfined'%python_interpreter, shell=True) - + output_force = subprocess.check_output('%s ./../Tools/aa-unconfined --paranoid'%python_interpreter, shell=True) - + self.assertIsNot(output, '', 'Failed to run aa-unconfined') - + self.assertIsNot(output_force, '', 'Failed to run aa-unconfined in paranoid mode') - - + def test_cleanprof(self): input_file = 'cleanprof_test.in' @@ -147,7 +146,7 @@ if __name__ == "__main__": #Should be the set of cleanprofile shutil.copytree('/etc/apparmor.d', './profiles', symlinks=True) - apparmor.profile_dir='./profiles' + apparmor.profile_dir = './profiles' atexit.register(clean_profile_dir) diff --git a/Testing/severity_test.py b/Testing/severity_test.py index 7856fe511..1abfa2847 100644 --- a/Testing/severity_test.py +++ b/Testing/severity_test.py @@ -34,57 +34,57 @@ class Test(unittest.TestCase): shutil.rmtree('./profiles') def testRank_Test(self): - s = severity.Severity('severity.db') - rank = s.rank('/usr/bin/whatis', 'x') + sev_db = severity.Severity('severity.db') + rank = sev_db.rank('/usr/bin/whatis', 'x') self.assertEqual(rank, 5, 'Wrong rank') - rank = s.rank('/etc', 'x') + rank = sev_db.rank('/etc', 'x') self.assertEqual(rank, 10, 'Wrong rank') - rank = s.rank('/dev/doublehit', 'x') + rank = sev_db.rank('/dev/doublehit', 'x') self.assertEqual(rank, 0, 'Wrong rank') - rank = s.rank('/dev/doublehit', 'rx') + rank = sev_db.rank('/dev/doublehit', 'rx') self.assertEqual(rank, 4, 'Wrong rank') - rank = s.rank('/dev/doublehit', 'rwx') + rank = sev_db.rank('/dev/doublehit', 'rwx') self.assertEqual(rank, 8, 'Wrong rank') - rank = s.rank('/dev/tty10', 'rwx') + rank = sev_db.rank('/dev/tty10', 'rwx') self.assertEqual(rank, 9, 'Wrong rank') - rank = s.rank('/var/adm/foo/**', 'rx') + rank = sev_db.rank('/var/adm/foo/**', 'rx') self.assertEqual(rank, 3, 'Wrong rank') - rank = s.rank('CAP_KILL') + rank = sev_db.rank('CAP_KILL') self.assertEqual(rank, 8, 'Wrong rank') - rank = s.rank('CAP_SETPCAP') + rank = sev_db.rank('CAP_SETPCAP') self.assertEqual(rank, 9, 'Wrong rank') - self.assertEqual(s.rank('/etc/apparmor/**', 'r') , 6, 'Invalid Rank') - self.assertEqual(s.rank('/etc/**', 'r') , 10, 'Invalid Rank') + self.assertEqual(sev_db.rank('/etc/apparmor/**', 'r') , 6, 'Invalid Rank') + self.assertEqual(sev_db.rank('/etc/**', 'r') , 10, 'Invalid Rank') # Load all variables for /sbin/klogd and test them - s.load_variables('profiles/sbin.klogd') - self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') - self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') - self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') + sev_db.load_variables('profiles/sbin.klogd') + self.assertEqual(sev_db.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') + self.assertEqual(sev_db.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') + self.assertEqual(sev_db.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') - s.unload_variables() + sev_db.unload_variables() - s.load_variables('profiles/usr.sbin.dnsmasq') - self.assertEqual(s.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') - self.assertEqual(s.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') - self.assertEqual(s.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') - self.assertEqual(s.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') + sev_db.load_variables('profiles/usr.sbin.dnsmasq') + self.assertEqual(sev_db.rank('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'), 6, 'Invalid Rank') + self.assertEqual(sev_db.rank('@{PROC}/sys/vm/overcommit_memory', 'r'), 6, 'Invalid Rank') + self.assertEqual(sev_db.rank('@{HOME}/sys/@{PROC}/overcommit_memory', 'r'), 10, 'Invalid Rank') + self.assertEqual(sev_db.rank('/overco@{multiarch}mmit_memory', 'r'), 10, 'Invalid Rank') - #self.assertEqual(s.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') + #self.assertEqual(sev_db.rank('/proc/@{PID}/maps', 'rw'), 9, 'Invalid Rank') def testInvalid(self): - s = severity.Severity('severity.db') - rank = s.rank('/dev/doublehit', 'i') + sev_db = severity.Severity('severity.db') + rank = sev_db.rank('/dev/doublehit', 'i') self.assertEqual(rank, 10, 'Wrong') try: - broken = severity.Severity('severity_broken.db') + severity.Severity('severity_broken.db') except AppArmorException: pass - rank = s.rank('CAP_UNKOWN') - rank = s.rank('CAP_K*') + rank = sev_db.rank('CAP_UNKOWN') + rank = sev_db.rank('CAP_K*') if __name__ == "__main__": #import sys;sys.argv = ['', 'Test.testName'] - unittest.main() \ No newline at end of file + unittest.main() diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 201cf833f..a048033f8 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -58,6 +58,12 @@ profiling = args.program profiledir = args.dir filename = args.file + +if not os.path.isfile(filename): + raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) + +apparmor.filename = filename + aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) diff --git a/Tools/aa-logprof b/Tools/aa-logprof index d3d591dac..28eddda71 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -31,6 +31,11 @@ filename = args.file logmark = args.mark or '' +if not os.path.isfile(filename): + raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) + +apparmor.filename = filename + aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index e7ed5fb1b..73a2280ff 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -13,8 +13,6 @@ # # ---------------------------------------------------------------------- import argparse -import gettext -import locale import os import re import sys @@ -24,82 +22,70 @@ init_translations() import apparmor.aa as apparmor -#gettext.bindtextdomain('apparmor-utils', '/usr/share/locale/')#/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0]) -#gettext.textdomain('apparmor-utils') -#_ = gettext.gettext -#gettext.translation('apparmor-utils','./Trans/') -#gettext.install('apparmor-utils') -#print(os.path.join('/usr/share/locale', locale.getlocale()[0], 'LC_MESSAGES', '%s.mo' % -# 'apparmor-utils')) -#_ = gettext.translation('apparmor-utils', '/usr/share/locale', [locale.getlocale()[0]]).gettext - -#gettext.find - - -parser = argparse.ArgumentParser(description=_('Lists unconfined processes having tcp or udp ports')) -parser.add_argument('--paranoid', action='store_true', help=_('scan all processes from /proc')) +parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports")) +parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc")) args = parser.parse_args() paranoid = args.paranoid aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: - raise apparmor.AppArmorException(_('It seems AppArmor was not started. Please enable AppArmor and try again.')) + raise apparmor.AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again.")) pids = [] if paranoid: - pids = list(filter(lambda x: re.search('^\d+$', x), apparmor.get_subdirectories('/proc'))) + pids = list(filter(lambda x: re.search(r"^\d+$", x), apparmor.get_subdirectories("/proc"))) else: - regex_tcp_udp = re.compile('^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)') + regex_tcp_udp = re.compile(r"^(tcp|udp)\s+\d+\s+\d+\s+\S+\:(\d+)\s+\S+\:(\*|\d+)\s+(LISTEN|\s+)\s+(\d+)\/(\S+)") import subprocess - if sys.version_info < (3,0): - output = subprocess.check_output('LANG=C netstat -nlp', shell=True).split('\n') + if sys.version_info < (3, 0): + output = subprocess.check_output("LANG=C netstat -nlp", shell=True).split("\n") else: #Python3 needs to translate a stream of bytes to string with specified encoding - output = str(subprocess.check_output('LANG=C netstat -nlp', shell=True), encoding='utf8').split('\n') + output = str(subprocess.check_output("LANG=C netstat -nlp", shell=True), encoding='utf8').split("\n") for line in output: match = regex_tcp_udp.search(line) if match: pids.append(match.groups()[4]) # We can safely remove duplicate pid's? -pids = list(map(lambda x: int(x), set(pids))) +pids = list(map(int, set(pids))) for pid in sorted(pids): try: - prog = os.readlink('/proc/%s/exe'%pid) - except: + prog = os.readlink("/proc/%s/exe"%pid) + except IOError: continue attr = None - if os.path.exists('/proc/%s/attr/current'%pid): - with apparmor.open_file_read('/proc/%s/attr/current'%pid) as current: + if os.path.exists("/proc/%s/attr/current"%pid): + with apparmor.open_file_read("/proc/%s/attr/current"%pid) as current: for line in current: - if line.startswith('/') or line.startswith('null'): + if line.startswith("/") or line.startswith("null"): attr = line.strip() - cmdline = apparmor.cmd(['cat', '/proc/%s/cmdline'%pid])[1] - pname = cmdline.split('\0')[0] + cmdline = apparmor.cmd(["cat", "/proc/%s/cmdline"%pid])[1] + pname = cmdline.split("\0")[0] if '/' in pname and pname != prog: - pname = '(%s)'%pname + pname = "(%s)"% pname else: - pname = '' - regex_interpreter = re.compile('^(/usr)?/bin/(python|perl|bash|dash|sh)$') + pname = "" + regex_interpreter = re.compile(r"^(/usr)?/bin/(python|perl|bash|dash|sh)$") if not attr: if regex_interpreter.search(prog): - cmdline = re.sub('\x00', ' ', cmdline) - cmdline = re.sub('\s+$', '', cmdline).strip() + cmdline = re.sub(r"\x00", " ", cmdline) + cmdline = re.sub(r"\s+$", "", cmdline).strip() - apparmor.UI_Info(_('%s %s (%s) not confined\n')%(pid, prog, cmdline)) + apparmor.UI_Info(_("%s %s (%s) not confined\n")%(pid, prog, cmdline)) else: if pname and pname[-1] == ')': pname += ' ' - apparmor.UI_Info(_('%s %s %snot confined\n')%(pid, prog, pname)) + apparmor.UI_Info(_("%s %s %snot confined\n")%(pid, prog, pname)) else: if regex_interpreter.search(prog): - cmdline = re.sub('\0', ' ', cmdline) - cmdline = re.sub('\s+$', '', cmdline).strip() + cmdline = re.sub(r"\0", " ", cmdline) + cmdline = re.sub(r"\s+$", "", cmdline).strip() apparmor.UI_Info(_("%s %s (%s) confined by '%s'\n")%(pid, prog, cmdline, attr)) else: if pname and pname[-1] == ')': pname += ' ' - apparmor.UI_Info(_("%s %s %sconfined by '%s'\n")%(pid, prog, pname, attr)) \ No newline at end of file + apparmor.UI_Info(_("%s %s %sconfined by '%s'\n")%(pid, prog, pname, attr)) diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 22f860868..3f93c45c8 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -10,16 +10,15 @@ # ------------------------------------------------------------------ import gettext import locale - + def init_localisation(): locale.setlocale(locale.LC_ALL, '') #If a correct locale has been provided set filename else let an IOError be raised filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0] try: trans = gettext.GNUTranslations(open(filename, 'rb')) - print("Locale installed for %s"%locale.getlocale()[0]) except IOError: trans = gettext.NullTranslations() trans.install() - -init_localisation() \ No newline at end of file + +init_localisation() diff --git a/apparmor/aa.py b/apparmor/aa.py index b71effbea..c9310b63b 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -13,7 +13,6 @@ # ---------------------------------------------------------------------- # No old version logs, only 2.6 + supported from __future__ import with_statement -import codecs import inspect import os import re @@ -113,7 +112,7 @@ def check_for_LD_XXX(file): # Limit to checking files under 100k for the sake of speed if size >100000: return False - with codecs.open(file, 'r', encoding='ascii') as f_in: + with open_file_read(file, encoding='ascii') as f_in: for line in f_in: if 'LD_PRELOAD' in line or 'LD_LIBRARY_PATH' in line: found = True diff --git a/apparmor/common.py b/apparmor/common.py index 8214b3798..5a1c8de0d 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -132,10 +132,10 @@ def get_directory_contents(path): files.sort() return files -def open_file_read(path): +def open_file_read(path, encoding='UTF-8'): '''Open specified file read-only''' try: - orig = codecs.open(path, 'r', 'UTF-8') + orig = codecs.open(path, 'r', encoding) except Exception: raise From 3edc4d16ac23a9eefcd19f9e117a6e7401580f17 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sun, 29 Dec 2013 15:12:30 +0530 Subject: [PATCH 092/183] Fixed some variable name conflicts, moved some code to methods from functions. Fixes the bug in custom logfile name. --- Tools/aa-complain | 2 +- Tools/aa-genprof | 9 ++- Tools/aa-logprof | 9 ++- Tools/aa-unconfined | 2 +- apparmor/aamode.py | 2 +- apparmor/cleanprofile.py | 160 +++++++++++++++++++-------------------- apparmor/common.py | 24 +++--- apparmor/config.py | 22 +++--- apparmor/severity.py | 8 +- apparmor/tools.py | 2 +- apparmor/ui.py | 4 +- 11 files changed, 123 insertions(+), 121 deletions(-) diff --git a/Tools/aa-complain b/Tools/aa-complain index e0a622bb9..34c04cf8c 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -26,5 +26,5 @@ parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() complain = apparmor.tools.aa_tools('complain', args) -print(args) +#print(args) complain.act() diff --git a/Tools/aa-genprof b/Tools/aa-genprof index a048033f8..292cdaf83 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -59,10 +59,11 @@ profiledir = args.dir filename = args.file -if not os.path.isfile(filename): - raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) - -apparmor.filename = filename +if filename: + if not os.path.isfile(filename): + raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) + else: + apparmor.filename = filename aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: diff --git a/Tools/aa-logprof b/Tools/aa-logprof index 28eddda71..26f892f39 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -31,10 +31,11 @@ filename = args.file logmark = args.mark or '' -if not os.path.isfile(filename): - raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) - -apparmor.filename = filename +if filename: + if not os.path.isfile(filename): + raise apparmor.AppArmorException(_('The logfile %s does not exist. Please check the path') % filename) + else: + apparmor.filename = filename aa_mountpoint = apparmor.check_for_apparmor() if not aa_mountpoint: diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index 73a2280ff..ca17a03e4 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -54,7 +54,7 @@ pids = list(map(int, set(pids))) for pid in sorted(pids): try: prog = os.readlink("/proc/%s/exe"%pid) - except IOError: + except OSError: continue attr = None if os.path.exists("/proc/%s/attr/current"%pid): diff --git a/apparmor/aamode.py b/apparmor/aamode.py index 9d4ded620..76fc011af 100644 --- a/apparmor/aamode.py +++ b/apparmor/aamode.py @@ -277,4 +277,4 @@ def log_str_to_mode(profile, string, nt_name): mode = mode - str_to_mode('Nx') mode |= tmode - return mode, nt_name \ No newline at end of file + return mode, nt_name diff --git a/apparmor/cleanprofile.py b/apparmor/cleanprofile.py index bfe83abc6..dad132f50 100644 --- a/apparmor/cleanprofile.py +++ b/apparmor/cleanprofile.py @@ -16,14 +16,14 @@ import copy import apparmor -class Prof: +class Prof(object): def __init__(self, filename): self.aa = apparmor.aa.aa self.filelist = apparmor.aa.filelist self.include = apparmor.aa.include self.filename = filename -class CleanProf: +class CleanProf(object): def __init__(self, same_file, profile, other): #If same_file we're basically comparing the file against itself to check superfluous rules self.same_file = same_file @@ -41,7 +41,7 @@ class CleanProf: for profile in self.profile.aa.keys(): deleted += self.remove_duplicate_rules(profile) - + return deleted def remove_duplicate_rules(self, program): @@ -61,93 +61,93 @@ class CleanProf: deleted += 1 #Clean up superfluous rules from includes in the other profile for inc in includes: - if not self.profile.include.get(inc, {}).get(inc,False): + if not self.profile.include.get(inc, {}).get(inc, False): apparmor.aa.load_include(inc) deleted += apparmor.aa.delete_duplicates(self.other.aa[program][hat], inc) #Clean the duplicates of caps in other profile - deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file) - deleted += self.delete_cap_duplicates(self.profile.aa[program][hat]['deny']['capability'], self.other.aa[program][hat]['deny']['capability'], self.same_file) + deleted += delete_cap_duplicates(self.profile.aa[program][hat]['allow']['capability'], self.other.aa[program][hat]['allow']['capability'], self.same_file) + deleted += delete_cap_duplicates(self.profile.aa[program][hat]['deny']['capability'], self.other.aa[program][hat]['deny']['capability'], self.same_file) #Clean the duplicates of path in other profile - deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file) - deleted += self.delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file) + deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'allow', self.same_file) + deleted += delete_path_duplicates(self.profile.aa[program][hat], self.other.aa[program][hat], 'deny', self.same_file) #Clean the duplicates of net rules in other profile - deleted += self.delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file) - deleted += self.delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file) + deleted += delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file) + deleted += delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file) return deleted - def delete_path_duplicates(self, profile, profile_other, allow, same_profile=True): - deleted = [] - #Check if any individual rule makes any rule superfluous - for rule in profile[allow]['path'].keys(): - for entry in profile_other[allow]['path'].keys(): - if rule == entry: - #Check the modes - cm = profile[allow]['path'][rule]['mode'] - am = profile[allow]['path'][rule]['audit'] - #If modes of rule are a superset of rules implied by entry we can safely remove it - if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): - if not same_profile: - deleted.append(entry) - continue - if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): - continue - #Check if the rule implies entry - if apparmor.aa.matchliteral(rule, entry): - #Check the modes - cm = profile[allow]['path'][rule]['mode'] - am = profile[allow]['path'][rule]['audit'] - #If modes of rule are a superset of rules implied by entry we can safely remove it - if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): - deleted.append(entry) - - for entry in deleted: - profile_other[allow]['path'].pop(entry) - - return len(deleted) - - def delete_cap_duplicates(self, profilecaps, profilecaps_other, same_profile=True): - deleted = [] - if profilecaps and profilecaps_other and not same_profile: - for capname in profilecaps.keys(): - if profilecaps_other[capname].get('set', False): - deleted.append(capname) - for capname in deleted: - profilecaps_other.pop(capname) - - return len(deleted) - - def delete_net_duplicates(self, netrules, netrules_other, same_profile=True): - deleted = 0 - hasher_obj = apparmor.aa.hasher() - copy_netrules_other = copy.deepcopy(netrules_other) - if netrules_other and netrules: - netglob = False - # Delete matching rules - if netrules.get('all', False): - netglob = True - #Iterate over a copy of the rules in the other profile - for fam in copy_netrules_other['rule'].keys(): - if netglob or (type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]): +def delete_path_duplicates(profile, profile_other, allow, same_profile=True): + deleted = [] + # Check if any individual rule makes any rule superfluous + for rule in profile[allow]['path'].keys(): + for entry in profile_other[allow]['path'].keys(): + if rule == entry: + # Check the modes + cm = profile[allow]['path'][rule]['mode'] + am = profile[allow]['path'][rule]['audit'] + # If modes of rule are a superset of rules implied by entry we can safely remove it + if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): + if not same_profile: + deleted.append(entry) + continue + if re.search('#?\s*include', rule) or re.search('#?\s*include', entry): + continue + # Check if the rule implies entry + if apparmor.aa.matchliteral(rule, entry): + # Check the modes + cm = profile[allow]['path'][rule]['mode'] + am = profile[allow]['path'][rule]['audit'] + # If modes of rule are a superset of rules implied by entry we can safely remove it + if apparmor.aa.mode_contains(cm, profile_other[allow]['path'][entry]['mode']) and apparmor.aa.mode_contains(am, profile_other[allow]['path'][entry]['audit']): + deleted.append(entry) + + for entry in deleted: + profile_other[allow]['path'].pop(entry) + + return len(deleted) + +def delete_cap_duplicates(profilecaps, profilecaps_other, same_profile=True): + deleted = [] + if profilecaps and profilecaps_other and not same_profile: + for capname in profilecaps.keys(): + if profilecaps_other[capname].get('set', False): + deleted.append(capname) + for capname in deleted: + profilecaps_other.pop(capname) + + return len(deleted) + +def delete_net_duplicates(netrules, netrules_other, same_profile=True): + deleted = 0 + hasher_obj = apparmor.aa.hasher() + copy_netrules_other = copy.deepcopy(netrules_other) + if netrules_other and netrules: + netglob = False + # Delete matching rules + if netrules.get('all', False): + netglob = True + # Iterate over a copy of the rules in the other profile + for fam in copy_netrules_other['rule'].keys(): + if netglob or (type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]): + if not same_profile: + if type(netrules_other['rule'][fam]) == type(hasher_obj): + deleted += len(netrules_other['rule'][fam].keys()) + else: + deleted += 1 + netrules_other['rule'].pop(fam) + elif type(netrules_other['rule'][fam]) != type(hasher_obj) and netrules_other['rule'][fam]: + if type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: if not same_profile: - if type(netrules_other['rule'][fam]) == type(hasher_obj): - deleted += len(netrules_other['rule'][fam].keys()) - else: - deleted += 1 netrules_other['rule'].pop(fam) - elif type(netrules_other['rule'][fam]) != type(hasher_obj) and netrules_other['rule'][fam]: - if type(netrules['rule'][fam]) != type(hasher_obj) and netrules['rule'][fam]: - if not same_profile: - netrules_other['rule'].pop(fam) - deleted += 1 - else: - for sock_type in netrules_other['rule'][fam].keys(): - if netrules['rule'].get(fam, False): - if netrules['rule'][fam].get(sock_type, False): - if not same_profile: - netrules_other['rule'][fam].pop(sock_type) - deleted += 1 - return deleted \ No newline at end of file + deleted += 1 + else: + for sock_type in netrules_other['rule'][fam].keys(): + if netrules['rule'].get(fam, False): + if netrules['rule'][fam].get(sock_type, False): + if not same_profile: + netrules_other['rule'][fam].pop(sock_type) + deleted += 1 + return deleted diff --git a/apparmor/common.py b/apparmor/common.py index 5a1c8de0d..159cc7341 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -176,7 +176,7 @@ def init_translations(domain='apparmor-utils'): def convert_regexp(regexp): regex_paren = re.compile('^(.*){([^}]*)}(.*)$') regexp = regexp.strip() - new_reg = re.sub(r'(? (3,0): + if sys.version_info > (3, 0): config = configparser.ConfigParser() else: config = configparser_py2() # Set the option form to string -prevents forced conversion to lowercase config.optionxform = str - if sys.version_info > (3,0): + if sys.version_info > (3, 0): config.read(filepath) else: try: @@ -114,9 +114,9 @@ class Config: def find_first_file(self, file_list): """Returns name of first matching file None otherwise""" filename = None - for file in file_list.split(): - if os.path.isfile(file): - filename = file + for f in file_list.split(): + if os.path.isfile(f): + filename = f break return filename @@ -133,8 +133,8 @@ class Config: def read_shell(self, filepath): """Reads the shell type conf files and returns config[''][option]=value""" config = {'': dict()} - with open_file_read(filepath) as file: - for line in file: + with open_file_read(filepath) as conf_file: + for line in conf_file: result = shlex.split(line, True) # If not a comment of empty line if result: @@ -294,4 +294,4 @@ def py2_parser(filename): line = line[1:] f_out.write(line) f_out.flush() - return tmp \ No newline at end of file + return tmp diff --git a/apparmor/severity.py b/apparmor/severity.py index a1b6ad6b7..b33fb224b 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -14,9 +14,9 @@ from __future__ import with_statement import os import re -from apparmor.common import AppArmorException, error, debug, open_file_read, warn, msg, convert_regexp +from apparmor.common import AppArmorException, open_file_read, warn, convert_regexp #, msg, error, debug -class Severity: +class Severity(object): def __init__(self, dbname=None, default_rank=10): """Initialises the class object""" self.PROF_DIR = '/etc/apparmor.d' # The profile directory @@ -43,7 +43,7 @@ class Severity: except ValueError: raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) else: - if read not in range(0,11) or write not in range(0,11) or execute not in range(0,11): + if read not in range(0, 11) or write not in range(0,11) or execute not in range(0,11): raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) path = path.lstrip('/') if '*' not in path: @@ -69,7 +69,7 @@ class Severity: #error(error_message) raise AppArmorException(error_message) # from None else: - if severity not in range(0,11): + if severity not in range(0, 11): raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) self.severity['CAPABILITIES'][resource] = severity else: diff --git a/apparmor/tools.py b/apparmor/tools.py index 3c4c77f46..70d9431a7 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -67,7 +67,7 @@ class aa_tools: if program and not program.startswith('/'): program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%program) + apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) sys.exit(1) if program and apparmor.profile_exists(program):#os.path.exists(program): diff --git a/apparmor/ui.py b/apparmor/ui.py index e1655eae2..66dc7a0a3 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -58,8 +58,8 @@ def get_translated_hotkey(translated, cmsg=''): msg = 'PromptUser: '+_('Invalid hotkey for') # Originally (\S) was used but with translations it would not work :( - if re.search('\((\S*)\)', translated, re.LOCALE): - return re.search('\((\S*)\)', translated, re.LOCALE).groups()[0] + if re.search('\((\S+)\)', translated, re.LOCALE): + return re.search('\((\S+)\)', translated, re.LOCALE).groups()[0] else: if cmsg: raise AppArmorException(cmsg) From b3c9d8b86bc0dbba593255d89a12198171604d2f Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Mon, 20 Jan 2014 11:51:01 -0800 Subject: [PATCH 093/183] utils: address pep8 complaints This patch eliminates the complaints from running: pep8 --ignore=E501 aa-easyprof vim/ (E501 is 'line too long', which I'm not too chuffed about.) Mostly, it's a lot of whitespace touchups, with a few conversions from '==' to 'is'. Commit includes applied feedback from cboltz. Signed-off-by: Steve Beattie Acked-by: Christian Boltz --- utils/aa-easyprof | 3 +- utils/vim/create-apparmor.vim.py | 121 ++++++++++++++++--------------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/utils/aa-easyprof b/utils/aa-easyprof index a042c55ee..da0d1b869 100755 --- a/utils/aa-easyprof +++ b/utils/aa-easyprof @@ -55,11 +55,10 @@ if __name__ == "__main__": files = [os.path.join(easyp.dirs['policygroups'], g)] apparmor.easyprof.print_files(files) sys.exit(0) - elif binary == None: + elif binary is None: error("Must specify full path to binary\n%s" % m) # if we made it here, generate a profile params = apparmor.easyprof.gen_policy_params(binary, opt) p = easyp.gen_policy(**params) sys.stdout.write('%s\n' % p) - diff --git a/utils/vim/create-apparmor.vim.py b/utils/vim/create-apparmor.vim.py index dc10ffb2c..3f17a27d3 100644 --- a/utils/vim/create-apparmor.vim.py +++ b/utils/vim/create-apparmor.vim.py @@ -15,16 +15,17 @@ import subprocess import sys # dangerous capabilities -danger_caps=["audit_control", - "audit_write", - "mac_override", - "mac_admin", - "set_fcap", - "sys_admin", - "sys_module", - "sys_rawio"] +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): + +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.''' @@ -36,12 +37,12 @@ def cmd(command, input = None, stderr = subprocess.STDOUT, stdout = subprocess.P out, outerr = sp.communicate(input) # Handle redirection of stdout - if out == None: + if out is None: out = '' # Handle redirection of stderr - if outerr == None: + if outerr is None: outerr = '' - return [sp.returncode,out+outerr] + return [sp.returncode, out + outerr] # get capabilities list (rc, output) = cmd(['make', '-s', '--no-print-directory', 'list_capabilities']) @@ -50,7 +51,7 @@ if rc != 0: exit(rc) capabilities = re.sub('CAP_', '', output.strip()).lower().split(" ") -benign_caps =[] +benign_caps = [] for cap in capabilities: if cap not in danger_caps: benign_caps.append(cap) @@ -73,28 +74,28 @@ for af_pair in af_pairs: # 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_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'] +aa_flags = ['complain', + 'audit', + 'attach_disconnect', + 'no_attach_disconnected', + 'chroot_attach', + 'chroot_no_attach', + 'chroot_relative', + 'namespace_relative'] -filename=r'(\/|\@\{\S*\})\S*' +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 + '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 + '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 + '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.*) + '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), @@ -104,6 +105,7 @@ aa_regex_map = { '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: @@ -112,48 +114,48 @@ def my_repl(matchobj): return matchobj.group(0) -def create_file_rule (highlighting, permissions, comment, denyrule = 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+)?' + 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" + 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 + 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 + 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('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) +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' ) +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 @@ -174,5 +176,4 @@ with open("apparmor.vim.in") as template: 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') - +sys.stdout.write(re.sub(regex, my_repl, filerule) + '\n') From 1886ab9f357268bda0425016a9d76369db87cf1c Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Mon, 20 Jan 2014 23:35:13 +0100 Subject: [PATCH 094/183] make sure all profiles have #include Acked-by: Steve Beattie --- profiles/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/profiles/Makefile b/profiles/Makefile index c70dcc7e4..6af6ffe8c 100644 --- a/profiles/Makefile +++ b/profiles/Makefile @@ -44,6 +44,7 @@ local: for profile in ${TOPLEVEL_PROFILES}; do \ fn=$$(basename $$profile); \ echo "# Site-specific additions and overrides for '$$fn'" > ${PROFILES_SOURCE}/local/$$fn; \ + grep "include\\s\\s*" "$$profile" >/dev/null || { echo "$$profile doesn't contain #include " ; exit 1; } ; \ done; \ .PHONY: install From 960a8aee871bbbb6cd9d82a022924a0f5d613501 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Thu, 23 Jan 2014 15:04:12 +0100 Subject: [PATCH 095/183] several updates for the winbindd profile (collected in the openSUSE package over the last months) - add abstractions/samba to usr.sbin.winbindd profile (and cleanup things that are included in the abstraction - the cleanup part is not in the openSUSE package) - add capabilities ipc_lock and setuid to usr.sbin.winbindd profile (bnc#851131) - updates for samba 4.x and kerberos (bnc#846586#c12 and #c15, bnc#845867, bnc#846054) - drop always-outdated "Last Modified" comment References: see the bnc# above (they are bug numbers at bugzilla.novell.com) Acked-by: John Johansen --- profiles/apparmor.d/usr.sbin.winbindd | 29 +++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/profiles/apparmor.d/usr.sbin.winbindd b/profiles/apparmor.d/usr.sbin.winbindd index d9ca72e15..47ae81a44 100644 --- a/profiles/apparmor.d/usr.sbin.winbindd +++ b/profiles/apparmor.d/usr.sbin.winbindd @@ -1,33 +1,32 @@ -# Last Modified: Mon Mar 26 20:28:18 2012 #include /usr/sbin/winbindd { #include #include + #include + + deny capability block_suspend, + + capability ipc_lock, + capability setuid, - /etc/samba/dhcp.conf r, /etc/samba/passdb.tdb rwk, /etc/samba/secrets.tdb rwk, @{PROC}/sys/kernel/core_pattern r, /tmp/.winbindd/ w, + /tmp/krb5cc_* rwk, /usr/lib*/samba/idmap/*.so mr, /usr/lib*/samba/nss_info/*.so mr, + /usr/lib*/samba/pdb/*.so mr, /usr/sbin/winbindd mr, - /var/lib/samba/account_policy.tdb rwk, - /var/lib/samba/gencache.tdb rwk, - /var/lib/samba/gencache_notrans.tdb rwk, - /var/lib/samba/group_mapping.tdb rwk, - /var/lib/samba/messages.tdb rwk, - /var/lib/samba/netsamlogon_cache.tdb rwk, - /var/lib/samba/serverid.tdb rwk, - /var/lib/samba/winbindd_cache.tdb rwk, - /var/lib/samba/winbindd_privileged/pipe w, - /var/log/samba/cores/ rw, - /var/log/samba/cores/winbindd/ rw, - /var/log/samba/cores/winbindd/** rw, - /var/log/samba/log.wb-* w, + /var/cache/samba/*.tdb rwk, + /var/lib/samba/smb_krb5/krb5.conf.* rw, + /var/lib/samba/smb_tmp_krb5.* rw, + /var/lib/samba/winbindd_cache.tdb* rwk, /var/log/samba/log.winbindd rw, /{var/,}run/samba/winbindd.pid rwk, + /{var/,}run/samba/winbindd/ rw, + /{var/,}run/samba/winbindd/pipe w, # Site-specific additions and overrides. See local/README for details. #include From 2d504e3c71667b8b1b3700b73a21eac3ec4a60c6 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:16:56 -0800 Subject: [PATCH 096/183] Subject: libapparmor: fix aa_change_hat token format string This patch fixes the format string for the magic token in aa_change_hat to match the type of the magic token (long). Without this, on 64 bit platforms, only the bottom 32 bits of the token would be used. aa_change_hatv() has the correct format string, so an aa_change_hatv() call followed by an exiting aa_change_hat() call would result in the latter having a different token, which would cause the process to be killed by apparmor. (Hat tip to John Johansen for spotting the actual bug.) Signed-off-by: Steve Beattie Acked-by: John Johansen --- libraries/libapparmor/src/kernel_interface.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/libapparmor/src/kernel_interface.c b/libraries/libapparmor/src/kernel_interface.c index 4a16c3860..3fa328b1b 100644 --- a/libraries/libapparmor/src/kernel_interface.c +++ b/libraries/libapparmor/src/kernel_interface.c @@ -355,7 +355,7 @@ int aa_change_hat(const char *subprofile, unsigned long token) int rc = -1; int len = 0; char *buf = NULL; - const char *fmt = "changehat %016x^%s"; + const char *fmt = "changehat %016lx^%s"; /* both may not be null */ if (!(token || subprofile)) { From 637a6bfe9fccd8199a31706fc05e65a814673866 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:38:31 -0800 Subject: [PATCH 097/183] mod_apparmor: fix logging The apache2 mod_apparmor module was failing to log debugging messages when the apache loglevel was set to debug or lower (i.e. traceN). This patch fixes it by using ap_log_rerror() (for request specific messages, with the request passed for context) and ap_log_error() (more general messages outside of a request context). Also, the APLOG_USE_MODULE macro is called, to mark the log messages as belonging to the apparmor module, so that the apache 2.4 feature of enabling debug logging for just the apparmor module will work, with an apache configuration entry like: LogLevel apparmor:debug See http://ci.apache.org/projects/httpd/trunk/doxygen/group__APACHE__CORE__LOG.html for specific about the ap_log_*error() and APLOG_USE_MODULE functions and macros, and http://httpd.apache.org/docs/2.4/mod/core.html.en#loglevel for the bits about module specific logging. Patch history: v1: initial version v2: - revert to using ap_log_error with (the 2.4 specific) ap_server_conf outside of a request specific context, as the pool specific ap_log_perror messages weren't being reported. - add compatibility workaround for apache 2.2 v3: keep commented out merge function's log call consistent with the others Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 49 ++++++++++++++++----------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index a91314456..5d2de9c00 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -17,6 +17,7 @@ #include "http_config.h" #include "http_request.h" #include "http_log.h" +#include "http_main.h" #include "http_protocol.h" #include "util_filter.h" #include "apr.h" @@ -35,6 +36,14 @@ #define DEFAULT_HAT "HANDLING_UNTRUSTED_INPUT" #define DEFAULT_URI_HAT "DEFAULT_URI" +/* Compatibility with apache 2.2 */ +#if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER < 3 + server_rec *ap_server_conf = NULL; +#endif + +#ifdef APLOG_USE_MODULE + APLOG_USE_MODULE(apparmor); +#endif module AP_MODULE_DECLARE_DATA apparmor_module; static unsigned int magic_token = 0; @@ -68,9 +77,9 @@ immunix_init (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) apr_file_read (file, (void *) &magic_token, &size); apr_file_close (file); } else { - ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "Failed to open /dev/urandom"); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to open /dev/urandom"); } - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "Opened /dev/urandom successfully"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "Opened /dev/urandom successfully"); return OK; } @@ -83,11 +92,11 @@ immunix_child_init (apr_pool_t *p, server_rec *s) { int ret; - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "init: calling change_hat"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "init: calling change_hat"); ret = change_hat (DEFAULT_HAT, magic_token); if (ret < 0) { change_hat (NULL, magic_token); - ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "Failed to change_hat to '%s'", + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to change_hat to '%s'", DEFAULT_HAT); } else { inside_default_hat = 1; @@ -130,7 +139,7 @@ immunix_enter_hat (request_rec *r) ap_get_module_config (r->server->module_config, &apparmor_module); debug_dump_uri (&r->parsed_uri); - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx", + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx", dcfg->path, (unsigned long) r->next, (unsigned long) r->prev, (unsigned long) r->main); @@ -144,7 +153,7 @@ immunix_enter_hat (request_rec *r) } if (dcfg != NULL && dcfg->hat_name != NULL) { - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "calling change_hat [dcfg] %s", dcfg->hat_name); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [dcfg] %s", dcfg->hat_name); sd_ret = change_hat (dcfg->hat_name, magic_token); if (sd_ret < 0) { change_hat (NULL, magic_token); @@ -153,7 +162,7 @@ immunix_enter_hat (request_rec *r) } } - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "calling change_hat [uri] %s", r->uri); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [uri] %s", r->uri); sd_ret = change_hat (r->uri, magic_token); if (sd_ret < 0) { change_hat (NULL, magic_token); @@ -162,7 +171,7 @@ immunix_enter_hat (request_rec *r) } if (scfg != NULL && scfg->hat_name != NULL) { - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "calling change_hat [scfg] %s", scfg->hat_name); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [scfg] %s", scfg->hat_name); sd_ret = change_hat (scfg->hat_name, magic_token); if (sd_ret < 0) { change_hat (NULL, magic_token); @@ -171,7 +180,7 @@ immunix_enter_hat (request_rec *r) } } - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "calling change_hat DEFAULT_URI"); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat DEFAULT_URI"); sd_ret = change_hat (DEFAULT_URI_HAT, magic_token); if (sd_ret < 0) change_hat (NULL, magic_token); @@ -186,13 +195,13 @@ immunix_exit_hat (request_rec *r) ap_get_module_config (r->per_dir_config, &apparmor_module); /* immunix_srv_cfg * scfg = (immunix_srv_cfg *) ap_get_module_config (r->server->module_config, &apparmor_module); */ - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "exiting change_hat - dir hat %s path %s", dcfg->hat_name, dcfg->path); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "exiting change_hat - dir hat %s path %s", dcfg->hat_name, dcfg->path); change_hat (NULL, magic_token); sd_ret = change_hat (DEFAULT_HAT, magic_token); if (sd_ret < 0) { change_hat (NULL, magic_token); - ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "Failed to change_hat to '%s'", + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to change_hat to '%s'", DEFAULT_HAT); } else { inside_default_hat = 1; @@ -204,7 +213,7 @@ immunix_exit_hat (request_rec *r) static const char * aa_cmd_ch_path (cmd_parms * cmd, void * mconfig, const char * parm1) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "config change hat %s", + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "directory config change hat %s", parm1 ? parm1 : "DEFAULT"); immunix_dir_cfg * dcfg = mconfig; if (parm1 != NULL) { @@ -221,7 +230,7 @@ static const char * immunix_cmd_ch_path (cmd_parms * cmd, void * mconfig, const char * parm1) { if (path_warn_once == 0) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, "ImmHatName is " + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, "ImmHatName is " "deprecated, please use AAHatName instead"); path_warn_once = 1; } @@ -231,7 +240,7 @@ immunix_cmd_ch_path (cmd_parms * cmd, void * mconfig, const char * parm1) static const char * aa_cmd_ch_srv (cmd_parms * cmd, void * mconfig, const char * parm1) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "config change hat %s", + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "server config change hat %s", parm1 ? parm1 : "DEFAULT"); immunix_srv_cfg * scfg = mconfig; if (parm1 != NULL) { @@ -248,7 +257,7 @@ static const char * immunix_cmd_ch_srv (cmd_parms * cmd, void * mconfig, const char * parm1) { if (srv_warn_once == 0) { - ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, "ImmDefaultHatName is " + ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, ap_server_conf, "ImmDefaultHatName is " "deprecated, please use AADefaultHatName instead"); srv_warn_once = 1; } @@ -260,9 +269,9 @@ immunix_create_dir_config (apr_pool_t * p, char * path) { immunix_dir_cfg * newcfg = (immunix_dir_cfg *) apr_pcalloc(p, sizeof(* newcfg)); - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "in immunix_create_dir (%s)", path ? path : ":no path:"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "in immunix_create_dir (%s)", path ? path : ":no path:"); if (newcfg == NULL) { - ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "immunix_create_dir: couldn't alloc dir config"); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "immunix_create_dir: couldn't alloc dir config"); return NULL; } newcfg->path = apr_pstrdup (p, path ? path : ":no path:"); @@ -277,7 +286,7 @@ immunix_merge_dir_config (apr_pool_t * p, void * parent, void * child) { immunix_dir_cfg * newcfg = (immunix_dir_cfg *) apr_pcalloc(p, sizeof(* newcfg)); - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "in immunix_merge_dir ()"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "in immunix_merge_dir ()"); if (newcfg == NULL) return NULL; @@ -290,9 +299,9 @@ immunix_create_srv_config (apr_pool_t * p, server_rec * srv) { immunix_srv_cfg * newcfg = (immunix_srv_cfg *) apr_pcalloc(p, sizeof(* newcfg)); - ap_log_error (APLOG_MARK, APLOG_DEBUG, 0, NULL, "in immunix_create_srv"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "in immunix_create_srv"); if (newcfg == NULL) { - ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "immunix_create_srv: couldn't alloc srv config"); + ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "immunix_create_srv: couldn't alloc srv config"); return NULL; } From 087ec5e1ce00b0b0bbf7ae43c09a7de4cab52bdd Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:40:19 -0800 Subject: [PATCH 098/183] mod_apparmor: use trace1 loglevel for developer-oriented debug messages Apache 2.4 added addition logging levels. This patch converts some of the log messages that are more intended for mod_apparmor development and debugging than for sysadmins configuring mod_apparmor to use trace1 (APLOG_TRACE1) level instead. Since apache 2.2. does not contain this level (or define), we define it back to APLOG_DEBUG. Patch history: v1: initial version v2: mark a couple of additional log messages as trace1 level Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 5d2de9c00..96eae007c 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -38,6 +38,7 @@ /* Compatibility with apache 2.2 */ #if AP_SERVER_MAJORVERSION_NUMBER == 2 && AP_SERVER_MINORVERSION_NUMBER < 3 + #define APLOG_TRACE1 APLOG_DEBUG server_rec *ap_server_conf = NULL; #endif @@ -79,7 +80,7 @@ immunix_init (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) } else { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to open /dev/urandom"); } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "Opened /dev/urandom successfully"); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "Opened /dev/urandom successfully"); return OK; } @@ -92,7 +93,7 @@ immunix_child_init (apr_pool_t *p, server_rec *s) { int ret; - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "init: calling change_hat"); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "init: calling change_hat"); ret = change_hat (DEFAULT_HAT, magic_token); if (ret < 0) { change_hat (NULL, magic_token); @@ -139,7 +140,7 @@ immunix_enter_hat (request_rec *r) ap_get_module_config (r->server->module_config, &apparmor_module); debug_dump_uri (&r->parsed_uri); - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx", + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx", dcfg->path, (unsigned long) r->next, (unsigned long) r->prev, (unsigned long) r->main); @@ -195,7 +196,7 @@ immunix_exit_hat (request_rec *r) ap_get_module_config (r->per_dir_config, &apparmor_module); /* immunix_srv_cfg * scfg = (immunix_srv_cfg *) ap_get_module_config (r->server->module_config, &apparmor_module); */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "exiting change_hat - dir hat %s path %s", dcfg->hat_name, dcfg->path); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "exiting change_hat - dir hat %s path %s", dcfg->hat_name, dcfg->path); change_hat (NULL, magic_token); sd_ret = change_hat (DEFAULT_HAT, magic_token); @@ -269,7 +270,7 @@ immunix_create_dir_config (apr_pool_t * p, char * path) { immunix_dir_cfg * newcfg = (immunix_dir_cfg *) apr_pcalloc(p, sizeof(* newcfg)); - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "in immunix_create_dir (%s)", path ? path : ":no path:"); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "in immunix_create_dir (%s)", path ? path : ":no path:"); if (newcfg == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "immunix_create_dir: couldn't alloc dir config"); return NULL; @@ -286,7 +287,7 @@ immunix_merge_dir_config (apr_pool_t * p, void * parent, void * child) { immunix_dir_cfg * newcfg = (immunix_dir_cfg *) apr_pcalloc(p, sizeof(* newcfg)); - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "in immunix_merge_dir ()"); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "in immunix_merge_dir ()"); if (newcfg == NULL) return NULL; @@ -299,7 +300,7 @@ immunix_create_srv_config (apr_pool_t * p, server_rec * srv) { immunix_srv_cfg * newcfg = (immunix_srv_cfg *) apr_pcalloc(p, sizeof(* newcfg)); - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "in immunix_create_srv"); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "in immunix_create_srv"); if (newcfg == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "immunix_create_srv: couldn't alloc srv config"); return NULL; From eff2a32082f92955923095271e238b14dac9ae47 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:41:57 -0800 Subject: [PATCH 099/183] Subject: mod_apparmor: convert debug_dump_uri to use trace loglevel This patch converts the debug_dump_uri() function to use the trace loglevels and enable it all the time, rather than just when DEBUG is defined at compile time. Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 96eae007c..019e339f0 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -104,24 +104,20 @@ immunix_child_init (apr_pool_t *p, server_rec *s) } } -#ifdef DEBUG static void -debug_dump_uri (apr_uri_t * uri) +debug_dump_uri(request_rec *r) { - if (uri) - ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "Dumping uri info " + apr_uri_t *uri = &r->parsed_uri; + if (uri) + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Dumping uri info " "scheme='%s' host='%s' path='%s' query='%s' fragment='%s'", uri->scheme, uri->hostname, uri->path, uri->query, uri->fragment); else - ap_log_error (APLOG_MARK, APLOG_ERR, 0, NULL, "Asked to dump NULL uri"); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Asked to dump NULL uri"); } -#else -static void -debug_dump_uri (apr_uri_t * __unused uri) { } -#endif - + /* immunix_enter_hat will attempt to change_hat in the following order: (1) to a hatname in a location directive @@ -139,7 +135,7 @@ immunix_enter_hat (request_rec *r) immunix_srv_cfg * scfg = (immunix_srv_cfg *) ap_get_module_config (r->server->module_config, &apparmor_module); - debug_dump_uri (&r->parsed_uri); + debug_dump_uri(r); ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx", dcfg->path, (unsigned long) r->next, (unsigned long) r->prev, (unsigned long) r->main); From 3d155a3016dac0ef8c73041316e50079c54d0ec7 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:43:36 -0800 Subject: [PATCH 100/183] mod_apparmor: convert change_hat to aa_change_hat() mod_apparmor never got converted to use the renamed aa_change_hat() call (there's a compatibility macro in sys/apparmor.h); this patch does that as well as converting the type of the magic_token to long from int. (This patch is somewhat mooted by a later patch in the series to convert to using aa_change_hatv(), but would be a safer candidate for e.g. the 2.8 branch.) Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 019e339f0..c66a65bea 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -47,7 +47,7 @@ #endif module AP_MODULE_DECLARE_DATA apparmor_module; -static unsigned int magic_token = 0; +static unsigned long magic_token = 0; static int inside_default_hat = 0; typedef struct { @@ -94,9 +94,9 @@ immunix_child_init (apr_pool_t *p, server_rec *s) int ret; ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "init: calling change_hat"); - ret = change_hat (DEFAULT_HAT, magic_token); + ret = aa_change_hat(DEFAULT_HAT, magic_token); if (ret < 0) { - change_hat (NULL, magic_token); + aa_change_hat(NULL, magic_token); ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to change_hat to '%s'", DEFAULT_HAT); } else { @@ -145,41 +145,41 @@ immunix_enter_hat (request_rec *r) return OK; if (inside_default_hat) { - change_hat (NULL, magic_token); + aa_change_hat(NULL, magic_token); inside_default_hat = 0; } if (dcfg != NULL && dcfg->hat_name != NULL) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [dcfg] %s", dcfg->hat_name); - sd_ret = change_hat (dcfg->hat_name, magic_token); + sd_ret = aa_change_hat(dcfg->hat_name, magic_token); if (sd_ret < 0) { - change_hat (NULL, magic_token); + aa_change_hat(NULL, magic_token); } else { return OK; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [uri] %s", r->uri); - sd_ret = change_hat (r->uri, magic_token); + sd_ret = aa_change_hat(r->uri, magic_token); if (sd_ret < 0) { - change_hat (NULL, magic_token); + aa_change_hat(NULL, magic_token); } else { return OK; } if (scfg != NULL && scfg->hat_name != NULL) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [scfg] %s", scfg->hat_name); - sd_ret = change_hat (scfg->hat_name, magic_token); + sd_ret = aa_change_hat(scfg->hat_name, magic_token); if (sd_ret < 0) { - change_hat (NULL, magic_token); + aa_change_hat(NULL, magic_token); } else { return OK; } } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat DEFAULT_URI"); - sd_ret = change_hat (DEFAULT_URI_HAT, magic_token); - if (sd_ret < 0) change_hat (NULL, magic_token); + sd_ret = aa_change_hat(DEFAULT_URI_HAT, magic_token); + if (sd_ret < 0) aa_change_hat(NULL, magic_token); return OK; } @@ -193,11 +193,11 @@ immunix_exit_hat (request_rec *r) /* immunix_srv_cfg * scfg = (immunix_srv_cfg *) ap_get_module_config (r->server->module_config, &apparmor_module); */ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "exiting change_hat - dir hat %s path %s", dcfg->hat_name, dcfg->path); - change_hat (NULL, magic_token); + aa_change_hat(NULL, magic_token); - sd_ret = change_hat (DEFAULT_HAT, magic_token); + sd_ret = aa_change_hat(DEFAULT_HAT, magic_token); if (sd_ret < 0) { - change_hat (NULL, magic_token); + aa_change_hat(NULL, magic_token); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to change_hat to '%s'", DEFAULT_HAT); } else { From 124f59809041322ad38fc7f0ac70acde82b76726 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:45:00 -0800 Subject: [PATCH 101/183] mod_apparmor: improve initial and exit aa_change_hat call log message This patch adds the name of the hat to the log message about the initial aa_change_hat call, just to be explicit about what's happening when debugging and changes the formatting slightly of the exiting change_hat log message. Patch history: v1: initial version v2: tweak output of exit trace message Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index c66a65bea..351b64c1f 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -93,7 +93,8 @@ immunix_child_init (apr_pool_t *p, server_rec *s) { int ret; - ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "init: calling change_hat"); + ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, + "init: calling change_hat with '%s'", DEFAULT_HAT); ret = aa_change_hat(DEFAULT_HAT, magic_token); if (ret < 0) { aa_change_hat(NULL, magic_token); @@ -192,7 +193,8 @@ immunix_exit_hat (request_rec *r) ap_get_module_config (r->per_dir_config, &apparmor_module); /* immunix_srv_cfg * scfg = (immunix_srv_cfg *) ap_get_module_config (r->server->module_config, &apparmor_module); */ - ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "exiting change_hat - dir hat %s path %s", dcfg->hat_name, dcfg->path); + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "exiting change_hat: dir hat %s dir path %s", + dcfg->hat_name, dcfg->path); aa_change_hat(NULL, magic_token); sd_ret = aa_change_hat(DEFAULT_HAT, magic_token); From 1a008da2952c77aaea57088aa74a0db0bd0d6940 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:46:17 -0800 Subject: [PATCH 102/183] mod_apparmor: fix AADefaultHatName storage When defining an AADefaultHatName entry, it was being stored in the passed mconfig location, which is not the module specific server config, but instead the top level (i.e. no path defined) default directory/location config. This would be superceded by a more specific directory config if it applied to the request. Thus, if an AAHatName was defined that applied, but the named hat was not defined in the apparmor policy, mod_apparmor would not attempt to fall back to the defined AADefaultHatName, but instead jump directly to trying the DEFAULT_URI hat. This patch fixes it by storing the defined AADefaultHatName correctly in the module specific storage in the related server data structure. It also adds a bit of developer debugging statements. Signed-off-by: Steve Beattie Acked-by: John Johansen Bug: https://launchpad.net/bugs/1207424 --- changehat/mod_apparmor/mod_apparmor.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 351b64c1f..62246d534 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -168,6 +168,13 @@ immunix_enter_hat (request_rec *r) return OK; } + if (scfg) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Dumping scfg info: " + "scfg='0x%lx' scfg->hat_name='%s'", + (unsigned long) scfg, scfg->hat_name); + } else { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "scfg is null"); + } if (scfg != NULL && scfg->hat_name != NULL) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [scfg] %s", scfg->hat_name); sd_ret = aa_change_hat(scfg->hat_name, magic_token); @@ -241,7 +248,8 @@ aa_cmd_ch_srv (cmd_parms * cmd, void * mconfig, const char * parm1) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ap_server_conf, "server config change hat %s", parm1 ? parm1 : "DEFAULT"); - immunix_srv_cfg * scfg = mconfig; + immunix_srv_cfg * scfg = (immunix_srv_cfg *) + ap_get_module_config(cmd->server->module_config, &apparmor_module); if (parm1 != NULL) { scfg->hat_name = parm1; } else { From 8250e061d4293f278a2eb63ed4cf1ec1fa74aca1 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 13:51:34 -0800 Subject: [PATCH 103/183] mod_apparmor: make the ServerName be the default AADefaultHatName Bug: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1207424 This patch makes the default value for AADefaultHatName be the server/vhost name, which can be specified in apache via the ServerName configuration declaration. It can be overridden by setting AADefaultHatName directly. Thus, with this patch applied, the order of attempted hats will be: 1. try to aa_change_hat(2) into a matching AAHatName hat if it exists and applies, otherwise 2. try to aa_change_hat(2) into the URI itself, otherwise 3. try to aa_change_hat(2) into the value of ServerName, unless AADefaultHatName has been explicitly set for this server/vhost, in which case that value will be used, otherwise 4. try to aa_change_hat(2) into the DEFAULT_URI hat, if it exists, otherwise 5. fall back to the global Apache policy This should eliminate the need for most admins to define both ServerName and AADefaultHatName, unless there's a specific need for the values to deviate. Man page documentation is updated as well, though probably more wordsmithing is needed there for clarity. Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 24 ++++++++++++++------- changehat/mod_apparmor/mod_apparmor.pod | 28 ++++++++++++++----------- 2 files changed, 33 insertions(+), 19 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 62246d534..2ce0b175e 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -175,13 +175,23 @@ immunix_enter_hat (request_rec *r) } else { ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "scfg is null"); } - if (scfg != NULL && scfg->hat_name != NULL) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [scfg] %s", scfg->hat_name); - sd_ret = aa_change_hat(scfg->hat_name, magic_token); - if (sd_ret < 0) { - aa_change_hat(NULL, magic_token); - } else { - return OK; + if (scfg != NULL) { + if (scfg->hat_name != NULL) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [scfg] %s", scfg->hat_name); + sd_ret = aa_change_hat(scfg->hat_name, magic_token); + if (sd_ret < 0) { + aa_change_hat(NULL, magic_token); + } else { + return OK; + } + } else { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat w/server_name %s", r->server->server_hostname); + sd_ret = aa_change_hat(r->server->server_hostname, magic_token); + if (sd_ret < 0) { + aa_change_hat(NULL, magic_token); + } else { + return OK; + } } } diff --git a/changehat/mod_apparmor/mod_apparmor.pod b/changehat/mod_apparmor/mod_apparmor.pod index d9755f8cf..7de7bc676 100644 --- a/changehat/mod_apparmor/mod_apparmor.pod +++ b/changehat/mod_apparmor/mod_apparmor.pod @@ -41,10 +41,12 @@ apparmor is also functioning. Once mod_apparmor is loaded within Apache, all requests to Apache will cause mod_apparmor to attempt to change into a hat named by the URI -(e.g. /app/some.cgi). If no such hat is found, it will fall back to +(e.g. /app/some.cgi). If no such hat is found, it will first fall +back by attempting to change into a hat that matches the ServerName +for the server/vhost. If that hat is not found, it will fall back to attempting to use the hat DEFAULT_URI; if that also does not exist, -it will fall back to using the global Apache profile. Most static web -pages can simply make use of the DEFAULT_URI hat. +it will fall back to using the global Apache profile. Most static +web pages can simply make use of the DEFAULT_URI hat. Additionally, before any requests come in to Apache, mod_apparmor will attempt to change hat into the HANDLING_UNTRUSTED_INPUT hat. @@ -70,13 +72,14 @@ behavior described above. =item B -AADefaultHatName allows you to specify a default hat to be used for -virtual hosts and other Apache server directives, so that you can have -different defaults for different virtual hosts. This can be overridden by -the AAHatName directive and is checked for only if there isn't a matching -AAHatName or hat named by the URI. If the AADefaultHatName hat does not -exist, it falls back to the DEFAULT_URI hat if it exists (as described -above). +AADefaultHatName allows you to specify a default hat to be used +for virtual hosts and other Apache server directives, so that you +can have different defaults for different virtual hosts. This can +be overridden by the AAHatName directive and is checked for only if +there isn't a matching AAHatName or hat named by the URI. The default +value of AADefaultHatName is the ServerName for the server/vhost +configuration. If the AADefaultHatName hat does not exist, it falls +back to the DEFAULT_URI hat if it exists (as described above). =back @@ -98,8 +101,9 @@ applies, otherwise it will 2. try to aa_change_hat(2) into the URI itself, otherwise it will -3. try to aa_change_hat(2) into an AADefaultHatName hat if it has been defined -for the server/vhost, otherwise it will +3. try to aa_change_hat(2) into an AADefaultHatName hat, either the +ServerName (the default) or the configuration value specified by the +AADefaultHatName directive, for the server/vhost, otherwise it will 4. try to aa_change_hat(2) into the DEFAULT_URI hat, if it exists, otherwise it will From c98f54ecdca66bc09d23a7a4609cff54d925a597 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 14:08:46 -0800 Subject: [PATCH 104/183] mod_apparmor: convert aa_change_hat()s into single aa_change_hatv() This patch converts the request entry point from using multiple (if necessary) aa_change_hat() calls into a single aa_change_hatv() call, simplifying the code a bit, requiring fewer round trips between mod_apparmor and the kernel for each request, as well as providing more information when the apache profile is in complain mode. Patch history: v1: initial version v2: - the server config (scfg) code accidentally re-added the directory config (dcfg) hat to the vector of hats, fix that - actually add the DEFAULT_URI hat to the vector of hats, instead of only logging that that is happening. - pass errno to ap_log_rerror() if aa_change_hatv() call fails. - don't call aa_change_hat again if aa_change_hatv() call fails, as this is no longer necessary. v3: - Based on feedback from jjohansen, convert exit point aa_change_hat() call to aa_change_hatv(), in order to work around aa_change_hat() bug addressed in trunk rev 2329, which causes the exiting aa_change_hat() call to fail and results in the apache process being killed by the kernel. When it's no longer likely that mod_apparmor could run into a system libapparmor that still contains this bug, this can be converted back. Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 60 +++++++++++++-------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 2ce0b175e..768212963 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -135,6 +135,8 @@ immunix_enter_hat (request_rec *r) ap_get_module_config (r->per_dir_config, &apparmor_module); immunix_srv_cfg * scfg = (immunix_srv_cfg *) ap_get_module_config (r->server->module_config, &apparmor_module); + const char *aa_hat_array[5] = { NULL, NULL, NULL, NULL, NULL }; + int i = 0; debug_dump_uri(r); ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx", @@ -151,22 +153,14 @@ immunix_enter_hat (request_rec *r) } if (dcfg != NULL && dcfg->hat_name != NULL) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [dcfg] %s", dcfg->hat_name); - sd_ret = aa_change_hat(dcfg->hat_name, magic_token); - if (sd_ret < 0) { - aa_change_hat(NULL, magic_token); - } else { - return OK; - } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[dcfg] adding hat '%s' to aa_change_hat vector", dcfg->hat_name); + aa_hat_array[i++] = dcfg->hat_name; } - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [uri] %s", r->uri); - sd_ret = aa_change_hat(r->uri, magic_token); - if (sd_ret < 0) { - aa_change_hat(NULL, magic_token); - } else { - return OK; - } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[uri] adding uri '%s' to aa_change_hat vector", r->uri); + aa_hat_array[i++] = r->uri; if (scfg) { ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "Dumping scfg info: " @@ -177,27 +171,25 @@ immunix_enter_hat (request_rec *r) } if (scfg != NULL) { if (scfg->hat_name != NULL) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat [scfg] %s", scfg->hat_name); - sd_ret = aa_change_hat(scfg->hat_name, magic_token); - if (sd_ret < 0) { - aa_change_hat(NULL, magic_token); - } else { - return OK; - } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[scfg] adding hat '%s' to aa_change_hat vector", scfg->hat_name); + aa_hat_array[i++] = scfg->hat_name; } else { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat w/server_name %s", r->server->server_hostname); - sd_ret = aa_change_hat(r->server->server_hostname, magic_token); - if (sd_ret < 0) { - aa_change_hat(NULL, magic_token); - } else { - return OK; - } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[scfg] adding server_name '%s' to aa_change_hat vector", + r->server->server_hostname); + aa_hat_array[i++] = r->server->server_hostname; } } - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "calling change_hat DEFAULT_URI"); - sd_ret = aa_change_hat(DEFAULT_URI_HAT, magic_token); - if (sd_ret < 0) aa_change_hat(NULL, magic_token); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, + "[default] adding '%s' to aa_change_hat vector", DEFAULT_URI_HAT); + aa_hat_array[i++] = DEFAULT_URI_HAT; + + sd_ret = aa_change_hatv(aa_hat_array, magic_token); + if (sd_ret < 0) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, r, "aa_change_hatv call failed"); + } return OK; } @@ -212,7 +204,11 @@ immunix_exit_hat (request_rec *r) ap_get_module_config (r->server->module_config, &apparmor_module); */ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "exiting change_hat: dir hat %s dir path %s", dcfg->hat_name, dcfg->path); - aa_change_hat(NULL, magic_token); + + /* can convert the following back to aa_change_hat() when the + * aa_change_hat() bug addressed in trunk commit 2329 lands in most + * system libapparmors */ + aa_change_hatv(NULL, magic_token); sd_ret = aa_change_hat(DEFAULT_HAT, magic_token); if (sd_ret < 0) { From 6fd2f36bd8ac072b53eab26e917211fc265b2d81 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 14:42:00 -0800 Subject: [PATCH 105/183] mod_apparmor: add logging for AAHatName/AADefaultHatName policy misconfig This patch adds code that checks the resulting hat that apache gets placed into, and verifies that if the apache configuration specified that an AAHatName or AADefaultHatName should have been the resulting hat. If it wasn't, emit a warning message to the apache log, as this likely indicates a mismatch between the apache configuration and its apparmor policy (i.e. why define AAHatName if you aren't going to create the corresponding hat in the apparmor policy?) Note for AADefaultHatName, a message is not logged if a defined AAHatName would also apply or if there is a hat defined for the uri, as each of those come first in the order of attempted hats. Also note that the way the hat name is manually calculated will break for nested profiles and stacking. It should be fine for all current deployments as we don't allow nesting beyond the first subprofile level in policy yet. And stacking will likely only be used between namespaces where aa_getcon() will not report parent namespace info. However, when libapparmor adds functionality to query the hatname, the code that computes it here should be replaced by a call to that library function. Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 768212963..83c7d500a 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -137,6 +137,7 @@ immunix_enter_hat (request_rec *r) ap_get_module_config (r->server->module_config, &apparmor_module); const char *aa_hat_array[5] = { NULL, NULL, NULL, NULL, NULL }; int i = 0; + char *aa_con, *aa_mode, *aa_hat; debug_dump_uri(r); ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "in immunix_enter_hat (%s) n:0x%lx p:0x%lx main:0x%lx", @@ -191,6 +192,37 @@ immunix_enter_hat (request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, r, "aa_change_hatv call failed"); } + /* Check to see if a defined AAHatName or AADefaultHatName would + * apply, but wasn't the hat we landed up in; report a warning if + * that's the case. */ + sd_ret = aa_getcon(&aa_con, &aa_mode); + if (sd_ret < 0) { + ap_log_rerror(APLOG_MARK, APLOG_WARNING, errno, r, "aa_getcon call failed"); + } else { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "AA checks: aa_getcon result is '%s', mode '%s'", aa_con, aa_mode); + /* TODO: use libapparmor get hat_name fn here once it is implemented */ + aa_hat = strstr(aa_con, "//"); + if (aa_hat != NULL && strcmp(aa_mode, "enforce") == 0) { + aa_hat += 2; /* skip "//" */ + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, + "AA checks: apache is in hat '%s', mode '%s'", aa_hat, aa_mode); + if (dcfg != NULL && dcfg->hat_name != NULL) { + if (strcmp(aa_hat, dcfg->hat_name) != 0) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "AAHatName '%s' applies, but does not appear to be a hat in the apache apparmor policy", + dcfg->hat_name); + } else if (scfg != NULL && scfg->hat_name != NULL) { + if (strcmp(aa_hat, scfg->hat_name) != 0 && + strcmp(aa_hat, r->uri) != 0) + ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, + "AADefaultHatName '%s' applies, but does not appear to be a hat in the apache apparmor policy", + scfg->hat_name); + } + } + free(aa_con); + } + return OK; } From 016e1f1b193b3a2f42c2591c09315946a4a62849 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 14:44:24 -0800 Subject: [PATCH 106/183] mod_apparmor: eliminate unnecessary back out aa_change_hat() calls This patch removes unnecessary back out aa_change_hat() calls that occur if the prior call to aa_change_hat() call failed. It used to be case that an aa_change_hat() call that failed would result in the task being placed in a profile with no permissions except the ability to aa_change_hat() back out, but this behavior has been removed from apparmor for many, many years now. Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index 83c7d500a..b3eb928f2 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -97,7 +97,6 @@ immunix_child_init (apr_pool_t *p, server_rec *s) "init: calling change_hat with '%s'", DEFAULT_HAT); ret = aa_change_hat(DEFAULT_HAT, magic_token); if (ret < 0) { - aa_change_hat(NULL, magic_token); ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to change_hat to '%s'", DEFAULT_HAT); } else { @@ -244,7 +243,6 @@ immunix_exit_hat (request_rec *r) sd_ret = aa_change_hat(DEFAULT_HAT, magic_token); if (sd_ret < 0) { - aa_change_hat(NULL, magic_token); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to change_hat to '%s'", DEFAULT_HAT); } else { From 52b34589721695091f13c0839923bea9befde8cc Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 23 Jan 2014 14:50:07 -0800 Subject: [PATCH 107/183] mod_apparmor: include errno in log messages for failures This patch includes the errno in the log messages generated by two different failed aa_change_hat() calls and the failure to open /dev/urandom to get the random token, to further ease failure diagnosis. Signed-off-by: Steve Beattie Acked-by: John Johansen --- changehat/mod_apparmor/mod_apparmor.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/changehat/mod_apparmor/mod_apparmor.c b/changehat/mod_apparmor/mod_apparmor.c index b3eb928f2..e6d283233 100644 --- a/changehat/mod_apparmor/mod_apparmor.c +++ b/changehat/mod_apparmor/mod_apparmor.c @@ -78,7 +78,8 @@ immunix_init (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) apr_file_read (file, (void *) &magic_token, &size); apr_file_close (file); } else { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to open /dev/urandom"); + ap_log_error(APLOG_MARK, APLOG_ERR, errno, ap_server_conf, + "Failed to open /dev/urandom"); } ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, ap_server_conf, "Opened /dev/urandom successfully"); @@ -97,8 +98,8 @@ immunix_child_init (apr_pool_t *p, server_rec *s) "init: calling change_hat with '%s'", DEFAULT_HAT); ret = aa_change_hat(DEFAULT_HAT, magic_token); if (ret < 0) { - ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, "Failed to change_hat to '%s'", - DEFAULT_HAT); + ap_log_error(APLOG_MARK, APLOG_ERR, errno, ap_server_conf, + "Failed to change_hat to '%s'", DEFAULT_HAT); } else { inside_default_hat = 1; } @@ -243,8 +244,8 @@ immunix_exit_hat (request_rec *r) sd_ret = aa_change_hat(DEFAULT_HAT, magic_token); if (sd_ret < 0) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Failed to change_hat to '%s'", - DEFAULT_HAT); + ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r, + "Failed to change_hat to '%s'", DEFAULT_HAT); } else { inside_default_hat = 1; } From fdd89f1da5386aa7f7304c985220381e6d5a711c Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:19:59 -0800 Subject: [PATCH 108/183] parser: eliminate bison warning This patch eliminates the bison warning about "%name-prefix =" being deprecated. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/libapparmor_re/parse.y | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/libapparmor_re/parse.y b/parser/libapparmor_re/parse.y index b29523fde..b410437b7 100644 --- a/parser/libapparmor_re/parse.y +++ b/parser/libapparmor_re/parse.y @@ -60,7 +60,7 @@ static inline Chars* insert_char_range(Chars* cset, uchar a, uchar b) %lex-param {YYLEX_PARAM} %parse-param {Node **root} %parse-param {const char *text} -%name-prefix = "regex_" +%name-prefix "regex_" %token CHAR %type regex_char cset_char1 cset_char cset_charN From 5f18a7c237da47cf81dc85693692daffc83d8fa4 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:21:30 -0800 Subject: [PATCH 109/183] parser: remove unneeded vars/allocations in regex unit tests Based on feedback from Seth Arnold, the convert_aaregex_to_pcre()'s first argument is const char *, and thus the unit test macros don't need to pass a copy of the input string to it, as it's guaranteed to be unmodified by the function. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_regex.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/parser/parser_regex.c b/parser/parser_regex.c index 0cc67285f..41c054864 100644 --- a/parser/parser_regex.c +++ b/parser/parser_regex.c @@ -1252,41 +1252,36 @@ static int test_filter_slashes(void) do { \ std::string tbuf; \ std::string tbuf2 = "testprefix"; \ - char *test_string; \ char *output_string = NULL; \ std::string expected_str2; \ pattern_t ptype; \ int pos; \ \ - test_string = strdup((input)); \ - ptype = convert_aaregex_to_pcre(test_string, 0, tbuf, &pos); \ + ptype = convert_aaregex_to_pcre((input), 0, tbuf, &pos); \ asprintf(&output_string, "simple regex conversion for '%s'\texpected = '%s'\tresult = '%s'", \ - (input), expected_str, tbuf.c_str()); \ + (input), (expected_str), tbuf.c_str()); \ MY_TEST(strcmp(tbuf.c_str(), (expected_str)) == 0, output_string); \ MY_TEST(ptype == (expected_type), "simple regex conversion type check for '" input "'"); \ free(output_string); \ /* ensure convert_aaregex_to_pcre appends only to passed ref string */ \ expected_str2 = tbuf2; \ expected_str2.append((expected_str)); \ - ptype = convert_aaregex_to_pcre(test_string, 0, tbuf2, &pos); \ + ptype = convert_aaregex_to_pcre((input), 0, tbuf2, &pos); \ asprintf(&output_string, "simple regex conversion for '%s'\texpected = '%s'\tresult = '%s'", \ (input), expected_str2.c_str(), tbuf2.c_str()); \ MY_TEST((tbuf2 == expected_str2), output_string); \ - free(test_string); free(output_string); \ + free(output_string); \ } \ while (0) #define MY_REGEX_FAIL_TEST(input) \ do { \ std::string tbuf; \ - char *test_string; \ pattern_t ptype; \ int pos; \ \ - test_string = strdup((input)); \ - ptype = convert_aaregex_to_pcre(test_string, 0, tbuf, &pos); \ + ptype = convert_aaregex_to_pcre((input), 0, tbuf, &pos); \ MY_TEST(ptype == ePatternInvalid, "simple regex conversion invalid type check for '" input "'"); \ - free(test_string); \ } \ while (0) From 6e701f798f86825651db1dfc169e88293c314ae3 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:25:47 -0800 Subject: [PATCH 110/183] parser: remove static sized buffer in process_dbus_entry() This patch converts a stack allocated buffer into an std::ostringstream object. The stringstream interface for specifying the equivalent of a printf %02x conversion is a bit of an awkward construction, however. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_regex.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/parser/parser_regex.c b/parser/parser_regex.c index 41c054864..30d1d1ec0 100644 --- a/parser/parser_regex.c +++ b/parser/parser_regex.c @@ -24,7 +24,10 @@ #include #define _(s) gettext(s) +#include #include +#include + /* #define DEBUG */ @@ -1015,7 +1018,7 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry std::string pathbuf; std::string ifacebuf; std::string memberbuf; - char buffer[128]; + std::ostringstream buffer; const char *vec[6]; pattern_t ptype; @@ -1024,8 +1027,8 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry if (!entry) /* shouldn't happen */ return TRUE; - sprintf(buffer, "\\x%02x", AA_CLASS_DBUS); - busbuf.append(buffer); + buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_DBUS; + busbuf.append(buffer.str()); if (entry->bus) { ptype = convert_aaregex_to_pcre(entry->bus, 0, busbuf, &pos); From 39564bbdf567a8f59e49c8c04df82518233f9879 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:27:58 -0800 Subject: [PATCH 111/183] parser: remove unneeded e_buffer_overflow As noted by Seth Arnold, e_buffer_overflow is no longer set in convert_aaregex_to_pcre(), so remove it and the sole check for it. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_regex.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/parser/parser_regex.c b/parser/parser_regex.c index 30d1d1ec0..40c6e189c 100644 --- a/parser/parser_regex.c +++ b/parser/parser_regex.c @@ -42,7 +42,6 @@ enum error_type { e_no_error, e_parse_error, - e_buffer_overflow }; /* Filters out multiple slashes (except if the first two are slashes, @@ -377,11 +376,6 @@ static pattern_t convert_aaregex_to_pcre(const char *aare, int anchor, } /* check error again, as above STORE may have set it */ if (error != e_no_error) { - if (error == e_buffer_overflow) { - PERROR(_("%s: Internal buffer overflow detected, %d characters exceeded\n"), - progname, PATH_MAX); - } - PERROR(_("%s: Unable to parse input line '%s'\n"), progname, aare); From 78fe398a2f3a7fe46eea7049505fe02e7761a7b4 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:30:08 -0800 Subject: [PATCH 112/183] parser: replace reverse iterator As suggested by Seth Arnold, we can use string::find_last_not_of() instead of using C++'s hideous reverse iterators. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_variable.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/parser/parser_variable.c b/parser/parser_variable.c index 8d50a264d..83e35d51a 100644 --- a/parser/parser_variable.c +++ b/parser/parser_variable.c @@ -137,11 +137,11 @@ void free_var_string(struct var_string *var) static void trim_trailing_slash(std::string& str) { - for (std::string::reverse_iterator rit = str.rbegin(); - rit != str.rend() && *rit == '/'; ++rit) { - /* yuck, reverse_iterators are ugly */ - str.erase(--rit.base()); - } + std::size_t found = str.find_last_not_of('/'); + if (found != std::string::npos) + str.erase(found + 1); + else + str.clear(); // str is all '/' } static void write_replacement(const char separator, const char* value, From d4c8971b650aaa4e4af62e1f77ed3c87f4976375 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:45:48 -0800 Subject: [PATCH 113/183] parser: pull forward free() calls As noted by Seth Arnold, in expand_by_alternations() if our set variable has at least one value, then we're going to rewrite the entry, so rather than sprinkle the free()s near where the reallocation occurs, use one free() once we're guaranteed to need to do so. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_variable.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/parser_variable.c b/parser/parser_variable.c index 83e35d51a..1e76258aa 100644 --- a/parser/parser_variable.c +++ b/parser/parser_variable.c @@ -177,10 +177,11 @@ static int expand_by_alternations(struct set_value **valuelist, exit(1); } + free(*name); + value = get_next_set_value(valuelist); if (!value) { /* only one entry for the variable, so just sub it in */ - free(*name); if (asprintf(name, "%s%s%s", split_var->prefix ? split_var->prefix : "", first_value, @@ -201,7 +202,6 @@ static int expand_by_alternations(struct set_value **valuelist, write_replacement(',', value, replacement, filter_leading_slash, filter_trailing_slash); } - free(*name); if (asprintf(name, "%s%s}%s", split_var->prefix ? split_var->prefix : "", replacement.c_str(), From 8237c6fb28fcf8e040d33b9af0eec6cfbc09a567 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:47:42 -0800 Subject: [PATCH 114/183] parser: simplify handling of default matching patterns Seth Arnold noticed an ugly string.clear(); convert_entry(string, NULL) pattern occurred frequently following the conversion to using std::string. This patch replaces that by using a static pointer to a constant string matching pattern, and also converts other uses of that pattern. It also adds a function wrapper that will clear the passed buffer before calling convert_entry(). Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_regex.c | 66 ++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/parser/parser_regex.c b/parser/parser_regex.c index 40c6e189c..8de30bb75 100644 --- a/parser/parser_regex.c +++ b/parser/parser_regex.c @@ -44,6 +44,9 @@ enum error_type { e_parse_error, }; +/* match any char except \000 0 or more times */ +static const char *default_match_pattern = "[^\\000]*"; + /* Filters out multiple slashes (except if the first two are slashes, * that's a distinct namespace in linux) and trailing slashes. * NOTE: modifies in place the contents of the path argument */ @@ -631,7 +634,7 @@ static int build_list_val_expr(std::string& buffer, struct value_list *list) int pos; if (!list) { - buffer.append("[^\\000]*"); + buffer.append(default_match_pattern); return TRUE; } @@ -664,12 +667,18 @@ static int convert_entry(std::string& buffer, char *entry) if (ptype == ePatternInvalid) return FALSE; } else { - buffer.append("[^\\000]*"); + buffer.append(default_match_pattern); } return TRUE; } +static int clear_and_convert_entry(std::string& buffer, char *entry) +{ + buffer.clear(); + return convert_entry(buffer, entry); +} + static int build_mnt_flags(char *buffer, int size, unsigned int flags, unsigned int inv_flags) { @@ -678,7 +687,7 @@ static int build_mnt_flags(char *buffer, int size, unsigned int flags, if (flags == MS_ALL_FLAGS) { /* all flags are optional */ - len = snprintf(p, size, "[^\\000]*"); + len = snprintf(p, size, "%s", default_match_pattern); if (len < 0 || len >= size) return FALSE; return TRUE; @@ -718,7 +727,7 @@ static int build_mnt_opts(std::string& buffer, struct value_list *opts) int pos; if (!opts) { - buffer.append("[^\\000]*"); + buffer.append(default_match_pattern); return TRUE; } @@ -769,12 +778,9 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry) vec[0] = mntbuf.c_str(); } /* skip device */ - devbuf.clear(); - if (!convert_entry(devbuf, NULL)) - goto fail; - vec[1] = devbuf.c_str(); + vec[1] = default_match_pattern; /* skip type */ - vec[2] = devbuf.c_str(); + vec[2] = default_match_pattern; flags = entry->flags; inv_flags = entry->inv_flags; @@ -820,14 +826,11 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry) if (!convert_entry(mntbuf, entry->mnt_point)) goto fail; vec[0] = mntbuf.c_str(); - devbuf.clear(); - if (!convert_entry(devbuf, entry->device)) + if (!clear_and_convert_entry(devbuf, entry->device)) goto fail; vec[1] = devbuf.c_str(); - typebuf.clear(); - if (!convert_entry(typebuf, NULL)) - goto fail; - vec[2] = typebuf.c_str(); + /* skip type */ + vec[2] = default_match_pattern; flags = entry->flags; inv_flags = entry->inv_flags; @@ -855,11 +858,8 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry) goto fail; vec[0] = mntbuf.c_str(); /* skip device and type */ - devbuf.clear(); - if (!convert_entry(devbuf, NULL)) - goto fail; - vec[1] = devbuf.c_str(); - vec[2] = devbuf.c_str(); + vec[1] = default_match_pattern; + vec[2] = default_match_pattern; flags = entry->flags; inv_flags = entry->inv_flags; @@ -885,15 +885,11 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry) if (!convert_entry(mntbuf, entry->mnt_point)) goto fail; vec[0] = mntbuf.c_str(); - devbuf.clear(); - if (!convert_entry(devbuf, entry->device)) + if (!clear_and_convert_entry(devbuf, entry->device)) goto fail; vec[1] = devbuf.c_str(); /* skip type */ - typebuf.clear(); - if (!convert_entry(typebuf, NULL)) - goto fail; - vec[2] = typebuf.c_str(); + vec[2] = default_match_pattern; flags = entry->flags; inv_flags = entry->inv_flags; @@ -920,8 +916,7 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry) if (!convert_entry(mntbuf, entry->mnt_point)) goto fail; vec[0] = mntbuf.c_str(); - devbuf.clear(); - if (!convert_entry(devbuf, entry->device)) + if (!clear_and_convert_entry(devbuf, entry->device)) goto fail; vec[1] = devbuf.c_str(); typebuf.clear(); @@ -982,8 +977,7 @@ static int process_mnt_entry(aare_ruleset_t *dfarules, struct mnt_entry *entry) if (!convert_entry(mntbuf, entry->mnt_point)) goto fail; vec[0] = mntbuf.c_str(); - devbuf.clear(); - if (!convert_entry(devbuf, entry->device)) + if (!clear_and_convert_entry(devbuf, entry->device)) goto fail; vec[1] = devbuf.c_str(); if (!aare_add_rule_vec(dfarules, entry->deny, entry->allow, @@ -1030,7 +1024,7 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry goto fail; } else { /* match any char except \000 0 or more times */ - busbuf.append("[^\\000]*"); + busbuf.append(default_match_pattern); } vec[0] = busbuf.c_str(); @@ -1041,7 +1035,7 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry vec[1] = namebuf.c_str(); } else { /* match any char except \000 0 or more times */ - vec[1] = "[^\\000]*"; + vec[1] = default_match_pattern; } if (entry->peer_label) { @@ -1052,7 +1046,7 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry vec[2] = peer_labelbuf.c_str(); } else { /* match any char except \000 0 or more times */ - vec[2] = "[^\\000]*"; + vec[2] = default_match_pattern; } if (entry->path) { @@ -1062,7 +1056,7 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry vec[3] = pathbuf.c_str(); } else { /* match any char except \000 0 or more times */ - vec[3] = "[^\\000]*"; + vec[3] = default_match_pattern; } if (entry->interface) { @@ -1072,7 +1066,7 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry vec[4] = ifacebuf.c_str(); } else { /* match any char except \000 0 or more times */ - vec[4] = "[^\\000]*"; + vec[4] = default_match_pattern; } if (entry->member) { @@ -1082,7 +1076,7 @@ static int process_dbus_entry(aare_ruleset_t *dfarules, struct dbus_entry *entry vec[5] = memberbuf.c_str(); } else { /* match any char except \000 0 or more times */ - vec[5] = "[^\\000]*"; + vec[5] = default_match_pattern; } if (entry->mode & AA_DBUS_BIND) { From fb3baeaf23971f75f06363914d0c6b77600550c8 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:58:06 -0800 Subject: [PATCH 115/183] parser: fix memory leak on calloc() failure Fix leaked memory if calloc() fails. Found by cppcheck. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_alias.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/parser/parser_alias.c b/parser/parser_alias.c index aea9f1af4..c3e03d58d 100644 --- a/parser/parser_alias.c +++ b/parser/parser_alias.c @@ -177,8 +177,10 @@ static void process_name(const void *nodep, VISIT value, int __unused level) return; /* aliases create alternate names */ alt = (struct alt_name *) calloc(1, sizeof(struct alt_name)); - if (!alt) + if (!alt) { + free(n); return; + } alt->name = n; alt->next = prof->altnames; prof->altnames = alt; From 1fd3b5ed5aa7bada690ac44fc1734e2757354c0d Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 10:59:30 -0800 Subject: [PATCH 116/183] parser: close file handle left opened Close file handle left opened if parser.cfg is found and read from. Found by cppcheck. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/parser/parser_main.c b/parser/parser_main.c index f6cf6cc3b..218ce249b 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -611,6 +611,7 @@ static int process_config_file(const char *name) while ((c = getopt_long_file(f, long_options, &optarg, &o)) != -1) process_arg(c, optarg); + fclose(f); return 1; } From f65368068fa982be5d24b069a28ce79d9e9f1e33 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 11:03:22 -0800 Subject: [PATCH 117/183] regression tests: minor dbus compilation cleanups This patch replaces explicitly named output targets with the make variable $@ as well as an instance where dbus_common.h was being added to the compile command line due to the use of $^ rather than $<. Signed-off-by: Steve Beattie Acked-by: John Johansen --- tests/regression/apparmor/Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/regression/apparmor/Makefile b/tests/regression/apparmor/Makefile index 1c4d01a5a..5ae5f45f3 100644 --- a/tests/regression/apparmor/Makefile +++ b/tests/regression/apparmor/Makefile @@ -186,16 +186,16 @@ changehat_pthread: changehat_pthread.c changehat.h ${CC} ${CFLAGS} ${LDFLAGS} $< -o $@ ${LDLIBS} -pthread dbus_common.o: dbus_common.c dbus_common.h - ${CC} ${CFLAGS} ${LDFLAGS} $^ -c ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) + ${CC} ${CFLAGS} ${LDFLAGS} $< -c ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) dbus_eavesdrop: dbus_eavesdrop.c dbus_common.o - ${CC} ${CFLAGS} ${LDFLAGS} $^ -o dbus_eavesdrop ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) + ${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) dbus_message: dbus_message.c dbus_common.o - ${CC} ${CFLAGS} ${LDFLAGS} $^ -o dbus_message ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) + ${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) dbus_service: dbus_message dbus_service.c dbus_common.o - ${CC} ${CFLAGS} ${LDFLAGS} $(filter-out dbus_message, $^) -o dbus_service ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) + ${CC} ${CFLAGS} ${LDFLAGS} $(filter-out dbus_message, $^) -o $@ ${LDLIBS} $(shell pkg-config --cflags --libs dbus-1) tests: all @if [ `whoami` = "root" ] ;\ From 9bb81e1ed3e9bca4606d5d002a2efaa5d2726f73 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 11:06:31 -0800 Subject: [PATCH 118/183] parser: add rttime rlimit support This patch adds support for the rttime rlimit (aka RLIMIT_RTTIME), available since the 2.6.25 kernel, according to the getrlimit(2) man page; see that man page for more details on this rlimit. An acceptance test is also added, as well as an update to the apparmor.vim input template. While reviewing to see what made sense in apparmor.vim for the rttime rlimit, I discovered that RLIMIT_RTTIME's units are microseconds, not seconds like RLIMIT_CPU (according to the setrlimit(2) manpage). This necessitated not sharing the case switch with RLIMIT_CPU. I didn't add a keyword for microseconds, but I did for milliseconds. I also don't accept any unit larger than minutes, as it didn't seem appropriate (and even minutes felt... gratuitous). I would appreciate feedback on what keywords would be useful here. Patch History: v1: initial submission v2: - add apparmor.vim support for rttime keyword - adjust RLIMIT_TIME value assignment due to its units being microseconds, not seconds, and add milliseconds keyword. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_misc.c | 3 +++ parser/parser_yacc.y | 17 +++++++++++++++++ parser/tst/simple_tests/rlimits/ok_rlimit_18.sd | 7 +++++++ utils/vim/apparmor.vim.in | 3 ++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 parser/tst/simple_tests/rlimits/ok_rlimit_18.sd diff --git a/parser/parser_misc.c b/parser/parser_misc.c index 101ef2a22..2dfb1bb6b 100644 --- a/parser/parser_misc.c +++ b/parser/parser_misc.c @@ -174,6 +174,9 @@ static struct keyword_table rlimit_table[] = { #endif #ifdef RLIMIT_RTPRIO {"rtprio", RLIMIT_RTPRIO}, +#endif +#ifdef RLIMIT_RTTIME + {"rttime", RLIMIT_RTTIME}, #endif /* terminate */ {NULL, 0} diff --git a/parser/parser_yacc.y b/parser/parser_yacc.y index f17658c4c..498533d54 100644 --- a/parser/parser_yacc.y +++ b/parser/parser_yacc.y @@ -754,6 +754,7 @@ rules: rules TOK_SET TOK_RLIMIT TOK_ID TOK_LE TOK_VALUE TOK_END_OF_RULE value = RLIM_INFINITY; } else { const char *seconds = "seconds"; + const char *milliseconds = "ms"; const char *minutes = "minutes"; const char *hours = "hours"; const char *days = "days"; @@ -779,6 +780,22 @@ rules: rules TOK_SET TOK_RLIMIT TOK_ID TOK_LE TOK_VALUE TOK_END_OF_RULE yyerror("RLIMIT '%s' invalid value %s\n", $4, $6); } break; + case RLIMIT_RTTIME: + /* RTTIME is measured in microseconds */ + if (!end || $6 == end || tmp < 0) + yyerror("RLIMIT '%s' invalid value %s\n", $4, $6); + if (*end == '\0') { + value = tmp; + } else if (strstr(milliseconds, end) == milliseconds) { + value = tmp * 1000; + } else if (strstr(seconds, end) == seconds) { + value = tmp * 1000 * 1000; + } else if (strstr(minutes, end) == minutes) { + value = tmp * 1000 * 1000 * 60; + } else { + yyerror("RLIMIT '%s' invalid value %s\n", $4, $6); + } + break; case RLIMIT_NOFILE: case RLIMIT_NPROC: case RLIMIT_LOCKS: diff --git a/parser/tst/simple_tests/rlimits/ok_rlimit_18.sd b/parser/tst/simple_tests/rlimits/ok_rlimit_18.sd new file mode 100644 index 000000000..f2747f10d --- /dev/null +++ b/parser/tst/simple_tests/rlimits/ok_rlimit_18.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION simple realtime time rlimit test +#=EXRESULT PASS + +profile rlimit { + set rlimit rttime <= 60minutes, +} diff --git a/utils/vim/apparmor.vim.in b/utils/vim/apparmor.vim.in index 00df1c993..f03970f37 100644 --- a/utils/vim/apparmor.vim.in +++ b/utils/vim/apparmor.vim.in @@ -160,7 +160,8 @@ syn match sdRLimit /\v^\s*set\s+rlimit\s+(locks|sigpending)\s+\<\=\s+[0-9]+@@EOL syn match sdRLimit /\v^\s*set\s+rlimit\s+(fsize|data|stack|core|rss|as|memlock|msgqueue)\s+\<\=\s+[0-9]+([KMG]B)?@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+nice\s+\<\=\s+(-1?[0-9]|-20|1?[0-9])@@EOL@@/ contains=sdComment syn match sdRLimit /\v^\s*set\s+rlimit\s+cpu\s+\<\=\s+[0-9]+(seconds|minutes|hours|days)?@@EOL@@/ contains=sdComment -syn match sdRLimit /\v^\s*set\s+rlimit\s+(cpu|nofile|nproc|rtprio|locks|sigpending|fsize|data|stack|core|rss|as|memlock|msgqueue|nice)\s+\<\=\s+infinity@@EOL@@/ contains=sdComment +syn match sdRLimit /\v^\s*set\s+rlimit\s+rttime\s+\<\=\s+[0-9]+(ms|seconds|minutes)?@@EOL@@/ contains=sdComment +syn match sdRLimit /\v^\s*set\s+rlimit\s+(cpu|rttime|nofile|nproc|rtprio|locks|sigpending|fsize|data|stack|core|rss|as|memlock|msgqueue|nice)\s+\<\=\s+infinity@@EOL@@/ contains=sdComment " link rules syn match sdEntryW /\v^\s+@@auditdenyowner@@link\s+(subset\s+)?@@FILENAME@@\s+-\>\s+@@FILENAME@@@@EOL@@/ contains=sdGlob From cb679f3206566cdf37a80a764a3bdad47eb48cfc Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 24 Jan 2014 11:17:23 -0800 Subject: [PATCH 119/183] add keyword 'other' vim syntax support, plus language parsing tests Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/tst/simple_tests/file/allow/ok_other_1.sd | 7 +++++++ parser/tst/simple_tests/file/allow/ok_other_2.sd | 7 +++++++ parser/tst/simple_tests/file/ok_other_2.sd | 7 +++++++ parser/tst/simple_tests/file/ok_other_3.sd | 7 +++++++ utils/vim/create-apparmor.vim.py | 8 ++++---- 5 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 parser/tst/simple_tests/file/allow/ok_other_1.sd create mode 100644 parser/tst/simple_tests/file/allow/ok_other_2.sd create mode 100644 parser/tst/simple_tests/file/ok_other_2.sd create mode 100644 parser/tst/simple_tests/file/ok_other_3.sd diff --git a/parser/tst/simple_tests/file/allow/ok_other_1.sd b/parser/tst/simple_tests/file/allow/ok_other_1.sd new file mode 100644 index 000000000..4e2104ba6 --- /dev/null +++ b/parser/tst/simple_tests/file/allow/ok_other_1.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION simple allow other flag test +#=EXRESULT PASS + +profile test { + allow other /tmp/** rw, +} diff --git a/parser/tst/simple_tests/file/allow/ok_other_2.sd b/parser/tst/simple_tests/file/allow/ok_other_2.sd new file mode 100644 index 000000000..bc13ce045 --- /dev/null +++ b/parser/tst/simple_tests/file/allow/ok_other_2.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION simple audit allow other flag test +#=EXRESULT PASS + +profile test { + audit allow other /tmp/** rw, +} diff --git a/parser/tst/simple_tests/file/ok_other_2.sd b/parser/tst/simple_tests/file/ok_other_2.sd new file mode 100644 index 000000000..d2eeb7402 --- /dev/null +++ b/parser/tst/simple_tests/file/ok_other_2.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION simple deny other flag test +#=EXRESULT PASS + +profile test { + deny other /tmp/** rw, +} diff --git a/parser/tst/simple_tests/file/ok_other_3.sd b/parser/tst/simple_tests/file/ok_other_3.sd new file mode 100644 index 000000000..2972f34a3 --- /dev/null +++ b/parser/tst/simple_tests/file/ok_other_3.sd @@ -0,0 +1,7 @@ +# +#=DESCRIPTION simple other flag test +#=EXRESULT PASS + +profile test { + audit other /tmp/** rw, +} diff --git a/utils/vim/create-apparmor.vim.py b/utils/vim/create-apparmor.vim.py index 3f17a27d3..10e221b55 100644 --- a/utils/vim/create-apparmor.vim.py +++ b/utils/vim/create-apparmor.vim.py @@ -89,11 +89,11 @@ 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 + 'FILE': r'\v^\s*(audit\s+)?(deny\s+|allow\s+)?(owner\s+|other\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 + '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 '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+)?', From 6733da5fcded6d9e572c2c28fb224a400c0731b4 Mon Sep 17 00:00:00 2001 From: Felix Geyer Date: Sun, 26 Jan 2014 12:16:54 -0800 Subject: [PATCH 120/183] nameservice abstraction: read permission to avahi socket From: Felix Geyer AppArmor requires read and write permission to connect to unix domain sockets but the nameservice abstraction only grants write access to the avahi socket. As a result mdns name resolution fails. Acked-by: John Johansen --- profiles/apparmor.d/abstractions/nameservice | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiles/apparmor.d/abstractions/nameservice b/profiles/apparmor.d/abstractions/nameservice index 7681e0ddb..a9d542be9 100644 --- a/profiles/apparmor.d/abstractions/nameservice +++ b/profiles/apparmor.d/abstractions/nameservice @@ -50,7 +50,7 @@ /etc/default/nss r, # avahi-daemon is used for mdns4 resolution - /{,var/}run/avahi-daemon/socket w, + /{,var/}run/avahi-daemon/socket rw, # nis #include From 86ed060f25b6c2b46d49933b01126df91ee3fba6 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Sun, 26 Jan 2014 22:43:42 +0100 Subject: [PATCH 121/183] [1/3] dovecot profiles: introduce tunables/dovecot Introduces tunables/dovecot (with @{DOVECOT_MAILSTORE}) and replace the mail storage location in various dovecot-related profiles with this variable. Also add nice copyright headers (I hope I got the bzr log right ;-) Acked-by: John Johansen --- profiles/apparmor.d/tunables/dovecot | 20 +++++++++++++ profiles/apparmor.d/usr.lib.dovecot.deliver | 29 ++++++++++++------- .../apparmor.d/usr.lib.dovecot.dovecot-auth | 13 ++++++++- profiles/apparmor.d/usr.lib.dovecot.imap | 29 +++++++++++-------- .../apparmor.d/usr.lib.dovecot.imap-login | 12 +++++++- .../usr.lib.dovecot.managesieve-login | 13 ++++++++- profiles/apparmor.d/usr.lib.dovecot.pop3 | 25 +++++++++++----- .../apparmor.d/usr.lib.dovecot.pop3-login | 13 ++++++++- 8 files changed, 120 insertions(+), 34 deletions(-) create mode 100644 profiles/apparmor.d/tunables/dovecot diff --git a/profiles/apparmor.d/tunables/dovecot b/profiles/apparmor.d/tunables/dovecot new file mode 100644 index 000000000..702da58e0 --- /dev/null +++ b/profiles/apparmor.d/tunables/dovecot @@ -0,0 +1,20 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim:ft=apparmor + +# @{DOVECOT_MAILSTORE} is a space-separated list of all directories +# where dovecot is allowed to store and read mails +# +# The default value is quite broad to avoid breaking existing setups. +# Please change @{DOVECOT_MAILSTORE} to (only) contain the directory +# you use, and remove everything else. + +@{DOVECOT_MAILSTORE}=@{HOME}/Maildir/ @{HOME}/mail/ @{HOME}/Mail/ /var/vmail/ /var/mail/ /var/spool/mail/ + diff --git a/profiles/apparmor.d/usr.lib.dovecot.deliver b/profiles/apparmor.d/usr.lib.dovecot.deliver index 80b0e9de0..1f4b05536 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.deliver +++ b/profiles/apparmor.d/usr.lib.dovecot.deliver @@ -1,6 +1,19 @@ -# Author: Dulmandakh Sukhbaatar +# ------------------------------------------------------------------ +# +# Copyright (C) 2009 Dulmandakh Sukhbaatar +# Copyright (C) 2009-2012 Canonical Ltd. +# Copyright (C) 2011-2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include +#include + /usr/lib/dovecot/deliver { #include #include @@ -8,20 +21,16 @@ capability setgid, capability setuid, + @{DOVECOT_MAILSTORE}/ rw, + @{DOVECOT_MAILSTORE}/** rwkl, + # http://www.postfix.org/SASL_README.html#server_dovecot /etc/dovecot/dovecot.conf r, /etc/dovecot/{auth,conf}.d/*.conf r, - /etc/dovecot/dovecot-postfix.conf r, + /etc/dovecot/dovecot-postfix.conf r, # ??? - @{HOME} r, - @{HOME}/Maildir/ rw, - @{HOME}/Maildir/** klrw, - @{HOME}/mail/ rw, - @{HOME}/mail/* klrw, - @{HOME}/mail/.imap/** klrw, + @{HOME} r, # ??? /usr/lib/dovecot/deliver mr, - /var/mail/* klrw, - /var/spool/mail/* klrw, # Site-specific additions and overrides. See local/README for details. #include diff --git a/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth b/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth index 556353630..25f125135 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth +++ b/profiles/apparmor.d/usr.lib.dovecot.dovecot-auth @@ -1,6 +1,17 @@ -# Author: Kees Cook +# ------------------------------------------------------------------ +# +# Copyright (C) 2009-2013 Canonical Ltd. +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include + /usr/lib/dovecot/dovecot-auth { #include #include diff --git a/profiles/apparmor.d/usr.lib.dovecot.imap b/profiles/apparmor.d/usr.lib.dovecot.imap index d490e3157..74ea18d3a 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.imap +++ b/profiles/apparmor.d/usr.lib.dovecot.imap @@ -1,6 +1,18 @@ -# Author: Kees Cook +# ------------------------------------------------------------------ +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2011-2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include +#include + /usr/lib/dovecot/imap { #include #include @@ -8,18 +20,11 @@ capability setgid, capability setuid, - @{HOME} r, - @{HOME}/Maildir/ rw, - @{HOME}/Maildir/** klrw, - @{HOME}/Mail/ rw, - @{HOME}/Mail/* klrw, - @{HOME}/Mail/.imap/** klrw, - @{HOME}/mail/ rw, - @{HOME}/mail/* klrw, - @{HOME}/mail/.imap/** klrw, + @{DOVECOT_MAILSTORE}/ rw, + @{DOVECOT_MAILSTORE}/** rwkl, + + @{HOME} r, # ??? /usr/lib/dovecot/imap mr, - /var/mail/* klrw, - /var/spool/mail/* klrw, # Site-specific additions and overrides. See local/README for details. #include diff --git a/profiles/apparmor.d/usr.lib.dovecot.imap-login b/profiles/apparmor.d/usr.lib.dovecot.imap-login index 31a4afb6d..2e5eca482 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.imap-login +++ b/profiles/apparmor.d/usr.lib.dovecot.imap-login @@ -1,4 +1,14 @@ -# Author: Kees Cook +# ------------------------------------------------------------------ +# +# Copyright (C) 2009-2011 Canonical Ltd. +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include /usr/lib/dovecot/imap-login { diff --git a/profiles/apparmor.d/usr.lib.dovecot.managesieve-login b/profiles/apparmor.d/usr.lib.dovecot.managesieve-login index 0a54cf307..1cda8a0c7 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.managesieve-login +++ b/profiles/apparmor.d/usr.lib.dovecot.managesieve-login @@ -1,4 +1,15 @@ -# Author: Dulmandakh Sukhbaatar +# ------------------------------------------------------------------ +# +# Copyright (c) 2009 Dulmandakh Sukhbaatar +# Copyright (C) 2009-2011 Canonical Ltd. +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include /usr/lib/dovecot/managesieve-login { diff --git a/profiles/apparmor.d/usr.lib.dovecot.pop3 b/profiles/apparmor.d/usr.lib.dovecot.pop3 index 841aea7cb..00782daf4 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.pop3 +++ b/profiles/apparmor.d/usr.lib.dovecot.pop3 @@ -1,6 +1,18 @@ -# Author: Kees Cook +# ------------------------------------------------------------------ +# +# Copyright (C) 2009-2010 Canonical Ltd. +# Copyright (C) 2011-2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include +#include + /usr/lib/dovecot/pop3 { #include #include @@ -8,13 +20,10 @@ capability setgid, capability setuid, - /var/mail/* klrw, - /var/spool/mail/* klrw, - @{HOME} r, - @{HOME}/mail/* klrw, - @{HOME}/mail/.imap/** klrw, - @{HOME}/Maildir/ rw, - @{HOME}/Maildir/** klrw, + @{DOVECOT_MAILSTORE}/ rw, + @{DOVECOT_MAILSTORE}/** rwkl, + + @{HOME} r, # ??? /usr/lib/dovecot/pop3 mr, # Site-specific additions and overrides. See local/README for details. diff --git a/profiles/apparmor.d/usr.lib.dovecot.pop3-login b/profiles/apparmor.d/usr.lib.dovecot.pop3-login index 01300ab28..b500f9c1c 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.pop3-login +++ b/profiles/apparmor.d/usr.lib.dovecot.pop3-login @@ -1,6 +1,17 @@ -# Author: Kees Cook +# ------------------------------------------------------------------ +# +# Copyright (C) 2009-2011 Canonical Ltd. +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include + /usr/lib/dovecot/pop3-login { #include #include From df94a355fcbb62403b68664ed273f5efabfabf7a Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Sun, 26 Jan 2014 22:46:51 +0100 Subject: [PATCH 122/183] [2/3] dovecot profiles: add profiles for new dovecot 2.x binaries dovecot 2.x comes with several new binaries in /usr/lib/dovecot. This patch adds profiles for /usr/lib/dovecot/anvil /usr/lib/dovecot/auth /usr/lib/dovecot/config /usr/lib/dovecot/dict /usr/lib/dovecot/dovecot-lda /usr/lib/dovecot/lmtp /usr/lib/dovecot/log /usr/lib/dovecot/managesieve /usr/lib/dovecot/ssl-params References: https://bugzilla.novell.com/show_bug.cgi?id=851984 Acked-by: John Johansen --- profiles/apparmor.d/usr.lib.dovecot.anvil | 25 ++++++++++++ profiles/apparmor.d/usr.lib.dovecot.auth | 38 +++++++++++++++++++ profiles/apparmor.d/usr.lib.dovecot.config | 32 ++++++++++++++++ profiles/apparmor.d/usr.lib.dovecot.dict | 31 +++++++++++++++ .../apparmor.d/usr.lib.dovecot.dovecot-lda | 33 ++++++++++++++++ profiles/apparmor.d/usr.lib.dovecot.lmtp | 35 +++++++++++++++++ profiles/apparmor.d/usr.lib.dovecot.log | 25 ++++++++++++ .../apparmor.d/usr.lib.dovecot.managesieve | 23 +++++++++++ .../apparmor.d/usr.lib.dovecot.ssl-params | 27 +++++++++++++ 9 files changed, 269 insertions(+) create mode 100644 profiles/apparmor.d/usr.lib.dovecot.anvil create mode 100644 profiles/apparmor.d/usr.lib.dovecot.auth create mode 100644 profiles/apparmor.d/usr.lib.dovecot.config create mode 100644 profiles/apparmor.d/usr.lib.dovecot.dict create mode 100644 profiles/apparmor.d/usr.lib.dovecot.dovecot-lda create mode 100644 profiles/apparmor.d/usr.lib.dovecot.lmtp create mode 100644 profiles/apparmor.d/usr.lib.dovecot.log create mode 100644 profiles/apparmor.d/usr.lib.dovecot.managesieve create mode 100644 profiles/apparmor.d/usr.lib.dovecot.ssl-params diff --git a/profiles/apparmor.d/usr.lib.dovecot.anvil b/profiles/apparmor.d/usr.lib.dovecot.anvil new file mode 100644 index 000000000..00f77e32c --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.anvil @@ -0,0 +1,25 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include + +/usr/lib/dovecot/anvil { + #include + + capability setgid, + capability setuid, + capability sys_chroot, + + /usr/lib/dovecot/anvil mr, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.auth b/profiles/apparmor.d/usr.lib.dovecot.auth new file mode 100644 index 000000000..d945561a9 --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.auth @@ -0,0 +1,38 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include + +/usr/lib/dovecot/auth { + #include + #include + #include + #include + + deny capability block_suspend, + + capability audit_write, + capability setgid, + capability setuid, + + /etc/dovecot/dovecot-database.conf.ext r, + /etc/dovecot/dovecot-sql.conf.ext r, + /usr/lib/dovecot/auth mr, + + # kerberos replay cache + /var/tmp/imap_* rw, + /var/tmp/pop_* rw, + /var/tmp/sieve_* rw, + /var/tmp/smtp_* rw, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.config b/profiles/apparmor.d/usr.lib.dovecot.config new file mode 100644 index 000000000..8ac39923e --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.config @@ -0,0 +1,32 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include + +/usr/lib/dovecot/config { + #include + #include + #include + + deny capability block_suspend, + + capability dac_override, + capability setgid, + + + /etc/dovecot/** r, + /usr/bin/doveconf rix, + /usr/lib/dovecot/config mr, + /usr/lib/dovecot/managesieve Px, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.dict b/profiles/apparmor.d/usr.lib.dovecot.dict new file mode 100644 index 000000000..c48a5b74b --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.dict @@ -0,0 +1,31 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include + +/usr/lib/dovecot/dict { + #include + #include + + capability setgid, + capability setuid, + + network inet stream, + + /etc/dovecot/dovecot-database.conf.ext r, + /etc/dovecot/dovecot-dict-sql.conf.ext r, + /etc/nsswitch.conf r, + /etc/services r, + /usr/lib/dovecot/dict mr, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.dovecot-lda b/profiles/apparmor.d/usr.lib.dovecot.dovecot-lda new file mode 100644 index 000000000..b74c92253 --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.dovecot-lda @@ -0,0 +1,33 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include +#include + +/usr/lib/dovecot/dovecot-lda { + #include + #include + + capability setgid, + capability setuid, + + @{DOVECOT_MAILSTORE}/ rw, + @{DOVECOT_MAILSTORE}/** rwkl, + + /etc/dovecot/** r, + /proc/*/mounts r, + /{var/,}run/dovecot/mounts r, + /usr/bin/doveconf mrix, + /usr/lib/dovecot/dovecot-lda mrix, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.lmtp b/profiles/apparmor.d/usr.lib.dovecot.lmtp new file mode 100644 index 000000000..dd8cd5a34 --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.lmtp @@ -0,0 +1,35 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include +#include + +/usr/lib/dovecot/lmtp { + #include + + deny capability block_suspend, + + capability dac_override, + capability setgid, + capability setuid, + + @{DOVECOT_MAILSTORE}/ rw, + @{DOVECOT_MAILSTORE}/** rwkl, + + /etc/resolv.conf r, + /proc/*/mounts r, + /tmp/dovecot.lmtp.* rw, + /usr/lib/dovecot/lmtp mr, + /{var/,}run/dovecot/mounts r, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.log b/profiles/apparmor.d/usr.lib.dovecot.log new file mode 100644 index 000000000..34a417560 --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.log @@ -0,0 +1,25 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include + +/usr/lib/dovecot/log { + #include + + deny capability block_suspend, + + capability setgid, + + /usr/lib/dovecot/log mr, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.managesieve b/profiles/apparmor.d/usr.lib.dovecot.managesieve new file mode 100644 index 000000000..3b39f0d25 --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.managesieve @@ -0,0 +1,23 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include + +/usr/lib/dovecot/managesieve { + #include + + /etc/dovecot/** r, + /usr/bin/doveconf rix, + /usr/lib/dovecot/managesieve mrix, + + # Site-specific additions and overrides. See local/README for details. + #include +} diff --git a/profiles/apparmor.d/usr.lib.dovecot.ssl-params b/profiles/apparmor.d/usr.lib.dovecot.ssl-params new file mode 100644 index 000000000..704dfcb66 --- /dev/null +++ b/profiles/apparmor.d/usr.lib.dovecot.ssl-params @@ -0,0 +1,27 @@ +# ------------------------------------------------------------------ +# +# Copyright (C) 2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor + +#include + +/usr/lib/dovecot/ssl-params { + #include + + deny capability block_suspend, + + capability setgid, + + /usr/lib/dovecot/ssl-params mr, + /var/lib/dovecot/ssl-parameters.dat rw, + /var/lib/dovecot/ssl-parameters.dat.tmp rwk, + + # Site-specific additions and overrides. See local/README for details. + #include +} From 0fa4676d30f76839ce2f5a0c7faa46b92436559d Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Sun, 26 Jan 2014 22:48:02 +0100 Subject: [PATCH 123/183] [3/3] dovecot profiles: update usr.sbin.dovecot profile for dovecot 2.x The usr.sbin.dovecot profile needs several updates for dovecot 2.x, including - capability dac_override and kill - Px for various binaries in /usr/lib/dovecot/ The patch also adds a nice copyright header (I hope I got the bzr log right ;-) Acked-by: John Johansen --- profiles/apparmor.d/usr.sbin.dovecot | 34 +++++++++++++++++++++------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/profiles/apparmor.d/usr.sbin.dovecot b/profiles/apparmor.d/usr.sbin.dovecot index 2ee8f8782..4a240960c 100644 --- a/profiles/apparmor.d/usr.sbin.dovecot +++ b/profiles/apparmor.d/usr.sbin.dovecot @@ -1,6 +1,17 @@ -# Author: Kees Cook +# ------------------------------------------------------------------ +# +# Copyright (C) 2009-2013 Canonical Ltd. +# Copyright (C) 2011-2013 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 published by the Free Software Foundation. +# +# ------------------------------------------------------------------ +# vim: ft=apparmor #include + /usr/sbin/dovecot { #include #include @@ -9,29 +20,36 @@ #include capability chown, + capability dac_override, + capability fsetid, + capability kill, capability net_bind_service, capability setgid, capability setuid, capability sys_chroot, - capability fsetid, /etc/dovecot/** r, /etc/mtab r, /etc/lsb-release r, /etc/SuSE-release r, @{PROC}/@{pid}/mounts r, + /usr/bin/doveconf rix, + /usr/lib/dovecot/anvil Px, + /usr/lib/dovecot/auth Px, + /usr/lib/dovecot/config Px, /usr/lib/dovecot/dovecot-auth Pxmr, /usr/lib/dovecot/imap Pxmr, /usr/lib/dovecot/imap-login Pxmr, + /usr/lib/dovecot/log Px, + /usr/lib/dovecot/managesieve Px, + /usr/lib/dovecot/managesieve-login Pxmr, /usr/lib/dovecot/pop3 Px, /usr/lib/dovecot/pop3-login Pxmr, - # temporarily commented out while testing - #/usr/lib/dovecot/managesieve Px, - /usr/lib/dovecot/managesieve-login Pxmr, - /usr/lib/dovecot/ssl-build-param ixr, - /usr/sbin/dovecot mr, + /usr/lib/dovecot/ssl-build-param rix, + /usr/lib/dovecot/ssl-params Px, + /usr/sbin/dovecot mrix, /var/lib/dovecot/ w, - /var/lib/dovecot/* krw, + /var/lib/dovecot/* rwkl, /{,var/}run/dovecot/ rw, /{,var/}run/dovecot/** rw, link /{,var/}run/dovecot/** -> /var/lib/dovecot/**, From bdaf2592e855e5f8653f6dad24623edee99813ed Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Wed, 29 Jan 2014 23:16:36 +0100 Subject: [PATCH 124/183] apparmor.vim says "attach_disconnect" is correct, but the parser only likes "attach_disconnected". Acked-By: Jamie Strandboge --- utils/vim/create-apparmor.vim.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/vim/create-apparmor.vim.py b/utils/vim/create-apparmor.vim.py index 10e221b55..5cd253dc7 100644 --- a/utils/vim/create-apparmor.vim.py +++ b/utils/vim/create-apparmor.vim.py @@ -78,7 +78,7 @@ aa_network_types = r'\s+tcp|\s+udp|\s+icmp' aa_flags = ['complain', 'audit', - 'attach_disconnect', + 'attach_disconnected', 'no_attach_disconnected', 'chroot_attach', 'chroot_no_attach', From c82fda86b6b956f04d896e148433e2ddac6e9171 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Feb 2014 06:14:05 +0530 Subject: [PATCH 125/183] Some bugfixes for UIYesNo to deny invalid keys, fix autodep when creating new profiles --- Tools/aa-audit | 8 ++++++-- apparmor/aa.py | 46 ++++++++++++++++++++++++++++---------------- apparmor/common.py | 41 +++++++++++++++++++++++++++++---------- apparmor/severity.py | 2 +- apparmor/tools.py | 15 ++++++++++----- apparmor/ui.py | 7 +++++-- 6 files changed, 82 insertions(+), 37 deletions(-) diff --git a/Tools/aa-audit b/Tools/aa-audit index e947231ee..06932c42a 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -25,6 +25,10 @@ parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() -audit = apparmor.tools.aa_tools('audit', args) +try: + audit = apparmor.tools.aa_tools('audit', args) -audit.act() + audit.act() +except Exception as e: + print(e) + raise e diff --git a/apparmor/aa.py b/apparmor/aa.py index c9310b63b..2cbaed4ed 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -73,7 +73,7 @@ user_globs = [] ## Variables used under logprof ### Were our -t = hasher()#dict() +t = hasher() # dict() transitions = hasher() aa = hasher() # Profiles originally in sd, replace by aa original_aa = hasher() @@ -82,7 +82,7 @@ extras = hasher() # Inactive profiles from extras log = [] pid = dict() -seen = hasher()#dir() +seen = hasher() # dir() profile_changes = hasher() prelog = hasher() log_dict = hasher()#dict() @@ -380,7 +380,7 @@ def create_new_profile(localfile): local_profile[localfile]['flags'] = 'complain' local_profile[localfile]['include']['abstractions/base'] = 1 - if os.path.isfile(localfile): + if os.path.exists(localfile) and os.path.isfile(localfile): hashbang = head(localfile) if hashbang.startswith('#!'): interpreter_path = get_full_path(hashbang.lstrip('#!').strip()) @@ -418,7 +418,8 @@ def create_new_profile(localfile): local_profile[hat]['flags'] = 'complain' created.append(localfile) - + changed.append(localfile) + debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) return {localfile: local_profile} @@ -552,6 +553,9 @@ def autodep(bin_name, pname=''): # Return if exectuable path not found if not bin_full: return None + else: + bin_full = pname # for named profiles + pname = bin_full read_inactive_profiles() profile_data = get_profile(pname) @@ -865,34 +869,42 @@ def set_profiles_local_only(profs): def build_x_functions(default, options, exec_toggle): ret_list = [] + fallback_toggle = False if exec_toggle: if 'i' in options: ret_list.append('CMD_ix') if 'p' in options: ret_list.append('CMD_pix') - ret_list.append('CMD_EXEC_IX_OFF') - elif 'c' in options: + fallback_toggle = True + if 'c' in options: ret_list.append('CMD_cix') - ret_list.append('CMD_EXEC_IX_OFF') - elif 'n' in options: + fallback_toggle = True + if 'n' in options: ret_list.append('CMD_nix') + fallback_toggle = True + if fallback_toggle: ret_list.append('CMD_EXEC_IX_OFF') - elif 'u' in options: + if 'u' in options: ret_list.append('CMD_ux') + else: if 'i' in options: ret_list.append('CMD_ix') - elif 'c' in options: + if 'c' in options: ret_list.append('CMD_cx') - ret_list.append('CMD_EXEC_IX_ON') - elif 'p' in options: + fallback_toggle = True + if 'p' in options: ret_list.append('CMD_px') - ret_list.append('CMD_EXEC_IX_OFF') - elif 'n' in options: + fallback_toggle = True + if 'n' in options: ret_list.append('CMD_nx') - ret_list.append('CMD_EXEC_IX_OFF') - elif 'u' in options: + fallback_toggle = True + if 'u' in options: ret_list.append('CMD_ux') + + if fallback_toggle: + ret_list.append('CMD_EXEC_IX_ON') + ret_list += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED'] return ret_list @@ -1223,7 +1235,7 @@ def handle_children(profile, hat, root): exec_toggle = False q['functions'] += build_x_functions(default, options, exec_toggle) - options = '|'.join(options) + # options = '|'.join(options) seen_events += 1 regex_options = re.compile('^CMD_(ix|px|cx|nx|pix|cix|nix|px_safe|cx_safe|nx_safe|pix_safe|cix_safe|nix_safe|ux|ux_safe|EXEC_TOGGLE|DENY)$') diff --git a/apparmor/common.py b/apparmor/common.py index 159cc7341..e9597025c 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -142,7 +142,7 @@ def open_file_read(path, encoding='UTF-8'): return orig def open_file_write(path): - """Open specified file in write/overwrite mode""" + '''Open specified file in write/overwrite mode''' try: orig = codecs.open(path, 'w', 'UTF-8') except Exception: @@ -150,7 +150,7 @@ def open_file_write(path): return orig def readkey(): - """Returns the pressed key""" + '''Returns the pressed key''' fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) try: @@ -162,7 +162,7 @@ def readkey(): return ch def hasher(): - """A neat alternative to perl's hash reference""" + '''A neat alternative to perl's hash reference''' # Creates a dictionary for any depth and returns empty dictionary otherwise return collections.defaultdict(hasher) @@ -201,9 +201,16 @@ def convert_regexp(regexp): new_reg = new_reg + '$' return new_reg +def user_perm(prof_dir): + if not os.access(prof_dir, os.R_OK): + sys.stdout.write("Cannot write to profile directory.\n" + + "Please run as a user with appropriate permissions." ) + return False + class DebugLogger(object): def __init__(self, module_name=__name__): self.debugging = False + self.logfile = '/var/log/apparmor/logprof.log' self.debug_level = logging.DEBUG if os.getenv('LOGPROF_DEBUG', False): self.debugging = os.getenv('LOGPROF_DEBUG') @@ -212,30 +219,44 @@ class DebugLogger(object): except Exception: self.debugging = False if self.debugging not in range(0, 4): - sys.stderr.write('Environment Variable: LOGPROF_DEBUG contains invalid value: %s' %os.getenv('LOGPROF_DEBUG')) - # self.debugging == 0 implies debugging = False + sys.stdout.write('Environment Variable: LOGPROF_DEBUG contains invalid value: %s' + %os.getenv('LOGPROF_DEBUG')) + if self.debugging == 0: # debugging disabled, don't need to setup logging + return if self.debugging == 1: self.debug_level = logging.ERROR elif self.debugging == 2: self.debug_level = logging.INFO elif self.debugging == 3: self.debug_level = logging.DEBUG - - - logging.basicConfig(filename='/var/log/apparmor/logprof.log', level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') - - self.logger = logging.getLogger(module_name) + + try: + logging.basicConfig(filename=self.logfile, level=self.debug_level, + format='%(asctime)s - %(name)s - %(message)s\n') + except OSError: + # Unable to open the default logfile, so create a temporary logfile and tell use about it + import tempfile + templog = tempfile.NamedTemporaryFile('w', prefix='apparmor', suffix='.log' ,delete=False) + sys.stdout.write("\nCould not open: %s\nLogging to: %s\n"%(self.logfile, templog.name)) + + logging.basicConfig(filename=templog.name, level=self.debug_level, + format='%(asctime)s - %(name)s - %(message)s\n') + + self.logger = logging.getLogger(module_name) def error(self, message): if self.debugging: self.logger.error(message) + def info(self, message): if self.debugging: self.logger.info(message) + def debug(self, message): if self.debugging: self.logger.debug(message) + def shutdown(self): logging.shutdown() #logging.shutdown([self.logger]) diff --git a/apparmor/severity.py b/apparmor/severity.py index b33fb224b..c62055ed8 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -197,7 +197,7 @@ class Severity(object): try: self.severity['VARIABLES'][line[0]] += [i.strip('"') for i in line[1].split()] except KeyError: - raise AppArmorException("Variable %s was not previously declared, but is being assigned additional values in file: %s" % (line[0], prof_path)) + raise AppArmorException("Variable %s was not previously declared, but is being assigned additional value in file: %s" % (line[0], prof_path)) else: line = line.split('=') if line[0] in self.severity['VARIABLES'].keys(): diff --git a/apparmor/tools.py b/apparmor/tools.py index 70d9431a7..7d2db44c0 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -15,6 +15,7 @@ import os import sys import apparmor.aa as apparmor +from apparmor.common import user_perm class aa_tools: def __init__(self, tool_name, args): @@ -23,6 +24,7 @@ class aa_tools: self.profiling = args.program self.check_profile_dir() self.silent = None + if tool_name in ['audit', 'complain']: self.remove = args.remove elif tool_name == 'disable': @@ -41,6 +43,9 @@ class aa_tools: if not os.path.isdir(apparmor.profile_dir): raise apparmor.AppArmorException("%s is not a directory." %self.profiledir) + if not user_perm(apparmor.profile_dir): + raise apparmor.AppArmorException("Cannot write to profile directory: %s." %(apparmor.profile_dir)) + def check_disable_dir(self): if not os.path.isdir(self.disabledir): raise apparmor.AppArmorException("Can't find AppArmor disable directory %s." %self.disabledir) @@ -70,11 +75,11 @@ class aa_tools: apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) sys.exit(1) - if program and apparmor.profile_exists(program):#os.path.exists(program): - if self.name == 'autodep': - self.use_autodep(program) - - elif self.name == 'cleanprof': + if self.name == 'autodep' and program and os.path.exists(program): + self.use_autodep(program) + + elif program and apparmor.profile_exists(program): + if self.name == 'cleanprof': self.clean_profile(program, p) else: diff --git a/apparmor/ui.py b/apparmor/ui.py index 66dc7a0a3..df9b892cc 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -94,6 +94,9 @@ def UI_YesNo(text, default): default = 'y' elif ans == 'right': default = 'n' + else: + ans = 'XXXINVALIDXXX' + continue # If user presses any other button ask again else: ans = default @@ -227,8 +230,8 @@ CMDS = { 'CMD_px_safe': _('(P)rofile Clean Exec'), 'CMD_cx': _('(C)hild'), 'CMD_cx_safe': _('(C)hild Clean Exec'), - 'CMD_nx': _('Named'), - 'CMD_nx_safe': _('Named Clean Exec'), + 'CMD_nx': _('(N)amed'), + 'CMD_nx_safe': _('(N)amed Clean Exec'), 'CMD_ux': _('(U)nconfined'), 'CMD_ux_safe': _('(U)nconfined Clean Exec'), 'CMD_pix': _('(P)rofile Inherit'), From 21d1c4572d38161731f3571455f4d75ff3e621ca Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Feb 2014 06:32:20 +0530 Subject: [PATCH 126/183] --- Tools/aa-audit | 7 +++++-- apparmor/common.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Tools/aa-audit b/Tools/aa-audit index 06932c42a..44145b182 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -23,6 +23,7 @@ parser = argparse.ArgumentParser(description=_('Switch the given programs to aud parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit mode')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) +parser.add_argument('--trace', action='store_true', help=_('Show full trace')) args = parser.parse_args() try: @@ -30,5 +31,7 @@ try: audit.act() except Exception as e: - print(e) - raise e + if not args.trace: + print(e.value) + else: + raise e diff --git a/apparmor/common.py b/apparmor/common.py index e9597025c..9ff96451a 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -202,10 +202,11 @@ def convert_regexp(regexp): return new_reg def user_perm(prof_dir): - if not os.access(prof_dir, os.R_OK): + if not os.access(prof_dir, os.W_OK): sys.stdout.write("Cannot write to profile directory.\n" + - "Please run as a user with appropriate permissions." ) + "Please run as a user with appropriate permissions.") return False + return True class DebugLogger(object): def __init__(self, module_name=__name__): From 1126e1f8d7861eaf440094666db7c74674893bf7 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Feb 2014 07:04:08 +0530 Subject: [PATCH 127/183] Fixed the sample --trace feature. Opinions on using it? and should it be implemented in every tool separately? --- Tools/aa-audit | 6 ++++-- apparmor/common.py | 2 +- apparmor/tools.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Tools/aa-audit b/Tools/aa-audit index 44145b182..92138dc4d 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -13,6 +13,7 @@ # # ---------------------------------------------------------------------- import argparse +import traceback from apparmor.common import init_translations init_translations() @@ -32,6 +33,7 @@ try: audit.act() except Exception as e: if not args.trace: - print(e.value) + print(e.value + "\n") + else: - raise e + traceback.print_exc() diff --git a/apparmor/common.py b/apparmor/common.py index 9ff96451a..51fbe857d 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -204,7 +204,7 @@ def convert_regexp(regexp): def user_perm(prof_dir): if not os.access(prof_dir, os.W_OK): sys.stdout.write("Cannot write to profile directory.\n" + - "Please run as a user with appropriate permissions.") + "Please run as a user with appropriate permissions.\n") return False return True diff --git a/apparmor/tools.py b/apparmor/tools.py index 7d2db44c0..ac06f708a 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -44,11 +44,11 @@ class aa_tools: raise apparmor.AppArmorException("%s is not a directory." %self.profiledir) if not user_perm(apparmor.profile_dir): - raise apparmor.AppArmorException("Cannot write to profile directory: %s." %(apparmor.profile_dir)) + raise apparmor.AppArmorException("Cannot write to profile directory: %s" %(apparmor.profile_dir)) def check_disable_dir(self): if not os.path.isdir(self.disabledir): - raise apparmor.AppArmorException("Can't find AppArmor disable directory %s." %self.disabledir) + raise apparmor.AppArmorException("Can't find AppArmor disable directory %s" %self.disabledir) def act(self): for p in self.profiling: From 8b802b3fe68726537fa5b911f06c6c505928a589 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Sun, 2 Feb 2014 15:12:32 +0100 Subject: [PATCH 128/183] update logprof.conf for UsrMove logprof.conf contains a list of binaries in the [qualifiers] section that should for example never have their own profile. Since some distributions moved lots of files from /bin/ to /usr/bin/ ("UsrMove"), this list is outdated. The patch adds copies of all /bin/ (and /sbin/) lines with /usr prepended. Acked-by: John Johansen --- utils/logprof.conf | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/utils/logprof.conf b/utils/logprof.conf index b7764ad54..ea6636e7a 100644 --- a/utils/logprof.conf +++ b/utils/logprof.conf @@ -43,14 +43,20 @@ [qualifiers] # things will be painfully broken if bash has a profile /bin/bash = icnu - /bin/ksh = icnu - /bin/dash = icnu + /usr/bin/bash = icnu + /bin/ksh = icnu + /usr/bin/ksh = icnu + /bin/dash = icnu + /usr/bin/dash = icnu # these programs can't function if they're confined /bin/mount = u + /usr/bin/mount = u /etc/init.d/subdomain = u /sbin/cardmgr = u + /usr/sbin/cardmgr = u /sbin/subdomain_parser = u + /usr/sbin/subdomain_parser = u /usr/sbin/genprof = u /usr/sbin/logprof = u /usr/lib/YaST2/servers_non_y2/ag_genprof = u @@ -58,24 +64,43 @@ # these ones shouln't have their own profiles /bin/awk = icn + /usr/bin/awk = icn /bin/cat = icn + /usr/bin/cat = icn /bin/chmod = icn + /usr/bin/chmod = icn /bin/chown = icn + /usr/bin/chown = icn /bin/cp = icn + /usr/bin/cp = icn /bin/gawk = icn + /usr/bin/gawk = icn /bin/grep = icn + /usr/bin/grep = icn /bin/gunzip = icn + /usr/bin/gunzip = icn /bin/gzip = icn + /usr/bin/gzip = icn /bin/kill = icn + /usr/bin/kill = icn /bin/ln = icn + /usr/bin/ln = icn /bin/ls = icn + /usr/bin/ls = icn /bin/mkdir = icn + /usr/bin/mkdir = icn /bin/mv = icn + /usr/bin/mv = icn /bin/readlink = icn + /usr/bin/readlink = icn /bin/rm = icn + /usr/bin/rm = icn /bin/sed = icn + /usr/bin/sed = icn /bin/touch = icn + /usr/bin/touch = icn /sbin/killall5 = icn + /usr/sbin/killall5 = icn /usr/bin/find = icn /usr/bin/killall = icn /usr/bin/nice = icn From a38ce71813adf8dceda46d0dd52d68412a27c92f Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Sun, 2 Feb 2014 15:13:51 +0100 Subject: [PATCH 129/183] update usr.bin.dovecot profile after testing the dovecot profiles on a new server, I noticed /usr/sbin/dovecot needs some more permissions: - mysql access - execution permissions for /usr/lib/dovecot/dict and lmtp - write access to some postfix sockets, used to - provide SMTP Auth via dovecot - deliver mails to dovecot via LMTP - and read access to /proc/filesystems Acked-by: John Johansen --- profiles/apparmor.d/usr.sbin.dovecot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/profiles/apparmor.d/usr.sbin.dovecot b/profiles/apparmor.d/usr.sbin.dovecot index 4a240960c..f676100a7 100644 --- a/profiles/apparmor.d/usr.sbin.dovecot +++ b/profiles/apparmor.d/usr.sbin.dovecot @@ -15,6 +15,7 @@ /usr/sbin/dovecot { #include #include + #include #include #include #include @@ -33,13 +34,16 @@ /etc/lsb-release r, /etc/SuSE-release r, @{PROC}/@{pid}/mounts r, + @{PROC}/filesystems r, /usr/bin/doveconf rix, /usr/lib/dovecot/anvil Px, /usr/lib/dovecot/auth Px, /usr/lib/dovecot/config Px, + /usr/lib/dovecot/dict Px, /usr/lib/dovecot/dovecot-auth Pxmr, /usr/lib/dovecot/imap Pxmr, /usr/lib/dovecot/imap-login Pxmr, + /usr/lib/dovecot/lmtp Px, /usr/lib/dovecot/log Px, /usr/lib/dovecot/managesieve Px, /usr/lib/dovecot/managesieve-login Pxmr, @@ -50,6 +54,8 @@ /usr/sbin/dovecot mrix, /var/lib/dovecot/ w, /var/lib/dovecot/* rwkl, + /var/spool/postfix/private/auth w, + /var/spool/postfix/private/dovecot-lmtp w, /{,var/}run/dovecot/ rw, /{,var/}run/dovecot/** rw, link /{,var/}run/dovecot/** -> /var/lib/dovecot/**, From 19038d063bc268d491672c23fad17fd5f30fae20 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Sun, 2 Feb 2014 15:16:25 +0100 Subject: [PATCH 130/183] /usr/lib/dovecot/auth reads the mysql config files, which is not covered by abstractions/mysql. This binary/profile seems to be the only one that needs to do this, so add it to this profile (instead of abstractions/mysql) to avoid superfluous permissions for other programs with abstractions/mysql Acked-by: John Johansen --- profiles/apparmor.d/usr.lib.dovecot.auth | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/profiles/apparmor.d/usr.lib.dovecot.auth b/profiles/apparmor.d/usr.lib.dovecot.auth index d945561a9..625f8d6a5 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.auth +++ b/profiles/apparmor.d/usr.lib.dovecot.auth @@ -23,6 +23,10 @@ capability setgid, capability setuid, + /etc/my.cnf r, + /etc/my.cnf.d/ r, + /etc/my.cnf.d/*.cnf r, + /etc/dovecot/dovecot-database.conf.ext r, /etc/dovecot/dovecot-sql.conf.ext r, /usr/lib/dovecot/auth mr, From 572fe066da96d77f2ead48d7b9806b2796d79f5a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 2 Feb 2014 19:23:10 -1000 Subject: [PATCH 131/183] The preprocessing output is broken, in a couple of places includes come out like #include ##included which is wrong because #include by itself is broken, and since -p is supposed to be removing includes, it should not be directly echoed any keyword in the keyword table is double echoed ownerowner /{run,dev}/shm/pulse-shm* rwk Signed-off-by: John Johansen Acked-by: Steve Beattie --- parser/parser_lex.l | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/parser/parser_lex.l b/parser/parser_lex.l index ad2f0f748..1938297d7 100644 --- a/parser/parser_lex.l +++ b/parser/parser_lex.l @@ -488,7 +488,12 @@ LT_EQUAL <= } } -#include/.*\r?\n { PUSH(INCLUDE); } +#include/.*\r?\n { + /* Don't use push here as we don't want #include echoed out. It needs + * to be handled specially + */ + yy_push_state(INCLUDE); +} #.*\r?\n { /* normal comment */ DUMP_AND_DEBUG("comment(%d): %s\n", current_lineno, yytext); @@ -536,7 +541,6 @@ LT_EQUAL <= {OPEN_PAREN} { PUSH_AND_RETURN(LIST_VAL_MODE, TOK_OPENPAREN); } {VARIABLE_NAME} { - DUMP_PREPROCESS; int token = get_keyword_token(yytext); int state = INITIAL; From 0a8e97098d7c153b108d11af75d8b888a112685e Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Tue, 4 Feb 2014 14:28:21 -0800 Subject: [PATCH 132/183] parser: fix --cache-loc short arg option (-L) When the --cache-loc option was added in trunk commit 1916, it was intended that -L would be the short form of the option (based on documentation and usage changes). However, the commit mistakenly did not include the short option in the list include in the call to getopt_long(3). This patch adds it along with the indicator that it requires an argument (the different cache location) to the getopt_long() call. Signed-off-by: Steve Beattie Acked-by: Seth Arnold --- parser/parser_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser_main.c b/parser/parser_main.c index 218ce249b..65f2cfdd0 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -583,7 +583,7 @@ static int process_args(int argc, char *argv[]) int count = 0; option = OPTION_ADD; - while ((c = getopt_long(argc, argv, "adf:h::rRVvI:b:BCD:NSm:qQn:XKTWkO:po:", long_options, &o)) != -1) + while ((c = getopt_long(argc, argv, "adf:h::rRVvI:b:BCD:NSm:qQn:XKTWkL:O:po:", long_options, &o)) != -1) { count += process_arg(c, optarg); } From 5df1ac36101e8d592e9c28f6f27ce0c6f9e2738d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 5 Feb 2014 09:10:53 -0500 Subject: [PATCH 133/183] Move short_options next to long_options to make them easier to keep in sync Signed-off-by: John Johansen Acked-by: Seth Arnold Acked-by: Steve Beattie --- parser/parser_main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/parser/parser_main.c b/parser/parser_main.c index 65f2cfdd0..e4d8d1041 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -87,6 +87,8 @@ char *cacheloc = NULL; /* per-profile settings */ int force_complain = 0; +/* Make sure to update BOTH the short and long_options */ +static const char *short_options = "adf:h::rRVvI:b:BCD:NSm:qQn:XKTWkL:O:po:"; struct option long_options[] = { {"add", 0, 0, 'a'}, {"binary", 0, 0, 'B'}, @@ -583,7 +585,7 @@ static int process_args(int argc, char *argv[]) int count = 0; option = OPTION_ADD; - while ((c = getopt_long(argc, argv, "adf:h::rRVvI:b:BCD:NSm:qQn:XKTWkL:O:po:", long_options, &o)) != -1) + while ((c = getopt_long(argc, argv, short_options, long_options, &o)) != -1) { count += process_arg(c, optarg); } From 2001fb6f81c6914abb8e00e21b4eeffbe186a7e3 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Wed, 5 Feb 2014 13:39:24 -0500 Subject: [PATCH 134/183] parser: Quiet valgrind false positive strlen() assumes that it can read an entire word but when a char array does not end on a word boundary, it reads past the end of the array. This results in the following valgrind warning: Invalid read of size 4 at 0x40A162: yylex() (parser_lex.l:277) by 0x40FA14: yyparse() (parser_yacc.c:1487) by 0x40C5B9: process_profile(int, char const*) (parser_main.c:1003) by 0x404074: main (parser_main.c:1340) Address 0x578d870 is 16 bytes inside a block of size 18 alloc'd at 0x4C2A420: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) by 0x53E31C9: strdup (strdup.c:42) by 0x40A145: yylex() (parser_lex.l:276) by 0x40FA14: yyparse() (parser_yacc.c:1487) by 0x40C5B9: process_profile(int, char const*) (parser_main.c:1003) by 0x404074: main (parser_main.c:1340) This patch quiets the warning by not using strlen(). This can be done because yyleng already contains the length of string. Signed-off-by: Tyler Hicks Acked-by: Steve Beattie --- parser/parser_lex.l | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/parser/parser_lex.l b/parser/parser_lex.l index 1938297d7..1b6005336 100644 --- a/parser/parser_lex.l +++ b/parser/parser_lex.l @@ -273,8 +273,7 @@ LT_EQUAL <= { (\<([^\> \t\n]+)\>|\"([^\" \t\n]+)\") { /* */ - char *filename = strdup(yytext); - filename[strlen(filename) - 1] = '\0'; + char *filename = strndup(yytext, yyleng - 1); include_filename(filename + 1, *filename == '<'); free(filename); yy_pop_state(); From 0d613279ba57f7a4305fd03659c05a969b1831a1 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Wed, 5 Feb 2014 10:58:03 -0800 Subject: [PATCH 135/183] parser: remove one valgrind suppression from test script With commit 2364 addressing one of valgrind's false positives, we can remove the related valgrind suppression entry from the test script. Signed-off-by: Steve Beattie Acked-by: Seth Arnold Acked-by: Tyler Hicks --- parser/tst/valgrind_simple.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/parser/tst/valgrind_simple.py b/parser/tst/valgrind_simple.py index 87d9186fa..15a5bb561 100755 --- a/parser/tst/valgrind_simple.py +++ b/parser/tst/valgrind_simple.py @@ -32,15 +32,6 @@ VALGRIND_SUPPRESSIONS = ''' fun:main } -{ - valgrind-yylex-obsessive-overreads - Memcheck:Addr4 - fun:_Z?yylex? - fun:_Z*yyparse* - fun:_Z*process_profile* - fun:main -} - { valgrind-serialize_profile-obsessive-overreads Memcheck:Addr4 From 4b950117f9ce06bac19174b07dddae8f889b39bb Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Wed, 5 Feb 2014 15:17:32 -0500 Subject: [PATCH 136/183] parser: Quiet search dir valgrind warning and remove suppression When passing an include directory on the command line to apparmor_parser, valgrind emits a warning: Invalid read of size 4 at 0x404DA6: add_search_dir(char const*) (parser_include.c:152) by 0x40BB37: process_arg(int, char*) (parser_main.c:457) by 0x403D43: main (parser_main.c:590) Address 0x572207c is 28 bytes inside a block of size 29 alloc'd at 0x4C2A420: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) by 0x53E31C9: strdup (strdup.c:42) by 0x404D94: add_search_dir(char const*) (parser_include.c:145) by 0x40BB37: process_arg(int, char*) (parser_main.c:457) by 0x403D43: main (parser_main.c:590) This patch quiets the warning by removing strlen() calls on the t char array. Instead, it only calls strlen() on the dir char array. t is a dupe of dir and strlen(dir) does not trigger the valgrind warning. Additionally, this patch adds a bit of defensive programming to the while loop to ensure that index into the t array is never negative. Finally, the valgrind suppression is removed from valgrind_simple.py. Signed-off-by: Tyler Hicks Acked-by: Steve Beattie --- parser/parser_include.c | 12 +++++++++--- parser/tst/valgrind_simple.py | 8 -------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/parser/parser_include.c b/parser/parser_include.c index 0ad865dd6..3f97a972a 100644 --- a/parser/parser_include.c +++ b/parser/parser_include.c @@ -133,13 +133,19 @@ void set_base_dir(char *dir) int add_search_dir(const char *dir) { char *t; + size_t len; + if (npath >= MAX_PATH) { PERROR(_("Error: Could not add directory %s to search path.\n"), dir); return 0; } - if (!dir || strlen(dir) <= 0) + if (!dir) + return 1; + + len = strlen(dir); + if (len == 0) return 1; t = strdup(dir); @@ -149,8 +155,8 @@ int add_search_dir(const char *dir) } /*strip trailing /'s */ - while (t[strlen(t) - 1] == '/') - t[strlen(t) - 1] = 0; + while (len > 0 && t[--len] == '/') + t[len] = '\0'; path[npath] = t; npath++; diff --git a/parser/tst/valgrind_simple.py b/parser/tst/valgrind_simple.py index 15a5bb561..868e3c175 100755 --- a/parser/tst/valgrind_simple.py +++ b/parser/tst/valgrind_simple.py @@ -24,14 +24,6 @@ VALGRIND_ERROR_CODE = 151 VALGRIND_ARGS = ['--leak-check=full', '--error-exitcode=%d' % (VALGRIND_ERROR_CODE)] VALGRIND_SUPPRESSIONS = ''' -{ - valgrind-add_search_dir-obsessive-overreads - Memcheck:Addr4 - fun:_Z*add_search_dir* - fun:_Z*process_arg* - fun:main -} - { valgrind-serialize_profile-obsessive-overreads Memcheck:Addr4 From 0c5d6f4660ea72f8831e219102fe3643a4dbae1d Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 5 Feb 2014 23:44:04 -0500 Subject: [PATCH 137/183] add ubuntu-unity7-* abstractions for Ubuntu desktop users --- .../abstractions/ubuntu-unity7-base | 167 ++++++++++++++++++ .../abstractions/ubuntu-unity7-launcher | 7 + .../abstractions/ubuntu-unity7-messaging | 7 + 3 files changed, 181 insertions(+) create mode 100644 profiles/apparmor.d/abstractions/ubuntu-unity7-base create mode 100644 profiles/apparmor.d/abstractions/ubuntu-unity7-launcher create mode 100644 profiles/apparmor.d/abstractions/ubuntu-unity7-messaging diff --git a/profiles/apparmor.d/abstractions/ubuntu-unity7-base b/profiles/apparmor.d/abstractions/ubuntu-unity7-base new file mode 100644 index 000000000..737581527 --- /dev/null +++ b/profiles/apparmor.d/abstractions/ubuntu-unity7-base @@ -0,0 +1,167 @@ +# vim:syntax=apparmor +# ------------------------------------------------------------------ +# +# Copyright (C) 2013-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. +# +# ------------------------------------------------------------------ + +# +# Rules common to applications running under Unity 7 +# + +#include + + # Allow connecting to session bus and where to connect to services + dbus (send) + bus=session + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=Hello + peer=(name=org.freedesktop.DBus), + dbus (send) + bus=session + path=/org/freedesktop/{db,DB}us + interface=org.freedesktop.DBus + member={Add,Remove}Match + peer=(name=org.freedesktop.DBus), + # NameHasOwner and GetNameOwner could leak running processes and apps + # depending on how services are implemented + dbus (send) + bus=session + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=GetNameOwner + peer=(name=org.freedesktop.DBus), + dbus (send) + bus=session + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=NameHasOwner + peer=(name=org.freedesktop.DBus), + + # Allow starting services on the session bus (actual communications with + # the service are mediated elsewhere) + dbus (send) + bus=session + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=StartServiceByName + peer=(name=org.freedesktop.DBus), + + # Allow connecting to system bus and where to connect to services. Put these + # here so we don't need to repeat these rules in multiple places (actual + # communications with any system services is mediated elsewhere). This does + # allow apps to brute-force enumerate system services, but our system + # services aren't a secret. + /{,var/}run/dbus/system_bus_socket rw, + dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=Hello + peer=(name=org.freedesktop.DBus), + dbus (send) + bus=system + path=/org/freedesktop/{db,DB}us + interface=org.freedesktop.DBus + member={Add,Remove}Match + peer=(name=org.freedesktop.DBus), + # NameHasOwner and GetNameOwner could leak running processes and apps + # depending on how services are implemented + dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=GetNameOwner + peer=(name=org.freedesktop.DBus), + dbus (send) + bus=system + path=/org/freedesktop/DBus + interface=org.freedesktop.DBus + member=NameHasOwner + peer=(name=org.freedesktop.DBus), + + # + # Access required for connecting to/communication with Unity HUD + # + dbus (send) + bus=session + path="/com/canonical/hud", + dbus (send) + bus=session + interface="com.canonical.hud.*", + dbus (send) + bus=session + path="/com/canonical/hud/applications/*", + dbus (receive) + bus=session + path="/com/canonical/hud", + dbus (receive) + bus=session + interface="com.canonical.hud.*", + + # + # Allow access for connecting to/communication with the appmenu + # + # dbusmenu + dbus (send) + bus=session + interface="com.canonical.AppMenu.*", + dbus (receive, send) + bus=session + path=/com/canonical/menu/**, + + # gmenu + dbus (receive, send) + bus=session + interface=org.gtk.Actions, + dbus (receive, send) + bus=session + interface=org.gtk.Menus, + + # + # Access required for using freedesktop notifications + # + dbus (send) + bus=session + path=/org/freedesktop/Notifications + member=GetCapabilities, + dbus (send) + bus=session + path=/org/freedesktop/Notifications + member=GetServerInformation, + dbus (send) + bus=session + path=/org/freedesktop/Notifications + member=Notify, + dbus (receive) + bus=session + member="Notify" + peer=(name="org.freedesktop.DBus"), + dbus (receive) + bus=session + path=/org/freedesktop/Notifications + member=NotificationClosed, + dbus (send) + bus=session + path=/org/freedesktop/Notifications + member=CloseNotification, + + # accessibility + dbus (send) + bus=session + peer=(name=org.a11y.Bus), + dbus (receive) + bus=session + interface=org.a11y.atspi*, + dbus (receive, send) + bus=accessibility, + + # + # Deny potentially dangerous access + # + deny dbus bus=session path=/com/canonical/[Uu]nity/[Dd]ebug**, diff --git a/profiles/apparmor.d/abstractions/ubuntu-unity7-launcher b/profiles/apparmor.d/abstractions/ubuntu-unity7-launcher new file mode 100644 index 000000000..52f6cd438 --- /dev/null +++ b/profiles/apparmor.d/abstractions/ubuntu-unity7-launcher @@ -0,0 +1,7 @@ + # + # Access required for connecting to/communicating with the Unity Launcher + # + dbus (send) + bus=session + interface="com.canonical.Unity.LauncherEntry" + member="Update", diff --git a/profiles/apparmor.d/abstractions/ubuntu-unity7-messaging b/profiles/apparmor.d/abstractions/ubuntu-unity7-messaging new file mode 100644 index 000000000..828592eef --- /dev/null +++ b/profiles/apparmor.d/abstractions/ubuntu-unity7-messaging @@ -0,0 +1,7 @@ + # + # Access required for connecting to/communicating with the Unity messaging + # indicator + # + dbus (receive, send) + bus=session + path="/com/canonical/indicator/messages/*", From 4b01cb254473ef95f99220b08a2d3e5f2e74ab11 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Thu, 6 Feb 2014 15:15:48 -0500 Subject: [PATCH 138/183] Move os.chdir(old_cwd) to before the aa-exec call it remove the side-effect of the chdir to $HOME when using Xpra. Acked-By: Jamie Strandboge Acked-by: Steve Beattie --- utils/apparmor/sandbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/apparmor/sandbox.py b/utils/apparmor/sandbox.py index dc2d19151..51048f6ff 100644 --- a/utils/apparmor/sandbox.py +++ b/utils/apparmor/sandbox.py @@ -706,6 +706,7 @@ def run_xsandbox(command, opt): x.start() except Exception as e: error(e) + os.chdir(old_cwd) if not opt.read_path: opt.read_path = [] @@ -721,6 +722,5 @@ def run_xsandbox(command, opt): x.cleanup() raise x.cleanup() - os.chdir(old_cwd) return rc, report From 395c429cb11f1cdaa41ca99ca95e68128aec60d6 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Mon, 10 Feb 2014 22:14:54 -0800 Subject: [PATCH 139/183] Delete empty file --- apparmor/writeprofile.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 apparmor/writeprofile.py diff --git a/apparmor/writeprofile.py b/apparmor/writeprofile.py deleted file mode 100644 index e69de29bb..000000000 From 35e193620297715e56f432d1757a967c1377f5d5 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Mon, 10 Feb 2014 22:15:05 -0800 Subject: [PATCH 140/183] Convert to using python's modular translations interface. This allows the utility python modules to be used inside another tool with another textdomain binding and still have the translations for that tool and the stuff internal to the apparmor module convert properly. --- Tools/aa-audit | 9 ++++++--- Tools/aa-autodep | 10 +++++++--- Tools/aa-cleanprof | 10 +++++++--- Tools/aa-complain | 8 ++++++-- Tools/aa-disable | 8 ++++++-- Tools/aa-enforce | 8 ++++++-- Tools/aa-genprof | 6 +++++- Tools/aa-logprof | 8 ++++++-- Tools/aa-mergeprof | 8 ++++++-- Tools/aa-unconfined | 8 ++++++-- apparmor/__init__.py | 15 --------------- apparmor/aa.py | 7 ++++++- apparmor/common.py | 10 ++-------- apparmor/logparser.py | 9 +++++++-- apparmor/tools.py | 9 +++++++-- apparmor/ui.py | 7 ++++++- 16 files changed, 89 insertions(+), 51 deletions(-) diff --git a/Tools/aa-audit b/Tools/aa-audit index 92138dc4d..5bf1d038e 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -13,13 +13,16 @@ # # ---------------------------------------------------------------------- import argparse +import gettext import traceback -from apparmor.common import init_translations -init_translations() - +from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Switch the given programs to audit mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit mode')) diff --git a/Tools/aa-autodep b/Tools/aa-autodep index 8f789db14..bc4ec1466 100644 --- a/Tools/aa-autodep +++ b/Tools/aa-autodep @@ -13,12 +13,16 @@ # # ---------------------------------------------------------------------- import argparse +import gettext -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements')) parser.add_argument('--force', type=str, help=_('overwrite existing profile')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) @@ -27,4 +31,4 @@ args = parser.parse_args() autodep = apparmor.tools.aa_tools('autodep', args) -autodep.act() \ No newline at end of file +autodep.act() diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index cc70f0aec..75e42b9c7 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -13,12 +13,16 @@ # # ---------------------------------------------------------------------- import argparse +import gettext -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) @@ -27,4 +31,4 @@ args = parser.parse_args() clean = apparmor.tools.aa_tools('cleanprof', args) -clean.act() \ No newline at end of file +clean.act() diff --git a/Tools/aa-complain b/Tools/aa-complain index 34c04cf8c..efba399fa 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -13,12 +13,16 @@ # # ---------------------------------------------------------------------- import argparse +import gettext -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-r', '--remove', action='store_true', help=_('remove complain mode')) diff --git a/Tools/aa-disable b/Tools/aa-disable index 67dd46226..878eb7ef8 100644 --- a/Tools/aa-disable +++ b/Tools/aa-disable @@ -13,12 +13,16 @@ # # ---------------------------------------------------------------------- import argparse +import gettext -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-r', '--revert', action='store_true', help=_('enable the profile for the given programs')) diff --git a/Tools/aa-enforce b/Tools/aa-enforce index 8d427f155..790bcac57 100644 --- a/Tools/aa-enforce +++ b/Tools/aa-enforce @@ -13,12 +13,16 @@ # # ---------------------------------------------------------------------- import argparse +import gettext -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-r', '--remove', action='store_true', help=_('switch to complain mode')) diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 292cdaf83..9918ff46f 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -14,16 +14,20 @@ # ---------------------------------------------------------------------- import argparse import atexit +import gettext import os import re import subprocess import sys from apparmor.common import init_translations -init_translations() import apparmor.aa as apparmor +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + def sysctl_read(path): value = None with open(path, 'r') as f_in: diff --git a/Tools/aa-logprof b/Tools/aa-logprof index 26f892f39..c95a2ace3 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -13,13 +13,17 @@ # # ---------------------------------------------------------------------- import argparse +import gettext import os -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.aa as apparmor +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Process log entries to generate profiles')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('-f', '--file', type=str, help=_('path to logfile')) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 255c96dea..90001d1dc 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -13,16 +13,20 @@ # # ---------------------------------------------------------------------- import argparse +import gettext import sys -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.aa import apparmor.aamode import apparmor.severity import apparmor.cleanprofile as cleanprofile +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_('Perform a 3way merge on the given profiles')) parser.add_argument('mine', type=str, help=_('your profile')) parser.add_argument('base', type=str, help=_('base profile')) diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index ca17a03e4..d1adbfa2a 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -13,15 +13,19 @@ # # ---------------------------------------------------------------------- import argparse +import gettext import os import re import sys -from apparmor.common import init_translations -init_translations() +from apparmor.common import TRANSLATION_DOMAIN import apparmor.aa as apparmor +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports")) parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc")) args = parser.parse_args() diff --git a/apparmor/__init__.py b/apparmor/__init__.py index 3f93c45c8..94f439406 100644 --- a/apparmor/__init__.py +++ b/apparmor/__init__.py @@ -1,24 +1,9 @@ # ------------------------------------------------------------------ # # Copyright (C) 2011-2012 Canonical Ltd. -# Copyright (C) 2013 Kshitij Gupta # # 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 gettext -import locale - -def init_localisation(): - locale.setlocale(locale.LC_ALL, '') - #If a correct locale has been provided set filename else let an IOError be raised - filename = '/usr/share/locale/%s/LC_MESSAGES/apparmor-utils.mo' % locale.getlocale()[0] - try: - trans = gettext.GNUTranslations(open(filename, 'rb')) - except IOError: - trans = gettext.NullTranslations() - trans.install() - -init_localisation() diff --git a/apparmor/aa.py b/apparmor/aa.py index 2cbaed4ed..64cc6ff3b 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -13,6 +13,7 @@ # ---------------------------------------------------------------------- # No old version logs, only 2.6 + supported from __future__ import with_statement +import gettext import inspect import os import re @@ -33,13 +34,17 @@ import LibAppArmor from copy import deepcopy from apparmor.common import (AppArmorException, error, debug, msg, cmd, - open_file_read, valid_path, + open_file_read, valid_path, TRANSLATION_DOMAIN, hasher, open_file_write, convert_regexp, DebugLogger) from apparmor.ui import * from apparmor.aamode import * +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + # Setup logging incase of debugging is enabled debug_logger = DebugLogger('aa') diff --git a/apparmor/common.py b/apparmor/common.py index 51fbe857d..66f556938 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -11,7 +11,6 @@ from __future__ import print_function import codecs import collections -import gettext import glob import logging import os @@ -22,6 +21,8 @@ import termios import tty DEBUGGING = False +TRANSLATION_DOMAIN = 'apparmor-utils' + # # Utility classes @@ -166,13 +167,6 @@ def hasher(): # Creates a dictionary for any depth and returns empty dictionary otherwise return collections.defaultdict(hasher) -def init_translations(domain='apparmor-utils'): - """Installs the translations for the given domain, defaults to apparmor-utils domain""" - #Setup Translation - gettext.translation(domain, fallback=True) - gettext.install(domain) - - def convert_regexp(regexp): regex_paren = re.compile('^(.*){([^}]*)}(.*)$') regexp = regexp.strip() diff --git a/apparmor/logparser.py b/apparmor/logparser.py index 1db358ceb..47c5caa83 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -11,17 +11,22 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- +import gettext import os import re import sys import time import LibAppArmor from apparmor.common import (AppArmorException, error, debug, msg, - open_file_read, valid_path, + open_file_read, valid_path, TRANSLATION_DOMAIN, hasher, open_file_write, convert_regexp, DebugLogger) from apparmor.aamode import * +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext + class ReadLog: RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=') RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=') @@ -392,4 +397,4 @@ class ReadLog: profile = "profile_" + profile profile = profile.replace('/', '.') full_profilename = self.profile_dir + '/' + profile - return full_profilename \ No newline at end of file + return full_profilename diff --git a/apparmor/tools.py b/apparmor/tools.py index ac06f708a..347b9087f 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -11,11 +11,16 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- +import gettext import os import sys import apparmor.aa as apparmor -from apparmor.common import user_perm +from apparmor.common import user_perm, TRANSLATION_DOMAIN + +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext class aa_tools: def __init__(self, tool_name, args): @@ -177,4 +182,4 @@ class aa_tools: apparmor.delete_symlink('disable', filename) def disable_profile(self, filename): - apparmor.create_symlink('disable', filename) \ No newline at end of file + apparmor.create_symlink('disable', filename) diff --git a/apparmor/ui.py b/apparmor/ui.py index df9b892cc..7a11f7288 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -11,12 +11,17 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- +import gettext import sys import os import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast -from apparmor.common import readkey, AppArmorException, DebugLogger, msg +from apparmor.common import readkey, AppArmorException, DebugLogger, msg, TRANSLATION_DOMAIN + +# setup module translations +t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) +_ = t.gettext # Set up UI logger for separate messages from UI module debug_logger = DebugLogger('UI') From 0525932561ae5ece3d0237844d94859c9d721ee7 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Mon, 10 Feb 2014 22:17:21 -0800 Subject: [PATCH 141/183] Get rid of the globbing imports, which allows pyflakes to do a better job. Clean up a bunch of pyflakes complaints. Doing so uncovered references to apparmor/yasti.py functions in aa.py that hadn't been imported. --- apparmor/aa.py | 161 +++++++++++++++++++++--------------------- apparmor/logparser.py | 4 +- apparmor/ui.py | 3 +- 3 files changed, 85 insertions(+), 83 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 64cc6ff3b..5c1cb7bed 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -18,7 +18,6 @@ import inspect import os import re import shutil -import stat import subprocess import sys import time @@ -37,9 +36,13 @@ from apparmor.common import (AppArmorException, error, debug, msg, cmd, open_file_read, valid_path, TRANSLATION_DOMAIN, hasher, open_file_write, convert_regexp, DebugLogger) -from apparmor.ui import * +import apparmor.ui as aaui -from apparmor.aamode import * +from apparmor.aamode import (str_to_mode, mode_to_str, contains, split_mode, + mode_to_str_user, mode_contains, AA_OTHER, + flatten_mode, owner_flatten_mode) + +from apparmor.yasti import SendDataToYast, GetDataFromYast, shutdown_yast # setup module translations t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) @@ -137,7 +140,7 @@ def fatal_error(message): sys.exit(1) # Else tell user what happened - UI_Important(message) + aaui.UI_Important(message) shutdown_yast() sys.exit(1) @@ -256,13 +259,13 @@ def enforce(path): def set_complain(filename, program): """Sets the profile to complain mode""" - UI_Info(_('Setting %s to complain mode.') % program) + aaui.UI_Info(_('Setting %s to complain mode.') % program) create_symlink('force-complain', filename) change_profile_flags(filename, program, 'complain', True) def set_enforce(filename, program): """Sets the profile to enforce mode""" - UI_Info(_('Setting %s to enforce mode.') % program) + aaui.UI_Info(_('Setting %s to enforce mode.') % program) delete_symlink('force-complain', filename) delete_symlink('disable', filename) change_profile_flags(filename, program, 'complain', False) @@ -439,9 +442,9 @@ def delete_profile(local_prof): #prof_unload(local_prof) def confirm_and_abort(): - ans = UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n') + ans = aaui.UI_YesNo(_('Are you sure you want to abandon this set of profile changes and exit?'), 'n') if ans == 'y': - UI_Info(_('Abandoning all changes.')) + aaui.UI_Info(_('Abandoning all changes.')) shutdown_yast() for prof in created: delete_profile(prof) @@ -454,13 +457,13 @@ def get_profile(prof_name): local_profiles = [] profile_hash = hasher() if repo_is_enabled(): - UI_BusyStart(_('Connecting to repository...')) + aaui.UI_BusyStart(_('Connecting to repository...')) status_ok, ret = fetch_profiles_by_name(repo_url, distro, prof_name) - UI_BusyStop() + aaui.UI_BusyStop() if status_ok: profile_hash = ret else: - UI_Important(_('WARNING: Error fetching profiles from the repository')) + aaui.UI_Important(_('WARNING: Error fetching profiles from the repository')) inactive_profile = get_inactive_profile(prof_name) if inactive_profile: uname = 'Inactive local profile for %s' % prof_name @@ -498,11 +501,11 @@ def get_profile(prof_name): ans = '' while 'CMD_USE_PROFILE' not in ans and 'CMD_CREATE_PROFILE' not in ans: - ans, arg = UI_PromptUser(q) + ans, arg = aaui.UI_PromptUser(q) p = profile_hash[options[arg]] q['selected'] = options.index(options[arg]) if ans == 'CMD_VIEW_PROFILE': - if UI_mode == 'yast': + if aaui.UI_mode == 'yast': SendDataToYast({ 'type': 'dialogue-view-profile', 'user': options[arg], @@ -535,7 +538,7 @@ def activate_repo_profiles(url, profiles, complain): if complain: fname = get_profile_filename(pname) set_profile_flags(profile_dir + fname, 'complain') - UI_Info(_('Setting %s to complain mode.') % pname) + aaui.UI_Info(_('Setting %s to complain mode.') % pname) except Exception as e: sys.stderr.write(_("Error activating profiles: %s") % e) @@ -684,7 +687,7 @@ def sync_profile(): if not status_ok: if not ret: ret = 'UNKNOWN ERROR' - UI_Important(_('WARNING: Error synchronizing profiles with the repository:\n%s\n') % ret) + aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository:\n%s\n') % ret) else: users_repo_profiles = ret serialize_opts['NO_FLAGS'] = True @@ -722,7 +725,7 @@ def sync_profile(): else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s') % ret) + aaui.UI_Important(_('WARNING: Error synchronizing profiles with the repository\n%s') % ret) continue if p_repo != p_local: changed_profiles.append(prof) @@ -748,7 +751,7 @@ def fetch_profiles_by_user(url, distro, user): def submit_created_profiles(new_profiles): #url = cfg['repository']['url'] if new_profiles: - if UI_mode == 'yast': + if aaui.UI_mode == 'yast': title = 'New Profiles' message = 'Please select the newly created profiles that you would like to store in the repository' yast_select_and_upload_profiles(title, message, new_profiles) @@ -760,7 +763,7 @@ def submit_created_profiles(new_profiles): def submit_changed_profiles(changed_profiles): #url = cfg['repository']['url'] if changed_profiles: - if UI_mode == 'yast': + if aaui.UI_mode == 'yast': title = 'Changed Profiles' message = 'Please select which of the changed profiles would you like to upload to the repository' yast_select_and_upload_profiles(title, message, changed_profiles) @@ -811,8 +814,8 @@ def yast_select_and_upload_profiles(title, message, profiles_up): else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (p, ret)) - UI_Info(_('Uploaded changes to repository.')) + aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (p, ret)) + aaui.UI_Info(_('Uploaded changes to repository.')) if yarg.get('NEVER_ASK_AGAIN'): unselected_profiles = [] for p in profs: @@ -838,13 +841,13 @@ def console_select_and_upload_profiles(title, message, profiles_up): q['selected'] = 0 ans = '' while 'CMD_UPLOAD_CHANGES' not in ans and 'CMD_ASK_NEVER' not in ans and 'CMD_ASK_LATER' not in ans: - ans, arg = UI_PromptUser(q) + ans, arg = aaui.UI_PromptUser(q) if ans == 'CMD_VIEW_CHANGES': display_changes(profs[arg][2], profs[arg][1]) if ans == 'CMD_NEVER_ASK': set_profiles_local_only([i[0] for i in profs]) elif ans == 'CMD_UPLOAD_CHANGES': - changelog = UI_GetString(_('Changelog Entry: '), '') + changelog = aaui.UI_GetString(_('Changelog Entry: '), '') user, passw = get_repo_user_pass() if user and passw: for p_data in profs: @@ -858,13 +861,13 @@ def console_select_and_upload_profiles(title, message, profiles_up): newid = newprof['id'] set_repo_info(aa[prof][prof], url, user, newid) write_profile_ui_feedback(prof) - UI_Info('Uploaded %s to repository' % prof) + aaui.UI_Info('Uploaded %s to repository' % prof) else: if not ret: ret = 'UNKNOWN ERROR' - UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (prof, ret)) + aaui.UI_Important(_('WARNING: An error occurred while uploading the profile %s\n%s') % (prof, ret)) else: - UI_Important(_('Repository Error\nRegistration or Signin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.')) + aaui.UI_Important(_('Repository Error\nRegistration or Signin was unsuccessful. User login\ninformation is required to upload profiles to the repository.\nThese changes could not be sent.')) def set_profiles_local_only(profs): for p in profs: @@ -990,7 +993,7 @@ def handle_children(profile, hat, root): seen_events += 1 - ans = UI_PromptUser(q) + ans = aaui.UI_PromptUser(q) transitions[context] = ans @@ -1048,7 +1051,7 @@ def handle_children(profile, hat, root): else: do_execute = True - if mode & AA_MAY_LINK: + if mode & apparmor.aamode.AA_MAY_LINK: regex_link = re.compile('^from (.+) to (.+)$') match = regex_link.search(detail) if match: @@ -1246,7 +1249,7 @@ def handle_children(profile, hat, root): ans = '' while not regex_options.search(ans): - ans = UI_PromptUser(q)[0].strip() + ans = aaui.UI_PromptUser(q)[0].strip() if ans.startswith('CMD_EXEC_IX_'): exec_toggle = not exec_toggle q['functions'] = [] @@ -1257,7 +1260,7 @@ def handle_children(profile, hat, root): arg = exec_target ynans = 'n' if profile == hat: - ynans = UI_YesNo(_('Are you specifying a transition to a local profile?'), 'n') + ynans = aaui.UI_YesNo(_('Are you specifying a transition to a local profile?'), 'n') if ynans == 'y': if ans == 'CMD_nx': ans = 'CMD_cx' @@ -1269,7 +1272,7 @@ def handle_children(profile, hat, root): else: ans = 'CMD_pix' - to_name = UI_GetString(_('Enter profile name to transition to: '), arg) + to_name = aaui.UI_GetString(_('Enter profile name to transition to: '), arg) regex_optmode = re.compile('CMD_(px|cx|nx|pix|cix|nix)') if ans == 'CMD_ix': @@ -1282,18 +1285,18 @@ def handle_children(profile, hat, root): if parent_uses_ld_xxx: px_msg = _("Should AppArmor sanitise the environment when\nswitching profiles?\n\nSanitising environment is more secure,\nbut this application appears to be using LD_PRELOAD\nor LD_LIBRARY_PATH and sanitising the environment\ncould cause functionality problems.") - ynans = UI_YesNo(px_msg, px_default) + ynans = aaui.UI_YesNo(px_msg, px_default) if ynans == 'y': # Disable the unsafe mode - exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) + exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE)) elif ans == 'CMD_ux': exec_mode = str_to_mode('ux') - ynans = UI_YesNo(_("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?") % exec_target, 'n') + ynans = aaui.UI_YesNo(_("Launching processes in an unconfined state is a very\ndangerous operation and can cause serious security holes.\n\nAre you absolutely certain you wish to remove all\nAppArmor protection when executing %s ?") % exec_target, 'n') if ynans == 'y': - ynans = UI_YesNo(_("Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), 'y') + ynans = aaui.UI_YesNo(_("Should AppArmor sanitise the environment when\nrunning this program unconfined?\n\nNot sanitising the environment when unconfining\na program opens up significant security holes\nand should be avoided if at all possible."), 'y') if ynans == 'y': # Disable the unsafe mode - exec_mode = exec_mode - (AA_EXEC_UNSAFE | AA_OTHER(AA_EXEC_UNSAFE)) + exec_mode = exec_mode - (apparmor.aamode.AA_EXEC_UNSAFE | AA_OTHER(apparmor.aamode.AA_EXEC_UNSAFE)) else: ans = 'INVALID' transitions[context_new] = ans @@ -1366,7 +1369,7 @@ def handle_children(profile, hat, root): if not os.path.exists(get_profile_filename(exec_target)): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') %exec_target, 'n') + ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') %exec_target, 'n') if ynans == 'y': helpers[exec_target] = 'enforce' if to_name: @@ -1384,7 +1387,7 @@ def handle_children(profile, hat, root): if not aa[profile].get(exec_target, False): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n') + ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n') if ynans == 'y': hat = exec_target aa[profile][hat]['declared'] = False @@ -1493,9 +1496,9 @@ def ask_the_questions(): for aamode in sorted(log_dict.keys()): # Describe the type of changes if aamode == 'PERMITTING': - UI_Info(_('Complain-mode changes:')) + aaui.UI_Info(_('Complain-mode changes:')) elif aamode == 'REJECTING': - UI_Info(_('Enforce-mode changes:')) + aaui.UI_Info(_('Enforce-mode changes:')) else: # This is so wrong! fatal_error(_('Invalid mode found: %s') % aamode) @@ -1551,7 +1554,7 @@ def ask_the_questions(): done = False while not done: - ans, selected = UI_PromptUser(q) + ans, selected = aaui.UI_PromptUser(q) # Ignore the log entry if ans == 'CMD_IGNORE_ENTRY': done = True @@ -1583,23 +1586,23 @@ def ask_the_questions(): deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True - UI_Info(_('Adding %s to profile.') % selection) + aaui.UI_Info(_('Adding %s to profile.') % selection) if deleted: - UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) aa[profile][hat]['allow']['capability'][capability]['set'] = True aa[profile][hat]['allow']['capability'][capability]['audit'] = audit_toggle changed[profile] = True - UI_Info(_('Adding capability %s to profile.') % capability) + aaui.UI_Info(_('Adding capability %s to profile.') % capability) done = True elif ans == 'CMD_DENY': aa[profile][hat]['deny']['capability'][capability]['set'] = True changed[profile] = True - UI_Info(_('Denying capability %s to profile.') % capability) + aaui.UI_Info(_('Denying capability %s to profile.') % capability) done = True else: done = False @@ -1637,7 +1640,7 @@ def ask_the_questions(): if cam: deny_audit |= cam - if deny_mode & AA_MAY_EXEC: + if deny_mode & apparmor.aamode.AA_MAY_EXEC: deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE # Mask off the denied modes @@ -1646,10 +1649,10 @@ def ask_the_questions(): # If we get an exec request from some kindof event that generates 'PERMITTING X' # check if its already in allow_mode # if not add ix permission - if mode & AA_MAY_EXEC: + if mode & apparmor.aamode.AA_MAY_EXEC: # Remove all type access permission mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE - if not allow_mode & AA_MAY_EXEC: + if not allow_mode & apparmor.aamode.AA_MAY_EXEC: mode |= str_to_mode('ix') # m is not implied by ix @@ -1794,7 +1797,7 @@ def ask_the_questions(): seen_events += 1 - ans, selected = UI_PromptUser(q) + ans, selected = aaui.UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True @@ -1818,9 +1821,9 @@ def ask_the_questions(): deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True changed[profile] = True - UI_Info(_('Adding %s to profile.') % path) + aaui.UI_Info(_('Adding %s to profile.') % path) if deleted: - UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: if aa[profile][hat]['allow']['path'][path].get('mode', False): @@ -1858,9 +1861,9 @@ def ask_the_questions(): changed[profile] = True - UI_Info(_('Adding %s %s to profile') % (path, mode_to_str_user(mode))) + aaui.UI_Info(_('Adding %s %s to profile') % (path, mode_to_str_user(mode))) if deleted: - UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) elif ans == 'CMD_DENY': path = options[selected].strip() @@ -1876,11 +1879,11 @@ def ask_the_questions(): elif ans == 'CMD_NEW': arg = options[selected] if not re_match_include(arg): - ans = UI_GetString(_('Enter new path: '), arg) + ans = aaui.UI_GetString(_('Enter new path: '), arg) if ans: if not matchliteral(ans, path): ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path,ans) - key = UI_YesNo(ynprompt, 'n') + key = aaui.UI_YesNo(ynprompt, 'n') if key == 'n': continue @@ -1946,7 +1949,7 @@ def ask_the_questions(): done = False while not done: - ans, selected = UI_PromptUser(q) + ans, selected = aaui.UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True break @@ -1977,9 +1980,9 @@ def ask_the_questions(): changed[profile] = True - UI_Info(_('Adding %s to profile') % selection) + aaui.UI_Info(_('Adding %s to profile') % selection) if deleted: - UI_Info(_('Deleted %s previous matching profile entries.') % deleted) + aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle @@ -1987,13 +1990,13 @@ def ask_the_questions(): changed[profile] = True - UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) + aaui.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) elif ans == 'CMD_DENY': done = True aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True changed[profile] = True - UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) + aaui.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) else: done = False @@ -2206,10 +2209,10 @@ def do_logprof_pass(logmark='', passno=0, pid=pid): skip = hasher() # filelist = hasher() - UI_Info(_('Reading log entries from %s.') %filename) + aaui.UI_Info(_('Reading log entries from %s.') %filename) if not passno: - UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) + aaui.UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) read_profiles() if not sev_db: @@ -2236,7 +2239,7 @@ def do_logprof_pass(logmark='', passno=0, pid=pid): ask_the_questions() - if UI_mode == 'yast': + if aaui.UI_mode == 'yast': # To-Do pass @@ -2268,7 +2271,7 @@ def save_profiles(): if changed_list: - if UI_mode == 'yast': + if aaui.UI_mode == 'yast': # To-Do selected_profiles = [] profile_changes = dict() @@ -2310,7 +2313,7 @@ def save_profiles(): while ans != 'CMD_SAVE_CHANGES': if not changed: return - ans, arg = UI_PromptUser(q) + ans, arg = aaui.UI_PromptUser(q) if ans == 'CMD_SAVE_SELECTED': profile_name = list(changed.keys())[arg] write_profile_ui_feedback(profile_name) @@ -2374,8 +2377,8 @@ def get_profile_diff(oldprofile, newprofile): return ''.join(diff) def display_changes(oldprofile, newprofile): - if UI_mode == 'yast': - UI_LongMessage(_('Profile Changes'), get_profile_diff(oldprofile, newprofile)) + if aaui.UI_mode == 'yast': + aaui.UI_LongMessage(_('Profile Changes'), get_profile_diff(oldprofile, newprofile)) else: difftemp = generate_diff(oldprofile, newprofile) subprocess.call('less %s' %difftemp.name, shell=True) @@ -2386,7 +2389,7 @@ def display_changes_with_comments(oldprofile, newprofile): """Compare the new profile with the existing profile inclusive of all the comments""" if not os.path.exists(oldprofile): raise AppArmorException(_("Can't find existing profile %s to compare changes.") %oldprofile) - if UI_mode == 'yast': + if aaui.UI_mode == 'yast': #To-Do pass else: @@ -2714,13 +2717,13 @@ def parse_profile_data(data, file, do_include): link = strip_quotes(matches[6]) value = strip_quotes(matches[7]) profile_data[profile][hat][allow]['link'][link]['to'] = value - profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', set()) | AA_MAY_LINK + profile_data[profile][hat][allow]['link'][link]['mode'] = profile_data[profile][hat][allow]['link'][link].get('mode', set()) | apparmor.aamode.AA_MAY_LINK if subset: - profile_data[profile][hat][allow]['link'][link]['mode'] |= AA_LINK_SUBSET + profile_data[profile][hat][allow]['link'][link]['mode'] |= apparmor.aamode.AA_LINK_SUBSET if audit: - profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', set()) | AA_LINK_SUBSET + profile_data[profile][hat][allow]['link'][link]['audit'] = profile_data[profile][hat][allow]['link'][link].get('audit', set()) | apparmor.aamode.AA_LINK_SUBSET else: profile_data[profile][hat][allow]['link'][link]['audit'] = set() @@ -3172,7 +3175,7 @@ def write_link_rules(prof_data, depth, allow): for path in sorted(prof_data[allow]['link'].keys()): to_name = prof_data[allow]['link'][path]['to'] subset = '' - if prof_data[allow]['link'][path]['mode'] & AA_LINK_SUBSET: + if prof_data[allow]['link'][path]['mode'] & apparmor.aamode.AA_LINK_SUBSET: subset = 'subset' audit = '' if prof_data[allow]['link'][path].get('audit', False): @@ -3577,11 +3580,11 @@ def serialize_profile_from_old_profile(profile_data, name, options): value = strip_quotes(matches[7]) if not write_prof_data[hat][allow]['link'][link]['to'] == value: correct = False - if not write_prof_data[hat][allow]['link'][link]['mode'] & AA_MAY_LINK: + if not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_MAY_LINK: correct = False - if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & AA_LINK_SUBSET: + if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_LINK_SUBSET: correct = False - if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & AA_LINK_SUBSET: + if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & apparmor.aamode.AA_LINK_SUBSET: correct = False if correct: @@ -3891,7 +3894,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): return string+'\n' def write_profile_ui_feedback(profile): - UI_Info(_('Writing updated profile for %s.') %profile) + aaui.UI_Info(_('Writing updated profile for %s.') %profile) write_profile(profile) def write_profile(profile): @@ -3937,19 +3940,19 @@ def profile_known_exec(profile, typ, exec_target): m = [] cm, am, m = rematchfrag(profile, 'deny', exec_target) - if cm & AA_MAY_EXEC: + if cm & apparmor.aamode.AA_MAY_EXEC: return -1 cm, am, m = match_prof_incs_to_path(profile, 'deny', exec_target) - if cm & AA_MAY_EXEC: + if cm & apparmor.aamode.AA_MAY_EXEC: return -1 cm, am, m = rematchfrag(profile, 'allow', exec_target) - if cm & AA_MAY_EXEC: + if cm & apparmor.aamode.AA_MAY_EXEC: return 1 cm, am, m = match_prof_incs_to_path(profile, 'allow', exec_target) - if cm & AA_MAY_EXEC: + if cm & apparmor.aamode.AA_MAY_EXEC: return 1 return 0 diff --git a/apparmor/logparser.py b/apparmor/logparser.py index 47c5caa83..d0a08a632 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -17,11 +17,11 @@ import re import sys import time import LibAppArmor -from apparmor.common import (AppArmorException, error, debug, msg, +from apparmor.common import (AppArmorException, error, debug, open_file_read, valid_path, TRANSLATION_DOMAIN, hasher, open_file_write, convert_regexp, DebugLogger) -from apparmor.aamode import * +from apparmor.aamode import validate_log_mode, log_str_to_mode, hide_log_mode, AA_MAY_EXEC # setup module translations t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) diff --git a/apparmor/ui.py b/apparmor/ui.py index 7a11f7288..fd8405e5c 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -13,11 +13,10 @@ # ---------------------------------------------------------------------- import gettext import sys -import os import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast -from apparmor.common import readkey, AppArmorException, DebugLogger, msg, TRANSLATION_DOMAIN +from apparmor.common import readkey, AppArmorException, DebugLogger, TRANSLATION_DOMAIN # setup module translations t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) From 4987e5b1586bf24942e0c4b880734b2443a4f30c Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Mon, 10 Feb 2014 22:20:36 -0800 Subject: [PATCH 142/183] Clean up a bunch of pep8 warnings, as found by running: pep8 --ignore=E501,E302 on individual files. This uncovered a bug where the type of an object was being compared to a type of a list. However, a python string is a list of characters, and so would cause the test to be true. --- apparmor/aa.py | 414 ++++++++++++++++++++++-------------------- apparmor/aamode.py | 18 +- apparmor/common.py | 21 +-- apparmor/config.py | 14 +- apparmor/logparser.py | 40 ++-- apparmor/severity.py | 37 ++-- apparmor/tools.py | 40 ++-- apparmor/ui.py | 50 +++-- apparmor/yasti.py | 1 - 9 files changed, 320 insertions(+), 315 deletions(-) diff --git a/apparmor/aa.py b/apparmor/aa.py index 5c1cb7bed..aecbae419 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -75,7 +75,7 @@ include = dict() existing_profiles = dict() -seen_events = 0 # was our +seen_events = 0 # was our # To store the globs entered by users so they can be provided again user_globs = [] @@ -84,7 +84,7 @@ user_globs = [] t = hasher() # dict() transitions = hasher() aa = hasher() # Profiles originally in sd, replace by aa -original_aa = hasher() +original_aa = hasher() extras = hasher() # Inactive profiles from extras ### end our log = [] @@ -93,11 +93,11 @@ pid = dict() seen = hasher() # dir() profile_changes = hasher() prelog = hasher() -log_dict = hasher()#dict() +log_dict = hasher() # dict() changed = dict() created = [] skip = hasher() -helpers = dict() # Preserve this between passes # was our +helpers = dict() # Preserve this between passes # was our ### logprof ends filelist = hasher() # File level variables and stuff in config files @@ -118,7 +118,7 @@ def check_for_LD_XXX(file): return False size = os.stat(file).st_size # Limit to checking files under 100k for the sake of speed - if size >100000: + if size > 100000: return False with open_file_read(file, encoding='ascii') as f_in: for line in f_in: @@ -136,7 +136,7 @@ def fatal_error(message): caller = inspect.stack()[1][3] # If caller is SendDataToYast or GetDatFromYast simply exit - if caller == 'SendDataToYast' or caller== 'GetDatFromYast': + if caller == 'SendDataToYast' or caller == 'GetDatFromYast': sys.exit(1) # Else tell user what happened @@ -172,7 +172,7 @@ def check_for_apparmor(): def which(file): """Returns the executable fullpath for the file, None otherwise""" - if sys.version_info >= (3,3): + if sys.version_info >= (3, 3): return shutil.which(file) env_dirs = os.getenv('PATH').split(':') for env_dir in env_dirs: @@ -246,14 +246,14 @@ def name_to_prof_filename(prof_filename): def complain(path): """Sets the profile to complain mode if it exists""" prof_filename, name = name_to_prof_filename(path) - if not prof_filename : + if not prof_filename: fatal_error(_("Can't find %s") % path) set_complain(prof_filename, name) def enforce(path): """Sets the profile to enforce mode if it exists""" prof_filename, name = name_to_prof_filename(path) - if not prof_filename : + if not prof_filename: fatal_error(_("Can't find %s") % path) set_enforce(prof_filename, name) @@ -272,7 +272,7 @@ def set_enforce(filename, program): def delete_symlink(subdir, filename): path = filename - link = re.sub('^%s'%profile_dir, '%s/%s'%(profile_dir, subdir), path) + link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path) if link != path and os.path.islink(link): os.remove(link) @@ -280,13 +280,13 @@ def create_symlink(subdir, filename): path = filename bname = os.path.basename(filename) if not bname: - raise AppArmorException(_('Unable to find basename for %s.')%filename) + raise AppArmorException(_('Unable to find basename for %s.') % filename) #print(filename) - link = re.sub('^%s'%profile_dir, '%s/%s'%(profile_dir, subdir), path) + link = re.sub('^%s' % profile_dir, '%s/%s' % (profile_dir, subdir), path) #print(link) #link = link + '/%s'%bname #print(link) - symlink_dir=os.path.dirname(link) + symlink_dir = os.path.dirname(link) if not os.path.exists(symlink_dir): # If the symlink directory does not exist create it os.makedirs(symlink_dir) @@ -295,7 +295,7 @@ def create_symlink(subdir, filename): try: os.symlink(filename, link) except: - raise AppArmorException(_('Could not create %s symlink to %s.')%(link, filename)) + raise AppArmorException(_('Could not create %s symlink to %s.') % (link, filename)) def head(file): """Returns the first/head line of the file""" @@ -308,7 +308,7 @@ def head(file): pass return first else: - raise AppArmorException(_('Unable to read first line from %s: File Not Found') %file) + raise AppArmorException(_('Unable to read first line from %s: File Not Found') % file) def get_output(params): """Returns the return code output by running the program with the args given in the list""" @@ -322,7 +322,7 @@ def get_output(params): # Get the output of the program output = subprocess.check_output(params) except OSError as e: - raise AppArmorException(_("Unable to fork: %s\n\t%s") %(program, str(e))) + raise AppArmorException(_("Unable to fork: %s\n\t%s") % (program, str(e))) # If exit-codes besides 0 except subprocess.CalledProcessError as e: output = e.output @@ -427,7 +427,7 @@ def create_new_profile(localfile): created.append(localfile) changed.append(localfile) - + debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) return {localfile: local_profile} @@ -506,8 +506,7 @@ def get_profile(prof_name): q['selected'] = options.index(options[arg]) if ans == 'CMD_VIEW_PROFILE': if aaui.UI_mode == 'yast': - SendDataToYast({ - 'type': 'dialogue-view-profile', + SendDataToYast({'type': 'dialogue-view-profile', 'user': options[arg], 'profile': p['profile'], 'profile_type': p['profile_type'] @@ -593,7 +592,7 @@ def get_profile_flags(filename, program): if profile == program: return flags - raise AppArmorException(_('%s contains no profile')%filename) + raise AppArmorException(_('%s contains no profile') % filename) def change_profile_flags(filename, program, flag, set_flag): old_flags = get_profile_flags(filename, program) @@ -603,7 +602,7 @@ def change_profile_flags(filename, program, flag, set_flag): # Flags maybe white-space and/or , separated old_flags = old_flags.split(',') - if type(old_flags) == type([]): + if not isinstance(old_flags, str): for i in old_flags: newflags += i.split() else: @@ -627,7 +626,7 @@ def set_profile_flags(prof_filename, program, newflags): regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$') if os.path.isfile(prof_filename): with open_file_read(prof_filename) as f_in: - temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename , suffix='~', delete=False, dir=profile_dir) + temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir) shutil.copymode(prof_filename, temp_file.name) with open_file_write(temp_file.name) as f_out: for line in f_in: @@ -778,8 +777,7 @@ def yast_select_and_upload_profiles(title, message, profiles_up): profs = profiles_up[:] for p in profs: profile_changes[p[0]] = get_profile_diff(p[2], p[1]) - SendDataToYast({ - 'type': 'dialog-select-profiles', + SendDataToYast({'type': 'dialog-select-profiles', 'title': title, 'explanation': message, 'default_select': 'false', @@ -855,7 +853,7 @@ def console_select_and_upload_profiles(title, message, profiles_up): prof_string = p_data[1] status_ok, ret = upload_profile(url, user, passw, cfg['repository']['distro'], - prof, prof_string, changelog ) + prof, prof_string, changelog) if status_ok: newprof = ret newid = newprof['id'] @@ -894,7 +892,7 @@ def build_x_functions(default, options, exec_toggle): ret_list.append('CMD_EXEC_IX_OFF') if 'u' in options: ret_list.append('CMD_ux') - + else: if 'i' in options: ret_list.append('CMD_ix') @@ -1098,7 +1096,7 @@ def handle_children(profile, hat, root): combinedaudit = set() ## Check return Value Consistency # Check if path matches any existing regexps in profile - cm, am , m = rematchfrag(aa[profile][hat], 'allow', exec_target) + cm, am, m = rematchfrag(aa[profile][hat], 'allow', exec_target) if cm: combinedmode |= cm if am: @@ -1210,7 +1208,7 @@ def handle_children(profile, hat, root): default = None if 'p' in options and os.path.exists(get_profile_filename(exec_target)): default = 'CMD_px' - sys.stdout.write(_('Target profile exists: %s\n') %get_profile_filename(exec_target)) + sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename(exec_target)) elif 'i' in options: default = 'CMD_ix' elif 'c' in options: @@ -1353,7 +1351,7 @@ def handle_children(profile, hat, root): if ans == 'CMD_ix': if hat: - profile_changes[pid] = '%s//%s' %(profile, hat) + profile_changes[pid] = '%s//%s' % (profile, hat) else: profile_changes[pid] = '%s//' % profile elif re.search('^CMD_(px|nx|pix|nix)', ans): @@ -1369,7 +1367,7 @@ def handle_children(profile, hat, root): if not os.path.exists(get_profile_filename(exec_target)): ynans = 'y' if exec_mode & str_to_mode('i'): - ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') %exec_target, 'n') + ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n') if ynans == 'y': helpers[exec_target] = 'enforce' if to_name: @@ -1528,7 +1526,7 @@ def ask_the_questions(): q = hasher() if newincludes: - options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) + options += list(map(lambda inc: '#include <%s>' % inc, sorted(set(newincludes)))) if options: options.append('capability %s' % capability) @@ -1582,7 +1580,7 @@ def ask_the_questions(): match = re_match_include(selection) if match: deleted = False - inc = match #.groups()[0] + inc = match # .groups()[0] deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True @@ -1686,7 +1684,7 @@ def ask_the_questions(): if aa[profile][hat][incname]: continue if incname.startswith(profile_dir): - incname = incname.replace(profile_dir+'/', '', 1) + incname = incname.replace(profile_dir + '/', '', 1) include_valid = valid_include('', incname) @@ -1733,7 +1731,7 @@ def ask_the_questions(): owner_toggle = cfg['settings']['default_owner_prompt'] done = False while not done: - q = hasher() + q = hasher() q['headers'] = [_('Profile'), combine_name(profile, hat), _('Path'), path] @@ -1814,13 +1812,13 @@ def ask_the_questions(): elif ans == 'CMD_ALLOW': path = options[selected] done = True - match = re_match_include(path) #.search('^#include\s+<(.+)>$', path) + match = re_match_include(path) # .search('^#include\s+<(.+)>$', path) if match: - inc = match #.groups()[0] + inc = match # .groups()[0] deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) - aa[profile][hat]['include'][inc] = True - changed[profile] = True + aa[profile][hat]['include'][inc] = True + changed[profile] = True aaui.UI_Info(_('Adding %s to profile.') % path) if deleted: aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) @@ -1853,7 +1851,7 @@ def ask_the_questions(): tmpmode = set() if audit_toggle == 1: - tmpmode = mode- allow_mode + tmpmode = mode - allow_mode elif audit_toggle == 2: tmpmode = mode @@ -1882,7 +1880,7 @@ def ask_the_questions(): ans = aaui.UI_GetString(_('Enter new path: '), arg) if ans: if not matchliteral(ans, path): - ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path,ans) + ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %s\n Entered Path: %s\nDo you really want to use this path?') % (path, ans) key = aaui.UI_YesNo(ynprompt, 'n') if key == 'n': continue @@ -1927,7 +1925,7 @@ def ask_the_questions(): newincludes = match_net_includes(aa[profile][hat], family, sock_type) q = hasher() if newincludes: - options += list(map(lambda s: '#include <%s>'%s, sorted(set(newincludes)))) + options += list(map(lambda s: '#include <%s>' % s, sorted(set(newincludes)))) if options: options.append('network %s %s' % (family, sock_type)) q['options'] = options @@ -1971,9 +1969,9 @@ def ask_the_questions(): elif ans == 'CMD_ALLOW': selection = options[selected] done = True - if re_match_include(selection): #re.search('#include\s+<.+>$', selection): - inc = re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0] - deleted = 0 + if re_match_include(selection): # re.search('#include\s+<.+>$', selection): + inc = re_match_include(selection) # re.search('#include\s+<(.+)>$', selection).groups()[0] + deleted = 0 deleted = delete_duplicates(aa[profile][hat], inc) aa[profile][hat]['include'][inc] = True @@ -2006,13 +2004,13 @@ def glob_path(newpath): if newpath[-1] == '/': if newpath[-4:] == '/**/' or newpath[-3:] == '/*/': # /foo/**/ and /foo/*/ => /**/ - newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) #re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) + newpath = re.sub('/[^/]+/\*{1,2}/$', '/**/', newpath) # re.sub('/[^/]+/\*{1,2}$/', '/\*\*/', newpath) elif re.search('/[^/]+\*\*[^/]*/$', newpath): # /foo**/ and /foo**bar/ => /**/ - newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath) + newpath = re.sub('/[^/]+\*\*[^/]*/$', '/**/', newpath) elif re.search('/\*\*[^/]+/$', newpath): # /**bar/ => /**/ - newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath) + newpath = re.sub('/\*\*[^/]+/$', '/**/', newpath) else: newpath = re.sub('/[^/]+/$', '/*/', newpath) else: @@ -2024,7 +2022,7 @@ def glob_path(newpath): newpath = re.sub('/[^/]*\*\*[^/]+$', '/**', newpath) elif re.search('/[^/]+\*\*$', newpath): # /foo** => /** - newpath = re.sub('/[^/]+\*\*$', '/**', newpath) + newpath = re.sub('/[^/]+\*\*$', '/**', newpath) else: newpath = re.sub('/[^/]+$', '/*', newpath) return newpath @@ -2035,19 +2033,19 @@ def glob_path_withext(newpath): match = re.search('/\*{1,2}(\.[^/]+)$', newpath) if match: # /foo/**.ext and /foo/*.ext => /**.ext - newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**'+match.groups()[0], newpath) + newpath = re.sub('/[^/]+/\*{1,2}\.[^/]+$', '/**' + match.groups()[0], newpath) elif re.search('/[^/]+\*\*[^/]*\.[^/]+$', newpath): # /foo**.ext and /foo**bar.ext => /**.ext match = re.search('/[^/]+\*\*[^/]*(\.[^/]+)$', newpath) - newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**'+match.groups()[0], newpath) + newpath = re.sub('/[^/]+\*\*[^/]*\.[^/]+$', '/**' + match.groups()[0], newpath) elif re.search('/\*\*[^/]+\.[^/]+$', newpath): # /**foo.ext => /**.ext match = re.search('/\*\*[^/]+(\.[^/]+)$', newpath) - newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**'+match.groups()[0], newpath) + newpath = re.sub('/\*\*[^/]+\.[^/]+$', '/**' + match.groups()[0], newpath) else: match = re.search('(\.[^/]+)$', newpath) if match: - newpath = re.sub('/[^/]+(\.[^/]+)$', '/*'+match.groups()[0], newpath) + newpath = re.sub('/[^/]+(\.[^/]+)$', '/*' + match.groups()[0], newpath) return newpath def delete_net_duplicates(netrules, incnetrules): @@ -2089,7 +2087,7 @@ def delete_cap_duplicates(profilecaps, inccaps): def delete_path_duplicates(profile, incname, allow): deleted = [] for entry in profile[allow]['path'].keys(): - if entry == '#include <%s>'%incname: + if entry == '#include <%s>' % incname: continue cm, am, m = match_include_to_path(incname, allow, entry) if cm and mode_contains(cm, profile[allow]['path'][entry]['mode']) and mode_contains(am, profile[allow]['path'][entry]['audit']): @@ -2209,10 +2207,10 @@ def do_logprof_pass(logmark='', passno=0, pid=pid): skip = hasher() # filelist = hasher() - aaui.UI_Info(_('Reading log entries from %s.') %filename) + aaui.UI_Info(_('Reading log entries from %s.') % filename) if not passno: - aaui.UI_Info(_('Updating AppArmor profiles in %s.') %profile_dir) + aaui.UI_Info(_('Updating AppArmor profiles in %s.') % profile_dir) read_profiles() if not sev_db: @@ -2281,8 +2279,7 @@ def save_profiles(): profile_changes[prof] = get_profile_diff(oldprofile, newprofile) explanation = _('Select which profile changes you would like to save to the\nlocal profile set.') title = _('Local profile changes') - SendDataToYast({ - 'type': 'dialog-select-profiles', + SendDataToYast({'type': 'dialog-select-profiles', 'title': title, 'explanation': explanation, 'dialog_select': 'true', @@ -2307,7 +2304,7 @@ def save_profiles(): q['default'] = 'CMD_VIEW_CHANGES' q['options'] = changed q['selected'] = 0 - p =None + p = None ans = '' arg = None while ans != 'CMD_SAVE_CHANGES': @@ -2358,7 +2355,7 @@ def generate_diff(oldprofile, newprofile): difftemp = tempfile.NamedTemporaryFile('w', delete=False) - subprocess.call('diff -u -p %s %s > %s' %(oldtemp.name, newtemp.name, difftemp.name), shell=True) + subprocess.call('diff -u -p %s %s > %s' % (oldtemp.name, newtemp.name, difftemp.name), shell=True) oldtemp.close() newtemp.close() @@ -2381,14 +2378,14 @@ def display_changes(oldprofile, newprofile): aaui.UI_LongMessage(_('Profile Changes'), get_profile_diff(oldprofile, newprofile)) else: difftemp = generate_diff(oldprofile, newprofile) - subprocess.call('less %s' %difftemp.name, shell=True) + subprocess.call('less %s' % difftemp.name, shell=True) difftemp.delete = True difftemp.close() def display_changes_with_comments(oldprofile, newprofile): """Compare the new profile with the existing profile inclusive of all the comments""" if not os.path.exists(oldprofile): - raise AppArmorException(_("Can't find existing profile %s to compare changes.") %oldprofile) + raise AppArmorException(_("Can't find existing profile %s to compare changes.") % oldprofile) if aaui.UI_mode == 'yast': #To-Do pass @@ -2399,10 +2396,10 @@ def display_changes_with_comments(oldprofile, newprofile): difftemp = tempfile.NamedTemporaryFile('w') - subprocess.call('diff -u -p %s %s > %s' %(oldprofile, newtemp.name, difftemp.name), shell=True) + subprocess.call('diff -u -p %s %s > %s' % (oldprofile, newtemp.name, difftemp.name), shell=True) newtemp.close() - subprocess.call('less %s' %difftemp.name, shell=True) + subprocess.call('less %s' % difftemp.name, shell=True) difftemp.close() def set_process(pid, profile): @@ -2505,8 +2502,8 @@ def validate_profile_mode(mode, allow, nt_name=None): def is_skippable_file(path): """Returns True if filename matches something to be skipped""" if (re.search('(^|/)\.[^/]*$', path) or re.search('\.rpm(save|new)$', path) - or re.search('\.dpkg-(old|new)$', path) or re.search('\.swp$', path) - or path[-1] == '~' or path == 'README'): + or re.search('\.dpkg-(old|new)$', path) or re.search('\.swp$', path) + or path[-1] == '~' or path == 'README'): return True def is_skippable_dir(path): @@ -2525,7 +2522,7 @@ def check_profile_syntax(errors): def read_profiles(): try: os.listdir(profile_dir) - except : + except: fatal_error(_("Can't read AppArmor profiles in %s") % profile_dir) for file in os.listdir(profile_dir): @@ -2540,7 +2537,7 @@ def read_inactive_profiles(): return None try: os.listdir(profile_dir) - except : + except: fatal_error(_("Can't read AppArmor profiles in %s") % extra_profile_dir) for file in os.listdir(profile_dir): @@ -2556,7 +2553,7 @@ def read_profile(file, active_profile): with open_file_read(file) as f_in: data = f_in.readlines() except IOError: - debug_logger.debug("read_profile: can't read %s - skipping" %file) + debug_logger.debug("read_profile: can't read %s - skipping" % file) return None profile_data = parse_profile_data(data, file, 0) @@ -2617,13 +2614,13 @@ def parse_profile_data(data, file, do_include): if profile: #print(profile, hat) if profile != hat or not matches[3]: - raise AppArmorException(_('%s profile in %s contains syntax errors in line: %s.') % (profile, file, lineno+1)) + raise AppArmorException(_('%s profile in %s contains syntax errors in line: %s.') % (profile, file, lineno + 1)) # Keep track of the start of a profile if profile and profile == hat and matches[3]: # local profile hat = matches[3] in_contained_hat = True - profile_data[profile][hat]['profile'] = True + profile_data[profile][hat]['profile'] = True else: if matches[1]: profile = matches[1] @@ -2669,7 +2666,7 @@ def parse_profile_data(data, file, do_include): elif RE_PROFILE_END.search(line): # If profile ends and we're not in one if not profile: - raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected End of Profile reached in file: %s line: %s') % (file, lineno + 1)) if in_contained_hat: hat = profile @@ -2684,7 +2681,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_CAP.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected capability entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: @@ -2703,7 +2700,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_LINK.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected link entry found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected link entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: @@ -2731,7 +2728,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %s line: %s') % (file, lineno + 1)) cp = strip_quotes(matches[0]) profile_data[profile][hat]['changes_profile'][cp] = True @@ -2753,7 +2750,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_RLIMIT.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected rlimit entry found in file: %s line: %s') % (file, lineno + 1)) from_name = matches[0] to_name = matches[2] @@ -2764,7 +2761,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_BOOLEAN.search(line) if not profile: - raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %s line: %s') % (file, lineno + 1)) bool_var = matches[0] value = matches[1] @@ -2804,7 +2801,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_PATH_ENTRY.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected path entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: @@ -2828,10 +2825,10 @@ def parse_profile_data(data, file, do_include): try: re.compile(p_re) except: - raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno+1)) + raise AppArmorException(_('Syntax Error: Invalid Regex %s in file: %s line: %s') % (path, file, lineno + 1)) if not validate_profile_mode(mode, allow, nt_name): - raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno+1)) + raise AppArmorException(_('Invalid mode %s in file: %s line: %s') % (mode, file, lineno + 1)) tmpmode = set() if user: @@ -2855,7 +2852,6 @@ def parse_profile_data(data, file, do_include): if include_name.startswith('local/'): profile_data[profile][hat]['localinclude'][include_name] = True - if profile: profile_data[profile][hat]['include'][include_name] = True else: @@ -2870,7 +2866,7 @@ def parse_profile_data(data, file, do_include): continue if os.path.isfile(profile_dir + '/' + include_name + '/' + path): file_name = include_name + '/' + path - file_name = file_name.replace(profile_dir+'/', '') + file_name = file_name.replace(profile_dir + '/', '') if not include.get(file_name, False): load_include(file_name) else: @@ -2881,7 +2877,7 @@ def parse_profile_data(data, file, do_include): matches = RE_PROFILE_NETWORK.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected network entry found in file: %s line: %s') % (file, lineno + 1)) audit = False if matches[0]: @@ -2897,7 +2893,7 @@ def parse_profile_data(data, file, do_include): ##Simply ignore any type subrules if family has True (seperately for allow and deny) ##This will lead to those type specific rules being lost when written #if type(profile_data[profile][hat][allow]['netdomain']['rule'].get(fam, False)) == dict: - profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = 1 + profile_data[profile][hat][allow]['netdomain']['rule'][fam][typ] = 1 profile_data[profile][hat][allow]['netdomain']['audit'][fam][typ] = audit elif RE_NETWORK_FAMILY.search(network): fam = RE_NETWORK_FAMILY.search(network).groups()[0] @@ -2905,13 +2901,13 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat][allow]['netdomain']['audit'][fam] = audit else: profile_data[profile][hat][allow]['netdomain']['rule']['all'] = True - profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True + profile_data[profile][hat][allow]['netdomain']['audit']['all'] = audit # True elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %s line: %s') % (file, lineno + 1)) hat = matches[0] hat = strip_quotes(hat) @@ -2923,7 +2919,7 @@ def parse_profile_data(data, file, do_include): # An embedded hat syntax definition starts matches = RE_PROFILE_HAT_DEF.search(line).groups() if not profile: - raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unexpected hat definition found in file: %s line: %s') % (file, lineno + 1)) in_contained_hat = True hat = matches[0] @@ -2939,7 +2935,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['initial_comment'] = initial_comment initial_comment = '' if filelist[file]['profiles'][profile].get(hat, False): - raise AppArmorException(_('Error: Multiple definitions for hat %s in profile %s.') %(hat, profile)) + raise AppArmorException(_('Error: Multiple definitions for hat %s in profile %s.') % (hat, profile)) filelist[file]['profiles'][profile][hat] = True elif line[0] == '#': @@ -2959,7 +2955,7 @@ def parse_profile_data(data, file, do_include): initial_comment = ' '.join(line) + '\n' else: - raise AppArmorException(_('Syntax Error: Unknown line found in file: %s line: %s') % (file, lineno+1)) + raise AppArmorException(_('Syntax Error: Unknown line found in file: %s line: %s') % (file, lineno + 1)) # Below is not required I'd say if not do_include: @@ -3003,18 +2999,18 @@ def store_list_var(var, list_var, value, var_operation): var[list_var] = set(vlist) else: #print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var]) - raise AppArmorException(_('An existing variable redefined: %s') %list_var) + raise AppArmorException(_('An existing variable redefined: %s') % list_var) elif var_operation == '+=': if var.get(list_var, False): var[list_var] = set(var[list_var] + vlist) else: - raise AppArmorException(_('Values added to a non-existing variable: %s') %list_var) + raise AppArmorException(_('Values added to a non-existing variable: %s') % list_var) else: - raise AppArmorException(_('Unknown variable operation: %s') %var_operation) + raise AppArmorException(_('Unknown variable operation: %s') % var_operation) def strip_quotes(data): - if data[0]+data[-1] == '""': + if data[0] + data[-1] == '""': return data[1:-1] else: return data @@ -3037,7 +3033,7 @@ def write_header(prof_data, depth, name, embedded_hat, write_flags): data = [] name = quote_if_needed(name) - if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]' ,name)): + if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]', name)): name = 'profile %s' % name if write_flags and prof_data['flags']: @@ -3055,7 +3051,7 @@ def write_single(prof_data, depth, allow, name, prefix, tail): if ref.get(name, False): for key in sorted(ref[name].keys()): qkey = quote_if_needed(key) - data.append('%s%s%s%s%s' %(pre, allow, prefix, qkey, tail)) + data.append('%s%s%s%s%s' % (pre, allow, prefix, qkey, tail)) if ref[name].keys(): data.append('') @@ -3085,8 +3081,8 @@ def write_pair(prof_data, depth, allow, name, prefix, sep, tail, fn): if ref.get(name, False): for key in sorted(ref[name].keys()): - value = fn(ref[name][key])#eval('%s(%s)' % (fn, ref[name][key])) - data.append('%s%s%s%s%s%s' %(pre, allow, prefix, key, sep, value)) + value = fn(ref[name][key]) # eval('%s(%s)' % (fn, ref[name][key])) + data.append('%s%s%s%s%s%s' % (pre, allow, prefix, key, sep, value)) if ref[name].keys(): data.append('') @@ -3124,7 +3120,7 @@ def write_cap_rules(prof_data, depth, allow): if prof_data[allow]['capability'][cap].get('audit', False): audit = 'audit ' if prof_data[allow]['capability'][cap].get('set', False): - data.append('%s%s%scapability %s,' %(pre, audit, allowstr, cap)) + data.append('%s%s%scapability %s,' % (pre, audit, allowstr, cap)) data.append('') return data @@ -3144,10 +3140,10 @@ def write_net_rules(prof_data, depth, allow): if prof_data[allow]['netdomain'].get('rule', False) == 'all': if prof_data[allow]['netdomain']['audit'].get('all', False): audit = 'audit ' - data.append('%s%snetwork,' %(pre, audit)) + data.append('%s%snetwork,' % (pre, audit)) else: for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()): - if prof_data[allow]['netdomain']['rule'][fam] == True: + if prof_data[allow]['netdomain']['rule'][fam] is True: if prof_data[allow]['netdomain']['audit'][fam]: audit = 'audit' data.append('%s%s%snetwork %s' % (pre, audit, allowstr, fam)) @@ -3155,7 +3151,7 @@ def write_net_rules(prof_data, depth, allow): for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()): if prof_data[allow]['netdomain']['audit'][fam].get(typ, False): audit = 'audit' - data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr,fam, typ)) + data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr, fam, typ)) if prof_data[allow].get('netdomain', False): data.append('') @@ -3182,7 +3178,7 @@ def write_link_rules(prof_data, depth, allow): audit = 'audit ' path = quote_if_needed(path) to_name = quote_if_needed(to_name) - data.append('%s%s%slink %s%s -> %s,' %(pre, audit, allowstr, subset, path, to_name)) + data.append('%s%s%slink %s%s -> %s,' % (pre, audit, allowstr, subset, path, to_name)) data.append('') return data @@ -3234,13 +3230,13 @@ def write_path_rules(prof_data, depth, allow): if tmpmode & tmpaudit: modestr = mode_to_str(tmpmode & tmpaudit) path = quote_if_needed(path) - data.append('%saudit %s%s%s %s%s,' %(pre, allowstr, ownerstr, path, modestr, tail)) + data.append('%saudit %s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail)) tmpmode = tmpmode - tmpaudit if tmpmode: modestr = mode_to_str(tmpmode) path = quote_if_needed(path) - data.append('%s%s%s%s %s%s,' %(pre, allowstr, ownerstr, path, modestr, tail)) + data.append('%s%s%s%s %s%s,' % (pre, allowstr, ownerstr, path, modestr, tail)) data.append('') return data @@ -3276,13 +3272,13 @@ def write_piece(profile_data, depth, name, nhat, write_flags): name = nhat inhat = True data += write_header(profile_data[name], depth, wname, False, write_flags) - data += write_rules(profile_data[name], depth+1) + data += write_rules(profile_data[name], depth + 1) - pre2 = ' ' * (depth+1) + pre2 = ' ' * (depth + 1) # External hat declarations for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if profile_data[hat].get('declared', False): - data.append('%s^%s,' %(pre2, hat)) + data.append('%s^%s,' % (pre2, hat)) if not inhat: # Embedded hats @@ -3290,21 +3286,21 @@ def write_piece(profile_data, depth, name, nhat, write_flags): if not profile_data[hat]['external'] and not profile_data[hat]['declared']: data.append('') if profile_data[hat]['profile']: - data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, write_flags))) + data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, write_flags))) else: - data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, write_flags))) + data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, write_flags))) - data += list(map(str, write_rules(profile_data[hat], depth+2))) + data += list(map(str, write_rules(profile_data[hat], depth + 2))) - data.append('%s}' %pre2) + data.append('%s}' % pre2) - data.append('%s}' %pre) + data.append('%s}' % pre) # External hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if name == nhat and profile_data[hat].get('external', False): data.append('') - data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, nhat, write_flags))) + data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, nhat, write_flags))) data.append(' }') return data @@ -3313,21 +3309,23 @@ def serialize_profile(profile_data, name, options): string = '' include_metadata = False include_flags = True - data= [] + data = [] - if options:# and type(options) == dict: + if options: # and type(options) == dict: if options.get('METADATA', False): include_metadata = True if options.get('NO_FLAGS', False): include_flags = False if include_metadata: - string = '# Last Modified: %s\n' %time.asctime() + string = '# Last Modified: %s\n' % time.asctime() - if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] - and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): + if (profile_data[name].get('repo', False) and + profile_data[name]['repo']['url'] and + profile_data[name]['repo']['user'] and + profile_data[name]['repo']['id']): repo = profile_data[name]['repo'] - string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id']) + string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id']) elif profile_data[name]['repo']['neversubmit']: string += '# REPOSITORY: NEVERSUBMIT\n' @@ -3359,7 +3357,7 @@ def serialize_profile(profile_data, name, options): string += '\n'.join(data) - return string+'\n' + return string + '\n' def serialize_profile_from_old_profile(profile_data, name, options): data = [] @@ -3371,28 +3369,29 @@ def serialize_profile_from_old_profile(profile_data, name, options): write_filelist = deepcopy(filelist[prof_filename]) write_prof_data = deepcopy(profile_data) - if options:# and type(options) == dict: + if options: # and type(options) == dict: if options.get('METADATA', False): include_metadata = True if options.get('NO_FLAGS', False): include_flags = False if include_metadata: - string = '# Last Modified: %s\n' %time.asctime() + string = '# Last Modified: %s\n' % time.asctime() - if (profile_data[name].get('repo', False) and profile_data[name]['repo']['url'] - and profile_data[name]['repo']['user'] and profile_data[name]['repo']['id']): + if (profile_data[name].get('repo', False) and + profile_data[name]['repo']['url'] and + profile_data[name]['repo']['user'] and + profile_data[name]['repo']['id']): repo = profile_data[name]['repo'] - string += '# REPOSITORY: %s %s %s\n' %(repo['url'], repo['user'], repo['id']) + string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id']) elif profile_data[name]['repo']['neversubmit']: string += '# REPOSITORY: NEVERSUBMIT\n' - if not os.path.isfile(prof_filename): raise AppArmorException(_("Can't find existing profile to modify")) - + profiles_list = filelist[prof_filename].keys() - + with open_file_read(prof_filename) as f_in: profile = None hat = None @@ -3407,8 +3406,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): 'change_profile': write_change_profile, } prof_correct = True - segments = { - 'alias': False, + segments = {'alias': False, 'lvar': False, 'include': False, 'rlimit': False, @@ -3418,7 +3416,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): 'path': False, 'change_profile': False, 'include_local_started': False, - } + } #data.append('reading prof') for line in f_in: correct = True @@ -3454,7 +3452,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not write_prof_data[hat]['name'] == profile: correct = False - if not write_filelist['profiles'][profile][hat] == True: + if not write_filelist['profiles'][profile][hat] is True: correct = False if not write_prof_data[hat]['flags'] == flags: @@ -3470,7 +3468,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: if write_prof_data[hat]['name'] == profile: depth = len(line) - len(line.lstrip()) - data += write_header(write_prof_data[name], int(depth/2), name, False, include_flags) + data += write_header(write_prof_data[name], int(depth / 2), name, False, include_flags) elif RE_PROFILE_END.search(line): # DUMP REMAINDER OF PROFILE @@ -3479,11 +3477,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) - + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) data += write_alias(write_prof_data[name], depth) data += write_list_vars(write_prof_data[name], depth) @@ -3502,25 +3501,25 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not in_contained_hat: # Embedded hats - depth = int((len(line) - len(line.lstrip()))/2) - pre2 = ' ' * (depth+1) + depth = int((len(line) - len(line.lstrip())) / 2) + pre2 = ' ' * (depth + 1) for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if not profile_data[hat]['external'] and not profile_data[hat]['declared']: data.append('') if profile_data[hat]['profile']: - data += list(map(str, write_header(profile_data[hat], depth+1, hat, True, include_flags))) + data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, include_flags))) else: - data += list(map(str, write_header(profile_data[hat], depth+1, '^'+hat, True, include_flags))) + data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, include_flags))) - data += list(map(str, write_rules(profile_data[hat], depth+2))) + data += list(map(str, write_rules(profile_data[hat], depth + 2))) - data.append('%s}' %pre2) + data.append('%s}' % pre2) # External hats for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))): if profile_data[hat].get('external', False): data.append('') - data += list(map(lambda x: ' %s' %x, write_piece(profile_data, depth-1, name, name, include_flags))) + data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, name, include_flags))) data.append(' }') if in_contained_hat: @@ -3530,7 +3529,6 @@ def serialize_profile_from_old_profile(profile_data, name, options): else: profile = None - elif RE_PROFILE_CAP.search(line): matches = RE_PROFILE_CAP.search(line).groups() audit = False @@ -3552,10 +3550,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['capability'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['capability'] = True write_prof_data[hat][allow]['capability'].pop(capability) data.append(line) @@ -3591,10 +3591,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['link'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['link'] = True write_prof_data[hat][allow]['link'].pop(link) data.append(line) @@ -3606,17 +3608,19 @@ def serialize_profile_from_old_profile(profile_data, name, options): matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() cp = strip_quotes(matches[0]) - if not write_prof_data[hat]['changes_profile'][cp] == True: + if not write_prof_data[hat]['changes_profile'][cp] is True: correct = False if correct: if not segments['change_profile'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['change_profile'] = True write_prof_data[hat]['change_profile'].pop(cp) data.append(line) @@ -3641,10 +3645,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['alias'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['alias'] = True if profile: write_prof_data[hat]['alias'].pop(from_name) @@ -3668,10 +3674,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['rlimit'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['rlimit'] = True write_prof_data[hat]['rlimit'].pop(from_name) data.append(line) @@ -3691,10 +3699,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['lvar'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['lvar'] = True write_prof_data[hat]['lvar'].pop(bool_var) data.append(line) @@ -3720,10 +3730,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['lvar'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['lvar'] = True if profile: write_prof_data[hat]['lvar'].pop(list_var) @@ -3755,7 +3767,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): tmpmode = set() if user: - tmpmode = str_to_mode('%s::' %mode) + tmpmode = str_to_mode('%s::' % mode) else: tmpmode = str_to_mode(mode) @@ -3772,10 +3784,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['path'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['path'] = True write_prof_data[hat][allow]['path'].pop(path) data.append(line) @@ -3790,10 +3804,12 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['include'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['include'] = True write_prof_data[hat]['include'].pop(include_name) data.append(line) @@ -3841,16 +3857,18 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not segments['netdomain'] and True in segments.values(): for segs in list(filter(lambda x: segments[x], segments.keys())): depth = len(line) - len(line.lstrip()) - data += write_methods[segs](write_prof_data[name], int(depth/2)) + data += write_methods[segs](write_prof_data[name], int(depth / 2)) segments[segs] = False - if write_prof_data[name]['allow'].get(segs, False): write_prof_data[name]['allow'].pop(segs) - if write_prof_data[name]['deny'].get(segs, False): write_prof_data[name]['deny'].pop(segs) + if write_prof_data[name]['allow'].get(segs, False): + write_prof_data[name]['allow'].pop(segs) + if write_prof_data[name]['deny'].get(segs, False): + write_prof_data[name]['deny'].pop(segs) segments['netdomain'] = True elif RE_PROFILE_CHANGE_HAT.search(line): matches = RE_PROFILE_CHANGE_HAT.search(line).groups() hat = matches[0] - hat = strip_quotes(hat) + hat = strip_quotes(hat) if not write_prof_data[hat]['declared']: correct = False if correct: @@ -3866,7 +3884,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): flags = matches[3] if not write_prof_data[hat]['flags'] == flags: correct = False - if not write_prof_data[hat]['declared'] == False: + if not write_prof_data[hat]['declared'] is False: correct = False if not write_filelist['profile'][profile][hat]: correct = False @@ -3891,10 +3909,10 @@ def serialize_profile_from_old_profile(profile_data, name, options): string += '\n'.join(data) - return string+'\n' + return string + '\n' def write_profile_ui_feedback(profile): - aaui.UI_Info(_('Writing updated profile for %s.') %profile) + aaui.UI_Info(_('Writing updated profile for %s.') % profile) write_profile(profile) def write_profile(profile): @@ -3904,7 +3922,7 @@ def write_profile(profile): else: prof_filename = get_profile_filename(profile) - newprof = tempfile.NamedTemporaryFile('w', suffix='~' ,delete=False, dir=profile_dir) + newprof = tempfile.NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir) if os.path.exists(prof_filename): shutil.copymode(prof_filename, newprof.name) else: @@ -3925,7 +3943,7 @@ def write_profile(profile): original_aa[profile] = deepcopy(aa[profile]) def matchliteral(aa_regexp, literal): - p_regexp = '^'+convert_regexp(aa_regexp)+'$' + p_regexp = '^' + convert_regexp(aa_regexp) + '$' match = False try: match = re.search(p_regexp, literal) @@ -3994,11 +4012,11 @@ def netrules_access_check(netrules, family, sock_type): net_family_sock = False if netrules['rule'].get('all', False): all_net = True - if netrules['rule'].get(family, False) == True: + if netrules['rule'].get(family, False) is True: all_net_family = True if (netrules['rule'].get(family, False) and - type(netrules['rule'][family]) == dict and - netrules['rule'][family][sock_type]): + type(netrules['rule'][family]) == dict and + netrules['rule'][family][sock_type]): net_family_sock = True if all_net or all_net_family or net_family_sock: @@ -4012,7 +4030,7 @@ def reload_base(bin_path): prof_filename = get_profile_filename(bin_path) - subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" %(prof_filename, parser ,profile_dir), shell=True) + subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" % (prof_filename, parser, profile_dir), shell=True) def reload(bin_path): bin_path = find_executable(bin_path) @@ -4028,7 +4046,7 @@ def get_include_data(filename): with open_file_read(filename) as f_in: data = f_in.readlines() else: - raise AppArmorException(_('File Not Found: %s') %filename) + raise AppArmorException(_('File Not Found: %s') % filename) return data def load_include(incname): @@ -4037,7 +4055,7 @@ def load_include(incname): return 0 while load_includeslist: incfile = load_includeslist.pop(0) - if os.path.isfile(profile_dir+'/'+incfile): + if os.path.isfile(profile_dir + '/' + incfile): data = get_include_data(incfile) incdata = parse_profile_data(data, incfile, True) #print(incdata) @@ -4048,8 +4066,8 @@ def load_include(incname): incdata[incname] = hasher() attach_profile_data(include, incdata) #If the include is a directory means include all subfiles - elif os.path.isdir(profile_dir+'/'+incfile): - load_includeslist += list(map(lambda x: incfile+'/'+x, os.listdir(profile_dir+'/'+incfile))) + elif os.path.isdir(profile_dir + '/' + incfile): + load_includeslist += list(map(lambda x: incfile + '/' + x, os.listdir(profile_dir + '/' + incfile))) return 0 @@ -4077,7 +4095,7 @@ def match_include_to_path(incname, allow, path): while includelist: incfile = str(includelist.pop(0)) ret = load_include(incfile) - if not include.get(incfile,{}): + if not include.get(incfile, {}): continue cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path) #print(incfile, cm, am, m) @@ -4119,7 +4137,7 @@ def suggest_incs_for_path(incname, path, allow): includelist = [incname] while includelist: inc = includelist.pop(0) - cm, am , m = rematchfrag(include[inc][inc], 'allow', path) + cm, am, m = rematchfrag(include[inc][inc], 'allow', path) if cm: combinedmode |= cm combinedaudit |= am @@ -4137,12 +4155,12 @@ def suggest_incs_for_path(incname, path, allow): def check_qualifiers(program): if cfg['qualifiers'].get(program, False): if cfg['qualifiers'][program] != 'p': - fatal_error(_("%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") %program) + fatal_error(_("%s is currently marked as a program that should not have its own\nprofile. Usually, programs are marked this way if creating a profile for \nthem is likely to break the rest of the system. If you know what you\'re\ndoing and are certain you want to create a profile for this program, edit\nthe corresponding entry in the [qualifiers] section in /etc/apparmor/logprof.conf.") % program) return False def get_subdirectories(current_dir): """Returns a list of all directories directly inside given directory""" - if sys.version_info < (3,0): + if sys.version_info < (3, 0): return os.walk(current_dir).next()[1] else: return os.walk(current_dir).__next__()[1] @@ -4160,7 +4178,7 @@ def loadincludes(): continue else: fi = dirpath + '/' + fi - fi = fi.replace(profile_dir+'/', '', 1) + fi = fi.replace(profile_dir + '/', '', 1) load_include(fi) def glob_common(path): @@ -4186,7 +4204,7 @@ def combine_name(name1, name2): if name1 == name2: return name1 else: - return '%s^%s' %(name1, name2) + return '%s^%s' % (name1, name2) def split_name(name): names = name.split('^') @@ -4195,7 +4213,7 @@ def split_name(name): else: return names[0], names[1] def commonprefix(new, old): - match=re.search(r'^([^\0]*)[^\0]*(\0\1[^\0]*)*$', '\0'.join([new, old])) + match = re.search(r'^([^\0]*)[^\0]*(\0\1[^\0]*)*$', '\0'.join([new, old])) if match: return match.groups()[0] return match @@ -4242,7 +4260,7 @@ if cfg['settings'].get('default_owner_prompt', False): profile_dir = conf.find_first_dir(cfg['settings']['profiledir']) or '/etc/apparmor.d' if not os.path.isdir(profile_dir): - raise AppArmorException('Can\'t find AppArmor profiles' ) + raise AppArmorException('Can\'t find AppArmor profiles') extra_profile_dir = conf.find_first_dir(cfg['settings']['inactive_profiledir']) or '/etc/apparmor/profiles/extras/' diff --git a/apparmor/aamode.py b/apparmor/aamode.py index 76fc011af..d0a5cb657 100644 --- a/apparmor/aamode.py +++ b/apparmor/aamode.py @@ -16,7 +16,7 @@ import re def AA_OTHER(mode): other = set() for i in mode: - other.add('::%s'%i) + other.add('::%s' % i) return other def AA_OTHER_REMOVE(mode): @@ -57,14 +57,14 @@ MODE_HASH = {'x': AA_MAY_EXEC, 'X': AA_MAY_EXEC, 'm': AA_EXEC_MMAP, 'M': AA_EXEC_MMAP, 'i': AA_EXEC_INHERIT, 'I': AA_EXEC_INHERIT, 'u': AA_EXEC_UNCONFINED | AA_EXEC_UNSAFE, # Unconfined + Unsafe - 'U': AA_EXEC_UNCONFINED, - 'p': AA_EXEC_PROFILE | AA_EXEC_UNSAFE, # Profile + unsafe - 'P': AA_EXEC_PROFILE, - 'c': AA_EXEC_CHILD | AA_EXEC_UNSAFE, # Child + Unsafe - 'C': AA_EXEC_CHILD, - 'n': AA_EXEC_NT | AA_EXEC_UNSAFE, - 'N': AA_EXEC_NT - } + 'U': AA_EXEC_UNCONFINED, + 'p': AA_EXEC_PROFILE | AA_EXEC_UNSAFE, # Profile + unsafe + 'P': AA_EXEC_PROFILE, + 'c': AA_EXEC_CHILD | AA_EXEC_UNSAFE, # Child + Unsafe + 'C': AA_EXEC_CHILD, + 'n': AA_EXEC_NT | AA_EXEC_UNSAFE, + 'N': AA_EXEC_NT + } LOG_MODE_RE = re.compile('(r|w|l|m|k|a|x|ix|ux|px|cx|nx|pix|cix|Ix|Ux|Px|PUx|Cx|Nx|Pix|Cix)') MODE_MAP_RE = re.compile('(r|w|l|m|k|a|x|i|u|p|c|n|I|U|P|C|N)') diff --git a/apparmor/common.py b/apparmor/common.py index 66f556938..e4e932562 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -111,7 +111,7 @@ def valid_path(path): debug("%s (relative)" % (m)) return False - if '"' in path: # We double quote elsewhere + if '"' in path: # We double quote elsewhere return False try: @@ -176,8 +176,8 @@ def convert_regexp(regexp): match = regex_paren.search(new_reg).groups() prev = match[0] after = match[2] - p1 = match[1].replace(',','|') - new_reg = prev+'('+p1+')'+after + p1 = match[1].replace(',', '|') + new_reg = prev + '(' + p1 + ')' + after new_reg = new_reg.replace('?', '[^/\000]') @@ -192,7 +192,7 @@ def convert_regexp(regexp): if regexp[0] != '^': new_reg = '^' + new_reg if regexp[-1] != '$': - new_reg = new_reg + '$' + new_reg = new_reg + '$' return new_reg def user_perm(prof_dir): @@ -215,7 +215,7 @@ class DebugLogger(object): self.debugging = False if self.debugging not in range(0, 4): sys.stdout.write('Environment Variable: LOGPROF_DEBUG contains invalid value: %s' - %os.getenv('LOGPROF_DEBUG')) + % os.getenv('LOGPROF_DEBUG')) if self.debugging == 0: # debugging disabled, don't need to setup logging return if self.debugging == 1: @@ -224,21 +224,20 @@ class DebugLogger(object): self.debug_level = logging.INFO elif self.debugging == 3: self.debug_level = logging.DEBUG - + try: logging.basicConfig(filename=self.logfile, level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') except OSError: # Unable to open the default logfile, so create a temporary logfile and tell use about it import tempfile - templog = tempfile.NamedTemporaryFile('w', prefix='apparmor', suffix='.log' ,delete=False) - sys.stdout.write("\nCould not open: %s\nLogging to: %s\n"%(self.logfile, templog.name)) - + templog = tempfile.NamedTemporaryFile('w', prefix='apparmor', suffix='.log', delete=False) + sys.stdout.write("\nCould not open: %s\nLogging to: %s\n" % (self.logfile, templog.name)) + logging.basicConfig(filename=templog.name, level=self.debug_level, format='%(asctime)s - %(name)s - %(message)s\n') - - self.logger = logging.getLogger(module_name) + self.logger = logging.getLogger(module_name) def error(self, message): if self.debugging: diff --git a/apparmor/config.py b/apparmor/config.py index 984d01ed0..5e613bc97 100644 --- a/apparmor/config.py +++ b/apparmor/config.py @@ -20,6 +20,7 @@ import sys import tempfile if sys.version_info < (3, 0): import ConfigParser as configparser + # Class to provide the object[section][option] behavior in Python2 class configparser_py2(configparser.ConfigParser): def __getitem__(self, section): @@ -34,7 +35,7 @@ else: import configparser -from apparmor.common import AppArmorException, open_file_read#, warn, msg, +from apparmor.common import AppArmorException, open_file_read # , warn, msg, # CFG = None @@ -110,7 +111,6 @@ class Config(object): # Replace the target config file with the temporary file os.rename(config_file.name, filepath) - def find_first_file(self, file_list): """Returns name of first matching file None otherwise""" filename = None @@ -164,7 +164,7 @@ class Config(object): option, value = result[0].split('=') if '#' in line: comment = value.split('#', 1)[1] - comment = '#'+comment + comment = '#' + comment else: comment = '' # If option exists in the new config file @@ -172,7 +172,7 @@ class Config(object): # If value is different if value != config[''][option]: value_new = config[''][option] - if value_new != None: + if value_new is not None: # Update value if '"' in line: value_new = '"' + value_new + '"' @@ -190,7 +190,7 @@ class Config(object): # If option exists in the new config file if option in options: # If its no longer option type - if config[''][option] != None: + if config[''][option] is not None: value = config[''][option] line = option + '=' + value + '\n' f_out.write(line) @@ -204,7 +204,7 @@ class Config(object): for option in options: value = config[''][option] # option type entry - if value == None: + if value is None: line = option + '\n' # option=value type entry else: @@ -273,7 +273,7 @@ class Config(object): if section in sections: sections.remove(section) for section in sections: - f_out.write('\n['+section+']\n') + f_out.write('\n[%s]\n' % section) options = config.options(section) for option in options: line = ' ' + option + ' = ' + config[section][option] + '\n' diff --git a/apparmor/logparser.py b/apparmor/logparser.py index d0a08a632..7b8b14379 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -36,9 +36,8 @@ class ReadLog: PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix') PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x') # Used by netdomain to identify the operation types - OPERATION_TYPES = { - # New socket names - 'create': 'net', + # New socket names + OPERATION_TYPES = {'create': 'net', 'post_create': 'net', 'bind': 'net', 'connect': 'net', @@ -52,6 +51,7 @@ class ReadLog: 'setsockopt': 'net', 'sock_shutdown': 'net' } + def __init__(self, pid, filename, existing_profiles, profile_dir, log): self.filename = filename self.profile_dir = profile_dir @@ -101,7 +101,7 @@ class ReadLog: msg = msg.strip() self.debug_logger.info('parse_event: %s' % msg) #print(repr(msg)) - if sys.version_info < (3,0): + if sys.version_info < (3, 0): # parse_record fails with u'foo' style strings hence typecasting to string msg = str(msg) event = LibAppArmor.parse_record(msg) @@ -157,8 +157,7 @@ class ReadLog: if ev['aamode']: # Convert aamode values to their counter-parts - mode_convertor = { - 0: 'UNKNOWN', + mode_convertor = {0: 'UNKNOWN', 1: 'ERROR', 2: 'AUDITING', 3: 'PERMITTING', @@ -259,30 +258,30 @@ class ReadLog: if e['operation'] == 'exec': if e.get('info', False) and e['info'] == 'mandatory profile missing': self.add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']]) + [profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']]) elif e.get('name2', False) and '\\null-/' in e['name2']: self.add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e.get('name', False): self.add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) else: self.debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile']) elif 'file_' in e['operation']: self.add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src', 'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']: #print(e['operation'], e['name']) self.add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] == 'capable': self.add_to_tree(e['pid'], e['parent'], 'capability', - [profile, hat, prog, aamode, e['name'], '']) + [profile, hat, prog, aamode, e['name'], '']) elif e['operation'] == 'setattr' or 'xattr' in e['operation']: self.add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif 'inode_' in e['operation']: is_domain_change = False if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING': @@ -295,17 +294,17 @@ class ReadLog: if is_domain_change: self.add_to_tree(e['pid'], e['parent'], 'exec', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']]) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], e['name2']]) else: self.add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] == 'sysctl': self.add_to_tree(e['pid'], e['parent'], 'path', - [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) + [profile, hat, prog, aamode, e['denied_mask'], e['name'], '']) elif e['operation'] == 'clone': - parent , child = e['pid'], e['task'] + parent, child = e['pid'], e['task'] if not parent: parent = 'null-complain-profile' if not hat: @@ -326,10 +325,10 @@ class ReadLog: elif self.op_type(e['operation']) == 'net': self.add_to_tree(e['pid'], e['parent'], 'netdomain', - [profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']]) + [profile, hat, prog, aamode, e['family'], e['sock_type'], e['protocol']]) elif e['operation'] == 'change_hat': self.add_to_tree(e['pid'], e['parent'], 'unknown_hat', - [profile, hat, aamode, hat]) + [profile, hat, aamode, hat]) else: self.debug_logger.debug('UNHANDLED: %s' % e) @@ -356,7 +355,7 @@ class ReadLog: if self.logmark in line: seenmark = True - self.debug_logger.debug('read_log: seenmark = %s' %seenmark) + self.debug_logger.debug('read_log: seenmark = %s' % seenmark) if not seenmark: continue @@ -387,7 +386,6 @@ class ReadLog: return True return False - def get_profile_filename(self, profile): """Returns the full profile name""" if profile.startswith('/'): diff --git a/apparmor/severity.py b/apparmor/severity.py index c62055ed8..9a0d3be9d 100644 --- a/apparmor/severity.py +++ b/apparmor/severity.py @@ -14,7 +14,7 @@ from __future__ import with_statement import os import re -from apparmor.common import AppArmorException, open_file_read, warn, convert_regexp #, msg, error, debug +from apparmor.common import AppArmorException, open_file_read, warn, convert_regexp # , msg, error, debug class Severity(object): def __init__(self, dbname=None, default_rank=10): @@ -31,10 +31,10 @@ class Severity(object): if not dbname: return None - with open_file_read(dbname) as database:#open(dbname, 'r') + with open_file_read(dbname) as database: # open(dbname, 'r') for lineno, line in enumerate(database, start=1): - line = line.strip() # or only rstrip and lstrip? - if line == '' or line.startswith('#') : + line = line.strip() # or only rstrip and lstrip? + if line == '' or line.startswith('#'): continue if line.startswith('/'): try: @@ -43,7 +43,7 @@ class Severity(object): except ValueError: raise AppArmorException("Insufficient values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) else: - if read not in range(0, 11) or write not in range(0,11) or execute not in range(0,11): + if read not in range(0, 11) or write not in range(0, 11) or execute not in range(0, 11): raise AppArmorException("Inappropriate values for permissions in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) path = path.lstrip('/') if '*' not in path: @@ -67,7 +67,7 @@ class Severity(object): except ValueError as e: error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) #error(error_message) - raise AppArmorException(error_message) # from None + raise AppArmorException(error_message) # from None else: if severity not in range(0, 11): raise AppArmorException("Inappropriate severity value present in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) @@ -83,7 +83,6 @@ class Severity(object): warn("unknown capability: %s" % resource) return self.severity['DEFAULT_RANK'] - def check_subtree(self, tree, mode, sev, segments): """Returns the max severity from the regex tree""" if len(segments) == 0: @@ -91,21 +90,21 @@ class Severity(object): else: first = segments[0] rest = segments[1:] - path = '/'.join([first]+rest) + path = '/'.join([first] + rest) # Check if we have a matching directory tree to descend into if tree.get(first, False): sev = self.check_subtree(tree[first], mode, sev, rest) # If severity still not found, match against globs - if sev == None: + if sev is None: # Match against all globs at this directory level for chunk in tree.keys(): if '*' in chunk: # Match rest of the path - if re.search("^"+chunk, path): + if re.search("^" + chunk, path): # Find max rank if "AA_RANK" in tree[chunk].keys(): for m in mode: - if sev == None or tree[chunk]["AA_RANK"].get(m, -1) > sev: + if sev is None or tree[chunk]["AA_RANK"].get(m, -1) > sev: sev = tree[chunk]["AA_RANK"].get(m, None) return sev @@ -118,12 +117,12 @@ class Severity(object): if resource in self.severity['FILES'].keys(): # Find max value among the given modes for m in mode: - if sev == None or self.severity['FILES'][resource].get(m, -1) > sev: + if sev is None or self.severity['FILES'][resource].get(m, -1) > sev: sev = self.severity['FILES'][resource].get(m, None) else: # Search regex tree for matching glob sev = self.check_subtree(self.severity['REGEXPS'], mode, sev, pieces) - if sev == None: + if sev is None: # Return default rank if severity cannot be found return self.severity['DEFAULT_RANK'] else: @@ -146,13 +145,13 @@ class Severity(object): rank = None if '@' in resource: variable = regex_variable.search(resource).groups()[0] - variable = '@{'+variable+'}' + variable = '@{%s}' % variable #variables = regex_variable.findall(resource) for replacement in self.severity['VARIABLES'][variable]: resource_replaced = self.variable_replace(variable, replacement, resource) rank_new = self.handle_variable_rank(resource_replaced, mode) #rank_new = self.handle_variable_rank(resource.replace('@{'+variable+'}', replacement), mode) - if rank == None or rank_new > rank: + if rank is None or rank_new > rank: rank = rank_new return rank else: @@ -163,13 +162,13 @@ class Severity(object): leading = False trailing = False # Check for leading or trailing / that may need to be collapsed - if resource.find("/"+variable) != -1 and resource.find("//"+variable) == -1: # find that a single / exists before variable or not + if resource.find("/" + variable) != -1 and resource.find("//" + variable) == -1: # find that a single / exists before variable or not leading = True - if resource.find(variable+"/") != -1 and resource.find(variable+"//") == -1: + if resource.find(variable + "/") != -1 and resource.find(variable + "//") == -1: trailing = True - if replacement[0] == '/' and replacement[:2] != '//' and leading: # finds if the replacement has leading / or not + if replacement[0] == '/' and replacement[:2] != '//' and leading: # finds if the replacement has leading / or not replacement = replacement[1:] - if replacement[-1] == '/' and replacement[-2:] !='//' and trailing: + if replacement[-1] == '/' and replacement[-2:] != '//' and trailing: replacement = replacement[:-1] return resource.replace(variable, replacement) diff --git a/apparmor/tools.py b/apparmor/tools.py index 347b9087f..85c63e5b0 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -29,12 +29,12 @@ class aa_tools: self.profiling = args.program self.check_profile_dir() self.silent = None - + if tool_name in ['audit', 'complain']: self.remove = args.remove elif tool_name == 'disable': self.revert = args.revert - self.disabledir = apparmor.profile_dir+'/disable' + self.disabledir = apparmor.profile_dir + '/disable' self.check_disable_dir() elif tool_name == 'autodep': self.force = args.force @@ -46,14 +46,14 @@ class aa_tools: if self.profiledir: apparmor.profile_dir = apparmor.get_full_path(self.profiledir) if not os.path.isdir(apparmor.profile_dir): - raise apparmor.AppArmorException("%s is not a directory." %self.profiledir) + raise apparmor.AppArmorException("%s is not a directory." % self.profiledir) if not user_perm(apparmor.profile_dir): - raise apparmor.AppArmorException("Cannot write to profile directory: %s" %(apparmor.profile_dir)) + raise apparmor.AppArmorException("Cannot write to profile directory: %s" % (apparmor.profile_dir)) def check_disable_dir(self): if not os.path.isdir(self.disabledir): - raise apparmor.AppArmorException("Can't find AppArmor disable directory %s" %self.disabledir) + raise apparmor.AppArmorException("Can't find AppArmor disable directory %s" % self.disabledir) def act(self): for p in self.profiling: @@ -77,12 +77,12 @@ class aa_tools: if program and not program.startswith('/'): program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + apparmor.UI_Info(_("%s does not exist, please double-check the path.") % p) sys.exit(1) if self.name == 'autodep' and program and os.path.exists(program): self.use_autodep(program) - + elif program and apparmor.profile_exists(program): if self.name == 'cleanprof': self.clean_profile(program, p) @@ -91,21 +91,21 @@ class aa_tools: filename = apparmor.get_profile_filename(program) if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - apparmor.UI_Info(_('Profile for %s not found, skipping')%p) + apparmor.UI_Info(_('Profile for %s not found, skipping') % p) elif self.name == 'disable': if not self.revert: - apparmor.UI_Info(_('Disabling %s.')%program) + apparmor.UI_Info(_('Disabling %s.') % program) self.disable_profile(filename) else: - apparmor.UI_Info(_('Enabling %s.')%program) + apparmor.UI_Info(_('Enabling %s.') % program) self.enable_profile(filename) elif self.name == 'audit': if not self.remove: - apparmor.UI_Info(_('Setting %s to audit mode.')%program) + apparmor.UI_Info(_('Setting %s to audit mode.') % program) else: - apparmor.UI_Info(_('Removing audit mode from %s.')%program) + apparmor.UI_Info(_('Removing audit mode from %s.') % program) apparmor.change_profile_flags(filename, program, 'audit', not self.remove) elif self.name == 'complain': @@ -116,9 +116,9 @@ class aa_tools: #apparmor.set_profile_flags(filename, self.name) else: # One simply does not walk in here! - raise apparmor.AppArmorException('Unknown tool: %s'%self.name) + raise apparmor.AppArmorException('Unknown tool: %s' % self.name) - cmd_info = apparmor.cmd([apparmor.parser, filename, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) + cmd_info = apparmor.cmd([apparmor.parser, filename, '-I%s' % apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) #cmd_info = apparmor.cmd(['cat', filename, '|', apparmor.parser, '-I%s'%apparmor.profile_dir, '-R 2>&1', '1>/dev/null']) if cmd_info[0] != 0: @@ -126,9 +126,9 @@ class aa_tools: else: if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.")%(p, p)) + apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % (p, p)) else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.")%p) + apparmor.UI_Info(_("%s does not exist, please double-check the path.") % p) sys.exit(1) def clean_profile(self, program, p): @@ -145,12 +145,12 @@ class aa_tools: q = apparmor.hasher() q['title'] = 'Changed Local Profiles' q['headers'] = [] - q['explanation'] = _('The local profile for %s in file %s was changed. Would you like to save it?') %(program, filename) + q['explanation'] = _('The local profile for %s in file %s was changed. Would you like to save it?') % (program, filename) q['functions'] = ['CMD_SAVE_CHANGES', 'CMD_VIEW_CHANGES', 'CMD_ABORT'] q['default'] = 'CMD_VIEW_CHANGES' q['options'] = [] q['selected'] = 0 - p =None + p = None ans = '' arg = None while ans != 'CMD_SAVE_CHANGES': @@ -166,13 +166,13 @@ class aa_tools: apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) else: - raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.')%p) + raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.') % p) def use_autodep(self, program): apparmor.check_qualifiers(program) if os.path.exists(apparmor.get_profile_filename(program) and not self.force): - apparmor.UI_Info('Profile for %s already exists - skipping.'%program) + apparmor.UI_Info('Profile for %s already exists - skipping.' % program) else: apparmor.autodep(program) if self.aa_mountpoint: diff --git a/apparmor/ui.py b/apparmor/ui.py index fd8405e5c..3a6b81bf7 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -52,14 +52,13 @@ def UI_Important(text): if UI_mode == 'text': sys.stdout.write('\n' + text + '\n') else: - SendDataToYast({ - 'type': 'dialog-error', + SendDataToYast({'type': 'dialog-error', 'message': text }) path, yarg = GetDataFromYast() def get_translated_hotkey(translated, cmsg=''): - msg = 'PromptUser: '+_('Invalid hotkey for') + msg = 'PromptUser: ' + _('Invalid hotkey for') # Originally (\S) was used but with translations it would not work :( if re.search('\((\S+)\)', translated, re.LOCALE): @@ -68,10 +67,10 @@ def get_translated_hotkey(translated, cmsg=''): if cmsg: raise AppArmorException(cmsg) else: - raise AppArmorException('%s %s' %(msg, translated)) + raise AppArmorException('%s %s' % (msg, translated)) def UI_YesNo(text, default): - debug_logger.debug('UI_YesNo: %s: %s %s' %(UI_mode, text, default)) + debug_logger.debug('UI_YesNo: %s: %s %s' % (UI_mode, text, default)) default = default.lower() ans = None if UI_mode == 'text': @@ -105,10 +104,9 @@ def UI_YesNo(text, default): ans = default else: - SendDataToYast({ - 'type': 'dialog-yesno', - 'question': text - }) + SendDataToYast({'type': 'dialog-yesno', + 'question': text + }) ypath, yarg = GetDataFromYast() ans = yarg['answer'] if not ans: @@ -146,7 +144,7 @@ def UI_YesNoCancel(text, default): elif ans == nokey: ans = 'n' elif ans == cancelkey: - ans= 'c' + ans = 'c' elif ans == 'left': if default == 'n': default = 'y' @@ -160,8 +158,7 @@ def UI_YesNoCancel(text, default): else: ans = default else: - SendDataToYast({ - 'type': 'dialog-yesnocancel', + SendDataToYast({'type': 'dialog-yesnocancel', 'question': text }) ypath, yarg = GetDataFromYast() @@ -177,8 +174,7 @@ def UI_GetString(text, default): sys.stdout.write('\n' + text) string = sys.stdin.readline() else: - SendDataToYast({ - 'type': 'dialog-getstring', + SendDataToYast({'type': 'dialog-getstring', 'label': text, 'default': default }) @@ -205,8 +201,7 @@ def UI_BusyStart(message): if UI_mode == 'text': UI_Info(message) else: - SendDataToYast({ - 'type': 'dialog-busy-start', + SendDataToYast({'type': 'dialog-busy-start', 'message': message }) ypath, yarg = GetDataFromYast() @@ -217,8 +212,7 @@ def UI_BusyStop(): SendDataToYast({'type': 'dialog-busy-stop'}) ypath, yarg = GetDataFromYast() -CMDS = { - 'CMD_ALLOW': _('(A)llow'), +CMDS = {'CMD_ALLOW': _('(A)llow'), 'CMD_OTHER': _('(M)ore'), 'CMD_AUDIT_NEW': _('Audi(t)'), 'CMD_AUDIT_OFF': _('Audi(t) off'), @@ -311,16 +305,14 @@ def confirm_and_abort(): sys.exit(0) def UI_ShortMessage(title, message): - SendDataToYast({ - 'type': 'short-dialog-message', + SendDataToYast({'type': 'short-dialog-message', 'headline': title, 'message': message }) ypath, yarg = GetDataFromYast() def UI_LongMessage(title, message): - SendDataToYast({ - 'type': 'long-dialog-message', + SendDataToYast({'type': 'long-dialog-message', 'headline': title, 'message': message }) @@ -360,7 +352,7 @@ def Text_PromptUser(question): keys[key] = cmd if default and default == cmd: - menutext = '[%s]' %menutext + menutext = '[%s]' % menutext menu_items.append(menutext) @@ -396,14 +388,14 @@ def Text_PromptUser(question): prompt = '\n' if title: - prompt += '= %s =\n\n' %title + prompt += '= %s =\n\n' % title if headers: header_copy = headers[:] while header_copy: header = header_copy.pop(0) value = header_copy.pop(0) - prompt += formatstr %(header+':', value) + prompt += formatstr % (header + ':', value) prompt += '\n' if explanation: @@ -415,12 +407,12 @@ def Text_PromptUser(question): format_option = ' [%s - %s]' else: format_option = ' %s - %s ' - prompt += format_option %(index+1, option) + prompt += format_option % (index + 1, option) prompt += '\n' prompt += ' / '.join(menu_items) - sys.stdout.write(prompt+'\n') + sys.stdout.write(prompt + '\n') ans = getkey().lower() @@ -431,7 +423,7 @@ def Text_PromptUser(question): ans = 'XXXINVALIDXXX' elif ans == 'down': - if options and selected < len(options)-1: + if options and selected < len(options) - 1: selected += 1 ans = 'XXXINVALIDXXX' @@ -450,7 +442,7 @@ def Text_PromptUser(question): ans = 'XXXINVALIDXXX' if keys.get(ans, False) == 'CMD_HELP': - sys.stdout.write('\n%s\n' %helptext) + sys.stdout.write('\n%s\n' % helptext) ans = 'again' if keys.get(ans, False): diff --git a/apparmor/yasti.py b/apparmor/yasti.py index 93aaf8083..a2db3aa5d 100644 --- a/apparmor/yasti.py +++ b/apparmor/yasti.py @@ -101,4 +101,3 @@ def ParseTerm(inp): else: ret += argref return ret - From d27752350aa3a3c2ccc0e0ee76504c2156f059e3 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Tue, 11 Feb 2014 16:23:21 -0800 Subject: [PATCH 143/183] Simplify the work tools and modules need to do to get the shared translations. External utilities can still use their own textdomains if they have strings that are not part of the apparmor-utils catalog. --- Tools/aa-audit | 6 ++---- Tools/aa-autodep | 7 ++----- Tools/aa-cleanprof | 7 ++----- Tools/aa-complain | 7 ++----- Tools/aa-disable | 7 ++----- Tools/aa-enforce | 7 ++----- Tools/aa-genprof | 7 ++----- Tools/aa-logprof | 7 ++----- Tools/aa-mergeprof | 7 ++----- Tools/aa-unconfined | 7 ++----- apparmor/aa.py | 8 ++++---- apparmor/common.py | 1 - apparmor/logparser.py | 9 +++++---- apparmor/tools.py | 6 +++--- apparmor/ui.py | 6 +++--- 15 files changed, 35 insertions(+), 64 deletions(-) diff --git a/Tools/aa-audit b/Tools/aa-audit index 5bf1d038e..f6382b322 100644 --- a/Tools/aa-audit +++ b/Tools/aa-audit @@ -13,15 +13,13 @@ # # ---------------------------------------------------------------------- import argparse -import gettext import traceback -from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Switch the given programs to audit mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) diff --git a/Tools/aa-autodep b/Tools/aa-autodep index bc4ec1466..b05471269 100644 --- a/Tools/aa-autodep +++ b/Tools/aa-autodep @@ -13,15 +13,12 @@ # # ---------------------------------------------------------------------- import argparse -import gettext - -from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements')) parser.add_argument('--force', type=str, help=_('overwrite existing profile')) diff --git a/Tools/aa-cleanprof b/Tools/aa-cleanprof index 75e42b9c7..b03261d43 100644 --- a/Tools/aa-cleanprof +++ b/Tools/aa-cleanprof @@ -13,15 +13,12 @@ # # ---------------------------------------------------------------------- import argparse -import gettext - -from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) diff --git a/Tools/aa-complain b/Tools/aa-complain index efba399fa..7394d1200 100644 --- a/Tools/aa-complain +++ b/Tools/aa-complain @@ -13,15 +13,12 @@ # # ---------------------------------------------------------------------- import argparse -import gettext - -from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) diff --git a/Tools/aa-disable b/Tools/aa-disable index 878eb7ef8..a463e57d6 100644 --- a/Tools/aa-disable +++ b/Tools/aa-disable @@ -13,15 +13,12 @@ # # ---------------------------------------------------------------------- import argparse -import gettext - -from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) diff --git a/Tools/aa-enforce b/Tools/aa-enforce index 790bcac57..0bc5c0582 100644 --- a/Tools/aa-enforce +++ b/Tools/aa-enforce @@ -13,15 +13,12 @@ # # ---------------------------------------------------------------------- import argparse -import gettext - -from apparmor.common import TRANSLATION_DOMAIN import apparmor.tools # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) diff --git a/Tools/aa-genprof b/Tools/aa-genprof index 9918ff46f..b10b8e03f 100644 --- a/Tools/aa-genprof +++ b/Tools/aa-genprof @@ -14,19 +14,16 @@ # ---------------------------------------------------------------------- import argparse import atexit -import gettext import os import re import subprocess import sys -from apparmor.common import init_translations - import apparmor.aa as apparmor # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() def sysctl_read(path): value = None diff --git a/Tools/aa-logprof b/Tools/aa-logprof index c95a2ace3..8b77ca335 100644 --- a/Tools/aa-logprof +++ b/Tools/aa-logprof @@ -13,16 +13,13 @@ # # ---------------------------------------------------------------------- import argparse -import gettext import os -from apparmor.common import TRANSLATION_DOMAIN - import apparmor.aa as apparmor # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Process log entries to generate profiles')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) diff --git a/Tools/aa-mergeprof b/Tools/aa-mergeprof index 90001d1dc..75839561a 100644 --- a/Tools/aa-mergeprof +++ b/Tools/aa-mergeprof @@ -13,19 +13,16 @@ # # ---------------------------------------------------------------------- import argparse -import gettext import sys -from apparmor.common import TRANSLATION_DOMAIN - import apparmor.aa import apparmor.aamode import apparmor.severity import apparmor.cleanprofile as cleanprofile # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_('Perform a 3way merge on the given profiles')) parser.add_argument('mine', type=str, help=_('your profile')) diff --git a/Tools/aa-unconfined b/Tools/aa-unconfined index d1adbfa2a..f92813ee4 100644 --- a/Tools/aa-unconfined +++ b/Tools/aa-unconfined @@ -13,18 +13,15 @@ # # ---------------------------------------------------------------------- import argparse -import gettext import os import re import sys -from apparmor.common import TRANSLATION_DOMAIN - import apparmor.aa as apparmor # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() parser = argparse.ArgumentParser(description=_("Lists unconfined processes having tcp or udp ports")) parser.add_argument("--paranoid", action="store_true", help=_("scan all processes from /proc")) diff --git a/apparmor/aa.py b/apparmor/aa.py index aecbae419..ae1b402f2 100644 --- a/apparmor/aa.py +++ b/apparmor/aa.py @@ -33,8 +33,8 @@ import LibAppArmor from copy import deepcopy from apparmor.common import (AppArmorException, error, debug, msg, cmd, - open_file_read, valid_path, TRANSLATION_DOMAIN, - hasher, open_file_write, convert_regexp, DebugLogger) + open_file_read, valid_path, hasher, + open_file_write, convert_regexp, DebugLogger) import apparmor.ui as aaui @@ -45,8 +45,8 @@ from apparmor.aamode import (str_to_mode, mode_to_str, contains, split_mode, from apparmor.yasti import SendDataToYast, GetDataFromYast, shutdown_yast # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() # Setup logging incase of debugging is enabled debug_logger = DebugLogger('aa') diff --git a/apparmor/common.py b/apparmor/common.py index e4e932562..30943456b 100644 --- a/apparmor/common.py +++ b/apparmor/common.py @@ -21,7 +21,6 @@ import termios import tty DEBUGGING = False -TRANSLATION_DOMAIN = 'apparmor-utils' # diff --git a/apparmor/logparser.py b/apparmor/logparser.py index 7b8b14379..e77d5d49d 100644 --- a/apparmor/logparser.py +++ b/apparmor/logparser.py @@ -18,14 +18,15 @@ import sys import time import LibAppArmor from apparmor.common import (AppArmorException, error, debug, - open_file_read, valid_path, TRANSLATION_DOMAIN, - hasher, open_file_write, convert_regexp, DebugLogger) + open_file_read, valid_path, hasher, + open_file_write, convert_regexp, + DebugLogger) from apparmor.aamode import validate_log_mode, log_str_to_mode, hide_log_mode, AA_MAY_EXEC # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() class ReadLog: RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=') diff --git a/apparmor/tools.py b/apparmor/tools.py index 85c63e5b0..0c86c1477 100644 --- a/apparmor/tools.py +++ b/apparmor/tools.py @@ -16,11 +16,11 @@ import os import sys import apparmor.aa as apparmor -from apparmor.common import user_perm, TRANSLATION_DOMAIN +from apparmor.common import user_perm # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() class aa_tools: def __init__(self, tool_name, args): diff --git a/apparmor/ui.py b/apparmor/ui.py index 3a6b81bf7..486d34342 100644 --- a/apparmor/ui.py +++ b/apparmor/ui.py @@ -16,11 +16,11 @@ import sys import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast -from apparmor.common import readkey, AppArmorException, DebugLogger, TRANSLATION_DOMAIN +from apparmor.common import readkey, AppArmorException, DebugLogger # setup module translations -t = gettext.translation(TRANSLATION_DOMAIN, fallback=True) -_ = t.gettext +from apparmor.translations import init_translation +_ = init_translation() # Set up UI logger for separate messages from UI module debug_logger = DebugLogger('UI') From c43d4eaa93a17015c09a190d98273eb9e6b5b4a8 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Wed, 12 Feb 2014 09:59:23 -0800 Subject: [PATCH 144/183] Move perl applications that were reimplemented in python by Kshitij Gupta to the deprecated directory. --- {utils => deprecated/utils}/aa-audit | 0 {utils => deprecated/utils}/aa-autodep | 0 {utils => deprecated/utils}/aa-complain | 0 {utils => deprecated/utils}/aa-disable | 0 {utils => deprecated/utils}/aa-enforce | 0 {utils => deprecated/utils}/aa-genprof | 0 {utils => deprecated/utils}/aa-logprof | 0 {utils => deprecated/utils}/aa-unconfined | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {utils => deprecated/utils}/aa-audit (100%) rename {utils => deprecated/utils}/aa-autodep (100%) rename {utils => deprecated/utils}/aa-complain (100%) rename {utils => deprecated/utils}/aa-disable (100%) rename {utils => deprecated/utils}/aa-enforce (100%) rename {utils => deprecated/utils}/aa-genprof (100%) rename {utils => deprecated/utils}/aa-logprof (100%) rename {utils => deprecated/utils}/aa-unconfined (100%) diff --git a/utils/aa-audit b/deprecated/utils/aa-audit similarity index 100% rename from utils/aa-audit rename to deprecated/utils/aa-audit diff --git a/utils/aa-autodep b/deprecated/utils/aa-autodep similarity index 100% rename from utils/aa-autodep rename to deprecated/utils/aa-autodep diff --git a/utils/aa-complain b/deprecated/utils/aa-complain similarity index 100% rename from utils/aa-complain rename to deprecated/utils/aa-complain diff --git a/utils/aa-disable b/deprecated/utils/aa-disable similarity index 100% rename from utils/aa-disable rename to deprecated/utils/aa-disable diff --git a/utils/aa-enforce b/deprecated/utils/aa-enforce similarity index 100% rename from utils/aa-enforce rename to deprecated/utils/aa-enforce diff --git a/utils/aa-genprof b/deprecated/utils/aa-genprof similarity index 100% rename from utils/aa-genprof rename to deprecated/utils/aa-genprof diff --git a/utils/aa-logprof b/deprecated/utils/aa-logprof similarity index 100% rename from utils/aa-logprof rename to deprecated/utils/aa-logprof diff --git a/utils/aa-unconfined b/deprecated/utils/aa-unconfined similarity index 100% rename from utils/aa-unconfined rename to deprecated/utils/aa-unconfined From 975e389f1d03d71d7a2febded35233396b0f8c26 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Wed, 12 Feb 2014 10:27:44 -0800 Subject: [PATCH 145/183] Move over the perl apparmor modules (Immunix) as well as some other perl utilities to the deprecated to directory; a couple of perl utilities remain, but they are still useful and do not depend on the Immunix module (just the LibAppArmor perl module). --- {utils => deprecated/utils}/Immunix/AppArmor.pm | 0 {utils => deprecated/utils}/Immunix/Config.pm | 0 {utils => deprecated/utils}/Immunix/Reports.pm | 0 {utils => deprecated/utils}/Immunix/Repository.pm | 0 {utils => deprecated/utils}/Immunix/Severity.pm | 0 {utils => deprecated/utils}/aa-eventd | 0 {utils => deprecated/utils}/aa-repo.pl | 0 {utils => deprecated/utils}/convert-profile.pl | 0 {utils => deprecated/utils}/repair_obsolete_profiles | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename {utils => deprecated/utils}/Immunix/AppArmor.pm (100%) rename {utils => deprecated/utils}/Immunix/Config.pm (100%) rename {utils => deprecated/utils}/Immunix/Reports.pm (100%) rename {utils => deprecated/utils}/Immunix/Repository.pm (100%) rename {utils => deprecated/utils}/Immunix/Severity.pm (100%) rename {utils => deprecated/utils}/aa-eventd (100%) rename {utils => deprecated/utils}/aa-repo.pl (100%) rename {utils => deprecated/utils}/convert-profile.pl (100%) rename {utils => deprecated/utils}/repair_obsolete_profiles (100%) diff --git a/utils/Immunix/AppArmor.pm b/deprecated/utils/Immunix/AppArmor.pm similarity index 100% rename from utils/Immunix/AppArmor.pm rename to deprecated/utils/Immunix/AppArmor.pm diff --git a/utils/Immunix/Config.pm b/deprecated/utils/Immunix/Config.pm similarity index 100% rename from utils/Immunix/Config.pm rename to deprecated/utils/Immunix/Config.pm diff --git a/utils/Immunix/Reports.pm b/deprecated/utils/Immunix/Reports.pm similarity index 100% rename from utils/Immunix/Reports.pm rename to deprecated/utils/Immunix/Reports.pm diff --git a/utils/Immunix/Repository.pm b/deprecated/utils/Immunix/Repository.pm similarity index 100% rename from utils/Immunix/Repository.pm rename to deprecated/utils/Immunix/Repository.pm diff --git a/utils/Immunix/Severity.pm b/deprecated/utils/Immunix/Severity.pm similarity index 100% rename from utils/Immunix/Severity.pm rename to deprecated/utils/Immunix/Severity.pm diff --git a/utils/aa-eventd b/deprecated/utils/aa-eventd similarity index 100% rename from utils/aa-eventd rename to deprecated/utils/aa-eventd diff --git a/utils/aa-repo.pl b/deprecated/utils/aa-repo.pl similarity index 100% rename from utils/aa-repo.pl rename to deprecated/utils/aa-repo.pl diff --git a/utils/convert-profile.pl b/deprecated/utils/convert-profile.pl similarity index 100% rename from utils/convert-profile.pl rename to deprecated/utils/convert-profile.pl diff --git a/utils/repair_obsolete_profiles b/deprecated/utils/repair_obsolete_profiles similarity index 100% rename from utils/repair_obsolete_profiles rename to deprecated/utils/repair_obsolete_profiles From 46f5f51909b08576c3d2e7af1bfd384254e542f8 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Wed, 12 Feb 2014 14:42:39 -0600 Subject: [PATCH 146/183] add support for python3.2 and python3.4 to the python abstraction Acked-By: Jamie Strandboge Acked-by: Steve Beattie --- profiles/apparmor.d/abstractions/python | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/profiles/apparmor.d/abstractions/python b/profiles/apparmor.d/abstractions/python index f84512563..6454c529c 100644 --- a/profiles/apparmor.d/abstractions/python +++ b/profiles/apparmor.d/abstractions/python @@ -13,12 +13,12 @@ /usr/lib{,32,64}/python2.[4567]/**.{pyc,so} mr, /usr/lib{,32,64}/python2.[4567]/**.{egg,py,pth} r, /usr/lib{,32,64}/python2.[4567]/{site,dist}-packages/ r, - /usr/lib{,32,64}/python3.3/lib-dynload/*.so mr, + /usr/lib{,32,64}/python3.[234]/lib-dynload/*.so mr, /usr/local/lib{,32,64}/python2.[4567]/**.{pyc,so} mr, /usr/local/lib{,32,64}/python2.[4567]/**.{egg,py,pth} r, /usr/local/lib{,32,64}/python2.[4567]/{site,dist}-packages/ r, - /usr/local/lib{,32,64}/python3.3/lib-dynload/*.so mr, + /usr/local/lib{,32,64}/python3.[234]/lib-dynload/*.so mr, # Site-wide configuration /etc/python2.[4567]/** r, From b3b4fd448e4939fefe5c1b6ddf7f602f5238decf Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Wed, 12 Feb 2014 13:27:30 -0800 Subject: [PATCH 147/183] Reorganize layout to ease merger into upstream apparmor-utils tree. --- .project | 17 ----------------- .pydevproject | 8 -------- README.md => utils.new/README.md | 0 {Translate => utils.new/Translate}/README | 0 {Translate => utils.new/Translate}/messages.pot | 0 {Tools => utils.new}/aa-audit | 0 {Tools/manpages => utils.new}/aa-audit.pod | 0 {Tools => utils.new}/aa-autodep | 0 {Tools/manpages => utils.new}/aa-autodep.pod | 0 {Tools => utils.new}/aa-cleanprof | 0 {Tools/manpages => utils.new}/aa-cleanprof.pod | 0 {Tools => utils.new}/aa-complain | 0 {Tools/manpages => utils.new}/aa-complain.pod | 0 {Tools => utils.new}/aa-disable | 0 {Tools/manpages => utils.new}/aa-disable.pod | 0 {Tools => utils.new}/aa-enforce | 0 {Tools/manpages => utils.new}/aa-enforce.pod | 0 {Tools => utils.new}/aa-genprof | 0 {Tools/manpages => utils.new}/aa-genprof.pod | 0 {Tools => utils.new}/aa-logprof | 0 {Tools/manpages => utils.new}/aa-logprof.pod | 0 {Tools => utils.new}/aa-mergeprof | 0 {Tools/manpages => utils.new}/aa-mergeprof.pod | 0 {Tools => utils.new}/aa-unconfined | 0 {Tools/manpages => utils.new}/aa-unconfined.pod | 0 {apparmor => utils.new/apparmor}/__init__.py | 0 {apparmor => utils.new/apparmor}/aa.py | 0 {apparmor => utils.new/apparmor}/aamode.py | 0 .../apparmor}/cleanprofile.py | 0 {apparmor => utils.new/apparmor}/common.py | 0 {apparmor => utils.new/apparmor}/config.py | 0 {apparmor => utils.new/apparmor}/logparser.py | 0 {apparmor => utils.new/apparmor}/severity.py | 0 {apparmor => utils.new/apparmor}/tools.py | 0 {apparmor => utils.new/apparmor}/ui.py | 0 {apparmor => utils.new/apparmor}/yasti.py | 0 {Testing => utils.new/test}/aa_test.py | 0 {Testing => utils.new/test}/cleanprof_test.in | 0 {Testing => utils.new/test}/cleanprof_test.out | 0 {Testing => utils.new/test}/common_test.py | 0 {Testing => utils.new/test}/config_test.py | 0 {Testing => utils.new/test}/easyprof.conf | 0 {Testing => utils.new/test}/logprof.conf | 0 {Testing => utils.new/test}/minitools_test.py | 0 {Testing => utils.new/test}/regex_tests.ini | 0 {Testing => utils.new/test}/runtests-py2.sh | 0 {Testing => utils.new/test}/runtests-py3.sh | 0 {Testing => utils.new/test}/severity.db | 0 {Testing => utils.new/test}/severity_broken.db | 0 {Testing => utils.new/test}/severity_test.py | 0 50 files changed, 25 deletions(-) delete mode 100644 .project delete mode 100644 .pydevproject rename README.md => utils.new/README.md (100%) rename {Translate => utils.new/Translate}/README (100%) rename {Translate => utils.new/Translate}/messages.pot (100%) rename {Tools => utils.new}/aa-audit (100%) rename {Tools/manpages => utils.new}/aa-audit.pod (100%) rename {Tools => utils.new}/aa-autodep (100%) rename {Tools/manpages => utils.new}/aa-autodep.pod (100%) rename {Tools => utils.new}/aa-cleanprof (100%) rename {Tools/manpages => utils.new}/aa-cleanprof.pod (100%) rename {Tools => utils.new}/aa-complain (100%) rename {Tools/manpages => utils.new}/aa-complain.pod (100%) rename {Tools => utils.new}/aa-disable (100%) rename {Tools/manpages => utils.new}/aa-disable.pod (100%) rename {Tools => utils.new}/aa-enforce (100%) rename {Tools/manpages => utils.new}/aa-enforce.pod (100%) rename {Tools => utils.new}/aa-genprof (100%) rename {Tools/manpages => utils.new}/aa-genprof.pod (100%) rename {Tools => utils.new}/aa-logprof (100%) rename {Tools/manpages => utils.new}/aa-logprof.pod (100%) rename {Tools => utils.new}/aa-mergeprof (100%) rename {Tools/manpages => utils.new}/aa-mergeprof.pod (100%) rename {Tools => utils.new}/aa-unconfined (100%) rename {Tools/manpages => utils.new}/aa-unconfined.pod (100%) rename {apparmor => utils.new/apparmor}/__init__.py (100%) rename {apparmor => utils.new/apparmor}/aa.py (100%) rename {apparmor => utils.new/apparmor}/aamode.py (100%) rename {apparmor => utils.new/apparmor}/cleanprofile.py (100%) rename {apparmor => utils.new/apparmor}/common.py (100%) rename {apparmor => utils.new/apparmor}/config.py (100%) rename {apparmor => utils.new/apparmor}/logparser.py (100%) rename {apparmor => utils.new/apparmor}/severity.py (100%) rename {apparmor => utils.new/apparmor}/tools.py (100%) rename {apparmor => utils.new/apparmor}/ui.py (100%) rename {apparmor => utils.new/apparmor}/yasti.py (100%) rename {Testing => utils.new/test}/aa_test.py (100%) rename {Testing => utils.new/test}/cleanprof_test.in (100%) rename {Testing => utils.new/test}/cleanprof_test.out (100%) rename {Testing => utils.new/test}/common_test.py (100%) rename {Testing => utils.new/test}/config_test.py (100%) rename {Testing => utils.new/test}/easyprof.conf (100%) rename {Testing => utils.new/test}/logprof.conf (100%) rename {Testing => utils.new/test}/minitools_test.py (100%) rename {Testing => utils.new/test}/regex_tests.ini (100%) rename {Testing => utils.new/test}/runtests-py2.sh (100%) rename {Testing => utils.new/test}/runtests-py3.sh (100%) rename {Testing => utils.new/test}/severity.db (100%) rename {Testing => utils.new/test}/severity_broken.db (100%) rename {Testing => utils.new/test}/severity_test.py (100%) diff --git a/.project b/.project deleted file mode 100644 index 303ccdf1f..000000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - apparmor-profile-tools - - - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index 9d8f69996..000000000 --- a/.pydevproject +++ /dev/null @@ -1,8 +0,0 @@ - - - -/apparmor-profile-tools - -python 3.0 -Python3.3 - diff --git a/README.md b/utils.new/README.md similarity index 100% rename from README.md rename to utils.new/README.md diff --git a/Translate/README b/utils.new/Translate/README similarity index 100% rename from Translate/README rename to utils.new/Translate/README diff --git a/Translate/messages.pot b/utils.new/Translate/messages.pot similarity index 100% rename from Translate/messages.pot rename to utils.new/Translate/messages.pot diff --git a/Tools/aa-audit b/utils.new/aa-audit similarity index 100% rename from Tools/aa-audit rename to utils.new/aa-audit diff --git a/Tools/manpages/aa-audit.pod b/utils.new/aa-audit.pod similarity index 100% rename from Tools/manpages/aa-audit.pod rename to utils.new/aa-audit.pod diff --git a/Tools/aa-autodep b/utils.new/aa-autodep similarity index 100% rename from Tools/aa-autodep rename to utils.new/aa-autodep diff --git a/Tools/manpages/aa-autodep.pod b/utils.new/aa-autodep.pod similarity index 100% rename from Tools/manpages/aa-autodep.pod rename to utils.new/aa-autodep.pod diff --git a/Tools/aa-cleanprof b/utils.new/aa-cleanprof similarity index 100% rename from Tools/aa-cleanprof rename to utils.new/aa-cleanprof diff --git a/Tools/manpages/aa-cleanprof.pod b/utils.new/aa-cleanprof.pod similarity index 100% rename from Tools/manpages/aa-cleanprof.pod rename to utils.new/aa-cleanprof.pod diff --git a/Tools/aa-complain b/utils.new/aa-complain similarity index 100% rename from Tools/aa-complain rename to utils.new/aa-complain diff --git a/Tools/manpages/aa-complain.pod b/utils.new/aa-complain.pod similarity index 100% rename from Tools/manpages/aa-complain.pod rename to utils.new/aa-complain.pod diff --git a/Tools/aa-disable b/utils.new/aa-disable similarity index 100% rename from Tools/aa-disable rename to utils.new/aa-disable diff --git a/Tools/manpages/aa-disable.pod b/utils.new/aa-disable.pod similarity index 100% rename from Tools/manpages/aa-disable.pod rename to utils.new/aa-disable.pod diff --git a/Tools/aa-enforce b/utils.new/aa-enforce similarity index 100% rename from Tools/aa-enforce rename to utils.new/aa-enforce diff --git a/Tools/manpages/aa-enforce.pod b/utils.new/aa-enforce.pod similarity index 100% rename from Tools/manpages/aa-enforce.pod rename to utils.new/aa-enforce.pod diff --git a/Tools/aa-genprof b/utils.new/aa-genprof similarity index 100% rename from Tools/aa-genprof rename to utils.new/aa-genprof diff --git a/Tools/manpages/aa-genprof.pod b/utils.new/aa-genprof.pod similarity index 100% rename from Tools/manpages/aa-genprof.pod rename to utils.new/aa-genprof.pod diff --git a/Tools/aa-logprof b/utils.new/aa-logprof similarity index 100% rename from Tools/aa-logprof rename to utils.new/aa-logprof diff --git a/Tools/manpages/aa-logprof.pod b/utils.new/aa-logprof.pod similarity index 100% rename from Tools/manpages/aa-logprof.pod rename to utils.new/aa-logprof.pod diff --git a/Tools/aa-mergeprof b/utils.new/aa-mergeprof similarity index 100% rename from Tools/aa-mergeprof rename to utils.new/aa-mergeprof diff --git a/Tools/manpages/aa-mergeprof.pod b/utils.new/aa-mergeprof.pod similarity index 100% rename from Tools/manpages/aa-mergeprof.pod rename to utils.new/aa-mergeprof.pod diff --git a/Tools/aa-unconfined b/utils.new/aa-unconfined similarity index 100% rename from Tools/aa-unconfined rename to utils.new/aa-unconfined diff --git a/Tools/manpages/aa-unconfined.pod b/utils.new/aa-unconfined.pod similarity index 100% rename from Tools/manpages/aa-unconfined.pod rename to utils.new/aa-unconfined.pod diff --git a/apparmor/__init__.py b/utils.new/apparmor/__init__.py similarity index 100% rename from apparmor/__init__.py rename to utils.new/apparmor/__init__.py diff --git a/apparmor/aa.py b/utils.new/apparmor/aa.py similarity index 100% rename from apparmor/aa.py rename to utils.new/apparmor/aa.py diff --git a/apparmor/aamode.py b/utils.new/apparmor/aamode.py similarity index 100% rename from apparmor/aamode.py rename to utils.new/apparmor/aamode.py diff --git a/apparmor/cleanprofile.py b/utils.new/apparmor/cleanprofile.py similarity index 100% rename from apparmor/cleanprofile.py rename to utils.new/apparmor/cleanprofile.py diff --git a/apparmor/common.py b/utils.new/apparmor/common.py similarity index 100% rename from apparmor/common.py rename to utils.new/apparmor/common.py diff --git a/apparmor/config.py b/utils.new/apparmor/config.py similarity index 100% rename from apparmor/config.py rename to utils.new/apparmor/config.py diff --git a/apparmor/logparser.py b/utils.new/apparmor/logparser.py similarity index 100% rename from apparmor/logparser.py rename to utils.new/apparmor/logparser.py diff --git a/apparmor/severity.py b/utils.new/apparmor/severity.py similarity index 100% rename from apparmor/severity.py rename to utils.new/apparmor/severity.py diff --git a/apparmor/tools.py b/utils.new/apparmor/tools.py similarity index 100% rename from apparmor/tools.py rename to utils.new/apparmor/tools.py diff --git a/apparmor/ui.py b/utils.new/apparmor/ui.py similarity index 100% rename from apparmor/ui.py rename to utils.new/apparmor/ui.py diff --git a/apparmor/yasti.py b/utils.new/apparmor/yasti.py similarity index 100% rename from apparmor/yasti.py rename to utils.new/apparmor/yasti.py diff --git a/Testing/aa_test.py b/utils.new/test/aa_test.py similarity index 100% rename from Testing/aa_test.py rename to utils.new/test/aa_test.py diff --git a/Testing/cleanprof_test.in b/utils.new/test/cleanprof_test.in similarity index 100% rename from Testing/cleanprof_test.in rename to utils.new/test/cleanprof_test.in diff --git a/Testing/cleanprof_test.out b/utils.new/test/cleanprof_test.out similarity index 100% rename from Testing/cleanprof_test.out rename to utils.new/test/cleanprof_test.out diff --git a/Testing/common_test.py b/utils.new/test/common_test.py similarity index 100% rename from Testing/common_test.py rename to utils.new/test/common_test.py diff --git a/Testing/config_test.py b/utils.new/test/config_test.py similarity index 100% rename from Testing/config_test.py rename to utils.new/test/config_test.py diff --git a/Testing/easyprof.conf b/utils.new/test/easyprof.conf similarity index 100% rename from Testing/easyprof.conf rename to utils.new/test/easyprof.conf diff --git a/Testing/logprof.conf b/utils.new/test/logprof.conf similarity index 100% rename from Testing/logprof.conf rename to utils.new/test/logprof.conf diff --git a/Testing/minitools_test.py b/utils.new/test/minitools_test.py similarity index 100% rename from Testing/minitools_test.py rename to utils.new/test/minitools_test.py diff --git a/Testing/regex_tests.ini b/utils.new/test/regex_tests.ini similarity index 100% rename from Testing/regex_tests.ini rename to utils.new/test/regex_tests.ini diff --git a/Testing/runtests-py2.sh b/utils.new/test/runtests-py2.sh similarity index 100% rename from Testing/runtests-py2.sh rename to utils.new/test/runtests-py2.sh diff --git a/Testing/runtests-py3.sh b/utils.new/test/runtests-py3.sh similarity index 100% rename from Testing/runtests-py3.sh rename to utils.new/test/runtests-py3.sh diff --git a/Testing/severity.db b/utils.new/test/severity.db similarity index 100% rename from Testing/severity.db rename to utils.new/test/severity.db diff --git a/Testing/severity_broken.db b/utils.new/test/severity_broken.db similarity index 100% rename from Testing/severity_broken.db rename to utils.new/test/severity_broken.db diff --git a/Testing/severity_test.py b/utils.new/test/severity_test.py similarity index 100% rename from Testing/severity_test.py rename to utils.new/test/severity_test.py From 32e09315887425387201874860714dd1459760a9 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Wed, 12 Feb 2014 16:59:27 -0800 Subject: [PATCH 148/183] Fix up execute permissions that were lost in the merger for some reason. --- utils/aa-audit | 0 utils/aa-autodep | 0 utils/aa-cleanprof | 0 utils/aa-complain | 0 utils/aa-disable | 0 utils/aa-enforce | 0 utils/aa-genprof | 0 utils/aa-logprof | 0 utils/aa-mergeprof | 0 utils/aa-unconfined | 0 utils/test/aa_test.py | 0 utils/test/common_test.py | 0 utils/test/config_test.py | 0 utils/test/minitools_test.py | 0 utils/test/runtests-py2.sh | 0 utils/test/runtests-py3.sh | 0 utils/test/severity_test.py | 0 17 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 utils/aa-audit mode change 100644 => 100755 utils/aa-autodep mode change 100644 => 100755 utils/aa-cleanprof mode change 100644 => 100755 utils/aa-complain mode change 100644 => 100755 utils/aa-disable mode change 100644 => 100755 utils/aa-enforce mode change 100644 => 100755 utils/aa-genprof mode change 100644 => 100755 utils/aa-logprof mode change 100644 => 100755 utils/aa-mergeprof mode change 100644 => 100755 utils/aa-unconfined mode change 100644 => 100755 utils/test/aa_test.py mode change 100644 => 100755 utils/test/common_test.py mode change 100644 => 100755 utils/test/config_test.py mode change 100644 => 100755 utils/test/minitools_test.py mode change 100644 => 100755 utils/test/runtests-py2.sh mode change 100644 => 100755 utils/test/runtests-py3.sh mode change 100644 => 100755 utils/test/severity_test.py diff --git a/utils/aa-audit b/utils/aa-audit old mode 100644 new mode 100755 diff --git a/utils/aa-autodep b/utils/aa-autodep old mode 100644 new mode 100755 diff --git a/utils/aa-cleanprof b/utils/aa-cleanprof old mode 100644 new mode 100755 diff --git a/utils/aa-complain b/utils/aa-complain old mode 100644 new mode 100755 diff --git a/utils/aa-disable b/utils/aa-disable old mode 100644 new mode 100755 diff --git a/utils/aa-enforce b/utils/aa-enforce old mode 100644 new mode 100755 diff --git a/utils/aa-genprof b/utils/aa-genprof old mode 100644 new mode 100755 diff --git a/utils/aa-logprof b/utils/aa-logprof old mode 100644 new mode 100755 diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof old mode 100644 new mode 100755 diff --git a/utils/aa-unconfined b/utils/aa-unconfined old mode 100644 new mode 100755 diff --git a/utils/test/aa_test.py b/utils/test/aa_test.py old mode 100644 new mode 100755 diff --git a/utils/test/common_test.py b/utils/test/common_test.py old mode 100644 new mode 100755 diff --git a/utils/test/config_test.py b/utils/test/config_test.py old mode 100644 new mode 100755 diff --git a/utils/test/minitools_test.py b/utils/test/minitools_test.py old mode 100644 new mode 100755 diff --git a/utils/test/runtests-py2.sh b/utils/test/runtests-py2.sh old mode 100644 new mode 100755 diff --git a/utils/test/runtests-py3.sh b/utils/test/runtests-py3.sh old mode 100644 new mode 100755 diff --git a/utils/test/severity_test.py b/utils/test/severity_test.py old mode 100644 new mode 100755 From e9db24ac23ca9556c13a03c478f7bd97f1a27310 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Wed, 12 Feb 2014 23:32:25 -0800 Subject: [PATCH 149/183] Add support for better integration of external apparmor modules (e.g. appamror-click), see http://www.python.org/dev/peps/pep-0402/ for details. --- utils/apparmor/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/apparmor/__init__.py b/utils/apparmor/__init__.py index 94f439406..352e8fc7d 100644 --- a/utils/apparmor/__init__.py +++ b/utils/apparmor/__init__.py @@ -7,3 +7,4 @@ # License published by the Free Software Foundation. # # ------------------------------------------------------------------ +__import__('pkg_resources').declare_namespace(__name__) From bf655b530f73aa12efc074403f2ddd46e04d1936 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 00:04:39 -0800 Subject: [PATCH 150/183] Fix make install to handle new python binaries, as well as informing setuptools to install the entire apparmor python package. --- utils/Makefile | 22 ++++++++-------------- utils/python-tools-setup.py | 1 + 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/utils/Makefile b/utils/Makefile index 744a50039..46911cf7e 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -26,16 +26,14 @@ common/Make.rules: $(COMMONDIR)/Make.rules ln -sf $(COMMONDIR) . endif -MODDIR = Immunix -PERLTOOLS = aa-genprof aa-logprof aa-autodep aa-audit aa-complain aa-enforce \ - aa-unconfined aa-notify aa-disable aa-exec -TOOLS = ${PERLTOOLS} aa-decode aa-status -MODULES = ${MODDIR}/AppArmor.pm ${MODDIR}/Repository.pm \ - ${MODDIR}/Config.pm ${MODDIR}/Severity.pm -PYTOOLS = aa-easyprof +PERLTOOLS = aa-exec aa-notify +PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \ + aa-autodep aa-audit aa-complain aa-enforce aa-disable \ + aa-status aa-unconfined +TOOLS = ${PERLTOOLS} ${PYTOOLS} aa-decode PYSETUP = python-tools-setup.py -MANPAGES = ${TOOLS:=.8} logprof.conf.5 ${PYTOOLS:=.8} +MANPAGES = ${TOOLS:=.8} logprof.conf.5 all: ${MANPAGES} ${HTMLMANPAGES} $(MAKE) -C po all @@ -45,12 +43,10 @@ all: ${MANPAGES} ${HTMLMANPAGES} DESTDIR=/ BINDIR=${DESTDIR}/usr/sbin CONFDIR=${DESTDIR}/etc/apparmor -VENDOR_PERL=$(shell perl -e 'use Config; print $$Config{"vendorlib"};') -PERLDIR=${DESTDIR}${VENDOR_PERL}/${MODDIR} PYPREFIX=/usr -po/${NAME}.pot: ${TOOLS} ${PYTOOLS} - $(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${TOOLS} ${MODULES} ${PYTOOLS}" +po/${NAME}.pot: ${TOOLS} + $(MAKE) -C po ${NAME}.pot NAME=${NAME} SOURCES="${TOOLS} ${MODULES}" .PHONY: install install: ${MANPAGES} ${HTMLMANPAGES} @@ -59,8 +55,6 @@ install: ${MANPAGES} ${HTMLMANPAGES} install -d ${BINDIR} ln -sf aa-status ${BINDIR}/apparmor_status install -m 755 ${TOOLS} ${BINDIR} - install -d ${PERLDIR} - install -m 644 ${MODULES} ${PERLDIR} $(MAKE) -C po install DESTDIR=${DESTDIR} NAME=${NAME} $(MAKE) install_manpages DESTDIR=${DESTDIR} $(MAKE) -C vim install DESTDIR=${DESTDIR} diff --git a/utils/python-tools-setup.py b/utils/python-tools-setup.py index 1e56f9e1c..5cd5d3659 100644 --- a/utils/python-tools-setup.py +++ b/utils/python-tools-setup.py @@ -81,6 +81,7 @@ setup (name='apparmor', license='GPL-2', cmdclass={'install': Install}, package_dir={'apparmor': 'staging'}, + packages=['apparmor'], py_modules=['apparmor.easyprof'] ) From 15a95e3b368d888c966acd593ed5ddbc30ca0e47 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 08:20:59 -0800 Subject: [PATCH 151/183] Fix up some more pyflakes issues with the tools --- utils/aa-genprof | 4 ++-- utils/aa-mergeprof | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/utils/aa-genprof b/utils/aa-genprof index b10b8e03f..c38e614bd 100755 --- a/utils/aa-genprof +++ b/utils/aa-genprof @@ -155,8 +155,8 @@ while not done_profiling: for p in sorted(apparmor.helpers.keys()): if apparmor.helpers[p] == 'enforce': - enforce(p) - reload(p) + apparmor.enforce(p) + apparmor.reload(p) apparmor.UI_Info(_('\nReloaded AppArmor profiles in enforce mode.')) apparmor.UI_Info(_('\nPlease consider contributing your new profile!\nSee the following wiki page for more information:')+'\nhttp://wiki.apparmor.net/index.php/Profiles\n') diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index 75839561a..9fc059a0c 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -13,7 +13,7 @@ # # ---------------------------------------------------------------------- import argparse -import sys +import re import apparmor.aa import apparmor.aamode @@ -492,7 +492,7 @@ class Merge(object): if match: inc = match deleted = 0 - deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc) + deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Adding %s to profile.') % path) From d318ff610078b599667d81c93cf79a1fe51021b4 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 08:24:02 -0800 Subject: [PATCH 152/183] Fix up last pyflakes issues with tools --- utils/aa-mergeprof | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index 9fc059a0c..230148b0f 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -54,7 +54,6 @@ def main(): q['default'] = 'CMD_VIEW_CHANGES' q['options'] = [] q['selected'] = 0 - p =None ans = '' arg = None programs = list(mergeprofiles.user.aa.keys()) @@ -110,7 +109,7 @@ class Merge(object): deleted += user_base.compare_profiles() #Remove off the parts in other profile which are common/superfluous from base profile - base_other = cleanprofile.CleanProf(False, self.base, self.other) + # base_other = cleanprofile.CleanProf(False, self.base, self.other) # XXX base_other not used? deleted += user_base.compare_profiles() def conflict_mode(self, profile, hat, allow, path, mode, new_mode, old_mode): From f652178a02449b0508d867d21f16d358625736b7 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 08:31:59 -0800 Subject: [PATCH 153/183] aa-mergeprof: fixup some of the whitespace issues --- utils/aa-mergeprof | 183 ++++++++++++++++++++++----------------------- 1 file changed, 90 insertions(+), 93 deletions(-) diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index 230148b0f..c26bd9a07 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -34,18 +34,19 @@ args = parser.parse_args() profiles = [args.mine, args.base, args.other] + def main(): mergeprofiles = Merge(profiles) #Get rid of common/superfluous stuff mergeprofiles.clear_common() - + if not args.auto: mergeprofiles.ask_the_questions('other') - + mergeprofiles.clear_common() mergeprofiles.ask_the_questions('base') - + q = apparmor.aa.hasher() q['title'] = 'Changed Local Profiles' q['headers'] = [] @@ -69,7 +70,7 @@ def main(): #oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '') newprofile = apparmor.aa.serialize_profile(mergeprofiles.user.aa[program], program, '') apparmor.aa.display_changes_with_comments(mergeprofiles.user.filename, newprofile) - + class Merge(object): def __init__(self, profiles): @@ -103,11 +104,11 @@ class Merge(object): #Remove off the parts in other profile which are common/superfluous from user profile user_other = cleanprofile.CleanProf(False, self.user, self.other) deleted += user_other.compare_profiles() - + #Remove off the parts in base profile which are common/superfluous from user profile user_base = cleanprofile.CleanProf(False, self.user, self.base) deleted += user_base.compare_profiles() - + #Remove off the parts in other profile which are common/superfluous from base profile # base_other = cleanprofile.CleanProf(False, self.base, self.other) # XXX base_other not used? deleted += user_base.compare_profiles() @@ -147,14 +148,14 @@ class Merge(object): else: raise apparmor.aa.AppArmorException(_('Unknown selection')) done = True - + def ask_the_questions(self, other): if other == 'other': other = self.other else: other = self.base #print(other.aa) - + #Add the file-wide includes from the other profile to the user profile done = False options = list(map(lambda inc: '#include <%s>' %inc, sorted(other.filelist[other.filename]['include'].keys()))) @@ -175,8 +176,7 @@ class Merge(object): self.user.filelist[self.user.filename]['include'][inc] = True options.pop(selected) apparmor.aa.UI_Info(_('Adding %s to the file.') % selection) - - + sev_db = apparmor.aa.sev_db if not sev_db: sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown')) @@ -205,7 +205,7 @@ class Merge(object): apparmor.aa.UI_Info(_('Adding %s to the file.') % selection) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + #Add the capabilities for allow in ['allow', 'deny']: if other.aa[profile][hat].get(allow, False): @@ -218,22 +218,22 @@ class Merge(object): q = apparmor.aa.hasher() if newincludes: options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) - + if options: options.append('capability %s' % capability) q['options'] = [options] q['selected'] = default_option - 1 - + q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q['headers'] += [_('Capability'), capability] q['headers'] += [_('Severity'), severity] - + audit_toggle = 0 - + q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] - + q['default'] = 'CMD_ALLOW' - + done = False while not done: ans, selected = apparmor.aa.UI_PromptUser(q) @@ -241,7 +241,7 @@ class Merge(object): if ans == 'CMD_IGNORE_ENTRY': done = True break - + if ans == 'CMD_ALLOW': selection = '' if options: @@ -252,28 +252,28 @@ class Merge(object): inc = match deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True - + apparmor.aa.UI_Info(_('Adding %s to profile.') % selection) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + self.user.aa[profile][hat]['allow']['capability'][capability]['set'] = True self.user.aa[profile][hat]['allow']['capability'][capability]['audit'] = other.aa[profile][hat]['allow']['capability'][capability]['audit'] - + apparmor.aa.changed[profile] = True - + apparmor.aa.UI_Info(_('Adding capability %s to profile.'), capability) done = True - + elif ans == 'CMD_DENY': self.user.aa[profile][hat]['deny']['capability'][capability]['set'] = True apparmor.aa.changed[profile] = True - + apparmor.aa.UI_Info(_('Denying capability %s to profile.') % capability) done = True else: done = False - + # Process all the path entries. for allow in ['allow', 'deny']: for path in sorted(other.aa[profile][hat][allow]['path'].keys()): @@ -290,37 +290,37 @@ class Merge(object): allow_audit = set() deny_mode = set() deny_audit = set() - + fmode, famode, fm = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'allow', path) if fmode: allow_mode |= fmode if famode: allow_audit |= famode - + cm, cam, m = apparmor.aa.rematchfrag(self.user.aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam - + imode, iamode, im = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'allow', path) if imode: allow_mode |= imode if iamode: allow_audit |= iamode - + cm, cam, m = apparmor.aa.match_prof_incs_to_path(self.user.aa[profile][hat], 'deny', path) if cm: deny_mode |= cm if cam: deny_audit |= cam - + if deny_mode & apparmor.aa.AA_MAY_EXEC: deny_mode |= apparmor.aamode.ALL_AA_EXEC_TYPE - + # Mask off the denied modes mode = mode - deny_mode - + # If we get an exec request from some kindof event that generates 'PERMITTING X' # check if its already in allow_mode # if not add ix permission @@ -329,32 +329,32 @@ class Merge(object): mode = mode - apparmor.aamode.ALL_AA_EXEC_TYPE if not allow_mode & apparmor.aa.AA_MAY_EXEC: mode |= apparmor.aa.str_to_mode('ix') - + # m is not implied by ix - + ### If we get an mmap request, check if we already have it in allow_mode ##if mode & AA_EXEC_MMAP: ## # ix implies m, so we don't need to add m if ix is present ## if contains(allow_mode, 'ix'): ## mode = mode - AA_EXEC_MMAP - + if not mode: continue - + matches = [] - + if fmode: matches += fm - + if imode: matches += im - + if not apparmor.aa.mode_contains(allow_mode, mode): default_option = 1 options = [] newincludes = [] include_valid = False - + for incname in apparmor.aa.include.keys(): include_valid = False # If already present skip @@ -362,14 +362,14 @@ class Merge(object): continue if incname.startswith(apparmor.aa.profile_dir): incname = incname.replace(apparmor.aa.profile_dir+'/', '', 1) - + include_valid = apparmor.aa.valid_include('', incname) - + if not include_valid: continue - + cm, am, m = apparmor.aa.match_include_to_path(incname, 'allow', path) - + if cm and apparmor.aa.mode_contains(cm, mode): dm = apparmor.aa.match_include_to_path(incname, 'deny', path)[0] # If the mode is denied @@ -389,19 +389,19 @@ class Merge(object): for user_glob in apparmor.aa.user_globs: if apparmor.aa.matchliteral(user_glob, path): matches.append(user_glob) - + matches = list(set(matches)) if path in matches: matches.remove(path) - + options += apparmor.aa.order_globs(matches, path) default_option = len(options) - + sev_db.unload_variables() sev_db.load_variables(apparmor.aa.get_profile_filename(profile)) severity = sev_db.rank(path, apparmor.aa.mode_to_str(mode)) sev_db.unload_variables() - + audit_toggle = 0 owner_toggle = 0 if apparmor.aa.cfg['settings']['default_owner_prompt']: @@ -411,7 +411,7 @@ class Merge(object): q = apparmor.aa.hasher() q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat), _('Path'), path] - + if allow_mode: mode |= allow_mode tail = '' @@ -428,7 +428,7 @@ class Merge(object): else: prompt_mode = apparmor.aa.owner_flatten_mode(mode) tail = ' ' + _('(force all rule perms to owner)') - + if audit_toggle == 1: s = apparmor.aa.mode_to_str_user(allow_mode) if allow_mode: @@ -438,10 +438,10 @@ class Merge(object): s = 'audit ' + apparmor.aa.mode_to_str_user(prompt_mode) + tail else: s = apparmor.aa.mode_to_str_user(prompt_mode) + tail - + q['headers'] += [_('Old Mode'), apparmor.aa.mode_to_str_user(allow_mode), _('New Mode'), s] - + else: s = '' tail = '' @@ -456,26 +456,26 @@ class Merge(object): else: prompt_mode = apparmor.aa.owner_flatten_mode(mode) tail = ' ' + _('(force perms to owner)') - + s = apparmor.aa.mode_to_str_user(prompt_mode) q['headers'] += [_('Mode'), s] - + q['headers'] += [_('Severity'), severity] q['options'] = options q['selected'] = default_option - 1 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_GLOB', 'CMD_GLOBEXT', 'CMD_NEW', 'CMD_ABORT', 'CMD_FINISHED', 'CMD_OTHER'] - + q['default'] = 'CMD_ALLOW' - + ans, selected = apparmor.aa.UI_PromptUser(q) - + if ans == 'CMD_IGNORE_ENTRY': done = True break - + if ans == 'CMD_OTHER': audit_toggle, owner_toggle = apparmor.aa.UI_ask_mode_toggles(audit_toggle, owner_toggle, allow_mode) elif ans == 'CMD_USER_TOGGLE': @@ -497,7 +497,7 @@ class Merge(object): apparmor.aa.UI_Info(_('Adding %s to profile.') % path) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + else: if self.user.aa[profile][hat]['allow']['path'][path].get('mode', False): mode |= self.user.aa[profile][hat]['allow']['path'][path]['mode'] @@ -505,14 +505,14 @@ class Merge(object): for entry in self.user.aa[profile][hat]['allow']['path'].keys(): if path == entry: continue - + if apparmor.aa.matchregexp(path, entry): if apparmor.aa.mode_contains(mode, self.user.aa[profile][hat]['allow']['path'][entry]['mode']): deleted.append(entry) for entry in deleted: self.user.aa[profile][hat]['allow']['path'].pop(entry) deleted = len(deleted) - + if owner_toggle == 0: mode = apparmor.aa.flatten_mode(mode) #elif owner_toggle == 1: @@ -521,36 +521,36 @@ class Merge(object): mode = allow_mode | apparmor.aa.owner_flatten_mode(mode - allow_mode) elif owner_toggle == 3: mode = apparmor.aa.owner_flatten_mode(mode) - + if not self.user.aa[profile][hat]['allow'].get(path, False): self.user.aa[profile][hat]['allow']['path'][path]['mode'] = self.user.aa[profile][hat]['allow']['path'][path].get('mode', set()) | mode - - + + tmpmode = set() if audit_toggle == 1: tmpmode = mode- allow_mode elif audit_toggle == 2: tmpmode = mode - + self.user.aa[profile][hat]['allow']['path'][path]['audit'] = self.user.aa[profile][hat]['allow']['path'][path].get('audit', set()) | tmpmode - + apparmor.aa.changed[profile] = True - + apparmor.aa.UI_Info(_('Adding %s %s to profile') % (path, apparmor.aa.mode_to_str_user(mode))) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + elif ans == 'CMD_DENY': path = options[selected].strip() # Add new entry? self.user.aa[profile][hat]['deny']['path'][path]['mode'] = self.user.aa[profile][hat]['deny']['path'][path].get('mode', set()) | (mode - allow_mode) - + self.user.aa[profile][hat]['deny']['path'][path]['audit'] = self.user.aa[profile][hat]['deny']['path'][path].get('audit', set()) - + apparmor.aa.changed[profile] = True - + done = True - + elif ans == 'CMD_NEW': arg = options[selected] if not apparmor.aa.re_match_include(arg): @@ -564,29 +564,29 @@ class Merge(object): apparmor.aa.user_globs.append(ans) options.append(ans) default_option = len(options) - + elif ans == 'CMD_GLOB': newpath = options[selected].strip() if not apparmor.aa.re_match_include(newpath): newpath = apparmor.aa.glob_path(newpath) - + if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 - + elif ans == 'CMD_GLOBEXT': newpath = options[selected].strip() if not apparmor.aa.re_match_include(newpath): newpath = apparmor.aa.glob_path_withext(newpath) - + if newpath not in options: options.append(newpath) default_option = len(options) else: default_option = options.index(newpath) + 1 - + elif re.search('\d', ans): default_option = ans @@ -608,24 +608,24 @@ class Merge(object): options.append('network %s %s' % (family, sock_type)) q['options'] = options q['selected'] = default_option - 1 - + q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q['headers'] += [_('Network Family'), family] q['headers'] += [_('Socket Type'), sock_type] - + audit_toggle = 0 q['functions'] = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', 'CMD_ABORT', 'CMD_FINISHED'] q['default'] = 'CMD_ALLOW' - + done = False while not done: ans, selected = apparmor.aa.UI_PromptUser(q) if ans == 'CMD_IGNORE_ENTRY': done = True break - + if ans.startswith('CMD_AUDIT'): audit_toggle = not audit_toggle audit = '' @@ -639,7 +639,7 @@ class Merge(object): q['headers'] = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q['headers'] += [_('Network Family'), audit + family] q['headers'] += [_('Socket Type'), sock_type] - + elif ans == 'CMD_ALLOW': #print(options, selected) selection = options[selected] @@ -648,34 +648,31 @@ class Merge(object): inc = apparmor.aa.re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0] deleted = 0 deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) - + self.user.aa[profile][hat]['include'][inc] = True - + apparmor.aa.changed[profile] = True - + apparmor.aa.UI_Info(_('Adding %s to profile') % selection) if deleted: apparmor.aa.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - + else: self.user.aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle self.user.aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True - + apparmor.aa.changed[profile] = True - + apparmor.aa.UI_Info(_('Adding network access %s %s to profile.') % (family, sock_type)) - + elif ans == 'CMD_DENY': done = True self.user.aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True apparmor.aa.changed[profile] = True apparmor.aa.UI_Info(_('Denying network access %s %s to profile') % (family, sock_type)) - + else: done = False - - - if __name__ == '__main__': main() From 81b3db3dbfdb9dbc17e4529c363d97cc58d0fd7b Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 10:01:03 -0800 Subject: [PATCH 154/183] Fix more pyflakes issues that cause make check to fail --- utils/apparmor/aa.py | 9 +++------ utils/apparmor/logparser.py | 6 +----- utils/apparmor/severity.py | 2 +- utils/apparmor/tools.py | 1 - utils/apparmor/ui.py | 1 - utils/apparmor/yasti.py | 1 - 6 files changed, 5 insertions(+), 15 deletions(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index ae1b402f2..f9e247772 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -13,7 +13,6 @@ # ---------------------------------------------------------------------- # No old version logs, only 2.6 + supported from __future__ import with_statement -import gettext import inspect import os import re @@ -28,12 +27,10 @@ import tempfile import apparmor.config import apparmor.logparser import apparmor.severity -import LibAppArmor from copy import deepcopy -from apparmor.common import (AppArmorException, error, debug, msg, cmd, - open_file_read, valid_path, hasher, +from apparmor.common import (AppArmorException, open_file_read, valid_path, hasher, open_file_write, convert_regexp, DebugLogger) import apparmor.ui as aaui @@ -313,7 +310,7 @@ def head(file): def get_output(params): """Returns the return code output by running the program with the args given in the list""" program = params[0] - args = params[1:] + # args = params[1:] ret = -1 output = [] # program is executable @@ -454,7 +451,7 @@ def get_profile(prof_name): profile_data = None distro = cfg['repository']['distro'] repo_url = cfg['repository']['url'] - local_profiles = [] + # local_profiles = [] profile_hash = hasher() if repo_is_enabled(): aaui.UI_BusyStart(_('Connecting to repository...')) diff --git a/utils/apparmor/logparser.py b/utils/apparmor/logparser.py index e77d5d49d..4e84f5dde 100644 --- a/utils/apparmor/logparser.py +++ b/utils/apparmor/logparser.py @@ -11,16 +11,12 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- -import gettext import os import re import sys import time import LibAppArmor -from apparmor.common import (AppArmorException, error, debug, - open_file_read, valid_path, hasher, - open_file_write, convert_regexp, - DebugLogger) +from apparmor.common import AppArmorException, open_file_read, DebugLogger from apparmor.aamode import validate_log_mode, log_str_to_mode, hide_log_mode, AA_MAY_EXEC diff --git a/utils/apparmor/severity.py b/utils/apparmor/severity.py index 9a0d3be9d..4c11ac71a 100644 --- a/utils/apparmor/severity.py +++ b/utils/apparmor/severity.py @@ -64,7 +64,7 @@ class Severity(object): try: resource, severity = line.split() severity = int(severity) - except ValueError as e: + except ValueError: error_message = 'No severity value present in file: %s\n\t[Line %s]: %s' % (dbname, lineno, line) #error(error_message) raise AppArmorException(error_message) # from None diff --git a/utils/apparmor/tools.py b/utils/apparmor/tools.py index 0c86c1477..01c016632 100644 --- a/utils/apparmor/tools.py +++ b/utils/apparmor/tools.py @@ -11,7 +11,6 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- -import gettext import os import sys diff --git a/utils/apparmor/ui.py b/utils/apparmor/ui.py index 486d34342..570d28cfa 100644 --- a/utils/apparmor/ui.py +++ b/utils/apparmor/ui.py @@ -11,7 +11,6 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- -import gettext import sys import re from apparmor.yasti import yastLog, SendDataToYast, GetDataFromYast diff --git a/utils/apparmor/yasti.py b/utils/apparmor/yasti.py index a2db3aa5d..8d01163b6 100644 --- a/utils/apparmor/yasti.py +++ b/utils/apparmor/yasti.py @@ -13,7 +13,6 @@ # ---------------------------------------------------------------------- import re #import ycp -import os import sys from apparmor.common import error, DebugLogger From be2296edf17dfc40e1447fabaca5e4949874885a Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 10:52:00 -0800 Subject: [PATCH 155/183] utils/apparmor/: work around last of pyflakes issues here --- utils/apparmor/aa.py | 22 +++++++++++----------- utils/apparmor/yasti.py | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index f9e247772..02e746ff1 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -1088,7 +1088,7 @@ def handle_children(profile, hat, root): context_new = context_new + '^%s' % hat context_new = context_new + ' -> %s' % exec_target - ans_new = transitions.get(context_new, '') + # ans_new = transitions.get(context_new, '') # XXX ans meant here? combinedmode = set() combinedaudit = set() ## Check return Value Consistency @@ -1120,7 +1120,7 @@ def handle_children(profile, hat, root): nt_name = None for entr in m: if aa[profile][hat]['allow']['path'][entry]['to']: - int_name = aa[profile][hat]['allow']['path'][entry]['to'] + nt_name = aa[profile][hat]['allow']['path'][entry]['to'] break if to_name and to_name != nt_name: pass @@ -1234,7 +1234,7 @@ def handle_children(profile, hat, root): q['headers'] += [_('Severity'), severity] q['functions'] = [] - prompt = '\n%s\n' % context_new + # prompt = '\n%s\n' % context_new # XXX exec_toggle = False q['functions'] += build_x_functions(default, options, exec_toggle) @@ -2188,9 +2188,9 @@ def match_net_includes(profile, family, nettype): def do_logprof_pass(logmark='', passno=0, pid=pid): # set up variables for this pass - t = hasher() +# t = hasher() # transitions = hasher() - seen = hasher() +# seen = hasher() # XXX global? global log global existing_profiles log = [] @@ -2201,7 +2201,7 @@ def do_logprof_pass(logmark='', passno=0, pid=pid): log = [] # log_dict = hasher() # changed = dict() - skip = hasher() +# skip = hasher() # XXX global? # filelist = hasher() aaui.UI_Info(_('Reading log entries from %s.') % filename) @@ -2268,7 +2268,7 @@ def save_profiles(): if aaui.UI_mode == 'yast': # To-Do - selected_profiles = [] + # selected_profiles = [] # XXX selected_profiles_ref? profile_changes = dict() for prof in changed_list: oldprofile = serialize_profile(original_aa[prof], prof) @@ -2301,7 +2301,6 @@ def save_profiles(): q['default'] = 'CMD_VIEW_CHANGES' q['options'] = changed q['selected'] = 0 - p = None ans = '' arg = None while ans != 'CMD_SAVE_CHANGES': @@ -3387,7 +3386,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): if not os.path.isfile(prof_filename): raise AppArmorException(_("Can't find existing profile to modify")) - profiles_list = filelist[prof_filename].keys() + # profiles_list = filelist[prof_filename].keys() # XXX with open_file_read(prof_filename) as f_in: profile = None @@ -3402,7 +3401,7 @@ def serialize_profile_from_old_profile(profile_data, name, options): 'path': write_paths, 'change_profile': write_change_profile, } - prof_correct = True + # prof_correct = True # XXX correct? segments = {'alias': False, 'lvar': False, 'include': False, @@ -4091,7 +4090,8 @@ def match_include_to_path(incname, allow, path): includelist = [incname] while includelist: incfile = str(includelist.pop(0)) - ret = load_include(incfile) + # ret = load_include(incfile) + load_include(incfile) if not include.get(incfile, {}): continue cm, am, m = rematchfrag(include[incfile].get(incfile, {}), allow, path) diff --git a/utils/apparmor/yasti.py b/utils/apparmor/yasti.py index 8d01163b6..e54930811 100644 --- a/utils/apparmor/yasti.py +++ b/utils/apparmor/yasti.py @@ -12,7 +12,7 @@ # # ---------------------------------------------------------------------- import re -#import ycp +import ycp import sys from apparmor.common import error, DebugLogger @@ -38,7 +38,7 @@ def SendDataToYast(data): ycommand, ypath, yargument = ParseCommand(line) if ycommand and ycommand == 'Read': debug_logger.info('SendDataToYast: Sending--%s' % data) - Return(data) + ycp.Return(data) # XXX is this right? return True else: debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) @@ -51,7 +51,7 @@ def GetDataFromYast(): ycommand, ypath, yarg = ParseCommand(line) debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg) if ycommand and ycommand == 'Write': - Return('true') + ycp.Return('true') # XXX is this right? return ypath, yarg else: debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) @@ -94,7 +94,7 @@ def ParseTerm(inp): inp = regex_term.sub('', inp) if not inp.startswith('('): ycp.y2error('No term parantheses') - argref, err, rest = ParseYcpTermBody(inp) + argref, err, rest = ycp.ParseYcpTermBody(inp) # XXX if err: ycp.y2error('%s (%s)' % (err, rest)) else: From 2db3a226dcef3912eea014417d63fce5aa0e3347 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 10:59:16 -0800 Subject: [PATCH 156/183] Regenerate apparmor-utils pot (translations template) file --- utils/po/Makefile | 2 +- utils/po/apparmor-utils.pot | 773 ++++++++++-------------------------- 2 files changed, 203 insertions(+), 572 deletions(-) diff --git a/utils/po/Makefile b/utils/po/Makefile index 974d57ce3..0c5527b8c 100644 --- a/utils/po/Makefile +++ b/utils/po/Makefile @@ -24,4 +24,4 @@ include ../common/Make-po.rules ../common/Make-po.rules: make -C .. common/Make.rules -XGETTEXT_ARGS+=--language=perl +XGETTEXT_ARGS+=--language=perl --language=python diff --git a/utils/po/apparmor-utils.pot b/utils/po/apparmor-utils.pot index c157fe7c7..969527a20 100644 --- a/utils/po/apparmor-utils.pot +++ b/utils/po/apparmor-utils.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: apparmor@lists.ubuntu.com\n" -"POT-Creation-Date: 2013-11-13 16:44-0800\n" +"POT-Creation-Date: 2014-02-13 10:57-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,702 +17,333 @@ msgstr "" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" -#: ../aa-genprof:72 ../aa-unconfined:54 +#: ../aa-genprof:52 +msgid "Generate profile for the given program" +msgstr "" + +#: ../aa-genprof:53 ../aa-logprof:25 ../aa-cleanprof:24 ../aa-mergeprof:31 +#: ../aa-autodep:25 ../aa-audit:25 ../aa-complain:24 ../aa-enforce:24 +#: ../aa-disable:24 +msgid "path to profiles" +msgstr "" + +#: ../aa-genprof:54 ../aa-logprof:26 +msgid "path to logfile" +msgstr "" + +#: ../aa-genprof:55 +msgid "name of program to profile" +msgstr "" + +#: ../aa-genprof:65 ../aa-logprof:37 +#, python-format +msgid "The logfile %s does not exist. Please check the path" +msgstr "" + +#: ../aa-genprof:71 ../aa-logprof:43 ../aa-unconfined:34 msgid "" -"AppArmor does not appear to be started. Please enable AppArmor and try again." +"It seems AppArmor was not started. Please enable AppArmor and try again." msgstr "" -#: ../aa-genprof:86 -msgid "Please enter the program to profile: " +#: ../aa-genprof:76 +#, python-format +msgid "%s is not a directory." msgstr "" -#: ../aa-genprof:105 -#, perl-format +#: ../aa-genprof:90 +#, python-format msgid "" -"Can't find %s in the system path list. If the name of the application is " -"correct, please run 'which %s' in the other window in order to find the " -"fully-qualified path." +"Can't find %s in the system path list. If the name of the application\n" +"is correct, please run 'which %s' as a user with correct PATH\n" +"environment set up in order to find the fully-qualified path and\n" +"use the full path as parameter." msgstr "" -#: ../aa-genprof:107 ../aa-autodep:110 ../aa-audit:120 ../aa-complain:119 -#: ../aa-enforce:130 ../aa-disable:140 -#, perl-format -msgid "%s does not exist, please double-check the path." +#: ../aa-genprof:92 +#, python-format +msgid "%s does not exists, please double-check the path." msgstr "" -#: ../aa-genprof:141 +#: ../aa-genprof:120 msgid "" "\n" "Before you begin, you may wish to check if a\n" "profile already exists for the application you\n" "wish to confine. See the following wiki page for\n" -"more information:\n" -"http://wiki.apparmor.net/index.php/Profiles" +"more information:" msgstr "" -#: ../aa-genprof:143 +#: ../aa-genprof:122 msgid "" -"Please start the application to be profiled in \n" +"Please start the application to be profiled in\n" "another window and exercise its functionality now.\n" "\n" -"Once completed, select the \"Scan\" button below in \n" -"order to scan the system logs for AppArmor events. \n" +"Once completed, select the \"Scan\" option below in \n" +"order to scan the system logs for AppArmor events. \n" "\n" -"For each AppArmor event, you will be given the \n" -"opportunity to choose whether the access should be \n" +"For each AppArmor event, you will be given the \n" +"opportunity to choose whether the access should be \n" "allowed or denied." msgstr "" -#: ../aa-genprof:163 +#: ../aa-genprof:143 msgid "Profiling" msgstr "" -#: ../aa-genprof:197 -msgid "Reloaded AppArmor profiles in enforce mode." -msgstr "" - -#: ../aa-genprof:198 +#: ../aa-genprof:161 msgid "" "\n" -"Please consider contributing your new profile! See\n" -"the following wiki page for more information:\n" -"http://wiki.apparmor.net/index.php/Profiles\n" +"Reloaded AppArmor profiles in enforce mode." msgstr "" -#: ../aa-genprof:199 -#, perl-format +#: ../aa-genprof:162 +msgid "" +"\n" +"Please consider contributing your new profile!\n" +"See the following wiki page for more information:" +msgstr "" + +#: ../aa-genprof:163 +#, python-format msgid "Finished generating profile for %s." msgstr "" -#: ../aa-genprof:203 -#, perl-format -msgid "" -"usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ program to " -"profile ]" +#: ../aa-logprof:24 +msgid "Process log entries to generate profiles" msgstr "" -#: ../aa-logprof:69 -#, perl-format -msgid "" -"usage: %s [ -d /path/to/profiles ] [ -f /path/to/logfile ] [ -m \"mark in " -"log to start processing after\"" +#: ../aa-logprof:27 +msgid "mark in the log to start processing after" msgstr "" -#: ../aa-autodep:61 -#, perl-format -msgid "Can't find AppArmor profiles in %s." +#: ../aa-cleanprof:23 +msgid "Cleanup the profiles for the given programs" msgstr "" -#: ../aa-autodep:69 -msgid "Please enter the program to create a profile for: " +#: ../aa-cleanprof:25 ../aa-autodep:26 ../aa-audit:27 ../aa-complain:26 +#: ../aa-enforce:26 ../aa-disable:26 +msgid "name of program" msgstr "" -#: ../aa-autodep:93 ../Immunix/AppArmor.pm:6339 -#, perl-format -msgid "" -"%s is currently marked as a program that should not have it's own profile. " -"Usually, programs are marked this way if creating a profile for them is " -"likely to break the rest of the system. If you know what you're doing and " -"are certain you want to create a profile for this program, edit the " -"corresponding entry in the [qualifiers] section in /etc/apparmor/logprof." -"conf." +#: ../aa-cleanprof:26 +msgid "Silently overwrite with a clean profile" msgstr "" -#: ../aa-autodep:100 -#, perl-format -msgid "Profile for %s already exists - skipping." +#: ../aa-mergeprof:27 +msgid "Perform a 3way merge on the given profiles" msgstr "" -#: ../aa-autodep:107 ../aa-audit:117 ../aa-complain:116 ../aa-enforce:127 -#: ../aa-disable:137 -#, perl-format -msgid "" -"Can't find %s in the system path list. If the name of the application is " -"correct, please run 'which %s' as a user with the correct PATH environment " -"set up in order to find the fully-qualified path." +#: ../aa-mergeprof:28 +msgid "your profile" msgstr "" -#: ../aa-audit:104 -#, perl-format -msgid "Setting %s to audit mode." +#: ../aa-mergeprof:29 +msgid "base profile" msgstr "" -#: ../aa-audit:129 -#, perl-format -msgid "usage: %s [ -d /path/to/profiles ] [ program to switch to audit mode ]" +#: ../aa-mergeprof:30 +msgid "other profile" msgstr "" -#: ../aa-complain:61 -msgid "Please enter the program to switch to complain mode: " +#: ../aa-mergeprof:32 +msgid "Automatically merge profiles, exits incase of *x conflicts" msgstr "" -#: ../aa-complain:103 ../Immunix/AppArmor.pm:621 ../Immunix/AppArmor.pm:941 -#, perl-format -msgid "Setting %s to complain mode." +#: ../aa-mergeprof:53 +msgid "The following local profiles were changed. Would you like to save them?" msgstr "" -#: ../aa-complain:128 -#, perl-format -msgid "" -"usage: %s [ -d /path/to/profiles ] [ program to switch to complain mode ]" -msgstr "" - -#: ../aa-enforce:62 -msgid "Please enter the program to switch to enforce mode: " -msgstr "" - -#: ../aa-enforce:103 ../Immunix/AppArmor.pm:634 -#, perl-format -msgid "Setting %s to enforce mode." -msgstr "" - -#: ../aa-enforce:139 -#, perl-format -msgid "" -"usage: %s [ -d /path/to/profiles ] [ program to switch to enforce mode ]" -msgstr "" - -#: ../aa-unconfined:48 -#, perl-format -msgid "Usage: %s [ --paranoid ]\n" -msgstr "" - -#: ../aa-unconfined:59 -msgid "Can't read /proc\n" -msgstr "" - -#: ../aa-unconfined:97 ../aa-unconfined:99 -msgid "not confined\n" -msgstr "" - -#: ../aa-unconfined:108 ../aa-unconfined:110 -msgid "confined by" -msgstr "" - -#: ../aa-disable:69 -msgid "Please enter the program whose profile should be disabled: " -msgstr "" - -#: ../aa-disable:113 -#, perl-format -msgid "Could not find basename for %s." -msgstr "" - -#: ../aa-disable:117 -#, perl-format -msgid "Disabling %s." -msgstr "" - -#: ../aa-disable:123 -#, perl-format -msgid "Could not create %s symlink." -msgstr "" - -#: ../aa-disable:149 -#, perl-format -msgid "usage: %s [ -d /path/to/profiles ] [ program to have profile disabled ]" -msgstr "" - -#: ../Immunix/AppArmor.pm:619 ../Immunix/AppArmor.pm:632 -#, perl-format -msgid "Can't find %s." -msgstr "" - -#: ../Immunix/AppArmor.pm:819 ../Immunix/AppArmor.pm:3326 -msgid "Connecting to repository....." -msgstr "" - -#: ../Immunix/AppArmor.pm:828 -#, perl-format -msgid "" -"WARNING: Error fetching profiles from the repository:\n" -"%s\n" -msgstr "" - -#: ../Immunix/AppArmor.pm:837 -msgid "Inactive local profile for " -msgstr "" - -#: ../Immunix/AppArmor.pm:874 ../Immunix/AppArmor.pm:1900 -#: ../Immunix/AppArmor.pm:2188 ../Immunix/AppArmor.pm:3453 -#: ../Immunix/AppArmor.pm:3486 ../Immunix/AppArmor.pm:3686 -#: ../Immunix/AppArmor.pm:3952 ../Immunix/AppArmor.pm:4004 -msgid "Profile" -msgstr "" - -#: ../Immunix/AppArmor.pm:908 -msgid "Profile submitted by" -msgstr "" - -#: ../Immunix/AppArmor.pm:949 -#, perl-format -msgid "Error activating profiles: %s\n" -msgstr "" - -#: ../Immunix/AppArmor.pm:1098 ../Immunix/AppArmor.pm:1151 -#, perl-format -msgid "" -"WARNING: Error syncronizing profiles with the repository:\n" -"%s\n" -msgstr "" - -#: ../Immunix/AppArmor.pm:1178 -msgid "New profiles" -msgstr "" - -#: ../Immunix/AppArmor.pm:1180 -msgid "" -"Please choose the newly created profiles that you would like\n" -"to store in the repository" -msgstr "" - -#: ../Immunix/AppArmor.pm:1187 -msgid "Submit newly created profiles to the repository" -msgstr "" - -#: ../Immunix/AppArmor.pm:1189 -msgid "Would you like to upload the newly created profiles?" -msgstr "" - -#: ../Immunix/AppArmor.pm:1202 -msgid "" -"Select which of the changed profiles you would like to upload\n" -"to the repository" -msgstr "" - -#: ../Immunix/AppArmor.pm:1204 -msgid "Changed profiles" -msgstr "" - -#: ../Immunix/AppArmor.pm:1210 -msgid "Submit changed profiles to the repository" -msgstr "" - -#: ../Immunix/AppArmor.pm:1212 -msgid "" -"The following profiles from the repository were changed.\n" -"Would you like to upload your changes?" -msgstr "" - -#: ../Immunix/AppArmor.pm:1279 ../Immunix/AppArmor.pm:1359 -#, perl-format -msgid "" -"WARNING: An error occured while uploading the profile %s\n" -"%s\n" -msgstr "" - -#: ../Immunix/AppArmor.pm:1284 -msgid "Uploaded changes to repository." -msgstr "" - -#: ../Immunix/AppArmor.pm:1306 ../Immunix/AppArmor.pm:3185 -#: ../Immunix/AppArmor.pm:3215 -msgid "Repository" -msgstr "" - -#: ../Immunix/AppArmor.pm:1333 -msgid "Changelog Entry: " -msgstr "" - -#: ../Immunix/AppArmor.pm:1354 -#, perl-format -msgid "Uploaded %s to repository." -msgstr "" - -#: ../Immunix/AppArmor.pm:1365 -msgid "" -"Repository Error\n" -"Registration or Signin was unsuccessful. User login\n" -"information is required to upload profiles to the\n" -"repository. These changes have not been sent.\n" -msgstr "" - -#: ../Immunix/AppArmor.pm:1422 ../Immunix/AppArmor.pm:1462 -msgid "(Y)es" -msgstr "" - -#: ../Immunix/AppArmor.pm:1423 ../Immunix/AppArmor.pm:1463 -msgid "(N)o" -msgstr "" - -#: ../Immunix/AppArmor.pm:1426 ../Immunix/AppArmor.pm:1467 -msgid "Invalid hotkey for" -msgstr "" - -#: ../Immunix/AppArmor.pm:1464 -msgid "(C)ancel" -msgstr "" - -#: ../Immunix/AppArmor.pm:1789 -msgid "Are you sure you want to abandon this set of profile changes and exit?" -msgstr "" - -#: ../Immunix/AppArmor.pm:1791 -msgid "Abandoning all changes." -msgstr "" - -#: ../Immunix/AppArmor.pm:1902 -msgid "Default Hat" -msgstr "" - -#: ../Immunix/AppArmor.pm:1904 -msgid "Requested Hat" -msgstr "" - -#: ../Immunix/AppArmor.pm:2190 -msgid "Program" -msgstr "" - -#: ../Immunix/AppArmor.pm:2195 -msgid "Execute" -msgstr "" - -#: ../Immunix/AppArmor.pm:2196 ../Immunix/AppArmor.pm:3455 -#: ../Immunix/AppArmor.pm:3488 ../Immunix/AppArmor.pm:3741 -msgid "Severity" -msgstr "" - -#: ../Immunix/AppArmor.pm:2241 -msgid "Enter profile name to transition to: " -msgstr "" - -#: ../Immunix/AppArmor.pm:2249 -msgid "" -"Should AppArmor sanitize the environment when\n" -"switching profiles?\n" -"\n" -"Sanitizing the environment is more secure,\n" -"but some applications depend on the presence\n" -"of LD_PRELOAD or LD_LIBRARY_PATH." -msgstr "" - -#: ../Immunix/AppArmor.pm:2251 -msgid "" -"Should AppArmor sanitize the environment when\n" -"switching profiles?\n" -"\n" -"Sanitizing the environment is more secure,\n" -"but this application appears to use LD_PRELOAD\n" -"or LD_LIBRARY_PATH and clearing these could\n" -"cause functionality problems." -msgstr "" - -#: ../Immunix/AppArmor.pm:2260 -#, perl-format -msgid "" -"Launching processes in an unconfined state is a very\n" -"dangerous operation and can cause serious security holes.\n" -"\n" -"Are you absolutely certain you wish to remove all\n" -"AppArmor protection when executing %s?" -msgstr "" - -#: ../Immunix/AppArmor.pm:2262 -msgid "" -"Should AppArmor sanitize the environment when\n" -"running this program unconfined?\n" -"\n" -"Not sanitizing the environment when unconfining\n" -"a program opens up significant security holes\n" -"and should be avoided if at all possible." -msgstr "" - -#: ../Immunix/AppArmor.pm:2352 -#, perl-format -msgid "A profile for %s does not exist. Create one?" -msgstr "" - -#: ../Immunix/AppArmor.pm:2379 -#, perl-format -msgid "A local profile for %s does not exist. Create one?" -msgstr "" - -#: ../Immunix/AppArmor.pm:2584 ../Immunix/AppArmor.pm:6733 -#: ../Immunix/AppArmor.pm:6738 -#, perl-format -msgid "Log contains unknown mode %s." -msgstr "" - -#: ../Immunix/AppArmor.pm:3068 -msgid "" -"An updated version of this profile has been found in the profile " -"repository. Would you like to use it?" -msgstr "" - -#: ../Immunix/AppArmor.pm:3098 -#, perl-format -msgid "Updated profile %s to revision %s." -msgstr "" - -#: ../Immunix/AppArmor.pm:3105 -msgid "Error parsing repository profile." -msgstr "" - -#: ../Immunix/AppArmor.pm:3141 -msgid "Create New User?" -msgstr "" - -#: ../Immunix/AppArmor.pm:3142 -msgid "Username: " -msgstr "" - -#: ../Immunix/AppArmor.pm:3143 -msgid "Password: " -msgstr "" - -#: ../Immunix/AppArmor.pm:3144 -msgid "Email Addr: " -msgstr "" - -#: ../Immunix/AppArmor.pm:3146 -msgid "Save Configuration? " -msgstr "" - -#: ../Immunix/AppArmor.pm:3155 -msgid "The Profile Repository server returned the following error:" -msgstr "" - -#: ../Immunix/AppArmor.pm:3157 -msgid "Please re-enter registration information or contact the administrator." -msgstr "" - -#: ../Immunix/AppArmor.pm:3158 -msgid "Login Error\n" -msgstr "" - -#: ../Immunix/AppArmor.pm:3165 -msgid "" -"Login failure\n" -" Please check username and password and try again." -msgstr "" - -#: ../Immunix/AppArmor.pm:3187 -msgid "" -"Would you like to enable access to the\n" -"profile repository?" -msgstr "" - -#: ../Immunix/AppArmor.pm:3218 -msgid "" -"Would you like to upload newly created and changed profiles to\n" -" the profile repository?" -msgstr "" - -#: ../Immunix/AppArmor.pm:3337 -#, perl-format -msgid "" -"WARNING: Profile update check failed\n" -"Error Detail:\n" -"%s" -msgstr "" - -#: ../Immunix/AppArmor.pm:3351 -msgid "Change mode modifiers" -msgstr "" - -#: ../Immunix/AppArmor.pm:3395 -msgid "Complain-mode changes:" -msgstr "" - -#: ../Immunix/AppArmor.pm:3397 -msgid "Enforce-mode changes:" -msgstr "" - -#: ../Immunix/AppArmor.pm:3403 -#, perl-format -msgid "Invalid mode found: %s" -msgstr "" - -#: ../Immunix/AppArmor.pm:3454 ../Immunix/AppArmor.pm:3487 -msgid "Capability" -msgstr "" - -#: ../Immunix/AppArmor.pm:3507 ../Immunix/AppArmor.pm:3781 -#: ../Immunix/AppArmor.pm:4028 -#, perl-format -msgid "Adding #include <%s> to profile." -msgstr "" - -#: ../Immunix/AppArmor.pm:3510 ../Immunix/AppArmor.pm:3782 -#: ../Immunix/AppArmor.pm:3822 ../Immunix/AppArmor.pm:4032 -#, perl-format -msgid "Deleted %s previous matching profile entries." -msgstr "" - -#: ../Immunix/AppArmor.pm:3521 -#, perl-format -msgid "Adding capability %s to profile." -msgstr "" - -#: ../Immunix/AppArmor.pm:3526 -#, perl-format -msgid "Denying capability %s to profile." -msgstr "" - -#: ../Immunix/AppArmor.pm:3687 +#: ../aa-mergeprof:131 ../aa-mergeprof:413 msgid "Path" msgstr "" -#: ../Immunix/AppArmor.pm:3698 ../Immunix/AppArmor.pm:3730 +#: ../aa-mergeprof:132 +msgid "Select the appropriate mode" +msgstr "" + +#: ../aa-mergeprof:149 +msgid "Unknown selection" +msgstr "" + +#: ../aa-mergeprof:166 ../aa-mergeprof:192 +msgid "File includes" +msgstr "" + +#: ../aa-mergeprof:166 ../aa-mergeprof:192 +msgid "Select the ones you wish to add" +msgstr "" + +#: ../aa-mergeprof:178 ../aa-mergeprof:205 +#, python-format +msgid "Adding %s to the file." +msgstr "" + +#: ../aa-mergeprof:182 +msgid "unknown" +msgstr "" + +#: ../aa-mergeprof:207 ../aa-mergeprof:258 ../aa-mergeprof:499 +#: ../aa-mergeprof:541 ../aa-mergeprof:658 +#, python-format +msgid "Deleted %s previous matching profile entries." +msgstr "" + +#: ../aa-mergeprof:227 ../aa-mergeprof:412 ../aa-mergeprof:612 +#: ../aa-mergeprof:639 +msgid "Profile" +msgstr "" + +#: ../aa-mergeprof:228 +msgid "Capability" +msgstr "" + +#: ../aa-mergeprof:229 ../aa-mergeprof:463 +msgid "Severity" +msgstr "" + +#: ../aa-mergeprof:256 ../aa-mergeprof:497 +#, python-format +msgid "Adding %s to profile." +msgstr "" + +#: ../aa-mergeprof:265 +#, python-format +msgid "Adding capability %s to profile." +msgstr "" + +#: ../aa-mergeprof:272 +#, python-format +msgid "Denying capability %s to profile." +msgstr "" + +#: ../aa-mergeprof:422 ../aa-mergeprof:453 msgid "(owner permissions off)" msgstr "" -#: ../Immunix/AppArmor.pm:3704 +#: ../aa-mergeprof:427 msgid "(force new perms to owner)" msgstr "" -#: ../Immunix/AppArmor.pm:3707 +#: ../aa-mergeprof:430 msgid "(force all rule perms to owner)" msgstr "" -#: ../Immunix/AppArmor.pm:3719 +#: ../aa-mergeprof:442 msgid "Old Mode" msgstr "" -#: ../Immunix/AppArmor.pm:3720 +#: ../aa-mergeprof:443 msgid "New Mode" msgstr "" -#: ../Immunix/AppArmor.pm:3736 +#: ../aa-mergeprof:458 msgid "(force perms to owner)" msgstr "" -#: ../Immunix/AppArmor.pm:3739 +#: ../aa-mergeprof:461 msgid "Mode" msgstr "" -#: ../Immunix/AppArmor.pm:3821 -#, perl-format -msgid "Adding %s %s to profile." +#: ../aa-mergeprof:539 +#, python-format +msgid "Adding %s %s to profile" msgstr "" -#: ../Immunix/AppArmor.pm:3837 +#: ../aa-mergeprof:557 msgid "Enter new path: " msgstr "" -#: ../Immunix/AppArmor.pm:3840 -msgid "The specified path does not match this log entry:" -msgstr "" - -#: ../Immunix/AppArmor.pm:3841 -msgid "Log Entry" -msgstr "" - -#: ../Immunix/AppArmor.pm:3842 -msgid "Entered Path" -msgstr "" - -#: ../Immunix/AppArmor.pm:3843 -msgid "Do you really want to use this path?" -msgstr "" - -#: ../Immunix/AppArmor.pm:3955 ../Immunix/AppArmor.pm:4007 +#: ../aa-mergeprof:613 ../aa-mergeprof:640 msgid "Network Family" msgstr "" -#: ../Immunix/AppArmor.pm:3958 ../Immunix/AppArmor.pm:4010 +#: ../aa-mergeprof:614 ../aa-mergeprof:641 msgid "Socket Type" msgstr "" -#: ../Immunix/AppArmor.pm:4058 -#, perl-format +#: ../aa-mergeprof:656 +#, python-format +msgid "Adding %s to profile" +msgstr "" + +#: ../aa-mergeprof:666 +#, python-format msgid "Adding network access %s %s to profile." msgstr "" -#: ../Immunix/AppArmor.pm:4077 -#, perl-format -msgid "Denying network access %s %s to profile." +#: ../aa-mergeprof:672 +#, python-format +msgid "Denying network access %s %s to profile" msgstr "" -#: ../Immunix/AppArmor.pm:4285 -#, perl-format -msgid "Reading log entries from %s." +#: ../aa-autodep:23 +msgid "Generate a basic AppArmor profile by guessing requirements" msgstr "" -#: ../Immunix/AppArmor.pm:4286 -#, perl-format -msgid "Updating AppArmor profiles in %s." +#: ../aa-autodep:24 +msgid "overwrite existing profile" msgstr "" -#: ../Immunix/AppArmor.pm:4290 -msgid "unknown\n" +#: ../aa-audit:24 +msgid "Switch the given programs to audit mode" msgstr "" -#: ../Immunix/AppArmor.pm:4324 -msgid "" -"The profile analyzer has completed processing the log files.\n" -"\n" -"All updated profiles will be reloaded" +#: ../aa-audit:26 +msgid "remove audit mode" msgstr "" -#: ../Immunix/AppArmor.pm:4330 -msgid "No unhandled AppArmor events were found in the system log." +#: ../aa-audit:28 +msgid "Show full trace" msgstr "" -#: ../Immunix/AppArmor.pm:4391 -msgid "" -"Select which profile changes you would like to save to the\n" -"local profile set" +#: ../aa-complain:23 +msgid "Switch the given program to complain mode" msgstr "" -#: ../Immunix/AppArmor.pm:4392 -msgid "Local profile changes" +#: ../aa-complain:25 +msgid "remove complain mode" msgstr "" -#: ../Immunix/AppArmor.pm:4419 -msgid "" -"The following local profiles were changed. Would you like to save them?" +#: ../aa-enforce:23 +msgid "Switch the given program to enforce mode" msgstr "" -#: ../Immunix/AppArmor.pm:4516 -msgid "Profile Changes" +#: ../aa-enforce:25 +msgid "switch to complain mode" msgstr "" -#: ../Immunix/AppArmor.pm:5139 ../Immunix/AppArmor.pm:5155 -#: ../Immunix/AppArmor.pm:5166 ../Immunix/AppArmor.pm:5174 -#: ../Immunix/AppArmor.pm:5195 ../Immunix/AppArmor.pm:5215 -#: ../Immunix/AppArmor.pm:5224 ../Immunix/AppArmor.pm:5256 -#: ../Immunix/AppArmor.pm:5334 ../Immunix/AppArmor.pm:5382 -#, perl-format -msgid "%s contains syntax errors." +#: ../aa-disable:23 +msgid "Disable the profile for the given programs" msgstr "" -#: ../Immunix/AppArmor.pm:5275 -#, perl-format -msgid "Profile %s contains invalid regexp %s." +#: ../aa-disable:25 +msgid "enable the profile for the given programs" msgstr "" -#: ../Immunix/AppArmor.pm:5280 -#, perl-format -msgid "Profile %s contains invalid mode %s." +#: ../aa-unconfined:26 +msgid "Lists unconfined processes having tcp or udp ports" msgstr "" -#: ../Immunix/AppArmor.pm:5430 -#, perl-format -msgid "%s contains syntax errors. Line [%s]" +#: ../aa-unconfined:27 +msgid "scan all processes from /proc" msgstr "" -#: ../Immunix/AppArmor.pm:6022 -#, perl-format -msgid "Writing updated profile for %s." +#: ../aa-unconfined:79 +#, python-format +msgid "%s %s (%s) not confined\n" msgstr "" -#: ../Immunix/AppArmor.pm:6528 -msgid "Unknown command" +#: ../aa-unconfined:83 +#, python-format +msgid "%s %s %snot confined\n" msgstr "" -#: ../Immunix/AppArmor.pm:6536 -msgid "Invalid hotkey in" +#: ../aa-unconfined:88 +#, python-format +msgid "%s %s (%s) confined by '%s'\n" msgstr "" -#: ../Immunix/AppArmor.pm:6546 -msgid "Duplicate hotkey for" -msgstr "" - -#: ../Immunix/AppArmor.pm:6567 -msgid "Invalid hotkey in default item" -msgstr "" - -#: ../Immunix/AppArmor.pm:6576 -msgid "Invalid default" +#: ../aa-unconfined:92 +#, python-format +msgid "%s %s %sconfined by '%s'\n" msgstr "" From 3b726b996db13e94f26483734e523a531bc3b8da Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 11:01:29 -0800 Subject: [PATCH 157/183] utils: no need to run pyflakes on aa-status twice --- utils/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/Makefile b/utils/Makefile index 46911cf7e..c72d5a774 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -94,7 +94,7 @@ check: check_severity_db perl -c $$i || exit 1; \ done tmpfile=$$(mktemp --tmpdir aa-pyflakes-XXXXXX); \ - for i in ${PYTOOLS} apparmor aa-status test/*.py; do \ + for i in ${PYTOOLS} apparmor test/*.py; do \ echo Checking $$i; \ pyflakes $$i 2>&1 | grep -v "undefined name '_'" > $$tmpfile; \ test -s $$tmpfile && cat $$tmpfile && rm -f $$tmpfile && exit 1; \ From 56b01b6eaa4490403a64d56b828e340e1a97600a Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 11:14:34 -0800 Subject: [PATCH 158/183] utils/apparmor/yasti.py: work around ycp not being available everywhere --- utils/apparmor/yasti.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/apparmor/yasti.py b/utils/apparmor/yasti.py index e54930811..5b7b2a351 100644 --- a/utils/apparmor/yasti.py +++ b/utils/apparmor/yasti.py @@ -12,8 +12,12 @@ # # ---------------------------------------------------------------------- import re -import ycp import sys +try: + import ycp +except ImportError: + # ycp isn't found everywhere. + ycp = None from apparmor.common import error, DebugLogger From f54a574ee4526d640c042ccb24c6b3bb75db9e63 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 11:32:36 -0800 Subject: [PATCH 159/183] utils/: fix last make check failure, though the new utils tests need to be added in (but they have failures themselves currently) --- utils/test/aa_test.py | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/utils/test/aa_test.py b/utils/test/aa_test.py index e8849efa0..fd6315e98 100755 --- a/utils/test/aa_test.py +++ b/utils/test/aa_test.py @@ -113,35 +113,16 @@ class Test(unittest.TestCase): def test_modes_to_string(self): - for string in self.MODE_TEST.keys(): mode = self.MODE_TEST[string] self.assertEqual(apparmor.aamode.mode_to_str(mode), string, 'mode is %s and string is %s'%(mode, string)) def test_string_to_modes(self): - #self.assertEqual(apparmor.aa.str_to_mode('wc'), 32270) - MODE_TEST = {'x': apparmor.aamode.AA_MAY_EXEC, - 'w': apparmor.aamode.AA_MAY_WRITE, - 'r': apparmor.aamode.AA_MAY_READ, - 'a': apparmor.aamode.AA_MAY_APPEND, - 'l': apparmor.aamode.AA_MAY_LINK, - 'k': apparmor.aamode.AA_MAY_LOCK, - 'm': apparmor.aamode.AA_EXEC_MMAP, - 'i': apparmor.aamode.AA_EXEC_INHERIT, - 'u': apparmor.aamode.AA_EXEC_UNCONFINED | apparmor.aamode.AA_EXEC_UNSAFE, # Unconfined + Unsafe - 'U': apparmor.aamode.AA_EXEC_UNCONFINED, - 'p': apparmor.aamode.AA_EXEC_PROFILE | apparmor.aamode.AA_EXEC_UNSAFE, # Profile + unsafe - 'P': apparmor.aamode.AA_EXEC_PROFILE, - 'c': apparmor.aamode.AA_EXEC_CHILD | apparmor.aamode.AA_EXEC_UNSAFE, # Child + Unsafe - 'C': apparmor.aamode.AA_EXEC_CHILD, - } - - #while MODE_TEST: - # string,mode = MODE_TEST.popitem() - # self.assertEqual(apparmor.aamode.str_to_mode(string), mode) - - #self.assertEqual(apparmor.aa.str_to_mode('C'), 2048) + for string in self.MODE_TEST.keys(): + mode = self.MODE_TEST[string] | apparmor.aamode.AA_OTHER(self.MODE_TEST[string]) + #print("mode: %s string: %s str_to_mode(string): %s" % (mode, string, apparmor.aamode.str_to_mode(string))) + self.assertEqual(mode, apparmor.aamode.str_to_mode(string), 'mode is %s and string is %s'%(mode, string)) if __name__ == "__main__": From e61d8bda60df0e2e84f94e5376d67cee289e754e Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 11:54:42 -0800 Subject: [PATCH 160/183] utils: have make clean purge python3 __pycache__ dir in apparmor/ --- utils/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/Makefile b/utils/Makefile index c72d5a774..2a9893cc3 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -72,7 +72,7 @@ clean: _clean $(MAKE) -C vim clean rm -rf staging/ build/ rm -f apparmor/*.pyc - rm -rf test/__pycache__/ + rm -rf test/__pycache__/ apparmor/__pycache__/ # ${CAPABILITIES} is defined in common/Make.rules .PHONY: check_severity_db From 311163203ae64c567411bcc15b60dbefec26af14 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Thu, 13 Feb 2014 22:19:26 +0100 Subject: [PATCH 161/183] dovecot profiles - use abstractions/nameservice After testing the dovecot profiles on a new server, I noticed /usr/lib/dovecot/dict and /usrlib/dovecot/lmtp need more nameservice- related permissions. Therefore include abstractions/nameservice instead of adding more and more files. Acked-by: John Johansen (on IRC) --- profiles/apparmor.d/usr.lib.dovecot.dict | 3 +-- profiles/apparmor.d/usr.lib.dovecot.lmtp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/profiles/apparmor.d/usr.lib.dovecot.dict b/profiles/apparmor.d/usr.lib.dovecot.dict index c48a5b74b..021fdb11c 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.dict +++ b/profiles/apparmor.d/usr.lib.dovecot.dict @@ -14,6 +14,7 @@ /usr/lib/dovecot/dict { #include #include + #include capability setgid, capability setuid, @@ -22,8 +23,6 @@ /etc/dovecot/dovecot-database.conf.ext r, /etc/dovecot/dovecot-dict-sql.conf.ext r, - /etc/nsswitch.conf r, - /etc/services r, /usr/lib/dovecot/dict mr, # Site-specific additions and overrides. See local/README for details. diff --git a/profiles/apparmor.d/usr.lib.dovecot.lmtp b/profiles/apparmor.d/usr.lib.dovecot.lmtp index dd8cd5a34..2c43d4c39 100644 --- a/profiles/apparmor.d/usr.lib.dovecot.lmtp +++ b/profiles/apparmor.d/usr.lib.dovecot.lmtp @@ -14,6 +14,7 @@ /usr/lib/dovecot/lmtp { #include + #include deny capability block_suspend, @@ -24,7 +25,6 @@ @{DOVECOT_MAILSTORE}/ rw, @{DOVECOT_MAILSTORE}/** rwkl, - /etc/resolv.conf r, /proc/*/mounts r, /tmp/dovecot.lmtp.* rw, /usr/lib/dovecot/lmtp mr, From 841c0e767c06d444339cb083eae13f2ff627c505 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 13 Feb 2014 14:32:28 -0800 Subject: [PATCH 162/183] deprecated/utils/: add back Makefile to simplify install of deprecated Immunix perl modules --- deprecated/utils/Makefile | 69 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 deprecated/utils/Makefile diff --git a/deprecated/utils/Makefile b/deprecated/utils/Makefile new file mode 100644 index 000000000..8e46d1272 --- /dev/null +++ b/deprecated/utils/Makefile @@ -0,0 +1,69 @@ +# ---------------------------------------------------------------------- +# Copyright (c) 1999, 2004-2009 NOVELL (All rights reserved) +# Copyright (c) 2010-2011, 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. +# +# 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. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact Novell, Inc. +# ---------------------------------------------------------------------- + +# NOTE: this Makefile has been adjusted from the original to assist in +# the installation of the Immunix perl modules, if they're still needed +# by users. Because the utilities conflict with their replacments, make +# install *will* *not* install them. + +NAME = apparmor-utils +all: +COMMONDIR=../../common/ + +include common/Make.rules + +COMMONDIR_EXISTS=$(strip $(shell [ -d ${COMMONDIR} ] && echo true)) +ifeq ($(COMMONDIR_EXISTS), true) +common/Make.rules: $(COMMONDIR)/Make.rules + ln -sf $(COMMONDIR) . +endif + +MODDIR = Immunix +PERLTOOLS = aa-genprof aa-logprof aa-autodep aa-audit aa-complain aa-enforce \ + aa-unconfined aa-disable +MODULES = ${MODDIR}/AppArmor.pm ${MODDIR}/Repository.pm \ + ${MODDIR}/Config.pm ${MODDIR}/Severity.pm + +all: + +# need some better way of determining this +DESTDIR=/ +BINDIR=${DESTDIR}/usr/sbin +CONFDIR=${DESTDIR}/etc/apparmor +VENDOR_PERL=$(shell perl -e 'use Config; print $$Config{"vendorlib"};') +PERLDIR=${DESTDIR}${VENDOR_PERL}/${MODDIR} + +.PHONY: install +install: + install -d ${PERLDIR} + install -m 644 ${MODULES} ${PERLDIR} + +.PHONY: clean +ifndef VERBOSE +.SILENT: clean +endif +clean: _clean + rm -f core core.* *.o *.s *.a *~ + rm -f Make.rules + rm -rf staging/ build/ + +.PHONY: check +.SILENT: check +check: + for i in ${MODULES} ${PERLTOOLS} ; do \ + perl -c $$i || exit 1; \ + done From 37ecdcfce59e53e7cc9c7ac62b38d624a1aac354 Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 17:15:03 -0800 Subject: [PATCH 163/183] =?UTF-8?q?Description:=20Allow=20using=20sssd=20f?= =?UTF-8?q?or=20group=20and=20password=20lookups=20Author:=20St=C3=A9phane?= =?UTF-8?q?=20Graber=20=20Acked-by:=20Steve=20Beattie?= =?UTF-8?q?=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This was originally patch 0018-lp1056391.patch in the Ubuntu apparmor packaging; Steve noticed the now-redundant line for /var/lib/sss/mc/passwd so I removed that at the same time. --- profiles/apparmor.d/abstractions/nameservice | 6 ++++++ profiles/apparmor.d/usr.sbin.smbd | 1 - 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/profiles/apparmor.d/abstractions/nameservice b/profiles/apparmor.d/abstractions/nameservice index a9d542be9..9492fa082 100644 --- a/profiles/apparmor.d/abstractions/nameservice +++ b/profiles/apparmor.d/abstractions/nameservice @@ -21,6 +21,12 @@ /etc/passwd r, /etc/protocols r, + # When using sssd, the passwd and group files are stored in an alternate path + # and the nss plugin also needs to talk to a pipe + /var/lib/sss/mc/group r, + /var/lib/sss/mc/passwd r, + /var/lib/sss/pipes/nss rw, + /etc/resolv.conf r, # on systems using resolvconf, /etc/resolv.conf is a symlink to # /{,var/}run/resolvconf/resolv.conf and a file sometimes referenced in diff --git a/profiles/apparmor.d/usr.sbin.smbd b/profiles/apparmor.d/usr.sbin.smbd index fc257671a..e4072d951 100644 --- a/profiles/apparmor.d/usr.sbin.smbd +++ b/profiles/apparmor.d/usr.sbin.smbd @@ -36,7 +36,6 @@ /var/cache/samba/** rwk, /var/cache/samba/printing/printers.tdb mrw, /var/lib/samba/** rwk, - /var/lib/sss/mc/passwd r, /var/lib/sss/pubconf/kdcinfo.* r, /{,var/}run/cups/cups.sock rw, /{,var/}run/dbus/system_bus_socket rw, From f88539d230777a1c553320eea4e9fef31555ffa6 Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 17:17:46 -0800 Subject: [PATCH 164/183] Description: /etc/vdpau_wrapper.cfg needed for Firefox 18+ on quantal Author: Micah Gersten Acked-by: Steve Beattie Modified by Seth Arnold; nvidia nvpau_wrapper.cfg permission was hoisted up into an nvidia abstraction. --- profiles/apparmor.d/abstractions/ubuntu-browsers.d/multimedia | 3 +++ 1 file changed, 3 insertions(+) diff --git a/profiles/apparmor.d/abstractions/ubuntu-browsers.d/multimedia b/profiles/apparmor.d/abstractions/ubuntu-browsers.d/multimedia index 09ddaf78c..e7c55c5b4 100644 --- a/profiles/apparmor.d/abstractions/ubuntu-browsers.d/multimedia +++ b/profiles/apparmor.d/abstractions/ubuntu-browsers.d/multimedia @@ -55,3 +55,6 @@ # Virus scanners /usr/bin/clamscan Cx -> sanitized_helper, + + # gxine (LP: #1057642) + /var/lib/xine/gxine.desktop r, From 8e5f15c603db33cbdf4ed795a12616fab96b4293 Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 17:21:41 -0800 Subject: [PATCH 165/183] Author: Jamie Strandboge Description: update mod_apparmor man page for Apache 2.4 and add new apparmor.d/usr.sbin.apache2 profile (based on the prefork profile) Acked-by: Steve Beattie Differs from original 0036-libapache2-mod-apparmor-profile-2.4.patch ubuntu patch -- I've deleted the "delete the apache 2.2 profile" part of the patch. So apache 2.2's profile is also still supported. --- changehat/mod_apparmor/mod_apparmor.pod | 3 +- profiles/apparmor.d/usr.sbin.apache2 | 83 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 profiles/apparmor.d/usr.sbin.apache2 diff --git a/changehat/mod_apparmor/mod_apparmor.pod b/changehat/mod_apparmor/mod_apparmor.pod index 7de7bc676..75616416c 100644 --- a/changehat/mod_apparmor/mod_apparmor.pod +++ b/changehat/mod_apparmor/mod_apparmor.pod @@ -116,7 +116,8 @@ will mod_apparmor() currently only supports apache2, and has only been tested with the prefork MPM configuration -- threaded configurations of Apache -may not work correctly. +may not work correctly. For Apache 2.4 users, you should enable the mpm_prefork +module. There are likely other bugs lurking about; if you find any, please report them at L. diff --git a/profiles/apparmor.d/usr.sbin.apache2 b/profiles/apparmor.d/usr.sbin.apache2 new file mode 100644 index 000000000..95c477860 --- /dev/null +++ b/profiles/apparmor.d/usr.sbin.apache2 @@ -0,0 +1,83 @@ +# Author: Marc Deslauriers + +#include +/usr/sbin/apache2 { + + # This profile is completely permissive. + # It is designed to target specific applications using mod_apparmor, + # hats, and the apache2.d directory. + # + # In order to enable this profile, you must: + # + # 1- Enable it: + # sudo aa-enforce /etc/apparmor.d/usr.sbin.apache2 + # + # 2- Load the mpm_prefork and mod_apparmor modules: + # sudo a2dismod + # sudo a2enmod mpm_prefork + # sudo a2enmod apparmor + # sudo service apache2 restart + # + # 3- Place an appropriate profile containing the desired hat in the + # /etc/apparmor.d/apache2.d directory. Such profiles should probably + # include the "apache2-common" abstraction. + # + # 4- Use the "AADefaultHatName" apache configuration option to specify a + # hat to be used for a given apache virtualhost or "AAHatName" for + # a given apache directory or location directive. + # + # + # There is an example profile for phpsysinfo included in the + # apparmor-profiles package. To try it: + # + # 1- Install the phpsysinfo and the apparmor-profiles packages: + # sudo apt-get install phpsysinfo apparmor-profiles + # + # 2- Enable the main apache2 profile + # sudo aa-enforce /etc/apparmor.d/usr.sbin.apache2 + # + # 3- Configure apache with the following: + # + # AAHatName phpsysinfo + # + # + + #include + #include + + capability dac_override, + capability kill, + capability net_bind_service, + capability setgid, + capability setuid, + capability sys_tty_config, + + / rw, + /** mrwlkix, + + + ^DEFAULT_URI { + #include + #include + + / rw, + /** mrwlkix, + + } + + ^HANDLING_UNTRUSTED_INPUT { + #include + + / rw, + /** mrwlkix, + + } + + # This directory contains web application + # package-specific apparmor files. + + #include + + # Site-specific additions and overrides. See local/README for details. + #include +} From b70d3fe48e29d8c4d9564b3fd123b186f63d498d Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 17:23:56 -0800 Subject: [PATCH 166/183] Author: Jamie Strandboge Description: allow mmap of fglrx dri libraries Bug-Ubuntu: https://launchpad.net/bugs/1200392 Acked-by: Steve Beattie Came from 0038-lp1200392.patch. --- profiles/apparmor.d/abstractions/X | 1 + 1 file changed, 1 insertion(+) diff --git a/profiles/apparmor.d/abstractions/X b/profiles/apparmor.d/abstractions/X index f1c3e1cbb..89f829e55 100644 --- a/profiles/apparmor.d/abstractions/X +++ b/profiles/apparmor.d/abstractions/X @@ -35,6 +35,7 @@ # DRI /usr/lib{,32,64}/dri/** mr, /usr/lib/@{multiarch}/dri/** mr, + /usr/lib/fglrx/dri/** mr, /dev/dri/** rw, /etc/drirc r, owner @{HOME}/.drirc r, From 3ee30ca14cf107a389d36f7eeaa648cc9d37f89f Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 17:25:31 -0800 Subject: [PATCH 167/183] Description: Remove access to pulseaudio debug socket from audio abstraction Grant access to specific files in the /var/run/user/UID/pulse/ directory to remove access to potentially dangerous and non-essential files such as the debug (cli) socket provided by the module-cli-protocol-unix module. Author: Tyler Hicks Bug-Ubuntu: https://launchpad.net/bugs/1211380 Acked-by: Steve Beattie --- profiles/apparmor.d/abstractions/audio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiles/apparmor.d/abstractions/audio b/profiles/apparmor.d/abstractions/audio index ef9b4310c..e9643253e 100644 --- a/profiles/apparmor.d/abstractions/audio +++ b/profiles/apparmor.d/abstractions/audio @@ -56,7 +56,7 @@ owner @{HOME}/.pulse-cookie rwk, owner @{HOME}/.pulse/ rw, owner @{HOME}/.pulse/* rwk, owner /{,var/}run/user/*/pulse/ rw, -owner /{,var/}run/user/*/pulse/* rwk, +owner /{,var/}run/user/*/pulse/{native,pid} rwk, owner @{HOME}/.config/pulse/cookie rwk, owner /tmp/pulse-*/ rw, owner /tmp/pulse-*/* rw, From b432cf45c984595d4428c3bce31736b20dadeb7f Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 17:53:40 -0800 Subject: [PATCH 168/183] Add aa-easyprof and easyprof.py and related pieces from the Ubuntu apparmor packaging. These were originally 0030-easyprof-sdk.patch and 0037-easyprof-sdk-pt2.patch. Jamie posted an updated 0030-easyprof-sdk_v2.patch and I squashed both patches into one commit. Acked-By: Jamie Strandboge --- utils/aa-easyprof | 88 +- utils/aa-easyprof.pod | 171 ++- utils/apparmor/easyprof.py | 630 +++++++- utils/easyprof/policygroups/networking | 2 - utils/easyprof/templates/default | 2 +- utils/easyprof/templates/sandbox | 2 +- utils/easyprof/templates/sandbox-x | 2 +- utils/easyprof/templates/user-application | 2 +- utils/test/test-aa-easyprof.py | 1664 ++++++++++++++++++++- 9 files changed, 2456 insertions(+), 107 deletions(-) delete mode 100644 utils/easyprof/policygroups/networking diff --git a/utils/aa-easyprof b/utils/aa-easyprof index da0d1b869..ac69ca706 100755 --- a/utils/aa-easyprof +++ b/utils/aa-easyprof @@ -1,7 +1,7 @@ #! /usr/bin/env python # ------------------------------------------------------------------ # -# Copyright (C) 2011-2012 Canonical Ltd. +# Copyright (C) 2011-2013 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 @@ -22,6 +22,7 @@ if __name__ == "__main__": (opt, args) = apparmor.easyprof.parse_args() binary = None + manifest = None m = usage() if opt.show_policy_group and not opt.policy_groups: @@ -33,32 +34,65 @@ if __name__ == "__main__": if len(args) >= 1: binary = args[0] - try: - easyp = apparmor.easyprof.AppArmorEasyProfile(binary, opt) - except AppArmorException as e: - error(e.value) - except Exception: - raise + # parse_manifest() returns a list of tuples (binary, options). Create a + # list of these profile tuples to support multiple profiles in one manifest + profiles = [] + if opt.manifest: + try: + # should hide this in a common function + if sys.version_info[0] >= 3: + f = open(opt.manifest, "r", encoding="utf-8") + else: + f = open(opt.manifest, "r") + manifest = f.read() + except EnvironmentError as e: + error("Could not read '%s': %s (%d)\n" % (opt.manifest, + os.strerror(e.errno), + e.errno)) + profiles = apparmor.easyprof.parse_manifest(manifest, opt) + else: # fake up a tuple list when processing command line args + profiles.append( (binary, opt) ) - if opt.list_templates: - apparmor.easyprof.print_basefilenames(easyp.get_templates()) - sys.exit(0) - elif opt.template and opt.show_template: - files = [os.path.join(easyp.dirs['templates'], opt.template)] - apparmor.easyprof.print_files(files) - sys.exit(0) - elif opt.list_policy_groups: - apparmor.easyprof.print_basefilenames(easyp.get_policy_groups()) - sys.exit(0) - elif opt.policy_groups and opt.show_policy_group: - for g in opt.policy_groups.split(','): - files = [os.path.join(easyp.dirs['policygroups'], g)] + count = 0 + for (binary, options) in profiles: + if len(profiles) > 1: + count += 1 + try: + easyp = apparmor.easyprof.AppArmorEasyProfile(binary, options) + except AppArmorException as e: + error(e.value) + except Exception: + raise + + if options.list_templates: + apparmor.easyprof.print_basefilenames(easyp.get_templates()) + sys.exit(0) + elif options.template and options.show_template: + files = [os.path.join(easyp.dirs['templates'], options.template)] apparmor.easyprof.print_files(files) - sys.exit(0) - elif binary is None: - error("Must specify full path to binary\n%s" % m) + sys.exit(0) + elif options.list_policy_groups: + apparmor.easyprof.print_basefilenames(easyp.get_policy_groups()) + sys.exit(0) + elif options.policy_groups and options.show_policy_group: + for g in options.policy_groups.split(','): + files = [os.path.join(easyp.dirs['policygroups'], g)] + apparmor.easyprof.print_files(files) + sys.exit(0) + elif binary == None and not options.profile_name and \ + not options.manifest: + error("Must specify binary and/or profile name\n%s" % m) - # if we made it here, generate a profile - params = apparmor.easyprof.gen_policy_params(binary, opt) - p = easyp.gen_policy(**params) - sys.stdout.write('%s\n' % p) + params = apparmor.easyprof.gen_policy_params(binary, options) + if options.manifest and options.verify_manifest and \ + not apparmor.easyprof.verify_manifest(params): + error("Manifest file requires review") + + if options.output_format == "json": + sys.stdout.write('%s\n' % easyp.gen_manifest(params)) + else: + params['no_verify'] = options.no_verify + try: + easyp.output_policy(params, count, opt.output_directory) + except AppArmorException as e: + error(e) diff --git a/utils/aa-easyprof.pod b/utils/aa-easyprof.pod index e47b65237..486edead2 100644 --- a/utils/aa-easyprof.pod +++ b/utils/aa-easyprof.pod @@ -78,8 +78,15 @@ Like --read-path but also allow owner writes in additions to reads. =item -n NAME, --name=NAME Specify NAME of policy. If not specified, NAME is set to the name of the -binary. The NAME of the policy is often used as part of the path in the -various templates. +binary. The NAME of the policy is typically only used for profile meta +data and does not specify the AppArmor profile name. + +=item --profile-name=PROFILENAME + +Specify the AppArmor profile name. When set, uses 'profile PROFILENAME' in the +profile. When set and specifying a binary, uses 'profile PROFILENAME BINARY' +in the profile. If not set, the binary will be used as the profile name and +profile attachment. =item --template-var="@{VAR}=VALUE" @@ -110,6 +117,32 @@ Display policy groups specified with --policy. Use PATH instead of system policy-groups directory. +=item --policy-version=VERSION + +Must be used with --policy-vendor and is used to specify the version of policy +groups and templates. When specified, B looks for the subdirectory +VENDOR/VERSION within the policy-groups and templates directory. The specified +version must be a positive decimal number compatible with the JSON Number type. +Eg, when using: + +=over + + $ aa-easyprof --templates-dir=/usr/share/apparmor/easyprof/templates \ + --policy-groups-dir=/usr/share/apparmor/easyprof/policygroups \ + --policy-vendor="foo" \ + --policy-version=1.0 + +=back + +Then /usr/share/apparmor/easyprof/templates/foo/1.0 will be searched for +templates and /usr/share/apparmor/easyprof/policygroups/foo/1.0 for policy +groups. + +=item --policy-vendor=VENDOR + +Must be used with --policy-version and is used to specify the vendor for policy +groups and templates. See --policy-version for more information. + =item --author Specify author of the policy. @@ -122,6 +155,104 @@ Specify copyright of the policy. Specify comment for the policy. +=item -m MANIFEST, --manifest=MANIFEST + +B also supports using a JSON manifest file for specifying options +related to policy. Unlike command line arguments, the JSON file may specify +multiple profiles. The structure of the JSON is: + + { + "security": { + "profiles": { + "": { + ... attributes specific to this profile ... + }, + "": { + ... + } + } + } + } + +Each profile JSON object (ie, everything under a profile name) may specify any +fields related to policy. The "security" JSON container object is optional and +may be omitted. An example manifest file demonstrating all fields is: + + { + "security": { + "profiles": { + "com.example.foo": { + "abstractions": [ + "audio", + "gnome" + ], + "author": "Your Name", + "binary": "/opt/foo/**", + "comment": "Unstructured single-line comment", + "copyright": "Unstructured single-line copyright statement", + "name": "My Foo App", + "policy_groups": [ + "networking", + "user-application" + ], + "policy_vendor": "somevendor", + "policy_version": 1.0, + "read_path": [ + "/tmp/foo_r", + "/tmp/bar_r/" + ], + "template": "user-application", + "template_variables": { + "APPNAME": "foo", + "VAR1": "bar", + "VAR2": "baz" + }, + "write_path": [ + "/tmp/foo_w", + "/tmp/bar_w/" + ] + } + } + } + } + +A manifest file does not have to include all the fields. Eg, a manifest file +for an Ubuntu SDK application might be: + + { + "security": { + "profiles": { + "com.ubuntu.developer.myusername.MyCoolApp": { + "policy_groups": [ + "networking", + "online-accounts" + ], + "policy_vendor": "ubuntu", + "policy_version": 1.0, + "template": "ubuntu-sdk", + "template_variables": { + "APPNAME": "MyCoolApp", + "APPVERSION": "0.1.2" + } + } + } + } + } + +=item --verify-manifest + +When used with --manifest, warn about potentially unsafe definitions in the +manifest file. + +=item --output-format=FORMAT + +Specify either B (default if unspecified) for AppArmor policy output or +B for JSON manifest format. + +=item --output-directory=DIR + +Specify output directory for profile. If unspecified, policy is sent to stdout. + =back =head1 EXAMPLE @@ -130,7 +261,41 @@ Example usage for a program named 'foo' which is installed in /opt/foo: =over -$ aa-easyprof --template=user-application --template-var="@{APPNAME}=foo" --policy-groups=opt-application,user-application /opt/foo/bin/FooApp + $ aa-easyprof --template=user-application --template-var="@{APPNAME}=foo" \ + --policy-groups=opt-application,user-application \ + /opt/foo/bin/FooApp + +=back + +When using a manifest file: + +=over + + $ aa-easyprof --manifest=manifest.json + +=back + +To output a manifest file based on aa-easyprof arguments: + +=over + + $ aa-easyprof --output-format=json \ + --author="Your Name" \ + --comment="Unstructured single-line comment" \ + --copyright="Unstructured single-line copyright statement" \ + --name="My Foo App" \ + --profile-name="com.example.foo" \ + --template="user-application" \ + --policy-groups="user-application,networking" \ + --abstractions="audio,gnome" \ + --read-path="/tmp/foo_r" \ + --read-path="/tmp/bar_r/" \ + --write-path="/tmp/foo_w" \ + --write-path=/tmp/bar_w/ \ + --template-var="@{APPNAME}=foo" \ + --template-var="@{VAR1}=bar" \ + --template-var="@{VAR2}=baz" \ + "/opt/foo/**" =back diff --git a/utils/apparmor/easyprof.py b/utils/apparmor/easyprof.py index 035edf502..38b9bb0d5 100644 --- a/utils/apparmor/easyprof.py +++ b/utils/apparmor/easyprof.py @@ -1,6 +1,6 @@ # ------------------------------------------------------------------ # -# Copyright (C) 2011-2012 Canonical Ltd. +# Copyright (C) 2011-2013 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 @@ -11,10 +11,13 @@ from __future__ import with_statement import codecs +import copy import glob +import json import optparse import os import re +import shutil import subprocess import sys import tempfile @@ -123,29 +126,117 @@ def valid_binary_path(path): return True -def valid_variable_name(var): +def valid_variable(v): '''Validate variable name''' - if re.search(r'[a-zA-Z0-9_]+$', var): + debug("Checking '%s'" % v) + try: + (key, value) = v.split('=') + except Exception: + return False + + if not re.search(r'^@\{[a-zA-Z0-9_]+\}$', key): + return False + + if '/' in value: + rel_ok = False + if not value.startswith('/'): + rel_ok = True + if not valid_path(value, relative_ok=rel_ok): + return False + + if '"' in value: + return False + + # If we made it here, we are safe + return True + + +def valid_path(path, relative_ok=False): + '''Valid path''' + m = "Invalid path: %s" % (path) + if not relative_ok and not path.startswith('/'): + debug("%s (relative)" % (m)) + return False + + if '"' in path: # We double quote elsewhere + debug("%s (quote)" % (m)) + return False + + if '../' in path: + debug("%s (../ path escape)" % (m)) + return False + + try: + p = os.path.normpath(path) + except Exception: + debug("%s (could not normalize)" % (m)) + return False + + if p != path: + debug("%s (normalized path != path (%s != %s))" % (m, p, path)) + return False + + # If we made it here, we are safe + return True + + +def _is_safe(s): + '''Known safe regex''' + if re.search(r'^[a-zA-Z_0-9\-\.]+$', s): return True return False -def valid_path(path): - '''Valid path''' - # No relative paths - m = "Invalid path: %s" % (path) - if not path.startswith('/'): - debug("%s (relative)" % (m)) - return False +def valid_policy_vendor(s): + '''Verify the policy vendor''' + return _is_safe(s) + +def valid_policy_version(v): + '''Verify the policy version''' try: - os.path.normpath(path) - except Exception: - debug("%s (could not normalize)" % (m)) + float(v) + except ValueError: + return False + if float(v) < 0: return False return True +def valid_template_name(s, strict=False): + '''Verify the template name''' + if not strict and s.startswith('/'): + if not valid_path(s): + return False + return True + return _is_safe(s) + + +def valid_abstraction_name(s): + '''Verify the template name''' + return _is_safe(s) + + +def valid_profile_name(s): + '''Verify the profile name''' + # profile name specifies path + if s.startswith('/'): + if not valid_path(s): + return False + return True + + # profile name does not specify path + # alpha-numeric and Debian version, plus '_' + if re.search(r'^[a-zA-Z0-9][a-zA-Z0-9_\+\-\.:~]+$', s): + return True + return False + + +def valid_policy_group_name(s): + '''Verify policy group name''' + return _is_safe(s) + + def get_directory_contents(path): '''Find contents of the given directory''' if not valid_path(path): @@ -202,6 +293,7 @@ def verify_policy(policy): class AppArmorEasyProfile: '''Easy profile class''' def __init__(self, binary, opt): + verify_options(opt) opt.ensure_value("conffile", "/etc/apparmor/easyprof.conf") self.conffile = os.path.abspath(opt.conffile) @@ -222,6 +314,25 @@ class AppArmorEasyProfile: if opt.policy_groups_dir and os.path.isdir(opt.policy_groups_dir): self.dirs['policygroups'] = os.path.abspath(opt.policy_groups_dir) + + self.policy_version = None + self.policy_vendor = None + if (opt.policy_version and not opt.policy_vendor) or \ + (opt.policy_vendor and not opt.policy_version): + raise AppArmorException("Must specify both policy version and vendor") + if opt.policy_version and opt.policy_vendor: + self.policy_vendor = opt.policy_vendor + self.policy_version = str(opt.policy_version) + + for i in ['templates', 'policygroups']: + d = os.path.join(self.dirs[i], \ + self.policy_vendor, \ + self.policy_version) + if not os.path.isdir(d): + raise AppArmorException( + "Could not find %s directory '%s'" % (i, d)) + self.dirs[i] = d + if not 'templates' in self.dirs: raise AppArmorException("Could not find templates directory") if not 'policygroups' in self.dirs: @@ -230,19 +341,29 @@ class AppArmorEasyProfile: self.aa_topdir = "/etc/apparmor.d" self.binary = binary - if binary != None: + if binary: if not valid_binary_path(binary): raise AppArmorException("Invalid path for binary: '%s'" % binary) - self.set_template(opt.template) + if opt.manifest: + self.set_template(opt.template, allow_abs_path=False) + else: + self.set_template(opt.template) + self.set_policygroup(opt.policy_groups) if opt.name: self.set_name(opt.name) elif self.binary != None: self.set_name(self.binary) - self.templates = get_directory_contents(self.dirs['templates']) - self.policy_groups = get_directory_contents(self.dirs['policygroups']) + self.templates = [] + for f in get_directory_contents(self.dirs['templates']): + if os.path.isfile(f): + self.templates.append(f) + self.policy_groups = [] + for f in get_directory_contents(self.dirs['policygroups']): + if os.path.isfile(f): + self.policy_groups.append(f) def _get_defaults(self): '''Read in defaults from configuration''' @@ -282,11 +403,18 @@ class AppArmorEasyProfile: '''Get contents of current template''' return open(self.template).read() - def set_template(self, template): + def set_template(self, template, allow_abs_path=True): '''Set current template''' - self.template = template - if not template.startswith('/'): + if "../" in template: + raise AppArmorException('template "%s" contains "../" escape path' % (template)) + elif template.startswith('/') and not allow_abs_path: + raise AppArmorException("Cannot use an absolute path template '%s'" % template) + + if template.startswith('/'): + self.template = template + else: self.template = os.path.join(self.dirs['templates'], template) + if not os.path.exists(self.template): raise AppArmorException('%s does not exist' % (self.template)) @@ -327,9 +455,11 @@ class AppArmorEasyProfile: def gen_variable_declaration(self, dec): '''Generate a variable declaration''' - if not re.search(r'^@\{[a-zA-Z_]+\}=.+', dec): + if not valid_variable(dec): raise AppArmorException("Invalid variable declaration '%s'" % dec) - return dec + # Make sure we always quote + k, v = dec.split('=') + return '%s="%s"' % (k, v) def gen_path_rule(self, path, access): rule = [] @@ -352,7 +482,18 @@ class AppArmorEasyProfile: return rule - def gen_policy(self, name, binary, template_var=[], abstractions=None, policy_groups=None, read_path=[], write_path=[], author=None, comment=None, copyright=None): + def gen_policy(self, name, + binary=None, + profile_name=None, + template_var=[], + abstractions=None, + policy_groups=None, + read_path=[], + write_path=[], + author=None, + comment=None, + copyright=None, + no_verify=False): def find_prefix(t, s): '''Calculate whitespace prefix based on occurrence of s in t''' pat = re.compile(r'^ *%s' % s) @@ -375,12 +516,22 @@ class AppArmorEasyProfile: tmp += line + "\n" policy = tmp - # Fill-in profile name and binary - policy = re.sub(r'###NAME###', name, policy) - if binary.startswith('/'): - policy = re.sub(r'###BINARY###', binary, policy) + attachment = "" + if binary: + if not valid_binary_path(binary): + raise AppArmorException("Invalid path for binary: '%s'" % \ + binary) + if profile_name: + attachment = 'profile "%s" "%s"' % (profile_name, binary) + else: + attachment = '"%s"' % binary + elif profile_name: + attachment = 'profile "%s"' % profile_name else: - policy = re.sub(r'###BINARY###', "profile %s" % binary, policy) + raise AppArmorException("Must specify binary and/or profile name") + policy = re.sub(r'###PROFILEATTACH###', attachment, policy) + + policy = re.sub(r'###NAME###', name, policy) # Fill-in various comment fields if comment != None: @@ -398,7 +549,9 @@ class AppArmorEasyProfile: s = "%s# No abstractions specified" % prefix if abstractions != None: s = "%s# Specified abstractions" % (prefix) - for i in abstractions.split(','): + t = abstractions.split(',') + t.sort() + for i in t: s += "\n%s%s" % (prefix, self.gen_abstraction_rule(i)) policy = re.sub(r' *%s' % search, s, policy) @@ -407,7 +560,9 @@ class AppArmorEasyProfile: s = "%s# No policy groups specified" % prefix if policy_groups != None: s = "%s# Rules specified via policy groups" % (prefix) - for i in policy_groups.split(','): + t = policy_groups.split(',') + t.sort() + for i in t: for line in self.get_policygroup(i).splitlines(): s += "\n%s%s" % (prefix, line) if i != policy_groups.split(',')[-1]: @@ -419,6 +574,7 @@ class AppArmorEasyProfile: s = "%s# No template variables specified" % prefix if len(template_var) > 0: s = "%s# Specified profile variables" % (prefix) + template_var.sort() for i in template_var: s += "\n%s%s" % (prefix, self.gen_variable_declaration(i)) policy = re.sub(r' *%s' % search, s, policy) @@ -428,8 +584,9 @@ class AppArmorEasyProfile: s = "%s# No read paths specified" % prefix if len(read_path) > 0: s = "%s# Specified read permissions" % (prefix) + read_path.sort() for i in read_path: - for r in self.gen_path_rule(i, 'r'): + for r in self.gen_path_rule(i, 'rk'): s += "\n%s%s" % (prefix, r) policy = re.sub(r' *%s' % search, s, policy) @@ -438,17 +595,110 @@ class AppArmorEasyProfile: s = "%s# No write paths specified" % prefix if len(write_path) > 0: s = "%s# Specified write permissions" % (prefix) + write_path.sort() for i in write_path: for r in self.gen_path_rule(i, 'rwk'): s += "\n%s%s" % (prefix, r) policy = re.sub(r' *%s' % search, s, policy) - if not verify_policy(policy): - debug("\n" + policy) + if no_verify: + debug("Skipping policy verification") + elif not verify_policy(policy): + msg("\n" + policy) raise AppArmorException("Invalid policy") return policy + def output_policy(self, params, count=0, dir=None): + '''Output policy''' + policy = self.gen_policy(**params) + if not dir: + if count: + sys.stdout.write('### aa-easyprof profile #%d ###\n' % count) + sys.stdout.write('%s\n' % policy) + else: + out_fn = "" + if 'profile_name' in params: + out_fn = params['profile_name'] + elif 'binary' in params: + out_fn = params['binary'] + else: # should not ever reach this + raise AppArmorException("Could not determine output filename") + + # Generate an absolute path, convertng any path delimiters to '.' + out_fn = os.path.join(dir, re.sub(r'/', '.', out_fn.lstrip('/'))) + if os.path.exists(out_fn): + raise AppArmorException("'%s' already exists" % out_fn) + + if not os.path.exists(dir): + os.mkdir(dir) + + if not os.path.isdir(dir): + raise AppArmorException("'%s' is not a directory" % dir) + + f, fn = tempfile.mkstemp(prefix='aa-easyprof') + if not isinstance(policy, bytes): + policy = policy.encode('utf-8') + os.write(f, policy) + os.close(f) + + shutil.move(fn, out_fn) + + def gen_manifest(self, params): + '''Take params list and output a JSON file''' + d = dict() + d['security'] = dict() + d['security']['profiles'] = dict() + + pkey = "" + if 'profile_name' in params: + pkey = params['profile_name'] + elif 'binary' in params: + # when profile_name is not specified, the binary (path attachment) + # also functions as the profile name + pkey = params['binary'] + else: + raise AppArmorException("Must supply binary or profile name") + + d['security']['profiles'][pkey] = dict() + + # Add the template since it isn't part of 'params' + template = os.path.basename(self.template) + if template != 'default': + d['security']['profiles'][pkey]['template'] = template + + # Add the policy_version since it isn't part of 'params' + if self.policy_version: + d['security']['profiles'][pkey]['policy_version'] = float(self.policy_version) + if self.policy_vendor: + d['security']['profiles'][pkey]['policy_vendor'] = self.policy_vendor + + for key in params: + if key == 'profile_name' or \ + (key == 'binary' and not 'profile_name' in params): + continue # don't re-add the pkey + elif key == 'binary' and not params[key]: + continue # binary can by None when specifying --profile-name + elif key == 'template_var': + d['security']['profiles'][pkey]['template_variables'] = dict() + for tvar in params[key]: + if not self.gen_variable_declaration(tvar): + raise AppArmorException("Malformed template_var '%s'" % tvar) + (k, v) = tvar.split('=') + k = k.lstrip('@').lstrip('{').rstrip('}') + d['security']['profiles'][pkey]['template_variables'][k] = v + elif key == 'abstractions' or key == 'policy_groups': + d['security']['profiles'][pkey][key] = params[key].split(",") + d['security']['profiles'][pkey][key].sort() + else: + d['security']['profiles'][pkey][key] = params[key] + json_str = json.dumps(d, + sort_keys=True, + indent=2, + separators=(',', ': ') + ) + return json_str + def print_basefilenames(files): for i in files: sys.stdout.write("%s\n" % (os.path.basename(i))) @@ -458,22 +708,65 @@ def print_files(files): with open(i) as f: sys.stdout.write(f.read()+"\n") +def check_manifest_conflict_args(option, opt_str, value, parser): + '''Check for -m/--manifest with conflicting args''' + conflict_args = ['abstractions', + 'read_path', + 'write_path', + # template always get set to 'default', can't conflict + # 'template', + 'policy_groups', + 'policy_version', + 'policy_vendor', + 'name', + 'profile_name', + 'comment', + 'copyright', + 'author', + 'template_var'] + for conflict in conflict_args: + if getattr(parser.values, conflict, False): + raise optparse.OptionValueError("can't use --%s with --manifest " \ + "argument" % conflict) + setattr(parser.values, option.dest, value) + +def check_for_manifest_arg(option, opt_str, value, parser): + '''Check for -m/--manifest with conflicting args''' + if parser.values.manifest: + raise optparse.OptionValueError("can't use --%s with --manifest " \ + "argument" % opt_str.lstrip('-')) + setattr(parser.values, option.dest, value) + +def check_for_manifest_arg_append(option, opt_str, value, parser): + '''Check for -m/--manifest with conflicting args (with append)''' + if parser.values.manifest: + raise optparse.OptionValueError("can't use --%s with --manifest " \ + "argument" % opt_str.lstrip('-')) + parser.values.ensure_value(option.dest, []).append(value) + def add_parser_policy_args(parser): '''Add parser arguments''' parser.add_option("-a", "--abstractions", + action="callback", + callback=check_for_manifest_arg, + type=str, dest="abstractions", help="Comma-separated list of abstractions", metavar="ABSTRACTIONS") parser.add_option("--read-path", + action="callback", + callback=check_for_manifest_arg_append, + type=str, dest="read_path", help="Path allowing owner reads", - metavar="PATH", - action="append") + metavar="PATH") parser.add_option("--write-path", + action="callback", + callback=check_for_manifest_arg_append, + type=str, dest="write_path", help="Path allowing owner writes", - metavar="PATH", - action="append") + metavar="PATH") parser.add_option("-t", "--template", dest="template", help="Use non-default policy template", @@ -484,12 +777,36 @@ def add_parser_policy_args(parser): help="Use non-default templates directory", metavar="DIR") parser.add_option("-p", "--policy-groups", + action="callback", + callback=check_for_manifest_arg, + type=str, help="Comma-separated list of policy groups", metavar="POLICYGROUPS") parser.add_option("--policy-groups-dir", dest="policy_groups_dir", help="Use non-default policy-groups directory", metavar="DIR") + parser.add_option("--policy-version", + action="callback", + callback=check_for_manifest_arg, + type=str, + dest="policy_version", + help="Specify version for templates and policy groups", + metavar="VERSION") + parser.add_option("--policy-vendor", + action="callback", + callback=check_for_manifest_arg, + type=str, + dest="policy_vendor", + help="Specify vendor for templates and policy groups", + metavar="VENDOR") + parser.add_option("--profile-name", + action="callback", + callback=check_for_manifest_arg, + type=str, + dest="profile_name", + help="AppArmor profile name", + metavar="PROFILENAME") def parse_args(args=None, parser=None): '''Parse arguments''' @@ -506,6 +823,10 @@ def parse_args(args=None, parser=None): help="Show debugging output", action='store_true', default=False) + parser.add_option("--no-verify", + help="Don't verify policy using 'apparmor_parser -p'", + action='store_true', + default=False) parser.add_option("--list-templates", help="List available templates", action='store_true', @@ -523,31 +844,72 @@ def parse_args(args=None, parser=None): action='store_true', default=False) parser.add_option("-n", "--name", + action="callback", + callback=check_for_manifest_arg, + type=str, dest="name", - help="Name of policy", - metavar="NAME") + help="Name of policy (not AppArmor profile name)", + metavar="COMMENT") parser.add_option("--comment", + action="callback", + callback=check_for_manifest_arg, + type=str, dest="comment", help="Comment for policy", metavar="COMMENT") parser.add_option("--author", + action="callback", + callback=check_for_manifest_arg, + type=str, dest="author", help="Author of policy", metavar="COMMENT") parser.add_option("--copyright", + action="callback", + callback=check_for_manifest_arg, + type=str, dest="copyright", help="Copyright for policy", metavar="COMMENT") parser.add_option("--template-var", + action="callback", + callback=check_for_manifest_arg_append, + type=str, dest="template_var", help="Declare AppArmor variable", - metavar="@{VARIABLE}=VALUE", - action="append") + metavar="@{VARIABLE}=VALUE") + parser.add_option("--output-format", + action="store", + dest="output_format", + help="Specify output format as text (default) or json", + metavar="FORMAT", + default="text") + parser.add_option("--output-directory", + action="store", + dest="output_directory", + help="Output policy to this directory", + metavar="DIR") + # This option conflicts with any of the value arguments, e.g. name, + # author, template-var, etc. + parser.add_option("-m", "--manifest", + action="callback", + callback=check_manifest_conflict_args, + type=str, + dest="manifest", + help="JSON manifest file", + metavar="FILE") + parser.add_option("--verify-manifest", + action="store_true", + default=False, + dest="verify_manifest", + help="Verify JSON manifest file") + # add policy args now add_parser_policy_args(parser) (my_opt, my_args) = parser.parse_args(args) + if my_opt.debug: DEBUGGING = True return (my_opt, my_args) @@ -555,10 +917,21 @@ def parse_args(args=None, parser=None): def gen_policy_params(binary, opt): '''Generate parameters for gen_policy''' params = dict(binary=binary) + + if not binary and not opt.profile_name: + raise AppArmorException("Must specify binary and/or profile name") + + if opt.profile_name: + params['profile_name'] = opt.profile_name + if opt.name: params['name'] = opt.name else: - params['name'] = os.path.basename(binary) + if opt.profile_name: + params['name'] = opt.profile_name + elif binary: + params['name'] = os.path.basename(binary) + if opt.template_var: # What about specified multiple times? params['template_var'] = opt.template_var if opt.abstractions: @@ -569,14 +942,183 @@ def gen_policy_params(binary, opt): params['read_path'] = opt.read_path if opt.write_path: params['write_path'] = opt.write_path - if opt.abstractions: - params['abstractions'] = opt.abstractions if opt.comment: params['comment'] = opt.comment if opt.author: params['author'] = opt.author if opt.copyright: params['copyright'] = opt.copyright + if opt.policy_version and opt.output_format == "json": + params['policy_version'] = opt.policy_version + if opt.policy_vendor and opt.output_format == "json": + params['policy_vendor'] = opt.policy_vendor return params +def parse_manifest(manifest, opt_orig): + '''Take a JSON manifest as a string and updates options, returning an + updated binary. Note that a JSON file may contain multiple profiles.''' + + try: + m = json.loads(manifest) + except ValueError: + raise AppArmorException("Could not parse manifest") + + if 'security' in m: + top_table = m['security'] + else: + top_table = m + + if 'profiles' not in top_table: + raise AppArmorException("Could not parse manifest (could not find 'profiles')") + table = top_table['profiles'] + + # generally mirrors what is settable in gen_policy_params() + valid_keys = ['abstractions', + 'author', + 'binary', + 'comment', + 'copyright', + 'name', + 'policy_groups', + 'policy_version', + 'policy_vendor', + 'profile_name', + 'read_path', + 'template', + 'template_variables', + 'write_path', + ] + + profiles = [] + + for profile_name in table: + if not isinstance(table[profile_name], dict): + raise AppArmorException("Wrong JSON structure") + opt = copy.deepcopy(opt_orig) + + # The JSON structure is: + # { + # "security": { + # : { + # "binary": ... + # ... + # but because binary can be the profile name, we need to handle + # 'profile_name' and 'binary' special. If a profile_name starts with + # '/', then it is considered the binary. Otherwise, set the + # profile_name and set the binary if it is in the JSON. + binary = None + if profile_name.startswith('/'): + if 'binary' in table[profile_name]: + raise AppArmorException("Profile name should not specify path with binary") + binary = profile_name + else: + setattr(opt, 'profile_name', profile_name) + if 'binary' in table[profile_name]: + binary = table[profile_name]['binary'] + setattr(opt, 'binary', binary) + + for key in table[profile_name]: + if key not in valid_keys: + raise AppArmorException("Invalid key '%s'" % key) + + if key == 'binary': + continue # handled above + elif key == 'abstractions' or key == 'policy_groups': + setattr(opt, key, ",".join(table[profile_name][key])) + elif key == "template_variables": + t = table[profile_name]['template_variables'] + vlist = [] + for v in t.keys(): + vlist.append("@{%s}=%s" % (v, t[v])) + setattr(opt, 'template_var', vlist) + else: + if hasattr(opt, key): + setattr(opt, key, table[profile_name][key]) + + profiles.append( (binary, opt) ) + + return profiles + + +def verify_options(opt, strict=False): + '''Make sure our options are valid''' + if hasattr(opt, 'binary') and opt.binary and not valid_path(opt.binary): + raise AppArmorException("Invalid binary '%s'" % opt.binary) + if hasattr(opt, 'profile_name') and opt.profile_name != None and \ + not valid_profile_name(opt.profile_name): + raise AppArmorException("Invalid profile name '%s'" % opt.profile_name) + if hasattr(opt, 'binary') and opt.binary and \ + hasattr(opt, 'profile_name') and opt.profile_name != None and \ + opt.profile_name.startswith('/'): + raise AppArmorException("Profile name should not specify path with binary") + if hasattr(opt, 'policy_vendor') and opt.policy_vendor and \ + not valid_policy_vendor(opt.policy_vendor): + raise AppArmorException("Invalid policy vendor '%s'" % \ + opt.policy_vendor) + if hasattr(opt, 'policy_version') and opt.policy_version and \ + not valid_policy_version(opt.policy_version): + raise AppArmorException("Invalid policy version '%s'" % \ + opt.policy_version) + if hasattr(opt, 'template') and opt.template and \ + not valid_template_name(opt.template, strict): + raise AppArmorException("Invalid template '%s'" % opt.template) + if hasattr(opt, 'template_var') and opt.template_var: + for i in opt.template_var: + if not valid_variable(i): + raise AppArmorException("Invalid variable '%s'" % i) + if hasattr(opt, 'policy_groups') and opt.policy_groups: + for i in opt.policy_groups.split(','): + if not valid_policy_group_name(i): + raise AppArmorException("Invalid policy group '%s'" % i) + if hasattr(opt, 'abstractions') and opt.abstractions: + for i in opt.abstractions.split(','): + if not valid_abstraction_name(i): + raise AppArmorException("Invalid abstraction '%s'" % i) + if hasattr(opt, 'read_paths') and opt.read_paths: + for i in opt.read_paths: + if not valid_path(i): + raise AppArmorException("Invalid read path '%s'" % i) + if hasattr(opt, 'write_paths') and opt.write_paths: + for i in opt.write_paths: + if not valid_path(i): + raise AppArmorException("Invalid write path '%s'" % i) + + +def verify_manifest(params): + '''Verify manifest for safe and unsafe options''' + err_str = "" + (opt, args) = parse_args() + fake_easyp = AppArmorEasyProfile(None, opt) + + unsafe_keys = ['read_path', 'write_path'] + safe_abstractions = ['base'] + for k in params: + debug("Examining %s=%s" % (k, params[k])) + if k in unsafe_keys: + err_str += "\nfound %s key" % k + elif k == 'profile_name': + if params['profile_name'].startswith('/') or \ + '*' in params['profile_name']: + err_str += "\nprofile_name '%s'" % params['profile_name'] + elif k == 'abstractions': + for a in params['abstractions'].split(','): + if not a in safe_abstractions: + err_str += "\nfound '%s' abstraction" % a + elif k == "template_var": + pat = re.compile(r'[*/\{\}\[\]]') + for tv in params['template_var']: + if not fake_easyp.gen_variable_declaration(tv): + err_str += "\n%s" % tv + continue + tv_val = tv.split('=')[1] + debug("Examining %s" % tv_val) + if '..' in tv_val or pat.search(tv_val): + err_str += "\n%s" % tv + + if err_str: + warn("Manifest definition is potentially unsafe%s" % err_str) + return False + + return True + diff --git a/utils/easyprof/policygroups/networking b/utils/easyprof/policygroups/networking deleted file mode 100644 index c60a4ede2..000000000 --- a/utils/easyprof/policygroups/networking +++ /dev/null @@ -1,2 +0,0 @@ -# Policygroup to allow networking -#include diff --git a/utils/easyprof/templates/default b/utils/easyprof/templates/default index c6d0be497..d19483f21 100644 --- a/utils/easyprof/templates/default +++ b/utils/easyprof/templates/default @@ -13,7 +13,7 @@ ###VAR### -###BINARY### { +###PROFILEATTACH### { #include ###ABSTRACTIONS### diff --git a/utils/easyprof/templates/sandbox b/utils/easyprof/templates/sandbox index acc81f97c..80dfbfc13 100644 --- a/utils/easyprof/templates/sandbox +++ b/utils/easyprof/templates/sandbox @@ -13,7 +13,7 @@ ###VAR### -###BINARY### { +###PROFILEATTACH### { #include / r, /**/ r, diff --git a/utils/easyprof/templates/sandbox-x b/utils/easyprof/templates/sandbox-x index 077cb6049..45fe09830 100644 --- a/utils/easyprof/templates/sandbox-x +++ b/utils/easyprof/templates/sandbox-x @@ -13,7 +13,7 @@ ###VAR### -###BINARY### { +###PROFILEATTACH### { #include #include #include diff --git a/utils/easyprof/templates/user-application b/utils/easyprof/templates/user-application index 766da425e..dd5f3266f 100644 --- a/utils/easyprof/templates/user-application +++ b/utils/easyprof/templates/user-application @@ -16,7 +16,7 @@ ###VAR### -###BINARY### { +###PROFILEATTACH### { #include ###ABSTRACTIONS### diff --git a/utils/test/test-aa-easyprof.py b/utils/test/test-aa-easyprof.py index e59c13860..5607ab7fd 100755 --- a/utils/test/test-aa-easyprof.py +++ b/utils/test/test-aa-easyprof.py @@ -1,7 +1,7 @@ #! /usr/bin/env python # ------------------------------------------------------------------ # -# Copyright (C) 2011-2012 Canonical Ltd. +# Copyright (C) 2011-2013 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 @@ -10,6 +10,8 @@ # ------------------------------------------------------------------ import glob +import json +import optparse import os import shutil import sys @@ -31,6 +33,69 @@ def recursive_rm(dirPath, contents_only=False): if contents_only == False: os.rmdir(dirPath) +# From Lib/test/test_optparse.py from python 2.7.4 +class InterceptedError(Exception): + def __init__(self, + error_message=None, + exit_status=None, + exit_message=None): + self.error_message = error_message + self.exit_status = exit_status + self.exit_message = exit_message + + def __str__(self): + return self.error_message or self.exit_message or "intercepted error" + + +class InterceptingOptionParser(optparse.OptionParser): + def exit(self, status=0, msg=None): + raise InterceptedError(exit_status=status, exit_message=msg) + + def error(self, msg): + raise InterceptedError(error_message=msg) + + +class Manifest(object): + def __init__(self, profile_name): + self.security = dict() + self.security['profiles'] = dict() + self.profile_name = profile_name + self.security['profiles'][self.profile_name] = dict() + + def add_policygroups(self, policy_list): + self.security['profiles'][self.profile_name]['policy_groups'] = policy_list.split(",") + + def add_author(self, author): + self.security['profiles'][self.profile_name]['author'] = author + + def add_copyright(self, copyright): + self.security['profiles'][self.profile_name]['copyright'] = copyright + + def add_comment(self, comment): + self.security['profiles'][self.profile_name]['comment'] = comment + + def add_binary(self, binary): + self.security['profiles'][self.profile_name]['binary'] = binary + + def add_template(self, template): + self.security['profiles'][self.profile_name]['template'] = template + + def add_template_variable(self, name, value): + if not 'template_variables' in self.security['profiles'][self.profile_name]: + self.security['profiles'][self.profile_name]['template_variables'] = dict() + + self.security['profiles'][self.profile_name]['template_variables'][name] = value + + def emit_json(self, use_security_prefix=True): + manifest = dict() + manifest['security'] = self.security + if use_security_prefix: + dumpee = manifest + else: + dumpee = self.security + + return json.dumps(dumpee, indent=2) + # # Our test class # @@ -58,7 +123,7 @@ class T(unittest.TestCase): ###VAR### -###BINARY### { +###PROFILEATTACH### { #include ###ABSTRACTIONS### @@ -101,7 +166,8 @@ TEMPLATES_DIR="%s/templates" def tearDown(self): '''Teardown for tests''' if os.path.exists(self.tmpdir): - sys.stdout.write("%s\n" % self.tmpdir) + if debugging: + sys.stdout.write("%s\n" % self.tmpdir) recursive_rm(self.tmpdir) # @@ -206,6 +272,30 @@ TEMPLATES_DIR="%s/templates" self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir") self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") + def test_policygroups_dir_valid_with_vendor(self): + '''Test --policy-groups-dir (valid DIR with vendor)''' + os.chdir(self.tmpdir) + valid = os.path.join(self.tmpdir, 'valid') + os.mkdir(valid) + shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), os.path.join(valid, self.test_policygroup)) + + vendor = "ubuntu" + version = "1.0" + valid_distro = os.path.join(valid, vendor, version) + os.mkdir(os.path.join(valid, vendor)) + os.mkdir(valid_distro) + shutil.copy(os.path.join(self.tmpdir, 'policygroups', self.test_policygroup), valid_distro) + + args = self.full_args + args += ['--policy-groups-dir', valid, '--show-policy-group', '--policy-groups=%s' % self.test_policygroup] + (self.options, self.args) = easyprof.parse_args(args) + easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) + + self.assertTrue(easyp.dirs['policygroups'] == valid, "Not using specified --policy-groups-dir") + self.assertFalse(easyp.get_policy_groups() == None, "Could not find policy-groups") + for f in easyp.get_policy_groups(): + self.assertFalse(os.path.basename(f) == vendor, "Found '%s' in %s" % (vendor, f)) + def test_configuration_file_t_invalid(self): '''Test config parsing (invalid TEMPLATES_DIR)''' contents = ''' @@ -305,13 +395,51 @@ POLICYGROUPS_DIR="%s/templates" self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir") self.assertFalse(easyp.get_templates() == None, "Could not find templates") + def test_templates_dir_valid_with_vendor(self): + '''Test --templates-dir (valid DIR with vendor)''' + os.chdir(self.tmpdir) + valid = os.path.join(self.tmpdir, 'valid') + os.mkdir(valid) + shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), os.path.join(valid, self.test_template)) + + vendor = "ubuntu" + version = "1.0" + valid_distro = os.path.join(valid, vendor, version) + os.mkdir(os.path.join(valid, vendor)) + os.mkdir(valid_distro) + shutil.copy(os.path.join(self.tmpdir, 'templates', self.test_template), valid_distro) + + args = self.full_args + args += ['--templates-dir', valid, '--show-template', '--template=%s' % self.test_template] + (self.options, self.args) = easyprof.parse_args(args) + easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) + + self.assertTrue(easyp.dirs['templates'] == valid, "Not using specified --template-dir") + self.assertFalse(easyp.get_templates() == None, "Could not find templates") + for f in easyp.get_templates(): + self.assertFalse(os.path.basename(f) == vendor, "Found '%s' in %s" % (vendor, f)) + # # Binary file tests # - def test_binary(self): - '''Test binary''' + def test_binary_without_profile_name(self): + '''Test binary ( { })''' easyprof.AppArmorEasyProfile('/bin/ls', self.options) + def test_binary_with_profile_name(self): + '''Test binary (profile { })''' + args = self.full_args + args += ['--profile-name=some-profile-name'] + (self.options, self.args) = easyprof.parse_args(args) + easyprof.AppArmorEasyProfile('/bin/ls', self.options) + + def test_binary_omitted_with_profile_name(self): + '''Test binary (profile { })''' + args = self.full_args + args += ['--profile-name=some-profile-name'] + (self.options, self.args) = easyprof.parse_args(args) + easyprof.AppArmorEasyProfile(None, self.options) + def test_binary_nonexistent(self): '''Test binary (nonexistent)''' easyprof.AppArmorEasyProfile(os.path.join(self.tmpdir, 'nonexistent'), self.options) @@ -399,9 +527,109 @@ POLICYGROUPS_DIR="%s/templates" self.assertTrue(os.path.exists(path), "Could not find '%s'" % path) open(path).read() +# +# Manifest file argument tests +# + def test_manifest_argument(self): + '''Test manifest argument''' + + # setup our manifest + self.manifest = os.path.join(self.tmpdir, 'manifest.json') + contents = ''' +{"security": {"domain.reverse.appname": {"name": "simple-app"}}} +''' + open(self.manifest, 'w').write(contents) + + args = self.full_args + args.extend(['--manifest', self.manifest]) + easyprof.parse_args(args) + + def _manifest_conflicts(self, opt, value): + '''Helper for conflicts tests''' + # setup our manifest + self.manifest = os.path.join(self.tmpdir, 'manifest.json') + contents = ''' +{"security": {"domain.reverse.appname": {"binary": /nonexistent"}}} +''' + open(self.manifest, 'w').write(contents) + + # opt first + args = self.full_args + args.extend([opt, value, '--manifest', self.manifest]) + raised = False + try: + easyprof.parse_args(args, InterceptingOptionParser()) + except InterceptedError: + raised = True + + self.assertTrue(raised, msg="%s and manifest arguments did not " \ + "raise a parse error" % opt) + + # manifest first + args = self.full_args + args.extend(['--manifest', self.manifest, opt, value]) + raised = False + try: + easyprof.parse_args(args, InterceptingOptionParser()) + except InterceptedError: + raised = True + + self.assertTrue(raised, msg="%s and manifest arguments did not " \ + "raise a parse error" % opt) + + def test_manifest_conflicts_profilename(self): + '''Test manifest arg conflicts with profile_name arg''' + self._manifest_conflicts("--profile-name", "simple-app") + + def test_manifest_conflicts_copyright(self): + '''Test manifest arg conflicts with copyright arg''' + self._manifest_conflicts("--copyright", "2013-01-01") + + def test_manifest_conflicts_author(self): + '''Test manifest arg conflicts with author arg''' + self._manifest_conflicts("--author", "Foo Bar") + + def test_manifest_conflicts_comment(self): + '''Test manifest arg conflicts with comment arg''' + self._manifest_conflicts("--comment", "some comment") + + def test_manifest_conflicts_abstractions(self): + '''Test manifest arg conflicts with abstractions arg''' + self._manifest_conflicts("--abstractions", "base") + + def test_manifest_conflicts_read_path(self): + '''Test manifest arg conflicts with read-path arg''' + self._manifest_conflicts("--read-path", "/etc/passwd") + + def test_manifest_conflicts_write_path(self): + '''Test manifest arg conflicts with write-path arg''' + self._manifest_conflicts("--write-path", "/tmp/foo") + + def test_manifest_conflicts_policy_groups(self): + '''Test manifest arg conflicts with policy-groups arg''' + self._manifest_conflicts("--policy-groups", "opt-application") + + def test_manifest_conflicts_name(self): + '''Test manifest arg conflicts with name arg''' + self._manifest_conflicts("--name", "foo") + + def test_manifest_conflicts_template_var(self): + '''Test manifest arg conflicts with template-var arg''' + self._manifest_conflicts("--template-var", "foo") + + def test_manifest_conflicts_policy_version(self): + '''Test manifest arg conflicts with policy-version arg''' + self._manifest_conflicts("--policy-version", "1.0") + + def test_manifest_conflicts_policy_vendor(self): + '''Test manifest arg conflicts with policy-vendor arg''' + self._manifest_conflicts("--policy-vendor", "somevendor") + + # # Test genpolicy # + def _gen_policy(self, name=None, template=None, extra_args=[]): '''Generate a policy''' # Build up our args @@ -446,6 +674,40 @@ POLICYGROUPS_DIR="%s/templates" return p + def _gen_manifest_policy(self, manifest, use_security_prefix=True): + # Build up our args + args = self.full_args + args.append("--manifest=/dev/null") + + (self.options, self.args) = easyprof.parse_args(args) + (binary, self.options) = easyprof.parse_manifest(manifest.emit_json(use_security_prefix), self.options)[0] + easyp = easyprof.AppArmorEasyProfile(binary, self.options) + params = easyprof.gen_policy_params(binary, self.options) + p = easyp.gen_policy(**params) + + # ###NAME### should be replaced with self.binary or 'name'. Check for that + inv_s = '###NAME###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + if debugging: + sys.stdout.write("%s\n" % p) + + return p + + def test__is_safe(self): + '''Test _is_safe()''' + bad = [ + "/../../../../etc/passwd", + "abstraction with spaces", + "semicolon;bad", + "bad\x00baz", + "foo/bar", + "foo'bar", + 'foo"bar', + ] + for s in bad: + self.assertFalse(easyprof._is_safe(s), "'%s' should be bad" %s) + def test_genpolicy_templates_abspath(self): '''Test genpolicy (abspath to template)''' # create a new template @@ -521,6 +783,54 @@ POLICYGROUPS_DIR="%s/templates" inv_s = '###ABSTRACTIONS###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + def test_genpolicy_abstractions_bad(self): + '''Test genpolicy (abstractions - bad values)''' + bad = [ + "nonexistent", + "/../../../../etc/passwd", + "abstraction with spaces", + ] + for s in bad: + try: + self._gen_policy(extra_args=['--abstractions=%s' % s]) + except easyprof.AppArmorException: + continue + except Exception: + raise + raise Exception ("abstraction '%s' should be invalid" % s) + + def test_genpolicy_profile_name_bad(self): + '''Test genpolicy (profile name - bad values)''' + bad = [ + "/../../../../etc/passwd", + "../../../../etc/passwd", + "profile name with spaces", + ] + for s in bad: + try: + self._gen_policy(extra_args=['--profile-name=%s' % s]) + except easyprof.AppArmorException: + continue + except Exception: + raise + raise Exception ("profile_name '%s' should be invalid" % s) + + def test_genpolicy_policy_group_bad(self): + '''Test genpolicy (policy group - bad values)''' + bad = [ + "/../../../../etc/passwd", + "../../../../etc/passwd", + "profile name with spaces", + ] + for s in bad: + try: + self._gen_policy(extra_args=['--policy-groups=%s' % s]) + except easyprof.AppArmorException: + continue + except Exception: + raise + raise Exception ("policy group '%s' should be invalid" % s) + def test_genpolicy_policygroups(self): '''Test genpolicy (single policygroup)''' groups = self.test_policygroup @@ -566,7 +876,7 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (read-path file)''' s = "/opt/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) - search = "%s r," % s + search = "%s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) @@ -575,7 +885,7 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (read-path file in /home)''' s = "/home/*/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) - search = "owner %s r," % s + search = "owner %s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) @@ -584,7 +894,7 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (read-path file in @{HOME})''' s = "@{HOME}/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) - search = "owner %s r," % s + search = "owner %s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) @@ -593,7 +903,7 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (read-path file in @{HOMEDIRS})''' s = "@{HOMEDIRS}/test-foo" p = self._gen_policy(extra_args=['--read-path=%s' % s]) - search = "owner %s r," % s + search = "owner %s rk," % s self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) @@ -602,7 +912,7 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (read-path directory/)''' s = "/opt/test-foo-dir/" p = self._gen_policy(extra_args=['--read-path=%s' % s]) - search_terms = ["%s r," % s, "%s** r," % s] + search_terms = ["%s rk," % s, "%s** rk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' @@ -612,7 +922,7 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (read-path directory/*)''' s = "/opt/test-foo-dir/*" p = self._gen_policy(extra_args=['--read-path=%s' % s]) - search_terms = ["%s r," % os.path.dirname(s), "%s r," % s] + search_terms = ["%s rk," % os.path.dirname(s), "%s rk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' @@ -622,7 +932,7 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (read-path directory/**)''' s = "/opt/test-foo-dir/**" p = self._gen_policy(extra_args=['--read-path=%s' % s]) - search_terms = ["%s r," % os.path.dirname(s), "%s r," % s] + search_terms = ["%s rk," % os.path.dirname(s), "%s rk," % s] for search in search_terms: self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p)) inv_s = '###READPATH###' @@ -646,13 +956,13 @@ POLICYGROUPS_DIR="%s/templates" if s.startswith('/home/') or s.startswith("@{HOME"): owner = "owner " if s.endswith('/'): - search_terms.append("%s r," % (s)) - search_terms.append("%s%s** r," % (owner, s)) + search_terms.append("%s rk," % (s)) + search_terms.append("%s%s** rk," % (owner, s)) elif s.endswith('/**') or s.endswith('/*'): - search_terms.append("%s r," % (os.path.dirname(s))) - search_terms.append("%s%s r," % (owner, s)) + search_terms.append("%s rk," % (os.path.dirname(s))) + search_terms.append("%s%s rk," % (owner, s)) else: - search_terms.append("%s%s r," % (owner, s)) + search_terms.append("%s%s rk," % (owner, s)) p = self._gen_policy(extra_args=args) for search in search_terms: @@ -784,33 +1094,48 @@ POLICYGROUPS_DIR="%s/templates" '''Test genpolicy (template-var single)''' s = "@{FOO}=bar" p = self._gen_policy(extra_args=['--template-var=%s' % s]) + k, v = s.split('=') + s = '%s="%s"' % (k, v) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###TEMPLATEVAR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_templatevar_multiple(self): '''Test genpolicy (template-var multiple)''' - variables = ["@{FOO}=bar", "@{BAR}=baz"] + variables = ['@{FOO}=bar', '@{BAR}=baz'] args = [] for s in variables: args.append('--template-var=%s' % s) p = self._gen_policy(extra_args=args) for s in variables: + k, v = s.split('=') + s = '%s="%s"' % (k, v) self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) inv_s = '###TEMPLATEVAR###' self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) def test_genpolicy_templatevar_bad(self): - '''Test genpolicy (template-var bad)''' - s = "{FOO}=bar" - try: - self._gen_policy(extra_args=['--template-var=%s' % s]) - except easyprof.AppArmorException: - return - except Exception: - raise - raise Exception ("template-var should be invalid") + '''Test genpolicy (template-var - bad values)''' + bad = [ + "{FOO}=bar", + "@FOO}=bar", + "@{FOO=bar", + "FOO=bar", + "@FOO=bar", + "@{FOO}=/../../../etc/passwd", + "@{FOO}=bar=foo", + "@{FOO;BAZ}=bar", + '@{FOO}=bar"baz', + ] + for s in bad: + try: + self._gen_policy(extra_args=['--template-var=%s' % s]) + except easyprof.AppArmorException: + continue + except Exception: + raise + raise Exception ("template-var should be invalid") def test_genpolicy_invalid_template_policy(self): '''Test genpolicy (invalid template policy)''' @@ -835,6 +1160,1291 @@ POLICYGROUPS_DIR="%s/templates" raise raise Exception ("policy should be invalid") + def test_genpolicy_no_binary_without_profile_name(self): + '''Test genpolicy (no binary with no profile name)''' + try: + easyprof.gen_policy_params(None, self.options) + except easyprof.AppArmorException: + return + except Exception: + raise + raise Exception ("No binary or profile name should have been invalid") + + def test_genpolicy_with_binary_with_profile_name(self): + '''Test genpolicy (binary with profile name)''' + profile_name = "some-profile-name" + p = self._gen_policy(extra_args=['--profile-name=%s' % profile_name]) + s = 'profile "%s" "%s" {' % (profile_name, self.binary) + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###PROFILEATTACH###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_genpolicy_with_binary_without_profile_name(self): + '''Test genpolicy (binary without profile name)''' + p = self._gen_policy() + s = '"%s" {' % (self.binary) + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###PROFILEATTACH###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_genpolicy_without_binary_with_profile_name(self): + '''Test genpolicy (no binary with profile name)''' + profile_name = "some-profile-name" + args = self.full_args + args.append('--profile-name=%s' % profile_name) + (self.options, self.args) = easyprof.parse_args(args) + easyp = easyprof.AppArmorEasyProfile(None, self.options) + params = easyprof.gen_policy_params(None, self.options) + p = easyp.gen_policy(**params) + s = 'profile "%s" {' % (profile_name) + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###PROFILEATTACH###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + +# manifest tests + + def test_gen_manifest_policy_with_binary_with_profile_name(self): + '''Test gen_manifest_policy (binary with profile name)''' + m = Manifest("test_gen_manifest_policy") + m.add_binary('/bin/ls') + self._gen_manifest_policy(m) + + def test_gen_manifest_policy_without_binary_with_profile_name(self): + '''Test gen_manifest_policy (no binary with profile name)''' + m = Manifest("test_gen_manifest_policy") + self._gen_manifest_policy(m) + + def test_gen_manifest_policy_templates_system(self): + '''Test gen_manifest_policy (system template)''' + m = Manifest("test_gen_manifest_policy") + m.add_template(self.test_template) + self._gen_manifest_policy(m) + + def test_gen_manifest_policy_templates_system_noprefix(self): + '''Test gen_manifest_policy (system template, no security prefix)''' + m = Manifest("test_gen_manifest_policy") + m.add_template(self.test_template) + self._gen_manifest_policy(m, use_security_prefix=False) + + def test_gen_manifest_abs_path_template(self): + '''Test gen_manifest_policy (abs path template)''' + m = Manifest("test_gen_manifest_policy") + m.add_template("/etc/shadow") + try: + self._gen_manifest_policy(m) + except easyprof.AppArmorException: + return + except Exception: + raise + raise Exception ("abs path template name should be invalid") + + def test_gen_manifest_escape_path_templates(self): + '''Test gen_manifest_policy (esc path template)''' + m = Manifest("test_gen_manifest_policy") + m.add_template("../../../../../../../../etc/shadow") + try: + self._gen_manifest_policy(m) + except easyprof.AppArmorException: + return + except Exception: + raise + raise Exception ("../ template name should be invalid") + + def test_gen_manifest_policy_templates_nonexistent(self): + '''Test gen manifest policy (nonexistent template)''' + m = Manifest("test_gen_manifest_policy") + m.add_template("nonexistent") + try: + self._gen_manifest_policy(m) + except easyprof.AppArmorException: + return + except Exception: + raise + raise Exception ("template should be invalid") + + def test_gen_manifest_policy_comment(self): + '''Test gen manifest policy (comment)''' + s = "test comment" + m = Manifest("test_gen_manifest_policy") + m.add_comment(s) + p = self._gen_manifest_policy(m) + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###COMMENT###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_gen_manifest_policy_author(self): + '''Test gen manifest policy (author)''' + s = "Archibald Poindexter" + m = Manifest("test_gen_manifest_policy") + m.add_author(s) + p = self._gen_manifest_policy(m) + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###AUTHOR###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_gen_manifest_policy_copyright(self): + '''Test genpolicy (copyright)''' + s = "2112/01/01" + m = Manifest("test_gen_manifest_policy") + m.add_copyright(s) + p = self._gen_manifest_policy(m) + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###COPYRIGHT###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_gen_manifest_policy_policygroups(self): + '''Test gen manifest policy (single policygroup)''' + groups = self.test_policygroup + m = Manifest("test_gen_manifest_policy") + m.add_policygroups(groups) + p = self._gen_manifest_policy(m) + + for s in ['#include ', '#include ']: + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###POLICYGROUPS###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_gen_manifest_policy_policygroups_multiple(self): + '''Test genpolicy (multiple policygroups)''' + test_policygroup2 = "test-policygroup2" + contents = ''' + # %s + #include + #include +''' % (self.test_policygroup) + open(os.path.join(self.tmpdir, 'policygroups', test_policygroup2), 'w').write(contents) + + groups = "%s,%s" % (self.test_policygroup, test_policygroup2) + m = Manifest("test_gen_manifest_policy") + m.add_policygroups(groups) + p = self._gen_manifest_policy(m) + + for s in ['#include ', + '#include ', + '#include ', + '#include ']: + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###POLICYGROUPS###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_gen_manifest_policy_policygroups_nonexistent(self): + '''Test gen manifest policy (nonexistent policygroup)''' + groups = "nonexistent" + m = Manifest("test_gen_manifest_policy") + m.add_policygroups(groups) + try: + self._gen_manifest_policy(m) + except easyprof.AppArmorException: + return + except Exception: + raise + raise Exception ("policygroup should be invalid") + + def test_gen_manifest_policy_templatevar(self): + '''Test gen manifest policy (template-var single)''' + m = Manifest("test_gen_manifest_policy") + m.add_template_variable("FOO", "bar") + p = self._gen_manifest_policy(m) + s = '@{FOO}="bar"' + self.assertTrue(s in p, "Could not find '%s' in:\n%s" % (s, p)) + inv_s = '###TEMPLATEVAR###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_gen_manifest_policy_templatevar_multiple(self): + '''Test gen manifest policy (template-var multiple)''' + variables = [["FOO", "bar"], ["BAR", "baz"]] + m = Manifest("test_gen_manifest_policy") + for s in variables: + m.add_template_variable(s[0], s[1]) + + p = self._gen_manifest_policy(m) + for s in variables: + str_s = '@{%s}="%s"' % (s[0], s[1]) + self.assertTrue(str_s in p, "Could not find '%s' in:\n%s" % (str_s, p)) + inv_s = '###TEMPLATEVAR###' + self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p)) + + def test_gen_manifest_policy_invalid_keys(self): + '''Test gen manifest policy (invalid keys)''' + keys = ['config_file', + 'debug', + 'help', + 'list-templates', + 'list_templates', + 'show-template', + 'show_template', + 'list-policy-groups', + 'list_policy_groups', + 'show-policy-group', + 'show_policy_group', + 'templates-dir', + 'templates_dir', + 'policy-groups-dir', + 'policy_groups_dir', + 'nonexistent', + 'no_verify', + ] + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + for k in keys: + security = dict() + security["profile_name"] = "test-app" + security[k] = "bad" + j = json.dumps(security, indent=2) + try: + easyprof.parse_manifest(j, self.options) + except easyprof.AppArmorException: + continue + raise Exception ("'%s' should be invalid" % k) + + def test_gen_manifest(self): + '''Test gen_manifest''' + # this should come from manpage + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "abstractions": [ + "audio", + "gnome" + ], + "author": "Your Name", + "binary": "/opt/foo/**", + "comment": "Unstructured single-line comment", + "copyright": "Unstructured single-line copyright statement", + "name": "My Foo App", + "policy_groups": [ + "opt-application", + "user-application" + ], + "policy_vendor": "somevendor", + "policy_version": 1.0, + "read_path": [ + "/tmp/foo_r", + "/tmp/bar_r/" + ], + "template": "user-application", + "template_variables": { + "APPNAME": "foo", + "VAR1": "bar", + "VAR2": "baz" + }, + "write_path": [ + "/tmp/foo_w", + "/tmp/bar_w/" + ] + } + } + } +}''' + + for d in ['policygroups', 'templates']: + shutil.copytree(os.path.join(self.tmpdir, d), + os.path.join(self.tmpdir, d, "somevendor/1.0")) + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] + easyp = easyprof.AppArmorEasyProfile(binary, self.options) + params = easyprof.gen_policy_params(binary, self.options) + + # verify we get the same manifest back + man_new = easyp.gen_manifest(params) + self.assertEquals(m, man_new) + + def test_gen_manifest_ubuntu(self): + '''Test gen_manifest (ubuntu)''' + # this should be based on the manpage (but use existing policy_groups + # and template + m = '''{ + "security": { + "profiles": { + "com.ubuntu.developer.myusername.MyCoolApp": { + "name": "MyCoolApp", + "policy_groups": [ + "opt-application", + "user-application" + ], + "policy_vendor": "ubuntu", + "policy_version": 1.0, + "template": "user-application", + "template_variables": { + "APPNAME": "MyCoolApp", + "APPVERSION": "0.1.2" + } + } + } + } +}''' + + for d in ['policygroups', 'templates']: + shutil.copytree(os.path.join(self.tmpdir, d), + os.path.join(self.tmpdir, d, "ubuntu/1.0")) + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] + easyp = easyprof.AppArmorEasyProfile(binary, self.options) + params = easyprof.gen_policy_params(binary, self.options) + + # verify we get the same manifest back + man_new = easyp.gen_manifest(params) + self.assertEquals(m, man_new) + + def test_parse_manifest_no_version(self): + '''Test parse_manifest (vendor with no version)''' + # this should come from manpage + m = '''{ + "security": { + "profiles": { + "com.ubuntu.developer.myusername.MyCoolApp": { + "policy_groups": [ + "opt-application", + "user-application" + ], + "policy_vendor": "ubuntu", + "template": "user-application", + "template_variables": { + "APPNAME": "MyCoolApp", + "APPVERSION": "0.1.2" + } + } + } + } +}''' + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] + try: + easyprof.AppArmorEasyProfile(binary, self.options) + except easyprof.AppArmorException: + return + raise Exception ("Should have failed on missing version") + + def test_parse_manifest_no_vendor(self): + '''Test parse_manifest (version with no vendor)''' + # this should come from manpage + m = '''{ + "security": { + "profiles": { + "com.ubuntu.developer.myusername.MyCoolApp": { + "policy_groups": [ + "opt-application", + "user-application" + ], + "policy_version": 1.0, + "template": "user-application", + "template_variables": { + "APPNAME": "MyCoolApp", + "APPVERSION": "0.1.2" + } + } + } + } +}''' + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] + try: + easyprof.AppArmorEasyProfile(binary, self.options) + except easyprof.AppArmorException: + return + raise Exception ("Should have failed on missing vendor") + + def test_parse_manifest_multiple(self): + '''Test parse_manifest_multiple''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "abstractions": [ + "audio", + "gnome" + ], + "author": "Your Name", + "binary": "/opt/foo/**", + "comment": "Unstructured single-line comment", + "copyright": "Unstructured single-line copyright statement", + "name": "My Foo App", + "policy_groups": [ + "opt-application", + "user-application" + ], + "read_path": [ + "/tmp/foo_r", + "/tmp/bar_r/" + ], + "template": "user-application", + "template_variables": { + "APPNAME": "foo", + "VAR1": "bar", + "VAR2": "baz" + }, + "write_path": [ + "/tmp/foo_w", + "/tmp/bar_w/" + ] + }, + "com.ubuntu.developer.myusername.MyCoolApp": { + "policy_groups": [ + "opt-application" + ], + "policy_vendor": "ubuntu", + "policy_version": 1.0, + "template": "user-application", + "template_variables": { + "APPNAME": "MyCoolApp", + "APPVERSION": "0.1.2" + } + } + } + } +}''' + + for d in ['policygroups', 'templates']: + shutil.copytree(os.path.join(self.tmpdir, d), + os.path.join(self.tmpdir, d, "ubuntu/1.0")) + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + profiles = easyprof.parse_manifest(m, self.options) + for (binary, options) in profiles: + easyp = easyprof.AppArmorEasyProfile(binary, options) + params = easyprof.gen_policy_params(binary, options) + easyp.gen_manifest(params) + easyp.gen_policy(**params) + + +# verify manifest tests + def _verify_manifest(self, m, expected, invalid=False): + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + try: + (binary, options) = easyprof.parse_manifest(m, self.options)[0] + except easyprof.AppArmorException: + if invalid: + return + raise + params = easyprof.gen_policy_params(binary, options) + if expected: + self.assertTrue(easyprof.verify_manifest(params), "params=%s\nmanifest=%s" % (params,m)) + else: + self.assertFalse(easyprof.verify_manifest(params), "params=%s\nmanifest=%s" % (params,m)) + + def test_verify_manifest_full(self): + '''Test verify_manifest (full)''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "abstractions": [ + "base" + ], + "author": "Your Name", + "binary": "/opt/com.example/foo/**", + "comment": "some free-form single-line comment", + "copyright": "Unstructured single-line copyright statement", + "name": "foo", + "policy_groups": [ + "user-application", + "opt-application" + ], + "template": "user-application", + "template_variables": { + "OK1": "foo", + "OK2": "com.example.foo" + } + } + } + } +}''' + self._verify_manifest(m, expected=True) + + def test_verify_manifest_full_bad(self): + '''Test verify_manifest (full bad)''' + m = '''{ + "security": { + "profiles": { + "/com.example.foo": { + "abstractions": [ + "audio", + "gnome" + ], + "author": "Your Name", + "binary": "/usr/foo/**", + "comment": "some free-form single-line comment", + "copyright": "Unstructured single-line copyright statement", + "name": "foo", + "policy_groups": [ + "user-application", + "opt-application" + ], + "read_path": [ + "/tmp/foo_r", + "/tmp/bar_r/" + ], + "template": "user-application", + "template_variables": { + "VAR1": "f*o", + "VAR2": "*foo", + "VAR3": "fo*", + "VAR4": "b{ar", + "VAR5": "b{a,r}", + "VAR6": "b}ar", + "VAR7": "bar[0-9]", + "VAR8": "b{ar", + "VAR9": "/tmp/../etc/passwd" + }, + "write_path": [ + "/tmp/foo_w", + "/tmp/bar_w/" + ] + } + } + } +}''' + + self._verify_manifest(m, expected=False, invalid=True) + + def test_verify_manifest_binary(self): + '''Test verify_manifest (binary in /usr)''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "binary": "/usr/foo/**", + "template": "user-application" + } + } + } +}''' + self._verify_manifest(m, expected=True) + + def test_verify_manifest_profile_profile_name_bad(self): + '''Test verify_manifest (bad profile_name)''' + m = '''{ + "security": { + "profiles": { + "/foo": { + "binary": "/opt/com.example/foo/**", + "template": "user-application" + } + } + } +}''' + self._verify_manifest(m, expected=False, invalid=True) + + m = '''{ + "security": { + "profiles": { + "bin/*": { + "binary": "/opt/com.example/foo/**", + "template": "user-application" + } + } + } +}''' + self._verify_manifest(m, expected=False) + + def test_verify_manifest_profile_profile_name(self): + '''Test verify_manifest (profile_name)''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "binary": "/opt/com.example/foo/**", + "template": "user-application" + } + } + } +}''' + self._verify_manifest(m, expected=True) + + def test_verify_manifest_profile_abstractions(self): + '''Test verify_manifest (abstractions)''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "binary": "/opt/com.example/foo/**", + "template": "user-application", + "abstractions": [ + "base" + ] + } + } + } +}''' + self._verify_manifest(m, expected=True) + + def test_verify_manifest_profile_abstractions_bad(self): + '''Test verify_manifest (bad abstractions)''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "binary": "/opt/com.example/foo/**", + "template": "user-application", + "abstractions": [ + "user-tmp" + ] + } + } + } +}''' + self._verify_manifest(m, expected=False) + + def test_verify_manifest_profile_template_var(self): + '''Test verify_manifest (good template_var)''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "binary": "/opt/com.example/something with spaces/**", + "template": "user-application", + "template_variables": { + "OK1": "foo", + "OK2": "com.example.foo", + "OK3": "something with spaces" + } + } + } + } +}''' + self._verify_manifest(m, expected=True) + + def test_verify_manifest_profile_template_var_bad(self): + '''Test verify_manifest (bad template_var)''' + for v in ['"VAR1": "f*o"', + '"VAR2": "*foo"', + '"VAR3": "fo*"', + '"VAR4": "b{ar"', + '"VAR5": "b{a,r}"', + '"VAR6": "b}ar"', + '"VAR7": "bar[0-9]"', + '"VAR8": "b{ar"', + '"VAR9": "foo/bar"' # this is valid, but potentially unsafe + ]: + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "binary": "/opt/com.example/foo/**", + "template": "user-application", + "template_variables": { + %s + } + } + } + } +}''' % v + self._verify_manifest(m, expected=False) + + def test_manifest_invalid(self): + '''Test invalid manifest (parse error)''' + m = '''{ + "security": { + "com.example.foo": { + "binary": "/opt/com.example/foo/**", + "template": "user-application", + "abstractions": [ + "base" + ] +}''' + self._verify_manifest(m, expected=False, invalid=True) + + def test_manifest_invalid2(self): + '''Test invalid manifest (profile_name is not key)''' + m = '''{ + "security": { + "binary": "/opt/com.example/foo/**", + "template": "user-application", + "abstractions": [ + "base" + ] + } +}''' + self._verify_manifest(m, expected=False, invalid=True) + + def test_manifest_invalid3(self): + '''Test invalid manifest (profile_name in dict)''' + m = '''{ + "security": { + "binary": "/opt/com.example/foo/**", + "template": "user-application", + "abstractions": [ + "base" + ], + "profile_name": "com.example.foo" + } +}''' + self._verify_manifest(m, expected=False, invalid=True) + + def test_manifest_invalid4(self): + '''Test invalid manifest (bad path in template var)''' + for v in ['"VAR1": "/tmp/../etc/passwd"', + '"VAR2": "./"', + '"VAR3": "foo\"bar"', + '"VAR4": "foo//bar"', + ]: + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "binary": "/opt/com.example/foo/**", + "template": "user-application", + "template_variables": { + %s + } + } + } + } +}''' % v + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, options) = easyprof.parse_manifest(m, self.options)[0] + params = easyprof.gen_policy_params(binary, options) + try: + easyprof.verify_manifest(params) + except easyprof.AppArmorException: + return + + raise Exception ("Should have failed with invalid variable declaration") + + +# policy version tests + def test_policy_vendor_manifest_nonexistent(self): + '''Test policy vendor via manifest (nonexistent)''' + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "policy_vendor": "nonexistent", + "policy_version": 1.0, + "binary": "/opt/com.example/foo/**", + "template": "user-application" + } + } + } +}''' + # Build up our args + args = self.full_args + args.append("--manifest=/dev/null") + + (self.options, self.args) = easyprof.parse_args(args) + (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] + try: + easyprof.AppArmorEasyProfile(binary, self.options) + except easyprof.AppArmorException: + return + + raise Exception ("Should have failed with non-existent directory") + + def test_policy_version_manifest(self): + '''Test policy version via manifest (good)''' + policy_vendor = "somevendor" + policy_version = "1.0" + policy_subdir = "%s/%s" % (policy_vendor, policy_version) + m = '''{ + "security": { + "profiles": { + "com.example.foo": { + "policy_vendor": "%s", + "policy_version": %s, + "binary": "/opt/com.example/foo/**", + "template": "user-application" + } + } + } +}''' % (policy_vendor, policy_version) + for d in ['policygroups', 'templates']: + shutil.copytree(os.path.join(self.tmpdir, d), + os.path.join(self.tmpdir, d, policy_subdir)) + + # Build up our args + args = self.full_args + args.append("--manifest=/dev/null") + + (self.options, self.args) = easyprof.parse_args(args) + (binary, self.options) = easyprof.parse_manifest(m, self.options)[0] + easyp = easyprof.AppArmorEasyProfile(binary, self.options) + + tdir = os.path.join(self.tmpdir, 'templates', policy_subdir) + for t in easyp.get_templates(): + self.assertTrue(t.startswith(tdir)) + + pdir = os.path.join(self.tmpdir, 'policygroups', policy_subdir) + for p in easyp.get_policy_groups(): + self.assertTrue(p.startswith(pdir)) + + params = easyprof.gen_policy_params(binary, self.options) + easyp.gen_policy(**params) + + def test_policy_vendor_version_args(self): + '''Test policy vendor and version via command line args (good)''' + policy_version = "1.0" + policy_vendor = "somevendor" + policy_subdir = "%s/%s" % (policy_vendor, policy_version) + + # Create the directories + for d in ['policygroups', 'templates']: + shutil.copytree(os.path.join(self.tmpdir, d), + os.path.join(self.tmpdir, d, policy_subdir)) + + # Build up our args + args = self.full_args + args.append("--policy-version=%s" % policy_version) + args.append("--policy-vendor=%s" % policy_vendor) + + (self.options, self.args) = easyprof.parse_args(args) + (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) + easyp = easyprof.AppArmorEasyProfile(self.binary, self.options) + + tdir = os.path.join(self.tmpdir, 'templates', policy_subdir) + for t in easyp.get_templates(): + self.assertTrue(t.startswith(tdir), \ + "'%s' does not start with '%s'" % (t, tdir)) + + pdir = os.path.join(self.tmpdir, 'policygroups', policy_subdir) + for p in easyp.get_policy_groups(): + self.assertTrue(p.startswith(pdir), \ + "'%s' does not start with '%s'" % (p, pdir)) + + params = easyprof.gen_policy_params(self.binary, self.options) + easyp.gen_policy(**params) + + def test_policy_vendor_args_nonexistent(self): + '''Test policy vendor via command line args (nonexistent)''' + policy_vendor = "nonexistent" + policy_version = "1.0" + args = self.full_args + args.append("--policy-version=%s" % policy_version) + args.append("--policy-vendor=%s" % policy_vendor) + + (self.options, self.args) = easyprof.parse_args(args) + (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) + try: + easyprof.AppArmorEasyProfile(self.binary, self.options) + except easyprof.AppArmorException: + return + + raise Exception ("Should have failed with non-existent directory") + + def test_policy_version_args_bad(self): + '''Test policy version via command line args (bad)''' + bad = [ + "../../../../../../etc", + "notanumber", + "v1.0a", + "-1", + ] + for policy_version in bad: + args = self.full_args + args.append("--policy-version=%s" % policy_version) + args.append("--policy-vendor=somevendor") + + (self.options, self.args) = easyprof.parse_args(args) + (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) + try: + easyprof.AppArmorEasyProfile(self.binary, self.options) + except easyprof.AppArmorException: + continue + + raise Exception ("Should have failed with bad version") + + def test_policy_vendor_args_bad(self): + '''Test policy vendor via command line args (bad)''' + bad = [ + "../../../../../../etc", + "vendor with space", + "semicolon;isbad", + ] + for policy_vendor in bad: + args = self.full_args + args.append("--policy-vendor=%s" % policy_vendor) + args.append("--policy-version=1.0") + + (self.options, self.args) = easyprof.parse_args(args) + (self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary]) + try: + easyprof.AppArmorEasyProfile(self.binary, self.options) + except easyprof.AppArmorException: + continue + + raise Exception ("Should have failed with bad vendor") + +# output_directory tests + def test_output_directory_multiple(self): + '''Test output_directory (multiple)''' + files = dict() + files["com.example.foo"] = "com.example.foo" + files["com.ubuntu.developer.myusername.MyCoolApp"] = "com.ubuntu.developer.myusername.MyCoolApp" + files["usr.bin.baz"] = "/usr/bin/baz" + m = '''{ + "security": { + "profiles": { + "%s": { + "abstractions": [ + "audio", + "gnome" + ], + "author": "Your Name", + "binary": "/opt/foo/**", + "comment": "Unstructured single-line comment", + "copyright": "Unstructured single-line copyright statement", + "name": "My Foo App", + "policy_groups": [ + "opt-application", + "user-application" + ], + "read_path": [ + "/tmp/foo_r", + "/tmp/bar_r/" + ], + "template": "user-application", + "template_variables": { + "APPNAME": "foo", + "VAR1": "bar", + "VAR2": "baz" + }, + "write_path": [ + "/tmp/foo_w", + "/tmp/bar_w/" + ] + }, + "%s": { + "policy_groups": [ + "opt-application", + "user-application" + ], + "template": "user-application", + "template_variables": { + "APPNAME": "MyCoolApp", + "APPVERSION": "0.1.2" + } + }, + "%s": { + "abstractions": [ + "gnome" + ], + "policy_groups": [ + "user-application" + ], + "template_variables": { + "APPNAME": "baz" + } + } + } + } +}''' % (files["com.example.foo"], + files["com.ubuntu.developer.myusername.MyCoolApp"], + files["usr.bin.baz"]) + + out_dir = os.path.join(self.tmpdir, "output") + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + profiles = easyprof.parse_manifest(m, self.options) + for (binary, options) in profiles: + easyp = easyprof.AppArmorEasyProfile(binary, options) + params = easyprof.gen_policy_params(binary, options) + easyp.output_policy(params, dir=out_dir) + + for fn in files: + f = os.path.join(out_dir, fn) + self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) + + def test_output_directory_single(self): + '''Test output_directory (single)''' + files = dict() + files["com.example.foo"] = "com.example.foo" + m = '''{ + "security": { + "profiles": { + "%s": { + "abstractions": [ + "audio", + "gnome" + ], + "author": "Your Name", + "binary": "/opt/foo/**", + "comment": "Unstructured single-line comment", + "copyright": "Unstructured single-line copyright statement", + "name": "My Foo App", + "policy_groups": [ + "opt-application", + "user-application" + ], + "read_path": [ + "/tmp/foo_r", + "/tmp/bar_r/" + ], + "template": "user-application", + "template_variables": { + "APPNAME": "foo", + "VAR1": "bar", + "VAR2": "baz" + }, + "write_path": [ + "/tmp/foo_w", + "/tmp/bar_w/" + ] + } + } + } +}''' % (files["com.example.foo"]) + + out_dir = os.path.join(self.tmpdir, "output") + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + profiles = easyprof.parse_manifest(m, self.options) + for (binary, options) in profiles: + easyp = easyprof.AppArmorEasyProfile(binary, options) + params = easyprof.gen_policy_params(binary, options) + easyp.output_policy(params, dir=out_dir) + + for fn in files: + f = os.path.join(out_dir, fn) + self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) + + + + + def test_output_directory_invalid(self): + '''Test output_directory (output directory exists as file)''' + files = dict() + files["usr.bin.baz"] = "/usr/bin/baz" + m = '''{ + "security": { + "profiles": { + "%s": { + "abstractions": [ + "gnome" + ], + "policy_groups": [ + "user-application" + ], + "template_variables": { + "APPNAME": "baz" + } + } + } + } +}''' % files["usr.bin.baz"] + + + out_dir = os.path.join(self.tmpdir, "output") + open(out_dir, 'w').close() + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, options) = easyprof.parse_manifest(m, self.options)[0] + easyp = easyprof.AppArmorEasyProfile(binary, options) + params = easyprof.gen_policy_params(binary, options) + try: + easyp.output_policy(params, dir=out_dir) + except easyprof.AppArmorException: + return + raise Exception ("Should have failed with 'is not a directory'") + + def test_output_directory_invalid_params(self): + '''Test output_directory (no binary or profile_name)''' + files = dict() + files["usr.bin.baz"] = "/usr/bin/baz" + m = '''{ + "security": { + "profiles": { + "%s": { + "abstractions": [ + "gnome" + ], + "policy_groups": [ + "user-application" + ], + "template_variables": { + "APPNAME": "baz" + } + } + } + } +}''' % files["usr.bin.baz"] + + out_dir = os.path.join(self.tmpdir, "output") + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, options) = easyprof.parse_manifest(m, self.options)[0] + easyp = easyprof.AppArmorEasyProfile(binary, options) + params = easyprof.gen_policy_params(binary, options) + del params['binary'] + try: + easyp.output_policy(params, dir=out_dir) + except easyprof.AppArmorException: + return + raise Exception ("Should have failed with 'Must specify binary and/or profile name'") + + def test_output_directory_invalid2(self): + '''Test output_directory (profile exists)''' + files = dict() + files["usr.bin.baz"] = "/usr/bin/baz" + m = '''{ + "security": { + "profiles": { + "%s": { + "abstractions": [ + "gnome" + ], + "policy_groups": [ + "user-application" + ], + "template_variables": { + "APPNAME": "baz" + } + } + } + } +}''' % files["usr.bin.baz"] + + out_dir = os.path.join(self.tmpdir, "output") + os.mkdir(out_dir) + open(os.path.join(out_dir, "usr.bin.baz"), 'w').close() + + args = self.full_args + args.append("--manifest=/dev/null") + (self.options, self.args) = easyprof.parse_args(args) + (binary, options) = easyprof.parse_manifest(m, self.options)[0] + easyp = easyprof.AppArmorEasyProfile(binary, options) + params = easyprof.gen_policy_params(binary, options) + try: + easyp.output_policy(params, dir=out_dir) + except easyprof.AppArmorException: + return + raise Exception ("Should have failed with 'already exists'") + + def test_output_directory_args(self): + '''Test output_directory (args)''' + files = dict() + files["usr.bin.baz"] = "/usr/bin/baz" + + # Build up our args + args = self.full_args + args.append('--template=%s' % self.test_template) + args.append('--name=%s' % 'foo') + args.append(files["usr.bin.baz"]) + + out_dir = os.path.join(self.tmpdir, "output") + + # Now parse our args + (self.options, self.args) = easyprof.parse_args(args) + easyp = easyprof.AppArmorEasyProfile(files["usr.bin.baz"], self.options) + params = easyprof.gen_policy_params(files["usr.bin.baz"], self.options) + easyp.output_policy(params, dir=out_dir) + + for fn in files: + f = os.path.join(out_dir, fn) + self.assertTrue(os.path.exists(f), "Could not find '%s'" % f) + +# +# utility classes +# + def test_valid_profile_name(self): + '''Test valid_profile_name''' + names = ['foo', + 'com.example.foo', + '/usr/bin/foo', + 'com.example.app_myapp_1:2.3+ab12~foo', + ] + for n in names: + self.assertTrue(easyprof.valid_profile_name(n), "'%s' should be valid" % n) + + def test_valid_profile_name_invalid(self): + '''Test valid_profile_name (invalid)''' + names = ['fo/o', + '/../../etc/passwd', + '../../etc/passwd', + './../etc/passwd', + './etc/passwd', + '/usr/bin//foo', + '/usr/bin/./foo', + 'foo`', + 'foo!', + 'foo@', + 'foo$', + 'foo#', + 'foo%', + 'foo^', + 'foo&', + 'foo*', + 'foo(', + 'foo)', + 'foo=', + 'foo{', + 'foo}', + 'foo[', + 'foo]', + 'foo|', + 'foo/', + 'foo\\', + 'foo;', + 'foo\'', + 'foo"', + 'foo<', + 'foo>', + 'foo?', + 'foo\/', + 'foo,', + '_foo', + ] + for n in names: + self.assertFalse(easyprof.valid_profile_name(n), "'%s' should be invalid" % n) + + def test_valid_path(self): + '''Test valid_path''' + names = ['/bin/bar', + '/etc/apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', + ] + names_rel = ['bin/bar', + 'apparmor.d/com.example.app_myapp_1:2.3+ab12~foo', + 'com.example.app_myapp_1:2.3+ab12~foo', + ] + for n in names: + self.assertTrue(easyprof.valid_path(n), "'%s' should be valid" % n) + for n in names_rel: + self.assertTrue(easyprof.valid_path(n, relative_ok=True), "'%s' should be valid" % n) + + def test_zz_valid_path_invalid(self): + '''Test valid_path (invalid)''' + names = ['/bin//bar', + 'bin/bar', + '/../etc/passwd', + './bin/bar', + './', + ] + names_rel = ['bin/../bar', + 'apparmor.d/../passwd', + 'com.example.app_"myapp_1:2.3+ab12~foo', + ] + for n in names: + self.assertFalse(easyprof.valid_path(n, relative_ok=False), "'%s' should be invalid" % n) + for n in names_rel: + self.assertFalse(easyprof.valid_path(n, relative_ok=True), "'%s' should be invalid" % n) + # # End test class From 0aefb378f9e6f0cc829c5aaa3a4568fb6244e195 Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 18:10:05 -0800 Subject: [PATCH 169/183] Subject: using webapps triggers firefox rejections Bug: https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1056418 From: Steve Beattie Came from 0021-webapps_abstraction.patch in the Ubuntu apparmor packaging. jdstrand: +1 (this is in the Ubuntu namespace, so feel free to commit) --- .../abstractions/ubuntu-browsers.d/ubuntu-integration | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/profiles/apparmor.d/abstractions/ubuntu-browsers.d/ubuntu-integration b/profiles/apparmor.d/abstractions/ubuntu-browsers.d/ubuntu-integration index d80b85e6e..0cd0928ef 100644 --- a/profiles/apparmor.d/abstractions/ubuntu-browsers.d/ubuntu-integration +++ b/profiles/apparmor.d/abstractions/ubuntu-browsers.d/ubuntu-integration @@ -33,3 +33,9 @@ /usr/lib/@{multiarch}/xfce4/exo-1/exo-helper-1 ixr, /etc/xdg/xdg-xubuntu/xfce4/helpers.rc r, /etc/xdg/xfce4/helpers.rc r, + + # unity webapps integration. Could go in its own abstraction + owner /run/user/*/dconf/user rw, + owner @{HOME}/.local/share/unity-webapps/availableapps*.db rwk, + /usr/bin/debconf-communicate Cxr -> sanitized_helper, + owner @{HOME}/.config/libaccounts-glib/accounts.db rk, From 35e79ef66dbdb6395e67257f392a35b2827df960 Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Thu, 13 Feb 2014 18:11:54 -0800 Subject: [PATCH 170/183] Author: Jamie Strandboge Description: Allow applications run under sanitized_helper to connect to DBus This was originally 0076_sanitized_helper_dbus_access.patch in the Ubuntu apparmor packaging. jdstrand: +1 (this is in the Ubuntu namespace, so feel free to commit) --- profiles/apparmor.d/abstractions/ubuntu-helpers | 3 +++ 1 file changed, 3 insertions(+) diff --git a/profiles/apparmor.d/abstractions/ubuntu-helpers b/profiles/apparmor.d/abstractions/ubuntu-helpers index 42bd431bb..4da98e690 100644 --- a/profiles/apparmor.d/abstractions/ubuntu-helpers +++ b/profiles/apparmor.d/abstractions/ubuntu-helpers @@ -38,6 +38,9 @@ profile sanitized_helper { network inet, network inet6, + # Allow all DBus communications + dbus, + # Allow exec of anything, but under this profile. Allow transition # to other profiles if they exist. /bin/* Pixr, From 4d2b9b5c5deb67057b9d0c67c18011fe1bb6e6a6 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 14 Feb 2014 11:20:20 -0800 Subject: [PATCH 171/183] utils/apparmor/aa.py: fix dict/list confusion in create_new_profile() --- utils/apparmor/aa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 02e746ff1..b0584d8a9 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -423,7 +423,7 @@ def create_new_profile(localfile): local_profile[hat]['flags'] = 'complain' created.append(localfile) - changed.append(localfile) + changed[local_profile] = True debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) return {localfile: local_profile} From edb874a2de4f7d26fb4180a86331e44df1c32fcc Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 14 Feb 2014 11:54:02 -0800 Subject: [PATCH 172/183] utils/apparmor/aa.py: fix confusion over name vs data structure --- utils/apparmor/aa.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index b0584d8a9..9f37239dc 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -423,7 +423,7 @@ def create_new_profile(localfile): local_profile[hat]['flags'] = 'complain' created.append(localfile) - changed[local_profile] = True + changed[localfile] = True debug_logger.debug("Profile for %s:\n\t%s" % (localfile, local_profile.__str__())) return {localfile: local_profile} From 3cbbeac60d39302812632f498d01972a68a917b4 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 14 Feb 2014 12:19:55 -0800 Subject: [PATCH 173/183] utils/apparmor/tools.py: fix misimport of UI stuff as well as a paren depth error in use_autodep() --- utils/apparmor/tools.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/utils/apparmor/tools.py b/utils/apparmor/tools.py index 01c016632..23d704c8a 100644 --- a/utils/apparmor/tools.py +++ b/utils/apparmor/tools.py @@ -15,6 +15,7 @@ import os import sys import apparmor.aa as apparmor +import apparmor.ui as aaui from apparmor.common import user_perm # setup module translations @@ -74,9 +75,9 @@ class aa_tools: if not program or not(os.path.exists(program) or apparmor.profile_exists(program)): if program and not program.startswith('/'): - program = apparmor.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') + program = aaui.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '') else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.") % p) + aaui.UI_Info(_("%s does not exist, please double-check the path.") % p) sys.exit(1) if self.name == 'autodep' and program and os.path.exists(program): @@ -90,21 +91,21 @@ class aa_tools: filename = apparmor.get_profile_filename(program) if not os.path.isfile(filename) or apparmor.is_skippable_file(filename): - apparmor.UI_Info(_('Profile for %s not found, skipping') % p) + aaui.UI_Info(_('Profile for %s not found, skipping') % p) elif self.name == 'disable': if not self.revert: - apparmor.UI_Info(_('Disabling %s.') % program) + aaui.UI_Info(_('Disabling %s.') % program) self.disable_profile(filename) else: - apparmor.UI_Info(_('Enabling %s.') % program) + aaui.UI_Info(_('Enabling %s.') % program) self.enable_profile(filename) elif self.name == 'audit': if not self.remove: - apparmor.UI_Info(_('Setting %s to audit mode.') % program) + aaui.UI_Info(_('Setting %s to audit mode.') % program) else: - apparmor.UI_Info(_('Removing audit mode from %s.') % program) + aaui.UI_Info(_('Removing audit mode from %s.') % program) apparmor.change_profile_flags(filename, program, 'audit', not self.remove) elif self.name == 'complain': @@ -125,9 +126,9 @@ class aa_tools: else: if '/' not in p: - apparmor.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % (p, p)) + aaui.UI_Info(_("Can't find %s in the system path list. If the name of the application\nis correct, please run 'which %s' as a user with correct PATH\nenvironment set up in order to find the fully-qualified path and\nuse the full path as parameter.") % (p, p)) else: - apparmor.UI_Info(_("%s does not exist, please double-check the path.") % p) + aaui.UI_Info(_("%s does not exist, please double-check the path.") % p) sys.exit(1) def clean_profile(self, program, p): @@ -136,7 +137,7 @@ class aa_tools: prof = cleanprofile.Prof(filename) cleanprof = cleanprofile.CleanProf(True, prof, prof) deleted = cleanprof.remove_duplicate_rules(program) - apparmor.UI_Info(_("\nDeleted %s rules.") % deleted) + aaui.UI_Info(_("\nDeleted %s rules.") % deleted) apparmor.changed[program] = True if filename: @@ -153,7 +154,7 @@ class aa_tools: ans = '' arg = None while ans != 'CMD_SAVE_CHANGES': - ans, arg = apparmor.UI_PromptUser(q) + ans, arg = aaui.UI_PromptUser(q) if ans == 'CMD_SAVE_CHANGES': apparmor.write_profile_ui_feedback(program) apparmor.reload_base(program) @@ -170,8 +171,8 @@ class aa_tools: def use_autodep(self, program): apparmor.check_qualifiers(program) - if os.path.exists(apparmor.get_profile_filename(program) and not self.force): - apparmor.UI_Info('Profile for %s already exists - skipping.' % program) + if os.path.exists(apparmor.get_profile_filename(program)) and not self.force: + aaui.UI_Info('Profile for %s already exists - skipping.' % program) else: apparmor.autodep(program) if self.aa_mountpoint: From a482139616e7e4ab06bd04d1f298ff31c0e72a0f Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 14 Feb 2014 12:25:13 -0800 Subject: [PATCH 174/183] utils/aa-autodep: make --force be a boolean argument, not require an additional value --- utils/aa-autodep | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/aa-autodep b/utils/aa-autodep index b05471269..04ec0d924 100755 --- a/utils/aa-autodep +++ b/utils/aa-autodep @@ -21,7 +21,7 @@ from apparmor.translations import init_translation _ = init_translation() parser = argparse.ArgumentParser(description=_('Generate a basic AppArmor profile by guessing requirements')) -parser.add_argument('--force', type=str, help=_('overwrite existing profile')) +parser.add_argument('--force', action='store_true', default=False, help=_('overwrite existing profile')) parser.add_argument('-d', '--dir', type=str, help=_('path to profiles')) parser.add_argument('program', type=str, nargs='+', help=_('name of program')) args = parser.parse_args() From e9c30a9361e377117df2be555de147ab7ab22db9 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 14 Feb 2014 14:28:12 -0600 Subject: [PATCH 175/183] libthai-data is used by LibThai which is the library used to deal with Thai-specific functions like word-breaking, input and output methods and basic character and string support. This is: https://launchpad.net/bugs/1278702 Acked-By: Jamie Strandboge Acked-by: Steve Beattie --- profiles/apparmor.d/abstractions/fonts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/profiles/apparmor.d/abstractions/fonts b/profiles/apparmor.d/abstractions/fonts index 85a86ab9b..e75166900 100644 --- a/profiles/apparmor.d/abstractions/fonts +++ b/profiles/apparmor.d/abstractions/fonts @@ -52,3 +52,6 @@ # poppler CMap tables /usr/share/poppler/cMap/** r, + + # data files for LibThai + /usr/share/libthai/thbrk.tri r, From 8a0951be18a66ac520c58e75b02c21cfa7c53025 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 14 Feb 2014 16:24:52 -0600 Subject: [PATCH 176/183] = Background = MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The xdg-user-dirs specification[1] allows for translatable and movable common directories. While this may be beneficial for users who for example want to have ~/Pictures translated into their own language, this flexibility provides challenges for AppArmor. Untranslated xdg user directories are typically (see ~/.config/user-dirs.dirs): XDG_DESKTOP_DIR="$HOME/Desktop" XDG_DOWNLOAD_DIR="$HOME/Downloads" XDG_TEMPLATES_DIR="$HOME/Templates" XDG_PUBLICSHARE_DIR="$HOME/Public" XDG_DOCUMENTS_DIR="$HOME/Documents" XDG_MUSIC_DIR="$HOME/Music" XDG_PICTURES_DIR="$HOME/Pictures" XDG_VIDEOS_DIR="$HOME/Videos" On an Ubuntu system with the fr_CA locale installed, these become: XDG_DESKTOP_DIR="$HOME/Desktop" XDG_DOWNLOAD_DIR="$HOME/Téléchargements" XDG_TEMPLATES_DIR="$HOME/Templates" XDG_PUBLICSHARE_DIR="$HOME/Public" XDG_DOCUMENTS_DIR="$HOME/Documents" XDG_MUSIC_DIR="$HOME/Musique" XDG_PICTURES_DIR="$HOME/Images" XDG_VIDEOS_DIR="$HOME/Vidéos" While the kernel and AppArmor parser handle these translations fine, the profiles do not. As an upstream, we can vastly improve the situation by simply creating the xdg-user-dirs tunable using the default 'C' xdg-user-dirs values: $ cat /etc/apparmor.d/tunables/xdg-user-dirs @{XDG_DESKTOP_DIR}=Desktop @{XDG_DOWNLOAD_DIR}=Downloads @{XDG_TEMPLATES_DIR}=Templates @{XDG_PUBLICSHARE_DIR}=Public @{XDG_DOCUMENTS_DIR}=Documents @{XDG_MUSIC_DIR}=Music @{XDG_PICTURES_DIR}=Pictures @{XDG_VIDEOS_DIR}=Videos # Also, include files in tunables/xdg-user-dirs.d for site-specific adjustments # to the various XDG directories #include and then create the /etc/apparmor.d/tunables/xdg-user-dirs.d directory. With that alone, we can start using rules like this in policy: owner @{HOME}/@{XDG_MUSIC_DIR}/** r, and users/admins can adjust /etc/apparmor.d/tunables/xdg-user-dirs or drop files into /etc/apparmor.d/tunables/xdg-user-dirs.d, providing a welcome convenience. This of course doesn't solve everything. Because users can modify their ~/.config/user-dirs.dirs file at will and have it point anywhere, so we can't examine those files and do anything automatic there (when we have user policy we can revisit this). This patch handles translations well though since use of translations for these directories happens outside of the user's control. Users who modify ~/.config/user-dirs.dirs can update policy like they need to now (ie, this patch doesn't change anything for them). [0] https://lists.ubuntu.com/archives/apparmor/2013-August/004183.html [1] http://freedesktop.org/wiki/Software/xdg-user-dirs/ This patch adds basic support for XDG user dirs: 1. Update profiles/apparmor.d/tunables/global to include xdg-user-dirs. 2. Create the xdg-user-dirs tunable using the default 'C' xdg-user-dirs values and includes tunables/xdg-user-dirs.d 3. Add profiles/apparmor.d/tunables/xdg-user-dirs.d/site.local with commented out examples on how to use the directory. Acked-By: Jamie Strandboge Acked-By: Christian Boltz --- profiles/apparmor.d/tunables/global | 3 ++- profiles/apparmor.d/tunables/xdg-user-dirs | 24 +++++++++++++++++++ .../tunables/xdg-user-dirs.d/site.local | 21 ++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 profiles/apparmor.d/tunables/xdg-user-dirs create mode 100644 profiles/apparmor.d/tunables/xdg-user-dirs.d/site.local diff --git a/profiles/apparmor.d/tunables/global b/profiles/apparmor.d/tunables/global index 69d827b91..58d087fbe 100644 --- a/profiles/apparmor.d/tunables/global +++ b/profiles/apparmor.d/tunables/global @@ -1,7 +1,7 @@ # ------------------------------------------------------------------ # # Copyright (C) 2006-2009 Novell/SUSE -# Copyright (C) 2010-2011 Canonical Ltd. +# Copyright (C) 2010-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 @@ -17,3 +17,4 @@ #include #include #include +#include diff --git a/profiles/apparmor.d/tunables/xdg-user-dirs b/profiles/apparmor.d/tunables/xdg-user-dirs new file mode 100644 index 000000000..fcaf8d40d --- /dev/null +++ b/profiles/apparmor.d/tunables/xdg-user-dirs @@ -0,0 +1,24 @@ +# ------------------------------------------------------------------ +# +# 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. +# +# ------------------------------------------------------------------ + +# Define the common set of XDG user directories (usually defined in +# /etc/xdg/user-dirs.defaults) +@{XDG_DESKTOP_DIR}="Desktop" +@{XDG_DOWNLOAD_DIR}="Downloads" +@{XDG_TEMPLATES_DIR}="Templates" +@{XDG_PUBLICSHARE_DIR}="Public" +@{XDG_DOCUMENTS_DIR}="Documents" +@{XDG_MUSIC_DIR}="Music" +@{XDG_PICTURES_DIR}="Pictures" +@{XDG_VIDEOS_DIR}="Videos" + +# Also, include files in tunables/xdg-user-dirs.d for site-specific adjustments +# to the various XDG directories +#include diff --git a/profiles/apparmor.d/tunables/xdg-user-dirs.d/site.local b/profiles/apparmor.d/tunables/xdg-user-dirs.d/site.local new file mode 100644 index 000000000..8fcabfa0d --- /dev/null +++ b/profiles/apparmor.d/tunables/xdg-user-dirs.d/site.local @@ -0,0 +1,21 @@ +# ------------------------------------------------------------------ +# +# 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. +# +# ------------------------------------------------------------------ + +# The following may be used to add additional entries such as for +# translations. See tunables/xdg-user-dirs for details. Eg: +#@{XDG_MUSIC_DIR}+="Musique" + +#@{XDG_DESKTOP_DIR}+="" +#@{XDG_DOWNLOAD_DIR}+="" +#@{XDG_TEMPLATES_DIR}+="" +#@{XDG_PUBLICSHARE_DIR}+="" +#@{XDG_DOCUMENTS_DIR}+="" +#@{XDG_MUSIC_DIR}+="" +#@{XDG_PICTURES_DIR}+="" +#@{XDG_VIDEOS_DIR}+="" From 6812e5e550b15bf2d6cdb347e0af4ec2bd12f3e1 Mon Sep 17 00:00:00 2001 From: Jamie Strandboge Date: Fri, 14 Feb 2014 16:28:16 -0600 Subject: [PATCH 177/183] Update abstractions to use new XDG_*_DIR values. Thanks to Christian Boltz for the suggestion to use @{XDG_DOWNLOAD_DIR} in abstractions/user-download as well as the existing entries. Acked-By: Jamie Strandboge Acked-By: Christian Boltz --- .../apparmor.d/abstractions/user-download | 7 +++++-- profiles/apparmor.d/abstractions/user-write | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/profiles/apparmor.d/abstractions/user-download b/profiles/apparmor.d/abstractions/user-download index efa946068..ffe1a1ff5 100644 --- a/profiles/apparmor.d/abstractions/user-download +++ b/profiles/apparmor.d/abstractions/user-download @@ -1,6 +1,7 @@ # ------------------------------------------------------------------ # # Copyright (C) 2002-2006 Novell/SUSE +# 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 @@ -15,7 +16,9 @@ owner @{HOME}/[dD]ownload{,s}/ r, owner @{HOME}/[dD]ownload{,s}/** rwl, owner @{HOME}/[a-zA-Z0-9]* rwl, - owner @{HOME}/Desktop/ r, - owner @{HOME}/Desktop/* rwl, + owner @{HOME}/@{XDG_DESKTOP_DIR}/ r, + owner @{HOME}/@{XDG_DESKTOP_DIR}/* rwl, + owner @{HOME}/@{XDG_DOWNLOAD_DIR}/ r, + owner @{HOME}/@{XDG_DOWNLOAD_DIR}/* rwl, owner "@{HOME}/My Downloads/" r, owner "@{HOME}/My Downloads/**" rwl, diff --git a/profiles/apparmor.d/abstractions/user-write b/profiles/apparmor.d/abstractions/user-write index adf8773f7..79a550aac 100644 --- a/profiles/apparmor.d/abstractions/user-write +++ b/profiles/apparmor.d/abstractions/user-write @@ -1,6 +1,7 @@ # ------------------------------------------------------------------ # # Copyright (C) 2002-2006 Novell/SUSE +# 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 @@ -9,12 +10,12 @@ # ------------------------------------------------------------------ # per-user write directories - owner @{HOME}/ r, - owner @{HOME}/Desktop/ r, - owner @{HOME}/Documents/ r, - owner @{HOME}/Public/ r, - owner @{HOME}/[a-zA-Z0-9]*/ rw, - owner @{HOME}/[a-zA-Z0-9]* rwl, - owner @{HOME}/Desktop/** rwl, - owner @{HOME}/Documents/** rwl, - owner @{HOME}/Public/** rwl, + owner @{HOME}/ r, + owner @{HOME}/@{XDG_DESKTOP_DIR}/ r, + owner @{HOME}/@{XDG_DOCUMENTS_DIR}/ r, + owner @{HOME}/@{XDG_PUBLICSHARE_DIR}/ r, + owner @{HOME}/[a-zA-Z0-9]*/ rw, + owner @{HOME}/[a-zA-Z0-9]* rwl, + owner @{HOME}/@{XDG_DESKTOP_DIR}/** rwl, + owner @{HOME}/@{XDG_DOCUMENTS_DIR}/** rwl, + owner @{HOME}/@{XDG_PUBLICSHARE_DIR}/** rwl, From 503d95167345f671987c9608387dc18cf2ac5652 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 14 Feb 2014 23:37:13 +0100 Subject: [PATCH 178/183] update abstractions/winbind - some *.dat files live in a different directory nowadays (at least in openSUSE) - the openSUSE smb.conf includes the (autogenerated) dhcp.conf, so this file also needs to be readable. References: https://bugzilla.novell.com/show_bug.cgi?id=863226 Acked-by: Seth Arnold --- profiles/apparmor.d/abstractions/winbind | 2 ++ 1 file changed, 2 insertions(+) diff --git a/profiles/apparmor.d/abstractions/winbind b/profiles/apparmor.d/abstractions/winbind index bc06f2c60..e982889ea 100644 --- a/profiles/apparmor.d/abstractions/winbind +++ b/profiles/apparmor.d/abstractions/winbind @@ -13,7 +13,9 @@ /tmp/.winbindd/pipe rw, /var/{lib,run}/samba/winbindd_privileged/pipe rw, /etc/samba/smb.conf r, + /etc/samba/dhcp.conf r, /usr/lib*/samba/valid.dat r, /usr/lib*/samba/upcase.dat r, /usr/lib*/samba/lowcase.dat r, + /usr/share/samba/codepages/{lowcase,upcase,valid}.dat r, From ec7676bdecc48cec5344cbe944208e01577a43fc Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 14 Feb 2014 14:42:19 -0800 Subject: [PATCH 179/183] utils/aa-*: adjust python shebang lines to ease rewriting to an alternate python if installed via the python-tools-setup.py script. --- utils/aa-audit | 2 +- utils/aa-autodep | 2 +- utils/aa-cleanprof | 2 +- utils/aa-complain | 2 +- utils/aa-disable | 2 +- utils/aa-enforce | 2 +- utils/aa-genprof | 2 +- utils/aa-logprof | 2 +- utils/aa-mergeprof | 2 +- utils/aa-status | 2 +- utils/aa-unconfined | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/utils/aa-audit b/utils/aa-audit index f6382b322..092a8cd32 100755 --- a/utils/aa-audit +++ b/utils/aa-audit @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-autodep b/utils/aa-autodep index 04ec0d924..16ac97bdd 100755 --- a/utils/aa-autodep +++ b/utils/aa-autodep @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-cleanprof b/utils/aa-cleanprof index b03261d43..ed23d3b39 100755 --- a/utils/aa-cleanprof +++ b/utils/aa-cleanprof @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-complain b/utils/aa-complain index 7394d1200..b6caa572e 100755 --- a/utils/aa-complain +++ b/utils/aa-complain @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-disable b/utils/aa-disable index a463e57d6..d7200138d 100755 --- a/utils/aa-disable +++ b/utils/aa-disable @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-enforce b/utils/aa-enforce index 0bc5c0582..a540d9b5b 100755 --- a/utils/aa-enforce +++ b/utils/aa-enforce @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-genprof b/utils/aa-genprof index c38e614bd..385ce7b89 100755 --- a/utils/aa-genprof +++ b/utils/aa-genprof @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-logprof b/utils/aa-logprof index 8b77ca335..1d0546c4c 100755 --- a/utils/aa-logprof +++ b/utils/aa-logprof @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index c26bd9a07..ac149c2f3 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # diff --git a/utils/aa-status b/utils/aa-status index 49104fe27..cc0807ca0 100755 --- a/utils/aa-status +++ b/utils/aa-status @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ------------------------------------------------------------------ # # Copyright (C) 2005-2006 Novell/SUSE diff --git a/utils/aa-unconfined b/utils/aa-unconfined index f92813ee4..7082242be 100755 --- a/utils/aa-unconfined +++ b/utils/aa-unconfined @@ -1,4 +1,4 @@ -#!/usr/bin/python +#! /usr/bin/env python # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # From b98c40181cff7b01b4908757a1ffc86cfd040a55 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Fri, 14 Feb 2014 22:52:31 -0800 Subject: [PATCH 180/183] utils/apparmor/yasti.py: remove XXX comments; when the time comes for someone to attempt to see if things work with the python ycp bindings, they'll discover whether the functions are the right ones or not. --- utils/apparmor/yasti.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/apparmor/yasti.py b/utils/apparmor/yasti.py index 5b7b2a351..180e7152a 100644 --- a/utils/apparmor/yasti.py +++ b/utils/apparmor/yasti.py @@ -42,7 +42,7 @@ def SendDataToYast(data): ycommand, ypath, yargument = ParseCommand(line) if ycommand and ycommand == 'Read': debug_logger.info('SendDataToYast: Sending--%s' % data) - ycp.Return(data) # XXX is this right? + ycp.Return(data) return True else: debug_logger.info('SendDataToYast: Expected \'Read\' but got-- %s' % line) @@ -55,7 +55,7 @@ def GetDataFromYast(): ycommand, ypath, yarg = ParseCommand(line) debug_logger.info('GetDataFromYast: Recieved--\n%s' % yarg) if ycommand and ycommand == 'Write': - ycp.Return('true') # XXX is this right? + ycp.Return('true') return ypath, yarg else: debug_logger.info('GetDataFromYast: Expected Write but got-- %s' % line) @@ -98,7 +98,7 @@ def ParseTerm(inp): inp = regex_term.sub('', inp) if not inp.startswith('('): ycp.y2error('No term parantheses') - argref, err, rest = ycp.ParseYcpTermBody(inp) # XXX + argref, err, rest = ycp.ParseYcpTermBody(inp) if err: ycp.y2error('%s (%s)' % (err, rest)) else: From c77143b5424f32659afa778c2317a24de02bf4ca Mon Sep 17 00:00:00 2001 From: Seth Arnold Date: Sun, 16 Feb 2014 22:12:43 -0800 Subject: [PATCH 181/183] Bump library version numbers for a 2.8.95 pre-release of 2.9. --- libraries/libapparmor/src/Makefile.am | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/libapparmor/src/Makefile.am b/libraries/libapparmor/src/Makefile.am index 0c9a88a39..00d2b85f9 100644 --- a/libraries/libapparmor/src/Makefile.am +++ b/libraries/libapparmor/src/Makefile.am @@ -18,8 +18,8 @@ INCLUDES = $(all_includes) # release, then # - set AA_LIB_AGE to 0. # -AA_LIB_CURRENT = 1 -AA_LIB_REVISION = 2 +AA_LIB_CURRENT = 2 +AA_LIB_REVISION = 0 AA_LIB_AGE = 0 SUFFIXES = .pc.in .pc From ea8e02412e237b01d263a2188611e8dbe97cfdf0 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Mon, 17 Feb 2014 22:56:02 +0100 Subject: [PATCH 182/183] dnsmasq profile - NetworkManager integration This is an updated version of the previous dnsmasq profile patch, again from develop7 [at] develop7.info Acked-by: John Johansen --- profiles/apparmor.d/usr.sbin.dnsmasq | 3 +++ 1 file changed, 3 insertions(+) diff --git a/profiles/apparmor.d/usr.sbin.dnsmasq b/profiles/apparmor.d/usr.sbin.dnsmasq index 6b20b93d8..0bb30eb2c 100644 --- a/profiles/apparmor.d/usr.sbin.dnsmasq +++ b/profiles/apparmor.d/usr.sbin.dnsmasq @@ -29,6 +29,8 @@ /etc/dnsmasq.d/ r, /etc/dnsmasq.d/* r, /etc/ethers r, + /etc/NetworkManager/dnsmasq.d/ r, + /etc/NetworkManager/dnsmasq.d/* r, /usr/sbin/dnsmasq mr, @@ -56,6 +58,7 @@ /{,var/}run/nm-dns-dnsmasq.conf r, /{,var/}run/sendsigs.omit.d/*dnsmasq.pid w, /{,var/}run/NetworkManager/dnsmasq.conf r, + /{,var/}run/NetworkManager/dnsmasq.pid w, # Site-specific additions and overrides. See local/README for details. #include From 192ca1dc57294b1a3ebaf57ef929638353b4af13 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Thu, 20 Feb 2014 16:53:18 -0800 Subject: [PATCH 183/183] parser: exit with error on invalid arguments The parser currently indicates that it exited successfully if invalid arguments are passed to it, which makes it difficult to detect when other tools are calling it incorrectly. This patch causes it to return '1' indicating a failure. Signed-off-by: Steve Beattie Acked-by: John Johansen --- parser/parser_main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser_main.c b/parser/parser_main.c index e4d8d1041..72a382507 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -572,7 +572,7 @@ static int process_arg(int c, char *optarg) break; default: display_usage(progname); - exit(0); + exit(1); break; }