docker reexec docker-mountfrom 源码分析
tags: container,docker,源码分析
docker reexec docker-mountfrom 源码分析
本文中代码均为v20.10.6分支的代码。
1. 简介
docker-mountfrom
是 dockerd
的一个“子命令”,用于实现对相对目录的挂载。
2. 使用
2.1 代码使用
2.2 二进制使用
除了通过以类似fork的形式在代码中使用,docker-mountfrom
也可以通过二进制的形式使用,但这种形式不常见。这种使用方式的价值在于能方便得直接进入 docker-mountfrom
的逻辑,在分析其实现、或研究相关的安全问题时,比较方便、直观。
将 dockerd
复制一份,或建立软链接,得到名为 docker-mountfrom
的文件,dockerd
与 docker-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
}