# How Magisk Daemon Communicates with the App via Unix Socket IPC

> Discover how Magisk daemon communicates with the app using Unix socket IPC. Learn about request handling and command dispatching for seamless interaction.

- Repository: [John Wu/Magisk](https://github.com/topjohnwu/Magisk)
- Tags: internals
- Published: 2026-03-05

---

**The Magisk daemon (`magiskd`) creates a Unix domain socket at `MAGISK_TMP/MAGISK_SOCKET` that accepts binary-encoded requests from the Android app, validates peer credentials, and dispatches commands through synchronous, asynchronous, or boot-stage handler tiers.**

The `topjohnwu/Magisk` repository implements a custom, permission-checked IPC channel to bridge the root-level daemon with the unprivileged Magisk app and native clients. This system relies on a private Unix socket residing on the Magisk temporary file system rather than Android Binder, enabling structured binary messaging and file descriptor passing between processes.

## Socket Creation and Daemon Startup

When the daemon initializes via `daemon_entry` in **[`native/src/core/daemon.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/daemon.rs)**, it constructs the socket path by joining `get_magisk_tmp()` with the `MAIN_SOCKET` constant (defined as `MAGISK_SOCKET` in **[`native/include/consts.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/include/consts.rs)**). The daemon binds a `UnixListener` to this path, sets world-writable permissions (`0o666`), and applies the `MAGISK_FILE_CON` SELinux context to ensure the socket is accessible to the app.

```rust
let sock_path = cstr::buf::new::<64>()
    .join_path(get_magisk_tmp())
    .join_path(MAIN_SOCKET);
let sock = UnixListener::bind(&sock_path)?;
sock_path.follow_link().chmod(0o666)?;
sock_path.set_secontext(cstr!(MAGISK_FILE_CON))?;

```

The daemon then enters a loop accepting connections and spawning threads to handle incoming clients.

## Client Connection Flow

Any client—including the Magisk app via JNI—initiates communication through `connect_daemon`, exposed in the CXX bridge within **[`native/src/core/lib.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/lib.rs)**. This function attempts to connect to the existing socket; if the daemon is not running and the caller is root (`getuid().is_root()`), it can optionally fork and launch the daemon automatically.

```rust
pub fn connect_daemon(code: RequestCode, create: bool) -> LoggedResult<UnixStream> {
    let sock_path = …;                // same path as daemon
    match UnixStream::connect(&sock_path) {
        Ok(socket) => send_request(code, socket),   // daemon already running
        Err(_) => {                               // daemon not running
            if create && getuid().is_root() { … fork & daemon_entry() … }
        }
    }
}

```

Once connected, the client receives a raw file descriptor that is wrapped into a `UnixStream` for subsequent I/O operations.

## Binary Message Framing and Protocol

The IPC protocol uses a simple binary framing mechanism. Clients write a 32-bit `RequestCode` (defined in the CXX enum in **[`native/src/core/lib.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/lib.rs)**) to the socket, and the daemon responds with a 32-bit `RespondCode` indicating success or failure (e.g., `OK`, `ROOT_REQUIRED`, `ACCESS_DENIED`).

```rust
// Client side
socket.write_pod(&code.repr)?;
let mut res = -1;
socket.read_pod(&mut res)?;

// Server side (daemon.rs)
let mut code = -1;
client.read_pod(&mut code)?;
let code = RequestCode { repr: code };
// permission checks …
client.write_pod(&RespondCode::OK.repr)?;

```

The `write_pod` and `read_pod` methods facilitate fixed-size primitive data exchange, while variable-length payloads use the `Encodable` and `Decodable` traits defined in **[`native/src/core/socket.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/socket.rs)**.

## Request Dispatch and Permission Validation

Upon receiving a request, the daemon’s `handle_requests` function (in **[`native/src/core/daemon.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/daemon.rs)**) inspects the peer’s Unix credentials (`UCred`) to validate whether the caller is root, the Zygote process, or an explicitly allowed application. After validation, the daemon categorizes the request code into one of three handler tiers:

