2011-01-28 29 views
11

Tôi vừa phát hiện ra rằng ai đó đang gọi điện thoại - từ một trình xử lý tín hiệu - một chức năng an toàn không chắc chắn-an toàn mà tôi đã viết. Và, tất nhiên, tôi nhận được sự đổ lỗi (mặc dù cảnh báo trong tài liệu của tôi). (Cùng một lập trình viên đang gọi tất cả các chức năng không an toàn-tín hiệu-an toàn từ bộ xử lý tín hiệu của mình.)HOWTO xác định xem mã có đang chạy trong ngữ cảnh trình xử lý tín hiệu không?

Vì vậy, bây giờ tôi tò mò: làm thế nào để tránh tình trạng này xảy ra lần nữa? Tôi muốn có thể dễ dàng xác định xem mã của tôi có đang chạy trong ngữ cảnh xử lý tín hiệu (ngôn ngữ là C, nhưng không phải giải pháp áp dụng cho bất kỳ ngôn ngữ nào không?):

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

Đây là dưới Linux. Hy vọng đây không phải là câu trả lời dễ dàng, nếu không tôi sẽ cảm thấy như một thằng ngốc.

+0

Đã hiểu. Tuy nhiên, khi một lập trình viên lần đầu tiên gặp phải khái niệm về tín hiệu, điều này luôn xảy ra. Đoán xem điều gì sẽ xảy ra khi một destructor của đối tượng C++ được gọi cưỡng bức từ một trình xử lý tín hiệu? – smcdow

Trả lời

7

Dường như, Linux/x86 mới hơn (có lẽ kể từ hạt nhân 2.6.x) gọi trình xử lý tín hiệu từ số vdso. Bạn có thể sử dụng thực tế này để gây ra sự tấn công khủng khiếp sau khi thế giới không ngờ:

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

+0

Tôi không biết rằng các trình xử lý tín hiệu được gọi từ vdso. Bạn có thể trỏ đến một tham chiếu? Ở mức nào, tôi thích hack này. Rất nhiều. Thật dễ dàng để cuộn nó thành một thư viện mờ đục. Bí quyết sẽ đảm bảo rằng parse_maps() được gọi trước bất kỳ trình xử lý tín hiệu nào. – smcdow

+0

Tham chiếu tốt nhất tôi có thể tìm thấy là http://lxr.free-electrons.com/source/arch/x86/kernel/signal.c?v=2.6.37#L310 – ninjalj

+0

Nhưng nhận xét ở dòng 320 có vẻ thú vị: http : //lxr.free-electrons.com/source/arch/x86/kernel/signal.c? v = 2.6.37 # L320 – ninjalj

0

Có hai cách thích hợp để đối phó với điều này:

  • Có đồng nghiệp của bạn ngừng làm điều sai trái. Tuy nhiên, may mắn sẽ kéo cái này ra với ông chủ ...

  • Làm cho hàm của bạn trở lại và không an toàn. Nếu cần, hãy cung cấp chức năng có chữ ký khác (ví dụ: sử dụng quy ước đặt tên *_r được sử dụng rộng rãi) với các đối số bổ sung cần thiết để bảo tồn trạng thái.

Đối với không đúng cách để làm điều này, trên Linux với GNU libc bạn có thể sử dụng backtrace() và bạn bè để đi qua danh sách người gọi của chức năng của bạn. Đó là không dễ dàng để có được quyền, an toàn hoặc di động, nhưng nó có thể làm cho một khoảng thời gian:

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

Theo ý kiến ​​của tôi, nó chỉ có thể là tốt hơn để bỏ hơn là bị buộc phải viết mã như này.

+0

Tôi vừa thử 'backtrace()' và nó không hoạt động: '__libc_start_main' nằm trong dấu vết cả trong và ngoài ngữ cảnh xử lý tín hiệu. –

+0

Như tôi đã đề cập, đó là _not_ dễ dàng để có được quyền. Bạn phải tìm một sự khác biệt trong backtrace giữa hai trường hợp và sử dụng đó. Ví dụ như đối với thử nghiệm của tôi, tôi giả định rằng không có hàm libc nào nằm trong backtrace trước khi đạt tới 'main()', trừ khi đó là mã xử lý tín hiệu. Backtrace của bạn trông như thế nào trong mỗi trường hợp? – thkala

+0

