This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Title: faulthandler does not properly restore sigaltstack during teardown
Type: enhancement Stage: resolved
Components: Extension Modules Versions: Python 3.7, Python 3.6, Python 3.5
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Mariatta, tich, vstinner
Priority: normal Keywords:

Created on 2017-03-23 05:43 by tich, last changed 2022-04-11 14:58 by admin. This issue is now closed.

File name Uploaded Description Edit
python_failure.txt tich, 2017-03-23 05:43 AddressSanitizer output
python_reproducer.cpp tich, 2017-03-23 05:44 Reproducer
Pull Requests
URL Status Linked Edit
PR 777 merged tich, 2017-03-23 05:46
PR 796 merged tich, 2017-03-24 11:18
PR 797 merged tich, 2017-03-24 11:19
Messages (2)
msg290025 - (view) Author: Christophe Zeitouny (tich) * Date: 2017-03-23 05:43
Looks like faulthandler is not properly tearing down its sigaltstack, causing potential double-free issues in any application that embeds the Python interpreter.
I stumbled upon this when I enabled AddressSanitizer on my application, which sets up and tears down a Python interpreter instance at runtime. AddressSanitizer complained about a double-free of the stack_t::ss_sp memory region.
After close inspection, here's what's happening:

1. When a new thread is created, AddressSanitizer sets up its own alternative stack by calling sigaltstack
2. Py_Initialize() is called, which initializes faulthandler, which sets up its own alternative stack, therefore overriding the one installed by AddressSanitizer
3. Py_Finalize() is called, which deinitializes faulthandler, which merely deletes the allocated stack region, but leaves the alternative stack installed. Any signal that occurs after this point will be using a memory region it doesn't own as stack. dangerous stuff.
4. The thread exits, at which point AddressSanitizer queries sigaltstack for the current alternative stack, blindly assumes that it's the same one that it installed, and attempts to free the allocated stack region. Therefore causing a double free issue

Regardless of the fact that AddressSanitizer should probably not blindly trust that the currently installed sigaltstack is the same one it installed earlier, the current code in faulthandler leaves the sigaltstack in a very bad state after finalizing.
This means that the application that embeds the Python interpreter better hope that no signals are raised after it calls Py_Finalize().

I have a patch that fixes this issue. faulthandler will save the previously installed alternative stack at initialization time. During deinitialization, it will query sigaltstack for the current stack. If it's the same as the one it installed,
it will restore the saved previous stack.

'sigaltstack' just sounds like a badly designed API. There is essentially no way to use it 'correctly'.
Here's how AddressSanitizer uses it (line 164):
and here's how the Chrome browser uses it:

Notice that my approach is closer to what Chrome does, but in the case where the installed stack is no longer ours, I don't disable whatever stack is installed. This is because I don't believe that will make much difference. Whoever switched out the stack could have
saved our stack somewhere and planned on blindly restoring it upon exit. In which case, whatever we do would be overridden.

Attached are a tiny reproducer for the issue, along with the complete analysis of what's reported by AddressSanitizer. I'll follow this up by a pull request for my changes.

msg290095 - (view) Author: Mariatta (Mariatta) * (Python committer) Date: 2017-03-24 16:29
PR was merged and backported into 3.5 and 3.6, so I'm closing this.
Thanks, Christophe :)
Date User Action Args
2022-04-11 14:58:44adminsetgithub: 74070
2017-03-24 16:29:34Mariattasetstatus: open -> closed

versions: + Python 3.5, Python 3.6, Python 3.7
nosy: + Mariatta

messages: + msg290095
resolution: fixed
stage: resolved
2017-03-24 11:19:24tichsetpull_requests: + pull_request704
2017-03-24 11:18:10tichsetpull_requests: + pull_request703
2017-03-23 05:46:27tichsetpull_requests: + pull_request682
2017-03-23 05:44:13tichsetfiles: + python_reproducer.cpp
2017-03-23 05:43:49tichcreate