# How Magisk's SQLite Database Stores Settings and Root Access Policies

> Discover how Magisk's SQLite database at /data/adb/magisk.db stores global settings and root access policies in its settings, policies, and strings tables for robust access control.

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

---

**Magisk persists global configuration and per-application root rules in a version-controlled SQLite database located at `/data/adb/magisk.db`, utilizing three core tables—`settings`, `policies`, and `strings`—to enforce access controls across reboots.**

The Magisk framework, maintained in the `topjohnwu/Magisk` repository, manages root access and module configuration through a lightweight SQLite backend rather than flat files. This **Magisk SQLite database** architecture enables atomic updates, per-user policy enforcement, and schema migrations while maintaining a minimal footprint on the device. Understanding the database structure reveals how the daemon makes real-time authorization decisions for superuser requests.

## Database Schema and Core Tables

The database file resides at `MAGISKDB` (typically **/data/adb/magisk.db**) and contains three distinct tables optimized for different data types:

- **`settings`** – Stores global key-value pairs using `key TEXT PRIMARY KEY` and `value INT` columns. This table controls features such as **root access mode**, **multi-user mode**, **deny-list** configuration, and **Zygisk** toggles.

- **`policies`** – Maintains per-UID root access rules with columns `uid INT PRIMARY KEY`, `policy INT`, `until INT`, `logging INT`, and `notification INT`. The `policy` column maps to the `SuPolicy` enum (Allow, Deny, Query), while `until` implements temporary overrides using Unix timestamps.

- **`strings`** – Holds arbitrary string-valued settings via `key TEXT PRIMARY KEY` and `value TEXT` columns, primarily used by the Magisk Manager app for custom update-channel URLs and similar configuration.

## Database Initialization and Versioning

During Magisk startup, the C++ helper in **[`native/src/core/sqlite.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/sqlite.cpp)** opens the database and executes a migration routine. The code defines table creation through lambdas that execute raw SQL:

```cpp
// native/src/core/sqlite.cpp
auto create_policy = [&] {
    return sql_exec_impl(db.get(),
        "CREATE TABLE IF NOT EXISTS policies "
        "(uid INT, policy INT, until INT, logging INT, "
        "notification INT, PRIMARY KEY(uid))");
};

auto create_settings = [&] {
    return sql_exec_impl(db.get(),
        "CREATE TABLE IF NOT EXISTS settings "
        "(key TEXT, value INT, PRIMARY KEY(key))");
};

```

The initialization logic checks `PRAGMA user_version` against the current `DB_VERSION` constant. If the existing schema is older, the code rewrites tables and updates the version number, ensuring forward compatibility while deliberately refusing to downgrade.

## Storing Global Settings

Global configuration options are stored as rows in the **`settings`** table. The Rust wrapper in **[`native/src/core/db.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/db.rs)** provides type-safe accessors:

```rust
// native/src/core/db.rs
pub fn set_db_setting(&self, key: DbEntryKey, value: i32) -> SqliteResult<()> {
    self.db_exec(
        "INSERT OR REPLACE INTO settings (key,value) VALUES(?,?)",
        &[Text(key.to_str()), Integer(value as i64)],
    ).sql_result()
}

```

The `DbEntryKey` enum defines supported configuration keys. Common settings include:

- **`RootAccess`** – `0=Disabled`, `1=AppsOnly`, `2=AdbOnly`, `3=AppsAndAdb`
- **`SuMultiuserMode`** – `0=OwnerOnly`, `1=OwnerManaged`, `2=User`
- **`DenylistConfig`** – Boolean flag for the deny-list feature
- **`ZygiskConfig`** – Enables Zygisk on emulators

Retrieval mirrors the insertion pattern through `get_db_setting`, which returns integer values with fallback defaults.

## Per-UID Root Access Policies

When an application or ADB shell requests root access, Magisk's daemon queries the **`policies`** table to determine authorization. The implementation in **[`native/src/core/su/db.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/su/db.rs)** defines the `RootSettings` struct and fetch logic:

```rust
// native/src/core/su/db.rs
pub fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> {
    self.db_exec_with_rows(
        "SELECT policy, logging, notification FROM policies \
         WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))",
        &[Integer(uid as i64)],
        settings,
    ).sql_result()
}

```

The `RootSettings` struct mirrors the database columns:

```rust
#[derive(Default)]
pub struct RootSettings {
    pub policy: SuPolicy,   // Allow / Deny / Query
    pub log: bool,          // Record request in logs
    pub notify: bool,       // Trigger UI notification
}

```

