Skip to content

Shell Domain

追踪 Android 17 adb shell 从 adbd 会话、shell domain 到命令执行、属性调试、Binder 访问和 run-as 边界。

基于android-17.0.0_r1
AndroidSELinuxshelladb源码阅读

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

text
type shell, domain, mlstrustedsubject;
type shell_exec, system_file_type, exec_type, file_type;

源码文件:system/sepolicy/private/shell.te

text
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

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

text
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

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

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

text
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

text
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

text
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

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

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

text
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 诊断命令 ​

sh
# 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

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. 源码导航 ​

  1. system/sepolicy/public/shell.te、system/sepolicy/private/shell.te:shell 类型、调试权限、Binder/HAL、property 和 neverallow。
  2. system/sepolicy/private/adbd.te:adbd 执行 shell_exec 时的 transition。
  3. packages/modules/adb/daemon/shell_service.cpp:设备端 shell subprocess owner 和 exec。
  4. packages/modules/adb/client/commandline.cpp:shell protocol、PTY/raw 和命令请求。
  5. system/core/run-as/run-as.cpp:caller、路径校验和目标 seapp context。
  6. 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 拒绝。