翻译来自:
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) 运行,以增加与系统其余部分的隔离:

在命名空间中运行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的/devdevexec标志。此中间装入命名空间还用于避免将装入泄漏到init装入命名空间中,并在命名空间中的最后一个进程退出时自动清除。这个过程通常是Android的init,但是如果容器无法启动,它也可以是run_oci本身。

仍在中间挂载命名空间内,通过调用clone(2 系统调用来创建容器进程。 CLONE_NEWPIDCLONE_NEWUSER标志。鉴于mount命名空间具有所有者用户命名空间,我们可以转换为两者的唯一方法是同时执行两者。从Linux 3.9开始,CLONE_NEWUSER意味着CLONE_FS,所以这也有使这个新进程不再共享其根目录的副作用(chroot(2))与任何其他过程。

进入容器用户命名空间后,容器进程将使用输入名称空间的其余部分
unshare(2)系统调用,每个命名空间都有相应的标志。在使用CLONE_NEWNS标志执行此操作后,它将进入mount命名空间,该命名空间称为容器安装命名空间。这是绝大多数坐骑发生的地方。由于这与容器用户命名空间相关联,并且此处的进程不再在init用户命名空间中以root身份运行,因此内核不再允许某些操作,即使可能设置了这些功能。一些例子是修改execsuiddev标志的重新安装。

一旦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.imgarc-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/cacheconfig.json将主机的只读目录之一绑定到/data。这个只读和接近空的/data仅用于登录屏幕的“迷你”容器,并且在用户登录Chrome OS之前一直使用。一旦用户登录,arc_setup.ccOnBootContinue()函数卸载只读/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.jsonlinux>resources>devices部分中明确允许的那些。

启动过程

Android 扫描外置sd卡目录_命名空间

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的第二阶段。

参考文献