首页
/ Magisk 内部机制详解:目录结构、启动流程、resetprop 与 SELinux 策略补丁

Magisk 内部机制详解:目录结构、启动流程、resetprop 与 SELinux 策略补丁

2026-09-03 16:20:33作者:裘晴惠Vivianne

本文基于 Magisk 仓库的 docs/details.md 展开,系统讲解 Magisk 在设备上的"内部布局":它把二进制与临时数据放在哪里(tmpfs 目录与 /data/adb)、从 pre-init 到 late_start 的启动流程如何分阶段执行、resetprop 如何绕过 property_service 直接改写系统属性、以及它是如何打补丁修改 SELinux 策略来构建 magisk 域沙箱的。读完本文,你将理解 Magisk 各组件在文件系统上的落点、每个启动阶段的触发时机与职责划分,并能从源码层面验证这些行为的实现细节。

一、文件系统布局:两个关键存放位置

Magisk 需要把不同性质的数据分开存放:一部分是每次开机都会重建的临时数据(二进制、挂载点、补丁文件),放在 tmpfs 中;另一部分是跨重启必须保留的数据(模块、数据库、脚本),放在 /data 的非易失存储中。仓库中这两组路径都以编译期常量的形式集中定义,见 consts.hppconsts.rs

// native/src/include/consts.hpp
#define SECURE_DIR      "/data/adb"
#define MODULEROOT      SECURE_DIR "/modules"
#define DATABIN         SECURE_DIR "/magisk"
#define MAGISKDB        SECURE_DIR "/magisk.db"

// tmpfs paths
#define INTLROOT      ".magisk"
#define MIRRDIR       INTLROOT "/mirror"
#define ROOTOVL       INTLROOT "/rootdir"
...

1.1 "Magisk tmpfs 目录"中的路径

Magisk 会挂载一个 tmpfs 目录来存放临时数据。对于存在 /sbin 目录的设备,会优先选择它,因为这个目录同时可以充当一个 overlay,把二进制注入到 PATH 中;从 Android 11 开始,/sbin 可能不再存在,此时 Magisk 使用 /debug_ramdisk 作为基础目录。当前实际使用的目录可以通过 magisk --path 命令查看。该基础目录的直接内容如下:

# In order to get the current base folder Magisk is using,
# use the command `magisk --path`.
# Binaries like magisk, magiskinit, and all symlinks to
# applets are directly stored in this path. This means when
# this is /sbin, these binaries will be directly in PATH.
MAGISKTMP=$(magisk --path)

# Magisk internal stuffs
INTERNALDIR=$MAGISKTMP/.magisk

# /data/adb/modules will be bind mounted here.
# The original folder is not used due to nosuid mount flag.
$INTERNALDIR/modules

# The current Magisk installation config
$INTERNALDIR/config

# Partition mirrors
# Each directory in this path will be mounted with the
# partition of its directory name.
# e.g. system, system_ext, vendor, data ...
$INTERNALDIR/mirror

# Root directory patch files
# On system-as-root devices, / is not writable.
# All pre-init patched files are stored here and bind mounted.
$INTERNALDIR/rootdir

结合 consts.rs 可以看到实际比文档列出更多的内部子目录:

路径(相对于 $MAGISKTMP/.magisk 用途
configMAIN_CONFIG 当前 Magisk 安装配置
modulesMODULEMNT /data/adb/modules 的 bind mount 挂载点,绕开 nosuid 标志
mirrorMIRRDIR 分区镜像目录,每个子目录按目录名挂载对应分区(system、vendor、data 等)
preinitPREINITMIRR pre-init 阶段的分区挂载镜像
rootdirROOTOVL system-as-root 设备上 / 不可写,pre-init 阶段被补丁修改的文件存放在这里并 bind mount 回去
deviceDEVICEDIR 存放与设备相关的文件,如 socketMAIN_SOCKET)、logLOG_PIPE)、preinit 块设备节点
workerWORKERDIR 模块安装等工作进程的私有挂载命名空间
busyboxBBPATH busybox 及其 applet 符号链接的安装目录

