Aikido

NASA’nın güvenlik açısından kritik kodlar için belirlediği 10 kodlama kuralı

Giriş

Uzay araçlarında veya otomotiv sistemlerinde kullanılanlar gibi güvenlik açısından kritik yazılımlar, son derece güvenilir kod gerektirir. Bu sorunu çözmek amacıyla NASA’nın Jet Propulsion Laboratory (JPL), 2006 yılında “
” ‍“Power of 10” kodlama kurallarını oluşturdu. Bu özlü kılavuzlar, analiz edilmesi zor olan karmaşık C yapılarını ortadan kaldırarak kodun basit, doğrulanabilir ve güvenilir kalmasını sağlar.

Günümüzde, Aikido kod kalitesi gibi araçlar, her yeni çekme isteğinde on kuralın tamamının uygulanmasını sağlamak üzere özel denetimlerle yapılandırılabilir. Bu makalede, her bir kuralı ve bunun neden önemli olduğunu açıklıyor, hem yanlış hem de doğru yaklaşımları gösteren kod örnekleri sunuyoruz.

Bu kuralların önemi nedir?

NASA’nın kuralları, uzay aracı kontrolü ve uçuş yazılımı gibi görev açısından kritik uygulamalar için hayati önem taşıyan okunabilirlik, analiz edilebilirlik ve güvenilirlik unsurlarına odaklanmaktadır. Bu kılavuzlar, anlaşılması zor C yapılarını yasaklayarak ve koruyucu kontroller uygulayarak kodun incelenmesini ve doğruluğunun kanıtlanmasını kolaylaştırır. Statik analiz araçlarının sıklıkla gözden kaçırdığı kalıpları ele alarak MISRA C gibi standartları tamamlayıcı bir rol oynarlar. Örneğin, özyineleme ve dinamik bellek kullanımından kaçınmak kaynak kullanımını öngörülebilir tutarken, dönüş değeri denetimlerini zorunlu kılmak derleme aşamasında birçok hatanın yakalanmasına yardımcı olur.

Aslında, NASA’nın Toyota’nın elektronik gaz kelebeği yazılımı gibi kitlesel pazara yönelik bir gömülü sistem üzerinde yaptığı araştırmada yüzlerce kural ihlali tespit edildi. Bu durum, gerçek hayattaki projelerin sıklıkla bu kuralların önlemeye yönelik tasarlandığı aynı sorunlarla karşılaştığını göstermektedir. Listedeki her kural, yaygın bir hata türünü (kontrolsüz döngüler, null işaretçiye erişim, görünmez yan etkiler vb.) önler.  Bu kuralları göz ardı etmek, zor fark edilen çalışma zamanı hatalarına, güvenlik açıklarına veya belirsiz davranışlara yol açabilir. Buna karşılık, on kuralın tümüne uymak, statik doğrulamayı çok daha yönetilebilir hale getirir.

Otomatikleştirilmiş araçlar önemlidir. Kod kalitesi platformları, yasaklanmış yapıları veya kalıpları tespit edecek şekilde yapılandırılabilir. Bu kurallar her pull isteğinde otomatik olarak çalışır ve kod birleştirilmeden önce sorunları tespit eder.

Bağlam ile kurallar arasında bağlantı kurma