The `until` column enables temporary grants. A non-zero Unix timestamp allows root access only until the specified time, after which the policy automatically reverts.

## Runtime Enforcement Flow

The daemon orchestrates database interactions through a structured lifecycle:

1. **Startup** – `open_and_init_db()` in [`sqlite.cpp`](https://github.com/topjohnwu/Magisk/blob/main/sqlite.cpp) creates or updates the schema.
2. **Configuration Changes** – The UI or CLI invokes `MagiskD::set_db_setting` (Rust) to execute `INSERT OR REPLACE` operations.
3. **Policy Lookup** – When a request reaches `MagiskD::uid_granted_root` in [`daemon.rs`](https://github.com/topjohnwu/Magisk/blob/main/daemon.rs), the code first loads global modes via `get_db_settings`, then retrieves per-UID overrides through `get_root_settings`.
4. **Enforcement** – The daemon evaluates `RootAccess` modes, `MultiuserMode`, and the specific `RootSettings` to render a final allow or deny decision.

## Practical Code Examples

### Modify Global Root Access (Rust)

Disable root for all applications by updating the `RootAccess` setting:

```rust
use magiskd::{MagiskD, DbEntryKey};

fn disable_root(magisk: &MagiskD) -> Result<(), magisk::SqliteError> {
    // Set key to 0 (Disabled)
    magisk.set_db_setting(DbEntryKey::RootAccess, 0)
}

```

*Source: [`native/src/core/db.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/db.rs)*

### Retrieve Per-UID Policy (Rust)

Fetch the current root policy for a specific application:

```rust
use magiskd::{MagiskD, RootSettings};

fn show_policy(magisk: &MagiskD, uid: i32) -> Result<RootSettings, magisk::SqliteError> {
    let mut settings = RootSettings::default();
    magisk.get_root_settings(uid, &mut settings)?;
    Ok(settings)
}

```

*Source: [`native/src/core/su/db.rs`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/su/db.rs)*

### Query via Command Line

Read the current root access mode directly from the database:

```bash
magisk --db get root_access

# Returns: 0 (Disabled), 1 (AppsOnly), 2 (AdbOnly), or 3 (AppsAndAdb)

```

*Source: CLI implementation calling `MagiskD::get_db_setting`*

## Summary

- **Magisk stores configuration in `/data/adb/magisk.db`**, a SQLite database with three tables: `settings`, `policies`, and `strings`.
- **Schema versioning** in [`native/src/core/sqlite.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/sqlite.cpp) ensures forward-compatible migrations during daemon startup.
- **Global settings** use integer key-value pairs in the `settings` table, controlled via `DbEntryKey` enums in [`db.rs`](https://github.com/topjohnwu/Magisk/blob/main/db.rs).
- **Per-UID policies** reside in the `policies` table, with columns for access rules, logging preferences, and temporary expiration timestamps.
- **Authorization decisions** combine global modes and per-user `RootSettings` fetched through `get_root_settings` during each superuser request.

## Frequently Asked Questions

### Where is the Magisk SQLite database stored on Android devices?

The database resides at **`/data/adb/magisk.db`** (referenced internally as `MAGISKDB`). This path is inaccessible to standard applications due to Android's permissions model, ensuring only the Magisk daemon and root processes can read or modify configuration data.

### What tables exist in the Magisk SQLite database?

The database contains three core tables: **`settings`** (global integer configuration), **`policies`** (per-UID root access rules with logging and notification flags), and **`strings`** (text-based configuration for the Manager app). Additional tables may appear in future versions as the schema evolves through the versioning system.

### How does Magisk handle database schema updates?

During initialization, the C++ code in [`native/src/core/sqlite.cpp`](https://github.com/topjohnwu/Magisk/blob/main/native/src/core/sqlite.cpp) compares `PRAGMA user_version` against the compiled `DB_VERSION`. If the stored version is older, the code executes migration routines that recreate tables with updated schemas. Magisk explicitly refuses to downgrade databases, preventing rollback issues when switching between versions.

### Can root access be granted temporarily through the database?

Yes. The **`until`** column in the `policies` table accepts a Unix timestamp. When set to a future time, `get_root_settings` filters the query with `until=0 OR until>strftime('%s', 'now')`, effectively ignoring expired policies. This allows time-bound root grants that automatically revoke access without manual intervention.