Shell Domain
本文面向已经读过 Init Domain、App Domains 和 AVC 日志 的读者。本文追踪 Android 17 adb shell 的真实安全链路:adbd 如何创建 shell 子进程,shell 如何访问调试资源,以及 run-as 如何在 UID、路径和 seapp selector 约束下切入应用域。
shell 是 UID 2000 对应的调试 domain,不是 root 的别名。每条命令仍由 source context、目标 type、class、permission、DAC 和 build variant 共同决定。
1. 会话入口
1.1 类型声明
源码文件:system/sepolicy/public/shell.te
type shell, domain, mlstrustedsubject;
type shell_exec, system_file_type, exec_type, file_type;源码文件:system/sepolicy/private/shell.te
typeattribute shell coredomain, mlstrustedsubject;
app_domain(shell)
net_domain(shell)
bluetooth_domain(shell)
selinux_check_access(shell)
selinux_check_context(shell)shell_exec 是可执行文件 type,shell 是 process domain。app_domain 只提供 appdomain 聚合和 tmpfs 基础规则,调试专用 Binder、property、Perfetto 和 trace 权限仍由 private 规则增加。
1.2 adbd子进程
源码文件:packages/modules/adb/daemon/shell_service.cpp
// Each incoming shell request gets a subprocess with the shell UID.
Subprocess* ShellService::CreateSubprocess(
const std::string& command, const std::vector<std::string>& args) {
auto subprocess = std::make_unique<Subprocess>(command, args);
subprocess->SetUid(AID_SHELL);
subprocess->SetGid(AID_SHELL);
return subprocess.release();
}源码文件:system/sepolicy/private/adbd.te
typeattribute adbd coredomain;
init_daemon_domain(adbd)
domain_auto_trans(adbd, shell_exec, shell)adbd 负责 USB/TCP transport 和认证,shell subprocess 负责命令执行。adbd 的传输权限不能替代 shell 的文件或 Binder 权限。
2. 命令执行
2.1 host协议
源码文件:packages/modules/adb/client/commandline.cpp
static int adb_shell(int argc, const char** argv) {
bool use_shell_protocol = CanUseFeature(*features, kFeatureShell2);
PtyAllocationMode tty = use_shell_protocol ? kPtyAuto : kPtyDefinitely;
// Parse PTY/raw options before constructing the device service string.
// ...
std::string service_string =
ShellServiceString(use_shell_protocol, shell_type_arg, command);
return RemoteShell(use_shell_protocol, shell_type_arg,
escape_char, command.empty(), service_string);
}shell protocol、PTY/raw 和命令字符串只改变 I/O 传输;设备端仍在 shell domain 执行。
2.2 child生命周期
源码文件:packages/modules/adb/daemon/shell_service.cpp
bool Subprocess::Start() {
pid_t pid = fork();
if (pid == 0) {
// Transport descriptors are closed before the command is executed.
CloseDescriptors();
if (execv(command_.c_str(), argv_.data()) == -1) {
_exit(127);
}
}
if (pid < 0) return false;
pid_ = pid;
return true;
}父 adbd 持有 pid/socket,child 关闭 transport descriptor 后 exec。127 是 exec 失败的结果码,命令运行后的 AVC 由具体访问触发。
3. 调试权限
3.1 属性与Binder
源码文件:system/sepolicy/private/shell.te
set_prop(shell, shell_prop)
set_prop(shell, ctl_bugreport_prop)
set_prop(shell, ctl_dumpstate_prop)
set_prop(shell, debug_prop)
set_prop(shell, perf_drop_caches_prop)
set_prop(shell, powerctl_prop)
get_prop(shell, serialno_prop)
get_prop(shell, device_logging_prop)
binder_call(shell, storaged)
binder_call(shell, statsd)
binder_call(shell, gpuservice)
binder_call(shell, lpdumpd)
binder_call(shell, hal_keymint)
hal_client_domain(shell, hal_atrace)set_prop 产生 property_service set 关系,get_prop 只读目标属性;binder_call 产生 Binder call/transfer/fd use,service_manager find 仍需独立规则。shell 能设置的是明确调试和测试属性,不是任意 persist 属性。
3.2 Perfetto
源码文件:system/sepolicy/private/shell.te
unix_socket_connect(shell, traced_consumer, traced)
perfetto_producer(shell)
domain_auto_trans(shell, perfetto_exec, perfetto)
allow shell perfetto:process signal;
allow shell perfetto_traces_data_file:dir rw_dir_perms;
allow shell perfetto_traces_data_file:file { r_file_perms unlink };shell 控制 traced,执行 perfetto_exec 时可切入 perfetto domain;trace 文件的读写由具体 data file type 决定。
4. 文件边界
源码文件:system/sepolicy/private/shell.te
allow shell shell_data_file:dir create_dir_perms;
allow shell shell_data_file:file create_file_perms;
allow shell tombstone_data_file:dir r_dir_perms;
allow shell tombstone_data_file:file r_file_perms;
allow shell anr_data_file:dir r_dir_perms;
allow shell anr_data_file:file r_file_perms;
neverallow shell file_type:file link;
neverallow shell dev_type:blk_file ~getattr;
neverallow shell input_device:chr_file no_w_file_perms;shell_data_file 是 /data/local/tmp 等调试工作区;tombstone/ANR 是只读诊断证据。neverallow 禁止硬链接、块设备写入和输入设备写入,防止 shell UID 构造旁路。
5. run-as
5.1 caller校验
源码文件:system/core/run-as/run-as.cpp
if (getuid() != AID_SHELL && getuid() != AID_ROOT) {
error(1, 0, "only 'shell' or 'root' users can run this program");
}
if (android::base::GetBoolProperty(
"ro.boot.disable_runas", false)) {
error(1, 0, "run-as is disabled from the kernel commandline");
}run-as 是带 file capabilities 的工具,先限制 caller UID 和设备开关,再读取 package list;它不是无条件的 shell 到 app transition。
5.2 路径与context
源码文件:system/core/run-as/run-as.cpp
static void check_data_path(
const char* package_name, const char* data_path, uid_t uid) {
if (data_path[0] != '/') {
error(1, 0, "%s data path not absolute: %s", package_name, data_path);
}
// Reject parent traversal and require system-owned parents.
// ...
check_directory(data_path, uid);
}
if (selinux_android_setcontext(
uid, 0, seinfo.c_str(), pkgname) < 0) {
error(1, errno, "couldn't set SELinux context");
}路径必须绝对、不能包含 ..,父目录由 system 拥有,最终目录由目标 UID 拥有;通过后才把 uid、seinfo、包名交给 seapp_context_lookup。
6. 条件与排障
6.1 build variant
源码文件:system/sepolicy/private/shell.te
userdebug_or_eng(`
allow shell debugfs_tracing_debug:file rw_file_perms;
allow shell profcollectd_exec:file rx_file_perms;
allow shell profcollectd:binder call;
set_prop(shell, persist_debug_prop)
')
recovery_only(`
allow shell rootfs:file rx_file_perms;
')debugfs、profcollectd、persist_debug 和 recovery rootfs 规则都有条件边界。user build 即使 UID 仍为 2000,也没有这些额外 allow。
6.2 诊断命令
# Confirm source contexts of adbd, shell and run-as.
adb shell 'ps -AZ | grep -E "adbd|/system/bin/sh|run-as"'
# Query Binder and service-manager edges separately.
sesearch -A -s shell -c binder
sesearch -A -s shell -c service_manager
# Inspect target labels and AVC fields.
adb shell 'ls -Zd /data/local/tmp /data/misc/perfetto-traces'
adb shell 'dmesg | grep "avc: denied" | grep "scontext=u:r:shell:s0"'ps -AZ 验证 source context,ls -Zd 验证 target type,sesearch 区分 Binder 与 service_manager,AVC 显示具体 permission。run-as 失败还要核对 package UID、data path owner、disable_runas 和 seinfo。
6.3 测试边界
源码文件:system/core/init/service_test.cpp
TEST(service, make_temporary_oneshot_service_with_seclabel) {
auto svc = Service::MakeTemporaryOneshotService(
{"exec", "u:r:su:s0", "--", "/system/bin/sh"});
ASSERT_TRUE(svc.ok());
ASSERT_EQ("u:r:su:s0", svc->seclabel());
}该测试验证 init parser 把 seclabel 保存到 Service;它不证明 user build 允许 su transition,也不证明 shell 可以读取 app data。
7. 源码导航
- system/sepolicy/public/shell.te、system/sepolicy/private/shell.te:shell 类型、调试权限、Binder/HAL、property 和 neverallow。
- system/sepolicy/private/adbd.te:adbd 执行 shell_exec 时的 transition。
- packages/modules/adb/daemon/shell_service.cpp:设备端 shell subprocess owner 和 exec。
- packages/modules/adb/client/commandline.cpp:shell protocol、PTY/raw 和命令请求。
- system/core/run-as/run-as.cpp:caller、路径校验和目标 seapp context。
- system/sepolicy/private/seapp_contexts:最终 domain/type 选择。
选择一个真实故障,依次记录 shell source、目标 type/class/permission、DAC mode 和 build variant;若使用 run-as,再加入 caller UID、目标 UID、路径 owner 和 seinfo。这样可以区分 shell 自身权限不足、run-as 前置校验失败和目标 app domain 拒绝。
