def AASetup(self):
        # Redirect sys.stdout to a buffer
        sys.stdout = io.StringIO()

        global _, debug_logger

        enable_aa_exception_handler()
        _ = init_translation()
        atexit.register(aa.on_exit)
        debug_logger = DebugLogger('Test AA')
        debug_logger.debug('Starting test')
 def __init__(self, pid, filename, existing_profiles, profile_dir):
     self.filename = filename
     self.profile_dir = profile_dir
     self.pid = pid
     self.existing_profiles = existing_profiles
     self.log = []
     self.debug_logger = DebugLogger('ReadLog')
     self.LOG = None
     self.logmark = ''
     self.seenmark = None
     self.next_log_entry = None
Beispiel #3
0
 def __init__(self, filename, active_profiles, profile_dir):
     self.filename = filename
     self.profile_dir = profile_dir
     self.active_profiles = active_profiles
     self.hashlog = {
         'PERMITTING': {},
         'REJECTING': {},
         'AUDIT': {}
     }  # structure inside {}: {'profilename': init_hashlog(aamode, profilename), 'profilename2': init_hashlog(...), ...}
     self.debug_logger = DebugLogger('ReadLog')
     self.LOG = None
     self.logmark = ''
     self.seenmark = None
     self.next_log_entry = None
Beispiel #4
0
 def __init__(self, pid, filename, existing_profiles, profile_dir, log):
     self.filename = filename
     self.profile_dir = profile_dir
     self.pid = pid
     self.existing_profiles = existing_profiles
     self.log = log
     self.debug_logger = DebugLogger('ReadLog')
     self.LOG = None
     self.logmark = ''
     self.seenmark = None
     self.next_log_entry = None
class ReadLog:
    RE_audit_time_id = '(msg=)?audit\([\d\.\:]+\):\s+'  # 'audit(1282626827.320:411): '
    RE_kernel_time = '\[[\d\.\s]+\]'  # '[ 1612.746129]'
    RE_type_num = '1[45][0-9][0-9]'  # 1400..1599
    RE_aa_or_op = '(apparmor=|operation=)'

    RE_log_parts = [
        'kernel:\s+(' + RE_kernel_time + '\s+)?(audit:\s+)?type=' +
        RE_type_num + '\s+' + RE_audit_time_id + RE_aa_or_op,  # v2_6 syslog
        'kernel:\s+(' + RE_kernel_time + '\s+)?' + RE_audit_time_id + 'type=' +
        RE_type_num + '\s+' + RE_aa_or_op,
        'type=(AVC|APPARMOR[_A-Z]*|' + RE_type_num + ')\s+' +
        RE_audit_time_id + '(type=' + RE_type_num + '\s+)?' +
        RE_aa_or_op,  # v2_6 audit and dmesg
        'type=USER_AVC\s+' + RE_audit_time_id + '.*apparmor=',  # dbus
        'type=UNKNOWN\[' + RE_type_num + '\]\s+' + RE_audit_time_id +
        RE_aa_or_op,
        'dbus\[[0-9]+\]:\s+apparmor=',  # dbus
    ]

    # used to pre-filter log lines so that we hand over only relevant lines to LibAppArmor parsing
    RE_LOG_ALL = re.compile('(' + '|'.join(RE_log_parts) + ')')

    def __init__(self, pid, filename, existing_profiles, profile_dir):
        self.filename = filename
        self.profile_dir = profile_dir
        self.pid = pid
        self.existing_profiles = existing_profiles
        self.log = []
        self.debug_logger = DebugLogger('ReadLog')
        self.LOG = None
        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)
        self.next_log_entry = self.LOG.readline()
        while not self.RE_LOG_ALL.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

    def get_next_log_entry(self):
        # If no next log entry fetch it
        if not self.next_log_entry:
            self.prefetch_next_log_entry()
        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()
        self.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
        ev['error_code'] = event.error_code
        ev['denied_mask'] = event.denied_mask
        ev['request_mask'] = event.requested_mask
        ev['magic_token'] = event.magic_token
        ev['family'] = event.net_family
        ev['protocol'] = event.net_protocol
        ev['sock_type'] = event.net_sock_type

        if event.ouid != 18446744073709551615:  # 2^64 - 1
            ev['fsuid'] = event.fsuid
            ev['ouid'] = event.ouid

        if ev['operation'] and ev['operation'] == 'signal':
            ev['signal'] = event.signal
            ev['peer'] = event.peer
        elif ev['operation'] and ev['operation'] == 'ptrace':
            ev['peer'] = event.peer
        elif ev['operation'] and ev['operation'].startswith('dbus_'):
            ev['peer_profile'] = event.peer_profile
            ev['bus'] = event.dbus_bus
            ev['path'] = event.dbus_path
            ev['interface'] = event.dbus_interface
            ev['member'] = event.dbus_member

        LibAppArmor.free_record(event)

        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: 'AUDIT',
                3: 'PERMITTING',
                4: 'REJECTING',
                5: 'HINT',
                6: 'STATUS'
            }
            try:
                ev['aamode'] = mode_convertor[ev['aamode']]
            except KeyError:
                ev['aamode'] = None

        # "translate" disconnected paths to errors, which means the event will be ignored.
        # XXX Ideally we should propose to add the attach_disconnected flag to the profile
        if ev['error_code'] == 13 and ev[
                'info'] == 'Failed name lookup - disconnected path':
            ev['aamode'] = 'ERROR'

        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):
            profile, hat = event[:2]
            if parent and self.pid.get(parent, False):
                if not hat:
                    hat = 'null-complain-profile'
                arrayref = []
                self.pid[parent].append(arrayref)
                self.pid[loc_pid] = arrayref
                for ia in ['fork', loc_pid, profile, hat]:
                    arrayref.append(ia)
