Skip to content

Commit 9e8eabe

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 <[email protected]>
1 parent 3de63fd commit 9e8eabe

File tree

3 files changed

+146
-1
lines changed

3 files changed

+146
-1
lines changed

internal/ldconfig/ldconfig.go

Lines changed: 60 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)
@@ -66,6 +74,10 @@ func NewRunner(id string, ldconfigPath string, containerRoot string, additionala
6674
// --ldconfig-path=LDCONFIG_PATH the path to ldconfig on the host
6775
// --container-root=CONTAINER_ROOT the path in which ldconfig must be run
6876
//
77+
// The following optional flags are supported:
78+
//
79+
// --no-pivot pivot_root should not be used to provide process isolation.
80+
//
6981
// The remaining args are folders where soname symlinks need to be created.
7082
func NewFromArgs(args ...string) (*Ldconfig, error) {
7183
if len(args) < 1 {
@@ -74,6 +86,7 @@ func NewFromArgs(args ...string) (*Ldconfig, error) {
7486
fs := flag.NewFlagSet(args[1], flag.ExitOnError)
7587
ldconfigPath := fs.String("ldconfig-path", "", "the path to ldconfig on the host")
7688
containerRoot := fs.String("container-root", "", "the path in which ldconfig must be run")
89+
noPivot := fs.Bool("no-pivot", false, "don't use pivot_root to perform isolation")
7790
if err := fs.Parse(args[1:]); err != nil {
7891
return nil, err
7992
}
@@ -88,6 +101,7 @@ func NewFromArgs(args ...string) (*Ldconfig, error) {
88101
l := &Ldconfig{
89102
ldconfigPath: *ldconfigPath,
90103
inRoot: *containerRoot,
104+
noPivotRoot: *noPivot,
91105
directories: fs.Args(),
92106
}
93107
return l, nil
@@ -128,9 +142,18 @@ func (l *Ldconfig) prepareRoot() (string, error) {
128142
return "", fmt.Errorf("error mounting host ldconfig: %w", err)
129143
}
130144

145+
// We select the function to pivot the root based on whether pivot_root is
146+
// supported.
147+
// See https://github.com/opencontainers/runc/blob/c3d127f6e8d9f6c06d78b8329cafa8dd39f6236e/libcontainer/rootfs_linux.go#L207-L216
148+
var pivotRootFn func(string) error
149+
if l.noPivotRoot {
150+
pivotRootFn = msMoveRoot
151+
} else {
152+
pivotRootFn = pivotRoot
153+
}
131154
// We pivot to the container root for the new process, this further limits
132155
// access to the host.
133-
if err := pivotRoot(l.inRoot); err != nil {
156+
if err := pivotRootFn(l.inRoot); err != nil {
134157
return "", fmt.Errorf("error running pivot_root: %w", err)
135158
}
136159

@@ -177,3 +200,39 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
177200

178201
return nil
179202
}
203+
204+
// noPivotRoot checks whether the current root filesystem supports a pivot_root.
205+
// See https://github.com/opencontainers/runc/blob/main/libcontainer/SPEC.md#filesystem
206+
// for a discussion on when this is not the case.
207+
// If we fail to detect whether pivot-root is supported, we assume that it is supported.
208+
// The logic to check for support is adapted from kata-containers:
209+
//
210+
// https://github.com/kata-containers/kata-containers/blob/e7b9eddcede4bbe2edeb9c3af7b2358dc65da76f/src/agent/src/sandbox.rs#L150
211+
//
212+
// and checks whether "/" is mounted as a rootfs.
213+
func noPivotRoot() bool {
214+
rootFsType, err := getRootfsType("/")
215+
if err != nil {
216+
return false
217+
}
218+
return rootFsType == "rootfs"
219+
}
220+
221+
func getRootfsType(path string) (string, error) {
222+
procSelf, err := procfs.Self()
223+
if err != nil {
224+
return "", err
225+
}
226+
227+
mountStats, err := procSelf.MountStats()
228+
if err != nil {
229+
return "", err
230+
}
231+
232+
for _, mountStat := range mountStats {
233+
if mountStat.Mount == path {
234+
return mountStat.Type, nil
235+
}
236+
}
237+
return "", fmt.Errorf("mount stats for %q not found", path)
238+
}

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)