Skip to content

Commit e9dd232

Browse files
committed
Allow update-ldcache to work when pivot-root is not supported
This change updates the update-ldcache logic to use an alternative to pivot-root when this is not supported. This includes cases where the root filesystem is in a ramfs (e.g. when running from the kata-agent). Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent 811db8d commit e9dd232

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed

internal/ldconfig/ldconfig.go

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
"path/filepath"
2626
"strings"
2727

28+
"github.com/prometheus/procfs"
29+
2830
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
2931
)
3032

@@ -40,6 +42,7 @@ const (
4042
type Ldconfig struct {
4143
ldconfigPath string
4244
inRoot string
45+
noPivotRoot bool
4346
directories []string
4447
}
4548

@@ -50,6 +53,11 @@ func NewRunner(id string, ldconfigPath string, containerRoot string, additionala
5053
"--ldconfig-path", strings.TrimPrefix(config.NormalizeLDConfigPath("@"+ldconfigPath), "@"),
5154
"--container-root", containerRoot,
5255
}
56+
57+
if noPivotRoot() {
58+
args = append(args, "--no-pivot")
59+
}
60+
5361
args = append(args, additionalargs...)
5462

5563
return createReexecCommand(args)
@@ -69,6 +77,7 @@ func NewFromArgs(args ...string) (*Ldconfig, error) {
6977
fs := flag.NewFlagSet(args[1], flag.ExitOnError)
7078
ldconfigPath := fs.String("ldconfig-path", "", "the path to ldconfig on the host")
7179
containerRoot := fs.String("container-root", "", "the path in which ldconfig must be run")
80+
noPivot := fs.Bool("no-pivot", false, "don't use pivot_root to perform isolation")
7281
if err := fs.Parse(args[1:]); err != nil {
7382
return nil, err
7483
}
@@ -83,6 +92,7 @@ func NewFromArgs(args ...string) (*Ldconfig, error) {
8392
l := &Ldconfig{
8493
ldconfigPath: *ldconfigPath,
8594
inRoot: *containerRoot,
95+
noPivotRoot: *noPivot,
8696
directories: fs.Args(),
8797
}
8898
return l, nil
@@ -159,9 +169,18 @@ func (l *Ldconfig) prepareRoot() (string, error) {
159169
return "", fmt.Errorf("error mounting host ldconfig: %w", err)
160170
}
161171

172+
// We select the function to pivot the root based on whether pivot_root is
173+
// supported.
174+
// See https://github.com/opencontainers/runc/blob/c3d127f6e8d9f6c06d78b8329cafa8dd39f6236e/libcontainer/rootfs_linux.go#L207-L216
175+
var pivotRootFn func(string) error
176+
if l.noPivotRoot {
177+
pivotRootFn = msMoveRoot
178+
} else {
179+
pivotRootFn = pivotRoot
180+
}
162181
// We pivot to the container root for the new process, this further limits
163182
// access to the host.
164-
if err := pivotRoot(l.inRoot); err != nil {
183+
if err := pivotRootFn(l.inRoot); err != nil {
165184
return "", fmt.Errorf("error running pivot_root: %w", err)
166185
}
167186

@@ -223,3 +242,39 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
223242

224243
return nil
225244
}
245+
246+
// noPivotRoot checks whether the current root filesystem supports a pivot_root.
247+
// See https://github.com/opencontainers/runc/blob/main/libcontainer/SPEC.md#filesystem
248+
// for a discussion on when this is not the case.
249+
// If we fail to detect whether pivot-root is supported, we assume that it is supported.
250+
// The logic to check for support is adapted from kata-containers:
251+
//
252+
// https://github.com/kata-containers/kata-containers/blob/e7b9eddcede4bbe2edeb9c3af7b2358dc65da76f/src/agent/src/sandbox.rs#L150
253+
//
254+
// and checks whether "/" is mounted as a rootfs.
255+
func noPivotRoot() bool {
256+
rootFsType, err := getRootfsType("/")
257+
if err != nil {
258+
return false
259+
}
260+
return rootFsType == "rootfs"
261+
}
262+
263+
func getRootfsType(path string) (string, error) {
264+
procSelf, err := procfs.Self()
265+
if err != nil {
266+
return "", err
267+
}
268+
269+
mountStats, err := procSelf.MountStats()
270+
if err != nil {
271+
return "", err
272+
}
273+
274+
for _, mountStat := range mountStats {
275+
if mountStat.Mount == path {
276+
return mountStat.Type, nil
277+
}
278+
}
279+
return "", fmt.Errorf("mount stats for %q not found", path)
280+
}

internal/ldconfig/ldconfig_linux.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import (
2626
"os/exec"
2727
"path/filepath"
2828
"strconv"
29+
"strings"
2930
"syscall"
3031

3132
securejoin "github.com/cyphar/filepath-securejoin"
33+
"github.com/moby/sys/mountinfo"
3234
"github.com/moby/sys/reexec"
3335

3436
"github.com/opencontainers/runc/libcontainer/utils"
@@ -99,6 +101,86 @@ func pivotRoot(rootfs string) error {
99101
return nil
100102
}
101103

104+
// msMoveRoot is used
105+
// filesystem, and everything else is cleaned up.
106+
// This is adapted from the implementation here:
107+
//
108+
// https://github.com/opencontainers/runc/blob/e89a29929c775025419ab0d218a43588b4c12b9a/libcontainer/rootfs_linux.go#L1056-L1113
109+
//
110+
// With the `mount` and `unmount` calls changed to direct unix.Mount and unix.Unmount calls.
111+
func msMoveRoot(rootfs string) error {
112+
// Before we move the root and chroot we have to mask all "full" sysfs and
113+
// procfs mounts which exist on the host. This is because while the kernel
114+
// has protections against mounting procfs if it has masks, when using
115+
// chroot(2) the *host* procfs mount is still reachable in the mount
116+
// namespace and the kernel permits procfs mounts inside --no-pivot
117+
// containers.
118+
//
119+
// Users shouldn't be using --no-pivot except in exceptional circumstances,
120+
// but to avoid such a trivial security flaw we apply a best-effort
121+
// protection here. The kernel only allows a mount of a pseudo-filesystem
122+
// like procfs or sysfs if there is a *full* mount (the root of the
123+
// filesystem is mounted) without any other locked mount points covering a
124+
// subtree of the mount.
125+
//
126+
// So we try to unmount (or mount tmpfs on top of) any mountpoint which is
127+
// a full mount of either sysfs or procfs (since those are the most
128+
// concerning filesystems to us).
129+
mountinfos, err := mountinfo.GetMounts(func(info *mountinfo.Info) (skip, stop bool) {
130+
// Collect every sysfs and procfs filesystem, except for those which
131+
// are non-full mounts or are inside the rootfs of the container.
132+
if info.Root != "/" ||
133+
(info.FSType != "proc" && info.FSType != "sysfs") ||
134+
strings.HasPrefix(info.Mountpoint, rootfs) {
135+
skip = true
136+
}
137+
return
138+
})
139+
if err != nil {
140+
return err
141+
}
142+
for _, info := range mountinfos {
143+
p := info.Mountpoint
144+
// Be sure umount events are not propagated to the host.
145+
if err := unix.Mount("", p, "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil {
146+
if errors.Is(err, unix.ENOENT) {
147+
// If the mountpoint doesn't exist that means that we've
148+
// already blasted away some parent directory of the mountpoint
149+
// and so we don't care about this error.
150+
continue
151+
}
152+
return err
153+
}
154+
if err := unix.Unmount(p, unix.MNT_DETACH); err != nil {
155+
if !errors.Is(err, unix.EINVAL) && !errors.Is(err, unix.EPERM) {
156+
return err
157+
} else {
158+
// If we have not privileges for umounting (e.g. rootless), then
159+
// cover the path.
160+
if err := unix.Mount("tmpfs", p, "tmpfs", 0, ""); err != nil {
161+
return err
162+
}
163+
}
164+
}
165+
}
166+
167+
// Move the rootfs on top of "/" in our mount namespace.
168+
if err := unix.Mount(rootfs, "/", "", unix.MS_MOVE, ""); err != nil {
169+
return err
170+
}
171+
return chroot()
172+
}
173+
174+
func chroot() error {
175+
if err := unix.Chroot("."); err != nil {
176+
return &os.PathError{Op: "chroot", Path: ".", Err: err}
177+
}
178+
if err := unix.Chdir("/"); err != nil {
179+
return &os.PathError{Op: "chdir", Path: "/", Err: err}
180+
}
181+
return nil
182+
}
183+
102184
// mountLdConfig mounts the host ldconfig to the mount namespace of the hook.
103185
// We use WithProcfd to perform the mount operations to ensure that the changes
104186
// are persisted across the pivot root.

internal/ldconfig/ldconfig_other.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func pivotRoot(newroot string) error {
2828
return fmt.Errorf("not supported")
2929
}
3030

31+
func msMoveRoot(rootfs string) error {
32+
return fmt.Errorf("not supported")
33+
}
34+
3135
func mountLdConfig(hostLdconfigPath string, containerRootDirPath string) (string, error) {
3236
return "", fmt.Errorf("not supported")
3337
}

0 commit comments

Comments
 (0)