Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The potential security vulnerability for the flag pre_dispatch in Parallel() class due to the eval() statement. #1128

Closed
jimlinntu opened this issue Nov 11, 2020 · 12 comments · Fixed by #1321

Comments

@jimlinntu
Copy link

jimlinntu commented Nov 11, 2020

As the title shows, if you try to enter a statement in the flag pre_dispatch, it will run whatever you want to run.
This should present a potential security vulnerability.

def f():
    return 1
p = Parallel(n_jobs=3, pre_dispatch="sys.exit(0)")
p(delayed(f)() for i in range(10)) # this will cause the system to exit

pre_dispatch = eval(pre_dispatch)

@jimlinntu jimlinntu changed the title The potential security issue for the flag pre_dispatch in Parallel() class due to the eval() statement. The potential security vulnerability for the flag pre_dispatch in Parallel() class due to the eval() statement. Nov 11, 2020
@ogrisel
Copy link
Contributor

ogrisel commented Dec 15, 2020

Thanks for the report. We should indeed improve this by making sure that the pre_dispatch expression only has expression to a list of symbols and cannot import modules.

@ogrisel
Copy link
Contributor

ogrisel commented Dec 15, 2020

Something like the following should work:

# Set builtins to empty dict to make it impossible to import arbitrary modules
# and other unsafe operation. 
pre_dispatch = eval(pre_dispatch, {"n_jobs": n_jobs, "__builtins__": {}})

@ogrisel
Copy link
Contributor

ogrisel commented Dec 15, 2020

We might want to add a whitelist of allowed built-in that can be useful for arithmetic operations (e.g. round, ceil, int, abs, float...), though.

@jimlinntu
Copy link
Author

Something like the following should work:

# Set builtins to empty dict to make it impossible to import arbitrary modules
# and other unsafe operation. 
pre_dispatch = eval(pre_dispatch, {"n_jobs": n_jobs, "__builtins__": {}})

Cool!
Thanks for your suggestion.
I think I can make a pull request for it later!

@adrinjalali
Copy link
Contributor

Opened #1321

@miraculixx
Copy link

miraculixx commented Sep 26, 2022

@jimlinntu @ogrisel What is the rationale of marking this a security issue? Presumably if someone uses Parallel they can already submit whatever code they want.

Unless I'm missing something, restricting pre_dispatch expressions will not change the security level of joblib (and this issue is not a potential security vulnerability to begin with, afaict). Even with the rather complex fix in #1327 the following will reproduce the exact behavior. To be sure, this is intended behavior, and there is no need to restrict f(), as that would render Parallel unusable.

def f():
    sys.exit(0)
p = Parallel(n_jobs=3, pre_dispatch="5*n_jobs")
p(delayed(f)() for i in range(10)) # this will cause the system to exit

The rationale for using eval(pre_dispatch) in the code is for syntactic reasons, such that the developer can write code like
Parallel(..., pre_dispatch='5*n_jobs'), as noted in the docstring. If we think use of eval() should be avoided (in case of what use case?), I would suggest the better path would be to deprecate this feature and remove it all together, or perhaps replace it with a callable. With a callable, the code would then be e.g. Parallel(..., pre_dispatch=lambda : 5 * n_jobs) which is only slightly less readable, albeit a lot more annoying to write .

@ogrisel
Copy link
Contributor

ogrisel commented Sep 26, 2022

If someone exposes the pre_dispatch parameter in a user facing web ui or config file for instance they might not expect that this can lead to arbitrary python code injection.

@ogrisel
Copy link
Contributor

ogrisel commented Sep 26, 2022

I would be ok to accept a callable if needed.

@miraculixx
Copy link

miraculixx commented Sep 26, 2022

@ogrisel Ok, I see how this might be a potential issue, though I would think this comes down to secure programming practices, i.e. never trust user input. Anyhow, I would prefer if pre_dispatch were changed into accepting expressions as a callable only, or perhaps to document the use of eval().

@adrinjalali
Copy link
Contributor

It's not just a user facing web UI. This argument is passed to Parallel by downstream libraries and that means if those downstream libraries somehow load some pre-existing persisted object, the user can easily exploit the fact that whatever's passed to pre_dispatch runs in eval.

We certainly shouldn't allow callable as an expression, since that itself again opens the door to easy exploitation.

Accepting a callable, however, might be okay.

@helmutg
Copy link

helmutg commented Dec 6, 2022

Even with the ast-based version, exceptions can escape, e.g. "1 / 0". Is that considered ok?

@adrinjalali
Copy link
Contributor

This fix was more about arbitrary code execution rather than an exception free run. I think 1/0 should raise an exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants