feat(terminal): add optional chroot container backend alongside proot - #475
feat(terminal): add optional chroot container backend alongside proot#475BUSHIGEMENNI wants to merge 10 commits into
Conversation
There was a problem hiding this comment.
当前请求修改,暂不建议合并。CI 虽然通过,但 chroot 路径仍有两个安全边界问题和数个确定的运行时缺陷:
-
[P1] Agent 提权边界被绕过。
TerminalManager.ensureShellScripts()读取同一个全局container_backend,因此terminal_execute、terminal_session_*和 ACP 进程都会切到 chroot;root 脚本同时以 root 挂载真实/data。但现有AgentToolDefinitions.kt/AgentSystemPrompt.kt明确承诺普通终端固定走 proot,Android 高权限动作应使用需要显式确认的 Shizuku 工具。请切换到独立、明确确认的 root 工具与权限策略,不能由终端 UI 的性能开关静默提权。 -
[P1] 停止/超时不能保证结束 root 命令。
init-host-chroot.sh的外层 shell 启动su -c,而TerminalManager、工具手动停止和 ACP 关闭只销毁ProcessBuilder返回的外层进程。destroyForcibly()不会自动清理其子孙进程,root 命令和挂载命名空间可能在工具已经报告停止后继续存在。请增加 root 侧监督/进程组回收,并覆盖超时、手动停止、ACP close 和后台子进程测试。 -
[P2]
bind_one()对/apex、/dev、/storage、/sys使用普通mount --bind。 普通 bind 不复制子挂载,会遗漏 APEX、/dev/pts和模拟存储。请按目录处理递归挂载,并在真机验证 APEX 路径、PTY 与/storage/emulated/0。 -
[P2]
su环境穿透不完整。 launcher 导出TERMINAL_DISTRIBUTION,但init.sh读取OMNIBOT_TERMINAL_DISTRIBUTION;会过滤环境的su下,Ubuntu rootfs 会按 Alpine 初始化。LocalAcpRuntime传入的动态extraEnvironment也没有进入 launcher。请统一变量并可靠传递动态环境,补 Ubuntu/ACP 回归测试。 -
[P2] launcher 清理存在并发竞争。 每次启动都执行
rm -f "$PREFIX"/local/bin/.chroot-launcher.*,可能删除另一个并发会话已创建、但尚未被su打开的 launcher。请只清理当前调用自己的文件;历史文件使用锁或按年龄回收。 -
[P2]
RootProbe只判断su -c id的退出码。 KernelSU App Profile 可以限制 UID/capabilities;命令退出 0 不等于具备unshare/mount/chroot 能力,而且授权撤销后持久化的 chroot 设置不会在启动时回退。请验证实际 UID/能力,并在每次启动时重新检查、失败则回退 proot。
本地补跑 :core:main:testDebugUnitTest、两份脚本 sh -n 与 git diff --check 均通过;这些检查无法覆盖上述 root 权限、挂载树和并发生命周期问题。
e782a29 to
ec3b7e9
Compare
|
已按 review 完成全部整改:提权隔离、进程组回收、递归挂载、环境穿透、launcher 并发、RootProbe 能力校验。均已单测 + 真机验证通过,请再审。 |
… cross-border mirrors Used -X theirs to take PR omnimind-ai#475's full rewrite of TerminalManager and EnvironmentSetupLogic (they include the chroot publishState / ManagedSessionClient APIs that HEAD lacks), and upstream's cross-border mirror fixes. Conflict on app/build.gradle.kts versionCode resolved in favor of upstream's 1 (monotonic since the master release is on 6). This effectively rolls back the previous manual cherry-pick attempt that missed several files.
…idget, ignore .progress/ - versionCode 1 -> 7 (must exceed 0.5.9 code=6 to upgrade) - versionName 0.5.9 -> 0.5.9 (kept; perf flavor adds -perf suffix) - Pull widgets/embedded_terminal_init_overlay.dart from upstream/main (referenced by app_bootstrap.dart but missing from cherry-pick). - .gitignore: skip .progress/ local artifacts
Flutter Gradle plugin requires Gradle 8.14+ (was forcing error on 8.13). Tencent mirror zip already cached at ~/.gradle/wrapper/dists.
Cherry-picking 8371aec + 43a2c87 with -X theirs dropped the perf flavor that 43a2c87 had added; upstream's main does not have it. Re-apply the perf productFlavor block and the x86_64 abiFilter so the assemblePerfStandardRelease task exists again.
e29b703 to
e000a55
Compare
The ReTerminal Theming section was deleted by the branch reset; this drop in the PR base is unrelated to PR omnimind-ai#475 (chroot backend) and only widens the conflict window. Restore it so the PR diff only carries chroot + cross-border mirror changes.
Base 0.5.9 release already shipped versionCode=6; the prior bump to 7 in the chroot branch only widened the merge conflict window without adding any functionality. Take base value so PR omnimind-ai#475 merges cleanly; a follow-up release commit can bump it back to 7 once merged.
chroot launcher's setgid+2770 keeps *new* files group-writable for the app uid, but historic root:root 0600 files (written before the init.sh umask 002 took effect, or by other tools) stay locked. Each new container start re-runs the chown so the app can always read its own workspace files.
chroot launcher's chown was only applied to OMNIBOT_HOST_WORKSPACE, but local/run/ (chroot pid files) is also written by root and read by the App. Without the chown, App-side ChrootProcessReaper can not remove stale pid files written by the launcher. Extend the self-heal chown to cover local/run/ alongside the workspace. Also rename the inline chown expression into chown_recursive_to_app() so future mount-target self-heal entries (e.g. attachments, browser, skills under workspace) can share the same guard clause without copy-pasting the stat/uid check.
Inside the chroot, root-uid processes (apt, npm install, corepack pnpm)
create symlinks and cache files owner=root. The App then tries to
invoke the resulting binaries (codex-acp, pnpm, ...) and hits
EACCES on the symlink even though the target is world-readable.
Recursively chown /root/{.npm-global,.cache,.config,.local,.dsh,
.codex,.opencode,.claude} to app uid on every container start so
agents can invoke any installed tool without first running a manual
chown pass.
Previously only workspace, local/run/, and the in-rootfs /root dirs were self-healed. Manual chroot exploration by the user (or any root-uid process that writes under \/shared_prefs, databases, code_cache, etc.) would leave root:root 0600 files that the App cannot read until next manual chown. Find every non-app-uid file/dir under \ except the embedded rootfs subtree (\/local/ubuntu), and chown to app uid in one pass. Skips symlinks (their target resolves separately) and only touches files whose current owner is wrong, which keeps the per-start cost bounded by whatever drift accumulated since last run.
背景
proot 用户态模拟存在性能开销;本 PR 新增可选 chroot 后端(需 root 设备),提供真 root 容器体验。
改动
验证
单测全量通过;真机(KernelSU)验证真 root、/data 完整视图、killpg 整组回收、挂载零泄漏。