Skip to main content


In contrast to full snapshots as seen in the web interface, MicroSnapshots are designed for a fuzzing use-case - running very short tests starting from a specific point of execution then rolling back to that point and running another test.

MicroSnapshots are exclusively available to on-site, Charm SDK, and desktop applicance customers with Premium licenses.


Suppose an IOKit driver is the target. The setup and main fuzzing loop might look something like this pseudocode:

// Setup
unsigned long long timeout = 0; // Timeout in usec, 0 for no timeout
io_connect_t conn;

// Main fuzzing loop
while (1) {
int res = hyp_fork(timeout);
if (!res) {
// Inside the fork, do the fuzz test
size_t testcase_len = 0;
char *testcase_buf = fuzzer_get_testcase(&testcase_len); // Get the next test case from some coordinator
IOConnectCallStructMethod(conn, selector, testcase_buf, testcase_len, NULL, NULL);
hyp_exit(1); // This return code goes to the "parent", needs to be >=1 to not collide with other status codes
} else if (res > 0) {
// Test case completed, res is the return code from the run
collect_and_report_coverage(); // Test case finished, gather the coverage and send up to the coordinator
} else if (res == -3) {
report_panic(); // Detected a panic! Let the coordinator know
} else {
// Handle other possible return values here (see possibilities below)


Note: This is subject to change when exposed as configuration in the web interface/via the API.

  1. Create a VM, let it fully boot, then power-off. Make note of the UUID from the URI of the device.
  2. SSH into the compute node the VM resides on.
  3. Edit /etc/corellium/charmd_boot_patch (create it if it doesn't exist) to append a line: name:<UUID> patch minisave-buf:1, replacing <UUID> with the UUID of the VM (without the brackets)
    • minisave-buf:1 allocates a 1 GB buffer for saving state. Supported values are 1-16, in gigabytes.
  4. Start the VM.

The setup is complete. Either use the in-VM API (programmatic) or manually save/restore via charmd (non-programmatic) listed below.


int hyp_fork(unsigned long long max_usec);

Begins recording changes, essentially starting the testcase. If max_usec is non-zero, the execution will be terminated when the timeout is reached. Set to zero for no timeout.

Return values:

  • MFS_ACTIVE (0): in minisave
  • status > 0: returned from minisave with hyp_exit(status);
  • MFS_FAIL (-1): failed to minisave
  • MFS_STOP_OVERRUN (-2): save overran reserved RAM block
  • MFS_STOP_PANIC (-3): save terminated by panic
  • MFS_STOP_EXTERNAL (-4): save terminated by charmd command
  • MFS_STOP_TIMER (-5): save terminated by running out of time (if max_usec is nonzero)

void hyp_exit(unsigned status);

Rolls back to the state from when hyp_fork was called. If function returns, rolling back the state failed.

The status code is returned to the caller of hyp_fork (conceptually similar to the parent process in regular UNIX-y fork).

int hyp_get_fork_state(void)

Return values:

  • 0: Not in minisave
  • 1: In minisave

int hyp_commit(void);

Commits current minisave state (i.e. abandons rollback).

Return values:

  • 0: success
  • MCS_FAIL (-1): commit failed (most likely disk save issue)
  • MCS_NOT_ACTIVE (-2): not in minisave

int hyp_persist(void *data, size_t size);

Marks memory as persistent (i.e. not affected by rollback); must be 4k-aligned and mlocked.

Return values:

  • 0: success
  • MPS_BAD_RANGE (-1): invalid memory range
  • MPS_NOT_RESIDENT (-2): memory not resident (mlock it or allocate from resident pool)
  • MPS_FAILED_MARK (-3): failed to mark persistent (hypervisor out of memory, probably)

For example:

posix_memalign((void **)&buf, 16384, 16384);
mlock(buf, 16384);
memset(buf, 0, 16384);
hyp_persist(buf, 16384);

int res = hyp_fork(0);
if (!res) {
strcpy(buf, "The buffer has been modified!");

printf("buf: %s\n", buf);
// Expected output: "buf: The buffer has been modified!"

void hyp_clear_persist(void)

Removes all memory marked with hyp_persist.

int hyp_get_panic_size(void)

Returns the size of the captured panic, or -1 on faillure.

int hyp_get_panic_content(void *buffer, uint64_t max_size)

Retrieve up to max_size bytes of kernel panic data into buffer, which must be mlocked.

Returns -1 on failure, or size of completed kernel panic data (regardless of whether the data was truncated to max_size).

The panic data is in a binary format consisting of an array of these structures:

struct kernel_panic {
uint64_t timestamp;
uint64_t panic_size;
char panic_data[]; // Where the size matches panic_size

Note that there may be multiple panics (because sometimes iOS panics multiple times before coming to a complete stop).

Non-Programmatic Usage

  1. For manual, non-programmatic operation, connect to charmd via SSH on the compute node:

    socat UNIX-CONNECT:/var/run/charmd -
  2. Obtain the list of running VMs and identify the target VM by UUID, making note of the VMID:

    > list
    < 0 request:list vmid:0 name:6205c765-41f9-41c6-83f8-c53872cde90c vmtag: state:run
  3. To save the current VM state, issue:

    > minisave vmid:0 start:
    < 0 request:minisave request-vmid:0 request-name:6205c765-41f9-41c6-83f8-c53872cde90c
  4. To restore, issue:

    > minisave vmid:0 stop:
    < 0 request:minisave request-vmid:0 request-name:6205c765-41f9-41c6-83f8-c53872cde90c

Note: The < and > characters are added for clarity and are not part of the command/output. > indicates a command entered by the user, and < indicates a response from charmd.


It is expected behavior that some Corellium or OS functionality will break when using MicroSnapshots. For example:

  • Network connections will likely be terminated.
  • Anything using USB/USBFlux, including SSH, will stop functioning.
  • The Files, Apps, Network, CoreTrace, and Frida tabs will be unresponsive, but may work itself out after some time.

Since MicroSnapshots are intended for fuzzing purposes, using the VM interactively is not recommended. Rebooting the VM should resolve this issues until the next time MicroSnapshots are used.