Skip to content

Commit bcabd2d

Browse files
committed
chrootarchive: Pass root via fd
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]>
1 parent 233a315 commit bcabd2d

File tree

4 files changed

+36
-32
lines changed

4 files changed

+36
-32
lines changed

pkg/chrootarchive/archive.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/containers/storage/pkg/fileutils"
1313
"github.com/containers/storage/pkg/idtools"
1414
"github.com/containers/storage/pkg/unshare"
15+
"golang.org/x/sys/unix"
1516
)
1617

1718
// NewArchiver returns a new Archiver which uses chrootarchive.Untar
@@ -82,6 +83,17 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions
8283
return err
8384
}
8485
}
86+
relDest, err := filepath.Rel(root, dest)
87+
if err != nil {
88+
return err
89+
}
90+
if relDest == "." {
91+
relDest = "/"
92+
}
93+
if relDest[0] != '/' {
94+
relDest = "/" + relDest
95+
}
96+
dest = relDest
8597

8698
r := tarArchive
8799
if decompress {
@@ -93,7 +105,12 @@ func untarHandler(tarArchive io.Reader, dest string, options *archive.TarOptions
93105
r = decompressedArchive
94106
}
95107

96-
return invokeUnpack(r, dest, options, root)
108+
rootfdRaw, err := unix.Open(root, unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
109+
if err != nil {
110+
return err
111+
}
112+
rootfd := os.NewFile(uintptr(rootfdRaw), "rootfs")
113+
return invokeUnpack(r, rootfd, dest, options)
97114
}
98115

99116
// Tar tars the requested path while chrooted to the specified root.

pkg/chrootarchive/archive_darwin.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ import (
77
)
88

99
func invokeUnpack(decompressedArchive io.Reader,
10+
rootfd *os.File,
1011
dest string,
11-
options *archive.TarOptions, root string,
12+
options *archive.TarOptions,
1213
) error {
13-
_ = root // Restricting the operation to this root is not implemented on macOS
14+
_ = rootfd // Restricting the operation to this root is not implemented on macOS
1415
return archive.Unpack(decompressedArchive, dest, options)
1516
}
1617

pkg/chrootarchive/archive_unix.go

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
"github.com/containers/storage/pkg/archive"
1818
"github.com/containers/storage/pkg/reexec"
19+
"golang.org/x/sys/unix"
1920
)
2021

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

3536
dst := flag.Arg(0)
36-
var root string
37-
if len(flag.Args()) > 1 {
38-
root = flag.Arg(1)
37+
// FD 4 is the root, passed by the caller.
38+
rootFd := os.NewFile(4, "options")
39+
if err := unix.Fchdir(int(rootFd.Fd())); err != nil {
40+
fatal(err)
3941
}
40-
41-
if root == "" {
42-
root = dst
42+
if err := chroot("."); err != nil {
43+
fatal(err)
4344
}
44-
45-
if err := chroot(root); err != nil {
45+
if err := rootFd.Close(); err != nil {
4646
fatal(err)
4747
}
4848

@@ -57,44 +57,29 @@ func untar() {
5757
os.Exit(0)
5858
}
5959

60-
func invokeUnpack(decompressedArchive io.Reader, dest string, options *archive.TarOptions, root string) error {
61-
if root == "" {
62-
return errors.New("must specify a root to chroot to")
63-
}
64-
60+
func invokeUnpack(decompressedArchive io.Reader, rootfd *os.File, dest string, options *archive.TarOptions) error {
6561
// We can't pass a potentially large exclude list directly via cmd line
6662
// because we easily overrun the kernel's max argument/environment size
6763
// when the full image list is passed (e.g. when this is used by
6864
// `docker load`). We will marshall the options via a pipe to the
6965
// child
7066
r, w, err := os.Pipe()
7167
if err != nil {
68+
rootfd.Close()
7269
return fmt.Errorf("untar pipe failure: %w", err)
7370
}
7471

75-
if root != "" {
76-
relDest, err := filepath.Rel(root, dest)
77-
if err != nil {
78-
return err
79-
}
80-
if relDest == "." {
81-
relDest = "/"
82-
}
83-
if relDest[0] != '/' {
84-
relDest = "/" + relDest
85-
}
86-
dest = relDest
87-
}
88-
89-
cmd := reexec.Command("storage-untar", dest, root)
72+
cmd := reexec.Command("storage-untar", dest)
9073
cmd.Stdin = decompressedArchive
9174

9275
cmd.ExtraFiles = append(cmd.ExtraFiles, r)
76+
cmd.ExtraFiles = append(cmd.ExtraFiles, rootfd)
9377
output := bytes.NewBuffer(nil)
9478
cmd.Stdout = output
9579
cmd.Stderr = output
9680

9781
if err := cmd.Start(); err != nil {
82+
rootfd.Close()
9883
w.Close()
9984
return fmt.Errorf("untar error on re-exec cmd: %w", err)
10085
}

pkg/chrootarchive/archive_windows.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ func chroot(path string) error {
1313
}
1414

1515
func invokeUnpack(decompressedArchive io.Reader,
16+
rootfd *os.File,
1617
dest string,
17-
options *archive.TarOptions, root string,
18+
options *archive.TarOptions
1819
) error {
1920
// Windows is different to Linux here because Windows does not support
2021
// chroot. Hence there is no point sandboxing a chrooted process to

0 commit comments

Comments
 (0)