从源码看,$INTERNALDIR/modules 的挂载与文档中"因为 nosuid 挂载标志而不能直接使用原目录"的说法对应:module.rs 中的 setup_module_mount 先把 MODULEROOT(即 /data/adb/modules)bind mount 到 tmpfs 下的 MODULEMNT,再以 MS_RDONLY 重新挂载,确保模块文件(很多带 setuid 位的二进制)能正常生效。而 clean_mountsmount.rs)在启动流程结束后解除这些挂载。

1.2 /data 中的路径

一些二进制与文件必须存放在非易失存储中。为了防检测,所有东西必须存放在 /data 中一个安全且不可察觉的位置。Magisk 选择了 /data/adb,理由有四条(与 bootstages.rssetup_magisk_env 的目录创建逻辑一致):

  • 它是现代 Android 上已经存在的目录,因此它的存在不能作为 Magisk 安装过的证据;
  • 该目录默认权限为 700、属主为 root,非 root 进程以任何方式都无法进入、读取或写入;
  • 该目录的 SELinux 标签是 u:object_r:adb_data_file:s0,只有极少数进程有权与该标签交互;
  • 该目录位于设备加密存储(Device encrypted storage)中,在 FBE(文件级加密)设备上,只要数据分区完成挂载就立即可访问。

/data/adb 下的目录布局:

SECURE_DIR=/data/adb

# Folder storing general post-fs-data scripts
$SECURE_DIR/post-fs-data.d

# Folder storing general late_start service scripts
$SECURE_DIR/service.d

# Magisk modules
$SECURE_DIR/modules

# Magisk modules that are pending for upgrade
# Module files are not safe to be modified when mounted
# Modules installed through the Magisk app will be stored here
# and will be merged into $SECURE_DIR/modules in the next reboot
$SECURE_DIR/modules_update

# Database storing settings and root permissions
MAGISKDB=$SECURE_DIR/magisk.db

# All magisk related binaries, including busybox,
# scripts, and magisk binaries. Used in supporting
# module installation, addon.d, the Magisk app etc.
DATABIN=$SECURE_DIR/magisk

几个值得注意的实现细节:

  • bootstages.rs 中的 post_fs_data 首先检查 SECURE_DIR 是否存在;在 SDK < 24 的旧设备上可以自行创建,现代设备上如果缺失则直接报错中止(bootstages.rs);
  • restoreconselinux.rs)会周期性地把 /data/adb 重新打回 adb_data_file 标签、把 /data/adb/modules 下的文件恢复为 system_file 标签,并把 magisk.db 的权限改为 000——这些都是为了进一步降低目录被识别的概率;
  • modules_update 的设计意图是:通过 Magisk App 安装的模块先落在"待升级"目录中,下次重启时才真正合并进 $SECURE_DIR/modules,避免在模块已挂载生效的状态下修改其文件。

二、Magisk 启动流程

Magisk 的启动分为三个阶段,全部由 magiskinitmagiskd 以及注入到 init.rc 的服务驱动。

2.1 Pre-Init:替换 init 成为第一个执行的程序

magiskinit 会替换 init 成为系统启动的第一个程序(对应 init.rs 中的 main 入口:当 getpid() == 1 时执行 MagiskInit::start())。文档列出的 pre-init 职责逐条如下,并在源码中都有对应实现:

  1. 尽早挂载必要分区。在 legacy system-as-root 设备上切换 root 到 system;在 2SI(two-stage init)设备上,把原始 init 打补丁,使其把第二阶段 init 文件的执行重定向到 magiskinit,由后者完成分区挂载。
    • 源码中 init.rsstart() 先挂载 /proc/sys,然后根据启动条件分流:selinux_setup 参数(2SI)、skip_initramfs(legacy SAR)、force_normal_boot(一阶段 init)、存在 recovery(恢复模式直接放弃并还原)等;
    • 2SI 的劫持有两种手段:hijack_init_with_switch_root(利用原 init 的 SwitchRoot 机制把 magiskinit bind mount 到 /system/bin/init,见 twostage.rs)和 hexpatch_init_for_second_stage——直接在内存映射的 /init 中把字符串 /system/bin/init 替换为 /data/magiskinit,若 /init 不可写则把补丁后的副本 bind mount 覆盖上去(twostage.rs)。
  2. 把 magisk 服务注入 init.rc,让后续 post-fs-datalate_start 等触发点能够拉起 magiskd。
  3. SELinux 策略加载:对于使用 monolithic policy 的设备,直接从 /sepolicy 加载补丁后的策略;否则(2SI 设备)用 FIFO 劫持 selinuxfs 节点,设置 LD_PRELOAD 钩住 security_load_policy,并启动守护进程等待 init 尝试加载 sepolicy 的时机。
  4. 打补丁 sepolicy 规则。如果使用"劫持"方式,则把补丁后的 sepolicy 加载进内核,解除对 init 的阻塞,daemon 退出。
  5. 执行原始 init 继续启动流程,即 init.rs 末尾的 self.exec_init()

