From a160c18398714637900a62d74d84aefb4e6ab0e9 Mon Sep 17 00:00:00 2001 From: Egor Tensin Date: Sun, 5 Mar 2023 14:41:28 +0100 Subject: add bad_attrs.py --- bad_attrs.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100755 bad_attrs.py (limited to 'bad_attrs.py') diff --git a/bad_attrs.py b/bad_attrs.py new file mode 100755 index 0000000..fc1f4d8 --- /dev/null +++ b/bad_attrs.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2023 Egor Tensin +# This file is part of the "audit-scripts" project. +# For details, see https://github.com/egor-tensin/audit-scripts. +# Distributed under the MIT License. + +import argparse +import array +from contextlib import contextmanager +import errno +import fcntl +import logging +import os +import stat +import sys + + +@contextmanager +def setup_logging(): + logging.basicConfig( + format='%(asctime)s | %(levelname)s | %(message)s', + datefmt='%Y-%m-%d %H:%M:%S', + level=logging.DEBUG) + try: + yield + except Exception as e: + logging.exception(e) + raise + + +def scandir(dir_path): + try: + entry_it = os.scandir(dir_path) + except (PermissionError, FileNotFoundError) as e: + logging.warning('%s', e) + return + with entry_it: + yield from entry_it + + +def traverse_tree(root): + queue = [root] + while queue: + for entry in scandir(queue.pop(0)): + if entry.is_dir(follow_symlinks=False): + if os.path.ismount(entry.path): + continue + queue.append(entry.path) + yield entry + + +def skip_leaf(entry): + if entry.is_dir(follow_symlinks=False): + return False + if entry.is_file(follow_symlinks=False): + return False + return True + + +@contextmanager +def low_level_open(path, flags): + fd = os.open(path, flags) + try: + yield fd + finally: + os.close(fd) + + +FS_IOC_GETFLAGS = 0x80086601 + +FS_IMMUTABLE_FL = 0x00000010 +FS_APPEND_FL = 0x00000020 + +BAD_FLAGS = [FS_IMMUTABLE_FL, FS_APPEND_FL] + + +def flags_contain_bad_flags(flags): + return any([flags & bad_flag for bad_flag in BAD_FLAGS]) + + +def fd_get_flags(fd): + a = array.array('L', [0]) + fcntl.ioctl(fd, FS_IOC_GETFLAGS, a, True) + return a[0] + + +def path_get_flags(path): + with low_level_open(path, os.O_RDONLY) as fd: + return fd_get_flags(fd) + + +def path_has_bad_flags(path): + try: + flags = path_get_flags(path) + except OSError as e: + if e.errno == errno.ENOTTY or e.errno == errno.EPERM: + # Either one of: + # Inappropriate ioctl for device + # Permission denied + logging.warning('%s: %s', path, e) + return False + raise + return flags_contain_bad_flags(flags) + + +def do_dir(root): + logging.info('Directory: %s', root) + for entry in traverse_tree(root): + if skip_leaf(entry): + continue + #logging.debug('Path: %s', entry.path) + if path_has_bad_flags(entry.path): + logging.warning('Bad flags: %s', entry.path) + + +def parse_args(argv=None): + if argv is None: + argv = sys.argv[1:] + parser = argparse.ArgumentParser() + parser.add_argument('dir', metavar='DIR', + help='set root directory') + return parser.parse_args() + + +def main(argv=None): + with setup_logging(): + args = parse_args() + do_dir(args.dir) + + +if __name__ == '__main__': + main() -- cgit v1.2.3