> For the complete documentation index, see [llms.txt](https://gitbook.cdxiaodong.life/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gitbook.cdxiaodong.life/rong-qi-an-quan/gai-shu/basics.md).

# 容器基础

\*\*容器（Container）\*\*是一个具有特殊属性的任务或任务集合，用于隔离任务并限制对系统资源的访问。

![container](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-f5cf6b923840b5c22b0447edaaef979c9ec24b9a%2Fcontainer.png?alt=media)

## 进程描述符和任务结构

内核将进程列表存储在一个称为\*\*任务列表（task list）\*\*的循环双向链表中。

![task-list](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-37aaaba1ca5e36d577b221fc197bf5ff11967393%2Ftask-list.png?alt=media)

任务列表中的每个元素都是`struct task_struct`类型的进程描述符。进程描述符包含特定进程的所有信息。

![task-struct](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-bb4cf95260cb5511d67d1a41d23867162512b331%2Ftask-struct.png?alt=media)

### /proc

![proc](https://drawings.jvns.ca/drawings/proc.jpeg)

`/proc`是一个特殊的文件系统挂载（procfs），通过读取"文件"条目直接从内核访问系统和进程信息。

![task-struct-example](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-9e762a843d0052b637ab7cc018bd7e8a30033304%2Ftask-struct-example.png?alt=media)

## 属性

### 凭证（Credentials）

[凭证（Credentials）](https://man7.org/linux/man-pages/man7/credentials.7.html)是进程标识符。凭证描述任务的用户身份，该身份确定其对共享资源（如文件、信号量和共享内存）的权限。

任务的所有凭证都保存在一个名为`struct cred`的引用计数结构中。每个任务通过其`task_struct`中的`cred`指针指向其凭证。

传统UNIX权限实现区分两类：

* 用户ID为0（root）的特权进程
* 所有其他进程

### 权能（Capabilities）

从内核2.2开始，Linux将与超级用户关联的权限分解为称为[权能（Capabilities）](https://man7.org/linux/man-pages/man7/capabilities.7.html)的不同单元。

Linux权能的引入是为了将root的角色分解为离散的子部分，这些子部分可以授予非root进程，使其能够执行特权操作。

![capabilities](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-21de1e8bf89e1ee49a2641b9eeffcf1e3090511c%2Fcapabilities.png?alt=media)

进程具有权能"允许集合"的概念，该集合充当其可能拥有的权能的限制性超集。重要的是，默认情况下，这个边界集合会传递给任何子进程，因此容器的"init"进程为容器内的所有进程创建了一个限制性权能集合（因为所有进程都从PID 1派生）。

容器是应该以受限权能集合运行的任务。

![restricted-capabilities](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-119dfbc924ee75b1ccfa8958d694aa26c208d096%2Frestricted-capabilities.png?alt=media)

要查看权能列表，可以使用`capsh`：

```bash
$ capsh --print
```

### 文件系统

容器的文件系统根目录通常通过[pivot\_root](https://man7.org/linux/man-pages/man2/pivot_root.2.html)系统调用与其他容器和主机的根文件系统隔离。

容器的根挂载通常植根于容器专用文件系统，如AUFS或OverlayFS。在OverlayFS的情况下，容器的`/`根目录实际上位于`/var/lib/docker/overlay2`中。

![overlayfs-root-path](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-1e7da2bd450a269fde4e8bc5ad72cff154678c21%2Foverlayfs-root-path.png?alt=media)

### 命名空间（Namespaces）

[命名空间（Namespace）](https://man7.org/linux/man-pages/man7/namespaces.7.html)将全局系统资源包装在抽象中，使得命名空间内的进程看起来拥有自己的全局资源隔离实例。对全局资源的更改对命名空间的其他成员进程可见，但对其他进程不可见。

Linux上有8种类型的命名空间。

| 命名空间    | 隔离内容                                                                                                              |
| ------- | ----------------------------------------------------------------------------------------------------------------- |
| Cgroup  | Cgroup根目录                                                                                                         |
| IPC     | 提供SystemV IPC和POSIX消息队列的命名空间版本                                                                                    |
| Network | 提供命名空间和隔离的网络栈。大多数容器用例涉及网络服务，因此这将成为容器的核心功能                                                                         |
| Mount   | 提供挂载点的命名空间视图。结合[pivot\_root()](https://man7.org/linux/man-pages/man2/pivot_root.2.html)系统调用，这将用于隔离容器的文件系统与主机的文件系统 |
| PID     | 提供进程ID（PID）的命名空间树。这允许每个容器拥有完整的隔离进程树，其中它有一个'init'进程，在该命名空间内作为PID 1运行。在容器中运行的进程在主机上的PID与在容器PID命名空间内的PID不同           |
| Time    | 启动和单调时钟                                                                                                           |
| User    | 提供用户ID（UID）和组ID（GID）的命名空间版本。这是现代容器系统最重要的功能之一，因为它用于提供"非特权容器"。这些容器中，容器内的root（UID 0）在容器外部不是root，大大提高了容器的安全性          |
| UTS     | 提供系统标识符的命名空间版本                                                                                                    |

![namespace-user-example](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-4c87e54a3316877283fc3c087a32369f564e1c51%2Fnamespace-user-example.png?alt=media)

在大多数情况下，使用[clone(2)](https://man7.org/linux/man-pages/man2/clone.2.html)和[unshare(2)](https://man7.org/linux/man-pages/man2/unshare.2.html)创建新命名空间需要`CAP_SYS_ADMIN`权能。用户命名空间是例外：从Linux 3.8开始，创建用户命名空间不需要特权。

每个进程都有一个`/proc/[pid]/ns/`子目录，为每个命名空间包含一个条目。

参考资料：

* [Quarkslab博客：深入Linux命名空间 - 第1部分](https://blog.quarkslab.com/digging-into-linux-namespaces-part-1.html)
* [Quarkslab博客：深入Linux命名空间 - 第2部分](https://blog.quarkslab.com/digging-into-linux-namespaces-part-2.html)

### Cgroups

[Cgroups](https://man7.org/linux/man-pages/man7/cgroups.7.html)是Linux控制组。

控制组（通常称为cgroups）是一个Linux内核功能，允许将进程组织到层次组中，然后可以限制和监控这些组对各种类型资源的使用。内核的cgroup接口通过称为**cgroupfs**的伪文件系统提供，其中层次结构通过每个挂载中的目录树表达。

![cgroupfs](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-a6a6bb622163854552605a98b8b947286b3399b4%2Fcgroupfs.png?alt=media)

分组在核心cgroup内核代码中实现，而资源跟踪和限制在一组按资源类型的子系统（内存、CPU等）中实现。

cgroup文件系统最初包含一个根cgroup `/`，所有进程都属于它。通过在cgroup文件系统中创建目录来创建新的cgroup。

```bash
$ mkdir /sys/fs/cgroup/cpu/cg1
```

这创建了一个新的空cgroup。可以通过将其PID写入cgroup的`cgroup.procs`文件将进程移动到此cgroup：

```bash
$ echo $$ > /sys/fs/cgroup/cpu/cg1/cgroup.procs
```

### Linux安全模块

[AppArmor](https://apparmor.net/)和[SELinux](https://www.redhat.com/en/topics/linux/what-is-selinux)是Linux安全模块，提供强制访问控制（MAC），其中程序的访问规则由配置文件描述。

Docker和LXC在强制模式下启用默认的LSM配置文件，主要用于限制容器对敏感的`/proc`和`/sys`条目的访问。

该配置文件还拒绝mount系统调用。

### seccomp

从内核3.17开始，Linux有一种通过[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html)子系统过滤系统调用访问的机制。

![seccomp](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-45395bd53959cbafda79981f6b87f9a599095e23%2Fseccomp.png?alt=media)

Seccomp策略有两个版本：

* **严格模式（Strict mode）** - 一小部分允许的系统调用，无法自定义
* **过滤模式（Filter mode）** - 系统调用过滤器编写为伯克利数据包过滤器（BPF）程序；这允许对系统调用使用设置更细粒度的策略（有一些注意事项，seccomp-bpf过滤器可以检查系统调用参数，但无法解引用指针）

## 容器安全模型

![container-security-model](https://2241975275-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7lonY2zgW7DNoIQMqYtw%2Fuploads%2Fgit-blob-dd92d4ec92d30334afd812e058e5f6cb8871729a%2Fcontainer-security-model.png?alt=media)

## 参考资料

* [容器逃逸汇编](https://capsule8.com/assets/ug/us-19-Edwards-Compendium-Of-Container-Escapes.pdf)
* [滥用特权和非特权Linux容器](https://www.nccgroup.com/globalassets/our-research/us/whitepapers/2016/june/container_whitepaper.pdf)
* [理解和强化Linux容器](https://research.nccgroup.com/wp-content/uploads/2020/07/ncc_group_understanding_hardening_linux_containers-1-1.pdf)
