Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 25

Sayısal Taşma ve Kayan Nokta Hataları

Recommended resources

Merhaba, bir önceki dersimizde hata yönetimi ve yeniden deneme mekanizmalarının, özellikle kısmi başarısızlıklar ve non-idempotent operasyonlar yoluyla nasıl istismar edilebileceğini detaylıca inceledik. Bir iş akışının mantıksal adımlarının güvenliğini sağlama üzerine odaklandık. O dersin sonunda belirttiğimiz gibi, şimdi bu işlemlerin içindeki "sayıların" kendilerine, yani verinin en temel temsiline ineceğiz.

Bu dersimizin öğrenme hedefi, finansal ve miktar hesaplamalarında sayısal taşma, alt taşma ve kayan nokta hassasiyet hatalarını bulmaktır. İş mantığı ne kadar kusursuz olursa olsun, temel alınan matematiksel operasyonlardaki küçük bir kusur veya hassasiyet kaybı, özellikle finansal sistemlerde veya kaynak yönetiminde yıkıcı güvenlik açıklarına yol açabilir. Matematik geçmişiniz, bu derste ele alacağımız sayısal temsilin doğasından kaynaklanan bu ince ama kritik sorunları anlamada size sağlam bir temel sunacaktır.

Sayıların Esareti: Tamsayı Taşması (Integer Overflow) ve Alt Taşması (Underflow)

Bilgisayarların sayıları temsil etme biçimi, soyut matematiksel kavramlardan farklı olarak fiziksel limitlere tabidir. Bir tamsayı (integer), genellikle 32 veya 64 bit gibi sabit boyutlu bir bellek alanında saklanır. Bu durum, temsil edilebilecek sayıların bir alt ve üst sınıra sahip olduğu anlamına gelir. Örneğin, 16-bitlik işaretli bir tamsayı -32,768 ile 32,767 arasındaki değerleri tutabilir. Peki bu sınır aşıldığında ne olur?

Tarihin en maliyetli yazılım hatalarından biri, tam da bu sorudan kaynaklanmıştır.

Yazılım Dünyasının Efsanevi Bug'ları: Kapsamlı Bir Rehber

Başlangıç olarak, sayısal taşmanın ne kadar yıkıcı olabileceğini gösteren ikonik bir örneği inceleyelim. Fatih Soysal'ın "Yazılım Dünyasının Efsanevi Bug'ları" adlı makalesinde, Ariane 5 roketinin neden infilak ettiğini anlatan bölümü okuyun.

Makalede ilgili bölümü bulabilirsiniz. Bu olayın, basit bir veri tipi dönüştürme hatasından nasıl kaynaklandığına ve yeniden kullanılan kodun test edilmemesinin sonuçlarına odaklanın.

Ariane 5 vakası, bir tamsayı taşmasının (integer overflow) en dramatik sonucunu gözler önüne serer. Bir değişken, tutabileceği maksimum değeri aştığında, genellikle en küçük değere "dolanır" (wrap around). İşaretli tamsayılarda bu durum daha da tehlikelidir: en büyük pozitif sayıya 1 eklemek, genellikle en küçük negatif sayıya dönüşür. Örneğin, 32-bit işaretli bir tamsayıda 2,147,483,647 + 1 işleminin sonucu -2,147,483,648 olur.

Tamsayı Alt Taşması (Integer Underflow) ise tam tersi bir durumdur. En küçük değerden bir sayı çıkarıldığında, değişkenin değeri en büyük değere dolanır.

Kod Analizinde Overflow/Underflow Zafiyetlerini Bulmak

Bu hatalar, genellikle aşağıdaki gibi güvenlik zafiyetlerine yol açar:

  1. Mantık Hataları ve Kontrol Atlatma: Bir e-ticaret sitesinde, kullanıcının satın almak istediği ürün adedi ile kullanıcının bakiyesini çarparak toplam tutarı hesaplayan bir mantık düşünelim.

    // Java benzeri bir dilde
    int quantity = getUserInput(); // Kullanıcı 200.000 giriyor
    int price = 20000; // Ürün fiyatı
    int userBalance = 50000;
    
    int totalCost = quantity * price; // 200000 * 20000 = 4,000,000,000
                                     // Bu değer, 32-bit int sınırını (2.147 milyar) aşar
                                     // ve overflow sonucu negatif bir sayıya (-294,967,296) dönüşür.
    
    if (totalCost <= userBalance) { // -294,967,296 <= 50000  (True)
        processPurchase(); // Kullanıcı, bakiyesi yetmemesine rağmen ürünü satın alır!
    }
    

    Burada, totalCost hesaplamasındaki taşma, if kontrolünün yanlış çalışmasına ve yetkisiz bir satın alma işlemine izin vermesine neden olur.

  2. Bellek Hataları (Buffer Overflow): Bellek ayırma işlemlerinde yapılan hesaplamalardaki taşmalar, klasik buffer overflow zafiyetlerine zemin hazırlayabilir. malloc(genislik * yukseklik * PIXEL_BOYUTU) gibi bir ifadede, çarpımın sonucu taşarak beklenenden çok daha küçük bir sayıya dönüşebilir. Bu durumda, ayrılan bellek alanı yetersiz kalır ve daha sonra bu alana veri yazılmaya çalışıldığında bitişik bellek bölgelerinin üzerine yazılır.

JavaScript gibi modern dillerde bile bu sorun mevcuttur. JavaScript, tüm sayıları başlangıçta 64-bit kayan nokta sayıları olarak ele alır, ancak tamsayılar için güvenli bir aralık tanımlar: Number.MAX_SAFE_INTEGER (değeri ). Bu sınır aşıldığında beklenmedik davranışlar ortaya çıkar.

Bu görsel, standart JavaScript sayılarının `Number.MAX_SAFE_INTEGER` değerini aştığında nasıl davrandığını göstermektedir. `a` değişkenine `9007199254740992` atandıktan sonra, `a === a + 1` ifadesinin `true` döndürmesi, hassasiyet kaybı ve taşma sorununu net bir şekilde ortaya koyar. Çözüm olarak `BigInt` veri tipi sunulmaktadır.

Görselde görüldüğü gibi, güvenli tamsayı sınırını aşan bir sayıya 1 eklemek, sayıyı değiştirmez. Bu, özellikle büyük ID'ler, kriptografik hesaplamalar veya yüksek hacimli finansal verilerle çalışırken kritik hatalara yol açabilir.

Güvenli Kod Review Stratejisi:

  • Sayısal işlemlerin yapıldığı her yerde (aritmetik, döngü sayaçları, bellek ayırma) kullanılan değişkenlerin veri tiplerini ve sınırlarını kontrol edin.
  • Kullanıcıdan gelen veya harici sistemlerden alınan sayısal girdilerin, hesaplamalara dahil edilmeden önce makul sınırlar içinde olup olmadığını (range check) doğrulayın.
  • Java'da Math.addExact(), Math.multiplyExact() gibi taşma durumunda istisna fırlatan güvenli fonksiyonların kullanımını teşvik edin.
  • JavaScript'te, Number.MAX_SAFE_INTEGER değerini aşma potansiyeli olan sayılar için mutlaka BigInt kullanılmasını sağlayın.

Hassasiyetin İllüzyonu: Kayan Nokta (Floating-Point) Aritmetiği

Tamsayılar limitli olsa da en azından kendi aralıklarında "kesin"dirler. Kayan nokta sayıları (float, double) ise tamamen farklı bir dünyadır. Matematiksel olarak bildiğimiz gerçel sayılar kümesinin aksine, bilgisayardaki kayan nokta sayıları sonsuz hassasiyete sahip değildir. IEEE 754 standardına göre temsil edilirler ve bu temsil, çoğu ondalık kesrin (örneğin 0.1) ikilik tabanda tam olarak ifade edilememesine yol açar. Sonuç olarak, küçük ama birikerek büyüyen yuvarlama hataları (rounding errors) ortaya çıkar.

Bu durumun en net göstergelerinden biri, toplama işleminin birleşme özelliğini ((a + b) + c = a + (b + c)) kaybetmesidir.

Bu Go kodu örneği, kayan nokta aritmetiğindeki birleşme özelliğinin ihlalini göstermektedir. `a`, `b` ve `c` değişkenlerine atanan çok küçük ondalık farklar içeren sayılarla yapılan toplama işleminde, işlem sırasının (`(a+b)+c` vs `a+(b+c)`) sonucu değiştirdiği görülmektedir. Bu, kayan nokta sayılarının hassasiyet limitlerinin pratik bir sonucudur.

Bu görseldeki gibi bir davranış, bilimsel hesaplamalarda kabul edilebilir bir hata payı olabilir, ancak finansal bir uygulamada felakettir. Bir kullanıcının hesap bakiyesinin, işlemlerin hangi sırayla yapıldığına bağlı olarak değiştiğini hayal edin!

Kod Analizinde Hassasiyet Hatalarını Bulmak

Finansal hesaplamalarla ilgili en önemli kural şudur: Asla para birimlerini veya kesinlik gerektiren miktarları float veya double veri tipleriyle temsil etmeyin.

