diff options
author | Egor Tensin <Egor.Tensin@gmail.com> | 2023-03-05 14:49:48 +0100 |
---|---|---|
committer | Egor Tensin <Egor.Tensin@gmail.com> | 2023-03-05 14:50:37 +0100 |
commit | 406f804a8042874d5760709398d95c61ec33b6db (patch) | |
tree | b7ca5f4bf21f2be6b3d0c5cb4fbd399b7f49c786 /src/bad-attrs | |
parent | add README.md (diff) | |
download | audit-scripts-406f804a8042874d5760709398d95c61ec33b6db.tar.gz audit-scripts-406f804a8042874d5760709398d95c61ec33b6db.zip |
rename scripts
Diffstat (limited to 'src/bad-attrs')
-rwxr-xr-x | src/bad-attrs | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/src/bad-attrs b/src/bad-attrs new file mode 100755 index 0000000..4683540 --- /dev/null +++ b/src/bad-attrs @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2023 Egor Tensin <Egor.Tensin@gmail.com> +# 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 + # It's relied upon that fcntl throws OSError instead of + # PermissionError. + 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() |