exec-used / W0122ΒΆ

Message emitted:

Use of exec

Description:

Raised when the 'exec' statement is used. It's dangerous to use this function for a user input, and it's also slower than actual code in general. This doesn't mean you should never use it, but you should consider alternatives first and restrict the functions available.

Problematic code:

username = "Ada"
code_to_execute = f"""input('Enter code to be executed please, {username}: ')"""
program = exec(code_to_execute)  # [exec-used]
exec(program)  # [exec-used]

Correct code:

def get_user_code(name):
    return input(f"Enter code to be executed please, {name}: ")


username = "Ada"
# If the globals dictionary does not contain a value for the key __builtins__,
# all builtins are allowed. You need to be explicit about it being disallowed.
allowed_globals = {"__builtins__": {}}
allowed_locals = {}
# pylint: disable-next=exec-used
exec(get_user_code(username), allowed_globals, allowed_locals)

Additional details:

The available methods and variables used in exec() may introduce a security hole. You can restrict the use of these variables and methods by passing optional globals and locals parameters (dictionaries) to the exec() method.

However, use of exec() is still insecure if you allow some functions like __import__ or open. For example, consider the following call that writes a file to the user's system and then execute code unrestrained by the allowed_globals, or allowed_locals parameters:

import textwrap


def forbid_print(*args):
    raise ValueError("This is raised when a print is used")


allowed_globals = {
    "__builtins__": {
        "__import__": __builtins__.__import__,
        "open": __builtins__.open,
        "print": forbid_print,
    }
}

exec(
    textwrap.dedent(
        """
    import textwrap

    with open("nefarious.py", "w") as f:
        f.write(textwrap.dedent('''
            def connive():
                print("Here's some code as nefarious as imaginable")
        '''))

    import nefarious

    nefarious.connive()  # This will NOT raise a ValueError
    """
    ),
    allowed_globals,
)

The import is used only for readability of the example in this case but it could import a dangerous functions:

  • subprocess.run('echo "print(\"Hello, World!\")" > nefarious.py'

  • pathlib.Path("nefarious.py").write_file("print(\"Hello, World!\")")

  • os.system('echo "print(\"Hello, World!\")" > nefarious.py')

  • logging.basicConfig(filename='nefarious.py'); logging.error('print("Hello, World!")')

  • etc.

Related links:

Created by the basic checker.