Live data from Hacker News

Why isn't memset() async-signal-safe?

boston.conman.org

51–60 of 98 posts

Re: Why isn't memset() async-signal-safe?

#52
post #39

Earlier quoted context omitted.

Processors are inherently extremely stateful; this is an argument against having unix-style signals.

No disagreement there. In a new OS that doesn't need 100% POSIX compatibility, I'd eliminate the standard signal model, and only have signalfd (plus a SIGKILL equivalent and a separate stop/continue mechanism similar to SIGSTOP/SIGCONT).

How would you handle being able to trap and fix bad memory access (i.e. hooking SIGSEGV/SIGBUS) with this scheme? Lots of programs use this for various reasons; off the top of my head at least a few generational GCs implement their write barrier by setting pages read-only and noticing the trap and resetting RW on SEGV...

(Yes, I believe something like card marking is more performant than this in several ways on modern CPUs, but then you need to insert card marking instructions into your code generator... trapping SEGV is easy and only happens in the GC.)

Also there's all the debugging and process inspection stuff that works via signals, all of which depends on being able to really interrupt code rather than just stuff a message in a queue.

Re: Why isn't memset() async-signal-safe?

#53
post #24

Couple of things. First, I don't think that's true nowadays. The discussion points that this (not restoring DF on signal return) is a kernel bug and should be fixed: https://lkml.org/lkml/2008/3/5/531 Second, the kernel tries to avoid doing too much work in the signal return code. It tries hard do _avoid_ heavy XSAVE and just preserve only the needed registers. This is the job of sigreturn(2) syscall btw. http://man7…

> Last one was merged into mainline. Then reversed. Sadly. > Then applied again: https://github.com/torvalds/linux/commit/6c25da5ad55d48c41b8.... > > The gist: if you modify SS register in the signal handler you are screwed. The only way around it is to install a trampoline using "the famous dosemu iret hack" described here:

I'm not sure what you mean. This issue is fixed, so SS works exactly the way you would expect it to, unless you do very strange things indeed in which case you might need to fiddle with uc_flags.

Re: Why isn't memset() async-signal-safe?

#54
post #27

Earlier quoted context omitted.

I mean, kernel bug or no, if that's how it actually works, then it isn't actually safe to be handle signals while executing these functions. If people are still working to change the code and standards we hold the code to, then you can't push changes that rely on the new behavior without introducing some truly bizarre race conditions.

The bug is 8 years old. At some point you can assume it's been fixed.

I mean, that's pretty optimistic, but perhaps: At some point you can assume that, if people get your updated code in 2017, they'll have an update from many years ago.

Depends on how releases are managed, but that might be much much more practical an assumption.

Re: Why isn't memset() async-signal-safe?

#55
There are a whole bunch of almost-correct comments here along with a surprising number of "it's been so long it must be fixed".

The alleged bug is that Linux didn't clear DF on signal entry. (This has nothing to do with what is saved or restored. Flags have to be saved and restored and, AFAIK, always were.) The x86 ABI is crystal clear: C functions are called with DF clear. Neither glibc nor Linux cleared it before calling a signal handler, so there was a bug.

But the bug was fixed in March 2008 for Linux 2.6.25. [1]

[1] https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux....

Re: Why isn't memset() async-signal-safe?

#56

This article seems flat out wrong. Since the direction flag is correctly saved and restored, everything is cool. When some code is interrupted and the signal or interrupt handler calls memmove, that memmove will set up the direction flag for itself correctly. Its entire execution is nested within the handler. If it is interrupted by a nested interrupt, that nested one will restore the flag. Now if an implementation o…

Also, POSIX explicitly requires memcpy and memmove to be async-signal-safe. Claiming they are not is misinformation.

The list of async-signal-safe functions is available here:

http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2...

Re: Why isn't memset() async-signal-safe?

#57
post #44
post #34

Slightly terrifying is the optimizer may very well replace the following while(n--) *m++ = c; With a call to the built in memset()

I'm fairly inexperienced on this matter. Can you give a real life example where this would be a bad thing?

Well there is this bug: 'memcpy implementation optimized as a call to memcpy'

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56888

The terrifying thing is while the programmer might know that memset() isn't async-signal-safe and not use it in a signal handler, the compiler may blissfully and silently optimize the code to use memset() anyways. Odd crashes or worse security leaks may result.

Reminds me a bit of old floating point implemented in software. Some implementations had global scratchpad registers. Very weird things happened if you did any floating point operations in multiple threads.

Re: Why isn't memset() async-signal-safe?

#58
post #53
post #24

Couple of things. First, I don't think that's true nowadays. The discussion points that this (not restoring DF on signal return) is a kernel bug and should be fixed: https://lkml.org/lkml/2008/3/5/531 Second, the kernel tries to avoid doing too much work in the signal return code. It tries hard do _avoid_ heavy XSAVE and just preserve only the needed registers. This is the job of sigreturn(2) syscall btw. http://man7…

> Last one was merged into mainline. Then reversed. Sadly. > Then applied again: https://github.com/torvalds/linux/commit/6c25da5ad55d48c41b8... . > > The gist: if you modify SS register in the signal handler you are screwed. The only way around it is to install a trampoline using "the famous dosemu iret hack" described here: I'm not sure what you mean. This issue is fixed, so SS works exactly the way you would expec…

Strange or not, linux did not restore SS on sigreturn in 64 bit mode. The point of explaining that is to emphasize that the issue described in the blog post is not the only one in this code (the code doing entering / exiting signal handlers).

The issue is fixed indeed, as for Feb 17, 2016.

Re: Why isn't memset() async-signal-safe?

#59
post #49
post #24

Couple of things. First, I don't think that's true nowadays. The discussion points that this (not restoring DF on signal return) is a kernel bug and should be fixed: https://lkml.org/lkml/2008/3/5/531 Second, the kernel tries to avoid doing too much work in the signal return code. It tries hard do _avoid_ heavy XSAVE and just preserve only the needed registers. This is the job of sigreturn(2) syscall btw. http://man7…

The bug wasn't "not restoring DF on signal return" - it was always saved and restored correctly. The bug was not clearing DF on signal entry.

agreed

Re: Why isn't memset() async-signal-safe?

#60
post #52

Earlier quoted context omitted.

No disagreement there. In a new OS that doesn't need 100% POSIX compatibility, I'd eliminate the standard signal model, and only have signalfd (plus a SIGKILL equivalent and a separate stop/continue mechanism similar to SIGSTOP/SIGCONT).

How would you handle being able to trap and fix bad memory access (i.e. hooking SIGSEGV/SIGBUS) with this scheme? Lots of programs use this for various reasons; off the top of my head at least a few generational GCs implement their write barrier by setting pages read-only and noticing the trap and resetting RW on SEGV... (Yes, I believe something like card marking is more performant than this in several ways on moder…

For trapping memory accesses, either something like userfaultfd, or otherwise handling segfaults via a signalfd from another thread with its own independent stack. (I'd also eliminate limitations about using a signalfd only within the same process that would have received the signals.)

Or, if you just need a "dirty page" bit, add a dedicated mechanism for that, which can use hardware features to run much faster without having to trap.

For debugging, we can do much better than ptrace. Linux already has dedicated syscalls to read and write another process's memory. Add some mechanisms to read and write registers, and extend the process stop/continue mechanism to allow single-stepping and stop-on-event (such as stop-on-syscall, or BPF-based filtering). I don't see any reason why debugging a process needs to incorporate signals.

Post reply on HN