Điều kiện đua ● Xảy ra khi nhiều tiến trình (tiểu trình) truy cập và sửa đổi cùng một dữ liệu vào cùng lúc, kết quả phụ thuộc vào thứ tự truy cập Dữ liệu chung Tiến trình 1 Tiến trình 2 Điều kiện đua ● Để tận dụng lỗi, người ta thường chạy thật nhiều tiến trình ngoài để “đua” với tiến trình bị lỗi → chữ “đua” ● Còn được gọi là TOC/TOU ● Xét ví dụ: ● if (access[argv[1], R_OK) == 0) { f = fopen(argv[1], “r”); } Điều kiện đua ● Giữa hàm access() và fopen() có một khoảng thời gian nhỏ ● CALL access CMP EAX, 0 JNZ ... PUSH ... PUSH ... CALL fopen ● Hệ điều hành có thể chuyển qua tiến trình khác ở giữa các lệnh đó Điều kiện đua Điều kiện đua ● access() và fopen() nhận tên file ● Cùng một tên nhưng có thể là 2 file khác nhau ● symlink ● Do đó, ta sẽ tận dụng bằng cách: ● Chỉ symlink đến tập tin có thể đọc được ● Chỉ symlink đến tập tin không có quyền đọc nhưng chương trình bị lỗi có thể đọc được ● Lập lại 2 việc này liên tục ● Song song đó, ta sẽ chạy chương trình bị lỗi và truyền tên symlink vào Điều kiện đua ● Điều kiện đua thường gặp ở các ứng dụng xử lý file, ứng dụng mạng, database, ứng dụng đa tiểu trình, ứng dụng web ● Ứng dụng web đặc biệt dễ mắc phải vì vừa đa tiến (tiểu) trình, vừa truy cập database, vừa truy cập file ● Cách dễ nhất để tránh lỗi là tuần tự hóa truy cập vào tài nguyên chung ● File lock, database isolation level ● Spinlock, mutex, futex, CRITICAL_SECTION, semaphore Điều kiện đua ● Cẩn thận với deadlock ● Khi hai hoặc nhiều tiến (tiểu) trình chờ nhau ● Xét ví dụ: ● lock(resource1) lock(resource2) lock(resource2) lock(resource1) dosomething() dosomething() ● Cách khắc phục ● Luôn luôn chú ý về thứ tự truy cập ● Lock phải được cấp theo cùng thứ tự, và mở theo thứ tự ngược ● Lock phải được mở ngay sau khi đã dùng xong Dư một ● Là trường hợp đặc biệt của tràn bộ đệm trong đó chỉ tràn duy nhất 01 byte ● Xét ví dụ ● void vuln(char *arg) { char buf[8]; strcpy(buf, arg); } void main(int argc, char **argv) { vuln(argv[1]); } Dư một ● Giả sử argv[1] dài 8 ký tự ● Khi vào vuln() thì biến buf sẽ chứa 8 ký tự này ● Và ký tự kết thúc (NUL) sẽ lem ra ngoài ● Ký tự \x00 này lem vào ô chứa EBP cũ ● Trong phần kết thúc của vuln(), POP EBP vì thế sẽ khiến EBP mang giá trị XXXXXX00 ● Trong phần kết thúc của main(), MOV ESP, EBP sẽ khiến ESP có giá trị XXXXXX00 Dư một ● Sau đó POP EBP sẽ làm ESP tăng thêm 4 ● Cuối cùng RET sẽ lấy giá trị hiện tại trên đỉnh ngăn xếp để quay về → lỗi xảy ra trong vuln() nhưng tận dụng trong main() ● Hai điểm cần chú ý: ● Giá trị EBP mới sẽ nhỏ hơn giá trị EBP đã lưu ● Và do đó có thể chỉ tới phần ngăn xếp trong vuln() ● Giả sử biến buf có địa chỉ tận cùng là 00 → cơ hội EBP chỉ tới buf sẽ cao → địa chỉ trở về của main() sẽ là từ địa chỉ của biến buf + 4 Dư một Dư một