github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/layer/tar_extract.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016-2020 SUSE LLC 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package layer 19 20 import ( 21 "archive/tar" 22 "bytes" 23 "fmt" 24 "io" 25 "os" 26 "path/filepath" 27 "strings" 28 "time" 29 30 "github.com/apex/log" 31 securejoin "github.com/cyphar/filepath-securejoin" 32 "github.com/opencontainers/umoci/pkg/fseval" 33 "github.com/opencontainers/umoci/pkg/system" 34 "github.com/opencontainers/umoci/third_party/shared" 35 "github.com/pkg/errors" 36 "golang.org/x/sys/unix" 37 ) 38 39 // inUserNamespace is a cached return value of shared.RunningInUserNS(). We 40 // compute this once globally rather than for each unpack. It won't change (we 41 // would hope) after we check it the first time. 42 var inUserNamespace = shared.RunningInUserNS() 43 44 // TarExtractor represents a tar file to be extracted. 45 type TarExtractor struct { 46 // mapOptions is the set of mapping options to use when extracting 47 // filesystem layers. 48 mapOptions MapOptions 49 50 // partialRootless indicates whether "partial rootless" tricks should be 51 // applied in our extraction. Rootless and userns execution have some 52 // similar tricks necessary, but not all rootless tricks should be applied 53 // when running in a userns -- hence the term "partial rootless" tricks. 54 partialRootless bool 55 56 // fsEval is an fseval.FsEval used for extraction. 57 fsEval fseval.FsEval 58 59 // upperPaths are paths that have either been extracted in the execution of 60 // this TarExtractor or are ancestors of paths extracted. The purpose of 61 // having this stored in-memory is to be able to handle opaque whiteouts as 62 // well as some other possible ordering issues with malformed archives (the 63 // downside of this approach is that it takes up memory -- we could switch 64 // to a trie if necessary). These paths are relative to the tar root but 65 // are fully symlink-expanded so no need to worry about that line noise. 66 upperPaths map[string]struct{} 67 68 // enotsupWarned is a flag set when we encounter the first ENOTSUP error 69 // dealing with xattrs. This is used to ensure extraction to a destination 70 // file system that does not support xattrs raises a single warning, rather 71 // than a warning for every file, which can amount to 1000s of messages that 72 // scroll a terminal, and may obscure other more important warnings. 73 enotsupWarned bool 74 75 // keepDirlinks is the corresponding flag from the UnpackOptions 76 // supplied when this TarExtractor was constructed. 77 keepDirlinks bool 78 79 // whiteoutMode indicates how this TarExtractor will handle whiteouts. 80 whiteoutMode WhiteoutMode 81 } 82 83 // NewTarExtractor creates a new TarExtractor. 84 func NewTarExtractor(opt UnpackOptions) *TarExtractor { 85 fsEval := fseval.Default 86 if opt.MapOptions.Rootless { 87 fsEval = fseval.Rootless 88 } 89 90 return &TarExtractor{ 91 mapOptions: opt.MapOptions, 92 partialRootless: opt.MapOptions.Rootless || inUserNamespace, 93 fsEval: fsEval, 94 upperPaths: make(map[string]struct{}), 95 enotsupWarned: false, 96 keepDirlinks: opt.KeepDirlinks, 97 whiteoutMode: opt.WhiteoutMode, 98 } 99 } 100 101 // restoreMetadata applies the state described in tar.Header to the filesystem 102 // at the given path. No sanity checking is done of the tar.Header's pathname 103 // or other information. In addition, no mapping is done of the header. 104 func (te *TarExtractor) restoreMetadata(path string, hdr *tar.Header) error { 105 // Some of the tar.Header fields don't match the OS API. 106 fi := hdr.FileInfo() 107 108 // Get the _actual_ file info to figure out if the path is a symlink. 109 isSymlink := hdr.Typeflag == tar.TypeSymlink 110 if realFi, err := te.fsEval.Lstat(path); err == nil { 111 isSymlink = realFi.Mode()&os.ModeSymlink == os.ModeSymlink 112 } 113 114 // Apply the owner. If we are rootless then "user.rootlesscontainers" has 115 // already been set up by unmapHeader, so nothing to do here. 116 if !te.mapOptions.Rootless { 117 // NOTE: This is not done through fsEval. 118 if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil { 119 return errors.Wrapf(err, "restore chown metadata: %s", path) 120 } 121 } 122 123 // We cannot apply hdr.Mode to symlinks, because symlinks don't have a mode 124 // of their own (they're special in that way). We have to apply this after 125 // we've applied the owner because setuid bits are cleared when changing 126 // owner (in rootless we don't care because we're always the owner). 127 if !isSymlink { 128 if err := te.fsEval.Chmod(path, fi.Mode()); err != nil { 129 return errors.Wrapf(err, "restore chmod metadata: %s", path) 130 } 131 } 132 133 // Apply access and modified time. Note that some archives won't fill the 134 // atime and mtime fields, so we have to set them to a more sane value. 135 // Otherwise Linux will start screaming at us, and nobody wants that. 136 mtime := hdr.ModTime 137 if mtime.IsZero() { 138 // XXX: Should we instead default to atime if it's non-zero? 139 mtime = time.Now() 140 } 141 atime := hdr.AccessTime 142 if atime.IsZero() { 143 // Default to the mtime. 144 atime = mtime 145 } 146 147 // Apply xattrs. In order to make sure that we *only* have the xattr set we 148 // want, we first clear the set of xattrs from the file then apply the ones 149 // set in the tar.Header. 150 err := te.fsEval.Lclearxattrs(path, ignoreXattrs) 151 if err != nil { 152 if errors.Cause(err) != unix.ENOTSUP { 153 return errors.Wrapf(err, "clear xattr metadata: %s", path) 154 } 155 if !te.enotsupWarned { 156 log.Warnf("xattr{%s} ignoring ENOTSUP on clearxattrs", path) 157 log.Warnf("xattr{%s} destination filesystem does not support xattrs, further warnings will be suppressed", path) 158 te.enotsupWarned = true 159 } else { 160 log.Debugf("xattr{%s} ignoring ENOTSUP on clearxattrs", path) 161 } 162 } 163 164 for name, value := range hdr.Xattrs { 165 value := []byte(value) 166 167 // Forbidden xattrs should never be touched. 168 if _, skip := ignoreXattrs[name]; skip { 169 // If the xattr is already set to the requested value, don't bail. 170 // The reason for this logic is kinda convoluted, but effectively 171 // because restoreMetadata is called with the *on-disk* metadata we 172 // run the risk of things like "security.selinux" being included in 173 // that metadata (and thus tripping the forbidden xattr error). By 174 // only touching xattrs that have a different value we are somewhat 175 // more efficient and we don't have to special case parent restore. 176 // Of course this will only ever impact ignoreXattrs. 177 if oldValue, err := te.fsEval.Lgetxattr(path, name); err == nil { 178 if bytes.Equal(value, oldValue) { 179 log.Debugf("restore xattr metadata: skipping already-set xattr %q: %s", name, hdr.Name) 180 continue 181 } 182 } 183 log.Warnf("xattr{%s} ignoring forbidden xattr: %q", hdr.Name, name) 184 continue 185 } 186 if err := te.fsEval.Lsetxattr(path, name, value, 0); err != nil { 187 // In rootless mode, some xattrs will fail (security.capability). 188 // This is _fine_ as long as we're not running as root (in which 189 // case we shouldn't be ignoring xattrs that we were told to set). 190 // 191 // TODO: We should translate all security.capability capabilities 192 // into v3 capabilities, which allow us to write them as 193 // unprivileged users (we also would need to translate them 194 // back when creating archives). 195 if te.partialRootless && os.IsPermission(errors.Cause(err)) { 196 log.Warnf("rootless{%s} ignoring (usually) harmless EPERM on setxattr %q", hdr.Name, name) 197 continue 198 } 199 // We cannot do much if we get an ENOTSUP -- this usually means 200 // that extended attributes are simply unsupported by the 201 // underlying filesystem (such as AUFS or NFS). 202 if errors.Cause(err) == unix.ENOTSUP { 203 if !te.enotsupWarned { 204 log.Warnf("xattr{%s} ignoring ENOTSUP on setxattr %q", hdr.Name, name) 205 log.Warnf("xattr{%s} destination filesystem does not support xattrs, further warnings will be suppressed", path) 206 te.enotsupWarned = true 207 } else { 208 log.Debugf("xattr{%s} ignoring ENOTSUP on clearxattrs", path) 209 } 210 continue 211 } 212 return errors.Wrapf(err, "restore xattr metadata: %s", path) 213 } 214 } 215 216 if err := te.fsEval.Lutimes(path, atime, mtime); err != nil { 217 return errors.Wrapf(err, "restore lutimes metadata: %s", path) 218 } 219 220 return nil 221 } 222 223 // applyMetadata applies the state described in tar.Header to the filesystem at 224 // the given path, using the state of the TarExtractor to remap information 225 // within the header. This should only be used with headers from a tar layer 226 // (not from the filesystem). No sanity checking is done of the tar.Header's 227 // pathname or other information. 228 func (te *TarExtractor) applyMetadata(path string, hdr *tar.Header) error { 229 // Modify the header. 230 if err := unmapHeader(hdr, te.mapOptions); err != nil { 231 return errors.Wrap(err, "unmap header") 232 } 233 234 // Restore it on the filesystme. 235 return te.restoreMetadata(path, hdr) 236 } 237 238 // isDirlink returns whether the given path is a link to a directory (or a 239 // dirlink in rsync(1) parlance) which is used by --keep-dirlink to see whether 240 // we should extract through the link or clobber the link with a directory (in 241 // the case where we see a directory to extract and a symlink already exists 242 // there). 243 func (te *TarExtractor) isDirlink(root string, path string) (bool, error) { 244 // Make sure it exists and is a symlink. 245 if _, err := te.fsEval.Readlink(path); err != nil { 246 return false, errors.Wrap(err, "read dirlink") 247 } 248 249 // Technically a string.TrimPrefix would also work... 250 unsafePath, err := filepath.Rel(root, path) 251 if err != nil { 252 return false, errors.Wrap(err, "get relative-to-root path") 253 } 254 255 // It should be noted that SecureJoin will evaluate all symlinks in the 256 // path, so we don't need to loop over it or anything like that. It'll just 257 // be done for us (in UnpackEntry only the dirname(3) is evaluated but here 258 // we evaluate the whole thing). 259 targetPath, err := securejoin.SecureJoinVFS(root, unsafePath, te.fsEval) 260 if err != nil { 261 // We hit a symlink loop -- which is fine but that means that this 262 // cannot be considered a dirlink. 263 if errno := InnerErrno(err); errno == unix.ELOOP { 264 err = nil 265 } 266 return false, errors.Wrap(err, "sanitize old target") 267 } 268 269 targetInfo, err := te.fsEval.Lstat(targetPath) 270 if err != nil { 271 // ENOENT or similar just means that it's a broken symlink, which 272 // means we have to overwrite it (but it's an allowed case). 273 if securejoin.IsNotExist(err) { 274 err = nil 275 } 276 return false, err 277 } 278 279 return targetInfo.IsDir(), nil 280 } 281 282 func (te *TarExtractor) ociWhiteout(root string, dir string, file string) error { 283 isOpaque := file == whOpaque 284 file = strings.TrimPrefix(file, whPrefix) 285 286 // We have to be quite careful here. While the most intuitive way of 287 // handling whiteouts would be to just RemoveAll without prejudice, We 288 // have to be careful here. If there is a whiteout entry for a file 289 // *after* a normal entry (in the same layer) then the whiteout must 290 // not remove the new entry. We handle this by keeping track of 291 // whichpaths have been touched by this layer's extraction (these form 292 // the "upperdir"). We also have to handle cases where a directory has 293 // been marked for deletion, but a child has been extracted in this 294 // layer. 295 296 path := filepath.Join(dir, file) 297 if isOpaque { 298 path = dir 299 } 300 301 // If the root doesn't exist we've got nothing to do. 302 // XXX: We currently cannot error out if a layer asks us to remove a 303 // non-existent path with this implementation (because we don't 304 // know if it was implicitly removed by another whiteout). In 305 // future we could add lowerPaths that would help track whether 306 // another whiteout caused the removal to "fail" or if the path 307 // was actually missing -- which would allow us to actually error 308 // out here if the layer is invalid). 309 if _, err := te.fsEval.Lstat(path); err != nil { 310 // Need to use securejoin.IsNotExist to handle ENOTDIR. 311 if securejoin.IsNotExist(err) { 312 err = nil 313 } 314 return errors.Wrap(err, "check whiteout target") 315 } 316 317 // Walk over the path to remove it. We remove a given path as soon as 318 // it isn't present in upperPaths (which includes ancestors of paths 319 // we've extracted so we only need to look up the one path). Otherwise 320 // we iterate over any children and try again. The only difference 321 // between opaque whiteouts and regular whiteouts is that we don't 322 // delete the directory itself with opaque whiteouts. 323 err := te.fsEval.Walk(path, func(subpath string, info os.FileInfo, err error) error { 324 // If we are passed an error, bail unless it's ENOENT. 325 if err != nil { 326 // If something was deleted outside of our knowledge it's not 327 // the end of the world. In principle this shouldn't happen 328 // though, so we log it for posterity. 329 if os.IsNotExist(errors.Cause(err)) { 330 log.Debugf("whiteout removal hit already-deleted path: %s", subpath) 331 err = filepath.SkipDir 332 } 333 return err 334 } 335 336 // Get the relative form of subpath to root to match 337 // te.upperPaths. 338 upperPath, err := filepath.Rel(root, subpath) 339 if err != nil { 340 return errors.Wrap(err, "find relative-to-root [should never happen]") 341 } 342 343 // Remove the path only if it hasn't been touched. 344 if _, ok := te.upperPaths[upperPath]; !ok { 345 // Opaque whiteouts don't remove the directory itself, so skip 346 // the top-level directory. 347 if isOpaque && CleanPath(path) == CleanPath(subpath) { 348 return nil 349 } 350 351 // Purge the path. We skip anything underneath (if it's a 352 // directory) since we just purged it -- and we don't want to 353 // hit ENOENT during iteration for no good reason. 354 err := errors.Wrap(te.fsEval.RemoveAll(subpath), "whiteout subpath") 355 if err == nil && info.IsDir() { 356 err = filepath.SkipDir 357 } 358 return err 359 } 360 return nil 361 }) 362 return errors.Wrap(err, "whiteout remove") 363 } 364 365 func (te *TarExtractor) overlayFSWhiteout(dir string, file string) error { 366 isOpaque := file == whOpaque 367 368 // if this is an opaque whiteout, whiteout the directory 369 if isOpaque { 370 err := te.fsEval.Lsetxattr(dir, "trusted.overlay.opaque", []byte("y"), 0) 371 return errors.Wrapf(err, "couldn't set overlayfs whiteout attr for %s", dir) 372 } 373 374 // otherwise, white out the file itself. 375 p := filepath.Join(dir, strings.TrimPrefix(file, whPrefix)) 376 if err := os.RemoveAll(p); err != nil && !os.IsNotExist(err) { 377 return errors.Wrapf(err, "couldn't create overlayfs whiteout for %s", p) 378 } 379 380 err := te.fsEval.Mknod(p, unix.S_IFCHR|0666, unix.Mkdev(0, 0)) 381 return errors.Wrapf(err, "couldn't create overlayfs whiteout for %s", p) 382 } 383 384 // UnpackEntry extracts the given tar.Header to the provided root, ensuring 385 // that the layer state is consistent with the layer state that produced the 386 // tar archive being iterated over. This does handle whiteouts, so a tar.Header 387 // that represents a whiteout will result in the path being removed. 388 func (te *TarExtractor) UnpackEntry(root string, hdr *tar.Header, r io.Reader) (Err error) { 389 // Make the paths safe. 390 hdr.Name = CleanPath(hdr.Name) 391 root = filepath.Clean(root) 392 393 log.WithFields(log.Fields{ 394 "root": root, 395 "path": hdr.Name, 396 "type": hdr.Typeflag, 397 }).Debugf("unpacking entry") 398 399 // Get directory and filename, but we have to safely get the directory 400 // component of the path. SecureJoinVFS will evaluate the path itself, 401 // which we don't want (we're clever enough to handle the actual path being 402 // a symlink). 403 unsafeDir, file := filepath.Split(hdr.Name) 404 if filepath.Join("/", hdr.Name) == "/" { 405 // If we got an entry for the root, then unsafeDir is the full path. 406 unsafeDir, file = hdr.Name, "." 407 // If we're being asked to change the root type, bail because they may 408 // change it to a symlink which we could inadvertently follow. 409 if hdr.Typeflag != tar.TypeDir { 410 return errors.New("malicious tar entry -- refusing to change type of root directory") 411 } 412 } 413 dir, err := securejoin.SecureJoinVFS(root, unsafeDir, te.fsEval) 414 if err != nil { 415 return errors.Wrap(err, "sanitise symlinks in root") 416 } 417 path := filepath.Join(dir, file) 418 419 // Before we do anything, get the state of dir. Because we might be adding 420 // or removing files, our parent directory might be modified in the 421 // process. As a result, we want to be able to restore the old state 422 // (because we only apply state that we find in the archive we're iterating 423 // over). We can safely ignore an error here, because a non-existent 424 // directory will be fixed by later archive entries. 425 if dirFi, err := te.fsEval.Lstat(dir); err == nil && path != dir { 426 // FIXME: This is really stupid. 427 // #nosec G104 428 link, _ := te.fsEval.Readlink(dir) 429 dirHdr, err := tar.FileInfoHeader(dirFi, link) 430 if err != nil { 431 return errors.Wrap(err, "convert dirFi to dirHdr") 432 } 433 434 // More faking to trick restoreMetadata to actually restore the directory. 435 dirHdr.Typeflag = tar.TypeDir 436 dirHdr.Linkname = "" 437 438 // os.Lstat doesn't get the list of xattrs by default. We need to fill 439 // this explicitly. Note that while Go's "archive/tar" takes strings, 440 // in Go strings can be arbitrary byte sequences so this doesn't 441 // restrict the possible values. 442 // TODO: Move this to a separate function so we can share it with 443 // tar_generate.go. 444 xattrs, err := te.fsEval.Llistxattr(dir) 445 if err != nil { 446 if errors.Cause(err) != unix.ENOTSUP { 447 return errors.Wrap(err, "get dirHdr.Xattrs") 448 } 449 if !te.enotsupWarned { 450 log.Warnf("xattr{%s} ignoring ENOTSUP on llistxattr", dir) 451 log.Warnf("xattr{%s} destination filesystem does not support xattrs, further warnings will be suppressed", path) 452 te.enotsupWarned = true 453 } else { 454 log.Debugf("xattr{%s} ignoring ENOTSUP on clearxattrs", path) 455 } 456 } 457 if len(xattrs) > 0 { 458 dirHdr.Xattrs = map[string]string{} 459 for _, xattr := range xattrs { 460 value, err := te.fsEval.Lgetxattr(dir, xattr) 461 if err != nil { 462 return errors.Wrap(err, "get xattr") 463 } 464 dirHdr.Xattrs[xattr] = string(value) 465 } 466 } 467 468 // Ensure that after everything we correctly re-apply the old metadata. 469 // We don't map this header because we're restoring files that already 470 // existed on the filesystem, not from a tar layer. 471 defer func() { 472 // Only overwrite the error if there wasn't one already. 473 if err := te.restoreMetadata(dir, dirHdr); err != nil { 474 if Err == nil { 475 Err = errors.Wrap(err, "restore parent directory") 476 } 477 } 478 }() 479 } 480 481 // Currently the spec doesn't specify what the hdr.Typeflag of whiteout 482 // files is meant to be. We specifically only produce regular files 483 // ('\x00') but it could be possible that someone produces a different 484 // Typeflag, expecting that the path is the only thing that matters in a 485 // whiteout entry. 486 if strings.HasPrefix(file, whPrefix) { 487 switch te.whiteoutMode { 488 case OCIStandardWhiteout: 489 return te.ociWhiteout(root, dir, file) 490 case OverlayFSWhiteout: 491 return te.overlayFSWhiteout(dir, file) 492 default: 493 return errors.Errorf("unknown whiteout mode %d", te.whiteoutMode) 494 } 495 } 496 497 // Get information about the path. This has to be done after we've dealt 498 // with whiteouts because it turns out that lstat(2) will return EPERM if 499 // you try to stat a whiteout on AUFS. 500 fi, err := te.fsEval.Lstat(path) 501 if err != nil { 502 // File doesn't exist, just switch fi to the file header. 503 fi = hdr.FileInfo() 504 } 505 506 // Attempt to create the parent directory of the path we're unpacking. 507 // We do a MkdirAll here because even though you need to have a tar entry 508 // for every component of a new path, applyMetadata will correct any 509 // inconsistencies. 510 // FIXME: We have to make this consistent, since if the tar archive doesn't 511 // have entries for some of these components we won't be able to 512 // verify that we have consistent results during unpacking. 513 if err := te.fsEval.MkdirAll(dir, 0777); err != nil { 514 return errors.Wrap(err, "mkdir parent") 515 } 516 517 isDirlink := false 518 // We remove whatever existed at the old path to clobber it so that 519 // creating a new path will not break. The only exception is if the path is 520 // a directory in both the layer and the current filesystem, in which case 521 // we don't delete it for obvious reasons. In all other cases we clobber. 522 // 523 // Note that this will cause hard-links in the "lower" layer to not be able 524 // to point to "upper" layer inodes even if the extracted type is the same 525 // as the old one, however it is not clear whether this is something a user 526 // would expect anyway. In addition, this will incorrectly deal with a 527 // TarLink that is present before the "upper" entry in the layer but the 528 // "lower" file still exists (so the hard-link would point to the old 529 // inode). It's not clear if such an archive is actually valid though. 530 if !fi.IsDir() || hdr.Typeflag != tar.TypeDir { 531 // If we are in --keep-dirlinks mode and the existing fs object is a 532 // symlink to a directory (with the pending object is a directory), we 533 // don't remove the symlink (and instead allow subsequent objects to be 534 // just written through the symlink into the directory). This is a very 535 // specific usecase where layers that were generated independently from 536 // each other (on different base filesystems) end up with weird things 537 // like /lib64 being a symlink only sometimes but you never want to 538 // delete libraries (not just the ones that were under the "real" 539 // directory). 540 // 541 // TODO: This code should also handle a pending symlink entry where the 542 // existing object is a directory. I'm not sure how we could 543 // disambiguate this from a symlink-to-a-file but I imagine that 544 // this is something that would also be useful in the same vein 545 // as --keep-dirlinks (which currently only prevents clobbering 546 // in the opposite case). 547 if te.keepDirlinks && 548 fi.Mode()&os.ModeSymlink == os.ModeSymlink && hdr.Typeflag == tar.TypeDir { 549 isDirlink, err = te.isDirlink(root, path) 550 if err != nil { 551 return errors.Wrap(err, "check is dirlink") 552 } 553 } 554 if !(isDirlink && te.keepDirlinks) { 555 if err := te.fsEval.RemoveAll(path); err != nil { 556 return errors.Wrap(err, "clobber old path") 557 } 558 } 559 } 560 561 // Now create or otherwise modify the state of the path. Right now, either 562 // the type of path matches hdr or the path doesn't exist. Note that we 563 // don't care about umasks or the initial mode here, since applyMetadata 564 // will fix all of that for us. 565 switch hdr.Typeflag { 566 // regular file 567 case tar.TypeReg, tar.TypeRegA: 568 // Create a new file, then just copy the data. 569 fh, err := te.fsEval.Create(path) 570 if err != nil { 571 return errors.Wrap(err, "create regular") 572 } 573 defer fh.Close() 574 575 // We need to make sure that we copy all of the bytes. 576 n, err := system.Copy(fh, r) 577 if int64(n) != hdr.Size { 578 if err != nil { 579 err = errors.Wrapf(err, "short write") 580 } else { 581 err = io.ErrShortWrite 582 } 583 } 584 if err != nil { 585 return errors.Wrap(err, "unpack to regular file") 586 } 587 588 // Force close here so that we don't affect the metadata. 589 if err := fh.Close(); err != nil { 590 return errors.Wrap(err, "close unpacked regular file") 591 } 592 593 // directory 594 case tar.TypeDir: 595 if isDirlink { 596 break 597 } 598 599 // Attempt to create the directory. We do a MkdirAll here because even 600 // though you need to have a tar entry for every component of a new 601 // path, applyMetadata will correct any inconsistencies. 602 if err := te.fsEval.MkdirAll(path, 0777); err != nil { 603 return errors.Wrap(err, "mkdirall") 604 } 605 606 // hard link, symbolic link 607 case tar.TypeLink, tar.TypeSymlink: 608 linkname := hdr.Linkname 609 610 // Hardlinks and symlinks act differently when it comes to the scoping. 611 // In both cases, we have to just unlink and then re-link the given 612 // path. But the function used and the argument are slightly different. 613 var linkFn func(string, string) error 614 switch hdr.Typeflag { 615 case tar.TypeLink: 616 linkFn = te.fsEval.Link 617 // Because hardlinks are inode-based we need to scope the link to 618 // the rootfs using SecureJoinVFS. As before, we need to be careful 619 // that we don't resolve the last part of the link path (in case 620 // the user actually wanted to hardlink to a symlink). 621 unsafeLinkDir, linkFile := filepath.Split(CleanPath(linkname)) 622 linkDir, err := securejoin.SecureJoinVFS(root, unsafeLinkDir, te.fsEval) 623 if err != nil { 624 return errors.Wrap(err, "sanitise hardlink target in root") 625 } 626 linkname = filepath.Join(linkDir, linkFile) 627 case tar.TypeSymlink: 628 linkFn = te.fsEval.Symlink 629 } 630 631 // Link the new one. 632 if err := linkFn(linkname, path); err != nil { 633 // FIXME: Currently this can break if tar hardlink entries occur 634 // before we hit the entry those hardlinks link to. I have a 635 // feeling that such archives are invalid, but the correct 636 // way of handling this is to delay link creation until the 637 // very end. Unfortunately this won't work with symlinks 638 // (which can link to directories). 639 return errors.Wrap(err, "link") 640 } 641 642 // character device node, block device node 643 case tar.TypeChar, tar.TypeBlock: 644 // In rootless mode we have no choice but to fake this, since mknod(2) 645 // doesn't work as an unprivileged user here. 646 // 647 // TODO: We need to add the concept of a fake block device in 648 // "user.rootlesscontainers", because this workaround suffers 649 // from the obvious issue that if the file is touched (even the 650 // metadata) then it will be incorrectly copied into the layer. 651 // This would break distribution images fairly badly. 652 if te.partialRootless { 653 log.Warnf("rootless{%s} creating empty file in place of device %d:%d", hdr.Name, hdr.Devmajor, hdr.Devminor) 654 fh, err := te.fsEval.Create(path) 655 if err != nil { 656 return errors.Wrap(err, "create rootless block") 657 } 658 defer fh.Close() 659 if err := fh.Chmod(0); err != nil { 660 return errors.Wrap(err, "chmod 0 rootless block") 661 } 662 goto out 663 } 664 665 // Otherwise the handling is the same as a FIFO. 666 fallthrough 667 // fifo node 668 case tar.TypeFifo: 669 // We have to remove and then create the device. In the FIFO case we 670 // could choose not to do so, but we do it anyway just to be on the 671 // safe side. 672 673 mode := system.Tarmode(hdr.Typeflag) 674 dev := unix.Mkdev(uint32(hdr.Devmajor), uint32(hdr.Devminor)) 675 676 // Create the node. 677 if err := te.fsEval.Mknod(path, os.FileMode(int64(mode)|hdr.Mode), dev); err != nil { 678 return errors.Wrap(err, "mknod") 679 } 680 681 // We should never hit any other headers (Go abstracts them away from us), 682 // and we can't handle any custom Tar extensions. So just error out. 683 default: 684 return fmt.Errorf("unpack entry: %s: unknown typeflag '\\x%x'", hdr.Name, hdr.Typeflag) 685 } 686 687 out: 688 // Apply the metadata, which will apply any mappings necessary. We don't 689 // apply metadata for hardlinks, because hardlinks don't have any separate 690 // metadata from their link (and the tar headers might not be filled). 691 if hdr.Typeflag != tar.TypeLink { 692 if err := te.applyMetadata(path, hdr); err != nil { 693 return errors.Wrap(err, "apply hdr metadata") 694 } 695 } 696 697 // Everything is done -- the path now exists. Add it (and all its 698 // ancestors) to the set of upper paths. We first have to figure out the 699 // proper path corresponding to hdr.Name though. 700 upperPath, err := filepath.Rel(root, path) 701 if err != nil { 702 // Really shouldn't happen because of the guarantees of SecureJoinVFS. 703 return errors.Wrap(err, "find relative-to-root [should never happen]") 704 } 705 for pth := upperPath; pth != filepath.Dir(pth); pth = filepath.Dir(pth) { 706 te.upperPaths[pth] = struct{}{} 707 } 708 return nil 709 }