Tek tek kurallara geçmeden önce, bağlamı anlamak önemlidir:

  • Hedef Dil: NASA’nın “Power of 10” kuralları, kapsamlı araç desteğine (derleyiciler, analizörler, hata ayıklayıcılar) sahip olmakla birlikte tanımlanmamış davranışlarıyla da bilinen C dili için yazılmıştır. Bu kurallar, çöp toplama veya gelişmiş bellek yönetimi olmadığını varsayar. Yalnızca basit ve iyi yapılandırılmış C dili kullanılarak, statik analizden yararlanarak program özelliklerini kanıtlamak mümkündür.
  • Statik Analiz: Otomatik kontrolleri kolaylaştırmak için pek çok kural mevcuttur. Örneğin, özyinelemeyi (rekürsiyonu) yasaklamak (Kural 1) ve döngü sınırları zorunlu kılmak (Kural 2), araçların herhangi bir fonksiyonun kaç yineleme yapabileceğini veya ne kadar yığın (stack) alanı kullanabileceğini kanıtlamasını sağlar. Benzer şekilde, karmaşık makroları yasaklamak ve işaretçileri (pointer) sınırlandırmak (Kural 8–9), kod kalıplarının ön işlemci sihrinde veya çoklu dolaylı yönlendirmelerde gizlenmesini önleyerek açık olmalarını sağlar.
  • Geliştirme İş Akışı: Modern DevSecOps pipeline'larında bu kurallar CI kontrollerinin bir parçası haline gelir. Kod kalitesi araçları GitHub, GitLab veya Bitbucket ile entegre olarak her bir pull request'i gözden geçirebilir ve hem basit sorunları hem de daha karmaşık kalıpları tespit edebilir. "Goto veya özyinelemeli fonksiyon çağrılarının kullanımını işaretle" veya "her döngünün sabit bir sınırı olduğundan emin ol" gibi her bir NASA yönergesi için özel bir kural oluşturabilirsiniz. Yapılandırıldıktan sonra, bu kurallar gelecekteki her kod taramasında otomatik olarak uygulanır; ihlalleri erken aşamada yakalar ve bunların nasıl düzeltileceğine dair rehberlik sağlar.

Özetle, NASA'nın 10 kuralı savunmacı ve analiz edilebilir C programlamayı temsil eder. Aşağıda her bir kuralı listeliyor, iyi ve kötü kod örneklerini gösteriyor, kuralın neden var olduğunu ve hangi riskleri azalttığını açıklıyoruz.‍

NASA'nın 10 kodlama kuralı

1. Karmaşık kontrol akışından kaçının.

Goto, setjmp veya longjmp kullanmayın; kodun hiçbir yerinde özyinelemeli (rekürsif) fonksiyonlar yazmaktan kaçının.

‍❌ Kurala Uymayan (Non-Compliant) örnek

// Non-compliant: recursive function call
int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n-1);   // recursion (direct)
}

✅ Kurala Uyan (Compliant) örnek (döngü kullanır)

// Compliant: uses an explicit loop instead of recursion
int factorial(int n) {
    int result = 1;
    for (int i = n; i > 1; --i) {
        result *= i;
    }
    return result;
}

Neden önemli?: Özyinelemeler ve goto ifadeleri, anlaşılması zor, doğrusal olmayan bir kontrol akışı oluşturur. Rekürsif çağrılar çağrı grafiğini döngüsel hale getirir ve yığın derinliğini sınırsız bırakır; goto ise spagetti koda yol açar. Basit döngüler ve düz akışlı kodlar kullanarak statik bir analizör yığın kullanımını ve program yollarını kolaylıkla doğrulayabilir. Bu kuralın ihlal edilmesi, beklenmeyen yığın taşmalarına (stack overflow) veya manuel olarak incelenmesi zor mantıksal yollara yol açabilir.

2. Döngülerin sabit üst sınırları olmalıdır.

Her döngünün derleme zamanında doğrulanabilir bir sınırı olmalıdır.

❌ Kurala Uymayan örnek (sınırsız döngü):

// Non-compliant: loop with dynamic or unknown bound
int i = 0;
while (array[i] != 0) {
    doSomething(array[i]);
    i++;
}

✅ Kurala Uyan örnek (sabit sınırlı döngü):

// Compliant: loop with explicit fixed upper bound and assert
#define MAX_LEN 100
for (int i = 0; i < MAX_LEN; i++) {
    if (array[i] == 0) break;
    doSomething(array[i]);
}

Neden önemli?: Sınırsız döngüler sonsuza kadar çalışabilir veya kaynak sınırlarını aşabilir. Sabit bir sınır sayesinde araçlar, maksimum yineleme sayısını statik olarak kanıtlayabilir. Kritik güvenlik sistemlerinde eksik bir sınır, kontrolden çıkan bir döngüye neden olabilir. Açık bir sınır (veya statik dizi boyutu) dayatarak, döngülerin öngörülebilir şekilde sonlanmasını sağlarız. Bu kural olmadan, döngü mantığındaki bir hata ancak devreye alım sırasında fark edilebilir (örneğin sonsuz döngüye neden olan "off-by-one" hatası).

