netfilter ipset range-check bypass (CVE-2024-53141)¶
bitmap_ip_uadt()'te eksik bir range check, CIDR'den türetilen birip'ninmap->first_ip'in altına düşmesine izin verir, böyleceip_to_id()negatif bir bitmap index hesaplar veset_bit()kmalloc'lanmışmap->membersbitmap'ine out-of-bounds yazar —CAP_NET_ADMINile 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_ipbitmap_ip_uadtlistelenen 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 edenbitmap:iptype'lıIPSET_CMD_CREATE/IPSET_CMD_ADDnetlink; KASAN OOBset_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=0ya daipset/netlink path'ini seccomp'layın; kullanılmıyorsaip_set/ip_set_bitmap_ip'yi unload edin. - OOB
set_bit'i yakalamak için test fleet'lerindeCONFIG_KASAN/CONFIG_SLUB_DEBUGile build edin.