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

PlatformInit resets signal handlers to SIG_DFL causing crashes #47013

Closed
dvyukov opened this issue Mar 8, 2023 · 6 comments · Fixed by #47188
Closed

PlatformInit resets signal handlers to SIG_DFL causing crashes #47013

dvyukov opened this issue Mar 8, 2023 · 6 comments · Fixed by #47188

Comments

@dvyukov
Copy link
Contributor

dvyukov commented Mar 8, 2023

Version

0c46051

Platform

Linux 5.19.11-amd64 #1 SMP x86_64 GNU/Linux

Subsystem

No response

What steps will reproduce the bug?

LD_PRELOAD or link in any library that sets a signal handler and schedules signal delivery (e.g. a posix timer).

How often does it reproduce? Is there a required condition?

No response

What is the expected behavior?

The library handles own signals.

What do you see instead?

The program crashes.

Additional information

#615 added this code that resets all signal handlers to SIG_DFL:

node/src/node.cc

Lines 426 to 434 in 0c46051

// The hard-coded upper limit is because NSIG is not very reliable; on Linux,
// it evaluates to 32, 34 or 64, depending on whether RT signals are enabled.
// Counting up to SIGRTMIN doesn't work for the same reason.
for (unsigned nr = 1; nr < kMaxSignal; nr += 1) {
if (nr == SIGKILL || nr == SIGSTOP)
continue;
act.sa_handler = (nr == SIGPIPE || nr == SIGXFSZ) ? SIG_IGN : SIG_DFL;
CHECK_EQ(0, sigaction(nr, &act, nullptr));
}

This causes crashes is there is a signal handler installed.

While SIG_IGN can indeed be inherited across execve, all actual handlers (not SIG_IGN/DFL) are reset to SIG_DFL.
So I think the startup code should reset to SIG_DFL iff the handler is set of SIG_IGN. Any real handlers should be left intact.

@bnoordhuis @sam-github @melver

@bnoordhuis
Copy link
Member

I'm a bit torn on whether to add workarounds for dodgy LD_PRELOAD wrappers but philosophical objections aside, I don't think node can detect their presence in a race-free manner.

If third-party code can run really early, then it can call sigaction() from a newly started thread. Meaning this won't work:

sigaction(nr, nullptr, &old);
// <-- sigaction()-from-other-thread race window here
if (old.sa_handler == SIG_DFL) sigaction(nr, &act, nullptr);

And neither will this:

sigaction(nr, &act, &old);
// <-- signal delivery race window here
if (old.sa_handler != SIG_DFL) sigaction(nr, &old, nullptr);

(And I'm skipping over the practical concern that reading .sa_handler and .sa_sigaction is problematic on some platforms.)

@dvyukov
Copy link
Contributor Author

dvyukov commented Mar 9, 2023

If third-party code can run really early, then it can call sigaction() from a newly started thread. Meaning this won't work:

Yes, this won't work and looks wild. I don't think we should be bothered with such possibilities. Signal handlers are inherently global and shouldn't be changed asynchronously.

A normal sequential check for existing handlers sounds reasonable.

(And I'm skipping over the practical concern that reading .sa_handler and .sa_sigaction is problematic on some platforms.)

The code currently does:

act.sa_handler = (nr == SIGPIPE || nr == SIGXFSZ) ? SIG_IGN : SIG_DFL; 

so presumably at least this is working fine on all platforms.
Do you expect if (oldact.sa_handler == SIG_IGN) to cause issues that are not triggered by the existing line?

@bnoordhuis
Copy link
Member

Yes, because .sa_handler and .sa_sigaction don't have to be union members, they can be separate fields. I'm reasonably sure this is the case on AIX or IBM i (or both) because I remember running afoul of that in the past.

The real logic should look something like this, and, caveat emptor, I'm not 100% sure this won't emit warnings or isn't UB according to POSIX:

if ((old.sa_flags & SA_SIGINFO) && old.sa_sigaction == SIG_DFL || old.sa_handler == SIG_DFL)

Well, pull request welcome, I suppose.

@bnoordhuis
Copy link
Member

@dvyukov are you interested in pursuing this? If not, all good, but then I'll go ahead and close this out.

@dvyukov
Copy link
Contributor Author

dvyukov commented Mar 21, 2023

Yes, I am. I just somehow missed your previous reply.
I have not looked yet, but is there existing type of tests that could test this? Or just a code change is fine?

@bnoordhuis
Copy link
Member

There's test/embedding/embedtest.cc and concomitant JS file but I wouldn't want to vouch it's the right place because it also doubles as an embedding example. I'd be okay with just an Obviously Correct Looking(TM) code change.

dvyukov added a commit to dvyukov/node that referenced this issue Mar 21, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix nodejs#47013
dvyukov added a commit to dvyukov/node that referenced this issue Mar 22, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix nodejs#47013
nodejs-github-bot pushed a commit that referenced this issue Mar 31, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix #47013

PR-URL: #47188
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
RafaelGSS pushed a commit that referenced this issue Apr 5, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix #47013

PR-URL: #47188
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
RafaelGSS pushed a commit that referenced this issue Apr 6, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix #47013

PR-URL: #47188
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
RafaelGSS pushed a commit that referenced this issue Apr 7, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix #47013

PR-URL: #47188
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
RafaelGSS pushed a commit that referenced this issue Apr 8, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix #47013

PR-URL: #47188
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
danielleadams pushed a commit that referenced this issue Jul 6, 2023
The only bad handler value we can inhert from before exec is SIG_IGN
(any actual function pointer is reset to SIG_DFL during exec).
If that's the case, we want to reset it back to SIG_DFL.
However, it's also possible that an embeder (or an LD_PRELOAD-ed
library) has set up own signal handler for own purposes
(e.g. profiling). If that's the case, keep it intact.

Fix #47013

PR-URL: #47188
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
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.

2 participants