blob: f2b6a84fa5998ae4c866bd0d49560bc2d26d3a0e [file] [log] [blame]
Ivan Suzdal9f9fbf42018-07-11 18:27:25 +04001"""
2Module for finding executable files with suid or sgid bit.
3It was implemented in opposite to using __salt__[cmd.run](find)
4due to complexity of filter logic in audit rules template.
5"""
6
7import multiprocessing.dummy
8import functools
9import logging
10import stat
11import os
12import salt.utils
13
14log = logging.getLogger(__name__)
15
16def __virtual__():
17 if salt.utils.is_windows():
18 return False
19 return 'auditd'
20
21def _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
Dzmitry Stremkouski6fbb5c02018-07-21 08:33:56 +020042 if not os.path.exists(fpath):
43 continue
Ivan Suzdal9f9fbf42018-07-11 18:27:25 +040044 mode = os.stat(fpath).st_mode
45 if (mode & e_bits) and (mode & s_bits):
46 yield fpath
47
48def find_privileged(filter_fs=None, filter_paths=None, filter_mounts=True):
49
50 filter_fs = filter_fs or []
51 filtered = filter_paths or []
52 mounts = []
53
54 for part, mount_opts in __salt__['mount.active']().items():
55 if mount_opts['fstype'] in filter_fs:
56 # Must be skipped according to the fstype filter.
57 log.debug("Add '%s' to filtered (filtered fs)", part)
58 filtered.append(part)
59 continue
60 if filter_mounts:
61 if set(['noexec', 'nosuid']) & set(mount_opts['opts']):
62 # It has noexec/nosuid option in mount options.
63 # Must be skipped.
64 log.debug("Add '%s' to filtered (mount options)", part)
65 filtered.append(part)
66 continue
67 if part == '/':
68 continue
69 mounts.append(part)
70
71 # Collect all directories for parallel scanning
72 scan_dirs = ['/'+d for d in os.listdir('/') if os.path.isdir('/'+d)]
73
74 # Add all mount points into the scan dirs list
75 for mount_point in mounts:
76 if mount_point in scan_dirs:
77 # It is already in the scan list
78 continue
79 if any([mount_point.startswith(f) for f in filtered]):
80 # Such mount point must be skipped
81 # (implements `-prune`/`-not` behavior of the find command)
82 continue
83 scan_dirs.append(mount_point)
84
85 pool = multiprocessing.dummy.Pool(len(scan_dirs))
86 find = functools.partial(_find_privileged_files,
87 filtered=filtered,
88 mounts=mounts)
89
90 p = pool.map_async(find, scan_dirs)
91
92 return sorted([item for res in p.get() for item in res])