Skip to content

Commit

Permalink
chrootarchive: Pass root via fd
Browse files Browse the repository at this point in the history
I'd like to support passing a file descriptor root for the
container storage, and not an absolute path.

In the bootc codebase (partially a philosophy inherited
from ostree) we've heavily invested in fd-relative accesses,
primarily because it's common for us to operate in different
namespaces/roots, and fd-relative access avoids a lot of
possible footguns when dealing with absolute paths. It's
also more efficient, avoiding the need for the kernel to
traverse full paths a lot.

This is just one of a few preparatory changes necessary
in making it work to do:

`podman --root=/proc/self/fd/3 --runroot=... pull busybox`

Note that as part of doing this, I refactored code here
to move the "split root and dest" logic into the caller...
there was some logically dead code here because

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Jul 29, 2024
1 parent 233a315 commit 309b451
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 30 deletions.
17 changes: 16 additions & 1 deletion pkg/chrootarchive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions
return err
}
}
relDest, err := filepath.Rel(root, dest)
if err != nil {
return err
}
if relDest == "." {
relDest = "/"
}
if relDest[0] != '/' {
relDest = "/" + relDest
}
dest = relDest

r := tarArchive
if decompress {
Expand All @@ -93,7 +104,11 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions
r = decompressedArchive
}

return invokeUnpack(r, dest, options, root)
rootfd, err := openDirectoryFd(root)
if err != nil {
return err
}
return invokeUnpack(r, rootfd, dest, options)
}

// Tar tars the requested path while chrooted to the specified root.
Expand Down
16 changes: 14 additions & 2 deletions pkg/chrootarchive/archive_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,31 @@ package chrootarchive

import (
"io"
"os"

"golang.org/x/sys/unix"

"github.com/containers/storage/pkg/archive"
)

func invokeUnpack(decompressedArchive io.Reader,
rootfd *os.File,
dest string,
options *archive.TarOptions, root string,
options *archive.TarOptions,
) error {
_ = root // Restricting the operation to this root is not implemented on macOS
_ = rootfd // Restricting the operation to this root is not implemented on macOS
return archive.Unpack(decompressedArchive, dest, options)
}

func invokePack(srcPath string, options *archive.TarOptions, root string) (io.ReadCloser, error) {
_ = root // Restricting the operation to this root is not implemented on macOS
return archive.TarWithOptions(srcPath, options)
}

func openDirectoryFd(path string) (*os.File, error) {
rootfdRaw, err := unix.Open(path, unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(rootfdRaw), "rootfs"), nil
}
45 changes: 19 additions & 26 deletions pkg/chrootarchive/archive_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/reexec"
"golang.org/x/sys/unix"
)

// untar is the entry-point for storage-untar on re-exec. This is not used on
Expand All @@ -33,16 +34,15 @@ func untar() {
}

dst := flag.Arg(0)
var root string
if len(flag.Args()) > 1 {
root = flag.Arg(1)
// FD 4 is the root, passed by the caller.
rootFd := os.NewFile(4, "options")
if err := unix.Fchdir(int(rootFd.Fd())); err != nil {
fatal(err)
}

if root == "" {
root = dst
if err := chroot("."); err != nil {
fatal(err)
}

if err := chroot(root); err != nil {
if err := rootFd.Close(); err != nil {
fatal(err)
}

Expand All @@ -57,44 +57,37 @@ func untar() {
os.Exit(0)
}

func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
if root == "" {
return errors.New("must specify a root to chroot to")
func openDirectoryFd(path string) (*os.File, error) {
rootfdRaw, err := unix.Open(path, unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return nil, err
}
return os.NewFile(uintptr(rootfdRaw), "rootfs"), nil
}

func invokeUnpack(decompressedArchive io.Reader, rootfd *os.File, dest string, options *archive.TarOptions) error {
// We can't pass a potentially large exclude list directly via cmd line
// because we easily overrun the kernel's max argument/environment size
// when the full image list is passed (e.g. when this is used by
// `docker load`). We will marshall the options via a pipe to the
// child
r, w, err := os.Pipe()
if err != nil {
rootfd.Close()
return fmt.Errorf("untar pipe failure: %w", err)
}

if root != "" {
relDest, err := filepath.Rel(root, dest)
if err != nil {
return err
}
if relDest == "." {
relDest = "/"
}
if relDest[0] != '/' {
relDest = "/" + relDest
}
dest = relDest
}

cmd := reexec.Command("storage-untar", dest, root)
cmd := reexec.Command("storage-untar", dest)
cmd.Stdin = decompressedArchive

cmd.ExtraFiles = append(cmd.ExtraFiles, r)
cmd.ExtraFiles = append(cmd.ExtraFiles, rootfd)
output := bytes.NewBuffer(nil)
cmd.Stdout = output
cmd.Stderr = output

if err := cmd.Start(); err != nil {
rootfd.Close()
w.Close()
return fmt.Errorf("untar error on re-exec cmd: %w", err)
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/chrootarchive/archive_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chrootarchive

import (
"io"
"os"

"github.com/containers/storage/pkg/archive"
"github.com/containers/storage/pkg/longpath"
Expand All @@ -12,9 +13,15 @@ func chroot(path string) error {
return nil
}

// openDirectoryFd is unused, no sandboxing on windows
func openDirectoryFd(path string) (*os.File, error) {
return nil, nil
}

func invokeUnpack(decompressedArchive io.Reader,
rootfd *os.File,
dest string,
options *archive.TarOptions, root string,
options *archive.TarOptions,
) error {
// Windows is different to Linux here because Windows does not support
// chroot. Hence there is no point sandboxing a chrooted process to
Expand Down

0 comments on commit 309b451

Please sign in to comment.