blob: f2b6a84fa5998ae4c866bd0d49560bc2d26d3a0e [file] [log] [blame]
"""
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])