Shell 安全边界¶
最后更新:
EvccApi 保留自由输入 Shell,但它不是无限制 Root 终端。获得 shell.exec 后仍会经过规范化、逐段
检查、永久黑名单、超时、输出和并发限制。
绝不能依赖客户端自行过滤
安全检查由 EVCC 服务端强制执行。第三方客户端中的提示、确认和语法高亮只改善体验,不能替代 服务端策略。
永久拒绝¶
以下类别不会因任何预设或授权而放行:
- 广泛删除系统、数据、固件、启动或持久化目录。
- 格式化、擦除、刷写块设备、修改启动链。
- 终止
system_server、init、SurfaceFlinger、zygote 等核心服务。 - 持久化提权、关闭 SELinux 或注入安全策略。
- 资源耗尽、重启、关机和改变启动状态。
- 直接向块设备重定向或通过危险设备流写入。
- 任意输出重定向,以及
cp、mv、rm、ln、tee、dd、归档解包、下载器等通用文件写入工具。即使目标看起来位于普通目录也会拒绝,因为软链接、绑定挂载和路径别名可能把它重定向到系统或块设备。
为防止第二层解析器绕过检查,以下构造也会拒绝:
$()和反引号命令替换。- Shell 变量展开、变量赋值和环境注入。
eval、source、sh -c、bash -c、su -c,以及直接启动 Shell 解释器。busybox、toybox、env、nice、setsid、nohup、chroot、nsenter、run-as等可包装或重新解释命令的入口。- here-doc、here-string、进程替换、子 Shell、命令分组和
{...}花括号展开。 - base64/xxd/openssl 解码后管道到 Shell。
- Python、Perl、Ruby、Node、PHP、Java 等通用解释器或字节码入口。
- Shell 控制流、函数、作业控制、后台
&、通配展开,以及可写目录中的可执行文件。 - 利用
find/xargs/awk system()间接执行高风险操作。
可见的 ;、&&、||、管道和换行会拆成多个段,每一段分别检查;不能用命令链隐藏第二条指令。
策略是有意保守的:即使某条命令在特定场景看似安全,只要它允许隐藏下一层解释或扩大目标集合,也会
直接拒绝,而不是交给客户端确认。
系统写入¶
被识别为系统分区或系统级设置写入的命令,需要同时满足:
- 应用拥有
shell.exec。 - 应用单独拥有
shell.system_write。 - EVCC 车机界面在约 30 秒确认窗口内由用户逐次批准。
超时或用户拒绝都不会执行。授权应用不能静默替用户确认。
系统设置、系统属性、受支持的应用/服务状态操作和进程控制会进入这一确认流程。通用文件系统修改、 输出重定向和永久黑名单中的命令即使同时拥有两个权限也不会显示确认窗口,更不会执行。
执行限制¶
- 命令最大 4096 字符。
EvccShellRequest默认超时 10 秒;服务将请求限制在 1~30 秒。- stdout 与 stderr 的原始合计上限为 256 KiB,随后还会按安全 Binder 预算二次裁剪;发生任一裁剪时
truncated = true。 - 同一 UID 在所有会话中同一时间只允许一个待确认或执行中的 Shell 任务,重复提交返回
RATE_LIMITED。 executeShell()先返回接收状态,最终结果通过onShellCompleted(EvccShellResult)回调。- 等待系统写入确认时返回
PENDING,直接开始执行时返回ACCEPTED。 - 使用
cancel(requestId)可取消待确认窗口,或请求终止正在运行的 Root 进程;运行中取消是尽力而为, 仍应等待最终回调并接受已经发生的副作用无法回滚。
val accepted = client.executeShell(
EvccShellRequest(
requestId = UUID.randomUUID().toString(),
command = "getprop ro.build.version.release",
timeoutMs = 10_000,
)
)
最终 EvccShellResult 提供真实 exitCode、stdout、stderr、truncated 和 durationMs:退出码为 0
时状态为 OK,非 0、超时(通常为 124)或 RootService 执行失败为 FAILED,取消为 CANCELLED。
不要只检查回调是否到达,也不要把 stderr 为空等同于成功。
Demo 不预置危险命令。日志导出默认隐藏 Shell 输入输出,只有用户理解风险并显式选择时才包含原文。