Làm thế nào là ngăn xếp ô nhiễm canary đăng nhập?


11

Cờ bảo vệ cờ GCC -fstack cho phép sử dụng các ngăn xếp ngăn xếp để bảo vệ chống tràn ngăn xếp. Việc sử dụng cờ này theo mặc định đã nổi bật hơn trong những năm gần đây.

Nếu một gói được biên dịch với -fstack-Protector và chúng tôi tràn bộ đệm trong chương trình, chúng tôi có thể gặp lỗi như:

*** buffer overflow detected ***: /xxx/xxx terminated

Tuy nhiên, "ai" chịu trách nhiệm về các thông báo lỗi này? Những thông điệp này được ghi lại ở đâu? Có syslog daemon chọn những tin nhắn này?

Câu trả lời:


10

Đập vỡ ngăn xếp được phát hiện bởi libssp, đó là một phần của gcc. Nó rất cố gắng để xuất thông báo đến một thiết bị đầu cuối và chỉ khi nó không đăng nhập vào nhật ký hệ thống - vì vậy trong thực tế, bạn sẽ thấy các thông báo tràn bộ đệm trong nhật ký cho trình nền và các ứng dụng GUI.

Khi nó xuất ra thông điệp của nó, hãy libsspthử nhiều cách để thoát, bao gồm cả việc làm hỏng ứng dụng; điều này có thể bị bắt bởi một trong những logger thoát bất thường, nhưng điều đó không được đảm bảo.


1
Hãy để tôi đưa ra một ví dụ cụ thể như một cách để khám phá thêm lời giải thích này. Hãy chọn nginx cho ví dụ này. Tôi đã biên dịch nginx với stack canaries. Khi tôi chạy nginx, nó bắt đầu một quá trình nhưng không xuất bất cứ thứ gì vào shell. Thay vào đó, bất kỳ tin nhắn được ghi vào nhiều tệp nhật ký của nó. Nếu nginx phát hiện ngăn xếp ngăn xếp, libsspsẽ xuất thông báo của nó bằng đầu ra stderr được sử dụng bởi nginx. Sau đó, libsspcó thể cố gắng thoát khỏi quy trình (hoặc quy trình con cho nginx). Nếu nó "không cần" làm sập ứng dụng, thì các logger thoát bất thường sẽ không chọn cái này. Đây có phải là một cách giải thích chính xác không?
aedcv

Không hoàn toàn - nó sẽ cố gắng đánh sập ứng dụng, sử dụng __builtin_trap()trước, sau đó nếu thất bại, cố gắng gây ra vi phạm phân khúc và chỉ khi thất bại, thoát khỏi trạng thái 127.
Stephen Kitt

Việc in phần thông điệp không đảm bảo thành công tốt hơn so với lối ra thông qua phương pháp năng suất cốt lõi (ví dụ abort()).
maxschlepzig

7

Các bản phân phối Linux hiện đại như CentOS / Fedora thiết lập một trình nền xử lý sự cố (ví dụ systemd-coredumphoặc abortd), theo mặc định.

Do đó, khi chương trình của bạn kết thúc theo kiểu bất thường (segfault, ngoại lệ chưa được lưu, hủy bỏ, hướng dẫn bất hợp pháp, v.v.), sự kiện này được đăng ký và đăng nhập bởi daemon đó. Vì vậy, bạn tìm thấy một số tin nhắn trong tạp chí hệ thống và có thể là một tham chiếu đến một thư mục với một số chi tiết bổ sung (ví dụ: tệp cốt lõi, nhật ký, v.v.).

Thí dụ

$ cat test_stack_protector.c 
#include <string.h>

int f(const char *q)
{
  char s[10];
  strcpy(s, q);
  return s[0] + s[1];
}

int main(int argc, char **argv)
{
  return f(argv[1]);
}

Biên dịch:

$ gcc -Wall -fstack-protector test_stack_protector.c -o test_stack_protector

Hành hình:

$ ./test_stack_protector 'hello world'
*** stack smashing detected ***: ./test_stack_protector terminated
======= Backtrace: =========
/lib64/libc.so.6(+0x7c8dc)[0x7f885b4388dc]
/lib64/libc.so.6(__fortify_fail+0x37)[0x7f885b4dfaa7]
/lib64/libc.so.6(__fortify_fail+0x0)[0x7f885b4dfa70]
./test_stack_protector[0x400599]
./test_stack_protector[0x4005bd]
/lib64/libc.so.6(__libc_start_main+0xea)[0x7f885b3dc50a]
./test_stack_protector[0x40049a]
======= Memory map: ========
00400000-00401000 r-xp 00000000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
00600000-00601000 r--p 00000000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
00601000-00602000 rw-p 00001000 00:28 1151979                            /home/juser/program/stackprotect/test_stack_protector
0067c000-0069d000 rw-p 00000000 00:00 0                                  [heap]
7f885b1a5000-7f885b1bb000 r-xp 00000000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b1bb000-7f885b3ba000 ---p 00016000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3ba000-7f885b3bb000 r--p 00015000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3bb000-7f885b3bc000 rw-p 00016000 00:28 1052100                    /usr/lib64/libgcc_s-7-20170915.so.1
7f885b3bc000-7f885b583000 r-xp 00000000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b583000-7f885b783000 ---p 001c7000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b783000-7f885b787000 r--p 001c7000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b787000-7f885b789000 rw-p 001cb000 00:28 945348                     /usr/lib64/libc-2.25.so
7f885b789000-7f885b78d000 rw-p 00000000 00:00 0 
7f885b78d000-7f885b7b4000 r-xp 00000000 00:28 945341                     /usr/lib64/ld-2.25.so
7f885b978000-7f885b97b000 rw-p 00000000 00:00 0 
7f885b9b0000-7f885b9b3000 rw-p 00000000 00:00 0 
7f885b9b3000-7f885b9b4000 r--p 00026000 00:28 945341                     /usr/lib64/ld-2.25.so
7f885b9b4000-7f885b9b6000 rw-p 00027000 00:28 945341                     /usr/lib64/ld-2.25.so
7ffc59966000-7ffc59987000 rw-p 00000000 00:00 0                          [stack]
7ffc5999c000-7ffc5999f000 r--p 00000000 00:00 0                          [vvar]
7ffc5999f000-7ffc599a1000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
zsh: abort (core dumped)  ./test_stack_protector 'hello world'

