Skip to content

Commit c678c8e

Browse files
avagingvisor-bot
authored andcommitted
runsc: create file as destination for file mount
Linux does not allow mounting files on top of directories and vice versa. This can lead to unexpected behavior and issues, such as `dockerd` failing to start when it encounters a file mount that appears as a directory entry during `/dev` enumeration. After this change, new mount destinations (files or directories) are created with permissions (0644 for files, 0755 for directories) consistent with `runc` behavior. Previously, new directories were created with 0777. The test case that request a bind mount into /dev/fd has been removed. /dev/fd is a symlink to /proc/self/fd. PiperOrigin-RevId: 753398796
1 parent d30c58e commit c678c8e

File tree

3 files changed

+219
-23
lines changed

3 files changed

+219
-23
lines changed

runsc/boot/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ go_test(
156156
],
157157
library = ":boot",
158158
deps = [
159+
"//pkg/abi/linux",
159160
"//pkg/control/server",
160161
"//pkg/cpuid",
161162
"//pkg/fspath",

runsc/boot/loader_test.go

+113-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/moby/sys/capability"
2626
specs "github.com/opencontainers/runtime-spec/specs-go"
2727
"golang.org/x/sys/unix"
28+
"gvisor.dev/gvisor/pkg/abi/linux"
2829
"gvisor.dev/gvisor/pkg/control/server"
2930
"gvisor.dev/gvisor/pkg/cpuid"
3031
"gvisor.dev/gvisor/pkg/fspath"
@@ -403,11 +404,6 @@ func createMountTestcases() []*CreateMountTestcase {
403404
Destination: "/dev",
404405
Type: "tmpfs",
405406
},
406-
{
407-
// Mounted by runsc by default.
408-
Destination: "/dev/fd",
409-
Type: "tmpfs",
410-
},
411407
{
412408
// Mount with the same prefix.
413409
Destination: "/dev/fd-foo",
@@ -508,6 +504,118 @@ func TestCreateMountNamespace(t *testing.T) {
508504
}
509505
}
510506

507+
type createMountPointTestCase struct {
508+
name string
509+
path string
510+
mode linux.FileMode
511+
fail bool
512+
}
513+
514+
func TestCreateMountPoint(t *testing.T) {
515+
var createMountPointTestCases = []createMountPointTestCase{
516+
{
517+
name: "File",
518+
path: "/test/test-file",
519+
mode: linux.ModeRegular,
520+
},
521+
{
522+
name: "Directory",
523+
path: "/test/test-dir",
524+
mode: linux.ModeDirectory,
525+
},
526+
{
527+
name: "FileWithIntDirs",
528+
path: "/test/a/b/c/test-file",
529+
mode: linux.ModeRegular,
530+
},
531+
{
532+
name: "DirectoryWithIntDirs",
533+
path: "/test/d/e/f/g/test-dir",
534+
mode: linux.ModeDirectory,
535+
},
536+
{
537+
name: "ExistingFile",
538+
path: "/test/test-file",
539+
mode: linux.ModeRegular,
540+
},
541+
{
542+
name: "ExistingDirectory",
543+
path: "/test/test-dir",
544+
mode: linux.ModeDirectory,
545+
},
546+
{
547+
name: "DirVSFile",
548+
path: "/test/test-file",
549+
mode: linux.ModeDirectory,
550+
fail: true,
551+
},
552+
{
553+
name: "FileVSDir",
554+
path: "/test/test-dir",
555+
mode: linux.ModeRegular,
556+
fail: true,
557+
},
558+
}
559+
560+
spec := testSpec()
561+
spec.Root = &specs.Root{
562+
Path: os.TempDir(),
563+
}
564+
spec.Mounts = append(spec.Mounts, specs.Mount{
565+
Destination: "/test",
566+
Type: "tmpfs",
567+
})
568+
569+
t.Logf("Using root: %q", spec.Root.Path)
570+
l, loaderCleanup, err := createLoader(testConfig(), spec)
571+
if err != nil {
572+
t.Fatalf("failed to create loader: %v", err)
573+
}
574+
defer l.Destroy()
575+
defer loaderCleanup()
576+
577+
l.mu.Lock()
578+
defer l.mu.Unlock()
579+
mntr := newContainerMounter(&l.root, l.k, l.mountHints, l.sharedMounts, "", l.sandboxID)
580+
ctx := l.k.SupervisorContext()
581+
creds := auth.NewRootCredentials(l.root.procArgs.Credentials.UserNamespace)
582+
mns, err := mntr.mountAll(ctx, creds, l.root.spec, l.root.conf, &l.root.procArgs)
583+
if err != nil {
584+
t.Fatalf("mountAll: %v", err)
585+
}
586+
587+
root := mns.Root(ctx)
588+
defer root.DecRef(ctx)
589+
590+
for _, tc := range createMountPointTestCases {
591+
t.Run(tc.name, func(t *testing.T) {
592+
if err := mntr.makeMountPoint(ctx, creds, mns, tc.path, tc.mode); err != nil {
593+
if tc.fail {
594+
return
595+
}
596+
t.Fatalf("makeMountPoint failed: %v", err)
597+
} else {
598+
if tc.fail {
599+
t.Fatalf("makeMountPoint doesn't fail")
600+
}
601+
}
602+
target := &vfs.PathOperation{
603+
Root: root,
604+
Start: root,
605+
Path: fspath.Parse(tc.path),
606+
}
607+
608+
if mode, err := mntr.getPathMode(ctx, creds, target); err != nil {
609+
t.Errorf("expected path %v, but got error %v", tc.path, err)
610+
} else {
611+
if mode.IsDir() != tc.mode.IsDir() {
612+
t.Errorf("path mode %v doesn't match the mount mode %v", mode, tc.mode)
613+
}
614+
}
615+
})
616+
}
617+
}
618+
511619
func TestMain(m *testing.M) {
512620
cpuid.Initialize()
513621
seccheck.Initialize()

runsc/boot/vfs.go

+105-18
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,20 @@ func (c *containerMounter) prepareMounts() ([]mountInfo, error) {
813813
return mounts, nil
814814
}
815815

816+
// getPathMode returns file mode for the specified path.
817+
func (c *containerMounter) getPathMode(ctx context.Context, creds *auth.Credentials, path *vfs.PathOperation) (linux.FileMode, error) {
818+
stat, err := c.k.VFS().StatAt(ctx, creds, path, &vfs.StatOptions{
819+
Mask: linux.STATX_TYPE,
820+
})
821+
if err != nil {
822+
return 0, err
823+
}
824+
if stat.Mask&linux.STATX_TYPE == 0 {
825+
return 0, fmt.Errorf("failed to get file type")
826+
}
827+
return linux.FileMode(stat.Mode), nil
828+
}
829+
816830
func (c *containerMounter) mountSubmount(ctx context.Context, spec *specs.Spec, conf *config.Config, mns *vfs.MountNamespace, creds *auth.Credentials, submount *mountInfo) (*vfs.Mount, error) {
817831
fsName, opts, err := getMountNameAndOptions(spec, conf, submount, c.productName, c.containerName)
818832
if err != nil {
@@ -823,10 +837,6 @@ func (c *containerMounter) mountSubmount(ctx context.Context, spec *specs.Spec,
823837
return nil, nil
824838
}
825839

826-
if err := c.makeMountPoint(ctx, creds, mns, submount.mount.Destination); err != nil {
827-
return nil, fmt.Errorf("creating mount point %q: %w", submount.mount.Destination, err)
828-
}
829-
830840
if submount.goferMountConf.ShouldUseOverlayfs() {
831841
log.Infof("Adding overlay on top of mount %q", submount.mount.Destination)
832842
var cleanup func()
@@ -838,18 +848,42 @@ func (c *containerMounter) mountSubmount(ctx context.Context, spec *specs.Spec,
838848
fsName = overlay.Name
839849
}
840850

851+
mount := submount.mount
841852
root := mns.Root(ctx)
842853
defer root.DecRef(ctx)
843854
target := &vfs.PathOperation{
844855
Root: root,
845856
Start: root,
846-
Path: fspath.Parse(submount.mount.Destination),
857+
Path: fspath.Parse(mount.Destination),
858+
}
859+
mnt, err := c.k.VFS().MountDisconnected(ctx, creds, "", fsName, opts)
860+
if err != nil {
861+
return nil, fmt.Errorf("mounting %q (type:%s, dest: %q): %w, opts: %v",
862+
mount.Source, mount.Type, mount.Destination, err, opts)
847863
}
848-
mnt, err := c.k.VFS().MountAt(ctx, creds, "", target, fsName, opts)
864+
defer mnt.DecRef(ctx)
865+
866+
vd := vfs.MakeVirtualDentry(mnt, mnt.Root())
867+
rootPath := &vfs.PathOperation{
868+
Root: vd,
869+
Start: vd,
870+
}
871+
872+
rootMode, err := c.getPathMode(ctx, creds, rootPath)
849873
if err != nil {
850-
return nil, fmt.Errorf("failed to mount %q (type: %s): %w, opts: %v", submount.mount.Destination, submount.mount.Type, err, opts)
874+
return nil, fmt.Errorf("stat mount %q (dest: %q): %w", mount.Source, mount.Destination, err)
875+
}
876+
if err := c.makeMountPoint(ctx, creds, mns, mount.Destination, rootMode); err != nil {
877+
return nil, fmt.Errorf("creating mount point %q: %w", mount.Destination, err)
851878
}
852-
log.Infof("Mounted %q to %q type: %s, internal-options: %q", submount.mount.Source, submount.mount.Destination, submount.mount.Type, opts.GetFilesystemOptions.Data)
879+
880+
if err := c.k.VFS().ConnectMountAt(ctx, creds, mnt, target); err != nil {
881+
return nil, fmt.Errorf("attaching %q to %q (type: %s): %w, opts: %v",
882+
mount.Source, mount.Destination, mount.Type, err, opts)
883+
}
884+
885+
log.Infof("Mounted %q to %q type: %s, internal-options: %q",
886+
mount.Source, mount.Destination, mount.Type, opts.GetFilesystemOptions.Data)
853887
return mnt, nil
854888
}
855889

@@ -1225,7 +1259,17 @@ func (c *containerMounter) mountSharedSubmount(ctx context.Context, conf *config
12251259
Path: fspath.Parse(mntInfo.mount.Destination),
12261260
}
12271261

1228-
if err := c.makeMountPoint(ctx, creds, mns, mntInfo.mount.Destination); err != nil {
1262+
vd := vfs.MakeVirtualDentry(newMnt, newMnt.Root())
1263+
rootPath := &vfs.PathOperation{
1264+
Root: vd,
1265+
Start: vd,
1266+
}
1267+
rootMode, err := c.getPathMode(ctx, creds, rootPath)
1268+
if err != nil {
1269+
return nil, err
1270+
}
1271+
1272+
if err := c.makeMountPoint(ctx, creds, mns, mntInfo.mount.Destination, rootMode); err != nil {
12291273
return nil, fmt.Errorf("creating mount point %q: %w", mntInfo.mount.Destination, err)
12301274
}
12311275

@@ -1236,24 +1280,67 @@ func (c *containerMounter) mountSharedSubmount(ctx context.Context, conf *config
12361280
return newMnt, nil
12371281
}
12381282

1239-
func (c *containerMounter) makeMountPoint(ctx context.Context, creds *auth.Credentials, mns *vfs.MountNamespace, dest string) error {
1283+
// makeMountPoint creates the mount point destination, ensuring its type (file
1284+
// or directory) matches `rootMode`. If the destination already exists, it just
1285+
// verifies that its type is consistent with `rootMode`; otherwise, the ENOTDIR
1286+
// error is returned.
1287+
func (c *containerMounter) makeMountPoint(
1288+
ctx context.Context,
1289+
creds *auth.Credentials,
1290+
mns *vfs.MountNamespace,
1291+
dest string,
1292+
rootMode linux.FileMode,
1293+
) error {
12401294
root := mns.Root(ctx)
12411295
defer root.DecRef(ctx)
1296+
12421297
target := &vfs.PathOperation{
12431298
Root: root,
12441299
Start: root,
12451300
Path: fspath.Parse(dest),
12461301
}
1247-
// First check if mount point exists. When overlay is enabled, gofer doesn't
1248-
// allow changes to the FS, making MakeSytheticMountpoint() ineffective
1249-
// because MkdirAt fails with EROFS even if file exists.
1250-
vd, err := c.k.VFS().GetDentryAt(ctx, creds, target, &vfs.GetDentryOptions{})
1251-
if err == nil {
1252-
// File exists, we're done.
1253-
vd.DecRef(ctx)
1302+
1303+
fs := c.k.VFS()
1304+
1305+
mode, err := c.getPathMode(ctx, creds, target)
1306+
// First check if mount point exists.
1307+
switch {
1308+
case err == nil:
1309+
if mode.IsDir() != rootMode.IsDir() {
1310+
if rootMode.IsDir() {
1311+
return fmt.Errorf("mountpoint %q isn't a directory", dest)
1312+
} else {
1313+
return fmt.Errorf("mountpoint %q isn't not a file", dest)
1314+
}
1315+
}
1316+
// Target already exists.
12541317
return nil
1318+
case linuxerr.Equals(linuxerr.ENOENT, err):
1319+
// Expected, we will create the mount point.
1320+
default:
1321+
return fmt.Errorf("stat failed for %q during mountpoint creation: %w", dest, err)
1322+
}
1323+
1324+
mkdirOpts := &vfs.MkdirOptions{Mode: 0755, ForSyntheticMountpoint: true}
1325+
1326+
// Make sure the parent directory of target exists.
1327+
if err := fs.MkdirAllAt(ctx, path.Dir(dest), root, creds, mkdirOpts, true /* mustBeDir */); err != nil {
1328+
return fmt.Errorf("failed to create parent directory of mountpoint %q: %w", dest, err)
1329+
}
1330+
1331+
if rootMode.IsDir() {
1332+
if err := fs.MkdirAt(ctx, creds, target, mkdirOpts); err != nil {
1333+
return fmt.Errorf("failed to create directory mountpoint %q: %w", dest, err)
1334+
}
1335+
} else {
1336+
mknodOpts := &vfs.MknodOptions{
1337+
Mode: linux.FileMode(linux.S_IFREG | 0644),
1338+
}
1339+
if err := fs.MknodAt(ctx, creds, target, mknodOpts); err != nil {
1340+
return fmt.Errorf("failed to create file mountpoint %q: %w", dest, err)
1341+
}
12551342
}
1256-
return c.k.VFS().MakeSyntheticMountpoint(ctx, dest, root, creds)
1343+
return nil
12571344
}
12581345

12591346
// configureRestore returns an updated context.Context including filesystem

0 commit comments

Comments
 (0)