我刚刚发现有人正在调用 - 从信号处理程序 - 一个绝对不是我编写的异步信号安全函数。而且,当然,我受到了责备(尽管我的文档中有警告)。 (同一个程序员从他的信号处理程序中调用各种非异步信号安全函数。叹气。)
那么,现在我很好奇:如何避免这种情况再次发生?我希望能够轻松确定我的代码是否在信号处理程序上下文中运行(语言是C,但解决方案不适用于任何语言吗?):
int myfunc( void ) {
if( in_signal_handler_context() ) { return(-1) }
// rest of function goes here
return( 0 );
}
这是在Linux下。
希望这不是一个简单的答案,否则我会觉得自己像个白痴。
显然,较新的Linux / x86(可能是因为某些2.6.x内核)调用了信号处理程序 vdso
。你可以利用这个事实给毫无疑问的世界造成以下可怕的黑客攻击:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
uintmax_t vdso_start = 0;
uintmax_t vdso_end = 0; /* actually, next byte */
int check_stack_for_vdso(uint32_t *esp, size_t len)
{
size_t i;
for (i = 0; i < len; i++, esp++)
if (*esp >= vdso_start && *esp < vdso_end)
return 1;
return 0;
}
void handler(int signo)
{
uint32_t *esp;
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
/* XXX only for demonstration, don't call printf from a signal handler */
printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
}
void parse_maps()
{
FILE *maps;
char buf[256];
char path[7];
uintmax_t start, end, offset, inode;
char r, w, x, p;
unsigned major, minor;
maps = fopen("/proc/self/maps", "rt");
if (maps == NULL)
return;
while (!feof(maps) && !ferror(maps)) {
if (fgets(buf, 256, maps) != NULL) {
if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s",
&start, &end, &r, &w, &x, &p, &offset,
&major, &minor, &inode, path) == 11) {
if (!strcmp(path, "[vdso]")) {
vdso_start = start;
vdso_end = end;
break;
}
}
}
}
fclose(maps);
printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end);
}
int main()
{
struct sigaction sa;
uint32_t *esp;
parse_maps();
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART;
if (sigaction(SIGUSR1, &sa, NULL) < 0) {
perror("sigaction");
exit(1);
}
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
kill(getpid(), SIGUSR1);
__asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp));
printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20));
return 0;
}
SCNR。
如果我们可以假设您的应用程序不使用手动阻止信号 sigprocmask()
要么 pthread_sigmask()
,这很简单:获取当前的线程ID(tid
)。打开 /proc/tid/status
并获取值 SigBlk
和 SigCgt
。 AND
那两个价值观。如果结果那样 AND
非零,那么该线程当前正在从信号处理程序内部运行。我自己测试了这个并且它有效。
有两种正确的方法可以解决这个问题:
至于 不正确的 这样做的方法,在Linux上使用GNU libc就可以使用了 backtrace()
和朋友一起浏览你的函数的调用者列表。它的 不 容易上手,安全或便携,但它可能会持续一段时间:
/*
* *** Warning ***
*
* Black, fragile and unportable magic ahead
*
* Do not use this, lest the daemons of hell be unleashed upon you
*/
int in_signal_handler_context() {
int i, n;
void *bt[1000];
char **bts = NULL;
n = backtrace(bt, 1000);
bts = backtrace_symbols(bt, n);
for (i = 0; i < n; ++i)
printf("%i - %s\n", i, bts[i]);
/* Have a look at the caller chain */
for (i = 0; i < n; ++i) {
/* Far more checks are needed here to avoid misfires */
if (strstr(bts[i], "(__libc_start_main+") != NULL)
return 0;
if (strstr(bts[i], "libc.so.6(+") != NULL)
return 1;
}
return 0;
}
void unsafe() {
if (in_signal_handler_context())
printf("John, you know you are an idiot, right?\n");
}
在我看来,它可能会更好 放弃 而不是被迫写代码 这个。
你可以用一些东西来解决问题 SIGALTSTACK。设置一个替代信号堆栈,以一些异步安全的方式获取堆栈指针,如果在备用堆栈中继续,否则abort()。
我想你需要做以下事情。这是一个复杂的解决方案,它不仅结合了编码方面的最佳实践,还结合了软件工程方面的最佳实践!
- 说服你的老板,信号处理程序的命名约定是一件好事。例如,建议a 匈牙利表示法,并告诉它在微软使用它取得了巨大成功。
因此,所有信号处理程序都将从一开始
sighnd
, 喜欢 sighndInterrupt
。
- 检测信号处理上下文的函数将执行以下操作:
- 得到
backtrace()
。
- 看看它中的任何函数是否以
sighnd...
。如果确实如此,那么恭喜你,你就是一个信号处理者!
- 否则,你不是。
- 尽量避免使用 吉米 在同一家公司。你知道,“只有一个”。
对于在-O2或更好(istr)优化的代码,我们发现需要添加-fno-omit-frame-pointer
否则gcc将优化堆栈上下文信息