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

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 opens the database and executes a migration routine. The code defines table creation through lambdas that execute raw SQL:

// 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 provides type-safe accessors:

// 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 defines the RootSettings struct and fetch logic:

// 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:

#[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 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, 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:

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

Retrieve Per-UID Policy (Rust)

Fetch the current root policy for a specific application:

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

Query via Command Line

Read the current root access mode directly from the database:

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 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.
  • 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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →