# How Dopamine Bypasses iOS Code Signing Enforcement Using the CS_DEBUGGED Flag

> Discover how Dopamine bypasses iOS code signing enforcement using the CS_DEBUGGED flag. Learn about user-space hooks and kernel flag injection for executing unsigned binaries.

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

---

**Dopamine bypasses iOS code signing enforcement by manipulating the CS_DEBUGGED process flag through user-space csops hooks and kernel-level flag injection, allowing execution of unsigned binaries while maintaining system stability.**

The [opa334/Dopamine](https://github.com/opa334/Dopamine) jailbreak tool for iOS disables code-signing enforcement by manipulating the **CS_DEBUGGED** process flag, which signals to the kernel that a process is being debugged and may therefore execute unsigned memory pages. This bypass operates on two architectural levels: user-space system call interception and direct kernel flag manipulation. By selectively applying this flag only to the jailbreak process and its children, Dopamine maintains compatibility with arm64 and arm64e devices while circumventing hardened runtime checks.

## Understanding CS_DEBUGGED and iOS Code Signing

iOS enforces code signing through the **csops** (code signing operations) system call, which manages process flags like **CS_VALID**, **CS_KILL**, **CS_HARD**, and **CS_DEBUGGED**. When **CS_DEBUGGED** (defined as `0x10000000` in [`BaseBin/libjailbreak/src/codesign.h`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/codesign.h)) is set, the kernel relaxes enforcement and permits execution of pages that lack valid code signatures. Dopamine exploits this debugging exception by forcing the flag on for its own process while ensuring other system processes remain protected.

## User-Space Interception via csops Hooks

Dopamine's **systemhook** library intercepts code signing status queries at the user-space level to control visibility of the **CS_DEBUGGED** flag across the system.

### The csops_hook Implementation

In [`BaseBin/systemhook/src/main.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/systemhook/src/main.c), the hook replaces the original `csops` and `csops_audittoken` system calls. After the kernel returns the legitimate status flags, the hook forces **CS_VALID** on and clears **CS_DEBUGGED** for every process by default. However, when the queried process matches Dopamine's own PID (`pid == getpid()`) and the global `gFullyDebugged` flag is true, the hook re-sets **CS_DEBUGGED**.

```c
/* Hooked csops – forces CS_DEBUGGED when requested */
uint32_t csops_hook(pid_t pid, unsigned int ops, void *useraddr, size_t usersize) {
    int rv = syscall(SYS_csops, pid, ops, useraddr, usersize);
    if (rv == 0 && ops == CS_OPS_STATUS && useraddr && usersize == sizeof(uint32_t)) {
        uint32_t csflag = *(uint32_t *)useraddr;
        csflag |= CS_VALID;                // always present
        csflag &= ~CS_DEBUGGED;            // clear by default
        if (pid == getpid() && gFullyDebugged)
            csflag |= CS_DEBUGGED;          // re-enable for Dopamine itself
        *(uint32_t *)useraddr = csflag;
    }
    return rv;
}

```

This selective application ensures unified behavior between arm64 and arm64e devices while preventing unauthorized processes from exploiting the debugged state.

## Kernel-Level Flag Injection

While user-space hooks manage visibility, Dopamine also directly manipulates kernel structures to establish the **CS_DEBUGGED** state at the system level.

### cs_allow_invalid for arm64 Devices

The `cs_allow_invalid()` function in [`BaseBin/libjailbreak/src/kernel.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/libjailbreak/src/kernel.c) performs the kernel-level bypass early in the jailbreak lifecycle (called from `launchdhook/src/main.m`). For arm64 devices, it clears restrictive flags (**CS_KILL** and **CS_HARD**) and sets **CS_DEBUGGED** via `proc_csflags_set(proc, CS_DEBUGGED)`. It also modifies the `vm_map_flags` structure to set `flags.cs_debugged = true`, enabling the task to map unsigned executable pages.

### pmap_cs_allow_invalid for arm64e Devices

On arm64e devices utilizing **pmap_cs** (Pointer Authentication Code signing), direct flag manipulation works differently. Instead of setting **CS_DEBUGGED** directly, `cs_allow_invalid()` calls `pmap_cs_allow_invalid(pmap)` to enable the "allow invalid code" flag in the physical map layer, achieving the same bypass effect through the pmap subsystem.

```c
/* Kernel-level bypass – called early in launchdhook */
cs_allow_invalid(proc_self(), false);   // clears CS_KILL/CS_HARD, sets CS_DEBUGGED

```

## Integration in the Jailbreak Lifecycle

The bypass activates during Dopamine's initialization sequence. In `BaseBin/launchdhook/src/main.m` (lines 49-51), the `cs_allow_invalid(proc_self(), false)` call executes early in the launchd hook, establishing the debugged state before any jailbreak payloads run. Additionally, [`BaseBin/launchdhook/src/jbserver/jbdomain_systemwide.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/launchdhook/src/jbserver/jbdomain_systemwide.c) demonstrates how the **CS_DEBUGGED** flag can be toggled per-domain based on user preferences, allowing granular control over which processes receive code signing exemptions.

## Summary

- **CS_DEBUGGED** (`0x10000000`) is the critical flag that tells the iOS kernel to permit execution of unsigned code pages.
- Dopamine applies a dual-layer bypass: user-space hooks in [`systemhook/src/main.c`](https://github.com/opa334/Dopamine/blob/main/systemhook/src/main.c) filter the flag's visibility, while kernel functions in [`libjailbreak/src/kernel.c`](https://github.com/opa334/Dopamine/blob/main/libjailbreak/src/kernel.c) directly set process code signing flags.
- The `csops_hook` function clears **CS_DEBUGGED** for all processes by default but re-enables it specifically for Dopamine when `gFullyDebugged` is true.
- `cs_allow_invalid()` handles architecture-specific implementation: direct flag manipulation for arm64 and pmap manipulation for arm64e devices.
- The bypass initializes early via `launchdhook/src/main.m` to ensure unsigned binaries can execute throughout the jailbreak session.

## Frequently Asked Questions

### What exactly does the CS_DEBUGGED flag do in iOS?

The **CS_DEBUGGED** flag signals to the iOS kernel that a process is currently being debugged. When this flag is set, the kernel's code signing enforcement subsystem relaxes its checks, allowing the process to execute memory pages that are not properly signed or that have been modified after loading. This is normally used by Xcode when debugging applications, but Dopamine leverages it to bypass signing requirements for jailbreak components.

### How does Dopamine prevent other apps from using the CS_DEBUGGED bypass?

Dopamine's `csops_hook` implementation in [`BaseBin/systemhook/src/main.c`](https://github.com/opa334/Dopamine/blob/main/BaseBin/systemhook/src/main.c) automatically clears the **CS_DEBUGGED** bit for every process by default (`csflag &= ~CS_DEBUGGED`). The flag is only re-enabled when the queried PID matches Dopamine's own process ID (`getpid()`) and the global `gFullyDebugged` variable is set to true. This ensures that only the jailbreak process and its specifically designated children can execute unsigned code, maintaining system security for other applications.

### Why are there different implementations for arm64 and arm64e devices?

The architectural differences between arm64 (older devices) and arm64e (devices with Pointer Authentication Codes) require distinct approaches to code signing enforcement. On arm64, Dopamine can directly manipulate process code signing flags using `proc_csflags_set()` to set **CS_DEBUGGED**. However, arm64e devices use **pmap_cs** (physical map code signing), which requires calling `pmap_cs_allow_invalid(pmap)` to enable unsigned code execution at the memory management layer rather than through traditional process flags.