-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
feat: handle SIGBUS / SIGSEGV only when originating from YARA's memory blocks #2020
Conversation
If multiple threads were running in parallel (e.g. each using a YR_SCANNER), race conditions could cause the original signal handler to not be restored correctly. Add a mutex that controls access to the (process wide) signal handler to ensure that the YARA signal handler is only installed once and only removed when it is no longer needed.
If a signal is received that does not originate from YARA (which is quite possible in multi-threaded applications using YARA), it is currently ignored. Especially for SIGSEGV, this may cause unexpected behaviour in the caller, even in threads that do not use YARA. Add logic to call the original signal handler to handle this case more cleanly. It is not perfect - with only a process wide signal handler, it's probably impossible to have a perfect solution - but it's better than the current behaviour.
Some callbacks from YARA code are done from YR_TRYCATCH. Currently, YARA assumes that SIGBUS / SIGSEGV from these callbacks are due to the scanned memory range, but this is not necessarily true. Add logic to identify the currently scanned memory block and only catch the signal if the SIGBUS / SIGSEGV originated from it.
This is related to hillu/go-yara#137, which tries to deal with these issues with an original signal handler (installed by the Golang runtime) versus YARA's signal handler. Thanks to @plusvic for initially identifying the problems with a process wide signal handler, and to @hillu for discussing possible solutions. |
Looks nice! Would have allowed us to catch #2016 much more easily. Not entirely related to your changes, but I wonder if there is actually a reason to catch SIGSEGV? From my understanding, this signal handler is to handle issues that can arise when reading from an mmap'ed memory region. But afaik, those issues can only trigger SIGBUS signals (for example, mmap'ing a file, shortening it, then reading past the new end of the file), and are not supposed to trigger SIGSEGV. Are there expected scenarios where reading from a mmap region raises a SIGSEGV signal? |
libyara/exception.h
Outdated
old_handler = old_sigsegv_exception_handler; | ||
pthread_mutex_unlock(&exception_handler_mutex); | ||
|
||
if (old_handler.sa_flags & SA_SIGINFO) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually calling the original handler should probably be handled together. I suggest the following simplification:
if (old_handler.sa_handler == SIG_DFL)
{
// Old handler is the default action. To do this, set the signal handler back to default and raise the signal.
// This is fairly volatile - since this is not an atomic operation, signals from other threads might also
// cause the default action while we're doing this. However, the default action will typically cause a
// process termination anyway.
pthread_mutex_lock(&exception_handler_mutex);
struct sigaction current_handler;
sigaction(sig, &old_handler, ¤t_handler);
raise(sig);
sigaction(sig, ¤t_handler, NULL);
pthread_mutex_unlock(&exception_handler_mutex);
}
else if (old_handler.sa_handler == SIG_IGN)
{
// SIG_IGN wants us to ignore the signal
return;
}
else if (old_handler.sa_flags & SA_SIGINFO)
{
old_handler.sa_sigaction(sig, info, context);
}
else
{
old_handler.sa_handler(sig);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SIG_DFL
is defined as zero, though. If I have a signal handler with flag SA_SIGINFO
set, which therefore only sets sa_sigaction
and not sa_handler
, this proposal would incorrectly use the SIG_DFL
part. I agree that an if / else structure makes it easier to read, but if we do so, we need the SA_SIGINFO
check at the start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, so that would be:
if (old_handler.sa_flags & SA_SIGINFO)
{
old_handler.sa_sigaction(sig, info, context);
}
else if (old_handler.sa_handler == SIG_DFL)
{
// Old handler is the default action. To do this, set the signal handler back to default and raise the signal.
// This is fairly volatile - since this is not an atomic operation, signals from other threads might also
// cause the default action while we're doing this. However, the default action will typically cause a
// process termination anyway.
pthread_mutex_lock(&exception_handler_mutex);
struct sigaction current_handler;
sigaction(sig, &old_handler, ¤t_handler);
raise(sig);
sigaction(sig, ¤t_handler, NULL);
pthread_mutex_unlock(&exception_handler_mutex);
}
else if (old_handler.sa_handler == SIG_IGN)
{
// SIG_IGN wants us to ignore the signal
return;
}
else
{
old_handler.sa_handler(sig);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented like this in the current version.
libyara/exception.h
Outdated
{ | ||
if (old_handler.sa_handler == SIG_DFL) | ||
{ | ||
// Old handler is the default action. To do this, set the signal handler back to default and raise the signal. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might get away without locking and having to worry about non-atomic operations if we use SA_RESETHAND
when installing our handler over SIG_DFL handlers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SA_RESETHAND
comes with additional complications, though. If we use it, we are inherently racy when two threads run (within YARA) into a SIGBUS
in parallel: the first occurrence will reset the signal handler and the second one will therefore no longer be caught by our signal handler, and instead cause a crash.
SIGSEGV is caught, I believe, because it's used on FreeBSD / OpenBSD instead of SIGBUS in the scenario you described, see issue #551. |
YARA currently registers its own signal handler unless instructed otherwise with
SCAN_FLAGS_NO_TRYCATCH
. This is dangerous for several reasons:This PR tries to improve this situation by adding several safety mechanisms when handling signals: