aboutsummaryrefslogtreecommitdiff
path: root/seccomp_tools/mass_seccomp_editor/mass_seccomp_editor.py
diff options
context:
space:
mode:
Diffstat (limited to 'seccomp_tools/mass_seccomp_editor/mass_seccomp_editor.py')
-rwxr-xr-xseccomp_tools/mass_seccomp_editor/mass_seccomp_editor.py463
1 files changed, 238 insertions, 225 deletions
diff --git a/seccomp_tools/mass_seccomp_editor/mass_seccomp_editor.py b/seccomp_tools/mass_seccomp_editor/mass_seccomp_editor.py
index d8dd7626..8b283d4d 100755
--- a/seccomp_tools/mass_seccomp_editor/mass_seccomp_editor.py
+++ b/seccomp_tools/mass_seccomp_editor/mass_seccomp_editor.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-# Copyright 2021 The Chromium OS Authors. All rights reserved.
+# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -15,259 +15,272 @@ from typing import Any, Iterable, Optional
from dataclasses import dataclass, field
# Pre-compiled regexes.
-AMD64_RE = re.compile(r'.*(amd|x86_)64.*\.policy')
-X86_RE = re.compile(r'.*x86.*\.policy')
-AARCH64_RE = re.compile(r'.*a(arch|rm)64.*\.policy')
-ARM_RE = re.compile(r'.*arm(v7)?.*\.policy')
+AMD64_RE = re.compile(r".*(amd|x86_)64.*\.policy")
+X86_RE = re.compile(r".*x86.*\.policy")
+AARCH64_RE = re.compile(r".*a(arch|rm)64.*\.policy")
+ARM_RE = re.compile(r".*arm(v7)?.*\.policy")
@dataclass(frozen=True)
class Policies:
- """Dataclass to hold lists of policies which match certain types."""
- arm: list[str] = field(default_factory=list)
- x86_64: list[str] = field(default_factory=list)
- x86: list[str] = field(default_factory=list)
- arm64: list[str] = field(default_factory=list)
- none: list[str] = field(default_factory=list)
+ """Dataclass to hold lists of policies which match certain types."""
- def to_dict(self) -> dict[str, list[str]]:
- """Convert this class to a dictionary."""
- return {**self.__dict__}
+ arm: list[str] = field(default_factory=list)
+ x86_64: list[str] = field(default_factory=list)
+ x86: list[str] = field(default_factory=list)
+ arm64: list[str] = field(default_factory=list)
+ none: list[str] = field(default_factory=list)
+ def to_dict(self) -> dict[str, list[str]]:
+ """Convert this class to a dictionary."""
+ return {**self.__dict__}
-def main():
- """Run the program from cmd line"""
- args = parse_args()
- if all(x is None for x in [args.all, args.b64, args.b32, args.none]):
- print('Require at least one of {--all, --b64, --b32, --none}',
- file=sys.stderr)
- sys.exit(1)
- matches, success = find_potential_policy_files(args.packages)
-
- separated = Policies()
-
- for m in matches:
- if AMD64_RE.match(m):
- separated.x86_64.append(m)
- continue
- if X86_RE.match(m):
- separated.x86.append(m)
- continue
- if AARCH64_RE.match(m):
- separated.arm64.append(m)
- continue
- if ARM_RE.match(m):
- separated.arm.append(m)
- continue
- separated.none.append(m)
-
- syscall_lookup_table = _make_syscall_lookup_table(args)
-
- for (type_, val) in separated.to_dict().items():
- for fp in val:
- syscalls = syscall_lookup_table[type_]
- missing = check_missing_syscalls(syscalls, fp)
- if missing is None:
- print(f'E ({type_}) {fp}')
- elif len(missing) == 0:
- print(f'_ ({type_}) {fp}')
- else:
- missing_str = ','.join(missing)
- print(f'M ({type_}) {fp} :: {missing_str}')
-
- if not args.edit:
- sys.exit(0 if success else 2)
- for (type_, val) in separated.to_dict().items():
- for fp in val:
- syscalls = syscall_lookup_table[type_]
- if args.force:
- _confirm_add(fp, syscalls, args.yes)
- continue
- missing = check_missing_syscalls(syscalls, fp)
- if missing is None or len(missing) == 0:
- print(f'Already good for {fp} ({type_})')
- else:
- _confirm_add(fp, missing, args.yes)
+def main():
+ """Run the program from cmd line"""
+ args = parse_args()
+ if all(x is None for x in [args.all, args.b64, args.b32, args.none]):
+ print(
+ "Require at least one of {--all, --b64, --b32, --none}",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+ matches, success = find_potential_policy_files(args.packages)
+
+ separated = Policies()
+
+ for m in matches:
+ if AMD64_RE.match(m):
+ separated.x86_64.append(m)
+ continue
+ if X86_RE.match(m):
+ separated.x86.append(m)
+ continue
+ if AARCH64_RE.match(m):
+ separated.arm64.append(m)
+ continue
+ if ARM_RE.match(m):
+ separated.arm.append(m)
+ continue
+ separated.none.append(m)
+
+ syscall_lookup_table = _make_syscall_lookup_table(args)
+
+ for (type_, val) in separated.to_dict().items():
+ for fp in val:
+ syscalls = syscall_lookup_table[type_]
+ missing = check_missing_syscalls(syscalls, fp)
+ if missing is None:
+ print(f"E ({type_}) {fp}")
+ elif len(missing) == 0:
+ print(f"_ ({type_}) {fp}")
+ else:
+ missing_str = ",".join(missing)
+ print(f"M ({type_}) {fp} :: {missing_str}")
+
+ if not args.edit:
+ sys.exit(0 if success else 2)
+
+ for (type_, val) in separated.to_dict().items():
+ for fp in val:
+ syscalls = syscall_lookup_table[type_]
+ if args.force:
+ _confirm_add(fp, syscalls, args.yes)
+ continue
+ missing = check_missing_syscalls(syscalls, fp)
+ if missing is None or len(missing) == 0:
+ print(f"Already good for {fp} ({type_})")
+ else:
+ _confirm_add(fp, missing, args.yes)
- sys.exit(0 if success else 2)
+ sys.exit(0 if success else 2)
def _make_syscall_lookup_table(args: Any) -> dict[str, list[str]]:
- """Make lookup table, segmented by all/b32/b64/none policies.
-
- Args:
- args: Direct output from parse_args.
-
- Returns:
- dict of syscalls we want to search for in each policy file,
- where the key is the policy file arch, and the value is
- a list of syscalls as strings.
- """
- syscall_lookup_table = Policies().to_dict()
- if args.all:
- split_syscalls = [x.strip() for x in args.all.split(',')]
- for v in syscall_lookup_table.values():
- v.extend(split_syscalls)
- if args.b32:
- split_syscalls = [x.strip() for x in args.b32.split(',')]
- syscall_lookup_table['x86'].extend(split_syscalls)
- syscall_lookup_table['arm'].extend(split_syscalls)
- if args.b64:
- split_syscalls = [x.strip() for x in args.b64.split(',')]
- syscall_lookup_table['x86_64'].extend(split_syscalls)
- syscall_lookup_table['arm64'].extend(split_syscalls)
- if args.none:
- split_syscalls = [x.strip() for x in args.none.split(',')]
- syscall_lookup_table['none'].extend(split_syscalls)
- return syscall_lookup_table
+ """Make lookup table, segmented by all/b32/b64/none policies.
+
+ Args:
+ args: Direct output from parse_args.
+
+ Returns:
+ dict of syscalls we want to search for in each policy file,
+ where the key is the policy file arch, and the value is
+ a list of syscalls as strings.
+ """
+ syscall_lookup_table = Policies().to_dict()
+ if args.all:
+ split_syscalls = [x.strip() for x in args.all.split(",")]
+ for v in syscall_lookup_table.values():
+ v.extend(split_syscalls)
+ if args.b32:
+ split_syscalls = [x.strip() for x in args.b32.split(",")]
+ syscall_lookup_table["x86"].extend(split_syscalls)
+ syscall_lookup_table["arm"].extend(split_syscalls)
+ if args.b64:
+ split_syscalls = [x.strip() for x in args.b64.split(",")]
+ syscall_lookup_table["x86_64"].extend(split_syscalls)
+ syscall_lookup_table["arm64"].extend(split_syscalls)
+ if args.none:
+ split_syscalls = [x.strip() for x in args.none.split(",")]
+ syscall_lookup_table["none"].extend(split_syscalls)
+ return syscall_lookup_table
def _confirm_add(fp: str, syscalls: Iterable[str], noninteractive=None):
- """Interactive confirmation check you wish to add a syscall.
-
- Args:
- fp: filepath of the file to edit.
- syscalls: list-like of syscalls to add to append to the files.
- noninteractive: Just add the syscalls without asking.
- """
- if noninteractive:
- _update_seccomp(fp, list(syscalls))
- return
- syscalls_str = ','.join(syscalls)
- user_input = input(f'Add {syscalls_str} for {fp}? [y/N]> ')
- if user_input.lower().startswith('y'):
- _update_seccomp(fp, list(syscalls))
- print('Edited!')
- else:
- print(f'Skipping {fp}')
+ """Interactive confirmation check you wish to add a syscall.
+
+ Args:
+ fp: filepath of the file to edit.
+ syscalls: list-like of syscalls to add to append to the files.
+ noninteractive: Just add the syscalls without asking.
+ """
+ if noninteractive:
+ _update_seccomp(fp, list(syscalls))
+ return
+ syscalls_str = ",".join(syscalls)
+ user_input = input(f"Add {syscalls_str} for {fp}? [y/N]> ")
+ if user_input.lower().startswith("y"):
+ _update_seccomp(fp, list(syscalls))
+ print("Edited!")
+ else:
+ print(f"Skipping {fp}")
def check_missing_syscalls(syscalls: list[str], fp: str) -> Optional[set[str]]:
- """Return which specified syscalls are missing in the given file."""
- missing_syscalls = set(syscalls)
- with open(fp) as f:
- try:
- lines = f.readlines()
- for syscall in syscalls:
- for line in lines:
- if re.match(syscall + r':\s*1', line):
- missing_syscalls.remove(syscall)
- except UnicodeDecodeError:
- return None
- return missing_syscalls
+ """Return which specified syscalls are missing in the given file."""
+ missing_syscalls = set(syscalls)
+ with open(fp) as f:
+ try:
+ lines = f.readlines()
+ for syscall in syscalls:
+ for line in lines:
+ if re.match(syscall + r":\s*1", line):
+ missing_syscalls.remove(syscall)
+ except UnicodeDecodeError:
+ return None
+ return missing_syscalls
def _update_seccomp(fp: str, missing_syscalls: list[str]):
- """Update the seccomp of the file based on the seccomp change type."""
- with open(fp, 'a') as f:
- sorted_syscalls = sorted(missing_syscalls)
- for to_write in sorted_syscalls:
- f.write(to_write + ': 1\n')
+ """Update the seccomp of the file based on the seccomp change type."""
+ with open(fp, "a") as f:
+ sorted_syscalls = sorted(missing_syscalls)
+ for to_write in sorted_syscalls:
+ f.write(to_write + ": 1\n")
def _search_cmd(query: str, use_fd=True) -> list[str]:
- if use_fd and shutil.which('fdfind') is not None:
+ if use_fd and shutil.which("fdfind") is not None:
+ return [
+ "fdfind",
+ "-t",
+ "f",
+ "--full-path",
+ f"^.*{query}.*\\.policy$",
+ ]
return [
- 'fdfind',
- '-t',
- 'f',
- '--full-path',
- f'^.*{query}.*\\.policy$',
+ "find",
+ ".",
+ "-regex",
+ f"^.*{query}.*\\.policy$",
+ "-type",
+ "f",
]
- return [
- 'find',
- '.',
- '-regex',
- f'^.*{query}.*\\.policy$',
- '-type',
- 'f',
- ]
def find_potential_policy_files(packages: list[str]) -> tuple[list[str], bool]:
- """Find potentially related policy files to the given packages.
-
- Returns:
- (policy_files, successful): A list of policy file paths, and a boolean
- indicating whether all queries were successful in finding at least
- one related policy file.
- """
- all_queries_succeeded = True
- matches = []
- for p in packages:
- # It's quite common that hyphens are translated to underscores
- # and similarly common that underscores are translated to hyphens.
- # We make them agnostic here.
- hyphen_agnostic = re.sub(r'[-_]', '[-_]', p)
- cmd = subprocess.run(
- _search_cmd(hyphen_agnostic),
- stdout=subprocess.PIPE,
- check=True,
- )
- new_matches = [a for a in cmd.stdout.decode('utf-8').split('\n') if a]
- if not new_matches:
- print(f'WARNING: No matches found for {p}', file=sys.stderr)
- all_queries_succeeded = False
- else:
- matches.extend(new_matches)
- return matches, all_queries_succeeded
+ """Find potentially related policy files to the given packages.
+
+ Returns:
+ (policy_files, successful): A list of policy file paths, and a boolean
+ indicating whether all queries were successful in finding at least
+ one related policy file.
+ """
+ all_queries_succeeded = True
+ matches = []
+ for p in packages:
+ # It's quite common that hyphens are translated to underscores
+ # and similarly common that underscores are translated to hyphens.
+ # We make them agnostic here.
+ hyphen_agnostic = re.sub(r"[-_]", "[-_]", p)
+ cmd = subprocess.run(
+ _search_cmd(hyphen_agnostic),
+ stdout=subprocess.PIPE,
+ check=True,
+ )
+ new_matches = [a for a in cmd.stdout.decode("utf-8").split("\n") if a]
+ if not new_matches:
+ print(f"WARNING: No matches found for {p}", file=sys.stderr)
+ all_queries_succeeded = False
+ else:
+ matches.extend(new_matches)
+ return matches, all_queries_succeeded
def parse_args() -> Any:
- """Handle command line arguments."""
- parser = argparse.ArgumentParser(
- description='Check for missing syscalls in'
- ' seccomp policy files, or make'
- ' mass seccomp changes.\n\n'
- 'The format of this output follows the template:\n'
- ' status (arch) local/policy/filepath :: syscall,syscall,syscall\n'
- 'Where the status can be "_" for present, "M" for missing,'
- ' or "E" for Error\n\n'
- 'Example:\n'
- ' mass_seccomp_editor.py --all fstatfs --b32 fstatfs64'
- ' modemmanager\n\n'
- 'Exit Codes:\n'
- " '0' for successfully found specific policy files\n"
- " '1' for python-related error.\n"
- " '2' for no matched policy files for a given query.",
- formatter_class=argparse.RawTextHelpFormatter,
- )
- parser.add_argument('packages', nargs='+')
- parser.add_argument(
- '--all',
- type=str,
- metavar='syscalls',
- help='comma separated syscalls to check in all policy files')
- parser.add_argument(
- '--b64',
- type=str,
- metavar='syscalls',
- help='Comma separated syscalls to check in 64bit architectures')
- parser.add_argument(
- '--b32',
- type=str,
- metavar='syscalls',
- help='Comma separated syscalls to check in 32bit architectures')
- parser.add_argument(
- '--none',
- type=str,
- metavar='syscalls',
- help='Comma separated syscalls to check in unknown architectures')
- parser.add_argument('--edit',
- action='store_true',
- help='Make changes to the listed files,'
- ' rather than just printing out what is missing')
- parser.add_argument('-y',
- '--yes',
- action='store_true',
- help='Say "Y" to all interactive checks')
- parser.add_argument('--force',
- action='store_true',
- help='Edit all files, regardless of missing status.'
- ' Does nothing without --edit.')
- return parser.parse_args()
-
-
-if __name__ == '__main__':
- main()
+ """Handle command line arguments."""
+ parser = argparse.ArgumentParser(
+ description="Check for missing syscalls in"
+ " seccomp policy files, or make"
+ " mass seccomp changes.\n\n"
+ "The format of this output follows the template:\n"
+ " status (arch) local/policy/filepath :: syscall,syscall,syscall\n"
+ 'Where the status can be "_" for present, "M" for missing,'
+ ' or "E" for Error\n\n'
+ "Example:\n"
+ " mass_seccomp_editor.py --all fstatfs --b32 fstatfs64"
+ " modemmanager\n\n"
+ "Exit Codes:\n"
+ " '0' for successfully found specific policy files\n"
+ " '1' for python-related error.\n"
+ " '2' for no matched policy files for a given query.",
+ formatter_class=argparse.RawTextHelpFormatter,
+ )
+ parser.add_argument("packages", nargs="+")
+ parser.add_argument(
+ "--all",
+ type=str,
+ metavar="syscalls",
+ help="comma separated syscalls to check in all policy files",
+ )
+ parser.add_argument(
+ "--b64",
+ type=str,
+ metavar="syscalls",
+ help="Comma separated syscalls to check in 64bit architectures",
+ )
+ parser.add_argument(
+ "--b32",
+ type=str,
+ metavar="syscalls",
+ help="Comma separated syscalls to check in 32bit architectures",
+ )
+ parser.add_argument(
+ "--none",
+ type=str,
+ metavar="syscalls",
+ help="Comma separated syscalls to check in unknown architectures",
+ )
+ parser.add_argument(
+ "--edit",
+ action="store_true",
+ help="Make changes to the listed files,"
+ " rather than just printing out what is missing",
+ )
+ parser.add_argument(
+ "-y",
+ "--yes",
+ action="store_true",
+ help='Say "Y" to all interactive checks',
+ )
+ parser.add_argument(
+ "--force",
+ action="store_true",
+ help="Edit all files, regardless of missing status."
+ " Does nothing without --edit.",
+ )
+ return parser.parse_args()
+
+
+if __name__ == "__main__":
+ main()