| """ |
| Module for finding executable files with suid or sgid bit. |
| It was implemented in opposite to using __salt__[cmd.run](find) |
| due to complexity of filter logic in audit rules template. |
| """ |
| |
| import multiprocessing.dummy |
| import functools |
| import logging |
| import stat |
| import os |
| import salt.utils |
| |
| log = logging.getLogger(__name__) |
| |
| def __virtual__(): |
| if salt.utils.is_windows(): |
| return False |
| return 'auditd' |
| |
| def _find_privileged_files(path, filtered=None, mounts=None): |
| """Find executable files with suid or sbig bit. |
| The function accepts list of paths wich will be skiped in search. |
| """ |
| |
| mounts = mounts or [] |
| filtered = filtered or [] |
| e_bits = stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH |
| s_bits = stat.S_ISUID | stat.S_ISGID |
| |
| for root, dirs, files in os.walk(path, topdown=True): |
| if (root in mounts and root != path) or root in filtered: |
| # Filter all mount points which lies down of the path |
| # (implements the `-xdev` of the find command), |
| # as well as all "filtered" paths (`-prune`/`-not`). |
| dirs[:] = [] |
| files[:] = [] |
| for fname in files: |
| fpath = os.path.join(root, fname) |
| if os.path.islink(fpath): |
| continue |
| if not os.path.exists(fpath): |
| continue |
| mode = os.stat(fpath).st_mode |
| if (mode & e_bits) and (mode & s_bits): |
| yield fpath |
| |
| def find_privileged(filter_fs=None, filter_paths=None, filter_mounts=True): |
| |
| filter_fs = filter_fs or [] |
| filtered = filter_paths or [] |
| mounts = [] |
| |
| for part, mount_opts in __salt__['mount.active']().items(): |
| if mount_opts['fstype'] in filter_fs: |
| # Must be skipped according to the fstype filter. |
| log.debug("Add '%s' to filtered (filtered fs)", part) |
| filtered.append(part) |
| continue |
| if filter_mounts: |
| if set(['noexec', 'nosuid']) & set(mount_opts['opts']): |
| # It has noexec/nosuid option in mount options. |
| # Must be skipped. |
| log.debug("Add '%s' to filtered (mount options)", part) |
| filtered.append(part) |
| continue |
| if part == '/': |
| continue |
| mounts.append(part) |
| |
| # Collect all directories for parallel scanning |
| scan_dirs = ['/'+d for d in os.listdir('/') if os.path.isdir('/'+d)] |
| |
| # Add all mount points into the scan dirs list |
| for mount_point in mounts: |
| if mount_point in scan_dirs: |
| # It is already in the scan list |
| continue |
| if any([mount_point.startswith(f) for f in filtered]): |
| # Such mount point must be skipped |
| # (implements `-prune`/`-not` behavior of the find command) |
| continue |
| scan_dirs.append(mount_point) |
| |
| pool = multiprocessing.dummy.Pool(len(scan_dirs)) |
| find = functools.partial(_find_privileged_files, |
| filtered=filtered, |
| mounts=mounts) |
| |
| p = pool.map_async(find, scan_dirs) |
| |
| return sorted([item for res in p.get() for item in res]) |