#                 self.pid[parent].append(array_ref)
#                 self.pid[loc_pid] = array_ref
            else:
                arrayref = []
                self.log.append(arrayref)
                self.pid[loc_pid] = arrayref
#                 self.log.append(array_ref)
#                 self.pid[loc_pid] = array_ref
        self.pid[loc_pid].append([type, loc_pid] + event)
        #print("\n\npid",self.pid)
        #print("log",self.log)

    def add_event_to_tree(self, e):
        e = self.parse_event_for_tree(e)
        if e is not None:
            (pid, parent, mode, details) = e
            self.add_to_tree(pid, parent, mode, details)

    def parse_event_for_tree(self, e):
        aamode = e.get('aamode', 'UNKNOWN')

        if aamode == 'UNKNOWN':
            raise AppArmorBug('aamode is UNKNOWN - %s' %
                              e['type'])  # should never happen

        if aamode in ['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('//')[:2]

        # Filter out change_hat events that aren't from learning
        if e['operation'] == 'change_hat':
            if aamode != 'HINT' and aamode != 'PERMITTING':
                return None
            if e['error_code'] == 1 and e[
                    'info'] == 'unconfined can not change_hat':
                return None
            profile = e['name2']
            #hat = None
            if '//' in e['name2']:
                profile, hat = e['name2'].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':
            # convert rmask and dmask to mode arrays
            e['denied_mask'], e['name2'] = log_str_to_mode(
                e['profile'], e['denied_mask'], e['name2'])
            e['request_mask'], e['name2'] = log_str_to_mode(
                e['profile'], e['request_mask'], e['name2'])

            if e.get('info',
                     False) and e['info'] == 'mandatory profile missing':
                return (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']) or e.get(
                    'name', False):
                return (e['pid'], e['parent'], 'exec', [
                    profile, hat, prog, aamode, e['denied_mask'], e['name'], ''
                ])
            else:
                self.debug_logger.debug(
                    'parse_event_for_tree: dropped exec event in %s' %
                    e['profile'])

        elif self.op_type(e) == 'file':
            # Map c (create) and d (delete) to w (logging is more detailed than the profile language)
            rmask = e['request_mask']
            rmask = rmask.replace('c', 'w')
            rmask = rmask.replace('d', 'w')
            if not validate_log_mode(hide_log_mode(rmask)):
                raise AppArmorException(
                    _('Log contains unknown mode %s') % rmask)

            dmask = e['denied_mask']
            dmask = dmask.replace('c', 'w')
            dmask = dmask.replace('d', 'w')
            if not validate_log_mode(hide_log_mode(dmask)):
                raise AppArmorException(
                    _('Log contains unknown mode %s') % dmask)

            if e.get('ouid') is not None and e['fsuid'] == e['ouid']:
                # mark as "owner" event
                if '::' not in rmask:
                    rmask = '%s::' % rmask
                if '::' not in dmask:
                    dmask = '%s::' % dmask

            # convert rmask and dmask to mode arrays
            e['denied_mask'], e['name2'] = log_str_to_mode(
                e['profile'], dmask, e['name2'])
            e['request_mask'], e['name2'] = log_str_to_mode(
                e['profile'], rmask, e['name2'])

            # check if this is an exec event
            is_domain_change = False
            if e['operation'] == 'inode_permission' and (
                    e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING':
                following = self.peek_at_next_log_entry()
                if following:
                    entry = self.parse_log_record(following)
                    if entry and entry.get('info', False) == 'set profile':
                        is_domain_change = True
                        self.throw_away_next_log_entry()

            if is_domain_change:
                return (e['pid'], e['parent'], 'exec', [
                    profile, hat, prog, aamode, e['denied_mask'], e['name'],
                    e['name2']
                ])
            else:
                return (e['pid'], e['parent'], 'path', [
                    profile, hat, prog, aamode, e['denied_mask'], e['name'], ''
                ])

        elif e['operation'] == 'capable':
            return (e['pid'], e['parent'], 'capability',
                    [profile, hat, prog, aamode, 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 = []
            if self.pid.get(parent, False):
                self.pid[parent].append(arrayref)
            else:
                self.log.append(arrayref)
            self.pid[child].append(arrayref)
            for ia in ['fork', child, profile, hat]:
                arrayref.append(ia)
#             if self.pid.get(parent, False):
#                 self.pid[parent] += [arrayref]
#             else:
#                 self.log += [arrayref]
#             self.pid[child] = arrayref

        elif self.op_type(e) == 'net':
            return (e['pid'], e['parent'], 'netdomain', [
                profile, hat, prog, aamode, e['family'], e['sock_type'],
                e['protocol']
            ])
        elif e['operation'] == 'change_hat':
            return (e['pid'], e['parent'], 'unknown_hat',
                    [profile, hat, aamode, hat])
        elif e['operation'] == 'ptrace':
            if not e['peer']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty peer')
                return None
            if not e['denied_mask']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty denied_mask')
                return None

            return (e['pid'], e['parent'], 'ptrace',
                    [profile, hat, prog, aamode, e['denied_mask'], e['peer']])
        elif e['operation'] == 'signal':
            return (e['pid'], e['parent'], 'signal', [
                profile, hat, prog, aamode, e['denied_mask'], e['signal'],
                e['peer']
            ])
        elif e['operation'].startswith('dbus_'):
            return (e['pid'], e['parent'], 'dbus', [
                profile, hat, prog, aamode, e['denied_mask'], e['bus'],
                e['path'], e['name'], e['interface'], e['member'],
                e['peer_profile']
            ])
        else:
            self.debug_logger.debug('UNHANDLED: %s' % e)

    def read_log(self, logmark):
        self.logmark = logmark
        seenmark = True
        if self.logmark:
            seenmark = False
        #last = None
        #event_type = None
        try:
            #print(self.filename)
            self.LOG = open_file_read(self.filename)
        except IOError:
            raise AppArmorException('Can not read AppArmor logfile: ' +
                                    self.filename)
        #LOG = open_file_read(log_open)
        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:
                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:
                try:
                    self.add_event_to_tree(event)
                except AppArmorException as e:
                    ex_msg = (
                        '%(msg)s\n\nThis error was caused by the log line:\n%(logline)s'
                        % {
                            'msg': e.value,
                            'logline': line
                        })
                    # when py3 only: Drop the original AppArmorException by passing None as the parent exception
                    raise AppArmorBug(ex_msg)  # py3-only: from None

        self.LOG.close()
        self.logmark = ''
        return self.log

    # operation types that can be network or file operations
    # (used by op_type() which checks some event details to decide)
    OP_TYPE_FILE_OR_NET = {
        # Note: op_type() also uses some startswith() checks which are not listed here!
        'create',
        'post_create',
        'bind',
        'connect',
        'listen',
        'accept',
        'sendmsg',
        'recvmsg',
        'getsockname',
        'getpeername',
        'getsockopt',
        'setsockopt',
        'socket_create',
        'sock_shutdown',
        'open',
        'truncate',
        'mkdir',
        'mknod',
        'chmod',
        'chown',
        'rename_src',
        'rename_dest',
        'unlink',
        'rmdir',
        'symlink_create',
        'link',
        'sysctl',
        'getattr',
        'setattr',
        'xattr',
    }

    def op_type(self, event):
        """Returns the operation type if known, unkown otherwise"""

        if (event['operation'].startswith('file_')
                or event['operation'].startswith('inode_')
                or event['operation'] in self.OP_TYPE_FILE_OR_NET):
            # file or network event?
            if event['family'] and event['protocol'] and event['sock_type']:
                # 'unix' events also use keywords like 'connect', but protocol is 0 and should therefore be filtered out
                return 'net'
            elif event['denied_mask']:
                return 'file'
            else:
                raise AppArmorException('unknown file or network event type')

        else:
            return 'unknown'

    def profile_exists(self, program):
        """Returns True if profile exists, False otherwise"""
        # Check cache of profiles
        if self.existing_profiles.get(program, False):
            return True
        # Check the disk for profile
        prof_path = self.get_profile_filename(program)
        #print(prof_path)
        if os.path.isfile(prof_path):
            # Add to cache of profile
            self.existing_profiles[program] = prof_path
            return True
        return False

    def get_profile_filename(self, profile):
        """Returns the full profile name"""
        if profile.startswith('/'):
            # Remove leading /
            profile = profile[1:]
        else:
            profile = "profile_" + profile
        profile = profile.replace('/', '.')
        full_profilename = self.profile_dir + '/' + profile
        return full_profilename
Beispiel #6
0
class ReadLog:

    # used to pre-filter log lines so that we hand over only relevant lines to LibAppArmor parsing
    RE_LOG_ALL = re.compile('apparmor=|operation=|type=AVC')

    def __init__(self, filename, active_profiles, profile_dir):
        self.filename = filename
        self.profile_dir = profile_dir
        self.active_profiles = active_profiles
        self.hashlog = {
            'PERMITTING': {},
            'REJECTING': {},
            'AUDIT': {}
        }  # structure inside {}: {'profilename': init_hashlog(aamode, profilename), 'profilename2': init_hashlog(...), ...}
        self.debug_logger = DebugLogger('ReadLog')
        self.LOG = None
        self.logmark = ''
        self.seenmark = None
        self.next_log_entry = None

    def init_hashlog(self, aamode, profile):
        ''' initialize self.hashlog[aamode][profile] for all rule types'''

        if profile in self.hashlog[aamode].keys():
            return  # already initialized, don't overwrite existing data

        self.hashlog[aamode][profile] = {
            'final_name':
            profile,  # might be changed for null-* profiles based on exec decisions
            'capability': {},  # flat, no hasher needed
            'change_hat': {},  # flat, no hasher needed
            'change_profile':
            {},  # flat, no hasher needed  (at least in logparser which doesn't support EXEC MODE and EXEC COND)
            'dbus': hasher(),
            'exec': hasher(),
            'network': hasher(),
            'path': hasher(),
            'ptrace': hasher(),
            'signal': hasher(),
        }

    def prefetch_next_log_entry(self):
        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_ALL.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

    def get_next_log_entry(self):
        # If no next log entry fetch it
        if not self.next_log_entry:
            self.prefetch_next_log_entry()
        log_entry = self.next_log_entry
        self.next_log_entry = None
        return log_entry

    def parse_event(self, msg):
        """Parse the event from log into key value pairs"""
        msg = msg.strip()
        self.debug_logger.info('parse_event: %s' % 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
        ev['error_code'] = event.error_code
        ev['denied_mask'] = event.denied_mask
        ev['request_mask'] = event.requested_mask
        ev['magic_token'] = event.magic_token
        ev['family'] = event.net_family
        ev['protocol'] = event.net_protocol
        ev['sock_type'] = event.net_sock_type

        if event.ouid != ctypes.c_ulong(-1).value:  # ULONG_MAX
            ev['fsuid'] = event.fsuid
            ev['ouid'] = event.ouid

        if ev['operation'] and ev['operation'] == 'signal':
            ev['signal'] = event.signal
            ev['peer'] = event.peer
        elif ev['operation'] and ev['operation'] == 'ptrace':
            ev['peer'] = event.peer
        elif ev['operation'] and ev['operation'].startswith('dbus_'):
            ev['peer_profile'] = event.peer_profile
            ev['bus'] = event.dbus_bus
            ev['path'] = event.dbus_path
            ev['interface'] = event.dbus_interface
            ev['member'] = event.dbus_member

        LibAppArmor.free_record(event)

        if not ev['time']:
            ev['time'] = int(time.time())

        if ev['aamode']:
            # Convert aamode values to their counter-parts
            mode_convertor = {
                0: 'UNKNOWN',
                1: 'ERROR',
                2: 'AUDIT',
                3: 'PERMITTING',
                4: 'REJECTING',
                5: 'HINT',
                6: 'STATUS'
            }
            try:
                ev['aamode'] = mode_convertor[ev['aamode']]
            except KeyError:
                ev['aamode'] = None

        # "translate" disconnected paths to errors, which means the event will be ignored.
        # XXX Ideally we should propose to add the attach_disconnected flag to the profile
        if ev['error_code'] == 13 and ev[
                'info'] == 'Failed name lookup - disconnected path':
            ev['aamode'] = 'ERROR'

        if ev['aamode']:
            return ev
        else:
            return None

    def parse_event_for_tree(self, e):
        aamode = e.get('aamode', 'UNKNOWN')

        if aamode == 'UNKNOWN':
            raise AppArmorBug('aamode is UNKNOWN - %s' %
                              e['type'])  # should never happen

        if aamode in ['AUDIT', 'STATUS', 'ERROR']:
            return None

        # Skip if AUDIT event was issued due to a change_hat in unconfined mode
        if not e.get('profile', False):
            return None

        full_profile = e['profile']  # full, nested profile name
        self.init_hashlog(aamode, full_profile)

        # Convert new null profiles to old single level null profile
        if '//null-' in e['profile']:
            e['profile'] = 'null-complain-profile'

        profile, hat = split_name(e['profile'])

        if profile != 'null-complain-profile' and not self.profile_exists(
                profile):
            return None
        if e['operation'] == 'exec':
            if not e['name']:
                raise AppArmorException('exec without executed binary')

            if not e['name2']:
                e['name2'] = ''  # exec events in enforce mode don't have target=...

            self.hashlog[aamode][full_profile]['exec'][e['name']][
                e['name2']] = True
            return None

        elif self.op_type(e) == 'file':
            # Map c (create) and d (delete) to w (logging is more detailed than the profile language)
            dmask = e['denied_mask']
            dmask = dmask.replace('c', 'w')
            dmask = dmask.replace('d', 'w')

            owner = False

            if '::' in dmask:
                # old log styles used :: to indicate if permissions are meant for owner or other
                (owner_d, other_d) = dmask.split('::')
                if owner_d and other_d:
                    raise AppArmorException(
                        'Found log event with both owner and other permissions. Please open a bugreport!'
                    )
                if owner_d:
                    dmask = owner_d
                    owner = True
                else:
                    dmask = other_d

            if e.get('ouid') is not None and e['fsuid'] == e['ouid']:
                # in current log style, owner permissions are indicated by a match of fsuid and ouid
                owner = True

            for perm in dmask:
                if perm in 'mrwalk':  # intentionally not allowing 'x' here
                    self.hashlog[aamode][full_profile]['path'][
                        e['name']][owner][perm] = True
                else:
                    raise AppArmorException(
                        _('Log contains unknown mode %s') % dmask)

            return None

        elif e['operation'] == 'capable':
            self.hashlog[aamode][full_profile]['capability'][e['name']] = True
            return None

        elif self.op_type(e) == 'net':
            self.hashlog[aamode][full_profile]['network'][e['family']][
                e['sock_type']][e['protocol']] = True
            return None

        elif e['operation'] == 'change_hat':
            if e['error_code'] == 1 and e[
                    'info'] == 'unconfined can not change_hat':
                return None

            self.hashlog[aamode][full_profile]['change_hat'][e['name2']] = True
            return None

        elif e['operation'] == 'change_profile':
            self.hashlog[aamode][full_profile]['change_profile'][
                e['name2']] = True
            return None

        elif e['operation'] == 'ptrace':
            if not e['peer']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty peer')
                return None
            if not e['denied_mask']:
                self.debug_logger.debug(
                    'ignored garbage ptrace event with empty denied_mask')
                return None

            self.hashlog[aamode][full_profile]['ptrace'][e['peer']][
                e['denied_mask']] = True
            return None

        elif e['operation'] == 'signal':
            self.hashlog[aamode][full_profile]['signal'][e['peer']][
                e['denied_mask']][e['signal']] = True
            return None

        elif e['operation'].startswith('dbus_'):
            self.hashlog[aamode][full_profile]['dbus'][e['denied_mask']][
                e['bus']][e['path']][e['name']][e['interface']][e['member']][
                    e['peer_profile']] = True
            return None

        else:
            self.debug_logger.debug('UNHANDLED: %s' % e)

    def read_log(self, logmark):
        self.logmark = logmark
        seenmark = True
        if self.logmark:
            seenmark = False
        try:
            self.LOG = open_file_read(self.filename)
        except IOError:
            raise AppArmorException('Can not read AppArmor logfile: ' +
                                    self.filename)
        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:
                seenmark = True

            self.debug_logger.debug('read_log: seenmark = %s' % seenmark)
            if not seenmark:
                continue

            event = self.parse_event(line)
            if event:
                try:
                    self.parse_event_for_tree(event)

                except AppArmorException as e:
                    ex_msg = (
                        '%(msg)s\n\nThis error was caused by the log line:\n%(logline)s'
                        % {
                            'msg': e.value,
                            'logline': line
                        })
                    # when py3 only: Drop the original AppArmorException by passing None as the parent exception
                    raise AppArmorBug(ex_msg)  # py3-only: from None

        self.LOG.close()
        self.logmark = ''

        return self.hashlog

    # operation types that can be network or file operations
    # (used by op_type() which checks some event details to decide)
    OP_TYPE_FILE_OR_NET = {
        # Note: op_type() also uses some startswith() checks which are not listed here!
        'create',
        'post_create',
        'bind',
        'connect',
        'listen',
        'accept',
        'sendmsg',
        'recvmsg',
        'getsockname',
        'getpeername',
        'getsockopt',
        'setsockopt',
        'socket_create',
        'sock_shutdown',
        'open',
        'truncate',
        'mkdir',
        'mknod',
        'chmod',
        'chown',
        'rename_src',
        'rename_dest',
        'unlink',
        'rmdir',
        'symlink',
        'symlink_create',
        'link',
        'sysctl',
        'getattr',
        'setattr',
        'xattr',
    }

    def op_type(self, event):
        """Returns the operation type if known, unkown otherwise"""

        if (event['operation'].startswith('file_')
                or event['operation'].startswith('inode_')
                or event['operation'] in self.OP_TYPE_FILE_OR_NET):
            # file or network event?
            if event['family'] and event['protocol'] and event['sock_type']:
                # 'unix' events also use keywords like 'connect', but protocol is 0 and should therefore be filtered out
                return 'net'
            elif event['denied_mask']:
                return 'file'
            else:
                raise AppArmorException('unknown file or network event type')

        else:
            return 'unknown'

    def profile_exists(self, program):
        """Returns True if profile exists, False otherwise"""
        # Check cache of profiles
        if self.active_profiles.filename_from_profile_name(program):
            return True

        return False
Beispiel #7
0
import json
import sys
import re
import readline
import os
import tempfile
import subprocess

from apparmor.common import readkey, AppArmorException, DebugLogger

# setup module translations
from apparmor.translations import init_translation
_ = init_translation()

# Set up UI logger for separate messages from UI module
debug_logger = DebugLogger('UI')

# If Python3, wrap input in raw_input so make check passes
if not 'raw_input' in dir(__builtins__): raw_input = input

ARROWS = {'A': 'UP', 'B': 'DOWN', 'C': 'RIGHT', 'D': 'LEFT'}

UI_mode = 'text'

def write_json(jsonout):
   print(json.dumps(jsonout, sort_keys=False, separators=(',', ': ')))
   sys.stdout.flush()

def set_json_mode():
    global UI_mode
    UI_mode = 'json'
Beispiel #8
0
class ReadLog:
    RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?(audit:\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=')
    RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=')
    # Used by netdomain to identify the operation types
    # New socket names
    OPERATION_TYPES = {'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 __init__(self, pid, filename, existing_profiles, profile_dir, log):
        self.filename = filename
        self.profile_dir = profile_dir
        self.pid = pid
        self.existing_profiles = existing_profiles
        self.log = log
        self.debug_logger = DebugLogger('ReadLog')
        self.LOG = None
        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)
        self.next_log_entry = self.LOG.readline()
        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

    def get_next_log_entry(self):
        # If no next log entry fetch it
        if not self.next_log_entry:
            self.prefetch_next_log_entry()
        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()
        self.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
        ev['error_code'] = event.error_code
        ev['denied_mask'] = event.denied_mask
        ev['request_mask'] = event.requested_mask
        ev['magic_token'] = event.magic_token
        if ev['operation'] and self.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)

        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: 'AUDIT',
                              3: 'PERMITTING',
                              4: 'REJECTING',
                              5: 'HINT',
                              6: 'STATUS'
                              }
            try:
                ev['aamode'] = mode_convertor[ev['aamode']]
            except KeyError:
                ev['aamode'] = None

        # "translate" disconnected paths to errors, which means the event will be ignored.
        # XXX Ideally we should propose to add the attach_disconnected flag to the profile
        if ev['error_code'] == 13 and ev['info'] == 'Failed name lookup - disconnected path':
            ev['aamode'] = 'ERROR'

        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):
            profile, hat = event[:2]
            if parent and self.pid.get(parent, False):
                if not hat:
                    hat = 'null-complain-profile'
                arrayref = []
                self.pid[parent].append(arrayref)
                self.pid[loc_pid] = arrayref
                for ia in ['fork', loc_pid, profile, hat]:
                    arrayref.append(ia)
#                 self.pid[parent].append(array_ref)
#                 self.pid[loc_pid] = array_ref
            else:
                arrayref = []
                self.log.append(arrayref)
                self.pid[loc_pid] = arrayref
#                 self.log.append(array_ref)
#                 self.pid[loc_pid] = array_ref
        self.pid[loc_pid].append([type, loc_pid] + event)
        #print("\n\npid",self.pid)
        #print("log",self.log)

    def add_event_to_tree(self, 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('//')[:2]

        # 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']
            #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':
            # convert rmask and dmask to mode arrays
            e['denied_mask'],  e['name2'] = log_str_to_mode(e['profile'], e['denied_mask'], e['name2'])
            e['request_mask'], e['name2'] = log_str_to_mode(e['profile'], e['request_mask'], e['name2'])

            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']])
            elif (e.get('name2', False) and '//null-' in e['name2']) or e.get('name', False):
                self.add_to_tree(e['pid'], e['parent'], 'exec',
                                 [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 ( e['operation'].startswith('file_') or e['operation'].startswith('inode_') or
            e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'chmod', 'rename_src',
                                'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link',
                                'sysctl', 'getattr', 'setattr', 'xattr'] ):

            # for some reason, we get file_perm log events without request_mask, see https://bugs.launchpad.net/apparmor/+bug/1466812/
            if e['operation'] == 'file_perm' and e['request_mask'] is None:
                self.debug_logger.debug('UNHANDLED (missing request_mask): %s' % e)
                return None

            # Map c (create) to a and d (delete) to w (logging is more detailed than the profile language)
            rmask = e['request_mask']
            rmask = rmask.replace('c', 'a')
            rmask = rmask.replace('d', 'w')
            if not validate_log_mode(hide_log_mode(rmask)):
                raise AppArmorException(_('Log contains unknown mode %s') % rmask)

            dmask = e['denied_mask']
            dmask = dmask.replace('c', 'a')
            dmask = dmask.replace('d', 'w')
            if not validate_log_mode(hide_log_mode(dmask)):
                raise AppArmorException(_('Log contains unknown mode %s') % dmask)

            # convert rmask and dmask to mode arrays
            e['denied_mask'],  e['name2'] = log_str_to_mode(e['profile'], dmask, e['name2'])
            e['request_mask'], e['name2'] = log_str_to_mode(e['profile'], rmask, e['name2'])

            # check if this is an exec event
            is_domain_change = False
            if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING':
                following = self.peek_at_next_log_entry()
                if following:
                    entry = self.parse_log_record(following)
                    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'] == 'capable':
            self.add_to_tree(e['pid'], e['parent'], 'capability',
                             [profile, hat, prog, aamode, 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 = []
            if self.pid.get(parent, False):
                self.pid[parent].append(arrayref)
            else:
                self.log.append(arrayref)
            self.pid[child].append(arrayref)
            for ia in ['fork', child, profile, hat]:
                arrayref.append(ia)
#             if self.pid.get(parent, False):
#                 self.pid[parent] += [arrayref]
#             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']])
        elif e['operation'] == 'change_hat':
            self.add_to_tree(e['pid'], e['parent'], 'unknown_hat',
                             [profile, hat, aamode, hat])
        else:
            self.debug_logger.debug('UNHANDLED: %s' % e)

    def read_log(self, logmark):
        self.logmark = logmark
        seenmark = True
        if self.logmark:
            seenmark = False
        #last = None
        #event_type = None
        try:
            #print(self.filename)
            self.LOG = open_file_read(self.filename)
        except IOError:
            raise AppArmorException('Can not read AppArmor logfile: ' + self.filename)
        #LOG = open_file_read(log_open)
        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:
                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:
                self.add_event_to_tree(event)
        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
        if self.existing_profiles.get(program, False):
            return True
        # Check the disk for profile
        prof_path = self.get_profile_filename(program)
        #print(prof_path)
        if os.path.isfile(prof_path):
            # Add to cache of profile
            self.existing_profiles[program] = prof_path
            return True
        return False

    def get_profile_filename(self, profile):
        """Returns the full profile name"""
        if profile.startswith('/'):
            # Remove leading /
            profile = profile[1:]
        else:
            profile = "profile_" + profile
        profile = profile.replace('/', '.')
        full_profilename = self.profile_dir + '/' + profile
        return full_profilename
Beispiel #9
0
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
# ----------------------------------------------------------------------
import re
import sys
try:
    import ycp
except ImportError:
    # ycp isn't found everywhere.
    ycp = None

from apparmor.common import error, DebugLogger

# Set up UI logger for separate messages from YaST module
debug_logger = DebugLogger('YaST')


def setup_yast():
    # To-Do
    pass


def shutdown_yast():
    # To-Do
    pass


def yastLog(text):
    ycp.y2milestone(text)