Hai bình luận: (1) Tôi sợ nó có thể giống như thế này. (2) Không phải là snot, nhưng printf (3) không phải là chức năng an toàn-tín hiệu không đồng bộ. Bạn sẽ phải sử dụng viết (2). - Một danh sách các chức năng an toàn-tín hiệu-an toàn (ít nhất, danh sách mà tôi thường đề cập đến) có thể được tìm thấy tại đây: http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html – smcdow

0

Bạn có thể tìm ra thứ gì đó bằng cách sử dụng sigaltstack. Thiết lập một ngăn xếp tín hiệu thay thế, lấy con trỏ ngăn xếp theo một số cách an toàn không đồng bộ, nếu trong ngăn xếp thay thế tiếp tục, nếu không hủy bỏ().

+0

Tôi nghĩ về một cái gì đó như thế, nhưng tôi không nghĩ rằng tôi có thể đảm bảo rằng tôi sẽ có ngăn xếp thay thế được thiết lập trước khi chức năng của tôi được gọi từ một bộ xử lý tín hiệu. Tôi cũng nên nhắc lại rằng chức năng của tôi KHÔNG BAO GIỜ được gọi từ một bộ xử lý tín hiệu, và tài liệu nói như vậy. – smcdow

+0

Có cách dễ dàng hơn. 'sigaltstack' là cần thiết để trả về lỗi nếu bạn đang chạy trên ngăn xếp thay thế và cố gắng thực hiện thay đổi cho nó, vì vậy bạn chỉ có thể thử gọi nó và xem liệu cuộc gọi có bị lỗi hay không. –

0

Tôi đoán bạn cần làm như sau. Đây là một giải pháp phức tạp, kết hợp các thực hành tốt nhất không chỉ từ mã hóa, mà còn từ kỹ thuật phần mềm!

  1. thuyết phục ông chủ rằng quy ước đặt tên trên trình xử lý tín hiệu là điều tốt. Đề xuất, ví dụ: Hungarian notation và nói rằng nó đã được sử dụng trong Microsoft với thành công lớn. Vì vậy, tất cả trình xử lý tín hiệu sẽ bắt đầu bằng sighnd, như sighndInterrupt.
  2. Chức năng của bạn để phát hiện tín hiệu bối cảnh xử lý sẽ làm như sau:
    1. Lấy backtrace().
    2. Hãy xem có bất kỳ chức năng nào trong số đó bắt đầu bằng sighnd... hay không. Nếu có, xin chúc mừng, bạn đang ở trong một bộ xử lý tín hiệu!
    3. Nếu không, bạn thì không.
  3. Cố gắng tránh làm việc với Jimmy trong cùng một công ty. "Có thể chỉ có một", bạn biết đấy.
+0

Chúng tôi sẽ có một người gác cổng thực hiện chuyển qua cơ sở mã và báo cáo lại bất kỳ và tất cả các chức năng được gọi từ trình xử lý tín hiệu. Tôi đã khóc rồi. – smcdow

+0

@smcdow, bằng cách này, bạn có thể sử dụng một công cụ phân tích tĩnh cho điều đó. Nhưng sau đó một lần nữa, bạn phải chú thích mỗi bộ xử lý tín hiệu, điều này làm cho giải pháp được đề xuất không phức tạp hơn. :-) –

+0

một số thời gian trước đây đã có một công cụ Valgrind để tìm các vấn đề xử lý tín hiệu, nó được gọi là crocus. – ninjalj

0

cho mã tối ưu hóa tại -O2 hoặc tốt hơn (báo cáo ISTR) đã tìm thấy cần phải thêm -fno-omit-frame-pointer

khác gcc sẽ tối ưu hóa các thông tin ngăn xếp bối cảnh

3

Nếu chúng ta có thể giả định của bạn ứng dụng không chặn tín hiệu theo cách thủ công bằng cách sử dụng sigprocmask() hoặc pthread_sigmask(), sau đó điều này khá đơn giản: nhận ID luồng hiện tại của bạn (tid). Mở /proc/tid/status và nhận các giá trị cho SigBlkSigCgt. AND hai giá trị đó. Nếu kết quả của số đó AND khác không, thì luồng đó hiện đang chạy từ bên trong trình xử lý tín hiệu. Tôi đã tự mình thử nghiệm và nó hoạt động.

+0

Bạn cần ID tiến trình (PID), không phải ID luồng. Và làm điều này sẽ liên quan đến việc gọi các chức năng không an toàn-tín hiệu-an toàn, chặn viết của riêng bạn. – mgarey

Các vấn đề liên quan