← Tüm makaleler

Therac-25: Bir Yazılım Hatası Nasıl 6 Kişiyi Öldürdü?

Yarış koşulu ve tamsayı taşması hatasının tıbbi bir radyasyon cihazını nasıl silaha dönüştürdüğünün hikayesi

Therac-25: Bir Yazılım Hatası Nasıl 6 Kişiyi Öldürdü?

Yazılım testlerinden geçmiş, on yıldır canlıda çalışan bir kod satırı insanı öldürebilir mi?

Therac-25 vakası bunu mümkün kılan koşulların bir kataloğudur: yeniden kullanılan ama doğrulanmayan kod, kaldırılan donanım güvenlik kilitleri, anlaşılmaz hata mesajları ve hepsinden önemlisi — hatayı bulmayı neredeyse imkânsız kılan bir zamanlama bağımlılığı.

1985 ile 1987 yılları arasında Kuzey Amerika'daki birkaç kanser kliniğinde tuhaf şeyler olmaya başladı. Radyasyon tedavisi gören hastalar tedavinin hemen ardından yanma, ağrı ve şişme bildiriyordu. Bazıları haftalar içinde öldü. Bazıları hayatta kaldı ama kalıcı hasarla.

Hepsinin ortak noktası aynı makineyi kullanmış olmalarıydı: Therac-25.


1. Therac-25 Neydi?

Therac-25, Kanadalı AECL (Atomic Energy of Canada Limited) şirketinin ürettiği bir radyasyon tedavi cihazıydı. İki farklı tedavi modunda çalışabiliyordu:

Doğru çalışırken makine, operatörün seçtiği moda göre mekanik bileşenleri doğru konuma otomatik olarak getiriyor, ardından demeti hastaya yönlendiriyordu.

Therac-25'in önceki modeli Therac-20, donanım seviyesinde güvenlik kilitlerine sahipti: yanlış mod seçildiğinde fiziksel bir mekanizma demeti engellerdi. Therac-25, bu donanım kilitlerini kaldırdı. Güvenlik tamamen yazılıma bırakıldı.

Bu karar ölümcül oldu.


2. Hata: Yarış Koşulu ve Taşma

Therac-25 kazalarının arkasında birbirini besleyen iki yazılım hatası yatıyordu.

Hata 1: Deneyimli Operatörün Tuzağı

Makineyi uzun süre kullanan operatörler hızlı yazma alışkanlığı kazanmıştı. Sistem, operatörün tedavi parametrelerini girdikten sonra bir onay ekranı gösteriyordu. Burada "yukarı ok" tuşuna basarak önceki alana dönüp düzeltme yapılabiliyordu.

Sorun şuydu: 8 saniyeden daha kısa sürede "yukarı ok → düzelt → onayla" işlemi yapılırsa, arka planda çalışan kontrol döngüsü (control loop) henüz mod değişikliğini işlememişti.

Yazılım şöyle çalışıyordu:

/* Basitleştirilmiş — gerçek kod Assembly'deydi */

void setup_treatment(void) {
    if (mode == XRAY) {
        position_flattening_filter();   /* Metali yerleştir */
        set_high_energy_beam();
    } else {
        retract_flattening_filter();    /* Metali çek */
        set_low_energy_beam();
    }
}

/* Ana döngü — mod değişikliği burada işleniyor */
void main_loop(void) {
    while (1) {
        read_operator_input();          /* Bu fonksiyon 'mode' değişkenini günceller */
        setup_treatment();              /* Bu fonksiyon yeni 'mode' değerini okur */
        /* ... */
    }
}

Operatör hızlı davranırsa read_operator_input() yeni modu yazarken setup_treatment() henüz eski modla çalışıyordu. Sonuç: metal hedef yerinde değilken yüksek enerjili X-ışını demeti hastaya gönderildi.

Bu bir yarış koşuluydu: iki süreç aynı bellek konumunu (mode değişkeni) eşzamansız olarak okuyup yazıyordu ve bu erişimler senkronize değildi.

Hata 2: 8-bit Taşması

İkinci hata daha sinsi bir biçimde çalışıyordu.

Yazılımda, tedavi kurulum döngüsünün kaç kez tamamlandığını sayan bir değişken vardı. Bu değişken 8-bit işaretsiz tamsayıydı (uint8_t) — 0'dan 255'e kadar sayabiliyordu.

  1. sayımdan sonra değişken taşarak 0'a döndü. Sıfır, sistemde "hata yok" anlamına geliyordu.
uint8_t setup_count = 0;

void increment_setup(void) {
    setup_count++;   /* 255 + 1 = 0 → "hata yok" kodu */
}

bool is_ready(void) {
    return (setup_count == 0);   /* Hata değil, hazır! */
}

Her 256 tedavide bir, güvenlik kontrolü yanlış olarak "hazır" durumuna geçiyordu. Yarış koşuluyla bu taşma bir araya geldiğinde, sistem hiçbir hata vermeden hastaya normalin 25.000 katı radyasyon dozu gönderdi.