此外源码中还包含文档未展开的防御性分支:充电模式(boot_mode == "charger")与 recovery 模式下 recovery_or_charger 会立即还原 ramdisk init、放弃加载 Magisk,避免在非常规启动路径中累积 bootloop 计数(init.rs)。

2.2 post-fs-data:/data 解密挂载后触发

这个阶段在 post-fs-data 触发——此时 /data 已解密并挂载。magiskd 守护进程被拉起,post-fs-data 脚本被执行,模块文件被"magic mount"(挂载到目标路径)。

源码 bootstages.rspost_fs_data 的完整流程:

  1. preserve_stub_apk 处理应用组件伪装;
  2. 检查 /data/adb 是否存在,不存在则(旧设备)创建或报错;
  3. setup_magisk_env:把备用位置的二进制(/cache/data_adb/magisk/data/magisk、应用数据目录下的 install)迁移到 DATABIN,创建 post-fs-data.dservice.d 等目录,把 busybox 复制到 tmpfs 并 busybox --install -s 安装 applet 符号链接,同时把 magisk32magiskpolicy 复制到 magisk tmp(bootstages.rs);
  4. 安全模式检测:读取 magisk.db 中的 BootloopCount,若连续 2 次未启动完成、persist.sys.safemode/ro.sys.safemode 为 1、或检测到特定按键组合,则禁用所有模块与 Zygisk 后直接返回,让下一次启动"干净";
  5. exec_common_scripts("post-fs-data") 执行 /data/adb/post-fs-data.d 中的通用脚本,随后 handle_modules 完成模块的 magic mount。

触发时机上有一个细节:boot_stage_handler 在处理 POST_FS_DATA 请求前会调用 check_data()bootstages.rs),它检查 /proc/mounts/data 是否已挂载,并结合 ro.crypto.stateinit.svc.vold 判断加密状态——这正是文档中"当 /data 被解密并挂载时触发"的实现。

2.3 late_start:service 模式

启动后期 late_start class 被触发,Magisk 的"service 模式"随之启动,执行 service 脚本。对应 bootstages.rs

fn late_start(&self) {
    setup_logfile();
    info!("** late_start service mode running");

    exec_common_scripts(cstr!("service"));
    if let Some(module_list) = self.module_list.get() {
        exec_module_scripts(cstr!("service"), module_list);
    }
}

即先执行 /data/adb/service.d 中的通用服务脚本,再为每个启用模块执行其 service.shboot_stage_handlerBootState 位标志保证 post-fs-data → late_start → boot-complete 严格串行且只执行一次(bootstages.rs)。boot-complete 阶段还会重置 bootloop 计数、创建 preinit 目录镜像(setup_preinit_dir)、并保证管理器应用就位。

三、Resetprop:绕过 property_service 的属性修改工具

3.1 背景:为什么 setprop 不够用

系统属性通常只允许 init 更新、非 root 进程只读。持有 root 后可以通过 setprop 向由 init 承载的 property_service 发请求来修改属性,但只读属性ro. 开头,如 ro.build.product)的修改和删除属性依然被禁止。

3.2 实现原理

