FTL/src/signals.c

390 lines
12 KiB
C

/* Pi-hole: A black hole for Internet advertisements
* (c) 2017 Pi-hole, LLC (https://pi-hole.net)
* Network-wide ad blocking via your own hardware.
*
* FTL Engine
* Signal processing routines
*
* This file is copyright under the latest version of the EUPL.
* Please see LICENSE file for your rights under this license. */
#include "FTL.h"
#if defined(__GLIBC__)
#include <execinfo.h>
#endif
#include "signals.h"
// logg()
#include "log.h"
// ls_dir()
#include "files.h"
// gettid()
#include "daemon.h"
// Eventqueue routines
#include "events.h"
// sleepms()
#include "timers.h"
// struct config
#include "config.h"
#define BINARY_NAME "pihole-FTL"
volatile sig_atomic_t killed = 0;
static volatile pid_t mpid = -1;
static time_t FTLstarttime = 0;
extern volatile int exit_code;
volatile sig_atomic_t thread_cancellable[THREADS_MAX] = { false };
const char *thread_names[THREADS_MAX] = { "" };
// Return the (null-terminated) name of the calling thread
// The name is stored in the buffer as well as returned for convenience
static char * __attribute__ ((nonnull (1))) getthread_name(char buffer[16])
{
prctl(PR_GET_NAME, buffer, 0, 0, 0);
return buffer;
}
#if defined(__GLIBC__)
static void print_addr2line(const char *symbol, const void *address, const int j, const void *offset)
{
// Only do this analysis for our own binary (skip trying to analyse libc.so, etc.)
if(strstr(symbol, BINARY_NAME) == NULL)
return;
// Find first occurrence of '(' or ' ' in the obtaned symbol string and
// assume everything before that is the file name. (Don't go beyond the
// string terminator \0)
int p = 0;
while(symbol[p] != '(' && symbol[p] != ' ' && symbol[p] != '\0')
p++;
// Compute address cleaned by binary offset
void *addr = (void*)(address-offset);
// Invoke addr2line command and get result through pipe
char addr2line_cmd[256];
snprintf(addr2line_cmd, sizeof(addr2line_cmd), "addr2line %p -e %.*s", addr, p, symbol);
FILE *addr2line = NULL;
char linebuffer[512];
if(config.addr2line &&
(addr2line = popen(addr2line_cmd, "r")) != NULL &&
fgets(linebuffer, sizeof(linebuffer), addr2line) != NULL)
{
char *pos;
// Strip possible newline at the end of the addr2line output
if ((pos=strchr(linebuffer, '\n')) != NULL)
*pos = '\0';
}
else
{
snprintf(linebuffer, sizeof(linebuffer), "N/A (%p -> %s)", addr, addr2line_cmd);
}
// Log result
logg("L[%04i]: %s", j, linebuffer);
// Close pipe
if(addr2line != NULL)
pclose(addr2line);
}
#endif
// Log backtrace
void generate_backtrace(void)
{
// Check GLIBC availability as MUSL does not support live backtrace generation
#if defined(__GLIBC__)
// Try to obtain backtrace. This may not always be helpful, but it is better than nothing
void *buffer[255];
const int calls = backtrace(buffer, sizeof(buffer)/sizeof(void *));
logg("Backtrace:");
char ** bcktrace = backtrace_symbols(buffer, calls);
if(bcktrace == NULL)
{
logg("Unable to obtain backtrace symbols!");
return;
}
// Try to compute binary offset from backtrace_symbols result
void *offset = NULL;
for(int j = 0; j < calls; j++)
{
void *p1 = NULL, *p2 = NULL;
char *pend = NULL;
if((pend = strrchr(bcktrace[j], '(')) != NULL &&
strstr(bcktrace[j], BINARY_NAME) != NULL &&
sscanf(pend, "(+%p) [%p]", &p1, &p2) == 2)
offset = (void*)(p2-p1);
}
for(int j = 0; j < calls; j++)
{
logg("B[%04i]: %s", j,
bcktrace != NULL ? bcktrace[j] : "---");
if(bcktrace != NULL)
print_addr2line(bcktrace[j], buffer[j], j, offset);
}
free(bcktrace);
#else
logg("!!! INFO: pihole-FTL has not been compiled with glibc/backtrace support, not generating one !!!");
#endif
}
static void __attribute__((noreturn)) signal_handler(int sig, siginfo_t *si, void *unused)
{
logg("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
logg("----------------------------> FTL crashed! <----------------------------");
logg("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
logg("Please report a bug at https://github.com/pi-hole/FTL/issues");
logg("and include in your report already the following details:");
if(FTLstarttime != 0)
{
logg("FTL has been running for %lli seconds", (long long)time(NULL) - FTLstarttime);
}
log_FTL_version(true);
char namebuf[16];
logg("Process details: MID: %i", mpid);
logg(" PID: %i", getpid());
logg(" TID: %i", gettid());
logg(" Name: %s", getthread_name(namebuf));
logg("Received signal: %s", strsignal(sig));
logg(" at address: %p", si->si_addr);
// Segmentation fault - program crashed
if(sig == SIGSEGV)
{
switch (si->si_code)
{
case SEGV_MAPERR: logg(" with code: SEGV_MAPERR (Address not mapped to object)"); break;
case SEGV_ACCERR: logg(" with code: SEGV_ACCERR (Invalid permissions for mapped object)"); break;
#ifdef SEGV_BNDERR
case SEGV_BNDERR: logg(" with code: SEGV_BNDERR (Failed address bound checks)"); break;
#endif
#ifdef SEGV_PKUERR
case SEGV_PKUERR: logg(" with code: SEGV_PKUERR (Protection key checking failure)"); break;
#endif
#ifdef SEGV_ACCADI
case SEGV_ACCADI: logg(" with code: SEGV_ACCADI (ADI not enabled for mapped object)"); break;
#endif
#ifdef SEGV_ADIDERR
case SEGV_ADIDERR: logg(" with code: SEGV_ADIDERR (Disrupting MCD error)"); break;
#endif
#ifdef SEGV_ADIPERR
case SEGV_ADIPERR: logg(" with code: SEGV_ADIPERR (Precise MCD exception)"); break;
#endif
default: logg(" with code: Unknown (%i)", si->si_code); break;
}
}
// Bus error - memory manager problem
else if(sig == SIGBUS)
{
switch (si->si_code)
{
case BUS_ADRALN: logg(" with code: BUS_ADRALN (Invalid address alignment)"); break;
case BUS_ADRERR: logg(" with code: BUS_ADRERR (Non-existent physical address)"); break;
case BUS_OBJERR: logg(" with code: BUS_OBJERR (Object specific hardware error)"); break;
case BUS_MCEERR_AR: logg(" with code: BUS_MCEERR_AR (Hardware memory error: action required)"); break;
case BUS_MCEERR_AO: logg(" with code: BUS_MCEERR_AO (Hardware memory error: action optional)"); break;
default: logg(" with code: Unknown (%i)", si->si_code); break;
}
}
// Illegal error - Illegal instruction detected
else if(sig == SIGILL)
{
switch (si->si_code)
{
case ILL_ILLOPC: logg(" with code: ILL_ILLOPC (Illegal opcode)"); break;
case ILL_ILLOPN: logg(" with code: ILL_ILLOPN (Illegal operand)"); break;
case ILL_ILLADR: logg(" with code: ILL_ILLADR (Illegal addressing mode)"); break;
case ILL_ILLTRP: logg(" with code: ILL_ILLTRP (Illegal trap)"); break;
case ILL_PRVOPC: logg(" with code: ILL_PRVOPC (Privileged opcode)"); break;
case ILL_PRVREG: logg(" with code: ILL_PRVREG (Privileged register)"); break;
case ILL_COPROC: logg(" with code: ILL_COPROC (Coprocessor error)"); break;
case ILL_BADSTK: logg(" with code: ILL_BADSTK (Internal stack error)"); break;
#ifdef ILL_BADIADDR
case ILL_BADIADDR: logg(" with code: ILL_BADIADDR (Unimplemented instruction address)"); break;
#endif
default: logg(" with code: Unknown (%i)", si->si_code); break;
}
}
// Floating point exception error
else if(sig == SIGFPE)
{
switch (si->si_code)
{
case FPE_INTDIV: logg(" with code: FPE_INTDIV (Integer divide by zero)"); break;
case FPE_INTOVF: logg(" with code: FPE_INTOVF (Integer overflow)"); break;
case FPE_FLTDIV: logg(" with code: FPE_FLTDIV (Floating point divide by zero)"); break;
case FPE_FLTOVF: logg(" with code: FPE_FLTOVF (Floating point overflow)"); break;
case FPE_FLTUND: logg(" with code: FPE_FLTUND (Floating point underflow)"); break;
case FPE_FLTRES: logg(" with code: FPE_FLTRES (Floating point inexact result)"); break;
case FPE_FLTINV: logg(" with code: FPE_FLTINV (Floating point invalid operation)"); break;
case FPE_FLTSUB: logg(" with code: FPE_FLTSUB (Subscript out of range)"); break;
#ifdef FPE_FLTUNK
case FPE_FLTUNK: logg(" with code: FPE_FLTUNK (Undiagnosed floating-point exception)"); break;
#endif
#ifdef FPE_CONDTRAP
case FPE_CONDTRAP: logg(" with code: FPE_CONDTRAP (Trap on condition)"); break;
#endif
default: logg(" with code: Unknown (%i)", si->si_code); break;
}
}
generate_backtrace();
// Print content of /dev/shm
ls_dir("/dev/shm");
logg("Please also include some lines from above the !!!!!!!!! header.");
logg("Thank you for helping us to improve our FTL engine!");
// Terminate main process if crash happened in a TCP worker
if(mpid != getpid())
{
// This is a forked process
logg("Asking parent pihole-FTL (PID %i) to shut down", (int)mpid);
kill(mpid, SIGRTMIN+2);
logg("FTL fork terminated!");
}
else
{
// This is the main process
cleanup(EXIT_FAILURE);
}
// Terminate process indicating failure
exit(EXIT_FAILURE);
}
static void SIGRT_handler(int signum, siginfo_t *si, void *unused)
{
// Backup errno
const int _errno = errno;
// Ignore real-time signals outside of the main process (TCP forks)
if(mpid != getpid())
{
// Restore errno before returning
errno = _errno;
return;
}
int rtsig = signum - SIGRTMIN;
logg("Received: %s (%d -> %d)", strsignal(signum), signum, rtsig);
if(rtsig == 0)
{
// Reload
// - gravity
// - exact whitelist
// - regex whitelist
// - exact blacklist
// - exact blacklist
// WITHOUT wiping the DNS cache itself
set_event(RELOAD_GRAVITY);
// Reload the privacy level in case the user changed it
set_event(RELOAD_PRIVACY_LEVEL);
// Reload blocking status
set_event(RELOAD_BLOCKINGSTATUS);
}
else if(rtsig == 2)
{
// Terminate FTL indicating failure
exit_code = EXIT_FAILURE;
kill(0, SIGTERM);
}
else if(rtsig == 3)
{
// Reimport alias-clients from database
set_event(REIMPORT_ALIASCLIENTS);
}
else if(rtsig == 4)
{
// Re-resolve all clients and forward destinations
// Force refreshing hostnames according to
// REFRESH_HOSTNAMES config option
set_event(RERESOLVE_HOSTNAMES_FORCE);
}
else if(rtsig == 5)
{
// Parse neighbor cache
set_event(PARSE_NEIGHBOR_CACHE);
}
// Restore errno before returning back to previous context
errno = _errno;
}
// Register ordinary signals handler
void handle_signals(void)
{
struct sigaction old_action;
const int signals[] = { SIGSEGV, SIGBUS, SIGILL, SIGFPE };
for(unsigned int i = 0; i < sizeof(signals)/sizeof(signals[0]); i++)
{
// Catch this signal
sigaction (signals[i], NULL, &old_action);
if(old_action.sa_handler != SIG_IGN)
{
struct sigaction SIGaction;
memset(&SIGaction, 0, sizeof(struct sigaction));
SIGaction.sa_flags = SA_SIGINFO;
sigemptyset(&SIGaction.sa_mask);
SIGaction.sa_sigaction = &signal_handler;
sigaction(signals[i], &SIGaction, NULL);
}
}
// Log start time of FTL
FTLstarttime = time(NULL);
}
// Register real-time signal handler
void handle_realtime_signals(void)
{
// This function is only called once (after forking), store the PID of
// the main process
mpid = getpid();
// Catch all real-time signals
for(int signum = SIGRTMIN; signum <= SIGRTMAX; signum++)
{
struct sigaction SIGACTION;
memset(&SIGACTION, 0, sizeof(struct sigaction));
SIGACTION.sa_flags = SA_SIGINFO;
sigemptyset(&SIGACTION.sa_mask);
SIGACTION.sa_sigaction = &SIGRT_handler;
sigaction(signum, &SIGACTION, NULL);
}
}
// Return PID of the main FTL process
pid_t main_pid(void)
{
if(mpid > -1)
// Has already been set
return mpid;
else
// Has not been set so far
return getpid();
}
void thread_sleepms(const enum thread_types thread, const int milliseconds)
{
if(killed)
return;
thread_cancellable[thread] = true;
sleepms(milliseconds);
thread_cancellable[thread] = false;
}