github.com/openflowlabs/storage@v1.12.13/pkg/chrootarchive/archive.go (about)

     1  package chrootarchive
     2  
     3  import (
     4  	stdtar "archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"sync"
    11  
    12  	"github.com/containers/storage/pkg/archive"
    13  	"github.com/containers/storage/pkg/idtools"
    14  	rsystem "github.com/opencontainers/runc/libcontainer/system"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  // NewArchiver returns a new Archiver which uses chrootarchive.Untar
    19  func NewArchiver(idMappings *idtools.IDMappings) *archive.Archiver {
    20  	archiver := archive.NewArchiver(idMappings)
    21  	archiver.Untar = Untar
    22  	return archiver
    23  }
    24  
    25  // NewArchiverWithChown returns a new Archiver which uses chrootarchive.Untar and the provided ID mapping configuration on both ends
    26  func NewArchiverWithChown(tarIDMappings *idtools.IDMappings, chownOpts *idtools.IDPair, untarIDMappings *idtools.IDMappings) *archive.Archiver {
    27  	archiver := archive.NewArchiverWithChown(tarIDMappings, chownOpts, untarIDMappings)
    28  	archiver.Untar = Untar
    29  	return archiver
    30  }
    31  
    32  // Untar reads a stream of bytes from `archive`, parses it as a tar archive,
    33  // and unpacks it into the directory at `dest`.
    34  // The archive may be compressed with one of the following algorithms:
    35  //  identity (uncompressed), gzip, bzip2, xz.
    36  func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
    37  	return untarHandler(tarArchive, dest, options, true, dest)
    38  }
    39  
    40  // UntarWithRoot is the same as `Untar`, but allows you to pass in a root directory
    41  // The root directory is the directory that will be chrooted to.
    42  // `dest` must be a path within `root`, if it is not an error will be returned.
    43  //
    44  // `root` should set to a directory which is not controlled by any potentially
    45  // malicious process.
    46  //
    47  // This should be used to prevent a potential attacker from manipulating `dest`
    48  // such that it would provide access to files outside of `dest` through things
    49  // like symlinks. Normally `ResolveSymlinksInScope` would handle this, however
    50  // sanitizing symlinks in this manner is inherrently racey:
    51  // ref: CVE-2018-15664
    52  func UntarWithRoot(tarArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
    53  	return untarHandler(tarArchive, dest, options, true, root)
    54  }
    55  
    56  // UntarUncompressed reads a stream of bytes from `archive`, parses it as a tar archive,
    57  // and unpacks it into the directory at `dest`.
    58  // The archive must be an uncompressed stream.
    59  func UntarUncompressed(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
    60  	return untarHandler(tarArchive, dest, options, false, dest)
    61  }
    62  
    63  // Handler for teasing out the automatic decompression
    64  func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions, decompress bool, root string) error {
    65  	if tarArchive == nil {
    66  		return fmt.Errorf("Empty archive")
    67  	}
    68  	if options == nil {
    69  		options = &archive.TarOptions{}
    70  		options.InUserNS = rsystem.RunningInUserNS()
    71  	}
    72  	if options.ExcludePatterns == nil {
    73  		options.ExcludePatterns = []string{}
    74  	}
    75  
    76  	idMappings := idtools.NewIDMappingsFromMaps(options.UIDMaps, options.GIDMaps)
    77  	rootIDs := idMappings.RootPair()
    78  
    79  	dest = filepath.Clean(dest)
    80  	if _, err := os.Stat(dest); os.IsNotExist(err) {
    81  		if err := idtools.MkdirAllAndChownNew(dest, 0755, rootIDs); err != nil {
    82  			return err
    83  		}
    84  	}
    85  
    86  	r := ioutil.NopCloser(tarArchive)
    87  	if decompress {
    88  		decompressedArchive, err := archive.DecompressStream(tarArchive)
    89  		if err != nil {
    90  			return err
    91  		}
    92  		defer decompressedArchive.Close()
    93  		r = decompressedArchive
    94  	}
    95  
    96  	return invokeUnpack(r, dest, options, root)
    97  }
    98  
    99  // Tar tars the requested path while chrooted to the specified root.
   100  func Tar(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
   101  	if options == nil {
   102  		options = &archive.TarOptions{}
   103  	}
   104  	return invokePack(srcPath, options, root)
   105  }
   106  
   107  // CopyFileWithTarAndChown returns a function which copies a single file from outside
   108  // of any container into our working container, mapping permissions using the
   109  // container's ID maps, possibly overridden using the passed-in chownOpts
   110  func CopyFileWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error {
   111  	untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap)
   112  	archiver := NewArchiverWithChown(nil, chownOpts, untarMappings)
   113  	if hasher != nil {
   114  		originalUntar := archiver.Untar
   115  		archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
   116  			contentReader, contentWriter, err := os.Pipe()
   117  			if err != nil {
   118  				return errors.Wrapf(err, "error creating pipe extract data to %q", dest)
   119  			}
   120  			defer contentReader.Close()
   121  			defer contentWriter.Close()
   122  			var hashError error
   123  			var hashWorker sync.WaitGroup
   124  			hashWorker.Add(1)
   125  			go func() {
   126  				t := stdtar.NewReader(contentReader)
   127  				_, err := t.Next()
   128  				if err != nil {
   129  					hashError = err
   130  				}
   131  				if _, err = io.Copy(hasher, t); err != nil && err != io.EOF {
   132  					hashError = err
   133  				}
   134  				hashWorker.Done()
   135  			}()
   136  			if err = originalUntar(io.TeeReader(tarArchive, contentWriter), dest, options); err != nil {
   137  				err = errors.Wrapf(err, "error extracting data to %q while copying", dest)
   138  			}
   139  			hashWorker.Wait()
   140  			if err == nil {
   141  				err = errors.Wrapf(hashError, "error calculating digest of data for %q while copying", dest)
   142  			}
   143  			return err
   144  		}
   145  	}
   146  	return archiver.CopyFileWithTar
   147  }
   148  
   149  // CopyWithTarAndChown returns a function which copies a directory tree from outside of
   150  // any container into our working container, mapping permissions using the
   151  // container's ID maps, possibly overridden using the passed-in chownOpts
   152  func CopyWithTarAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error {
   153  	untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap)
   154  	archiver := NewArchiverWithChown(nil, chownOpts, untarMappings)
   155  	if hasher != nil {
   156  		originalUntar := archiver.Untar
   157  		archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
   158  			return originalUntar(io.TeeReader(tarArchive, hasher), dest, options)
   159  		}
   160  	}
   161  	return archiver.CopyWithTar
   162  }
   163  
   164  // UntarPathAndChown returns a function which extracts an archive in a specified
   165  // location into our working container, mapping permissions using the
   166  // container's ID maps, possibly overridden using the passed-in chownOpts
   167  func UntarPathAndChown(chownOpts *idtools.IDPair, hasher io.Writer, uidmap []idtools.IDMap, gidmap []idtools.IDMap) func(src, dest string) error {
   168  	untarMappings := idtools.NewIDMappingsFromMaps(uidmap, gidmap)
   169  	archiver := NewArchiverWithChown(nil, chownOpts, untarMappings)
   170  	if hasher != nil {
   171  		originalUntar := archiver.Untar
   172  		archiver.Untar = func(tarArchive io.Reader, dest string, options *archive.TarOptions) error {
   173  			return originalUntar(io.TeeReader(tarArchive, hasher), dest, options)
   174  		}
   175  	}
   176  	return archiver.UntarPath
   177  }