Skip to content

Stack variable auto-init (INIT_STACK)

Aksi halde uninitialized kalacak automatic (stack) variable'ların compiler-güdümlü olarak sıfıra ya da bir poison pattern'e initialize edilmesi; uninitialized-stack-memory infoleak ve exploitation sınıfını ortadan kaldırır.

Mechanism

Note

Uninitialized automatic variable'lar ve struct padding'i, stack'te ya da register'larda en son ne varsa onu korur. O memory daha sonra userspace'e kopyalanırsa kernel data'sını leak eder (bir infoleak); control data olarak tüketilirse bir uninitialized-use exploitation primitive'i olur — "uninitialized stack memory eski/attacker-controlled data içerir."

-ftrivial-auto-var-init=<mode> compiler flag'i, aksi halde uninitialized kalacak her automatic variable'ı (padding dahil) function entry'de tanımlı bir değere zorlar ve tüm bug sınıfını ortadan kaldırır. Mode'lar:

  • =uninitialized — default / kapalı.
  • =zero — her şeyi (padding dahil) sıfıra initialize et. Güvenlik için en güvenlisi: sıfır, string'ler, pointer'lar, index'ler ve size'lar için zararsız bir default'tur.
  • =pattern — bir debug poison ile doldur. Clang, 64-bit integer'lar için tekrar eden 0xAA ("infinite scream") kullanır (float/double için 0xFF; 32-bit'te tekrar eden 0xFF); GCC tekrar eden 0xFE kullanır. Pattern, değerler latent logic error'ları crash ettirecek şekilde tasarlandığından bug'ları bulmak için daha iyidir, ama mevcut bug'ları tetikleme ihtimali daha yüksektir.

Compiler, redundant olduğunu kanıtlayabildiği initialization'ı optimize edip çıkarır (örn. int x = 123;). GCC'de bu, gimplification'da eklenen bir internal .DEFERRED_INIT çağrısıyla implement edilir.

Linux kernel'i bunu security/Kconfig.hardening üzerinden, "Initialize kernel stack variables at function entry" menuconfig prompt'u altında açar:

  • CONFIG_INIT_STACK_NONE — auto-init yok (en zayıf).
  • CONFIG_INIT_STACK_ALL_PATTERN-ftrivial-auto-var-init=pattern (CC_HAS_AUTO_VAR_INIT_PATTERN gerektirir; KMSAN ile uyumsuz — Kconfig'de depends on !KMSAN, çünkü auto-init KMSAN'ın uninitialized-memory detection'ını maskeler; bu bir build failure değil, semantik bir çakışmadır).
  • CONFIG_INIT_STACK_ALL_ZERO-ftrivial-auto-var-init=zero (CC_HAS_AUTO_VAR_INIT_ZERO gerektirir; KMSAN ile aynı sebeple uyumsuz: auto-init, KMSAN'ın uninitialized-memory detection'ını maskeler). Compiler desteklediğinde default budur; aksi halde INIT_STACK_NONE.

Toolchain geçmişi: başlangıçta yalnızca Clang (~Clang 8). =zero varyantı, standardizasyonu tartışılırken uzun -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang flag'inin ardına kapatılmıştı; kernel -ftrivial-auto-var-init=zero -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang çağırıyordu. GCC 12 destek ekledi (uninitialized/pattern/zero artı opt-out attribute'u). Clang 16 uzun-flag gereksinimini kaldırdı; Clang 18 flag'i kaldırdı.

Bu, syscall return'ünde kullanılmış stack bölgesini silen stackleak-stack-erase-on-syscall-return'den farklıdır; auto-init bunun yerine local'leri function entry'de tanımlar. İkisi tamamlayıcıdır.

Walkthrough

Kernel configuration'ı (zero, önerilen ayar):

menuconfig -> Kernel hardening options
          -> Initialize kernel stack variables at function entry
          -> (X) zero-init everything (strongest and safest)

Ortaya çıkan .config:

# CONFIG_INIT_STACK_NONE is not set
# CONFIG_INIT_STACK_ALL_PATTERN is not set
CONFIG_INIT_STACK_ALL_ZERO=y

Küçük bir örnek üzerinde compiler davranışı — write edilmeden bırakılıp sonra read edilen bir local:

/* leak.c */
#include <stdio.h>
void dump(void) {
    char buf[16];                 /* never initialized */
    fwrite(buf, 1, sizeof buf, stdout);
}
$ clang -ftrivial-auto-var-init=uninitialized -o leak_off leak.c   # stale stack bytes
$ clang -ftrivial-auto-var-init=zero          -o leak_zero leak.c  # buf is auto-zeroed
$ gcc-12 -ftrivial-auto-var-init=pattern      -o leak_pat  leak.c  # buf filled 0xFE...

=zero ile, buf önceki stack içeriğinden bağımsız olarak 16 NUL byte olarak geri okunur — infoleak gitmiştir. =pattern ile, GCC 0xFE (Clang 0xAA) doldurur ve stale data'ya yanlışlıkla bel bağlamayı bariz, debug edilebilir bir değere çevirir.

Bir hot variable'ı dışarıda bırakmak (performans-kritik path'ler):

char scratch[4096] __attribute__((uninitialized));   /* excluded from auto-init */

Performans: pattern init genelde ~%3–5 runtime overhead ile anılır; zero eşit ya da daha ucuzdur ve daha kompakt code üretir. Clang'in -ftrivial-auto-var-init-stop-after=<N> ve -ftrivial-auto-var-init-max-size flag'leri coverage'ı bisect etmek/sınırlamak için vardır.

Detection

Bir saldırgan tekniği değil — bu bir savunmadır. Coverage'ı audit etmek için, build'in gerçekten flag'i geçirdiğini doğrula (örn. CC_HAS_AUTO_VAR_INIT_ZERO'yu ve unused bir local'i olan bir function'ın disassembly'sinde emit edilen .DEFERRED_INIT / zeroing'i incele).

Mitigation

Bilinen boşluklar ve uyarılar (yani bu savunmanın zayıf olduğu ya da sürtünme yarattığı yerler):

  • Bu, C'ye yapılan semantik bir değişikliktir ve -Wuninitialized warning'lerini maskeleyebilir; GCC/Clang bu diagnostic'leri emit etmeye devam edecek şekilde ayarlandı, ama static analyzer'lar artık auto-zeroing'e kasıtlı bel bağlamayı gerçek uninitialized-use bug'larından ayırt edemez.
  • Dynamically-sized object'ler (VLA'lar) özel MEMSET/MEMCPY handling'i gerektirir ve her zaman aynı şekilde kapsanmaz.
  • Heap initialization'ı ele almaz; slab/page allocation'ları için init-on-alloc ve init-on-free ile eşle.
  • GCC-plugin-tabanlı öncüller (structleak-structure-auto-init) KASAN gibi tool'ları şaşırtabilirdi.

References