Trang chủ Development

Race condition là gì? Làm sao để khai thác?

Race condition là gì ?

Ảnh 1.

(Ảnh: 256stuff.com)

Race condition là một tình huống xảy ra khi nhiều threads cùng truy cập và cùng lúc muốn thay đổi dữ liệu (có thể là một biến, một row trong database, một vùng shared data, memory , etc...). Vì thuật toán chuyển đổi việc thực thi giữa các threads có thể xảy ra bất cứ lúc nào, nên không thể biết được thứ tự của các threads truy cập và thay đổi dữ liệu đó sẽ dẫn đến giá trị của data sẽ không như mong muốn. Kết quả sẽ phụ thuộc vào thuật toán thread scheduling của hệ điều hành. Quá trình các threads thực thi lệnh trông như 1 cuộc đua giữa các vận động viên điền kinh olympic vì vậy có thể liên tưởng đến thuật ngữ "Race condition".

Làm sao để khai thác lỗi ?

Nếu một chương trình vướng phải lỗi này, người tận dụng lỗi có thể chạy nhiều tiến trình song song để "race" với chương trình có lỗi, với mục đích là thay đổi hoạt động của chương trình ấy. Đôi khi, trường hợp đua còn được biết đến với tên gọi thời điểm kiểm tra/thời điểm sử dụng (Time Of Check/Time Of Use, TOC/TOU).

Thông thường, khi khai thác một lỗi thì hai câu hỏi chính cho việc tận dụng lỗi là cần nhập gì và cách nhập dữ liệu ấy vào chương trình, ở trường hợp này chúng ta gặp câu hỏi quan trọng thứ ba là khi nào thì nhập dữ liệu vào chương trình. Một chương trình có thể không nhận dữ liệu cho đến khi một số yêu cầu được thỏa mãn, và chỉ nhận dữ liệu trong một khoảng thời gian ngắn. Do đó xác định được thời điểm nhập liệu chính xác trở thành một vấn đề căn bản.

Chẳng hạn, chúng ta có một đoạn code sau

Ảnh 2.

Trước hết chúng ta sẽ gán quyền root cho chương trình:
Ảnh 3.

Chương trình này cho phép đọc nội dung của một tập tin có tên là tham số dòng lệnh đầu tiên và in nội dung tập tin đó ra màn hình. Do chương trình này có quyền root, nên hàm fopen sẽ có thể đọc được nội dung của bất kỳ tập tin nào. Vì không thể để một người dùng thông thường đọc nội dung của các tập tin nhạy cảm. Tạo một tập tin thuộc về root để đảm bảo chỉ có root mới đọc được tập tin này. Nội dung tập tin là dòng chữ "Ok, fine :(" .

Ảnh 4.

Chương trình đã sử dụng thêm hàm access để kiểm tra xem người dùng khác có thể đọc tập tin này hay không. Và người dùng bình thường không thể đọc được tập tin này

Ảnh 5.

Vấn đề với chương trình này đó là hàm access và hàm fopen không thực hiện hai tác vụ kiểm tra quyền và mở tập tin một cách không thể tách rời (atomic). Nói một cách khác, có một khoảng thời gian ngắn giữa hàm access và hàm fopen mà hệ điều hành có thể chuyển qua thực thi một tiến trình khác, rồi quay lại như trong hình sau.
Ảnh 6.

Bước 1: Tạo một liên kết tên raceexp chỉ đến một tập tin chúng ta có thể đọc ví dụ như race.c.

Bước 2: Thực thi chương trình bị lỗi với tham số raceexp để chương trình này kiểm tra khả năng đọc tập tin raceexp, mà thật chất là tập tin race.c.

Ảnh 7.

Bước 3: Nếu may mắn, hệ điều hành chuyển quyền thực thi lại cho tiến trình được tạo ở bước 1 ngay sau khi tiến trình ở bước 2 hoàn thành việc kiểm tra, thì chúng ta sẽ chuyển liên kết raceexp chỉ đến tập tin race.txt.

Bước 4: Hệ điều hành chuyển lại tiến trình bị lỗi, và hàm fopen mở tập tin raceexp mà bây giờ thật ra là tập tin race.txt.

Để tối ưu việc tận dụng, chúng ta sẽ đặt các tác vụ chuyển đổi liên kết mềm trong một script như trên. Chúng ta sẽ thực thi đoạn script này ở chế độ background. Ở chế độ foreground, chúng ta sẽ thực hiện lệnh gọi chương trình bị lỗi. Sau một ít lần gọi, chúng ta sẽ đọc được nội dung của tập tin race.txt

Ảnh 8.

Race conditon thường gặp nhiều trong các ứng dụng xử lý tập tin, hoặc truy cập cơ sở dữ liệu. Các tài nguyên này được dùng chung bởi nhiều tiến trình, hoặc tiểu trình (thread) của cùng một tiến trình nên rất dễ xảy ra các cuộc "đua" giành quyền sử dụng. Cách thông thường nhất để tránh lỗi là tuần tự hóa (serialize) truy cập vào những tài nguyên này, với các khóa (lock), hoặc cờ hiệu (semaphore).

>> Tìm hiểu thêm: Sơ bộ về kiến trúc của Java