Skip to content

STRUCTLEAK structure auto-init

Compiler'ın aksi halde uninitialized bırakacağı on-stack structure'ları zero-initialize eden bir GCC-plugin hardening'i (grsecurity/PaX'ten port edilmiş); yaygın bir kernel stack infoleak kaynağını kapatır.

Mechanism

CONFIG_GCC_PLUGIN_STRUCTLEAK, uninitialized bırakılan local structure variable'larını bulup onlara bir zero-initialization ekleyen bir GCC plugin'idir. Seçilen coverage seviyesine bağlı olarak ya bir __user attribute'u içeren structure'ları, ya reference ile geçilen structure'ları, ya da bu tür tüm local'leri hedefler.

Neden işe yarar

Kernel, kaçırılmış stack-variable initialization'ı için warning'lerle build edilir, ama dokümantasyon, başka bir function'a reference ile geçilen herhangi bir şey için o warning'in susturulduğunu belirtir — "function'ın initialization'ı yapacağına dair ara sıra yanıltıcı olan varsayım altında." O varsayım yanlış olduğunda, bir struct stack'in tuttuğu her stale byte'ı korur (artık pointer'lar, secret'lar, padding delikleri). Böyle bir struct — ya da padding'i — daha sonra userspace'e kopyalanırsa, o byte'lar leak eder. STRUCTLEAK şu önkoşulu kırar: uninitialized struct memory attacker-useful artık taşır: tüm structure'ı (padding/delikler dahil) definition'da sıfırlayarak, code'un set etmeyi unuttuğu herhangi bir field ya da delik, stale kernel data yerine 0 olarak okunur. __user-only mode'u, kernel/user boundary'sini geçme olasılığı en yüksek structure'lara tam isabetle yönelen ucuz, hedefli bir varyanttır, dolayısıyla minimal maliyetle "bazı information exposure sınıflarını" önler.

Varyantlar (coverage seviyeleri):

GCC_PLUGIN_STRUCTLEAK_USER       # zero-init structs containing a __user pointer
GCC_PLUGIN_STRUCTLEAK_BYREF      # zero-init structs that may be passed by reference
GCC_PLUGIN_STRUCTLEAK_BYREF_ALL  # broadest: aim to eliminate all such uninit cases
GCC_PLUGIN_STRUCTLEAK_VERBOSE    # emit which variables were instrumented

Walkthrough

Plugin'i etkinleştir ve kernel config'inde bir coverage seviyesi seç:

CONFIG_GCC_PLUGIN_STRUCTLEAK=y
CONFIG_GCC_PLUGIN_STRUCTLEAK_USER=y        # or _BYREF / _BYREF_ALL
CONFIG_GCC_PLUGIN_STRUCTLEAK_VERBOSE=y     # optional build-time reporting
Kapattığı bug sınıfı

struct layout {
    __u32 a;
    /* 4 bytes of implicit padding here */
    __u64 b;
};

long do_ioctl(void __user *arg)
{
    struct layout out;     /* uninitialized: stale stack bytes */
    out.a = 1;
    out.b = 2;
    /* out.a and out.b are set, but the 4-byte padding hole is NOT.
       copy_to_user leaks 4 bytes of leftover kernel stack data. */
    return copy_to_user(arg, &out, sizeof(out));
}
STRUCTLEAK ile compiler, assignment'lardan önce out'un tam bir zeroing'ini (padding deliğini de kapsayarak) ekler, böylece kopyalanan padding residual kernel memory yerine 0 olur.

Uyarılar ve soy

  • STRUCTLEAK yalnızca GCC'dir (bir plugin). Stack'teki padding dahil her şeyi zero-initialize eden, compiler-agnostic whole-stack yaklaşımı CONFIG_INIT_STACK_ALL_ZERO'nun (Clang/GCC -ftrivial-auto-var-init=zero) öncülü/akrabasıdır — bkz. Mitigation.
  • STRUCTLEAK_USER yalnızca bir __user member'ı olan struct'ları kapsar, dolayısıyla başka path'ler üzerinden yine de userspace'e ulaşan non-__user struct'lar o mode tarafından kapsanmaz; daha geniş mode'lar daha pahalıdır.
  • Structure'ları initialize eder; scalar local'ler ve daha geniş uninitialized-read sınıfı, full stack auto-init tarafından daha iyi ele alınır.

Detection

Plugin'in derlemeye dahil edildiğini doğrula:

grep -E 'GCC_PLUGIN_STRUCTLEAK' /boot/config-$(uname -r)

..._VERBOSE=y ile, build log'u her instrument edilen variable'ı raporlar; bu da maintainer'ların plugin'in neye dokunduğunu görmesini sağlar.

Mitigation

Residual risk / daha tam coverage'ı nasıl elde edilir:

References

  • Linux kernel docs, "Kernel Self-Protection" (Memory initialization): https://docs.kernel.org/security/self-protection.html
  • Linux Kernel Driver DataBase, CONFIG_GCC_PLUGIN_STRUCTLEAK: https://cateee.net/lkddb/web-lkddb/GCC_PLUGIN_STRUCTLEAK.html