Trạng thái thoát là 134 là 128 + 6, tức là 128 cộng với số tín hiệu hủy bỏ.

Tạp chí hệ thống:

Oct 16 20:57:59 example.org audit[17645]: ANOM_ABEND auid=1000 uid=1000 gid=1000 ses=3 subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 pid=17645 comm="test_stack_prot" exe="/home/juser/program/stackprotect/test_stack_protector" sig=6 res=1
Oct 16 20:57:59 example.org systemd[1]: Started Process Core Dump (PID 17646/UID 0).
Oct 16 20:57:59 example.org audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-coredump@21-17646-0 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
Oct 16 20:57:59 example.org systemd-coredump[17647]: Process 17645 (test_stack_prot) of user 1000 dumped core.

                           Stack trace of thread 17645:
                           #0  0x00007f885b3f269b raise (libc.so.6)
                           #1  0x00007f885b3f44a0 abort (libc.so.6)
                           #2  0x00007f885b4388e1 __libc_message (libc.so.6)
                           #3  0x00007f885b4dfaa7 __fortify_fail (libc.so.6)
                           #4  0x00007f885b4dfa70 __stack_chk_fail (libc.so.6)
                           #5  0x0000000000400599 f (test_stack_protector)
                           #6  0x00000000004005bd main (test_stack_protector)
                           #7  0x00007f885b3dc50a __libc_start_main (libc.so.6)
                           #8  0x000000000040049a _start (test_stack_protector)
Oct 16 20:57:59 example.org audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 subj=system_u:system_r:init_t:s0 msg='unit=systemd-coredump@21-17646-0 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
Oct 16 20:58:00 example.org abrt-notification[17696]: Process 17645 (test_stack_protector) crashed in __fortify_fail()

Điều đó có nghĩa là bạn nhận được đăng nhập từ auditdtrình nền kiểm toán systemd-coredump trình xử lý sự cố.

Để xác minh xem daemon xử lý sự cố có được cấu hình hay không, bạn có thể kiểm tra /proc, ví dụ:

$ cat /proc/sys/kernel/core_pattern
|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %e

(mọi thứ được thử nghiệm trên Fedora 26, x86-64)


1
Tôi rất vui vì bạn đã đăng ví dụ này. Các hoàng yến được đặt tại chỗ bởi gcc. (Vui lòng sửa lại cho tôi nếu tôi sai) Tôi cho rằng những gì xảy ra giống như: gcc đưa "mã bổ sung" vào chương trình để thực hiện chức năng canary; trong khi thực hiện và trước khi hàm trả về, giá trị được kiểm tra; nếu bị ô nhiễm, chương trình sẽ xuất ra thông báo "ngăn xếp đập vỡ được phát hiện" và gây ra lỗi. Lỗi này được HĐH nhận ra, nhận ra lỗi phân đoạn và in bản đồ nền và bộ nhớ bạn đã đăng. Cuối cùng, HĐH giết chết ứng dụng, tạo kết xuất lõi và ghi nhật ký vào sys tạp chí
aedcv

@aedcv, đây là câu chuyện khá nhiều - nói chính xác hơn: ngăn xếp các cuộc gọi mã kiểm tra ngăn xếp abort()tạo ra tín hiệu hủy bỏ, tức là không có lỗi phân đoạn nào xảy ra. Chỉ là các trình xử lý tín hiệu mặc định cho lỗi hủy bỏ / phân đoạn, vv mang lại cùng một hành động: ghi lõi và thoát quá trình với trạng thái thoát không bằng 0 cũng mã hóa số tín hiệu. Việc viết lõi được thực hiện bởi kernel và hành vi của nó có thể cấu hình thông qua /proc/.../core_pattern. Trong ví dụ trên, một trình trợ giúp không gian người dùng được cấu hình và do đó được gọi. Hạt nhân cũng kích hoạt kiểm toán.
maxschlepzig

@maxschlepzig không hoàn toàn abort(), mã SSP sử dụng __builtin_trap()(nhưng hiệu quả là như nhau).
Stephen Kitt

1
@StephenKitt, tốt, hãy xem dấu vết ngăn xếp trong ví dụ trên. Ở đó bạn thấy rõ cách abort()gọi.
maxschlepzig

1
@maxschlepzig có, tất nhiên, nhưng đó là một chi tiết triển khai (mã GCC sử dụng __builtin_trap()để tránh có sự phụ thuộc rõ ràng vào abort()). Các bản phân phối khác có dấu vết ngăn xếp khác nhau.
Stephen Kitt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.