-
-
Notifications
You must be signed in to change notification settings - Fork 31k
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
subprocess.Popen: Performance regression on Linux since 124af17b6e [CVE-2023-6507] #112334
Comments
Gentle ping @arhadthedev because you authored the referenced commit 124af17. :) |
Thanks for the bug and deep dive analysis! We'll take a look. (yes this code is a lot more complicated than any of us would like) 😅 |
The change from 124af17 also impacts the performance if there are a lot of imports in the process spawning new processes. For example: import timeit
import subprocess
# a lot of local imports here
def test():
popen = subprocess.Popen(["python3.12", "-c", "1+1"])
popen.communicate()
result = timeit.timeit(test, number=1000)
print(f"Timeit: {result}") On my PC, it prints ~26s for 3.12 and ~24s for 3.11. When I remove all imports from |
…roups=[]`. Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it would no longer use the fast-path ``vfork()`` system call when it could have due to a logic bug, instead falling back to the safe but slower ``fork()``. Also fixed a second 3.12.0 potential security bug. If a value of ``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, the underlying ``setgroups(0, NULL)`` system call to clear the groups list would not be made in the child process prior to ``exec()``. The security issue was identified via code inspection in the process of fixing the first bug. Thanks to @vain for the detailed report and analysis in the initial bug on Github. * [ ] A regression test is desirable. I'm pondering a test that runs when `strace` is available and permitted which and confirms use of `vfork()` vs `clone()`...
…[]` behavior (#112617) Restore `subprocess`'s intended use of `vfork()` by default for performance on Linux; also fixes the behavior of `extra_groups=[]` which was unintentionally broken in 3.12.0: Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it would no longer use the fast-path ``vfork()`` system call when it could have due to a logic bug, instead falling back to the safe but slower ``fork()``. Also fixed a security bug introduced in 3.12.0. If a value of ``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, the underlying ``setgroups(0, NULL)`` system call to clear the groups list would not be made in the child process prior to ``exec()``. The security issue was identified via code inspection in the process of fixing the first bug. Thanks to @vain for the detailed report and analysis in the initial bug on Github. Co-authored-by: Serhiy Storchaka <[email protected]>
…roups=[]` behavior (pythonGH-112617) Restore `subprocess`'s intended use of `vfork()` by default for performance on Linux; also fixes the behavior of `extra_groups=[]` which was unintentionally broken in 3.12.0: Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it would no longer use the fast-path ``vfork()`` system call when it could have due to a logic bug, instead falling back to the safe but slower ``fork()``. Also fixed a security bug introduced in 3.12.0. If a value of ``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, the underlying ``setgroups(0, NULL)`` system call to clear the groups list would not be made in the child process prior to ``exec()``. The security issue was identified via code inspection in the process of fixing the first bug. Thanks to @vain for the detailed report and analysis in the initial bug on Github. (cherry picked from commit 9fe7655) Co-authored-by: Gregory P. Smith <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]>
…groups=[]` behavior (GH-112617) (#112731) Restore `subprocess`'s intended use of `vfork()` by default for performance on Linux; also fixes the behavior of `extra_groups=[]` which was unintentionally broken in 3.12.0: Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it would no longer use the fast-path ``vfork()`` system call when it could have due to a logic bug, instead falling back to the safe but slower ``fork()``. Also fixed a security bug introduced in 3.12.0. If a value of ``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, the underlying ``setgroups(0, NULL)`` system call to clear the groups list would not be made in the child process prior to ``exec()``. The security issue was identified via code inspection in the process of fixing the first bug. Thanks to @vain for the detailed report and analysis in the initial bug on Github. (cherry picked from commit 9fe7655) + Reword NEWS for the bugfix/security release. (mentions the assigned CVE number) Co-authored-by: Gregory P. Smith [Google LLC] <[email protected]> Co-authored-by: Serhiy Storchaka <[email protected]>
Regression test that vfork is used when expected by subprocess. This is written integration test style, it uses strace if it is present and appears to work to find out what system call actually gets used in different scenarios. Test coverage is added for the default behavior and that of each of the specific arguments that must disable the use of vfork. obviously not an entire test matrix, but it covers the most important aspects. If there are ever issues with this test being flaky or failing on new platforms, rather than try and adapt it for all possible platforms, feel free to narrow the range it gets tested on when appropriate. That is not likely to reduce coverage.
…roups=[]` behavior (python#112617) Restore `subprocess`'s intended use of `vfork()` by default for performance on Linux; also fixes the behavior of `extra_groups=[]` which was unintentionally broken in 3.12.0: Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it would no longer use the fast-path ``vfork()`` system call when it could have due to a logic bug, instead falling back to the safe but slower ``fork()``. Also fixed a security bug introduced in 3.12.0. If a value of ``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, the underlying ``setgroups(0, NULL)`` system call to clear the groups list would not be made in the child process prior to ``exec()``. The security issue was identified via code inspection in the process of fixing the first bug. Thanks to @vain for the detailed report and analysis in the initial bug on Github. Co-authored-by: Serhiy Storchaka <[email protected]>
…ython#112734) Regression test that vfork is used when expected by subprocess. This is written integration test style, it uses strace if it is present and appears to work to find out what system call actually gets used in different scenarios. Test coverage is added for the default behavior and that of each of the specific arguments that must disable the use of vfork. obviously not an entire test matrix, but it covers the most important aspects. If there are ever issues with this test being flaky or failing on new platforms, rather than try and adapt it for all possible platforms, feel free to narrow the range it gets tested on when appropriate. That is not likely to reduce coverage.
…roups=[]` behavior (python#112617) Restore `subprocess`'s intended use of `vfork()` by default for performance on Linux; also fixes the behavior of `extra_groups=[]` which was unintentionally broken in 3.12.0: Fixed a performance regression in 3.12's :mod:`subprocess` on Linux where it would no longer use the fast-path ``vfork()`` system call when it could have due to a logic bug, instead falling back to the safe but slower ``fork()``. Also fixed a security bug introduced in 3.12.0. If a value of ``extra_groups=[]`` was passed to :mod:`subprocess.Popen` or related APIs, the underlying ``setgroups(0, NULL)`` system call to clear the groups list would not be made in the child process prior to ``exec()``. The security issue was identified via code inspection in the process of fixing the first bug. Thanks to @vain for the detailed report and analysis in the initial bug on Github. Co-authored-by: Serhiy Storchaka <[email protected]>
…ython#112734) Regression test that vfork is used when expected by subprocess. This is written integration test style, it uses strace if it is present and appears to work to find out what system call actually gets used in different scenarios. Test coverage is added for the default behavior and that of each of the specific arguments that must disable the use of vfork. obviously not an entire test matrix, but it covers the most important aspects. If there are ever issues with this test being flaky or failing on new platforms, rather than try and adapt it for all possible platforms, feel free to narrow the range it gets tested on when appropriate. That is not likely to reduce coverage.
Bug report
Bug description:
Apologies if this is a duplicate. I couldn’t find a similar report, though.
The issue and how to reproduce
We’re seeing a performance regression since 124af17. The best way to reproduce it is to spawn lots of processes from a
ThreadPoolExecutor
. For example:Before, on 49cae39, it’s roughly this:
Since 124af17, it’s roughly this:
An attempt at an analysis and possible fix
strace
shows that the new code doesn’t usevfork()
anymore butclone()
. I believe that the reason for this is an incorrect check ofnum_groups
(orextra_group_size
, as it is now called onmain
).124af17 checks if
extra_group_size
is less than zero to determine if we can usevfork()
. Is that correct? Maybe this should be equal to zero? I’m talking about these two locations (diff relative tomain
/9e56eedd018e1a46):extra_group_size
is the result of the call toPySequence_Size(extra_groups_packed)
. If I understand the docs correctly, then this function only returns negative values to indicate errors. This error condition is already checked, right after the call itself:Later in the code,
extra_group_size
can never be less than zero. It can, however, be equal to zero ifextra_groups
is an empty list. I believe this is what was meant to be checked here.I’ll happily open a PR for this if you agree that this is the way to go.
CPython versions tested on:
3.11, 3.12, 3.13, CPython main branch
Operating systems tested on:
Linux
Linked PRs
vfork()
& fixextra_groups=[]
behavior #112617vfork()
& fixextra_groups=[]
behavior (GH-112617) #112731The text was updated successfully, but these errors were encountered: