如何确定代码是否在信号处理程序上下文中运行?

发布于 2024-10-14 23:32:56 字数 330 浏览 1 评论 0原文

我刚刚发现有人正在从信号处理程序调用我编写的绝对不是异步信号安全的函数。

所以,现在我很好奇:如何避免这种情况再次发生?我希望能够轻松确定我的代码是否在信号处理程序上下文中运行(语言是 C,但该解决方案不适用于任何语言吗?):

int myfunc( void ) {
    if( in_signal_handler_context() ) { return(-1) }
    // rest of function goes here
    return( 0 );
}

这是在 Linux 下。 希望这不是一个简单的答案,否则我会觉得自己像个白痴。

I just found out that someone is calling - from a signal handler - a definitely not async-signal-safe function that I wrote.

So, now I'm curious: how to circumvent this situation from happening again? I'd like to be able to easily determine if my code is running in signal handler context (language is C, but wouldn't the solution apply to any language?):

int myfunc( void ) {
    if( in_signal_handler_context() ) { return(-1) }
    // rest of function goes here
    return( 0 );
}

This is under Linux.
Hope this isn't an easy answer, or else I'll feel like an idiot.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

三寸金莲 2024-10-21 23:32:56

显然,较新的 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。

Apparently, newer Linux/x86 (probably since some 2.6.x kernel) calls signal handlers from the vdso. You could use this fact to inflict the following horrible hack upon the unsuspecting world:

#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.

毁虫ゝ 2024-10-21 23:32:56

如果我们可以假设您的应用程序不会使用 sigprocmask() 或 pthread_sigmask() 手动阻止信号,那么这非常简单:获取当前线程 ID (tid)。打开 /proc/tid/status 并获取 SigBlkSigCgt 的值。 AND 这两个值。如果该 AND 的结果非零,则该线程当前正在信号处理程序内部运行。我自己测试过这个并且有效。

If we can assume your application doesn't manually block signals using sigprocmask() or pthread_sigmask(), then this is pretty simple: get your current thread ID (tid). Open /proc/tid/status and get the values for SigBlk and SigCgt. AND those two values. If the result of that AND is non-zero, then that thread is currently running from inside a signal handler. I've tested this myself and it works.

貪欢 2024-10-21 23:32:56

有两种正确的方法可以解决此问题:

  • 让您的同事停止做错误的事情。不过,祝你好运,和老板一起解决这个问题……

  • 让你的函数可重入且异步安全。如有必要,请提供具有不同签名的函数(例如,使用广泛使用的 *_r 命名约定)以及状态保存所需的附加参数。

至于执行此操作的非正确方法,在带有 GNU libc 的 Linux 上,您可以使用 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");
}

在我看来,退出可能比被迫编写更好像这样的代码。

There are two proper ways to deal with this:

  • Have your co-workers stop doing the wrong thing. Good luck pulling this off with the boss, though...

  • Make your function re-entrant and async-safe. If necessary, provide a function with a different signature (e.g. using the widely-used *_r naming convention) with the additional arguments that are necessary for state preservation.

As for the non-proper way to do this, on Linux with GNU libc you can use backtrace() and friends to go through the caller list of your function. It's not easy to get right, safe or portable, but it might do for a while:

/*
 * *** 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");
}

In my opinion, it might just be better to quit rather than be forced to write code like this.

厌味 2024-10-21 23:32:56

您可以使用 sigaltstack 来解决一些问题。设置替代信号堆栈,以某种异步安全方式获取堆栈指针,如果在替代堆栈内继续,否则 abort()。

You could work out something using sigaltstack. Set up an alternative signal stack, get the stack pointer in some async-safe way, if within the alternative stack go on, otherwise abort().

栖迟 2024-10-21 23:32:56

我想你需要执行以下操作。这是一个复杂的解决方案,它不仅结合了编码的最佳实践,还结合了软件工程的最佳实践!

  1. 说服你的老板信号处理程序的命名约定是一件好事。例如,提出一个匈牙利表示法,并说明它用于微软取得了巨大的成功。
    因此,所有信号处理程序都以 sighnd 开头,例如 sighndInterrupt
  2. 检测信号处理上下文的函数将执行以下操作:
    1. 获取backtrace()
    2. 查看其中是否有任何函数以 signighd... 开头。如果是这样,那么恭喜你,你已经进入了信号处理程序!
    3. 否则,你就不是。
  3. 尽量避免与 Jimmy 在同一家公司工作。 “只能有一个”,你知道。

I guess you need to do the following. This is a complex solution, which combines the best practices not only from coding, but from software engineering as well!

  1. Persuade your boss that naming convention on signal handlers is a good thing. Propose, for example, a Hungarian notation, and tell that it was used in Microsoft with great success.
    So, all signal handlers will start with sighnd, like sighndInterrupt.
  2. Your function that detects signal handling context would do the following:
    1. Get the backtrace().
    2. Look if any of the functions in it begin with sighnd.... If it does, then congratulations, you're inside a signal handler!
    3. Otherwise, you're not.
  3. Try to avoid working with Jimmy in the same company. "There can be only one", you know.
三生一梦 2024-10-21 23:32:56

对于在 -O2 或更好(istr)优化的代码,发现需要添加 -fno-omit-frame-pointer

否则 gcc 将优化堆栈上下文信息

for code optimized at -O2 or better (istr) have found need to add -fno-omit-frame-pointer

else gcc will optimize out the stack context information

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文