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 }