READ_(?)ONLY Pages!

Memory access of code to any part of RAM is subject to complying with protection bits of the page descriptor entry in the respected page table. Violating these protection bits leads to stopping the code from accessing the requested address by hardware. This means that if page X is set as read_only in its page table entry then user space code will not be able to write into that page. But users should be careful that the meaning of read_only is not necessarily the same in every view point the operating system might take.

First of all the behavior of hardware in such a violation is different for user/supervisor level code. If WP bit in CR0 register is 1 then supervisor code (kernel code) cannot write into read-only pages either. This is where copy_to_user function which is responsible to safely write data into user space, will fail to write into the requested address after taking this execution flow:


copy_to_user
     |
     |
     `--> _copy_to_user
                |
                |
                `--> access_ok
                |
                |
                `--> __copy_yo_user
                            |
                            |
                            `--> __copy_to_user_nocheck
                                            |
                                            |
                                            `--> __put_user_asm
                                                      |
                                                      |
                                                      `-->asm volatile("\n"						
		                                                     "1:	mov"itype" %"rtype"1,%2\n"		
		                                                     "2:\n"						
		                                                     ".section .fixup,\"ax\"\n"				
		                                                     "3:	mov %3,%0\n"				
		                                                     "	jmp 2b\n"					
		                                                     ".previous\n"					
		                                                     _ASM_EXTABLE(1b, 3b)				
		                                                     : "=r"(err)					
		                                                     : ltype(x), "m" (__m(addr)), "i" (errret), "0" (err))
		                                                                      |
		                                                                      |
		                                                                      |
		                                                                      |
		                                                                      `--> RAISE EXCEPTION

Since WP=1 by default we can safely say that write access to read_only pages is not allowed by hardware. However, one can ask what type of writing is disallowed here? Thanks to Linux file-oriented policy we can either look at memory as physical RAM or as simply a file. When we have a memory mapped file the concept of read_only memory changes its meaning. A read_only page is prohibited from being written when you are working with it as memory. The story is different when it’s seen as a file. A memory-mapped file is actually the real physical memory which is depicted as a file. And this means that access to memory using this approach takes a completely different path in kernel code. File systems have their own handlers, and we need to see what the corresponding handlers of memory mapped files behave in a situation that you want to write to a file address that represents a READ_ONLY area of RAM. Starting with mem_rw function we can see that kernel follows this execution path:(scroll horizontally)


mem_rw
  |
  |
  `--> access_remote_vm
             |
             |
             `--> get_user_pages_remote
             |            |
             |            |
             |            `--> __get_user_pages_locked
             |                            |
             |                            |
             |                            `--> __get_user_pages
             |                                         |
             |                                         |
             |                                         `--> follow_page_mask
             |                                         |           |
             |                                         |           |
             |                                         |           `--> follow_p4d_mask (kernel 4.20)
             |                                         |                      |
             |                                         |                      |
             |                                         |                      `--> follow_page_pte
             |                                         |                                |
             |                                         |                                |
             |                                         |                                `   vm_normal_page 
             |                                         |                                |
             |                                         |                                |
             |                                         |                                `--> can_follow_write_pte
             |                                         |                                                       |
             |                                         |                                                       |
             |                                         |                                                       `--> pte_write
             |                                         |                                                                |
             |                                         |                                                                |
             |                                         |<................................................ NULL<---------
             |                                         |
             |                                         |
             |                                         `---> faultin_page 
             |                                                   |
             |                                                   |
             |                                                   `--> faultin_page 
             |                                                             |
             |                                                             |
             |                                                             `--> handle_mm_fault
             |                                                                        |
             |                                                                        |
             |                                                                        `--> __handle_mm_fault
             |                                                                                     |
             |                                                                                     |
             |                                                                                     `--> handle_pte_fault
             |                                                                                     |          |
             |                                                                                     |          |
             |                                                                                     |          `--> do_wp_page
             |                                                                                     |                 |
             |                                                                                     |                 |
             |                                                                                     |                 `--> wp_page_copy
             |                                                                                     |                           |
             |                                                                                     |                           |  
             |                                                                                     |                           `--> alloc_zeroed_user_highpage_movable (create new page)
             |                                                                                     |                           |
             |                                                                                     |                           |
             |                                                                                     |                           `--> cow_user_page
             |                                                                                     |                                    |
             |                                                                                     |                                    |
             |                                                                                     |                                    `--> copy_user_page
             |                                                                                     |                                    |
             |                                                                                     |                                    |
             |                                                                                     |<-----------------------------------     
             |                                                                                     |                                
             |                                                                                     `--> pte_mkdirty new page
             |
             |
             `--> kmap(page)   
             |
             |
             `--> copy_to_user_page
             |
             |
             `--> kunmap   

When kernel finds out that the requested address is not writable it copies the page to another physical page, changes the task page table entries to reflect the change in the task’s address space so that the virtual address points to the correct newly copied page. The kernel then maps the physical page in its supervisor mode page table entries and then writes to the address. But the address in the task’s address space remains read_only even after copying the page. I made this tiny program to demonstrate writing to read_only parts of a program’s address space. When the program is run with the argument=1 it calls a function that prints a message and then exits. Functions are executable so they are mapped in a read_only area. But in our test if the program is run with argument=2 it overwrites the function of interest with arbitrary shell code. The shell code is a printf as well but with a different message.



#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>

#define MAX_POSITIVE_INT (int)((unsigned int)(1<<31)-1)

//This shellcode prints a message in stdout
unsigned char sc[] = {
  0x48, 0x8d, 0x35, 0x27, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc2, 0x2c, 0x00,
  0x00, 0x00, 0x48, 0xc7, 0xc7, 0x01, 0x00, 0x00, 0x00, 0x48, 0xc7, 0xc0,
  0x01, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00,
  0x00, 0x48, 0xc7, 0xc7, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x1b, 0x5b,
  0x33, 0x31, 0x6d, 0x42, 0x61, 0x72, 0x61, 0x66, 0x72, 0x61, 0x73, 0x68,
  0x74, 0x65, 0x20, 0x42, 0x61, 0x61, 0x64, 0x20, 0x44, 0x65, 0x72, 0x61,
  0x66, 0x73, 0x68, 0x65, 0x20, 0x4b, 0x61, 0x76, 0x69, 0x61, 0x6e, 0x69,
  0x21, 0x0a, 0x1b, 0x5b, 0x30, 0x6d, 0x00
};
unsigned int sc_len = 91;

void t_func(){printf("In original function. Exiting Normally\n");}

#define MASK (unsigned long)(~((1<<12)-1))

int main(int argc, char **argv){
    int of=0;
    unsigned long po = 0;
    unsigned long tf;
    void *t_fu;
    int fd;
    void *pointer=&t_func;
    int pid;
    char path[100];
    FILE *fi;
    if (argc < 2)
        return -1;
    fi = popen("cat /proc/self/maps | head -n 1 | awk '{print $2}' | cut -c2","r");
        printf(fgetc(fi)=='-' ? "[>] Region READ_ONLY\n" : "");
        fclose(fi);
    int o = atoi(argv[1]);
    if (o==1){
        fi = popen("cat /proc/self/maps | head -n 1 | awk '{print $2}' | cut -c2","r");
        printf(fgetc(fi)=='-' ? "[>] Region Still READ_ONLY\n" : "");
        fclose(fi);
        t_func();
        exit(0);
    }else if(o==2){ 
        pid=getpid();       
        sprintf(path,"/proc/%d/mem",pid);
        fd = open(path,O_RDWR);            
        tf = (unsigned long)&t_func;
        lseek(fd,0,SEEK_SET);
        while (po MAX_POSITIVE_INT)
                of=MAX_POSITIVE_INT;
            else
                of = tf-po;
            lseek(fd,of,SEEK_CUR);
            po+=of;
        }
        write(fd,sc,sc_len);
        fi = popen("cat /proc/self/maps | head -n 1 | awk '{print $2}' | cut -c2","r");
        printf(fgetc(fi)=='-' ? "[>] Region Still READ_ONLY\n" : "");
        fclose(fi);
        t_func();
        return 0;
    }
    return -1;
}

Of course another way to do the same in the sample program was to simply call mprotect syscall to elevate the protection level of the page to PROT_WRITE and then write to it, however I changed the contents of a READ_ONLY page without making the page writable in this example. So we could see that users need to take care that being read_only in memory doesn’t mean read_only in its corresponding file as well. This can make since considering that sometimes you need to be able to change the contents of a memory mapped file regardless of how a program has mapped it in its address space. So the operating systems allows you to do that without worrying about page level protection mechanisms.

Leave a Reply

Your email address will not be published. Required fields are marked *