- **Synchronous handling** – Codes below `_SYNC_BARRIER_` are processed immediately in the same thread via `handle_request_sync`.
- **Asynchronous handling** – Codes between `_SYNC_BARRIER_` and `_STAGE_BARRIER_` are dispatched to a thread pool through `handle_request_async`.
- **Boot-stage handling** – Codes above `_STAGE_BARRIER_` are routed to `boot_stage_handler` for early-boot operations.

This tiered approach prevents blocking the main accept loop during long-running operations like superuser request processing or Zygisk module initialization.

## Structured Data and File Descriptor Passing

For complex interactions, the **[`native/src/core/socket.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/socket.rs)** module provides a custom binary codec. Types implement the `Encodable` and `Decodable` traits to serialize POD values, strings, and vectors over the `UnixStream`. This enables structured data transfer for requests like `SUPERUSER`, where the daemon receives a `SuRequest` struct:

```rust
fn handle_request_async(&self, mut client: UnixStream, code: RequestCode, cred: UCred) {
    match code {
        RequestCode::SUPERUSER => {
            let su_req: SuRequest = client.read_decodable().unwrap();
            self.su_daemon_handler(client, cred);
        }
        // … other request handling …
    }
}

```

When a request requires passing an open file descriptor—such as a PTY master for shell sessions—the daemon uses `send_fd` and `recv_fd` helpers that transmit the FD using `SCM_RIGHTS` ancillary data over the socket.

```rust
let pty_fd = open_pty()?;
send_fd(client.as_raw_fd(), pty_fd); // Transfers FD using SCM_RIGHTS

```

## Summary

- The daemon binds a `UnixListener` at `MAGISK_TMP/MAIN_SOCKET` with `0666` permissions and the `MAGISK_FILE_CON` SELinux context.
- Clients invoke `connect_daemon()` from **[`native/src/core/lib.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/lib.rs)** to establish a `UnixStream`, spawning the daemon if necessary.
- Communication uses 32-bit `RequestCode` and `RespondCode` integers for simple command/response pairs.
- The daemon validates peer credentials and dispatches requests through synchronous, asynchronous, or boot-stage handlers based on barrier constants.
- Complex payloads utilize `Encodable`/`Decodable` traits in **[`native/src/core/socket.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/socket.rs)**.
- File descriptors are transferred via `SO_PASSCRED`-style ancillary data using `send_fd` and `recv_fd`.

## Frequently Asked Questions

### How does the Magisk app establish a socket connection from Java or Kotlin?

The Android app connects through a JNI bridge that invokes `connect_daemon_for_cxx` defined in **[`native/src/core/lib.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/lib.rs)**. This Rust function returns a raw file descriptor as an `int`, which the Java wrapper converts into a `ParcelFileDescriptor` or uses directly via native code to exchange `RequestCode` values and read daemon responses.

### What security mechanisms restrict access to the Magisk socket?

The daemon enforces three layers of security: Unix credentials (`UCred`) retrieved via `SO_PEERCRED` validate the UID/PID of the caller; explicit permission checks in `handle_requests` filter for root, Zygote, or allowlisted apps; and the socket file itself carries the `MAGISK_FILE_CON` SELinux context, ensuring only properly labeled processes can access it even with `0666` permissions.

### Why does Magisk use Unix sockets instead of Android Binder for IPC?

Unix domain sockets provide a simpler, lower-level abstraction that works reliably in early boot stages before the full Android framework is available, and they natively support passing arbitrary file descriptors (like PTYs or mount points) via `SCM_RIGHTS`. This avoids the complexity and dependency overhead of Binder while maintaining compatibility with both native code and JNI layers.

### How are variable-length data structures serialized over the socket?

The **[`native/src/core/socket.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/socket.rs)** module defines `Encodable` and `Decodable` traits that implement a custom binary protocol. These traits handle primitive types, length-prefixed strings, and vectors by writing raw bytes to the `UnixStream`, allowing structured data like `SuRequest` objects to be transmitted seamlessly alongside simple integer response codes.