user_key_payload spray¶
"user" key tipiyle
add_keysyscall'unu kullanarak saldırgan kontrolündekistruct user_key_payloadnesnelerini kmalloc cache'leri boyunca spray'le, sonra corrupt edilmiş içerikleri leak'lemek için onlarıKEYCTL_READile geri oku.
Mechanism¶
Kernel keyring subsystem'i, unprivileged task'ların add_key(2) aracılığıyla küçük blob'lar saklamasına izin verir. "user" key tipi için, payload bir struct user_key_payload içinde tutulur ve kullanıcının verdiği veri uzunluğunu takip eden bir boyutta kmalloc ile allocate edilir:
struct user_key_payload {
struct rcu_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[] __aligned(__alignof__(u64)); /* actual data */
};
Note
Nesne klasik spray invariant'ını sağlar: size saldırgan kontrolündedir (kernel allocation'ı sizeof(struct user_key_payload) + datalen'dir) ve küçük header'ın ötesindeki içerik saldırgan kontrolündedir (data[]'deki her şey). datalen'i değiştirerek, aynı syscall nesneleri kmalloc-32'den kmalloc-4k'ya kadar indirir, dolayısıyla neredeyse her genel amaçlı slab cache'i hedefleyebilir. Önemlisi, nesne kalıcıdır: hemen free olan msg_msg ya da setxattr buffer'larının aksine, key unlink/revoke edilene kadar allocate edilmiş kalır.
Leak primitive'i KEYCTL_READ'ten gelir: data[]'in datalen byte'a kadarını userspace'e geri kopyalar. Bir use-after-free ya da overflow bitişik bir user_key_payload'u corrupt ederse — datalen'i büyük bir değerle overwrite ederek ya da data[]'e bir kernel pointer yazarak — key'i geri okumak o out-of-bounds ya da enjekte edilmiş veriyi açığa çıkarır. Bu, sınırlı bir write'ı bir memory disclosure'a çevirir.
Walkthrough¶
"user" key tipiyle kontrol edilen boyutta bir spray nesnesi allocate et. add_key bir key serial döner, ki bu aynı zamanda sonraki read'ler için handle'dır:
#include <sys/syscall.h>
#include <keyutils.h> /* or raw syscalls */
static inline int key_alloc(char *desc, void *payload, int len)
{
return syscall(__NR_add_key, "user", desc,
payload, len, KEY_SPEC_PROCESS_KEYRING);
}
Hedef cache'i groom etmek için çok sayıda key spray'le. len'i öyle seçmek ki
sizeof(struct user_key_payload) + len victim slab'ine düşsün tüm oyundur:
#define N 256
int keys[N];
char payload[0x200];
memset(payload, 'A', sizeof(payload));
for (int i = 0; i < N; i++) {
char desc[16];
sprintf(desc, "spray-%d", i);
keys[i] = key_alloc(desc, payload, 0x1c0); /* -> kmalloc-512 */
if (keys[i] < 0) { perror("add_key"); break; }
}
Bug bitişik bir payload'un datalen'ini corrupt ettikten (artık devasa) ya da data[]'e leak'lenen bir pointer yazdıktan sonra, onu geri oku:
char out[0x1000];
long n = syscall(__NR_keyctl, KEYCTL_READ, keys[i],
out, sizeof(out));
/* n bytes copied; if datalen was overwritten with a large value,
out[] now contains adjacent kernel heap (OOB read / pointer leak). */
printf("leaked %ld bytes, first qword = %#lx\n", n, *(unsigned long *)out);
Beklenen: datalen corrupt edildiğinde read orijinal 0x1c0 byte'tan fazlasını döner ve buffer bitişik slab nesnelerini içerir — örneğin CVE-2022-32250 nf_tables UAF'sinde, bir nft_set'in bindings list'inin adresi payload'a yazıldı ve KASLR'ı bozmak için userland'den geri okundu.
Talep üzerine free etme
keyctl(KEYCTL_UNLINK, key, keyring) (ya da KEYCTL_REVOKE) key'i düşürür, ki bu user_key_payload'u RCU ile free eder. Bu, diğer elastic objects'e benzer şekilde, cross-cache ya da free-then-reuse kurulumları için kontrollü zamanlama verir.
Detection¶
Unprivileged process'lerin olağandışı payload-length dağılımlarıyla add_key("user", ...) patlamaları yapması ve hemen ardından KEYCTL_READ gelmesi bir heap-grooming imzasıdır. /proc/keys ve kullanıcı-başına key kotası (/proc/sys/kernel/keys/maxbytes, maxkeys) anormal key popülasyonunu açığa çıkarır.
Mitigation¶
user.maxbytes/user.maxkeys kotaları UID başına ne kadar spray'lenebileceğini sınırlar ve tekniği kısıtlar. CONFIG_SLAB_FREELIST_RANDOM ve RANDOM_KMALLOC_CACHES adjacency güvenilirliğini azaltır. Genel olarak herhangi bir universal heap spray ve slab grooming'e uygulanan aynı hardening burada da geçerlidir.