Ivan Suzdal | 9f9fbf4 | 2018-07-11 18:27:25 +0400 | [diff] [blame^] | 1 | """ |
| 2 | Module for finding executable files with suid or sgid bit. |
| 3 | It was implemented in opposite to using __salt__[cmd.run](find) |
| 4 | due to complexity of filter logic in audit rules template. |
| 5 | """ |
| 6 | |
| 7 | import multiprocessing.dummy |
| 8 | import functools |
| 9 | import logging |
| 10 | import stat |
| 11 | import os |
| 12 | import salt.utils |
| 13 | |
| 14 | log = logging.getLogger(__name__) |
| 15 | |
| 16 | def __virtual__(): |
| 17 | if salt.utils.is_windows(): |
| 18 | return False |
| 19 | return 'auditd' |
| 20 | |
| 21 | def _find_privileged_files(path, filtered=None, mounts=None): |
| 22 | """Find executable files with suid or sbig bit. |
| 23 | The function accepts list of paths wich will be skiped in search. |
| 24 | """ |
| 25 | |
| 26 | mounts = mounts or [] |
| 27 | filtered = filtered or [] |
| 28 | e_bits = stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH |
| 29 | s_bits = stat.S_ISUID | stat.S_ISGID |
| 30 | |
| 31 | for root, dirs, files in os.walk(path, topdown=True): |
| 32 | if (root in mounts and root != path) or root in filtered: |
| 33 | # Filter all mount points which lies down of the path |
| 34 | # (implements the `-xdev` of the find command), |
| 35 | # as well as all "filtered" paths (`-prune`/`-not`). |
| 36 | dirs[:] = [] |
| 37 | files[:] = [] |
| 38 | for fname in files: |
| 39 | fpath = os.path.join(root, fname) |
| 40 | if os.path.islink(fpath): |
| 41 | continue |
| 42 | mode = os.stat(fpath).st_mode |
| 43 | if (mode & e_bits) and (mode & s_bits): |
| 44 | yield fpath |
| 45 | |
| 46 | def find_privileged(filter_fs=None, filter_paths=None, filter_mounts=True): |
| 47 | |
| 48 | filter_fs = filter_fs or [] |
| 49 | filtered = filter_paths or [] |
| 50 | mounts = [] |
| 51 | |
| 52 | for part, mount_opts in __salt__['mount.active']().items(): |
| 53 | if mount_opts['fstype'] in filter_fs: |
| 54 | # Must be skipped according to the fstype filter. |
| 55 | log.debug("Add '%s' to filtered (filtered fs)", part) |
| 56 | filtered.append(part) |
| 57 | continue |
| 58 | if filter_mounts: |
| 59 | if set(['noexec', 'nosuid']) & set(mount_opts['opts']): |
| 60 | # It has noexec/nosuid option in mount options. |
| 61 | # Must be skipped. |
| 62 | log.debug("Add '%s' to filtered (mount options)", part) |
| 63 | filtered.append(part) |
| 64 | continue |
| 65 | if part == '/': |
| 66 | continue |
| 67 | mounts.append(part) |
| 68 | |
| 69 | # Collect all directories for parallel scanning |
| 70 | scan_dirs = ['/'+d for d in os.listdir('/') if os.path.isdir('/'+d)] |
| 71 | |
| 72 | # Add all mount points into the scan dirs list |
| 73 | for mount_point in mounts: |
| 74 | if mount_point in scan_dirs: |
| 75 | # It is already in the scan list |
| 76 | continue |
| 77 | if any([mount_point.startswith(f) for f in filtered]): |
| 78 | # Such mount point must be skipped |
| 79 | # (implements `-prune`/`-not` behavior of the find command) |
| 80 | continue |
| 81 | scan_dirs.append(mount_point) |
| 82 | |
| 83 | pool = multiprocessing.dummy.Pool(len(scan_dirs)) |
| 84 | find = functools.partial(_find_privileged_files, |
| 85 | filtered=filtered, |
| 86 | mounts=mounts) |
| 87 | |
| 88 | p = pool.map_async(find, scan_dirs) |
| 89 | |
| 90 | return sorted([item for res in p.get() for item in res]) |