pub struct VirtMemAllocator { /* private fields */ }
Expand description

Virtual memory allocator.

Role of the Virtual Memory Allocator in the Fuzzer

PhysMemAllocator and PageTableManager provides the necessary building blocks to create multiple independant virtual address spaces over a shared physical one.

The role of this allocator is to provide an abstraction over PageTableManager, to easily allocate and access virtual memory inside a guest VM, but also to initialize the different ARM system registers used for memory management (e.g. TTBR0_EL1/TTBR1_EL1, SCTRL_EL1, MAIR_EL1, etc.). It also provides fuzzing specific function, such as the ability to restore a virtual address space from a snapshot.

Each fuzzing Executor has at least one instance of this allocator to manage its virtual memory ranges.

Example

use applevisor as av;
use hyperpom::memory::{PhysMemAllocator, VirtMemAllocator};

// First we create an hypervisor virtual machine instance to allow memory management in the
// guest (there's only one per process).
let vm = applevisor::VirtualMachine::new().unwrap();

// We create a new physical memory allocator over an address range of size 0x1000_0000.
let mut pma = PhysMemAllocator::new(0x1000_0000).unwrap();

// We create a new virtual memory allocator using `pma` as the physical page provider.
let mut vma = VirtMemAllocator::new(pma).unwrap();

// We can now map a virtual memory range starting at address `0x1234_0000`.
vma.map(0x1234_0000, 0x1000, av::MemPerms::RWX).unwrap();

// We can clone the virtual address space.
let vma_snapshot = vma.clone();

// We can write to it.
vma.write_qword(0x0000_0000_1000_0000, 0xdead_beef_dead_beef).unwrap();

// We can read from it.
assert_eq!(vma.read_qword(0x0000_0000_1000_0000), Ok(0xdead_beef_dead_beef));

// We can restore the virtual address space from a snapshot.
vma.restore_from_snapshot(&vma_snapshot).unwrap();

Now it’s also possible to map code and make the cpu execute arbitrary programs.

use applevisor as av;
use keystone as ks;
use hyperpom::memory::{PhysMemAllocator, VirtMemAllocator};

// Creates a new hypervisor virtual machine for this process.
let vm = av::VirtualMachine::new().unwrap();

// Creates an address space of size 0x1000_0000.
let pma = PhysMemAllocator::new(0x1000_0000).unwrap();

// Creates a virtual memory allocator.
let mut vma = VirtMemAllocator::new(pma.clone()).unwrap();

// Creates a new Vcpu.
let mut vcpu = av::Vcpu::new().unwrap();

// Initializes Vcpu system registers.
vma.init(&mut vcpu, true).unwrap();

// Maps an executable page at address 0x10_0000.
vma.map(0x10_0000, 0x1000, av::MemPerms::RX).unwrap();

// We compile a small program using the keystone engine.
let ks = ks::Keystone::new(keystone::Arch::ARM64, keystone::Mode::LITTLE_ENDIAN)
    .expect("Could not initialize Keystone engine");
let asm = String::from(
    "mov x0, #0x0000
    movk x0, #0x20, lsl #16
    blr x0
    brk #0",
);
let entry_func = ks.asm(asm, 0).expect("could not assemble");

// We write the function at address 0x10_0000.
vma.write(0x10_0000, &entry_func.bytes).unwrap();

// We create a mapping and write a second function (that will be called by the first one) at
// address 0x20_0000.
vma.map(0x20_0000, 0x1000, av::MemPerms::RX).unwrap();
let asm = String::from(
    "mov x0, #0x42
    ret",
);
let func = ks.asm(asm, 0).expect("could not assemble");
vma.write(0x200000, &func.bytes).unwrap();

// Sets PC to the entry point address.
vcpu.set_reg(av::Reg::PC, 0x10_0000).unwrap();

// Runs the program
vcpu.run().unwrap();

// Checks that the value stored after the program execution is 0x42.
assert_eq!(vcpu.get_reg(av::Reg::X0), Ok(0x42));

// Checks that the Vcpu stopped its execution after an exception was raised when hitting
// the breakpoint.
let exit = vcpu.get_exit_info();
assert_eq!(exit.reason, av::ExitReason::EXCEPTION);
assert_eq!(exit.exception.syndrome, 0xf2000000);

Implementations

Creates a new virtual memory allocator over the physical memory allocator pma.

Modifies different system registers to:

  • set the page memory attributes;
  • set the granule size;
  • set the size of the upper and lower virtual address ranges;
  • set the page table address of the upper and lower virtual address ranges;
  • enable caches and the MMU;
  • disable SIMD and FP registers access trapping;
  • set the current exception level to EL0;
  • unmask interrupts;
  • initialize the Exceptions vector table;
  • enable debug features for the hypervisor.

The map_exceptions argument determines if we need to remap the exception vector table in the current address space. This argument should be set to false if the function is called after restoring from a snapshot.

Sets TTBR0_EL1 and TTBR1_EL1 to the addresses of the current virtual address space page tables.

Maps a non-privileged virtual address range of size size, starting at address addr and with permissions perms.

Maps a privileged virtual address range of size size, starting at address addr and with permissions perms.

This function exists mainly because PAN is enabled by default on Apple Silicon. Therefore, all code that runs at EL1 (cache maintenance, exception handling, etc.) should be mapped using this function, otherwise it will trigger an exception.

Unmaps a virtual address range of size size and starting at address addr.

Checks if the page fault was due to dirty state detection, and handles it accordingly, or if it’s a legitimate data abort exception that needs to be propagated.

Restores the current virtual address space from a snapshot.

Reads from virtual address addr into the slice buf. The number of bytes read is the size of buf.

Reads one byte at virtual address addr.

Reads one word at virtual address addr.

Reads one dword at virtual address addr.

Reads one qword at virtual address addr.

Reads a C-string at virtual address addr.

Writes to virtual address addr from the slice buf. The number of bytes written is the size of buf.

Writes one byte at virtual address addr.

Writes one word at virtual address addr.

Writes one dword at virtual address addr.

Writes one qword at virtual address addr.

Writes a C-string at virtual address addr.

Writes to virtual address addr from the slice buf. The number of bytes written is the size of buf. Sets the dirty bit.

Writes one byte at virtual address addr. Sets the dirty bit.

Writes one word at virtual address addr. Sets the dirty bit.

Writes one dword at virtual address addr. Sets the dirty bit.

Writes one qword at virtual address addr. Sets the dirty bit.

Writes a C-string at virtual address addr. Sets the dirty bit.

Dumps the current virtual memory content in hex into a file.

Trait Implementations

Returns a copy of the value. Read more
Performs copy-assignment from source. Read more

Auto Trait Implementations

Blanket Implementations

Gets the TypeId of self. Read more
Immutably borrows from an owned value. Read more
Mutably borrows from an owned value. Read more

Returns the argument unchanged.

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

The resulting type after obtaining ownership.
Creates owned data from borrowed data, usually by cloning. Read more
Uses borrowed data to replace owned data, usually by cloning. Read more
The type returned in the event of a conversion error.
Performs the conversion.
The type returned in the event of a conversion error.
Performs the conversion.