# Kernel Memory Allocation (kalloc) Methods in Dopamine Explained: IOSurface-Based Primitives and API Design

> Discover Dopamine's kernel memory allocation methods. Learn how kalloc and kalloc_with_options leverage IOSurface primitives for efficient global and local buffer management.

- Repository: [Lars Fröder/Dopamine](https://github.com/opa334/Dopamine)
- Tags: internals
- Published: 2026-08-12

---

**Dopamine's kernel memory allocation relies on `kalloc()` and `kalloc_with_options()` functions that delegate to IOSurface-based primitives for global and local kernel buffer allocation.**

Dopamine is a rootless jailbreak for iOS 15–17 that provides a unified **kalloc** API for kernel-mode memory allocation. Understanding these methods is essential for security researchers analyzing the jailbreak's exploitation primitives and memory management architecture. This guide examines the layered implementation, from public API entry points to low-level IOSurface Mach-port manipulations.

## Core kalloc API: Entry Points and Options

Dopamine exposes two primary functions for kernel memory allocation in [`BaseBin/libjailbreak/src/primitives.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/primitives.c). The simpler `kalloc()` wraps `kalloc_with_options()` with global allocation semantics, while the latter provides explicit control over allocation scope.

### kalloc(): Default Global Allocation

```c
int kalloc(uint64_t *addr, uint64_t size) {
    return kalloc_with_options(addr, size, KALLOC_OPTION_GLOBAL);
}

```

This function is the default entry point used throughout Dopamine's codebase. It returns zero on success and populates `*addr` with the kernel virtual address of the allocated buffer.

### kalloc_with_options(): Explicit Scope Control

```c
int kalloc_with_options(uint64_t *addr, uint64_t size, kalloc_options options) {
    if (options == KALLOC_OPTION_GLOBAL && gPrimitives.kalloc_global)
        return gPrimitives.kalloc_global(addr, size);
    else if (options == KALLOC_OPTION_LOCAL && gPrimitives.kalloc_local)
        return gPrimitives.kalloc_local(addr, size);
    return -1;
}

```

The `kalloc_options` enum distinguishes two allocation strategies:

- **KALLOC_OPTION_GLOBAL** – Allocates persistent kernel memory that survives Mach port destruction
- **KALLOC_OPTION_LOCAL** – Allocates memory tied to IOSurface Mach port lifetime

The `gPrimitives` structure is populated during initialization in `libjailbreak_IOSurface_primitives_init()`, which registers the IOSurface-based implementations.

## IOSurface Primitives: The Actual Allocator

The real kernel memory allocation logic resides in `BaseBin/libjailbreak/src/primitives_IOSurface.m`. Dopamine leverages **IOSurface** framework objects—normally used for graphics buffer sharing—to achieve arbitrary kernel memory allocation through Mach-port manipulation.

### Global vs. Local Allocation

| Method | Flag | Persistence | Use Case |
|--------|------|-------------|----------|
| `IOSurface_kalloc_global` | `leak = true` | Survives port close | Long-lived kernel structures |
| `IOSurface_kalloc_local` | `leak = false` | Freed with port | Temporary allocations |

Both variants call into version-specific helpers that handle iOS kernel alignment differences:

- **iOS 16+**: `IOSurface_kalloc_16up` – 16-byte alignment (`0x10`)
- **iOS 15 and earlier**: `IOSurface_kalloc_15` – 15-byte alignment (`0x15`)

The runtime version detection uses `@available(iOS 16.0, *)` to select the appropriate code path.

### Allocation Mechanism

The IOSurface primitive operates through four coordinated steps:

1. **Surface creation** with a crafted `IOSurfaceAddressRanges` dictionary pointing to user-controlled pages
2. **Mach port generation** via `IOSurfaceCreateMachPort`
3. **Kernel address extraction** through `IOSurface_get_ranges` to obtain the buffer's kernel virtual address
4. **Conditional zeroing** of range entries when `leak=true`, preventing automatic deallocation when the port is destroyed

This technique exploits the IOSurface kernel driver's trust of user-provided address ranges, converting them into allocated kernel buffers.

## libkrw Integration: Plugin Architecture

Dopamine registers its kalloc implementation as a **libkrw** provider, enabling external tools to use standard kernel read/write primitives. The registration occurs in [`Packages/libkrw-provider/src/main.c`](https://github.com/opa334/Dopamine/blob/main/Packages/libkrw-provider/src/main.c):

```c
handlers->kmalloc = (krw_kmalloc_func_t)(kalloc);
handlers->kdealloc = (krw_kdealloc_func_t)(kfree);

```

This bridge allows third-party jailbreak components to allocate kernel memory without direct dependency on Dopamine's internal headers.

## Usage Patterns in Dopamine Codebase

### C Exploit Modules: TrustCache Manipulation

The TrustCache extension code in [`BaseBin/libjailbreak/src/trustcache.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/trustcache.c) demonstrates standard allocation and cleanup:

```c
uint64_t kern_addr = 0;
if (kalloc(&kern_addr, 0x4000) != 0) {
    // allocation failed
}
// ... populate and use kernel buffer ...
kfree(kern_addr, 0x4000);   // explicit deallocation

```

### Swift Wrapper: iDownloadKRW Module

Dopamine's Swift bindings expose kalloc to higher-level components:

```swift
public func kalloc(size: UInt) throws -> UInt64 {
    var kallocAddr: UInt64 = 0
    let r = c_kalloc(&kallocAddr, UInt64(size))
    guard r == 0 else {
        throw KRWError.customError(description: "kalloc_data_external failed to allocate!")
    }
    return kallocAddr
}

```

This wrapper converts C return codes into Swift errors while preserving the raw kernel address for subsequent operations.

### Exploit-Specific Helpers

Some modules use thin convenience wrappers for common allocation sizes:

```c
uint64_t page = kalloc_page();   // allocates 0x4000 bytes with KALLOC_OPTION_GLOBAL

```

This pattern appears in page table manipulation code where 16KB aligned allocations are required.

## Key Implementation Files

| Component | Path | Purpose |
|-----------|------|---------|
| Public API | [`BaseBin/libjailbreak/src/primitives.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/primitives.c) | `kalloc()`, `kalloc_with_options()`, `kfree()` |
| IOSurface primitives | `BaseBin/libjailbreak/src/primitives_IOSurface.m` | Actual kernel allocation via Mach ports |
| Initialization | [`BaseBin/libjailbreak/src/main.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/main.c) | Primitive registration into `gPrimitives` |
| libkrw bridge | [`Packages/libkrw-provider/src/main.c`](https://github.com/opa334/Dopamine/blob/main/Packages/libkrw-provider/src/main.c) | Plugin standardization |
| Example consumer | [`BaseBin/libjailbreak/src/trustcache.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/trustcache.c) | Real-world allocation patterns |
| Swift interface | [`Application/Dopamine/Exploits/idownloadd/src/idownloadd/iDownloadKRW.swift`](https://github.com/opa334/Dopamine/blob/main/Application/Dopamine/Exploits/idownloadd/src/idownloadd/iDownloadKRW.swift) | High-level language binding |

## Summary

- **Dopamine's kalloc API** provides `kalloc()` and `kalloc_with_options()` as the primary kernel memory allocation interface
- **IOSurface primitives** implement the actual allocation through Mach-port manipulation of graphics buffer objects
- **Global vs. local distinction** controls allocation persistence, with global allocations deliberately "leaked" for long-term kernel use
- **Version-aware alignment** handles iOS 16+ (16-byte) versus earlier kernels (15-byte) automatically
- **libkrw integration** exposes the allocator to standardized kernel read/write frameworks
- **Multi-language support** spans C exploit code, Swift wrappers, and external plugin consumers

## Frequently Asked Questions

### What is the difference between kalloc and kalloc_with_options in Dopamine?

The `kalloc()` function is a convenience wrapper that always uses `KALLOC_OPTION_GLOBAL`, allocating persistent kernel memory. The `kalloc_with_options()` function allows explicit selection between global (persistent) and local (port-bound) allocation strategies. Most Dopamine internal code uses `kalloc()`, while `kalloc_with_options()` is reserved for scenarios requiring precise lifetime control.

### How does IOSurface enable kernel memory allocation?

IOSurface framework objects accept user-provided address ranges that the kernel driver copies into allocated buffers. By crafting these ranges and manipulating the surface's Mach port, Dopamine causes the kernel to allocate memory that the user partially controls. The `IOSurface_get_ranges` function reveals the kernel virtual address of this buffer, completing the primitive.

### Why does Dopamine distinguish between 16-byte and 15-byte alignment?

iOS 16 introduced changes to the IOSurface kernel driver's internal structure layout, affecting address alignment requirements. The `@available(iOS 16.0, *)` check in `primitives_IOSurface.m` selects `IOSurface_kalloc_16up` for modern kernels and `IOSurface_kalloc_15` for iOS 15 and earlier, ensuring compatibility across supported jailbreak targets.

### When should kernel memory be freed with kfree versus deallocated by closing the Mach port?

Use `kfree()` for allocations made with `KALLOC_OPTION_GLOBAL`, as these persist beyond port lifetime due to range zeroing. Allocations made with `KALLOC_OPTION_LOCAL` are automatically reclaimed when their associated IOSurface Mach port is destroyed, making explicit `kfree()` unnecessary and potentially unsafe for these objects.