From ce1fcbab3d20a2eb39ce2b6b70537b3f05de6415 Mon Sep 17 00:00:00 2001 From: Fred-Barclay Date: Fri, 24 Aug 2018 12:09:16 -0500 Subject: Add python program to more easily debug profiles Should help with issues like #1946 where the user needs to comment out all profile lines and then re-enable them individually to test --- contrib/jail_prober.py | 163 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100755 contrib/jail_prober.py diff --git a/contrib/jail_prober.py b/contrib/jail_prober.py new file mode 100755 index 000000000..a5f279cb4 --- /dev/null +++ b/contrib/jail_prober.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +""" +Figure out which profile options may be causing a particular program to break +when run in firejail. + +Instead of having to comment out each line in a profile by hand, and then +enable each line individually until the bad line or lines are found, this +largely automates the process. Users only have to provide the path to the +profile, program name, and answer 'y' for yes or 'n' for no when prompted. + +After completion, you'll be provided with some information to copy and then +paste into a GitHub issue in the Firejail project repository: +https://github.com/netblue30/firejail/issues + +Paths to the profile should be absolute. If the program is in your path, then +you only have to type the profile name. Else, you'll need to provide the +absolute path to the profile. + +Examples: +python jail_prober.py /etc/firejail/spotify.profile spotify +python jail_prober.py /usr/local/etc/firejail/firefox.profile /usr/bin/firefox +""" + +import sys +import os +import subprocess + + +def check_params(profilePath): + """ + Ensure the path to the profile is valid and that an actual profile has been + passed (as opposed to a config or .local file). + + :params profilePath: The absolute path to the problematic profile. + """ + if not os.path.isfile(profilePath): + raise FileNotFoundError( + 'The path %s is not a valid system path.' % profilePath) + if not profilePath.endswith('.profile'): + raise ValueError('%s is not a valid Firejail profile.' % profilePath) + + +def get_args(profilePath): + """ + Read the profile, stripping out comments and newlines + + :params profilePath: The absolute path to the problematic profile. + + :returns profile: A list containing all active profile arguments + """ + with open(profilePath, 'r') as f: + profile = f.readlines() + profile = [ + arg.strip() for arg in profile + if not arg.startswith('#') and arg.strip() != '' + ] + + return profile + + +def arg_converter(argList, style): + """ + Convert between firejail command-line arguments (--example=something) and + profile arguments (example something) + + :params argList: A list of firejail arguments + + :params style: Whether to convert arguments to command-line form or profile + form + """ + if style == 'to_profile': + oldSep = '=' + newSep = ' ' + prefix = '' + elif style == 'to_commandline': + oldSep = ' ' + newSep = '=' + prefix = '--' + newArgs = [prefix + word.replace(oldSep, newSep) for word in argList] + # Additional strip of '--' if converting to profile form + if style == 'to_profile': + newArgs = [word[2:] for word in newArgs] + + # Remove invalid '--include' args if converting to command-line form + elif style == 'to_commandline': + newArgs = [word for word in newArgs if 'include' not in word] + + return newArgs + + +def run_firejail(program, allArgs): + """ + Attempt to run the program in firejail, incrementally adding to the number + of firejail arguments. Initial run has no additional params besides + noprofile. + + :params program: The program name. If it doesn't exist in the user's path + then the full path should be provided. + + :params allArgs: A list of all Firejail arguments to try, in command-line + format. + + :returns goodArgs: A list of arguments that the user has reported to not + affect the program + + :returns badArgs: A list of arguments that the user has reported to break + the program when sandboxing with Firejail + """ + goodArgs = ['firejail', '--noprofile', program] + badArgs = [] + print('Attempting to run %s in Firejail' % program) + for arg in allArgs: + print('Running with', arg) + subprocess.call(goodArgs) + ans = input('Did %s run correctly? [y]/n ' % program) + if ans == 'n' or ans == 'N': + badArgs.append(arg) + else: + goodArgs.insert(-1, arg) + print('\n') + # Don't include 'firejail', '--noprofile', or program name in arguments + goodArgs = goodArgs[2:-1] + + return goodArgs, badArgs + + +def main(): + profilePath = sys.argv[1] + program = sys.argv[2] + # Quick error check and extract arguments + check_params(profilePath) + profile = get_args(profilePath) + allArgs = arg_converter(profile, 'to_commandline') + # Find out which profile options break the program when running in firejail + goodArgs, badArgs = run_firejail(program, allArgs) + + goodArgs = arg_converter(goodArgs, 'to_profile') + badArgs = arg_converter(badArgs, 'to_profile') + + print('\n###########################') + print('Debugging completed.') + print( + 'Please copy the following and report it to the Firejail development', + 'team on GitHub at %s \n\n' % + 'https://github.com/netblue30/firejail/issues') + + subprocess.call(['firejail', '--version']) + + print('These profile options break the program.') + print('```') + for item in badArgs: + print(item) + print('```\n\n\n') + + print('This is a minimal working profile:') + print('```') + for item in goodArgs: + print(item) + print('```') + + +if __name__ == '__main__': + main() -- cgit v1.2.3-54-g00ecf