3. Neden Bu Kadar Uzun Süre Fark Edilmedi?

Kazalar 1985'te başladı; AECL hatayı ancak 1987'de tespit edebildi. Bu gecikmenin birkaç nedeni var:

Semptomlar başka nedenlere bağlandı. Hastaların şikayetleri başlangıçta yanlış kurulum, hasta hareketi veya başka tıbbi nedenlerle açıklandı. Radyasyon zehirlenmesi hemen belli olmaz.

Hata üretmek için belirli bir zamanlaması gerekiyordu. Yarış koşulu yalnızca deneyimli operatörler hızlı yazım yaptığında tetikleniyordu. Yeni başlayan operatörler daha yavaş yazdığından, makineleri hiç sorun çıkarmadı. Bu durum sorunu izole etmeyi son derece güçleştirdi.

Test prosedürleri yetersizdi. AECL, Therac-25 yazılımını Therac-20'den büyük ölçüde yeniden kullandı. Güvenlik testleri donanım kilitleri kaldırıldıktan sonra güncellenmedi.

Hata mesajları anlamsızdı. Makine bir hata oluştuğunda ekranda MALFUNCTION 54 gibi bir kod gösteriyordu. Bu kodun ne anlama geldiği ne el kitabında ne de servis belgelerinde açıklanmıştı.


4. Sonuç: 6 Kişi Öldü, Sektör Değişti

Kazalarda 6 kişi hayatını kaybetti, en az 3 kişi kalıcı sakatlandı. FDA, 1988'de AECL'e kapsamlı bir uyarı mektubu gönderdi. Makine piyasadan çekildi.

Bu dava, yazılım mühendisliği tarihinin en çok atıfta bulunulan güvenlik olaylarından biri haline geldi. Sonrasında:


5. Gömülü Programcı İçin Dersler

Ders 1: Yarış koşullarını senkronizasyonla engelleyin

Birden fazla görev (veya kesme, interrupt) aynı değişkene erişiyorsa, bu erişimi koruyun:

/* Kötü: kesme ve ana döngü aynı değişkeni korumasız okuyup yazıyor */
volatile uint8_t mode;

void operator_input_irq(void) {
    mode = get_new_mode();          /* Kesme: yazar */
}

void setup_treatment(void) {
    if (mode == XRAY) { ... }       /* Ana döngü: okur — race condition! */
}

/* İyi: kritik bölümü atomik bir işlem içinde koru */
void setup_treatment(void) {
    __disable_irq();                /* Kesmeleri geçici kapat */
    uint8_t current_mode = mode;    /* Atomik okuma */
    __enable_irq();

    if (current_mode == XRAY) { ... }
}

Ders 2: Sayaç taşmalarını tasarım aşamasında önleyin

/* Kötü: 8-bit taşması güvenlik koşulunu sıfırlıyor */
uint8_t setup_count = 0;
setup_count++;

/* İyi: taşmayı açıkça doyur ya da 32-bit kullan */
if (setup_count < UINT8_MAX) {
    setup_count++;
} else {
    enter_fault_state();
}

Ders 3: Donanım güvenlik kilitlerini yazılımla ikame etmeyin

Güvenlik açısından kritik sistemlerde yazılım hatalarına karşı bir donanım güvencesi bulunmalıdır. Bir röle, bir fiziksel kilit, bir watchdog timer — yazılım başarısız olduğunda bile sistemi güvenli konuma getirecek bağımsız bir mekanizma.

Ders 4: Hata mesajları anlamlı olmalıdır

/* Kötü */
error_code = 54;

/* İyi */
typedef enum {
    ERR_BEAM_INTERLOCK_FAILED = 1,
    ERR_FILTER_POSITION_MISMATCH,
    ERR_SETUP_COUNTER_OVERFLOW,
} SystemError;

void report_error(SystemError err) {
    log_error(err);
    display_human_readable_message(err);
    halt_beam_immediately();
}

Ders 5: Güvenlik koşulları test edilmelidir

Bir güvenlik kontrolü, yalnızca "normal koşulda çalışıyor mu?" diye test edilmemeli; "güvenlik koşulu tetiklendiğinde doğru davranıyor mu?" diye de test edilmelidir. Her güvenlik yolu bağımsız olarak doğrulanmalıdır.


Özet

Hata Açıklama
Yarış koşulu Operatör hızı, mod güncellemesi ile doz hesabı arasında senkronizasyon bozdu
Tamsayı taşması uint8_t sayaç 255'ten 0'a döndü — "hata yok" kodunu tetikledi
Donanım kilidi kaldırıldı Güvenlik tamamen yazılıma bırakıldı; yazılım başarısız olunca koruma kalmadı
Yetersiz test Güvenlik yolları bağımsız olarak test edilmedi
Anlaşılmaz hatalar MALFUNCTION 54 — ne anlama geldiğini kimse bilmiyordu
Sonuç 6 ölü, 3 kalıcı sakat, sektör çapında yeni güvenlik standartları
Kitap serisini incele →