package daemon // import "github.com/docker/docker/daemon" import ( "bufio" "context" "fmt" "io" "net" "os" "regexp" "strings" "sync" "github.com/containerd/log" "github.com/docker/docker/daemon/config" "github.com/docker/docker/libnetwork/ns" "github.com/docker/docker/libnetwork/resolvconf" "github.com/moby/sys/mount" "github.com/moby/sys/mountinfo" "github.com/pkg/errors" "github.com/vishvananda/netlink" "golang.org/x/sys/unix" ) // On Linux, plugins use a static path for storing execution state, // instead of deriving path from daemon's exec-root. This is because // plugin socket files are created here and they cannot exceed max // path length of 108 bytes. func getPluginExecRoot(_ *config.Config) string { return "/run/docker/plugins" } func (daemon *Daemon) cleanupMountsByID(id string) error { log.G(context.TODO()).Debugf("Cleaning up old mountid %s: start.", id) f, err := os.Open("/proc/self/mountinfo") if err != nil { return err } defer f.Close() return daemon.cleanupMountsFromReaderByID(f, id, mount.Unmount) } func (daemon *Daemon) cleanupMountsFromReaderByID(reader io.Reader, id string, unmount func(target string) error) error { if daemon.root == "" { return nil } var errs []string regexps := getCleanPatterns(id) sc := bufio.NewScanner(reader) for sc.Scan() { if fields := strings.Fields(sc.Text()); len(fields) > 4 { if mnt := fields[4]; strings.HasPrefix(mnt, daemon.root) { for _, p := range regexps { if p.MatchString(mnt) { if err := unmount(mnt); err != nil { log.G(context.TODO()).Error(err) errs = append(errs, err.Error()) } } } } } } if err := sc.Err(); err != nil { return err } if len(errs) > 0 { return fmt.Errorf("Error cleaning up mounts:\n%v", strings.Join(errs, "\n")) } log.G(context.TODO()).Debugf("Cleaning up old mountid %v: done.", id) return nil } // cleanupMounts umounts used by container resources and the daemon root mount func (daemon *Daemon) cleanupMounts(cfg *config.Config) error { if err := daemon.cleanupMountsByID(""); err != nil { return err } info, err := mountinfo.GetMounts(mountinfo.SingleEntryFilter(daemon.root)) if err != nil { return errors.Wrap(err, "error reading mount table for cleanup") } if len(info) < 1 { // no mount found, we're done here return nil } // `info.Root` here is the root mountpoint of the passed in path (`daemon.root`). // The ony cases that need to be cleaned up is when the daemon has performed a // `mount --bind /daemon/root /daemon/root && mount --make-shared /daemon/root` // This is only done when the daemon is started up and `/daemon/root` is not // already on a shared mountpoint. if !shouldUnmountRoot(daemon.root, info[0]) { return nil } unmountFile := getUnmountOnShutdownPath(cfg) if _, err := os.Stat(unmountFile); err != nil { return nil } log.G(context.TODO()).WithField("mountpoint", daemon.root).Debug("unmounting daemon root") if err := mount.Unmount(daemon.root); err != nil { return err } return os.Remove(unmountFile) } func getCleanPatterns(id string) (regexps []*regexp.Regexp) { var patterns []string if id == "" { id = "[0-9a-f]{64}" patterns = append(patterns, "containers/"+id+"/mounts/shm", "containers/"+id+"/shm") } patterns = append(patterns, "overlay2/"+id+"/merged$", "zfs/graph/"+id+"$") for _, p := range patterns { r, err := regexp.Compile(p) if err == nil { regexps = append(regexps, r) } } return } func shouldUnmountRoot(root string, info *mountinfo.Info) bool { if !strings.HasSuffix(root, info.Root) { return false } return hasMountInfoOption(info.Optional, sharedPropagationOption) } // setupResolvConf sets the appropriate resolv.conf file if not specified // When systemd-resolved is running the default /etc/resolv.conf points to // localhost. In this case fetch the alternative config file that is in a // different path so that containers can use it // In all the other cases fallback to the default one func setupResolvConf(config *config.Config) { if config.ResolvConf != "" { return } config.ResolvConf = resolvconf.Path() } // ifaceAddrs returns the IPv4 and IPv6 addresses assigned to the network // interface with name linkName. // // No error is returned if the named interface does not exist. func ifaceAddrs(linkName string) (v4, v6 []*net.IPNet, err error) { nl := ns.NlHandle() link, err := nl.LinkByName(linkName) if err != nil { if !errors.As(err, new(netlink.LinkNotFoundError)) { return nil, nil, err } return nil, nil, nil } get := func(family int) ([]*net.IPNet, error) { addrs, err := nl.AddrList(link, family) if err != nil { return nil, err } ipnets := make([]*net.IPNet, len(addrs)) for i := range addrs { ipnets[i] = addrs[i].IPNet } return ipnets, nil } v4, err = get(netlink.FAMILY_V4) if err != nil { return nil, nil, err } v6, err = get(netlink.FAMILY_V6) if err != nil { return nil, nil, err } return v4, v6, nil } var ( kernelSupportsRROOnce sync.Once kernelSupportsRROErr error ) func kernelSupportsRecursivelyReadOnly() error { fn := func() error { tmpMnt, err := os.MkdirTemp("", "moby-detect-rro") if err != nil { return fmt.Errorf("failed to create a temp directory: %w", err) } for { err = unix.Mount("", tmpMnt, "tmpfs", 0, "") if !errors.Is(err, unix.EINTR) { break } } if err != nil { return fmt.Errorf("failed to mount tmpfs on %q: %w", tmpMnt, err) } defer func() { var umErr error for { umErr = unix.Unmount(tmpMnt, 0) if !errors.Is(umErr, unix.EINTR) { break } } if umErr != nil { log.G(context.TODO()).WithError(umErr).Warnf("Failed to unmount %q", tmpMnt) } }() attr := &unix.MountAttr{ Attr_set: unix.MOUNT_ATTR_RDONLY, } for { err = unix.MountSetattr(-1, tmpMnt, unix.AT_RECURSIVE, attr) if !errors.Is(err, unix.EINTR) { break } } // ENOSYS on kernel < 5.12 if err != nil { return fmt.Errorf("failed to call mount_setattr: %w", err) } return nil } kernelSupportsRROOnce.Do(func() { kernelSupportsRROErr = fn() }) return kernelSupportsRROErr } func supportsRecursivelyReadOnly(cfg *configStore, runtime string) error { if err := kernelSupportsRecursivelyReadOnly(); err != nil { return fmt.Errorf("rro is not supported: %w (kernel is older than 5.12?)", err) } if runtime == "" { runtime = cfg.Runtimes.Default } features := cfg.Runtimes.Features(runtime) if features == nil { return fmt.Errorf("rro is not supported by runtime %q: OCI features struct is not available", runtime) } for _, s := range features.MountOptions { if s == "rro" { return nil } } return fmt.Errorf("rro is not supported by runtime %q", runtime) }