github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/pkg/unpriv/unpriv.go (about) 1 /* 2 * umoci: Umoci Modifies Open Containers' Images 3 * Copyright (C) 2016, 2017, 2018 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 unpriv 19 20 import ( 21 "archive/tar" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 "time" 27 28 "github.com/cyphar/filepath-securejoin" 29 "github.com/openSUSE/umoci/pkg/system" 30 "github.com/pkg/errors" 31 "golang.org/x/sys/unix" 32 ) 33 34 // fiRestore restores the state given by an os.FileInfo instance at the given 35 // path by ensuring that an Lstat(path) will return as-close-to the same 36 // os.FileInfo. 37 func fiRestore(path string, fi os.FileInfo) { 38 // archive/tar handles the OS-specific syscall stuff required to get atime 39 // and mtime information for a file. 40 hdr, _ := tar.FileInfoHeader(fi, "") 41 42 // Apply the relevant information from the FileInfo. 43 // XXX: Should we return errors here to ensure that everything is 44 // deterministic or we fail? 45 os.Chmod(path, fi.Mode()) 46 os.Chtimes(path, hdr.AccessTime, hdr.ModTime) 47 } 48 49 // splitpath splits the given path into each of the path components. 50 func splitpath(path string) []string { 51 path = filepath.Clean(path) 52 parts := strings.Split(path, string(os.PathSeparator)) 53 if filepath.IsAbs(path) { 54 parts = append([]string{string(os.PathSeparator)}, parts...) 55 } 56 return parts 57 } 58 59 // WrapFunc is a function that can be passed to Wrap. It takes a path (and 60 // presumably operates on it -- since Wrap only ensures that the path given is 61 // resolvable) and returns some form of error. 62 type WrapFunc func(path string) error 63 64 // Wrap will wrap a given function, and call it in a context where all of the 65 // parent directories in the given path argument are such that the path can be 66 // resolved (you may need to make your own changes to the path to make it 67 // readable). Note that the provided function may be called several times, and 68 // if the error returned is such that !os.IsPermission(err), then no trickery 69 // will be performed. If fn returns an error, so will this function. All of the 70 // trickery is reverted when this function returns (which is when fn returns). 71 func Wrap(path string, fn WrapFunc) error { 72 // FIXME: Should we be calling fn() here first? 73 if err := fn(path); err == nil || !os.IsPermission(errors.Cause(err)) { 74 return err 75 } 76 77 // We need to chown all of the path components we don't have execute rights 78 // to. Specifically these are the path components which are parents of path 79 // components we cannot stat. However, we must make sure to not touch the 80 // path itself. 81 parts := splitpath(filepath.Dir(path)) 82 start := len(parts) 83 for { 84 current := filepath.Join(parts[:start]...) 85 _, err := os.Lstat(current) 86 if err == nil { 87 // We've hit the first element we can chown. 88 break 89 } 90 if !os.IsPermission(err) { 91 // This is a legitimate error. 92 return errors.Wrapf(err, "unpriv.wrap: lstat parent: %s", current) 93 } 94 start-- 95 } 96 // Chown from the top down. 97 for i := start; i <= len(parts); i++ { 98 current := filepath.Join(parts[:i]...) 99 fi, err := os.Lstat(current) 100 if err != nil { 101 return errors.Wrapf(err, "unpriv.wrap: lstat parent: %s", current) 102 } 103 // Add +rwx permissions to directories. If we have the access to change 104 // the mode at all then we are the user owner (not just a group owner). 105 if err := os.Chmod(current, fi.Mode()|0700); err != nil { 106 return errors.Wrapf(err, "unpriv.wrap: chmod parent: %s", current) 107 } 108 defer fiRestore(current, fi) 109 } 110 111 // Everything is wrapped. Return from this nightmare. 112 return fn(path) 113 } 114 115 // Open is a wrapper around os.Open which has been wrapped with unpriv.Wrap to 116 // make it possible to open paths even if you do not currently have read 117 // permission. Note that the returned file handle references a path that you do 118 // not have read access to (since all changes are reverted when this function 119 // returns), so attempts to do Readdir() or similar functions that require 120 // doing lstat(2) may fail. 121 func Open(path string) (*os.File, error) { 122 var fh *os.File 123 err := Wrap(path, func(path string) error { 124 // Get information so we can revert it. 125 fi, err := os.Lstat(path) 126 if err != nil { 127 return errors.Wrap(err, "lstat file") 128 } 129 130 // Add +r permissions to the file. 131 if err := os.Chmod(path, fi.Mode()|0400); err != nil { 132 return errors.Wrap(err, "chmod +r") 133 } 134 defer fiRestore(path, fi) 135 136 // Open the damn thing. 137 fh, err = os.Open(path) 138 return err 139 }) 140 return fh, errors.Wrap(err, "unpriv.open") 141 } 142 143 // Create is a wrapper around os.Create which has been wrapped with unpriv.Wrap 144 // to make it possible to create paths even if you do not currently have read 145 // permission. Note that the returned file handle references a path that you do 146 // not have read access to (since all changes are reverted when this function 147 // returns). 148 func Create(path string) (*os.File, error) { 149 var fh *os.File 150 err := Wrap(path, func(path string) error { 151 var err error 152 fh, err = os.Create(path) 153 return err 154 }) 155 return fh, errors.Wrap(err, "unpriv.create") 156 } 157 158 // Readdir is a wrapper around (*os.File).Readdir which has been wrapper with 159 // unpriv.Wrap to make it possible to get []os.FileInfo for the set of children 160 // of the provided directory path. The interface for this is quite different to 161 // (*os.File).Readdir because we have to have a proper filesystem path in order 162 // to get the set of child FileInfos (because all of the child paths need to be 163 // resolveable). 164 func Readdir(path string) ([]os.FileInfo, error) { 165 var infos []os.FileInfo 166 err := Wrap(path, func(path string) error { 167 // Get information so we can revert it. 168 fi, err := os.Lstat(path) 169 if err != nil { 170 return errors.Wrap(err, "lstat dir") 171 } 172 173 // Add +rx permissions to the file. 174 if err := os.Chmod(path, fi.Mode()|0500); err != nil { 175 return errors.Wrap(err, "chmod +rx") 176 } 177 defer fiRestore(path, fi) 178 179 // Open the damn thing. 180 fh, err := os.Open(path) 181 if err != nil { 182 return errors.Wrap(err, "opendir") 183 } 184 defer fh.Close() 185 186 // Get the set of dirents. 187 infos, err = fh.Readdir(-1) 188 return err 189 }) 190 return infos, errors.Wrap(err, "unpriv.readdir") 191 } 192 193 // Lstat is a wrapper around os.Lstat which has been wrapped with unpriv.Wrap 194 // to make it possible to get os.FileInfo about a path even if you do not 195 // currently have the required mode bits set to resolve the path. Note that you 196 // may not have resolve access after this function returns because all of the 197 // trickery is reverted by unpriv.Wrap. 198 func Lstat(path string) (os.FileInfo, error) { 199 var fi os.FileInfo 200 err := Wrap(path, func(path string) error { 201 // Fairly simple. 202 var err error 203 fi, err = os.Lstat(path) 204 return err 205 }) 206 return fi, errors.Wrap(err, "unpriv.lstat") 207 } 208 209 // Lstatx is like Lstat but uses unix.Lstat and returns unix.Stat_t instead 210 func Lstatx(path string) (unix.Stat_t, error) { 211 var s unix.Stat_t 212 err := Wrap(path, func(path string) error { 213 return unix.Lstat(path, &s) 214 }) 215 return s, errors.Wrap(err, "unpriv.lstatx") 216 } 217 218 // Readlink is a wrapper around os.Readlink which has been wrapped with 219 // unpriv.Wrap to make it possible to get the linkname of a symlink even if you 220 // do not currently have teh required mode bits set to resolve the path. Note 221 // that you may not have resolve access after this function returns because all 222 // of this trickery is reverted by unpriv.Wrap. 223 func Readlink(path string) (string, error) { 224 var linkname string 225 err := Wrap(path, func(path string) error { 226 // Fairly simple. 227 var err error 228 linkname, err = os.Readlink(path) 229 return err 230 }) 231 return linkname, errors.Wrap(err, "unpriv.readlink") 232 } 233 234 // Symlink is a wrapper around os.Symlink which has been wrapped with 235 // unpriv.Wrap to make it possible to create a symlink even if you do not 236 // currently have the required access bits to create the symlink. Note that you 237 // may not have resolve access after this function returns because all of the 238 // trickery is reverted by unpriv.Wrap. 239 func Symlink(linkname, path string) error { 240 return errors.Wrap(Wrap(path, func(path string) error { 241 return os.Symlink(linkname, path) 242 }), "unpriv.symlink") 243 } 244 245 // Link is a wrapper around os.Link which has been wrapped with unpriv.Wrap to 246 // make it possible to create a hard link even if you do not currently have the 247 // required access bits to create the hard link. Note that you may not have 248 // resolve access after this function returns because all of the trickery is 249 // reverted by unpriv.Wrap. 250 func Link(linkname, path string) error { 251 return errors.Wrap(Wrap(path, func(path string) error { 252 // We have to double-wrap this, because you need search access to the 253 // linkname. This is safe because any common ancestors will be reverted 254 // in reverse call stack order. 255 return errors.Wrap(Wrap(linkname, func(linkname string) error { 256 return os.Link(linkname, path) 257 }), "unpriv.wrap linkname") 258 }), "unpriv.link") 259 } 260 261 // Chmod is a wrapper around os.Chmod which has been wrapped with unpriv.Wrap 262 // to make it possible to change the permission bits of a path even if you do 263 // not currently have the required access bits to access the path. 264 func Chmod(path string, mode os.FileMode) error { 265 return errors.Wrap(Wrap(path, func(path string) error { 266 return os.Chmod(path, mode) 267 }), "unpriv.chmod") 268 } 269 270 // Lchown is a wrapper around os.Lchown which has been wrapped with unpriv.Wrap 271 // to make it possible to change the owner of a path even if you do not 272 // currently have the required access bits to access the path. Note that this 273 // function is not particularly useful in most rootless scenarios. 274 // 275 // FIXME: This probably should be removed because it's questionably useful. 276 func Lchown(path string, uid, gid int) error { 277 return errors.Wrap(Wrap(path, func(path string) error { 278 return os.Lchown(path, uid, gid) 279 }), "unpriv.lchown") 280 } 281 282 // Chtimes is a wrapper around os.Chtimes which has been wrapped with 283 // unpriv.Wrap to make it possible to change the modified times of a path even 284 // if you do not currently have the required access bits to access the path. 285 func Chtimes(path string, atime, mtime time.Time) error { 286 return errors.Wrap(Wrap(path, func(path string) error { 287 return os.Chtimes(path, atime, mtime) 288 }), "unpriv.chtimes") 289 } 290 291 // Lutimes is a wrapper around system.Lutimes which has been wrapped with 292 // unpriv.Wrap to make it possible to change the modified times of a path even 293 // if you do no currently have the required access bits to access the path. 294 func Lutimes(path string, atime, mtime time.Time) error { 295 return errors.Wrap(Wrap(path, func(path string) error { 296 return system.Lutimes(path, atime, mtime) 297 }), "unpriv.lutimes") 298 } 299 300 // Remove is a wrapper around os.Remove which has been wrapped with unpriv.Wrap 301 // to make it possible to remove a path even if you do not currently have the 302 // required access bits to modify or resolve the path. 303 func Remove(path string) error { 304 return errors.Wrap(Wrap(path, os.Remove), "unpriv.remove") 305 } 306 307 // foreachSubpath executes WrapFunc for each child of the given path (not 308 // including the path itself). If path is not a directory, then WrapFunc will 309 // not be called and no error will be returned. This should be called within a 310 // context where path has already been made resolveable, however the . If WrapFunc returns an 311 // error, the first error is returned and iteration is halted. 312 func foreachSubpath(path string, wrapFn WrapFunc) error { 313 // Is the path a directory? 314 fi, err := os.Lstat(path) 315 if err != nil { 316 return errors.WithStack(err) 317 } 318 if !fi.IsDir() { 319 return nil 320 } 321 322 // Open the directory. 323 fd, err := Open(path) 324 if err != nil { 325 return errors.WithStack(err) 326 } 327 defer fd.Close() 328 329 // We need to change the mode to Readdirnames. We don't need to worry about 330 // permissions because we're already in a context with filepath.Dir(path) 331 // is at least a+rx. However, because we are calling wrapFn we need to 332 // restore the original mode immediately. 333 os.Chmod(path, fi.Mode()|0444) 334 names, err := fd.Readdirnames(-1) 335 fiRestore(path, fi) 336 if err != nil { 337 return errors.WithStack(err) 338 } 339 340 // Make iteration order consistent. 341 sort.Strings(names) 342 343 // Call on all the sub-directories. We run it in a Wrap context to ensure 344 // that the path we pass is resolveable when executed. 345 for _, name := range names { 346 subpath := filepath.Join(path, name) 347 if err := Wrap(subpath, wrapFn); err != nil { 348 return err 349 } 350 } 351 return nil 352 } 353 354 // RemoveAll is similar to os.RemoveAll but with all of the internal functions 355 // wrapped with unpriv.Wrap to make it possible to remove a path (even if it 356 // has child paths) even if you do not currently have enough access bits. 357 func RemoveAll(path string) error { 358 return errors.Wrap(Wrap(path, func(path string) error { 359 // If remove works, we're done. 360 err := os.Remove(path) 361 if err == nil || os.IsNotExist(errors.Cause(err)) { 362 return nil 363 } 364 365 // Is this a directory? 366 fi, serr := os.Lstat(path) 367 if serr != nil { 368 // Use securejoin's IsNotExist to handle ENOTDIR sanely. 369 if securejoin.IsNotExist(errors.Cause(serr)) { 370 serr = nil 371 } 372 return errors.Wrap(serr, "lstat") 373 } 374 // Return error from remove if it's not a directory. 375 if !fi.IsDir() { 376 return errors.Wrap(err, "remove non-directory") 377 } 378 err = nil 379 380 err1 := foreachSubpath(path, func(subpath string) error { 381 err2 := RemoveAll(subpath) 382 if err == nil { 383 err = err2 384 } 385 return nil 386 }) 387 if err1 != nil { 388 // We must have hit a race, but we don't care. 389 if os.IsNotExist(errors.Cause(err1)) { 390 err1 = nil 391 } 392 return errors.Wrap(err1, "foreach subpath") 393 } 394 395 // Remove the directory. This should now work. 396 err1 = os.Remove(path) 397 if err1 == nil || os.IsNotExist(errors.Cause(err1)) { 398 return nil 399 } 400 if err == nil { 401 err = err1 402 } 403 return errors.Wrap(err, "remove") 404 }), "unpriv.removeall") 405 } 406 407 // Mkdir is a wrapper around os.Mkdir which has been wrapped with unpriv.Wrap 408 // to make it possible to remove a path even if you do not currently have the 409 // required access bits to modify or resolve the path. 410 func Mkdir(path string, perm os.FileMode) error { 411 return errors.Wrap(Wrap(path, func(path string) error { 412 return os.Mkdir(path, perm) 413 }), "unpriv.mkdir") 414 } 415 416 // MkdirAll is similar to os.MkdirAll but in order to implement it properly all 417 // of the internal functions were wrapped with unpriv.Wrap to make it possible 418 // to create a path even if you do not currently have enough access bits. 419 func MkdirAll(path string, perm os.FileMode) error { 420 return errors.Wrap(Wrap(path, func(path string) error { 421 // Check whether the path already exists. 422 fi, err := os.Stat(path) 423 if err == nil { 424 if fi.IsDir() { 425 return nil 426 } 427 return &os.PathError{Op: "mkdir", Path: path, Err: unix.ENOTDIR} 428 } 429 430 // Create parent. 431 parent := filepath.Dir(path) 432 if parent != "." && parent != "/" { 433 err = MkdirAll(parent, perm) 434 if err != nil { 435 return err 436 } 437 } 438 439 // Parent exists, now we can create the path. 440 err = os.Mkdir(path, perm) 441 if err != nil { 442 // Handle "foo/.". 443 fi, err1 := os.Lstat(path) 444 if err1 == nil && fi.IsDir() { 445 return nil 446 } 447 return err 448 } 449 return nil 450 }), "unpriv.mkdirall") 451 } 452 453 // Mknod is a wrapper around unix.Mknod which has been wrapped with unpriv.Wrap 454 // to make it possible to remove a path even if you do not currently have the 455 // required access bits to modify or resolve the path. 456 func Mknod(path string, mode os.FileMode, dev uint64) error { 457 return errors.Wrap(Wrap(path, func(path string) error { 458 return unix.Mknod(path, uint32(mode), int(dev)) 459 }), "unpriv.mknod") 460 } 461 462 // Llistxattr is a wrapper around system.Llistxattr which has been wrapped with 463 // unpriv.Wrap to make it possible to remove a path even if you do not 464 // currently have the required access bits to resolve the path. 465 func Llistxattr(path string) ([]string, error) { 466 var xattrs []string 467 err := Wrap(path, func(path string) error { 468 var err error 469 xattrs, err = system.Llistxattr(path) 470 return err 471 }) 472 return xattrs, errors.Wrap(err, "unpriv.llistxattr") 473 } 474 475 // Lremovexattr is a wrapper around system.Lremovexattr which has been wrapped 476 // with unpriv.Wrap to make it possible to remove a path even if you do not 477 // currently have the required access bits to resolve the path. 478 func Lremovexattr(path, name string) error { 479 return errors.Wrap(Wrap(path, func(path string) error { 480 return unix.Lremovexattr(path, name) 481 }), "unpriv.lremovexattr") 482 } 483 484 // Lsetxattr is a wrapper around system.Lsetxattr which has been wrapped 485 // with unpriv.Wrap to make it possible to set a path even if you do not 486 // currently have the required access bits to resolve the path. 487 func Lsetxattr(path, name string, value []byte, flags int) error { 488 return errors.Wrap(Wrap(path, func(path string) error { 489 return unix.Lsetxattr(path, name, value, flags) 490 }), "unpriv.lsetxattr") 491 } 492 493 // Lgetxattr is a wrapper around system.Lgetxattr which has been wrapped 494 // with unpriv.Wrap to make it possible to get a path even if you do not 495 // currently have the required access bits to resolve the path. 496 func Lgetxattr(path, name string) ([]byte, error) { 497 var value []byte 498 err := Wrap(path, func(path string) error { 499 var err error 500 value, err = system.Lgetxattr(path, name) 501 return err 502 }) 503 return value, errors.Wrap(err, "unpriv.lgetxattr") 504 } 505 506 // Lclearxattrs is similar to system.Lclearxattrs but in order to implement it 507 // properly all of the internal functions were wrapped with unpriv.Wrap to make 508 // it possible to create a path even if you do not currently have enough access 509 // bits. 510 func Lclearxattrs(path string, except map[string]struct{}) error { 511 return errors.Wrap(Wrap(path, func(path string) error { 512 names, err := Llistxattr(path) 513 if err != nil { 514 return err 515 } 516 for _, name := range names { 517 if _, skip := except[name]; skip { 518 continue 519 } 520 if err := Lremovexattr(path, name); err != nil { 521 // SELinux won't let you change security.selinux (for obvious 522 // security reasons), so we don't clear xattrs if attempting to 523 // clear them causes an EPERM. This EPERM will not be due to 524 // resolution issues (Llistxattr already has done that for us). 525 if os.IsPermission(errors.Cause(err)) { 526 continue 527 } 528 return err 529 } 530 } 531 return nil 532 }), "unpriv.lclearxattrs") 533 } 534 535 // walk is the inner implementation of Walk. 536 func walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error { 537 // Always run walkFn first. If we're not a directory there's no children to 538 // iterate over and so we bail even if there wasn't an error. 539 err := walkFn(path, info, nil) 540 if !info.IsDir() || err != nil { 541 return err 542 } 543 544 // Now just execute walkFn over each subpath. 545 return foreachSubpath(path, func(subpath string) error { 546 info, err := Lstat(subpath) 547 if err != nil { 548 // If it doesn't exist, just pass it directly to walkFn. 549 if err := walkFn(subpath, info, err); err != nil { 550 // Ignore SkipDir. 551 if errors.Cause(err) != filepath.SkipDir { 552 return err 553 } 554 } 555 } else { 556 if err := walk(subpath, info, walkFn); err != nil { 557 // Ignore error if it's SkipDir and subpath is a directory. 558 if !(info.IsDir() && errors.Cause(err) == filepath.SkipDir) { 559 return err 560 } 561 } 562 } 563 return nil 564 }) 565 } 566 567 // Walk is a reimplementation of filepath.Walk, wrapping all of the relevant 568 // function calls with Wrap, allowing you to walk over a tree even in the face 569 // of multiple nested cases where paths are not normally accessible. The 570 // os.FileInfo passed to walkFn is the "pristine" version (as opposed to the 571 // currently-on-disk version that may have been temporarily modified by Wrap). 572 func Walk(root string, walkFn filepath.WalkFunc) error { 573 return Wrap(root, func(root string) error { 574 info, err := Lstat(root) 575 if err != nil { 576 err = walkFn(root, nil, err) 577 } else { 578 err = walk(root, info, walkFn) 579 } 580 if errors.Cause(err) == filepath.SkipDir { 581 err = nil 582 } 583 return errors.Wrap(err, "unpriv.walk") 584 }) 585 }