-
-
Notifications
You must be signed in to change notification settings - Fork 30.7k
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
mmap() dumps core upon resizing the underlying file #60416
Comments
|
The following code crashes the interpreter on Linux: #!/usr/bin/python3 import mmap
with open('test', 'wb') as f:
f.write(bytes(1))
with open('test', 'r+b') as f:
m = mmap.mmap(f.fileno(), 0)
f.truncate()
a = m[:]It's not specific to the zero size truncation, it's enough if the file size decreases beyond a page border. |
|
I'm able to reproduce the bug. Here is a stack trace: #0 0x00000000005a650d in PyBytes_FromStringAndSize (str=0x7f44c127a000 <Address 0x7f44c127a000 out of bounds>, size=1) |
|
That's normal. """ The only way to guard against it would be to call fstat() in every buffer-protocol method in case of file-backed mapping to check against the current file size, but that would be awfully slow in case of sequential access (just imagine performing an md5sum of an mmap-ed file). |
|
The fstat() check isn't bullet proof, too. It has a race condition as another process could truncate the file between the fstat() check and the code lines that access the mmapped file. |
|
No, it's not. |
|
I think, handling the signal would do. |
You can't. |
|
"Well, there are some higly non-portable ways to try to escape this (see There is also the libsigsegv library, but it's hard and unsafe to handle such low level signals in Python. "It has a race condition as another process could truncate the file between the fstat() check and the code lines that access the mmapped file." You can use file locks to be protected against such race condition. See for example: |
Those are advisory locks, not mandatory locks. |
|
I know how to avoid the problem in my case, the bug does not really affect me. I posted it because I thought that the possibility to crash the interpreter is something to be avoided at all costs. I've found a few examples of handling non-restartable signals with longjmps, but not that familiar with the codebase to estimate how reliably it can be done on all supported platforms. I don't really know how this code would behave, say, on Windows. I'm gonna do some research on the topic when I have a little more time. |
I fully agree with you.
You can't use longjmp from signal handlers. Well, you can, but 99% of |
|
A pity Posix isn't smart enough to refuse truncate()ing when there's a mmap open on the affected pages. Python 3's buffer API is superior in that respect :-) |
I don't see the reason to run the interpreter. But a quick look at the source shows that the current implementation already uses longjmps to handle SIGFPE (see pyfpe.h and fpectlmodule.c). It seems to be in use on many platforms. The only problem I see so far is the possibility that we have to protect too much. However, as I could guess from quickly browsing through the mmap() implementation, the address of the mmap()ed region never leaves the module, all accesses are done using exported methods. If it is really the case, we can wrap them into something similar to PyFPE_START_PROTECT() and PyFPE_END_PROTECT() (see comments in pyfpe.h). |
SIGFPE is not handled by default (it's not generated by default, you For example, in our case, let's say you want to protect mmap_item(): The *only* thing you can do is exit. Please have a look at this:
Yes, but Python only cares about the current process. |
I proposed a generic signal handler for SIGSEGV (SIGFPE, SIGILL and SIGBUS) converting the fatal signal to a classic Python exception: issue issue bpo-3999. The idea was rejected by most core developers because it's just impossible to guarantee that Python internal structures are still consistent. A signal can occur anywhere, the longjmp() will skip all "cleanup" instructions and so it's easy to get into a deadlock case. So I proposed to display a Python traceback on such signal and just exit. For your specific case, it would much better if the kernel handles the case.
It's quite easy to crash Python using extensions implemented in C. A simple example: ctypes.string_at(0). For your specific case, you should develop an extension which sets a signal handler before reading the mmap and then restore the old signal handler after. It might be safe if the signal handler protects a single instruction, but it's very hard to develop a signal handler for such critical signals (SIGSEGV and friends). I suggest to just close this issue as "wont fix". Python cannot do anything useful in a safe manner against this "OS bug". |
|
SIGBUS as well as SIGFPE or SIGSEGV is a synchronous signal. It is delivered to the thread that caused the trouble and the stack contents is well defined. Does not apply here: longjmp is not a return. The grounds are weak. They refer to SIG30-C, which is based on ISO C standard. POSIX explicitly declares the fact that longjmp is allowed to exit signal handlers as an extension to ISO C standard. (See http://pubs.opengroup.org/onlinepubs/9699919799/) Besides, POSIX explicitly states that only async-signal-safe functions can be called from the handlers, which "are invoked asynchronously with process execution". That's not out case as mentioned above, we are not in asynchronous context as it could be with, say, SIGINT. (See http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html) Glibc doesn't mind as well: http://www.gnu.org/software/libc/manual/html_node/Longjmp-in-Handler.html I can doubt about non-POSIX platforms, but with POSIX everything seems clean to me. Especially since the method is already in use with SIGFPE. |
There's nothing I should. As I said above, the bug doesn't trouble me. I just posted it as a generic contribution and don't really care whether it's going to be fixed or not. If decided so, I could help. Otherwise I'll just mind my business. :) |
In generic case, I completely agree. Here the things are much simpler (unless I missed something). We know perfectly well the code we need to protect (mmap accessors). We can make the protected sections small enough to ensure that no internal state gets corrupted. I just feel, I can do it simply and safely. |
|
Update: The correct link to the POSIX definition of longjmp exiting a signal handler as an ISO C extension is http://pubs.opengroup.org/onlinepubs/9699919799/functions/longjmp.html |
Except that signal handlers are per-process, not per thread.
I still don't understand how you plan to handle the SIGBUS/SIGSEGV. As for the stack content, variables with automatic storage class (i.e.
It is allowed, but as said earlier, 99% of use cases are just wrong.
Well, nothing prevents "kill -SEGV <PID>". Besides, setjmp() is expensive, and here we would be setting up a But I think we've talked enough :-) |
So do I. Let's go for it. I'll make a patch (which apparently takes some time) and post it here, we'll discuss it then. Thanks for your comments. Now I believe it's a bit harder than I thought initially, but still doable. |
|
Closing as this has been open for over a decade, and several core devs have indicated they believe there is nothing that can be done. |
|
Vladimir, if you ever see this, thanks for opening the discussion. However, 'all costs' in
|
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: