Skip to content

Integer underflow

Bir tipin minimum temsil edilebilir değerinin altına çıkacak şekilde çıkarma yapmak, sonucu negatife gitmek yerine büyük bir pozitif (ya da başka türlü yanlış) değere wrap eder.

Mechanism

Invariant: bir çıkarmanın sonucu tipin temsil edilebilir aralığında kalmalı

Bu CWE-191'dir (Integer Underflow / Wrap or Wraparound). Fixed-width integer'lar modüler aritmetiğe uyar: a - b tipin minimumunun altında bir değer ürettiğinde, sonuç gerçek (aralık-dışı) yanıtı üretmek yerine aralığın ters ucuna wrap eder. unsigned tipler için minimum 0'dır, dolayısıyla a < b olan herhangi bir a - b, a - b + 2^width'e wrap eder — dev bir pozitif sayı. signed tipler için INT_MIN'in altına decrement etmek INT_MAX'e wrap eder (ve C'de undefined behavior'dır). Tehlike, wrap'lenmiş değerin ardından bir length, index ya da loop bound olarak güvenilmesidir; böylece görünüşte minik ya da negatif bir miktar attacker-lehine büyük bir miktar olur. En sık bir integer-overflow-to-buffer-overflow ya da bir out-of-bounds erişimi besleyen, unsigned/length durumudur.

Walkthrough

MITRE'in CWE-191 signed örneği: INT_MIN'den başlayıp 1 çıkarmak INT_MAX'e wrap eder.

#include <stdio.h>
int main(void)
{
  int i;
  i = -2147483648;   /* INT_MIN for 32-bit signed int */
  i = i - 1;         /* wraps to 2147483647 (INT_MAX) */
  return 0;
}

Daha tehlikeli durum, size olarak kullanılan bir unsigned underflow'dur. CWE-191 Örnek 2:

int a = 5, b = 6;
size_t len = a - b;   /* a - b == -1, but size_t is unsigned */
char buf[len];        /* len wraps to ~SIZE_MAX -> enormous VLA */

5 - 6 -1'dir; size_t'ye dönüştürmek 0xFFFFFFFFFFFFFFFF verir, dolayısıyla variable-length array'den (ya da sonraki bir malloc(len) / memcpy(dst, src, len)'den) ~16 EiB istenir.

Klasik len - headersize parse bug'ı

Untrusted input üzerinde length çıkarmaları, gerçek-dünyadaki en yaygın tetikleyicidir:

size_t payload = packet_len - HEADER_SIZE;   /* if packet_len < HEADER_SIZE -> wraps */
memcpy(dst, packet + HEADER_SIZE, payload);  /* copies ~SIZE_MAX bytes */
Çıkarmadan önce her zaman packet_len >= HEADER_SIZE'ı kontrol et.

CERT C INT30-C unsigned çıkarma için precondition pattern'ini verir: önce operand'ları karşılaştır, asla çıkarıp sonra (zaten-wrap'lenmiş) farkı test etme.

/* Noncompliant: udiff may have already wrapped */
void func(unsigned int ui_a, unsigned int ui_b) {
   unsigned int udiff = ui_a - ui_b;  /* ... */
}

/* Compliant: order operands before subtracting */
void func(unsigned int ui_a, unsigned int ui_b) {
   unsigned int udiff;
   if (ui_a < ui_b){
      /* Handle error */
   } else {
      udiff = ui_a - ui_b;
   }
   /* ... */
}
Wrap'i gözlemlemek

#include <stdio.h>
int main(void) {
    unsigned int a = 5, b = 6;
    printf("%u\n", a - b);   /* prints 4294967295 on a 32-bit unsigned int */
    size_t len = (size_t)(a - b);
    printf("%zu\n", len);    /* 18446744073709551615 on LP64 */
    return 0;
}
İlk print zaten unsigned int'in UINT_MAX'e wrap'ini gösterir; size_t'ye genişletmek -1'i SIZE_MAX'e sign-extend eder.

Detection

  • SAST / UBSan: -fsanitize=integer (Clang) signed overflow'u ve unsigned wrap'i runtime'da işaretler; static analyzer'lar length sink'lerine ulaşan çıkarmanın unsigned/size_t sonuçlarını işaretler.
  • Sonucu bir size, index ya da loop bound olarak kullanılan her len - k / a - b'yi denetle.

Mitigation

  • Önce operand sıralamasını doğrula: if (a < b) error; else d = a - b; (INT30-C).
  • Signed tipler için wrap'e güvenmekten kaçın; a - b'den önce a < INT_MIN + b'yi kontrol et (INT32-C).
  • Mevcut olduğunda checked builtin'leri (__builtin_sub_overflow, C23 ckd_sub) kullan.
  • Untrusted input'tan türetilen length'leri range-check edilene kadar şüpheli kabul et.

References