diff --git a/plugins/file.py b/plugins/file.py index 906ad88..ae13869 100644 --- a/plugins/file.py +++ b/plugins/file.py @@ -11,35 +11,35 @@ # inode types # see https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/stat.h -S_IFMT = 0o170000 # inode type mask -S_IFSOCK = 0o140000 # socket -S_IFLNK = 0o120000 # symbolic link -S_IFREG = 0o100000 # regular file -S_IFBLK = 0o60000 # block device -S_IFDIR = 0o40000 # directory -S_IFCHR = 0o20000 # character device -S_IFIFO = 0o10000 # fifo (pipe) -S_ISUID = 0o4000 -S_ISGID = 0o2000 -S_ISVTX = 0o1000 +S_IFMT = 0o170000 # inode type mask +S_IFSOCK = 0o140000 # socket +S_IFLNK = 0o120000 # symbolic link +S_IFREG = 0o100000 # regular file +S_IFBLK = 0o60000 # block device +S_IFDIR = 0o40000 # directory +S_IFCHR = 0o20000 # character device +S_IFIFO = 0o10000 # fifo (pipe) +S_ISUID = 0o4000 +S_ISGID = 0o2000 +S_ISVTX = 0o1000 # user permissions -S_IRWXU = 0o700 # user permissions mask -S_IRUSR = 0o400 # user read -S_IWUSR = 0o200 # user write -S_IXUSR = 0o100 # user execute +S_IRWXU = 0o700 # user permissions mask +S_IRUSR = 0o400 # user read +S_IWUSR = 0o200 # user write +S_IXUSR = 0o100 # user execute # group permissions -S_IRWXG = 0o070 # group permissions mask -S_IRGRP = 0o040 # group read -S_IWGRP = 0o020 # group write -S_IXGRP = 0o010 # group execute +S_IRWXG = 0o070 # group permissions mask +S_IRGRP = 0o040 # group read +S_IWGRP = 0o020 # group write +S_IXGRP = 0o010 # group execute # other permissions -S_IRWXO = 0o007 # other permissions mask -S_IROTH = 0o004 # other read -S_IWOTH = 0o002 # other write -S_IXOTH = 0o001 # other execute +S_IRWXO = 0o007 # other permissions mask +S_IROTH = 0o004 # other read +S_IWOTH = 0o002 # other write +S_IXOTH = 0o001 # other execute vollog = logging.getLogger(__name__) @@ -83,7 +83,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] description='Sort files by path', optional=True) ] - + @classmethod def create_mount_filter(cls, mnt_list: List[int] = None) -> Callable[[Any], bool]: """Constructs a filter function for mount IDs. @@ -104,7 +104,7 @@ def filter_func(mount): return filter_func else: return lambda _: False - + @classmethod def create_path_filter(cls, path) -> Callable[[Any], bool]: """Constructs a filter function for file paths. @@ -118,9 +118,9 @@ def create_path_filter(cls, path) -> Callable[[Any], bool]: def filter_func(x): return not x.startswith(path) - + return filter_func - + @classmethod def create_uid_filter(cls, uid_list: List[int] = None) -> Callable[[Any], bool]: """Constructs a filter function for owner UIDs. @@ -137,13 +137,13 @@ def create_uid_filter(cls, uid_list: List[int] = None) -> Callable[[Any], bool]: def filter_func(uid): return uid not in filter_list - + return filter_func else: return lambda _: False @classmethod - def _mode_to_str(cls, mode:int) -> str: + def _mode_to_str(cls, mode: int) -> str: """Calculate the mode string (see http://cvsweb.netbsd.org/bsdweb.cgi/src/lib/libc/string/strmode.c?rev=1.16&content-type=text/x-cvsweb-markup)""" string = '' @@ -165,7 +165,7 @@ def _mode_to_str(cls, mode:int) -> str: string += 'p' else: string += '?' - + # get user permissions string += 'r' if mode & S_IRUSR else '-' string += 'w' if mode & S_IWUSR else '-' @@ -178,7 +178,7 @@ def _mode_to_str(cls, mode:int) -> str: string += 'S' elif user_execute == S_IXUSR | S_ISUID: string += 's' - + # get group permissions string += 'r' if mode & S_IRGRP else '-' string += 'w' if mode & S_IWGRP else '-' @@ -220,7 +220,8 @@ def get_file_info(cls, """ # get file path try: - path = symbols.linux.LinuxUtilities.prepend_path(dentry, mount, task.fs.root) + path = symbols.linux.LinuxUtilities.prepend_path( + dentry, mount, task.fs.root) except exceptions.PagedInvalidAddressException: path = '' else: @@ -240,7 +241,7 @@ def get_file_info(cls, inode_id = -1 inode_addr = 0 inode = None - + # get file info mode = '' uid = -1 @@ -272,7 +273,7 @@ def get_file_info(cls, accessed = inode.i_atime.tv_sec return mnt_id, inode_id, inode_addr, mode, uid, gid, size, created, modified, accessed, path - + @classmethod def _walk_dentry(cls, context: interfaces.context.ContextInterface, @@ -300,8 +301,9 @@ def _walk_dentry(cls, field_name = 'd_child' if dentry.has_member('d_child') else 'd_u' for subdir_dentry in dentry.d_subdirs.to_list(symbol_table + constants.BANG + 'dentry', field_name): # walk subdir dentry - cls._walk_dentry(context, vmlinux_module_name, dentry_set, subdir_dentry) - + cls._walk_dentry(context, vmlinux_module_name, + dentry_set, subdir_dentry) + @classmethod def get_dentries(cls, context: interfaces.context.ContextInterface, @@ -319,15 +321,18 @@ def get_dentries(cls, if pid_filter is None: pid_filter = pslist.PsList.create_pid_filter([1]) - non_filtered_mounts = mount_plugin.Mount.get_mounts(context, vmlinux_module_name, pid_filter) - + non_filtered_mounts = mount_plugin.Mount.get_mounts( + context, vmlinux_module_name, pid_filter) + # filter out mounts - mounts = [(task, mount) for task, mount in non_filtered_mounts if not mnt_filter(mount)] + mounts = [(task, mount) + for task, mount in non_filtered_mounts if not mnt_filter(mount)] num_mounts = len(mounts) for i, (task, mount) in enumerate(mounts): - vollog.info(f'[{i}/{num_mounts}] listing files for mount ID {mount.mnt_id}') - + vollog.info( + f'[{i}/{num_mounts}] listing files for mount ID {mount.mnt_id}') + # set of dentry addresses for this mount mount_dentries = set() @@ -335,15 +340,17 @@ def get_dentries(cls, root_dentry = mount.get_mnt_root().dereference() # walk root dentry and extract all dentries recursively - cls._walk_dentry(context, vmlinux_module_name, mount_dentries, root_dentry) + cls._walk_dentry(context, vmlinux_module_name, + mount_dentries, root_dentry) # add dentries for this mount to global list for dentry_ptr in mount_dentries: - dentry = vmlinux.object(object_type='dentry', offset=dentry_ptr, absolute=True) + dentry = vmlinux.object( + object_type='dentry', offset=dentry_ptr, absolute=True) dentries.append((task, mount, dentry)) - + return dentries - + def _generator(self): # create path and UID filters path_filter = self.create_path_filter(self.config.get('path', None)) @@ -351,14 +358,14 @@ def _generator(self): # get requested PIDs pids = self.config.get('pid') - + # if a mount list was specified but PID list wasn't, extract mounts from all PIDs if self.config.get('mount') and not pids: # get PIDs of all tasks pids = [] for task in pslist.PsList.list_tasks(self.context, self.config['kernel']): pids.append(task.pid) - + # build PID filter if pids: pid_filter = pslist.PsList.create_pid_filter(pids) @@ -378,7 +385,8 @@ def _generator(self): for i, (task, mount, dentry) in enumerate(dentries): # print info message every 1000 files if i % 1000 == 0: - vollog.info(f'[{i}/{num_dentries}] extracting file info and filtering paths') + vollog.info( + f'[{i}/{num_dentries}] extracting file info and filtering paths') info = self.get_file_info(task, mount, dentry) # info could not be extracted @@ -389,7 +397,7 @@ def _generator(self): # apply path and UID filters if not path_filter(file_path) and not uid_filter(uid): files[file_path] = mnt_id, inode_id, inode_addr, mode, uid, gid, size, created, modified, accessed, file_path - + paths = list(files.keys()) # sort files by path if self.config.get('sort', None): @@ -397,8 +405,9 @@ def _generator(self): paths.sort() vollog.info('done sorting') for path in paths: - mnt_id, inode_id, inode_addr, mode, uid, gid, size, created, modified, accessed, file_path = files[path] + mnt_id, inode_id, inode_addr, mode, uid, gid, size, created, modified, accessed, file_path = files[ + path] yield (0, (mnt_id, inode_id, format_hints.Hex(inode_addr), mode, uid, gid, size, created, modified, accessed, file_path)) - + def run(self): return renderers.TreeGrid([('Mount ID', int), ('Inode ID', int), ('Inode Address', format_hints.Hex), ('Mode', str), ('UID', int), ('GID', int), ('Size', int), ('Created', int), ('Modified', int), ('Accessed', int), ('File Path', str)], self._generator()) diff --git a/plugins/ifconfig.py b/plugins/ifconfig.py index 677f430..fed78da 100644 --- a/plugins/ifconfig.py +++ b/plugins/ifconfig.py @@ -26,16 +26,17 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] ] @classmethod - def _get_devs_namespaces(cls, + def _get_devs_namespaces(cls, context: interfaces.context.ContextInterface, vmlinux_module_name: str) -> Iterable[Tuple[int, symbols.linux.extensions.net_device]]: """Walk the list of net namespaces and extract all net devices from them (kernel >= 2.6.24).""" vmlinux = context.modules[vmlinux_module_name] symbol_table = vmlinux.symbol_table_name - net_namespace_list = vmlinux.object_from_symbol(symbol_name='net_namespace_list') - - # enumerate each network namespace (struct net) in memory and pass the first one + net_namespace_list = vmlinux.object_from_symbol( + symbol_name='net_namespace_list') + + # enumerate each network namespace (struct net) in memory and pass the first one for net_ns in net_namespace_list.to_list(symbol_table + constants.BANG + 'net', 'list', sentinel=True): try: ns_num = net_ns.get_inum() @@ -45,15 +46,16 @@ def _get_devs_namespaces(cls, # for each net namespace, walk the list of net devices for net_dev in net_ns.dev_base_head.to_list(symbol_table + constants.BANG + 'net_device', 'dev_list', sentinel=True): yield ns_num, net_dev - + @classmethod def _get_devs_base(cls, - context: interfaces.context.ContextInterface, - vmlinux_module_name: str) -> Iterable[Tuple[int, symbols.linux.extensions.net_device]]: + context: interfaces.context.ContextInterface, + vmlinux_module_name: str) -> Iterable[Tuple[int, symbols.linux.extensions.net_device]]: """Walk the list of net devices headed by dev_base (kernel < 2.6.22).""" vmlinux = context.modules[vmlinux_module_name] - first_net_device = vmlinux.object_from_symbol(symbol_name='dev_base').dereference() + first_net_device = vmlinux.object_from_symbol( + symbol_name='dev_base').dereference() for net_dev in symbols.linux.LinuxUtilities.walk_internal_list(vmlinux, 'net_device', 'next', first_net_device): # no network namespace, so yield -1 instead of namespace number @@ -74,13 +76,15 @@ def get_net_devs(cls, func = cls._get_devs_base # kernel 2.6.22 and 2.6.23 elif vmlinux.has_symbol('dev_name_head'): - vollog.error('Cannot extract net devices from kernel versions 2.6.22 - 2.6.23') + vollog.error( + 'Cannot extract net devices from kernel versions 2.6.22 - 2.6.23') return # other unsupported kernels else: - vollog.error("Unable to determine ifconfig information. Probably because it's an old kernel") + vollog.error( + "Unable to determine ifconfig information. Probably because it's an old kernel") return - + # yield net devices for net_ns, dev in func(context, vmlinux_module_name): yield net_ns, dev @@ -101,7 +105,8 @@ def get_net_dev_info(cls, # get MAC address mac_addr = '' for netdev_hw_addr in net_dev.dev_addrs.list.to_list(symbol_table + constants.BANG + 'netdev_hw_addr', 'list', sentinel=True): - mac_addr = ':'.join(['{0:02x}'.format(x) for x in netdev_hw_addr.addr][:6]) + mac_addr = ':'.join(['{0:02x}'.format(x) + for x in netdev_hw_addr.addr][:6]) # use only first address break @@ -113,14 +118,15 @@ def get_net_dev_info(cls, except exceptions.PagedInvalidAddressException: ipv4_addr = '' ipv4_prefixlen = 0 - + # get IPv6 info ipv6_addr = '' ipv6_prefixlen = 0 try: inet6_dev = net_dev.ip6_ptr.dereference() for inet6_ifaddr in inet6_dev.addr_list.to_list(symbol_table + constants.BANG + 'inet6_ifaddr', 'if_list', sentinel=True): - ipv6_addr = conversion.convert_ipv6(inet6_ifaddr.addr.in6_u.u6_addr32) + ipv6_addr = conversion.convert_ipv6( + inet6_ifaddr.addr.in6_u.u6_addr32) ipv6_prefixlen = inet6_ifaddr.prefix_len # use only first address break @@ -132,13 +138,14 @@ def get_net_dev_info(cls, return name, mac_addr, ipv4_addr, ipv4_prefixlen, ipv6_addr, ipv6_prefixlen, promisc - def _generator(self): + def _generator(self): # get all network devices for _, net_dev in self.get_net_devs(self.context, self.config['kernel']): # extract information from each device - info = self.get_net_dev_info(self.context, self.config['kernel'], net_dev) + info = self.get_net_dev_info( + self.context, self.config['kernel'], net_dev) name, mac_addr, ipv4_addr, ipv4_prefixlen, ipv6_addr, ipv6_prefixlen, promisc = info - + # convert to CIDR notation if ipv4_addr: ipv4 = ipv4_addr + '/' + str(ipv4_prefixlen) diff --git a/plugins/mount.py b/plugins/mount.py index eea3264..1e8c8d6 100644 --- a/plugins/mount.py +++ b/plugins/mount.py @@ -14,35 +14,36 @@ # mount flags - see https://elixir.bootlin.com/linux/v5.15-rc5/source/include/linux/mount.h#L26 MNT_FLAGS = { - 0x1 : "MNT_NOSUID", - 0x2 : "MNT_NODEV", - 0x4 : "MNT_NOEXEC", - 0x8 : "MNT_NOATIME", - 0x10 : "MNT_NODIRATIME", - 0x20 : "MNT_RELATIME", - 0x40 : "MNT_READONLY", - 0x80 : "MNT_NOSYMFOLLOW", - 0x100 : "MNT_SHRINKABLE", - 0x200 : "MNT_WRITE_HOLD", - 0x1000 : "MNT_SHARED", - 0x2000 : "MNT_UNBINDABLE", - 0x4000 : "MNT_INTERNAL", - 0x40000 : "MNT_LOCK_ATIME", - 0x80000 : "MNT_LOCK_NOEXEC", - 0x100000 : "MNT_LOCK_NOSUID", - 0x200000 : "MNT_LOCK_NODEV", - 0x400000 : "MNT_LOCK_READONLY", - 0x800000 : "MNT_LOCKED", - 0x1000000 : "MNT_DOOMED", - 0x2000000 : "MNT_SYNC_UMOUNT", - 0x4000000 : "MNT_MARKED", - 0x8000000 : "MNT_UMOUNT", - 0x10000000 : "MNT_CURSOR" + 0x1: "MNT_NOSUID", + 0x2: "MNT_NODEV", + 0x4: "MNT_NOEXEC", + 0x8: "MNT_NOATIME", + 0x10: "MNT_NODIRATIME", + 0x20: "MNT_RELATIME", + 0x40: "MNT_READONLY", + 0x80: "MNT_NOSYMFOLLOW", + 0x100: "MNT_SHRINKABLE", + 0x200: "MNT_WRITE_HOLD", + 0x1000: "MNT_SHARED", + 0x2000: "MNT_UNBINDABLE", + 0x4000: "MNT_INTERNAL", + 0x40000: "MNT_LOCK_ATIME", + 0x80000: "MNT_LOCK_NOEXEC", + 0x100000: "MNT_LOCK_NOSUID", + 0x200000: "MNT_LOCK_NODEV", + 0x400000: "MNT_LOCK_READONLY", + 0x800000: "MNT_LOCKED", + 0x1000000: "MNT_DOOMED", + 0x2000000: "MNT_SYNC_UMOUNT", + 0x4000000: "MNT_MARKED", + 0x8000000: "MNT_UMOUNT", + 0x10000000: "MNT_CURSOR" } # for determining access -MNT_READONLY = 0x40 # https://elixir.bootlin.com/linux/v5.15-rc4/source/include/linux/mount.h#L32 -SB_RDONLY = 0x1 # https://elixir.bootlin.com/linux/v5.15-rc4/source/include/linux/fs.h#L1394 +# https://elixir.bootlin.com/linux/v5.15-rc4/source/include/linux/mount.h#L32 +MNT_READONLY = 0x40 +SB_RDONLY = 0x1 # https://elixir.bootlin.com/linux/v5.15-rc4/source/include/linux/fs.h#L1394 vollog = logging.getLogger(__name__) @@ -74,8 +75,8 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] description='Sort mounts by mount ID', optional=True, default=False) - ] - + ] + @classmethod def get_all_mounts(cls, context: interfaces.context.ContextInterface, @@ -96,26 +97,29 @@ def get_all_mounts(cls, else: mnt_type = 'vfsmount' mount_hashtable_type = 'list_head' - + # in kernel < 3.13.9 mount_hashtable size is predefined if mount_hashtable_type == 'list_head': list_head_size = vmlinux.get_type('list_head').size page_size = layer.page_size - mount_hashtable_entries = 1 << int(math.log(page_size/list_head_size, 2)) - + mount_hashtable_entries = 1 << int( + math.log(page_size/list_head_size, 2)) + # in kernel >= 3.13.9 mount_hashtable size is determined at boot time else: # m_hash_mask is the binary mask of the number of entries - mount_hashtable_entries = vmlinux.object_from_symbol('m_hash_mask') + 1 - + mount_hashtable_entries = vmlinux.object_from_symbol( + 'm_hash_mask') + 1 + vollog.info(f'mount_hashtable entries: {mount_hashtable_entries}') mount_hashtable_ptr = vmlinux.object_from_symbol('mount_hashtable') mount_hashtable = vmlinux.object(object_type='array', - offset=mount_hashtable_ptr, - subtype=vmlinux.get_type(mount_hashtable_type), - count=mount_hashtable_entries, - absolute=True) + offset=mount_hashtable_ptr, + subtype=vmlinux.get_type( + mount_hashtable_type), + count=mount_hashtable_entries, + absolute=True) # iterate through mount_hashtable for hash in mount_hashtable: @@ -136,7 +140,8 @@ def get_all_mounts(cls, if mount.mnt_id < 0: continue try: - devname = utility.pointer_to_string(mount.mnt_devname, MAX_STRING) + devname = utility.pointer_to_string( + mount.mnt_devname, MAX_STRING) except exceptions.PagedInvalidAddressException: continue else: @@ -172,19 +177,20 @@ def get_mounts(cls, try: mnt_ns = task.get_mnt_ns() except AttributeError as ex: - vollog.error(f'No mount namespace information available: {str(ex)}') + vollog.error( + f'No mount namespace information available: {str(ex)}') return except exceptions.PagedInvalidAddressException: vollog.error(f'Cannot extract mounts from pid {task.pid}') continue - + # get identifier for mnt_ns try: identifier = mnt_ns.get_inum() # in kernel < 3.8 mnt_namespace has no inum, track address of the mnt_namespace struct instead except AttributeError: identifier = mnt_ns.vol.offset - + # make sure we haven't seen this namespace yet if identifier in seen_mnt_namespaces: continue @@ -200,7 +206,7 @@ def get_mounts(cls, def get_mount_info(cls, context: interfaces.context.ContextInterface, vmlinux_module_name: str, - mount:symbols.linux.extensions.mount, + mount: symbols.linux.extensions.mount, task: symbols.linux.extensions.task_struct) -> Tuple[int, str, str, str, str, str, str]: """Parse a mount and return the following tuple: id, devname, path, absolute_path, fstype, access, flags @@ -227,7 +233,8 @@ def get_mount_info(cls, # get path if task is not None: try: - path = symbols.linux.LinuxUtilities.prepend_path(mount.get_mnt_root().dereference(), mount, task.fs.root) + path = symbols.linux.LinuxUtilities.prepend_path( + mount.get_mnt_root().dereference(), mount, task.fs.root) except exceptions.PagedInvalidAddressException: path = '' else: @@ -241,7 +248,8 @@ def get_mount_info(cls, mnt_parent = mount.mnt_parent.dereference() mnt_root = mount.get_mnt_root().dereference() try: - path = symbols.linux.LinuxUtilities._do_get_path(s_root, mnt_parent, mnt_root, mount) + path = symbols.linux.LinuxUtilities._do_get_path( + s_root, mnt_parent, mnt_root, mount) except exceptions.PagedInvalidAddressException: path = '' @@ -251,7 +259,7 @@ def get_mount_info(cls, # when a mount has a master, its absolute path is the master's path if mount.mnt_master != 0: root_mnt = mount.mnt_master.dereference() - + # otherwise, the mount's absolute path is calculated by treating its root as belonging to the absolute fs root mount else: root_mnt = init_task.fs.root.mnt.dereference() @@ -260,7 +268,8 @@ def get_mount_info(cls, # the absolute path is calculated relative to the fs root of the init task try: - absolute_path = symbols.linux.LinuxUtilities.prepend_path(dentry, root_mnt, init_task.fs.root) + absolute_path = symbols.linux.LinuxUtilities.prepend_path( + dentry, root_mnt, init_task.fs.root) except exceptions.PagedInvalidAddressException: absolute_path = '' else: @@ -270,7 +279,8 @@ def get_mount_info(cls, # get fs typee try: - fs_type = utility.pointer_to_string(mount.get_mnt_sb().dereference().s_type.dereference().name, MAX_STRING) + fs_type = utility.pointer_to_string( + mount.get_mnt_sb().dereference().s_type.dereference().name, MAX_STRING) except exceptions.PagedInvalidAddressException: fs_type = '' @@ -293,7 +303,7 @@ def get_mount_info(cls, flags.append(MNT_FLAGS[flag]) except KeyError: flags.append(f'FLAG_{hex(flag)}') - + return mnt_id, parent_id, devname, path, absolute_path, fs_type, access, ','.join(flags) def _generator(self): @@ -307,21 +317,24 @@ def _generator(self): if not pids: pids = [1] pid_filter = pslist.PsList.create_pid_filter(pids) - mounts = self.get_mounts(self.context, self.config['kernel'], pid_filter) - + mounts = self.get_mounts( + self.context, self.config['kernel'], pid_filter) + # sort mounts by ID if self.config.get('sort', False): - mounts_by_id = {mount.mnt_id: (task, mount) for task, mount in mounts} + mounts_by_id = {mount.mnt_id: (task, mount) + for task, mount in mounts} ids = list(mounts_by_id.keys()) ids.sort() mounts = [mounts_by_id[id] for id in ids] for task, mount in mounts: yield (0, self.get_mount_info(self.context, self.config['kernel'], mount, task=task)) - + def run(self): # make sure 'all' and 'pid' aren't used together if self.config.get('all') and self.config.get('pid'): - raise exceptions.PluginRequirementException('"pid" and "all" cannot be used together') + raise exceptions.PluginRequirementException( + '"pid" and "all" cannot be used together') return renderers.TreeGrid([('Mount ID', int), ('Parent ID', int), ('Devname', str), ('Path', str), ('Absolute Path', str), ('FS Type', str), ('Access', str), ('Flags', str)], self._generator())