翻译来自:
Android container in Chrome OS
https://chromium.googlesource.com/chromiumos/platform2/+/master/arc/container-bundle/README.md
本文档概述了Android在Chrome OS中的Linux容器中运行的过程。
除非另有说明,否则本文档将说明Android主机容器的工作原理。 N的容器可以以稍微不同的方式工作。
config.json
run_oci 使用 config.json
来描述容器的设置方式。此文件描述了要创建的安装结构,名称空间,设备节点,cgroups配置以及继承的功能。
命名空间
Android正在使用所有可用的Linux namespaces(7) 运行,以增加与系统其余部分的隔离:
- cgroup_namespaces(7)
- IPC(用于System V IPC)
- mount_namespaces(7)
- network_namespaces(7)
- pid_namespaces(7)
- user_namespaces(7)
- UTS(主机名和域名)
在命名空间中运行Android的所有用户空间也会增加兼容性,因为我们可以为它提供一个更接近正常情况下预期的环境。
run_oci
在init命名空间(与大多数Chrome OS共享)中启动,作为具有所有功能的真正根目录运行。与之关联的mount命名空间称为 init mount namespace。在init mount命名空间中执行的任何挂载都将跨越用户会话,并在run_oci
启动之前执行,因此它们不会出现在config.json
中。
首先,run_oci
创建一个mount命名空间(同时仍与init的用户命名空间相关联),称为 中间挂载命名空间。
由于当它在这个命名空间中运行时它仍然具有init命名空间中的所有root功能,它可以执行特权操作,例如执行重新安装(例如,使用MS_REMOUNT
调用mount(2)
和
没有MS_BIND
),并要求将tmpfs(5) 安装到Android的/dev
中dev
和exec
标志。此中间装入命名空间还用于避免将装入泄漏到init装入命名空间中,并在命名空间中的最后一个进程退出时自动清除。这个过程通常是Android的init,但是如果容器无法启动,它也可以是run_oci
本身。
仍在中间挂载命名空间内,通过调用clone(2 系统调用来创建容器进程。 CLONE_NEWPID
和CLONE_NEWUSER
标志。鉴于mount命名空间具有所有者用户命名空间,我们可以转换为两者的唯一方法是同时执行两者。从Linux 3.9开始,CLONE_NEWUSER
意味着CLONE_FS
,所以这也有使这个新进程不再共享其根目录的副作用(chroot(2))与任何其他过程。
进入容器用户命名空间后,容器进程将使用输入名称空间的其余部分
unshare(2)系统调用,每个命名空间都有相应的标志。在使用CLONE_NEWNS
标志执行此操作后,它将进入mount命名空间,该命名空间称为容器安装命名空间。这是绝大多数坐骑发生的地方。由于这与容器用户命名空间相关联,并且此处的进程不再在init用户命名空间中以root身份运行,因此内核不再允许某些操作,即使可能设置了这些功能。一些例子是修改exec
,suid
,dev
标志的重新安装。
一旦run_oci
完成设置容器进程并调用exit(2)来守护容器进程树,系统中不再有任何直接引用中间装入命名空间的进程,因此无法再从任何位置访问它。这意味着无法获得可以按顺序传递给setns(2)的文件描述符输入它。命名空间本身仍然存在,因为它是容器装入命名空间的父级。
用户名称空间
通过以下方式为用户命名空间分配2,000,000个uid:
init namespace uid range | container namespace uid range |
655360 - 660359 | 0 - 4999 |
600 - 649 | 5000 - 5049 |
660410 - 2655360 | 5050 - 2000000 |
第二个范围将Chrome OS守护程序uids(600-649)映射到Android的 OEM特定AID 之一范围。
类似地,gid的分配方式与uids赋值相同,除了为容器gid 1065分配特殊gid 20119,这是Android的保留gid。这个例外是因为ext4 resgid 只接受16位gid,因此最初映射的gid 1065 + 655360不适合ext4回复了。
根据上述规则,特殊的GID 5005(内部容器,也称为vendor_arc_debugfs)映射到GID 605(外部容器,也称为debugfs-access)。此GID作为init进程的补充组添加,以允许对/sys/kernel/debug/tracing/(在开发模式下)中的某些跟踪文件进行写访问。
网络命名空间
Mounts
有几种方法可以在容器中安装资源:
*循环安装:用于将文件系统映像安装到文件系统中。 Android使用其中两个:一个用于system.raw.img
,另一个用于vendor.raw.img
。
*绑定挂载:这些使得文件或目录可以从另一个子目录中看到,并且可以跨越chroot(2)和 pivot_root(2)。
*共享挂载:这些挂载在init mount命名空间中使用mount(2)的MS_SHARED
标志,容器装入命名空间中的“MS_SLAVE”,它会导致该装载点下的任何装载更改传播到其他共享子树。
在Android上,它的init的第一个阶段设置了几个挂载点,如/dev
和/mnt
,但ARC根本不使用init的功能。相反,在ARC上,config.json
设置了这些标准的Android挂载点以及
几个ARC特定的。
所有安装都在/opt/google/container/android/rootfs/root
子树中执行。鉴于run_oci
不修改init mount命名空间,任何跨越用户会话的安装(例如system.raw.img
循环安装)应该在run_oci
启动之前执行。这通常由arc-setup 处理。
mounts
部分的标志是mount(8)理解的标志。请注意,一个挂载条目可能会成为多次调用mount(2)
,因为内核会忽略某些标志组合(例如,挂载传播标志的更改会忽略所有其他标志)。
容器装入命名空间中可见的装入列表
-
/
:这是/opt/google/containers/android/system.raw.img
由arc-setup
循环安装(从/etc/init/arc-system-mount.conf
调用)在init命名空间中。这跨越了容器调用,因为它是无状态的。exec
/suid
标志被添加到中间装入命名空间中,并递归地将其传播标志改为MS_SLAVE
。 -
/config/sdcardfs
:由esdfs
创建的普通configfs
的/sys/kernel/config/sdcardfs
子目录的绑定。
*/dev
:这是一个tmpfs
安装在中间挂载命名空间中,android-root
作为所有者。这是获取dev
/exec
挂载标志所必需的。 -
/dev/pts
:Pseudo TTS使用命名空间支持来保护文件系统,使其与父命名空间位于不同的命名空间,即使设备节点ID看起来相同。仿生CTS测试需要。该设备安装了nosuid和noexec挂载选项,以提高安全性,尽管Android不使用它们。 -
/dev/ptmx
:devpts的内核文档表明有两种方法可以支持/dev/ptmx
:创建指向/dev/pts/ptmx
或绑定挂载/dev/pts/ptmx
的符号链接。选择bind-mount来标记它:u:object_r:ptmx_device:s0`。 -
/dev/kmsg
:这是主机的/run/arc/android.kmsg.fifo
的绑定挂载,它只是一个FIFO文件。写入假设备的日志由名为arc-kmsg-logger
的作业读取,并存储在主机的/var/log/android.kmsg中。 -
/dev/socket
:这是一个普通的tmpfs
,由Android的init
用来存储套接字文件。 -
/dev/usb-ffs/adb
:这是主机的/run/arc/adbd
的绑定挂载,是一个从挂载,它包含一个FIFO,作为通过ConfigFS/FunctionFS配置的ADB小工具。此文件仅出现在开发者模式中。写入/dev/usb-ffs/adb/ep0
文件后,批量输入和批量输出端点将被绑定挂载到同一目录中。 -
/data
和/data/cache
:config.json
将主机的只读目录之一绑定到/data
。这个只读和接近空的/data
仅用于登录屏幕的“迷你”容器,并且在用户登录Chrome OS之前一直使用。一旦用户登录,arc_setup.cc
的OnBootContinue()
函数卸载只读/data
,然后bind-mounts/home/root/$ {HASH}/android-data/{数据,缓存}分别为
/data和
/data/cache`。这些源目录是可写的,并且位于由加密家管理的Chrome OS用户加密目录中。 -
/var/run/arc
:一个tmpfs
,它包含来自其他容器的几个挂载点,用于Chrome <=> Android文件系统通信,例如dlfs
,OBB和外部存储。 -
/var/run/arc/sdcard
:在容器外部运行的sdcard
守护程序提供的FUSE文件系统。 -
/var/run/chrome
:保存ARC桥和Wayland UNIX域套接字。 -
/var/run/cras
:保存CRAS UNIX域套接字。 -
/var/run/inputbridge
:保存FIFO以在容器内执行IPC。 surfaceflinger使用FIFO来支持从主机到容器的输入事件。 -
/sys
:一个普通的sysfs
。 -
/sys/fs/selinux
:这是从容器外的/sys/fs/selinux
绑定安装的。 -
/sys/kernel/debug
:由于这个目录由具有非常严格权限的真实root拥有(因此容器无法访问该目录中的任何资源),因此在其位置安装了tmpfs
。 -
/sys/kernel/debug/sync
:放宽主机中该目录的权限,以便android-root
可以访问它,并绑定挂载在容器中。 -
/sys/kernel/debug/tracing
:这是从主机的/run/arc/debugfs/tracing绑定挂载的,仅在开发模式下。请注意,组ID映射到容器中以允许DAC从内部进行访问。 -
/proc
:正常的proc
fs。它安装在容器装入命名空间中,该命名空间与容器用户+ pid命名空间相关联,以显示正确的PID映射。 -
/proc/cmdline
:带有运行时生成的内核命令行的常规文件是绑定安装的,而不是Chrome OS内核命令行。 -
/proc/sys/vm/mmap_rnd_compat_bits
,/proc/sys/vm/mmap_rnd_bits
:两个常规文件是绑定挂载的,因为原始文件由真正的root拥有,具有非常严格的权限。 Android的init
修改了这些文件的内容以增加mmap(2)熵,并且如果不允许此操作。挂载这两个文件会将mod的数量减少到init
。 -
/proc/sys/kernel/kptr_restrict
:与/proc/sys/vm/mmap_rnd_bits
相同。 -
/oem/etc
:这是从主机的/run/arc/oem/etc
绑定挂起并保存platform.xml
文件。 -
/var/run/arc/bugreport
:这是从主机的/run/arc/bugreport
绑定安装的。容器在目录中创建一个管道文件,以允许主机的debugd
读取它。当它被读取时,Android的bugreport
输出被发送到主机端。 -
/var/run/arc/apkcache
:这是从主机的`/mnt/stateful_partition/unencrypted/apkcache绑定挂载的。主机目录用于存储由设备策略指定并在主机端下载的APK文件。 -
/var/run/arc/dalvik-cache
:这是从主机的/mnt/stateful_partition/unencrypted/art-data/dalvik-cache
进行绑定安装的。主机目录用于存储在主机端编译的boot * .art文件。这允许容器立即加载文件而不构建它们。 -
/var/run/camera
:保存arc-camera UNIX域套接字。 -
/var/run/arc/obb
:这是从主机的/run/arc/obb
绑定挂载的。在容器外部运行的名为/usr/bin/arc-obb-mounter
的守护程序在请求时将OBB映像文件作为FUSE文件系统挂载到目录。 -
/var/run/arc/media
:这是从主机的/run/arc/media
绑定安装的。在容器外部运行的名为/usr/bin/mount-passthrough
的守护程序在需要时将外部存储作为FUSE文件系统安装到目录中。 -
/vendor
:这是从主机的/opt/google/containers/android/vendor.raw.img
循环安装的。该目录可能包含图形驱动程序,Houdini,特定于板的APK等。 -
/mnt
:这是一个tmpfs挂载点。请注意,我们不应该在config.json
中的/mnt中挂载任何其他文件系统,即使它是一个空的tmpfs。这样做会使我们的容器与Android的兼容性降低,甚至可能破坏CTS。
能力
Android在用户命名空间中运行,命名空间中的root
用户具有该命名空间中的所有可能功能。尽管如此,内核中仍有一些操作可以在init命名空间中对用户执行功能检查。以这种方式完成所有检查的所有功能(例如CAP_SYS_MODULE
)都将被删除,因为容器中的任何用户都无法使用它。
此外,删除了以下功能(通过从允许的,可继承的,有效的和环境的功能集列表中删除它们)来通知容器它无法执行某些操作:
*CAP_SYS_BOOT
:这表示Android的init
进程不应该使用reboot(2)
,而是调用exit(2)
。它还用于决定是否阻止SIGTERM
信号,该信号可用于请求容器从外部终止自身。
*CAP_SYSLOG
:这表示Android无法访问/proc/kallsyms
中的内核指针。
Cgroups
默认情况下,不允许在容器内运行的进程访问任何设备文件。他们只能访问config.json
的linux
>resources
>devices
部分中明确允许的那些。
启动过程
Hooks
run_oci
使用的钩子遵循POSIX-platform Hooks的Open Container Initiative规范,使用Chrome OS特定的扩展,允许在处理完所有挂载之后安装挂钩,但在调用chroot(2)之前/chroot.2.html)。
所有的钩子都是通过调用fork(2)+ execve(2)来运行的,来自run_oci
进程(它是容器进程的父进程),并位于中间装入命名空间内。
为了避免为创建多个进程和在命名空间之间来回切换(在天真地完成时为引导时间增加几毫秒)付出代价,我们将所有挂钩执行合并到两个挂钩:pre-create和pre- chroot环境。
预创建钩子使用--mode = setup
标志调用arc-setup并创建主机将通过config.json
绑定到容器的文件和目录。
pre-chroot hook使用--mode = pre-chroot
标志调用arc-setup执行几个操作:
- 设置binfmt_misc以在英特尔设备上执行ARM二进制转换。
- 恢复由
run_oci
创建的几个文件和目录的SELinux上下文,因为这些文件和目录不是由构建系统处理,也不是在调用run_oci
之前首次调用arc-setup
。 - 触摸
/dev/.coldboot_done
,它被Android用作在启动序列期间已达到某一点的信号。这通常是由Android的init
在第一阶段完成的,但是我们不使用它并直接启动Android进入init
的第二阶段。
参考文献
- 共享子树内核文档
- ARC M/N文件系统设计文档