resetprop 的做法是从 AOSP 中抽出系统属性相关源码并打补丁,允许直接修改 property 区(prop_area,完全绕开 property_service。从源码看(resetprop/mod.rs),它直接链接了 AOSP 的内部符号:

unsafe extern "C" {
    #[link_name = "__system_property_find2"]
    fn sys_prop_find(key: CharPtr) -> Option<&'static mut PropInfo>;
    #[link_name = "__system_property_update2"]
    fn sys_prop_update(info: &mut PropInfo, val: CharPtr, val_len: u32) -> i32;
    #[link_name = "__system_property_add2"]
    fn sys_prop_add(key: CharPtr, key_len: u32, val: CharPtr, val_len: u32) -> i32;
    #[link_name = "__system_property_delete"]
    fn sys_prop_delete(key: CharPtr, prune: bool) -> i32;
    ...
}

由于绕过了 property_service,文档指出两个注意事项,与 resetprop/cli.rs 的实现一一对应:

  1. on property:foo=bar 动作不会触发。如果属性变更不经过 property_service*.rc 脚本中注册的动作就不会被触发。resetprop 默认的 set 行为与 setprop 一致——触发事件(实现方式是先删除属性、再经 property_service 重新设置);若需要"静默"修改,可用 -n 标志跳过 property_service

    源码中的 set 方法(cli.rs)展示了这一逻辑:ro. 开头的属性强制走 skip_svc(直接修改),且如果属性是 long property 或新值超出 PROP_VALUE_MAX,会先 SYS_PROP.delete(key, false) 删除节点再 add 回去,因为长属性无法直接通过 __system_property_update 修改。

  2. persist 属性的双份存储persist. 开头的属性(如 persist.sys.usb.config)同时存在于 prop_area/data/property 两处。默认情况下,删除属性不会移除持久化存储中的副本(重启后属性会恢复);读取属性也不会从持久化存储读取(这与 getprop 的行为一致)。使用 -p 标志后,删除会同时清除 prop_area/data/property 中的属性,读取也会同时读取两处。

    对应源码:deleteif self.persist && key.starts_with("persist.") 时调用 persist_delete_propcli.rs);get 中当内存区读不到且开启 -p/-P 时回落到 persist_get_propcli.rs);set 中绕过 service 写入 persist 属性时会显式调用 persist_set_prop 落盘。

3.3 完整命令行参考

resetprop 的完整参数表可直接引用 cli.rs 内置的 usage 文本,这也是工具 --help 的实际输出:

resetprop - System Property Manipulation Tool

Usage: resetprop [flags] [arguments...]

Read mode arguments:
   (no arguments)    print all properties
   NAME              get property of NAME

Write mode arguments:
   NAME VALUE        set property NAME as VALUE
   -f,--file   FILE  load and set properties from FILE
   -d,--delete NAME  delete property

Wait mode arguments (toggled with -w):
    NAME             wait until property NAME changes
    NAME OLD_VALUE   if value of property NAME is not OLD_VALUE, get value
                     or else wait until property NAME changes

General flags:
   -h,--help         show this message
   -v,--verbose      print verbose output to stderr
   -w                switch to wait mode
   -c,--compact      compact property area (optional: context label)

Read mode flags:
   -p      also read persistent properties from storage
   -P      only read persistent properties from storage
   -Z      get property context instead of value

Write mode flags:
   -n      set properties bypassing property_service
   -p      always write persistent prop changes to storage

各模式要点:

  • 读模式:无参数打印全部属性(格式 [key]: [value]),单参数读取指定属性;-Z 返回属性的 SELinux 上下文而非值(调用 __system_property_get_context);
  • 写模式:NAME VALUE 设置属性;-f FILEkey=value 格式文件批量加载;-d NAME 删除属性;
  • 等待模式:-w NAME 阻塞直到该属性发生变化;-w NAME OLD_VALUE 在属性等于 OLD_VALUE 时等待其变化、不等则直接取值。底层通过 __system_property_wait + 属性序列号(serial)实现(cli.rs);
  • -c/--compact 对属性区做压缩整理(__system_property_compact),可选传入 context 标签只压缩指定上下文;
  • 互斥校验:-w-c-f-d 四种操作模式不能同时出现,否则报错(cli.rs)。

此外,resetprop 在 Magisk 内部也被当作属性读写的基础库使用:set_prop/load_prop_file 固定 skip_svc: true(内部调用一律绕过 property_service),get_proppersist. 前缀自动开启持久化回落(cli.rs)。

四、SELinux 策略补丁

Magisk 会打补丁修改原版 sepolicy,确保 root 与 Magisk 的操作能在安全受控的方式进行。核心设计(常量定义见 consts.rs):

  • 新增域 magiskSEPOL_PROC_DOMAIN,上下文 u:r:magisk:s0),事实上是permissive 域——magiskd 与所有 root shell 都运行在该域中;
  • 新增文件类型 magisk_fileSEPOL_FILE_TYPE,上下文 u:object_r:magisk_file:s0),被配置为允许所有域访问,即"不受限制的文件上下文";
  • 另有一个文档未提及的日志管道类型 magisk_log_file,只允许 root 与 zygote 打开,用于日志输出。

