tags: container,docker,源码分析

docker reexec docker-mountfrom 源码分析

本文中代码均为v20.10.6分支的代码。

1. 简介

docker-mountfromdockerd 的一个“子命令”,用于实现对相对目录的挂载。

2. 使用

2.1 代码使用

参考 docker reexec源码分析

2.2 二进制使用

除了通过以类似fork的形式在代码中使用,docker-mountfrom 也可以通过二进制的形式使用,但这种形式不常见。这种使用方式的价值在于能方便得直接进入 docker-mountfrom 的逻辑,在分析其实现、或研究相关的安全问题时,比较方便、直观。

dockerd 复制一份,或建立软链接,得到名为 docker-mountfrom 的文件,dockerddocker-mountfrom 的文件内容相同,通过执行时的不同的cmdline,进入不同的代码逻辑。

ln -s /usr/bin/dockerd /usr/bin/docker-mountfrom

docker-mountfrom的stdin是json格式的配置选项,arg1是相对路径所在的目录。

echo '{"device":"shm","target":"dst","type":"tmpfs","label":"", "flag":0}' > /tmp/json
mkdir /tmp/dst
docker-mountfrom < /tmp/json /tmp

其中,配置选项的具体格式为:

type mountOptions struct {
	Device string
	Target string
	Type   string
	Label  string
	Flag   uint32
}

3. 实现分析

docker-mountfrom命令被注册到 mountFromMain 函数。

https://github.com/moby/moby/blob/master/daemon/graphdriver/overlay2/mount.go#L18-L20

func init() {
    reexec.Register("docker-mountfrom", mountFromMain)
}

可以看到,该命令的第一个参数为一个路径,会chroot到该目录;其余参数由stdin中获取,传递给unix.Mount系统调用。

https://github.com/moby/moby/blob/master/daemon/graphdriver/overlay2/mount.go#L71-L90

func mountFromMain() {
    ...
    var options *mountOptions

    if err := json.NewDecoder(os.Stdin).Decode(&options); err != nil {
        fatal(err)
    }

    if err := os.Chdir(flag.Arg(0)); err != nil {
        fatal(err)
    }

    if err := unix.Mount(options.Device, options.Target, options.Type, uintptr(options.Flag), options.Label); err != nil {
        fatal(err)
    }

    os.Exit(0)
}

目前(v20.10.14), docker-mountfrom命令只在mountFrom函数中被调用。

https://github.com/moby/moby/blob/master/daemon/graphdriver/overlay2/mount.go#L35

mount的参数形式为mountOptions结构体,以json格式传递至mountFrom命令的stdin中。

func mountFrom(dir, device, target, mType string, flags uintptr, label string) error {
    options := &mountOptions{
        Device: device,
        Target: target,
        Type:   mType,
        Flag:   uint32(flags),
        Label:  label,
    }

    cmd := reexec.Command("docker-mountfrom", dir)
    w, err := cmd.StdinPipe()
    if err != nil {
        return fmt.Errorf("mountfrom error on pipe creation: %v", err)
    }

    output := bytes.NewBuffer(nil)
    cmd.Stdout = output
    cmd.Stderr = output
    if err := cmd.Start(); err != nil {
        w.Close()
        return fmt.Errorf("mountfrom error on re-exec cmd: %v", err)
    }
    // write the options to the pipe for the untar exec to read
    if err := json.NewEncoder(w).Encode(options); err != nil {
        w.Close()
        return fmt.Errorf("mountfrom json encode to pipe failed: %v", err)
    }
    w.Close()

    if err := cmd.Wait(); err != nil {
        return fmt.Errorf("mountfrom re-exec error: %v: output: %v", err, output)
    }
    return nil
}