Skip to content

netfilter ipset range-check bypass (CVE-2024-53141)

bitmap_ip_uadt()'te eksik bir range check, CIDR'den türetilen bir ip'nin map->first_ip'in altına düşmesine izin verir, böylece ip_to_id() negatif bir bitmap index hesaplar ve set_bit() kmalloc'lanmış map->members bitmap'ine out-of-bounds yazar — CAP_NET_ADMIN ile reachable, kontrol edilebilir bir kernel heap OOB write.

Mechanism

Note

bitmap:ip type'ında bir ipset, oluşturma anında seçilen [first_ip, last_ip] range'i için boyutlandırılmış sabit boyutlu bir membership bitmap'i allocate eder:

map = ip_set_alloc(sizeof(*map) + elements * set->dsize);  /* kmalloc */
/* map->members is the bitmap; map->first_ip / map->last_ip / map->elements / map->hosts */

Her add/del, bir IP'den bir bit index hesaplar ve onu flip eder:

static u32 ip_to_id(const struct bitmap_ip *m, u32 ip) {
    return ((ip & ip_set_hostmask(m->netmask)) - m->first_ip) / m->hosts;
}
/* later: set_bit(e->id, map->members);  (or test_and_set_bit) */

Invariant, first_ip <= ip <= last_ip'dir ve id'yi [0, elements) içinde tutar. bitmap_ip_uadt()'in bunu her code path'te enforce etmesi gerekir. Bug: kullanıcı IPSET_ATTR_IP_TO olmadan IPSET_ATTR_CIDR sağladığında, CIDR ip_set_mask_from_to() tarafından genişletilir:

#define ip_set_mask_from_to(from, to, cidr) do { \
    from &= ip_set_hostmask(cidr);               \
    to   = from | ~ip_set_hostmask(cidr);        \
} while (0)

Masking, from'u (çalışan ip'yi) orijinal değerinin altına — ve map->first_ip'in altına — düşürebilir. Fix öncesi kod yalnızca if (ip_to > map->last_ip) valide ediyordu ve ip < map->first_ip'i sadece if (ip > ip_to) { swap(...); } branch'inin içinde kontrol ediyordu; bu branch CIDR path'inde alınmaz. Yani ip < first_ip sıyrılıp geçer. ip_to_id()'de unsigned subtraction (ip - m->first_ip) devasa bir değere underflow eder, büyük/negatif bir id'ye bölünür ve set_bit(id, map->members) bitmap allocation'ının out of bounds'una yazar — offset'i attacker'ın seçtiği IP ve CIDR ile etkilenen bir heap OOB write.

Etkilenen: Linux ≥ 2.6.39, 4.19.325 / 6.6.64 / 6.11.11 / 6.12.2'de düzeltildi (commit 35f56c554eb1b56b77b3cf197a6b00922d49033d). CVSS 7.8.

Walkthrough

Vulnerable path, CAP_NET_ADMIN tuttuğunuzda ip set (netlink IPSET_CMD_ADD) üzerinden reachable'dır — bir user+net namespace içinde unprivileged elde edilebilir.

1. Küçük bir bitmap:ip set oluşturun, böylece members allocation'ı küçük ve ötesine overflow yapması kolay olsun:

# unshare -Urn  ; then:
ipset create myset bitmap:ip range 10.0.0.0/24
# first_ip = 10.0.0.0, last_ip = 10.0.0.255  -> small members bitmap

2. IP_TO olmadan CIDR ile bir element ekleyin, masked base'i first_ip'in altına düşen bir CIDR/IP seçin:

# CIDR masking pulls the base below 10.0.0.0 (first_ip) -> ip < map->first_ip
ipset add myset 9.255.255.255/8
# bitmap_ip_uadt: IP_TO absent, CIDR present
#   ip_set_mask_from_to(ip, ip_to, 8)  => ip = 9.255.255.255 & /8 = 9.0.0.0  (< first_ip 10.0.0.0)
#                                        ip_to = 9.0.0.0 | 0.255.255.255 = 9.255.255.255 (<= last_ip)
#   (masked ip < first_ip while ip_to <= last_ip dodges the only check)

Amaçlanan saldırı, masked base IP'nin map->first_ip'ten kesinlikle küçük olduğu, ip_to'nun ise <= map->last_ip kaldığı bir set range ve bir add CIDR sağlar, böylece tek ip_to > map->last_ip guard'ı geçer ve underflow eden index set_bit'e ulaşır.

3. Ortaya çıkan primitive. ip_to_id() underflow'u büyük bir index verir; loop

for (; !before(ip_to, ip); ip += map->hosts) {
    e.id = ip_to_id(map, ip);
    ret = adtfn(set, &e, &ext, &ext, flags);  /* -> set_bit(e.id, map->members) */
}

bit'leri map->members'ın çok dışına flip eder ve komşu kmalloc object'lerini corrupt eder. Target cache'i bitmap'in etrafında spray'lemek (cache feng shui), OOB set_bit'i komşu bir object üzerinde kontrollü bir single-bit-set'e çevirir — örneğin bir victim structure'da bir flag/refcount/pointer LSB'sini flip eder.

Warning

set_bit, addr + (id/64)*8'de bir whole word yazar ve bir bit'i OR'lar — OOB offset'i members'dan (id/64) word ötede, underflow eden id ile ölçeklenmiştir, yani reach büyüktür ama granularity tek bir set bit'tir. Exploitation, arbitrary byte kontrolü değil, corrupt offset'e düşen word'ü groom etmeyi gerektirir.

Fix, bounds check'i tüm attribute handling'den sonra konsolide eder:

/* added in 35f56c5 */
if (ip < map->first_ip || ip_to > map->last_ip)
    return -IPSET_ERR_BITMAP_RANGE;
/* removed: the partial check that only ran inside the (ip > ip_to) swap branch */

Bu, IP_TO ya da CIDR path'inin alınıp alınmadığına bakmaksızın her iki ucun da valide edilmesini garanti eder ve herhangi bir set_bit'ten önce first_ip <= ip <= ip_to <= last_ip invariant'ını geri getirir.

OOB offset'e bir victim yerleştirmek için slab grooming / kmalloc cache feng shui'ye, ve bir bit-flip'i kullanılabilir bir corruption'a çevirmek için freelist corruption'a bakın.

Detection

  • Patch durumunu yükleme: ip_set_bitmap_ip bitmap_ip_uadt listelenen point release'lerde düzeltildi; kernel version'ı 4.19.325 / 6.6.64 / 6.11.11 / 6.12.2'ye karşı kontrol edin.
  • Runtime: unprivileged unshare(CLONE_NEWUSER|CLONE_NEWNET)'i takip eden bitmap:ip type'lı IPSET_CMD_CREATE/IPSET_CMD_ADD netlink; KASAN OOB set_bit'i hemen işaretler.

Mitigation

  • Upstream fix'i (commit 35f56c554eb1b56b77b3cf197a6b00922d49033d) / vendor backport'unu uygulayın.
  • Unprivileged rotayı kaldırın: sysctl kernel.unprivileged_userns_clone=0 ya da ipset/netlink path'ini seccomp'layın; kullanılmıyorsa ip_set / ip_set_bitmap_ip'yi unload edin.
  • OOB set_bit'i yakalamak için test fleet'lerinde CONFIG_KASAN/CONFIG_SLUB_DEBUG ile build edin.

References