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 eden0xAA("infinite scream") kullanır (float/double için0xFF; 32-bit'te tekrar eden0xFF); GCC tekrar eden0xFEkullanı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_PATTERNgerektirir;KMSANile uyumsuz — Kconfig'dedepends 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_ZEROgerektirir;KMSANile aynı sebeple uyumsuz: auto-init, KMSAN'ın uninitialized-memory detection'ını maskeler). Compiler desteklediğinde default budur; aksi haldeINIT_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):
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
-Wuninitializedwarning'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/MEMCPYhandling'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¶
- linux/security/Kconfig.hardening (torvalds/linux)
- The Linux Kernel — Kernel Self-Protection
- Preventing kernel-stack leaks (LWN.net)
- Comparing GCC and Clang security features (LWN.net)
- security: allow using Clang's zero initialization for stack variables (LWN.net)
- Security things in Linux v5.9 (Kees Cook)
- add -ftrivial-auto-var-init to gcc (gcc-patches)
- Deprecate -enable-trivial-auto-var-init-zero-knowing-it-will-be-removed-from-clang (D125142)
- Zero initialized memory (Android Open Source Project)