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 usingkey TEXT PRIMARY KEYandvalue INTcolumns. 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 columnsuid INT PRIMARY KEY,policy INT,until INT,logging INT, andnotification INT. Thepolicycolumn maps to theSuPolicyenum (Allow, Deny, Query), whileuntilimplements temporary overrides using Unix timestamps. -
strings– Holds arbitrary string-valued settings viakey TEXT PRIMARY KEYandvalue TEXTcolumns, 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=AppsAndAdbSuMultiuserMode–0=OwnerOnly,1=OwnerManaged,2=UserDenylistConfig– Boolean flag for the deny-list featureZygiskConfig– 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:
- Startup –
open_and_init_db()insqlite.cppcreates or updates the schema. - Configuration Changes – The UI or CLI invokes
MagiskD::set_db_setting(Rust) to executeINSERT OR REPLACEoperations. - Policy Lookup – When a request reaches
MagiskD::uid_granted_rootindaemon.rs, the code first loads global modes viaget_db_settings, then retrieves per-UID overrides throughget_root_settings. - Enforcement – The daemon evaluates
RootAccessmodes,MultiuserMode, and the specificRootSettingsto 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, andstrings. - Schema versioning in
native/src/core/sqlite.cppensures forward-compatible migrations during daemon startup. - Global settings use integer key-value pairs in the
settingstable, controlled viaDbEntryKeyenums indb.rs. - Per-UID policies reside in the
policiestable, with columns for access rules, logging preferences, and temporary expiration timestamps. - Authorization decisions combine global modes and per-user
RootSettingsfetched throughget_root_settingsduring 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →