https://github.com/golang/go/issues/20126
代码实现 �https://github.com/cyphar/filepath-securejoin
// SecureJoinVFS joins the two given path components (similar to Join) except// that the returned path is guaranteed to be scoped inside the provided root// path (when evaluated). Any symbolic links in the path are evaluated with the// given root treated as the root of the filesystem, similar to a chroot. The// filesystem state is evaluated through the given VFS interface (if nil, the// standard os.* family of functions are used).//// Note that the guarantees provided by this function only apply if the path// components in the returned string are not modified (in other words are not// replaced with symlinks on the filesystem) after this function has returned.// Such a symlink race is necessarily out-of-scope of SecureJoin.func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {// Use the os.* VFS implementation if none was specified.if vfs == nil {vfs = osVFS{}}var path bytes.Buffern := 0for unsafePath != "" {if n > 255 {return "", &os.PathError{Op: "SecureJoin", Path: root + "/" + unsafePath, Err: syscall.ELOOP}}// Next path component, p.i := strings.IndexRune(unsafePath, filepath.Separator)var p stringif i == -1 {p, unsafePath = unsafePath, ""} else {p, unsafePath = unsafePath[:i], unsafePath[i+1:]}// Create a cleaned path, using the lexical semantics of /../a, to// create a "scoped" path component which can safely be joined to fullP// for evaluation. At this point, path.String() doesn't contain any// symlink components.cleanP := filepath.Clean(string(filepath.Separator) + path.String() + p)if cleanP == string(filepath.Separator) {path.Reset()continue}fullP := filepath.Clean(root + cleanP)// Figure out whether the path is a symlink.fi, err := vfs.Lstat(fullP)if err != nil && !IsNotExist(err) {return "", err}// Treat non-existent path components the same as non-symlinks (we// can't do any better here).// 不存在的路径和 非软链接一样处理if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 {path.WriteString(p)path.WriteRune(filepath.Separator)continue}// Only increment when we actually dereference a link.n++// It's a symlink, expand it by prepending it to the yet-unparsed path.dest, err := vfs.Readlink(fullP)if err != nil {return "", err}// Absolute symlinks reset any work we've already done.if filepath.IsAbs(dest) {path.Reset()}// 因为软链接解析过,需要重新解析完整的路径unsafePath = dest + string(filepath.Separator) + unsafePath}// We have to clean path.String() here because it may contain '..'// components that are entirely lexical, but would be misleading otherwise.// And finally do a final clean to ensure that root is also lexically// clean.fullP := filepath.Clean(string(filepath.Separator) + path.String())return filepath.Clean(root + fullP), nil}
runc在挂载 volumes 时是不允许将软链接挂载至容器中的,因为runc会跟随软链接指向的地址,将宿主机上的目录挂载至容器中。
因此runc会经过一个 securejoin.SecureJoinVFS() 的函数,先对要挂载的目录进行check,然后再进行mount操作。