3. Başlatma (initialization) sonrasında dinamik bellek kullanılmamalıdır.

Çalışan kodda malloc/free kullanımından veya herhangi bir yığın (heap) kullanımından kaçının; yalnızca sabit veya yığın (stack) tabanlı tahsisat kullanın.

❌ Kurala Uymayan örnek (malloc kullanır)

// Non-compliant: dynamic allocation inside the code
void storeData(int size) {
    int *buffer = malloc(size * sizeof(int));
    if (buffer == NULL) return;
    // ... use buffer ...
    free(buffer);
}

✅ Kurala Uyan örnek (statik tahsisat)

// Compliant: fixed-size array on stack or global
#define MAX_SIZE 256
void storeData() {
    int buffer[MAX_SIZE];
    // ... use buffer without dynamic alloc ...
}

Neden önemli?: Çalışma zamanında (runtime) dinamik bellek tahsisi; özellikle uzay araçları veya gömülü denetleyiciler gibi sınırlı kaynaklara sahip sistemlerde öngörü olmayan davranışlara, bellek parçalanmasına veya tahsis hatalarına yol açabilir. Malloc veya free işlemleri görev sırasında başarısız olursa, yazılım çöker veya öngörülemeyen davranışlar sergileyebilir. Yalnızca sabit boyutta veya yığın üzerinde (stack-allocated) bellek kullanmak deterministik davranışı garanti eder, doğrulamayı basitleştirir ve çalışma zamanında bellek sızıntılarını önler.

4. Fonksiyonlar tek bir sayfaya sığmalıdır (~60 satır).

Her fonksiyonu kısa tutun (yaklaşık ⩿ 60 satır).

❌ Kurala Uymayan örnek

// Non-compliant: hundreds of lines in one function (not shown)
void processAllData() {
    // ... imagine 100+ lines of code doing many tasks ...
}

✅ Kurala Uyan örnek (modüler fonksiyonlar)

// Compliant: break the task into clear sub-functions
void processAllData() {
    preprocessData();
    analyzeData();
    postprocessData();
}
void preprocessData() { /* ... */ }
void analyzeData()   { /* ... */ }
void postprocessData(){ /* ... */ }

Neden önemli?: Aşırı uzun fonksiyonların birim olarak anlaşılması, test edilmesi ve doğrulanması zordur. Her fonksiyonu tek bir kavramsal görevle (ve basılı bir sayfanın sınırlarıyla) sınırlı tutarak kod incelemeleri ve statik kontroller yönetilebilir hale gelir. Bir fonksiyon çok fazla satıra yayılırsa, mantıksal hatalar veya sınır koşulları gözden kaçabilir. Kodu daha küçük fonksiyonlara bölmek anlaşılırlığı artırır ve diğer kuralların (fonksiyon başına iddia yoğunluğu ve dönüş kontrolleri gibi) uygulanmasını kolaylaştırır.

5. Fonksiyon başına en az iki assert (iddia) ifadesi kullanın.

Her fonksiyon savunmacı kontroller gerçekleştirmelidir.

❌ Kurala Uymayan Örnek (assertion kullanılmamış):

int get_element(int *array, size_t size, size_t index) {
return array[index];
}

✅ Kurala Uygun Örnek (assertion kullanılmış):

int get_element(int *array, size_t size, size_t index) {
    assert(array != NULL);        // Assertion 1: pointer validity
    assert(index < size);          // Assertion 2: bounds check
    
    if (array == NULL) return -1;  // Recovery: return error
    if (index >= size) return -1;  // Recovery: return error
    
    return array[index];
}

Neden önemli: Assertion'lar, geçersiz koşullara karşı ilk savunma hattıdır. NASA, yüksek assertion yoğunluğunun hata yakalama olasılığını önemli ölçüde artırdığını tespit etmiştir. Fonksiyon başına en az iki assert (ön koşulları, sınırları ve değişmezleri kontrol eden) ile kod, varsayımlarını kendi kendine belgeer ve test sırasında anormallikleri hemen işaretler. Assert'ler olmadan, beklenmeyen bir değer sessizce yayılabilir ve hatanın kaynağından uzak noktalarda arızalara neden olabilir.

6. Verileri minimum kapsamda tanımlayın.

