From e40445dea73644f6404adadd1feedcbc428c5539 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Jun 2013 15:37:31 +0530 Subject: [PATCH 001/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] --- 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/101] --- 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] --- 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/101] 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/101] 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/101] --- 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/101] 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/101] 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/101] --- 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] --- 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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/101] 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 c82fda86b6b956f04d896e148433e2ddac6e9171 Mon Sep 17 00:00:00 2001 From: Kshitij Gupta Date: Sat, 1 Feb 2014 06:14:05 +0530 Subject: [PATCH 093/101] 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 094/101] --- 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 095/101] 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 395c429cb11f1cdaa41ca99ca95e68128aec60d6 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Mon, 10 Feb 2014 22:14:54 -0800 Subject: [PATCH 096/101] 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 097/101] 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 098/101] 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 099/101] 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 100/101] 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 b3b4fd448e4939fefe5c1b6ddf7f602f5238decf Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Wed, 12 Feb 2014 13:27:30 -0800 Subject: [PATCH 101/101] 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