Skip to content

Rust's libc MAP_FIXED is incorrect

I tested this code

use std::ffi::c_void;
use std::ptr;

fn main() {
    // Get the system's page size. Memory protection works on a page-by-page basis.
    let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;

    println!("System page size: {} bytes", page_size);

    // --- NEW: Define a fixed address for MAP_FIXED demonstration ---
    // WARNING: MAP_FIXED is dangerous. It can overwrite existing memory mappings
    // if the chosen address is already in use, leading to undefined behavior or crashes.
    // For this example, we pick an arbitrary high address that is likely unused.
    // On a 64-bit system, this will likely work. On 32-bit, it will fail.
    let fixed_address = 0x100000000 as *mut c_void;
    println!("\nAttempting to map memory at fixed address: {:?}", fixed_address);


    // Allocate one page of memory using mmap with MAP_FIXED.
    // This tells the OS that we require the mapping at the specific address.
    let memory = unsafe {
        libc::mmap(
            fixed_address,        // Use the exact address we specified
            page_size,            // Allocate one page
            libc::PROT_READ | libc::PROT_WRITE, // Initial protection: Read + Write
            // Add the MAP_FIXED flag
            libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
            -1,                   // No file descriptor
            0,                    // No offset
        )
    };

    if memory == libc::MAP_FAILED {
        panic!("mmap failed");
    }

    // With MAP_FIXED, the returned address should be the one we requested.
    if memory != fixed_address {
        // This case is unlikely with MAP_FIXED as it should either succeed at the address or fail.
        // However, it's good practice to be thorough.
        panic!(
            "mmap with MAP_FIXED returned a different address! Expected {:?}, got {:?}",
            fixed_address, memory
        );
    }


    println!("Successfully allocated one page at address: {:?}", memory);

    // Cast the void pointer to a mutable u8 pointer to work with it.
    let mem_ptr = memory as *mut u8;

    // --- 1. Demonstrate WRITE access ---
    // The memory is currently writable, so let's write a value.
    unsafe {
        println!("\n--- Step 1: Writing to memory (Read/Write protection) ---");
        *mem_ptr.add(0) = 42;
        println!("Wrote value: {}", *mem_ptr.add(0));
    }

    // --- 2. Change protection to READ-ONLY ---
    // Now, we use mprotect to remove the write permission.
    let result = unsafe {
        println!("\n--- Step 2: Changing memory protection to Read-Only ---");
        libc::mprotect(memory, page_size, libc::PROT_READ)
    };

    if result != 0 {
        panic!("mprotect failed to set read-only");
    }
    println!("Memory is now Read-Only.");

    // Reading should still be fine.
    unsafe {
        println!("Value read from memory: {}", *mem_ptr.add(0));
    }

    // --- 3. Demonstrate the effect of READ-ONLY protection ---
    // If you uncomment the following lines, the program will crash with a
    // segmentation fault, because we are illegally trying to write to a

    // read-only memory page. This is the expected behavior.
    /*
    unsafe {
        println!("\n--- Step 3: Attempting to write to Read-Only memory (EXPECTED TO CRASH) ---");
        *mem_ptr.add(0) = 99; // This line will cause a segmentation fault
        println!("This line will not be reached.");
    }
    */
    println!("\n--- Step 3: Skipped write-attempt to Read-Only memory. ---");
    println!("(Uncomment the code in main.rs to see the segmentation fault)");


    // --- 4. Change protection back to READ-WRITE ---
    let result = unsafe {
        println!("\n--- Step 4: Changing memory back to Read/Write ---");
        libc::mprotect(memory, page_size, libc::PROT_READ | libc::PROT_WRITE)
    };

    if result != 0 {
        panic!("mprotect failed to set read-write");
    }
    println!("Memory is now Read/Write again.");

    // Now we can write to it again successfully.
    unsafe {
        *mem_ptr.add(0) = 123;
        println!("Wrote new value: {}", *mem_ptr.add(0));
    }


    // --- 5. Clean up ---
    // Finally, we must unmap the memory to release it back to the OS.
    unsafe {
        println!("\n--- Step 5: Cleaning up ---");
        libc::munmap(memory, page_size);
        println!("Memory unmapped successfully.");
    }
}

Result from Redox

Attempting to map memory at fixed address: 0x100000000

thread 'main' panicked at src/main.rs:41:9:
mmap with MAP_FIXED returned a different address! Expected 0x100000000, got 0x16000
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
redoxerd: exit status: 101

## redoxer (failure) ##
System page size: 4096 bytes

Attempting to map memory at fixed address: 0x100000000

thread 'main' panicked at src/main.rs:41:9:
mmap with MAP_FIXED returned a different address! Expected 0x100000000, got 0x16000
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
redoxer cargo: exit status: 1

Result from Linux

Attempting to map memory at fixed address: 0x100000000
Successfully allocated one page at address: 0x100000000

--- Step 1: Writing to memory (Read/Write protection) ---
Wrote value: 42

--- Step 2: Changing memory protection to Read-Only ---
Memory is now Read-Only.
Value read from memory: 42

--- Step 3: Skipped write-attempt to Read-Only memory. ---
(Uncomment the code in main.rs to see the segmentation fault)

--- Step 4: Changing memory back to Read/Write ---
Memory is now Read/Write again.
Wrote new value: 123

--- Step 5: Cleaning up ---
Memory unmapped successfully.

If the code is changed

libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | 0x4

It working just like Linux part