Değişkenleri olabildiğince yerel tutun; global değişkenlerden kaçının.

❌ Kurala Uymayan Örnek (global veri):

// Non-compliant: global variable visible everywhere
int statusFlag;
void setStatus(int f) {
    statusFlag = f;
}

✅ Kurala Uygun Örnek (yerel kapsam):

// Compliant: local variable inside function
void setStatus(int f) {
    int statusFlag = f;
    // ... use statusFlag only here ...
}

Neden önemli: Kapsamı en aza indirmek, bağımlılıkları ve istenmeyen etkileşimleri azaltır. Bir değişken yalnızca bir fonksiyon içinde gerekiyorsa, onu global olarak tanımlamak diğer kodların onu beklenmedik şekilde değiştirmesi riskini doğurur. Verileri yerel tutarak her fonksiyon daha bağımsız ve yan etkilerden arınmış hale gelir; bu da analizi ve test etmeyi kolaylaştırır. Kural ihlalleri (global durumun yeniden kullanımı gibi), takma ad (aliasing) veya beklenmeyen değişiklikler nedeniyle bulunması zor hatalara yol açabilir.

7. Tüm fonksiyon dönüş değerlerini ve parametrelerini kontrol edin.

Çağıran taraf, void olmayan her dönüş değerini incelemelidir; her fonksiyon girdi parametrelerini doğrulamalıdır.

❌ Kurala Uymayan Örnek (dönüş değerini yoksayar)

int bad_mission_control(int velocity, int time) {
    int distance;
    calculate_trajectory(velocity, time, &distance);  // Didn't check!
    return distance;  // Could be garbage if calculation failed
}

✅ Kurala Uygun Örnek

int good_mission_control(int velocity, int time) {
    int distance;
    int status = calculate_trajectory(velocity, time, &distance);
    
    if (status != 0) {  // Checked the return value
        return -1;  // Propagate error to caller
    }
    
    return distance;  // Safe to use
}

