github.com/rkt/rkt@v1.30.1-0.20200224141603-171c416fac02/pkg/tar/tar.go (about) 1 // Copyright 2014 The rkt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package tar contains helper functions for working with tar files 16 package tar 17 18 import ( 19 "archive/tar" 20 "errors" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "strings" 27 "syscall" 28 29 "github.com/appc/spec/pkg/device" 30 "github.com/rkt/rkt/pkg/fileutil" 31 "github.com/rkt/rkt/pkg/user" 32 ) 33 34 const DEFAULT_DIR_MODE os.FileMode = 0755 35 36 var ErrNotSupportedPlatform = errors.New("platform and architecture is not supported") 37 38 var xattrPrefixWhitelist = []string{ 39 "security.capability", 40 "user", 41 } 42 43 // Map of paths that should be whitelisted. The paths should be relative to the 44 // root of the tar file and should be cleaned (for example using filepath.Clean) 45 type PathWhitelistMap map[string]struct{} 46 47 type FilePermissionsEditor func(path string, uid, gid int, typ byte, _ os.FileInfo, xattr map[string]string) error 48 49 func NewUidShiftingFilePermEditor(uidRange *user.UidRange) (FilePermissionsEditor, error) { 50 if os.Geteuid() != 0 { 51 return func(_ string, _, _ int, _ byte, _ os.FileInfo, _ map[string]string) error { 52 // The files are owned by the current user on creation. 53 // If we do nothing, they will remain so. 54 return nil 55 }, nil 56 } 57 58 return func(path string, uid, gid int, typ byte, fi os.FileInfo, xattr map[string]string) error { 59 shiftedUid, shiftedGid, err := uidRange.ShiftRange(uint32(uid), uint32(gid)) 60 if err != nil { 61 return err 62 } 63 if err := os.Lchown(path, int(shiftedUid), int(shiftedGid)); err != nil { 64 return err 65 } 66 67 // lchown(2) says that, depending on the linux kernel version, it 68 // can change the file's mode also if executed as root. So call 69 // os.Chmod after it. 70 if typ != tar.TypeSymlink { 71 if err := os.Chmod(path, fi.Mode()); err != nil { 72 return err 73 } 74 } 75 76 if typ == tar.TypeReg || typ == tar.TypeRegA { 77 for k, v := range xattr { 78 if !isXattrAllowed(k) { 79 continue 80 } 81 82 err := syscall.Setxattr(path, k, []byte(v), 0) 83 if err != nil { 84 return err 85 } 86 } 87 } 88 89 return nil 90 }, nil 91 } 92 93 // ExtractTarInsecure extracts a tarball (from a tar.Reader) into the target 94 // directory. If pwl is not nil, only the paths in the map are extracted. If 95 // overwrite is true, existing files will be overwritten. 96 func ExtractTarInsecure(tr *tar.Reader, target string, overwrite bool, pwl PathWhitelistMap, editor FilePermissionsEditor) error { 97 um := syscall.Umask(0) 98 defer syscall.Umask(um) 99 100 var dirhdrs []*tar.Header 101 Tar: 102 for { 103 hdr, err := tr.Next() 104 switch err { 105 case io.EOF: 106 break Tar 107 case nil: 108 if pwl != nil { 109 relpath := filepath.Clean(hdr.Name) 110 if _, ok := pwl[relpath]; !ok { 111 continue 112 } 113 } 114 err = extractFile(tr, target, hdr, overwrite, editor) 115 if err != nil { 116 return fmt.Errorf("could not extract file %q in %q: %v", hdr.Name, target, err) 117 } 118 if hdr.Typeflag == tar.TypeDir { 119 dirhdrs = append(dirhdrs, hdr) 120 } 121 default: 122 return err 123 } 124 } 125 126 // Restore dirs atime and mtime. This has to be done after extracting 127 // as a file extraction will change its parent directory's times. 128 for _, hdr := range dirhdrs { 129 p := filepath.Join(target, hdr.Name) 130 if err := syscall.UtimesNano(p, HdrToTimespec(hdr)); err != nil { 131 return fmt.Errorf("UtimesNano failed on %q: %v", p, err) 132 } 133 } 134 return nil 135 } 136 137 // extractFile extracts the file described by hdr from the given tarball into 138 // the target directory. 139 // If overwrite is true, existing files will be overwritten. 140 func extractFile(tr *tar.Reader, target string, hdr *tar.Header, overwrite bool, editor FilePermissionsEditor) error { 141 p := filepath.Join(target, hdr.Name) 142 fi := hdr.FileInfo() 143 typ := hdr.Typeflag 144 if overwrite { 145 info, err := os.Lstat(p) 146 switch { 147 case os.IsNotExist(err): 148 case err == nil: 149 // If the old and new paths are both dirs do nothing or 150 // RemoveAll will remove all dir's contents 151 if !info.IsDir() || typ != tar.TypeDir { 152 err := os.RemoveAll(p) 153 if err != nil { 154 return err 155 } 156 } 157 default: 158 return err 159 } 160 } 161 162 // Create parent dir if it doesn't exist 163 if err := os.MkdirAll(filepath.Dir(p), DEFAULT_DIR_MODE); err != nil { 164 return err 165 } 166 switch { 167 case typ == tar.TypeReg || typ == tar.TypeRegA: 168 f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR, fi.Mode()) 169 if err != nil { 170 return err 171 } 172 _, err = io.Copy(f, tr) 173 if err != nil { 174 f.Close() 175 return err 176 } 177 f.Close() 178 case typ == tar.TypeDir: 179 if err := os.MkdirAll(p, fi.Mode()); err != nil { 180 return err 181 } 182 dir, err := os.Open(p) 183 if err != nil { 184 return err 185 } 186 if err := dir.Chmod(fi.Mode()); err != nil { 187 dir.Close() 188 return err 189 } 190 dir.Close() 191 case typ == tar.TypeLink: 192 dest := filepath.Join(target, hdr.Linkname) 193 if err := os.Link(dest, p); err != nil { 194 return err 195 } 196 case typ == tar.TypeSymlink: 197 if err := os.Symlink(hdr.Linkname, p); err != nil { 198 return err 199 } 200 case typ == tar.TypeChar: 201 dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) 202 mode := uint32(fi.Mode()) | syscall.S_IFCHR 203 if err := syscall.Mknod(p, mode, int(dev)); err != nil { 204 return err 205 } 206 case typ == tar.TypeBlock: 207 dev := device.Makedev(uint(hdr.Devmajor), uint(hdr.Devminor)) 208 mode := uint32(fi.Mode()) | syscall.S_IFBLK 209 if err := syscall.Mknod(p, mode, int(dev)); err != nil { 210 return err 211 } 212 case typ == tar.TypeFifo: 213 if err := syscall.Mkfifo(p, uint32(fi.Mode())); err != nil { 214 return err 215 } 216 case typ == tar.TypeXGlobalHeader: 217 return nil 218 // TODO(jonboulle): implement other modes 219 default: 220 return fmt.Errorf("unsupported type: %v", typ) 221 } 222 223 if editor != nil { 224 if err := editor(p, hdr.Uid, hdr.Gid, hdr.Typeflag, fi, hdr.Xattrs); err != nil { 225 return err 226 } 227 } 228 229 // Restore entry atime and mtime. 230 // Use special function LUtimesNano not available on go's syscall package because we 231 // have to restore symlink's times and not the referenced file times. 232 ts := HdrToTimespec(hdr) 233 if hdr.Typeflag != tar.TypeSymlink { 234 if err := syscall.UtimesNano(p, ts); err != nil { 235 return err 236 } 237 } else { 238 if err := fileutil.LUtimesNano(p, ts); err != nil && err != ErrNotSupportedPlatform { 239 return err 240 } 241 } 242 243 return nil 244 } 245 246 // extractFileFromTar extracts a regular file from the given tar, returning its 247 // contents as a byte slice 248 func extractFileFromTar(tr *tar.Reader, file string) ([]byte, error) { 249 for { 250 hdr, err := tr.Next() 251 switch err { 252 case io.EOF: 253 return nil, fmt.Errorf("file not found") 254 case nil: 255 if filepath.Clean(hdr.Name) != filepath.Clean(file) { 256 continue 257 } 258 switch hdr.Typeflag { 259 case tar.TypeReg: 260 case tar.TypeRegA: 261 default: 262 return nil, fmt.Errorf("requested file not a regular file") 263 } 264 buf, err := ioutil.ReadAll(tr) 265 if err != nil { 266 return nil, err 267 } 268 return buf, nil 269 default: 270 return nil, err 271 } 272 } 273 } 274 275 func HdrToTimespec(hdr *tar.Header) []syscall.Timespec { 276 return []syscall.Timespec{fileutil.TimeToTimespec(hdr.AccessTime), fileutil.TimeToTimespec(hdr.ModTime)} 277 } 278 279 func isXattrAllowed(xattr string) bool { 280 for _, v := range xattrPrefixWhitelist { 281 if strings.HasPrefix(xattr, v) { 282 return true 283 } 284 } 285 286 return false 287 }