Intel'in 1994'te yaşadığı bir kriz, bu konunun donanım seviyesinde bile ne kadar kritik olduğunu göstermiştir.

Yazılım Dünyasının Efsanevi Bug'ları: Kapsamlı Bir Rehber

Şimdi, kayan nokta hatalarının donanım düzeyindeki etkisini inceleyelim. Aynı makalede bu kez Intel'in Pentium işlemcisindeki FDIV hatasını anlatan bölümü okuyun.

Makalede ilgili bölümü bulabilirsiniz. Bu hatanın neden özellikle bilimsel ve finansal hesaplamalar için bir tehdit oluşturduğuna ve Intel'in bu sorunu nasıl yönettiğine dikkat edin.

Pentium hatası, en temel aritmetik işlemlerin bile kusurlu olabileceğini ve bu kusurların en çok hassasiyet gerektiren alanları vurduğunu kanıtlamıştır.

Güvenli Kod Review Stratejisi:

  • Kod tabanında float veya double anahtar kelimelerini aratın. Bu veri tiplerinin para, kur, faiz oranı, hisse senedi değeri gibi finansal verileri depolamak veya işlemek için kullanıldığı her yer potansiyel bir zafiyettir.
  • Doğru Yaklaşım:
    • Decimal Veri Tipleri: Java'da BigDecimal, Python'da Decimal, C#'ta decimal gibi, ondalık aritmetiği hatasız yapmak için tasarlanmış özel veri tiplerini arayın ve kullanılmasını sağlayın. Bu tipler daha yavaş olsalar da kesinlik garantisi verirler.
    • Tamsayı Yaklaşımı: Parasal değerleri, en küçük para birimi cinsinden (örneğin kuruş, cent) tamsayı olarak saklayın. Örneğin, 10.75 TL yerine 1075 kuruş olarak saklamak, tüm hesaplamaların tamsayı aritmetiğiyle yapılmasını sağlar ve kayan nokta hatalarını tamamen ortadan kaldırır. Değerin kullanıcıya gösterileceği son anda ondalık noktası eklenir.
  • Kayan nokta sayılarının eşitlik için karşılaştırıldığı (==) yerleri tespit edin. Bu neredeyse her zaman yanlıştır. İki kayan nokta sayısının eşitliği, aralarındaki mutlak farkın çok küçük bir epsilon değerinden daha az olup olmadığı kontrol edilerek yapılmalıdır: if (Math.abs(a - b) < 0.00001). Finansal bağlamda bu bile risklidir ve kaçınılması gereken bir desendir.

Sonuç

Bu derste, yazılımın en temel yapı taşlarından olan sayıların, yanlış kullanıldığında nasıl ciddi güvenlik açıklarına yol açabildiğini gördük. Otomatik analiz araçları genellikle bu tür bağlamsal ve mantıksal hataları kaçırır; bu nedenle bir kod denetçisinin "uzman gözü" bu noktada paha biçilmezdir.

Temel Çıkarımlar:

  • Tamsayı Taşması/Alt Taşması: Sabit boyutlu tamsayıların limitlerini zorlayan her türlü aritmetik işlem şüpheyle yaklaşılması gereken bir noktadır. Özellikle kullanıcı girdisiyle beslenen hesaplamalar, mantık atlatma veya bellek bozulması zafiyetlerine yol açabilir.
  • Kayan Nokta Hassasiyeti: float ve double veri tipleri, doğaları gereği kesin değildir. Finansal veya miktar bazlı, kesinlik gerektiren hiçbir hesaplamada kullanılmamalıdırlar.
  • Doğru Veri Tipi Seçimi Bir Güvenlik Kararıdır: Büyük sayılar için BigInt gibi keyfi hassasiyetli tamsayılar, finansal veriler için ise BigDecimal gibi ondalık veri tipleri veya en küçük birimi temel alan tamsayı temsilleri kullanılmalıdır.

İş mantığı zafiyetleri modülünü tamamlarken, işlemlerin adımlarını (TOCTOU), hata durumlarını (retry/rollback) ve şimdi de işlemlerin içindeki sayısal değerleri analiz ettik. Bir sonraki modülümüzde, "İleri Seviye Enjeksiyon ve Veri Sınırı İhlalleri" konusuna geçiş yapacağız. İlk dersimizde, bir bağlamda güvenli görünen verinin depolandıktan sonra başka bir bağlamda nasıl tehlikeli hale gelebileceğini, yani ikinci derece enjeksiyon (second-order injection) zafiyetlerini nasıl tespit edeceğimizi öğreneceğiz.