shim/vendor/github.com/docker/docker/oci/devices_linux.go:85: return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", pathOnHost, err) shim/vendor/github.com/moby/moby/oci/devices_linux.go:85: return devs, devPermissions, fmt.Errorf("error gathering device information while adding custom device %q: %s", pathOnHost, err)
Here is some code I found in another kernel module, which uses the sys_* calls: #include <linux/init.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/delay.h> #include <linux/string.h> #include <linux/syscalls.h> /* Snip */ int openflags = O_WRONLY|O_CREAT; if (ml != 1) openflags |= O_TRUNC; wfd = sys_open(collected, openflags, mode); if (wfd >= 0) { sys_fchown(wfd, uid, gid); sys_fchmod(wfd, mode); state = CopyFile; } Also found: asmlinkage long sys_rename(const char __user *oldname, const char __user *newname); asmlinkage long sys_chmod(const char __user *filename, mode_t mode); asmlinkage long sys_fchmod(unsigned int fd, mode_t mode);
func prepareRootfs(pipe io.ReadWriter, iConfig *initConfig) (err error)
chroot pivotRoot
drivers/char/random.c
func setupDevSymlinks(rootfs string) error { var links = [][2]string{ {"/proc/self/fd", "/dev/fd"}, {"/proc/self/fd/0", "/dev/stdin"}, {"/proc/self/fd/1", "/dev/stdout"}, {"/proc/self/fd/2", "/dev/stderr"}, } // kcore support can be toggled with CONFIG_PROC_KCORE; only create a symlink // in /dev if it exists in /proc. if _, err := os.Stat("/proc/kcore"); err == nil { links = append(links, [2]string{"/proc/kcore", "/dev/kcore"}) } for _, link := range links { var ( src = link[0] dst = filepath.Join(rootfs, link[1]) ) if err := os.Symlink(src, dst); err != nil && !os.IsExist(err) { return fmt.Errorf("symlink %s %s %s", src, dst, err) } } return nil }
// Reads the specified statistics available under /sys/class/net/<EthInterface>/statistics func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) { data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile)) if err != nil { return 0, err } return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) }
runc 启动容器过程分析(附 CVE-2019-5736 实现过程)
环境
OCI runtime spec 地址:https://github.com/opencontainers/runtime-spec
runc 地址:https://github.com/opencontainers/runc/
Commit:f414f497b50a61750ea3af9fccf998a3db687cea
系统版本:Fedora Release 28
内核版本:4.17.9-200.fc28.x86_64
runc 介绍
runc 实现了 OCI 的容器标准,能够管理容器的生命周期。runc 的详细功能请参考 帮助文档。
runc 不是基于 server 形式的,所以所有的配置和状态都会存储在本地文件系统中(以下均为使用 docker 时的默认路径):
- 容器配置:/run/docker/libcontainerd/{cnotainer-id}/config.json
- 容器 init 进程的标准输入输出流:/run/docker/libcontainerd/{cnotainer-id}/{init-stdin,init-stdout,init-stderr}
- 容器状态信息:/run/runc/*/state.json
runc 创建容器时会将状态记录到 state.json 中,所有查询都是从 state.json 中取得容器基本信息,然后再从系统中获取容器实时状态。
docker 的调用链如下:
docker-client -> dockerd -> docker-containerd -> docker-containerd-shim -> runc(容器外) -> runc(容器内) -> containter-entrypoint
runc 启动容器过程
runc 在被 docker-containerd-shim 调用时,参数中会指定容器的配置路径(即 config.json 的位置),同时容器的根路径也已经准备完毕,因此 runc 不会有跟镜像相关的概念。容器的启动过程分析直接从 runc run 开始,即 docker 调用链中的 runc(容器外)这个时间点。
runc(容器外)环境准备
读取 config.json(github.com/opencontainers/runc/run.go#65):
// 读取 config.json
spec, err := setupSpec(context)
if err != nil {
return err
}
// 启动容器
status, err := startContainer(context, spec, CT_ACT_RUN, nil)
if err == nil {
os.Exit(status)
}
return err
startContainer 创建容器信息,并启动(github.com/opencontainers/runc/utils_linux.go#396):
func startContainer(context *cli.Context, spec *specs.Spec, action CtAct, criuOpts *libcontainer.CriuOpts) (int, error) {
// 通过 spec 创建容器结构,在 createContainer 中将 spec 转换为了 runc 的 container config
container, err := createContainer(context, id, spec)
if err != nil {
return -1, err
}
// 构建 runner 启动容器
r := &runner{
// 容器
container: container,
// 即 CT_ACT_RUN
action: action,
// 用于设置 process.Init 字段
init: true,
}
return r.run(spec.Process)
}
r.run() 启动容器(github.com/opencontainers/runc/utils_linux.go#268):
func (r *runner) run(config *specs.Process) (int, error) {
// 根据 config 构建容器进程,此处 r.init 为 true
process, err := newProcess(*config, r.init)
if err != nil {
r.destroy()
return -1, err
}
// 根据 action 调用 container 的对应方法
switch r.action {
case CT_ACT_CREATE:
err = r.container.Start(process)
case CT_ACT_RESTORE:
err = r.container.Restore(process, r.criuOpts)
case CT_ACT_RUN:
// 此处调用的是这个方法
err = r.container.Run(process)
default:
panic("Unknown action")
}
}
container 是由 createContainer() 方法创建,根据创建链路 createContainer() -> loadFactory() -> libcontainer.New() 确认容器由 LinuxFactory.Create() 创建:
// github.com/opencontainers/runc/libcontainer/factory_linux.go#132
func New(root string, options ...func(*LinuxFactory) error) (Factory, error) {
l := &LinuxFactory{
// 指向当前的 exe 程序,即 runc 本身
InitPath: "/proc/self/exe",
// os.Args[0] 是当前 runc 的路径,本质上和 InitPath 是一样的,即 runc init
InitArgs: []string{os.Args[0], "init"},
}
return l, nil
}
// github.com/opencontainers/runc/libcontainer/factory_linux.go#189
func (l *LinuxFactory) Create(id string, config *configs.Config) (Container, error) {
// 创建 linux 容器结构
c := &linuxContainer{
// 容器 ID
id: id,
// 容器状态文件存放目录,默认是 /run/runc/{容器 id}/
root: containerRoot,
// 容器配置
config: config,
// 即 /proc/self/exe,就是 runc
initPath: l.InitPath,
// 即 runc init
initArgs: l.InitArgs,
}
return c, nil
}
所以整个容器的启动逻辑在 linuxContainer.Run() 里,调用链是 linuxContainer.Run() -> linuxContainer.Start() -> linuxContainer.start():
// github.com/opencontainers/runc/libcontainer/container_linux.go#334
func (c *linuxContainer) start(process *Process) error {
// process 是容器的 entrypoint,此处创建的是 entrypoint 的父进程
parent, err := c.newParentProcess(process)
if err != nil {
return newSystemErrorWithCause(err, "creating new parent process")
}
// 启动父进程
if err := parent.start(); err != nil {
// terminate the process to ensure that it properly is reaped.
if err := ignoreTerminateErrors(parent.terminate()); err != nil {
logrus.Warn(err)
}
return newSystemErrorWithCause(err, "starting container process")
}
}
func (c *linuxContainer) newParentProcess(p *Process) (parentProcess, error) {
// 创建用于父子进程通信的 pipe
parentPipe, childPipe, err := utils.NewSockPair("init")
if err != nil {
return nil, newSystemErrorWithCause(err, "creating new init pipe")
}
// 创建父进程的 cmd
cmd, err := c.commandTemplate(p, childPipe)
if err != nil {
return nil, newSystemErrorWithCause(err, "creating new command template")
}
if !p.Init {
// 由于 p.Init 为 true,所以不会执行到这里
return c.newSetnsProcess(p, cmd, parentPipe, childPipe)
}
// 返回标准 init 进程
return c.newInitProcess(p, cmd, parentPipe, childPipe)
}
func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.Cmd, error) {
// 这里可以看到 cmd 就是 runc init
cmd := exec.Command(c.initPath, c.initArgs[1:]...)
cmd.Args[