Bài 34.230 phút đọc
AddressSanitizer
Sau bài này bạn sẽ làm được
- Dịch và chạy chương trình với -fsanitize=address
- Đọc báo cáo ASan: nơi truy cập sai, nơi cấp phát, nơi giải phóng
- Hiểu redzone và shadow memory làm việc ra sao
- Biết bốn loại lỗi ASan không bắt được
AddressSanitizer là công cụ hiệu quả nhất trong toàn bộ phần này. Một cờ dịch, chậm gấp đôi, và nó biến mọi lỗi truy cập bộ nhớ từ chỗ "sập ở đâu đó xa lắm" thành một báo cáo chỉ đúng ba dòng mã.
#Chạy thử một lần
tran_bo_dem.c
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *a = malloc(5 * sizeof *a);
for (int i = 0; i <= 5; ++i) /* <= thay vi <: thua mot vong */
a[i] = i;
printf("%d\n", a[0]);
free(a);
return 0;
}terminal
# Không có ASan: chạy êm, mã thoát 0
gcc -std=c11 -g -O1 -Wall -Wextra -o tbd tran_bo_dem.c && ./tbd
0
echo $?
0
terminal
# Có ASan
gcc -std=c11 -g -O1 -fno-omit-frame-pointer -fsanitize=address -o tbd tran_bo_dem.c && ./tbd
=================================================================
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000014 at pc 0x0000004011b6 bp 0x7ffd4f2c1e30 sp 0x7ffd4f2c1e28
WRITE of size 4 at 0x602000000014 thread T0
#0 0x4011b5 in main tran_bo_dem.c:8
#1 0x7f8c3f029d8f in __libc_start_call_main
#2 0x7f8c3f029e3f in __libc_start_main
#3 0x401104 in _start
0x602000000014 is located 0 bytes to the right of 20-byte region [0x602000000000,0x602000000014)
allocated by thread T0 here:
#0 0x7f8c3f2b7357 in malloc
#1 0x401186 in main tran_bo_dem.c:5
SUMMARY: AddressSanitizer: heap-buffer-overflow tran_bo_dem.c:8 in main
==12345==ABORTING#Đọc báo cáo
| Dòng | Nói gì |
|---|---|
| ERROR: heap-buffer-overflow | Loại lỗi. Có bảy loại, xem mục dưới |
| WRITE of size 4 | Ghi hay đọc, và bao nhiêu byte |
| #0 ... main tran_bo_dem.c:8 | Dòng gây lỗi. Đây là dòng bạn cần mở ra |
| 0 bytes to the right of 20-byte region | Vượt bao nhiêu byte, và về phía nào |
| allocated by thread T0 here: ... :5 | Dòng đã cấp phát khối đó |
| SUMMARY | Một dòng tóm tắt, tiện cho việc lọc trong nhật ký |
#Cơ chế: vành đỏ và bộ nhớ bóng
Vành đỏ (redzone)
Vùng đệm ASan chèn vào trước và sau mỗi khối cấp phát. Mọi truy cập rơi vào vành đỏ đều bị báo lỗi ngay lập tức.
Bộ nhớ bóng (shadow memory)
Một byte trạng thái cho mỗi tám byte bộ nhớ thật. Trước mỗi lần đọc hoặc ghi, ASan tra byte bóng tương ứng để biết vùng đó có dùng được không.
Trình biên dịch chèn gì vào
/* Ban viet: */
a[i] = 1;
/* ASan bien no thanh, dai y: */
char *dia_chi = (char *)&a[i];
char *bong = (char *)(((uintptr_t)dia_chi >> 3) + 0x7fff8000);
if (*bong != 0 && ((uintptr_t)dia_chi & 7) + 4 > *bong)
__asan_report_store4(dia_chi); /* in bao cao roi dung */
a[i] = 1;
/* Mot phep dich, mot phep cong, mot lan doc bo nho, mot phep so sanh.
Do la ly do ASan chi cham gap doi chu khong gap hai muoi nhu Valgrind:
Valgrind mo phong tung lenh may, con ASan chi chen them bon lenh. */| Giá trị byte bóng | Nghĩa |
|---|---|
| 00 | Cả 8 byte dùng được |
| 01 tới 07 | n byte đầu dùng được, phần còn lại là vành đỏ |
| fa | Vành đỏ trái của khối heap |
| fb | Vành đỏ phải của khối heap |
| fd | Vùng đã giải phóng |
| f1 | Vành đỏ trái của biến ngăn xếp |
| f5 | Vùng ngăn xếp của hàm đã trả về |
| f9 | Vành đỏ của biến toàn cục |
#Sáu loại lỗi nó bắt
heap-buffer-overflow
int *a = malloc(20);
a[5] = 1; /* 0 bytes to the right of 20-byte region */
a[-1] = 1; /* 4 bytes to the left */stack-buffer-overflow
void f(void) {
int a[5];
a[5] = 1; /* ASan: stack-buffer-overflow */
}
/* Valgrind KHONG bat duoc loai nay, vi no khong biet ranh gioi
giua cac bien tren ngan xep. ASan biet vi trinh bien dich
noi cho no. */global-buffer-overflow
static int a[5];
int main(void) {
a[5] = 1; /* ASan: global-buffer-overflow */
}heap-use-after-free
terminal
./uaf
==12345==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x000000401203 bp 0x7ffe1d3e5a10 sp 0x7ffe1d3e5a08
READ of size 4 at 0x602000000010 thread T0
#0 0x401202 in main uaf.c:11
0x602000000010 is located 0 bytes inside of 16-byte region [0x602000000010,0x602000000020)
freed by thread T0 here:
#0 0x7f1a2c4b8b6f in free
#1 0x4011ec in main uaf.c:9
previously allocated by thread T0 here:
#0 0x7f1a2c4b8357 in malloc
#1 0x4011c6 in main uaf.c:6
SUMMARY: AddressSanitizer: heap-use-after-free uaf.c:11 in maindouble-free và alloc-dealloc-mismatch
free(p);
free(p); /* ASan: attempting double-free */
void *p = malloc(10);
delete p; /* ASan: alloc-dealloc-mismatch, trong C++ */stack-use-after-return
int *f(void) {
int x = 42;
return &x;
}
/* Mac dinh ASan KHONG bat loai nay vi no dat. Phai bat rieng: */
ASAN_OPTIONS=detect_stack_use_after_return=1 ./prog
/* Luc do ASan cap phat khung ngan xep tren heap, nen giu duoc
vanh do sau khi ham tra ve. Cham hon dang ke. */#Kiểm tra rò rỉ
ro_ri.c
#include <stdlib.h>
#include <string.h>
static char *tao_ten(const char *s) {
char *p = malloc(strlen(s) + 1);
strcpy(p, s);
return p;
}
int main(void) {
char *a = tao_ten("Nguyen Van A");
char *b = tao_ten("Tran Thi B");
free(a);
/* quen free(b) */
return 0;
}terminal
gcc -std=c11 -g -O1 -fsanitize=address -o rr ro_ri.c && ./rr
=================================================================
==12345==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 11 bytes in 1 object(s) allocated from:
#0 0x7f2b1c8b7357 in malloc
#1 0x401196 in tao_ten ro_ri.c:5
#2 0x4011f8 in main ro_ri.c:12
SUMMARY: AddressSanitizer: 11 byte(s) leaked in 1 allocation(s).| Tùy chọn | Tác dụng |
|---|---|
| ASAN_OPTIONS=detect_leaks=1 | Bật kiểm tra rò rỉ, mặc định đã bật trên Linux |
| ASAN_OPTIONS=detect_leaks=0 | Tắt, khi bạn cố ý không giải phóng lúc thoát |
| LSAN_OPTIONS=suppressions=bo_qua.txt | Bỏ qua rò rỉ khớp một danh sách |
| ASAN_OPTIONS=fast_unwind_on_malloc=0 | Vết gọi chính xác hơn, chậm hơn |
| -fsanitize=leak | Chỉ chạy LeakSanitizer, không kèm ASan, rất nhẹ |
#Bốn thứ nó không bắt
Trường hợp ASan bỏ qua
typedef struct {
char ten[8];
int diem;
} SinhVien;
SinhVien *sv = malloc(sizeof *sv);
strcpy(sv->ten, "NguyenVanA"); /* 11 byte vao mang 8 byte */
/* ASan KHONG bao, vi ca 11 byte van nam TRONG khoi 16 byte
ma malloc cap. Vanh do nam sau khoi, khong nam giua cac truong.
Chinh vi du nay o Bai 35.5 se duoc GDB bat bang watchpoint.
Ba cach chua:
- dung strncpy hoac snprintf thay strcpy
- bat -D_FORTIFY_SOURCE=2, no bat duoc strcpy voi kich thuoc biet truoc
- dung -fsanitize=address kem -fsanitize-address-use-after-scope */terminal
# _FORTIFY_SOURCE bắt được nhiều lỗi chuỗi ngay lúc dịch
gcc -std=c11 -O2 -D_FORTIFY_SOURCE=2 -Wall -c sv.c
sv.c:9:5: warning: '__builtin___strcpy_chk' writing 11 bytes into a region of size 8 overflows the destination [-Wstringop-overflow=]
Tự làm thử
- Viết chương trình tràn bộ đệm lệch một và chạy nó có và không có ASan.
- Đọc báo cáo và chỉ ra ba thông tin: dòng ghi, dòng cấp phát, số byte vượt.
- Viết ba biến thể tràn: trên heap, trên ngăn xếp, trên biến toàn cục.
- Viết chương trình rò rỉ trong một hàm tiện ích được gọi ba lần, rồi xác định lời gọi nào rò rỉ.
- Chứng minh ASan không bắt được ghi tràn giữa hai trường của một struct.
- Bật
-D_FORTIFY_SOURCE=2và xem nó bắt được thêm gì.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Một cờ dịch, chậm gấp đôi, và báo cáo chỉ đúng dòng truy cập sai, dòng cấp phát và số byte vượt.
- Cơ chế là một byte bóng cho mỗi tám byte thật, cộng với vành đỏ quanh mỗi khối.
- Hàng đợi quarantine giữ khối đã giải phóng một thời gian, nhờ đó bắt được dùng sau khi giải phóng.
- Vết gọi rò rỉ có hai tầng, và tầng gọi mới cho biết lời gọi nào bị rò rỉ.
- Nó không bắt biến chưa khởi tạo, không bắt tràn số, và không bắt ghi tràn giữa hai trường của cùng một struct.