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  }