Neden önemli: Dönüş değerlerinin veya geçersiz parametrelerin yoksayılması büyük bir hata kaynağıdır. Örneğin, malloc sonucunun kontrol edilmemesi null-pointer dereference (boş işaretçi başvurusu) durumuna yol açabilir. Benzer şekilde, girdilerin doğrulanmaması (örn. dizi indeksleri veya biçim dizileri) arabellek taşmalarına (buffer overflow) veya çökmelere neden olabilir. NASA, her dönüş değerinin ele alınmasını (veya niyetin belirtilmesi için açıkça void'e cast edilmesini) ve her argüمنى doğrulanmasını zorunlu kılar. Bu her şeyi kapsayan yaklaşım, hiçbir hatanın sessizce göz ardı edilmemesini sağlar.

8. Önişlemciyi (preprocessor) dahil etmeler ve basit makrolarla sınırlayın.

Karmaşık makrolardan veya koşullu derleme trick'lerinden kaçının.

❌ Kurala Uymayan Örnek (karmaşık makro):

#define DECLARE_FUNC(name) void func_##name(void)

DECLARE_FUNC(init);  // Şuna genişler: void func_init(void)

✅ Kurala Uygun Örnek (basit makrolar / inline):

// Compliant: use inline function or straightforward definitions
static inline int sqr(int x) { return x*x; }
#define MAX_BUFFER 256

Neden önemli: Karmaşık makrolar (özellikle çok satırlı veya fonksiyon benzeri makrolar) mantığı gizleyebilir, kontrol akışını karmaşıklaştırabilir ve statik analizi zorlaştırabilir. Önişlemciyi önemsiz görevlerle (örn. sabitler ve başlık dosyaları) sınırlandırmak, kodu açık ve anlaşılır tutar. Örneğin, makroların yerine inline fonksiyonlar kullanmak tip kontrolünü ve hata ayıklanabilirliği (debuggability) iyileştirir. Bu kural olmadan, ince makro genişletme hataları veya koşullu derleme hataları incelemelerden fark edilmeden kaçabilir.

9. İşaretçi (pointer) kullanımını sınırlayın.

Dolaylı adreslemeyi tek bir seviyeyle sınırlayın; int** ve fonksiyon işaretçilerinden kaçının.

❌ Kurala Uymayan Örnek (çoklu dolaylı adresleme):

// Kurala uygun değil: çift işaretçi ve fonksiyon işaretçisi
int **doublePtr;
int (*funcPtr)(int) = someFunction;

✅ Kurala Uygun Örnek (tek işaretçi):

// Uyumlu: tek seviyeli işaretçi, fonksiyon işaretçisi yok
int *singlePtr;
// Fonksiyon işaretçisi yerine açık çağrı kullanın
int result = someFunction(5);

Neden önemli: Çoklu işaretçi seviyeleri ve fonksiyon işaretçileri veri akışını karmaşıklaştırır ve hangi belleğe veya koda erişildiğini takip etmeyi zorlaştırır.  Statik analiz araçlarının her bir dolaylı yönlendirmeyi çözmesi gerekir; bu durum genel olarak karar verilemez olabilir. Yalnızca tekli işaretçi referanslarıyla sınırlandırılarak kod daha basit ve güvenli kalır.  Bunun ihlal edilmesi, güvenlik açısından kritik bağlamlarda riskli olan belirsiz takma adlara (alias) (bir işaretçinin diğer verileri değiştirmesine) veya beklenmeyen geri arama (callback) davranışlarına yol açabilir.

10. Tüm uyarılar etkinleştirilmiş şekilde derleyin ve bunları giderin.

Her derleyici uyarısını etkinleştirin ve sürüm yayınlanmadan önce bunları ele alın.

❌ Uyumlu olmayan örnek (uyarılar içeren kod)

// Non-compliant: code that generates warnings (uninitialized, suspicious assignment)
int x;
if (x = 5) {  // bug: should be '==' or initialize x
    // ...
}
printf("%d\n", x);  // warning: 'x' is used uninitialized

✅ Uyumlu örnek (temiz derleme)

// Compliant: initialize variables and use '==' in condition
int x = 0;
if (x == 5) {
    // ...
}
printf("%d\n", x);

Neden önemli: Derleyici uyarıları genellikle gerçek hatalara işaret eder (başlatılmamış değişkenler, tür uyumsuzlukları veya istenmeyen atamalar gibi).  NASA'nın kuralı, hiçbir uyarının göz ardı edilmemesini zorunlu kılar. Herhangi bir sürüm yayınlanmadan önce kod, maksimum ayrıntı düzeyinde uyarı vermeden derlenmelidir. Bu uygulama, birçok basit hatanın erken aşamada yakalanmasını sağlar. Bir uyarı çözülemiyorsa, uyarı hiçbir zaman ortaya çıkmayacak şekilde kod yeniden yapılandırılmalı veya belgelenmelidir.

Bu kuralların her biri, gizli hataların bir kategorisini ortadan kaldırır.  Birlikte izlendiklerinde, C kodunu çok daha öngörülebilir ve doğrulanabilir hale getirirler.

Sonuç

NASA'nın 10 kuralı (“Power of 10”), kritik C yazılımları için net ve etkili bir kodlama standardı sağlar. Karmaşık yapılardan kaçınarak ve kontrolleri zorunlu kılarak, gizli hata olasılığını azaltır ve statik analizi uygulanabilir hale getirirler. Modern geliştirmede bu yönergeler, kod kalitesi araçlarıyla otomatize edilebilir. NASA'nın yönergelerinin herhangi bir ihlalini işaretlemek için özel kurallar tanımlanabilir ve bu kurallar her pull request üzerinde çalıştırılarak geliştiricilere anında geri bildirim sağlanabilir.

Bu kontrollerin erken benimsenmesi, bakımını yapmak daha kolay olan daha güvenli ve daha yüksek kaliteli koda yol açar. Havacılık ve uzay sanayi dışında bile ilkeler geçerlidir: küçük, anlaşılır fonksiyonlar, açık döngüler, savunmacı programlama ve kafa karıştırıcı işaretçi manipülasyonlarından kaçınma.  Bu kuralları takip etmek ve bir kod kalitesi aracıyla otomatikleştirmek, ekibinizin hataları erkenden yakalamasına ve daha güvenilir yazılımlar göndermesine yardımcı olur.

Sık Sorulan Sorular

Sorularınız mı var?

NASA'nın kuralları yalnızca uzay veya gömülü sistem projeleri için mi?

Kesinlikle hayır. Bu kurallar güvenlik açısından kritik bir bağlamda ortaya çıkmıştır, ancak genel olarak geniş bir kullanım alanına sahiptir. Sürdürülebilirliğe ve güvenilirliğe önem veren her C projesi bundan faydalanabilir. Aslında bu kurallar, MISRA C gibi endüstri standartlarını tamamlar. NASA dışındaki birçok geliştirici, bu yönergelerin yalnızca bir alt kümesini bile uygulamanın kod kalitesini artırdığını fark etmiştir.

Bu kuralları otomatik olarak nasıl uygulayabilirim?

Statik analiz veya kod inceleme aracı kullanın. Aikido Security'nin Code Quality aracı, özel kurallar oluşturmanıza olanak tanır. Her yönerge için küçük bir kural yazabilir (örneğin goto ifadelerini veya 60 satırdan uzun fonksiyonları işaretleyen bir kural) ve bunu Aikido'ya kaydedebilirsiniz. Aikido daha sonra her yeni pull request'i özel kurallarınıza göre kontrol eder ve bir ihlal varsa merge (birleştirmeyi) engeller. Bu, GitHub/GitLab/Bitbucket ve benzeri araçlarla sorunsuz bir şekilde entegre olur.

Neden dinamik bellekten ve özyinelemeden (recursion) kaçınmalıyım?

Dinamik bellek ayırıcılar (malloc gibi) başarısız olabilir veya öngörülemez şekilde davranabilir ve yönetilmeyen özyineleme, yığın (stack) kullanımını sınırsız hale getirir. Kritik yazılımlarda, genellikle kaynak sınırlarını kanıtlamanız ve en kötü senaryoları yönetmeniz gerekir. Çalışma zamanında malloc ve özyinelemeyi yasaklayarak, tüm bellek ve çağrı derinliğinin önceden bilinmesini sağlarsınız.  Bu; insan hayatının veya milyonlarca dolarlık ekipmanın söz konusu olduğu durumlarda özellikle tehlikeli olan bellek sızıntıları, taşmalar (overflow) veya yığın taşmaları gibi klasik hataları önler.

Projemin bu kurallardan birini ihlal etmesi gerekirse ne olur?

NASA'nın yönergeleri tasarımları gereği katıdır. Eğer sapma göstermeniz şartsa (örneğin küçük bir dinamik arabellek kullanmak gibi), bunu bilinçli olarak yapmalısınız: istisnayı belgeleyin, gerekçelendirin ve muhtemelen çalışma zamanı kontrolleri ekleyin. Bazı ekipler birkaç kuralı hata yerine uyarı olarak ele almayı tercih eder, ancak en güvenli yaklaşım kodu uyumlu olacak şekilde yeniden düzenlemektir. NASA kuralları muhafazakardır, ancak tam olarak bu yüzden işe yararlar. Aikido veya başka bir araç kullanıyorsanız, bir kuralı düşük öncelikli olarak işaretleyebilirsiniz, ancak yine de temel sorunu çözmek en iyisidir.

Aikido, NASA kuralı ihlallerini diğer sorunlardan ayırabilir mi?

Evet. Aikido'nun kuralları özelleştirilebilir ve etiketlenebilirdir. Özel kurallarınızı "NASA Kuralı 1", "NASA Kuralı 2" vb. olarak etiketleyebilirsiniz, böylece ihlaller hangi yönergenin çiğnendiğini açıkça gösterir. Aikido ayrıca zaman içindeki analitik verileri de takip eder; böylece kod tabanınız genelinde "NASA kurallarına uyum oranı" gibi metrikleri görebilirsiniz. Bu izlenebilirlik, ekiplerin düzeltmelere öncelik vermesine ve denetimler sırasında uyumluluğu kanıtlamasına yardımcı olur.

Şimdi güvenliğinizi sağlayın

Kodunuzu, bulutunuzu ve çalışma zamanınızı tek bir merkezi sistemde güvenceye alın.
Güvenlik açıklarını otomatik olarak hızla bulun ve düzeltin.

Kredi kartı gerekmez | Tarama sonuçları 32 saniyede.