Search code examples
cassemblygccldvirtual-memory

Is it possible to write at virtual 0x0 on a classical OS?


I'm not sure if I'm asking the question right, but I'm wondering if it's possible for a C or ASM program to write at the virtual adress 0x0 ?

I know the kernel don't allow write/read at virtual 0x0, but I'm wondering if there's some strange way to do this. Using maybe a flag for ld or gcc that I don't know about ?

The settings are as follow :

  • Computer running Linux on x86-64
  • Can use root if necessary

Solution

  • Three steps are needed to do this:

    1. Configure the system to permit mapping memory at address zero. This is done by setting the vm.mmap_min_addr sysctl to 0:

      sysctl vm.mmap_min_addr=0
      

      If you are using Wine on the system, there's a chance this sysctl is already configured as it is needed for Wine to be able to execute 16 bit code.

      An alterative to vm.mmap_min_addr=0 is to run your process with CAP_SYS_RAWIO, such as by running as root like sudo strace ./a.out. (strace lets you see the system calls it makes, so you can see mmap return a successful 0 rather than (void*)-1)

    2. Establish a mapping at address 0. To let the kernel have you write data to address 0, you need to map some memory there. This is easily done by calling mmap to map some memory there or by arranging for your binary to load a segment there.

      For an mmap example, see allocating address zero on Linux with mmap fails

    3. Dereference a pointer to address 0. This can be done with C code like *(int *)0 = 42.
      This happens to compile to the asm we want with GCC or clang with optimization disabled1.


    Footnote 1:
    0 is a null pointer constant in C, and dereferencing it is undefined behaviour. With optimization disabled, GCC and clang just compile to asm that stores to that address (Godbolt) , but clang warns indirection of non-volatile null pointer will be deleted, not trap, consider using __builtin_trap() or qualifying pointer with 'volatile'. That is indeed what happens with optimization enabled. GCC -O2 or higher still emits a store instruction, but on x86-64 follows it with a ud2 illegal instruction trap.

    volatile int *volatile zero_ptr = 0; *zero_ptr = 42; works in GCC and clang to hide the UB from the compiler: Godbolt. (Making the pointer object itself volatile hides the fact that it's a null pointer. Making it a pointer to volatile int works around a GCC bug(?) where it optimizes away the actual store, perhaps assuming that it can't be pointing to anything with a lifetime that outlasts this function.)