https://github.com/golang/go/issues/20126

    代码实现 �https://github.com/cyphar/filepath-securejoin

    1. // SecureJoinVFS joins the two given path components (similar to Join) except
    2. // that the returned path is guaranteed to be scoped inside the provided root
    3. // path (when evaluated). Any symbolic links in the path are evaluated with the
    4. // given root treated as the root of the filesystem, similar to a chroot. The
    5. // filesystem state is evaluated through the given VFS interface (if nil, the
    6. // standard os.* family of functions are used).
    7. //
    8. // Note that the guarantees provided by this function only apply if the path
    9. // components in the returned string are not modified (in other words are not
    10. // replaced with symlinks on the filesystem) after this function has returned.
    11. // Such a symlink race is necessarily out-of-scope of SecureJoin.
    12. func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
    13. // Use the os.* VFS implementation if none was specified.
    14. if vfs == nil {
    15. vfs = osVFS{}
    16. }
    17. var path bytes.Buffer
    18. n := 0
    19. for unsafePath != "" {
    20. if n > 255 {
    21. return "", &os.PathError{Op: "SecureJoin", Path: root + "/" + unsafePath, Err: syscall.ELOOP}
    22. }
    23. // Next path component, p.
    24. i := strings.IndexRune(unsafePath, filepath.Separator)
    25. var p string
    26. if i == -1 {
    27. p, unsafePath = unsafePath, ""
    28. } else {
    29. p, unsafePath = unsafePath[:i], unsafePath[i+1:]
    30. }
    31. // Create a cleaned path, using the lexical semantics of /../a, to
    32. // create a "scoped" path component which can safely be joined to fullP
    33. // for evaluation. At this point, path.String() doesn't contain any
    34. // symlink components.
    35. cleanP := filepath.Clean(string(filepath.Separator) + path.String() + p)
    36. if cleanP == string(filepath.Separator) {
    37. path.Reset()
    38. continue
    39. }
    40. fullP := filepath.Clean(root + cleanP)
    41. // Figure out whether the path is a symlink.
    42. fi, err := vfs.Lstat(fullP)
    43. if err != nil && !IsNotExist(err) {
    44. return "", err
    45. }
    46. // Treat non-existent path components the same as non-symlinks (we
    47. // can't do any better here).
    48. // 不存在的路径和 非软链接一样处理
    49. if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 {
    50. path.WriteString(p)
    51. path.WriteRune(filepath.Separator)
    52. continue
    53. }
    54. // Only increment when we actually dereference a link.
    55. n++
    56. // It's a symlink, expand it by prepending it to the yet-unparsed path.
    57. dest, err := vfs.Readlink(fullP)
    58. if err != nil {
    59. return "", err
    60. }
    61. // Absolute symlinks reset any work we've already done.
    62. if filepath.IsAbs(dest) {
    63. path.Reset()
    64. }
    65. // 因为软链接解析过,需要重新解析完整的路径
    66. unsafePath = dest + string(filepath.Separator) + unsafePath
    67. }
    68. // We have to clean path.String() here because it may contain '..'
    69. // components that are entirely lexical, but would be misleading otherwise.
    70. // And finally do a final clean to ensure that root is also lexically
    71. // clean.
    72. fullP := filepath.Clean(string(filepath.Separator) + path.String())
    73. return filepath.Clean(root + fullP), nil
    74. }

    runc在挂载 volumes 时是不允许将软链接挂载至容器中的,因为runc会跟随软链接指向的地址,将宿主机上的目录挂载至容器中。

    因此runc会经过一个 securejoin.SecureJoinVFS() 的函数,先对要挂载的目录进行check,然后再进行mount操作。