moby--moby/pkg/archive/archive_unix.go

118 lines
3.2 KiB
Go
Raw Permalink Normal View History

//go:build !windows
// +build !windows
package archive // import "github.com/docker/docker/pkg/archive"
import (
"archive/tar"
"errors"
"os"
"path/filepath"
"strings"
"syscall"
"github.com/containerd/containerd/pkg/userns"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/system"
"golang.org/x/sys/unix"
)
Remove local fork of archive/tar package A copy of Go's archive/tar packge was vendored with a patch applied to mitigate CVE-2019-14271. Vendoring standard library packages is not supported by Go in module-aware mode, which is getting in the way of maintenance. A different approach to mitigate the vulnerability is needed which does not involve vendoring parts of the standard library. glibc implements name service lookups such as users, groups and DNS using a scheme known as Name Service Switch. The services are implemented as modules, shared libraries which glibc dynamically links into the process the first time a function requiring the module is called. This is the crux of the vulnerability: if a process linked against glibc chroots, then calls one of the functions implemented with NSS for the first time, glibc may load NSS modules out of the chrooted filesystem. The API underlying the `docker cp` command is implemented by forking a new process which chroots into the container's rootfs and writes a tar stream of files from the container over standard output. It utilizes the Go standard library's archive/tar package to write the tar stream. It makes use of the tar.FileInfoHeader function to construct a tar.Header value from an fs.FileInfo value. In modern versions of Go on *nix platforms, FileInfoHeader will attempt to resolve the file's UID and GID to their respective user and group names by calling the os/user functions LookupId and LookupGroupId. The cgo implementation of os/user on *nix performs lookups by calling the corresponding libc functions. So when linked against glibc, calls to tar.FileInfoHeader after the process has chrooted into the container's rootfs can have the side effect of loading NSS modules from the container! Without any mitigations, a malicious container image author can trivially get arbitrary code execution by leveraging this vulnerability and escape the chroot (which is not a sandbox) into the host. Mitigate the vulnerability without patching or forking archive/tar by hiding the OS-dependent file info from tar.FileInfoHeader which it needs to perform the lookups. Without that information available it falls back to populating the tar.Header with only the information obtainable directly from the FileInfo value without making any calls into os/user. Fixes #42402 Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-01-24 20:33:54 +00:00
func init() {
sysStat = statUnix
}
// fixVolumePathPrefix does platform specific processing to ensure that if
// the path being passed in is not in a volume path format, convert it to one.
func fixVolumePathPrefix(srcPath string) string {
return srcPath
}
// getWalkRoot calculates the root path when performing a TarWithOptions.
// We use a separate function as this is platform specific. On Linux, we
// can't use filepath.Join(srcPath,include) because this will clean away
// a trailing "." or "/" which may be important.
func getWalkRoot(srcPath string, include string) string {
return strings.TrimSuffix(srcPath, string(filepath.Separator)) + string(filepath.Separator) + include
}
// chmodTarEntry is used to adjust the file permissions used in tar header based
// on the platform the archival is done.
func chmodTarEntry(perm os.FileMode) os.FileMode {
return perm // noop for unix as golang APIs provide perm bits correctly
}
Remove local fork of archive/tar package A copy of Go's archive/tar packge was vendored with a patch applied to mitigate CVE-2019-14271. Vendoring standard library packages is not supported by Go in module-aware mode, which is getting in the way of maintenance. A different approach to mitigate the vulnerability is needed which does not involve vendoring parts of the standard library. glibc implements name service lookups such as users, groups and DNS using a scheme known as Name Service Switch. The services are implemented as modules, shared libraries which glibc dynamically links into the process the first time a function requiring the module is called. This is the crux of the vulnerability: if a process linked against glibc chroots, then calls one of the functions implemented with NSS for the first time, glibc may load NSS modules out of the chrooted filesystem. The API underlying the `docker cp` command is implemented by forking a new process which chroots into the container's rootfs and writes a tar stream of files from the container over standard output. It utilizes the Go standard library's archive/tar package to write the tar stream. It makes use of the tar.FileInfoHeader function to construct a tar.Header value from an fs.FileInfo value. In modern versions of Go on *nix platforms, FileInfoHeader will attempt to resolve the file's UID and GID to their respective user and group names by calling the os/user functions LookupId and LookupGroupId. The cgo implementation of os/user on *nix performs lookups by calling the corresponding libc functions. So when linked against glibc, calls to tar.FileInfoHeader after the process has chrooted into the container's rootfs can have the side effect of loading NSS modules from the container! Without any mitigations, a malicious container image author can trivially get arbitrary code execution by leveraging this vulnerability and escape the chroot (which is not a sandbox) into the host. Mitigate the vulnerability without patching or forking archive/tar by hiding the OS-dependent file info from tar.FileInfoHeader which it needs to perform the lookups. Without that information available it falls back to populating the tar.Header with only the information obtainable directly from the FileInfo value without making any calls into os/user. Fixes #42402 Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-01-24 20:33:54 +00:00
// statUnix populates hdr from system-dependent fields of fi without performing
// any OS lookups.
func statUnix(fi os.FileInfo, hdr *tar.Header) error {
s, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return nil
}
Remove local fork of archive/tar package A copy of Go's archive/tar packge was vendored with a patch applied to mitigate CVE-2019-14271. Vendoring standard library packages is not supported by Go in module-aware mode, which is getting in the way of maintenance. A different approach to mitigate the vulnerability is needed which does not involve vendoring parts of the standard library. glibc implements name service lookups such as users, groups and DNS using a scheme known as Name Service Switch. The services are implemented as modules, shared libraries which glibc dynamically links into the process the first time a function requiring the module is called. This is the crux of the vulnerability: if a process linked against glibc chroots, then calls one of the functions implemented with NSS for the first time, glibc may load NSS modules out of the chrooted filesystem. The API underlying the `docker cp` command is implemented by forking a new process which chroots into the container's rootfs and writes a tar stream of files from the container over standard output. It utilizes the Go standard library's archive/tar package to write the tar stream. It makes use of the tar.FileInfoHeader function to construct a tar.Header value from an fs.FileInfo value. In modern versions of Go on *nix platforms, FileInfoHeader will attempt to resolve the file's UID and GID to their respective user and group names by calling the os/user functions LookupId and LookupGroupId. The cgo implementation of os/user on *nix performs lookups by calling the corresponding libc functions. So when linked against glibc, calls to tar.FileInfoHeader after the process has chrooted into the container's rootfs can have the side effect of loading NSS modules from the container! Without any mitigations, a malicious container image author can trivially get arbitrary code execution by leveraging this vulnerability and escape the chroot (which is not a sandbox) into the host. Mitigate the vulnerability without patching or forking archive/tar by hiding the OS-dependent file info from tar.FileInfoHeader which it needs to perform the lookups. Without that information available it falls back to populating the tar.Header with only the information obtainable directly from the FileInfo value without making any calls into os/user. Fixes #42402 Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-01-24 20:33:54 +00:00
hdr.Uid = int(s.Uid)
hdr.Gid = int(s.Gid)
if s.Mode&unix.S_IFBLK != 0 ||
s.Mode&unix.S_IFCHR != 0 {
hdr.Devmajor = int64(unix.Major(uint64(s.Rdev))) //nolint: unconvert
hdr.Devminor = int64(unix.Minor(uint64(s.Rdev))) //nolint: unconvert
}
Remove local fork of archive/tar package A copy of Go's archive/tar packge was vendored with a patch applied to mitigate CVE-2019-14271. Vendoring standard library packages is not supported by Go in module-aware mode, which is getting in the way of maintenance. A different approach to mitigate the vulnerability is needed which does not involve vendoring parts of the standard library. glibc implements name service lookups such as users, groups and DNS using a scheme known as Name Service Switch. The services are implemented as modules, shared libraries which glibc dynamically links into the process the first time a function requiring the module is called. This is the crux of the vulnerability: if a process linked against glibc chroots, then calls one of the functions implemented with NSS for the first time, glibc may load NSS modules out of the chrooted filesystem. The API underlying the `docker cp` command is implemented by forking a new process which chroots into the container's rootfs and writes a tar stream of files from the container over standard output. It utilizes the Go standard library's archive/tar package to write the tar stream. It makes use of the tar.FileInfoHeader function to construct a tar.Header value from an fs.FileInfo value. In modern versions of Go on *nix platforms, FileInfoHeader will attempt to resolve the file's UID and GID to their respective user and group names by calling the os/user functions LookupId and LookupGroupId. The cgo implementation of os/user on *nix performs lookups by calling the corresponding libc functions. So when linked against glibc, calls to tar.FileInfoHeader after the process has chrooted into the container's rootfs can have the side effect of loading NSS modules from the container! Without any mitigations, a malicious container image author can trivially get arbitrary code execution by leveraging this vulnerability and escape the chroot (which is not a sandbox) into the host. Mitigate the vulnerability without patching or forking archive/tar by hiding the OS-dependent file info from tar.FileInfoHeader which it needs to perform the lookups. Without that information available it falls back to populating the tar.Header with only the information obtainable directly from the FileInfo value without making any calls into os/user. Fixes #42402 Signed-off-by: Cory Snider <csnider@mirantis.com>
2022-01-24 20:33:54 +00:00
return nil
}
func getInodeFromStat(stat interface{}) (inode uint64, err error) {
s, ok := stat.(*syscall.Stat_t)
if ok {
inode = s.Ino
}
return
}
func getFileUIDGID(stat interface{}) (idtools.Identity, error) {
s, ok := stat.(*syscall.Stat_t)
if !ok {
return idtools.Identity{}, errors.New("cannot convert stat value to syscall.Stat_t")
}
return idtools.Identity{UID: int(s.Uid), GID: int(s.Gid)}, nil
}
// handleTarTypeBlockCharFifo is an OS-specific helper function used by
// createTarFile to handle the following types of header: Block; Char; Fifo
func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
mode := uint32(hdr.Mode & 07777)
switch hdr.Typeflag {
case tar.TypeBlock:
mode |= unix.S_IFBLK
case tar.TypeChar:
mode |= unix.S_IFCHR
case tar.TypeFifo:
mode |= unix.S_IFIFO
}
err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor)))
if errors.Is(err, syscall.EPERM) && userns.RunningInUserNS() {
// In most cases, cannot create a device if running in user namespace
err = nil
}
return err
}
func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
if hdr.Typeflag == tar.TypeLink {
if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) {
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
return err
}
}
} else if hdr.Typeflag != tar.TypeSymlink {
if err := os.Chmod(path, hdrInfo.Mode()); err != nil {
return err
}
}
return nil
}