4.1 Android 8.0 之前:放权式模型

所有被允许的 su client 域都可以直接连接 magiskd 并与守护进程建立连接,获取远程 root shell。为此 Magisk 还必须放宽若干 ioctl 操作,让 root shell 能正常工作。

4.2 Android 8.0 之后:magisk_client 沙箱模型

为了减少对 Android 沙箱规则的放宽,8.0 之后部署了新的 SELinux 模型:

  • magisk 二进制的文件类型标签为 magisk_exec
  • 运行在"允许的 su client 域"中的进程执行 magisk 二进制(包括 su 命令)时,通过 type_transition 规则转入 magisk_client 域;
  • 规则严格限制:只有 magisk 域进程才能把文件归属为 magisk_exec
  • 禁止任何域直接连接 magiskd 的 socket,访问守护进程的唯一途径是经过一个 magisk_client 进程。

这些改动让 Magisk 自身的规则与系统其余策略隔离开,同时保持 Android 沙箱完整。文档指出完整规则集位于 sepolicy/rules.cpp——在当前仓库中对应实现是 sepolicy/rules.rsmagisk_rules,其中可以看到上述设计的落地:

// Create unconstrained file type
allow(["domain"], [file],
    ["file", "dir", "fifo_file", "chr_file", "lnk_file", "sock_file"], all);

// Make our root domain unconstrained
allow([proc], [
    "fs_type", "dev_type", "file_type", "domain",
    "service_manager_type", "hwservice_manager_type", "vndservice_manager_type",
    "port_type", "node_type", "property_type"
], all, all);

// Just in case, make the domain permissive
permissive([proc]);

// Allow us to do any ioctl
allowxperm([proc], ["fs_type", "dev_type", "file_type", "domain"],
    ["blk_file", "fifo_file", "chr_file"], xall);

几个与文档论述直接呼应的规则:allow(["domain"], [file], ...all)magisk_file 对所有域开放;permissive([proc]) 使 magisk 域事实上无约束;allowxperm 全量放开 ioctlxall 表示 0x0000-0xFFFF),对应"放宽 ioctl 让 root shell 正常工作";allowxperm([proc], [proc], ["tcp_socket", "udp_socket", "rawip_socket"], xall) 则覆盖网络 socket 操作。

同文件还有一些文档未提及但属于该策略补丁一部分的防御性规则:deny(all, ["kernel"], ["security"], ["load_policy"]) 阻止任何域绕过 Magisk 自行加载策略;deny(["init"], ["adb_data_file"], ["dir"], ["search"]) 与 vendor_init 同理,确保 /data/adb 的标签不被 init 的恢复逻辑干扰(rules.rs)。

五、小结

docs/details.md 所描述的四个部分——tmpfs 目录布局、/data/adb 布局、三阶段启动流程、resetprop 与 SELinux 补丁——构成了 Magisk 在设备上的完整"内部地图":

  1. 目录层:易失数据(magisk 二进制、applet 符号链接、模块挂载点、分区镜像、rootdir 补丁)在 $MAGISKTMP/.magisk,持久数据(modules、magisk.db、busybox、脚本目录)在 /data/adb,全部由 consts.hpp 集中定义;
  2. 启动层magiskinit 以 PID 1 劫持启动完成分区挂载与策略加载(init.rs),magiskdpost-fs-data(环境初始化 + 模块 magic mount)→ late_start(service 脚本)→ boot-complete(收尾与安全模式计数重置)严格串行推进(bootstages.rs);
  3. 属性层resetprop 通过链接 AOSP 内部符号直写 prop_area,用 -n/-p 精细控制事件触发与持久化语义(resetprop/cli.rs);
  4. 策略层:permissive 的 magisk 域 + 全开放的 magisk_file + magisk_client 中转模型,把 root 能力与系统沙箱解耦(sepolicy/rules.rs)。

理解这条链路后,排查模块挂载失败、属性修改不生效、启动卡 safe mode 等问题时,就可以直接定位到对应的启动阶段与源码位置。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384