Skip to content

Commit 905c667

Browse files
nixprimegvisor-bot
authored andcommitted
Establish lock ordering for vfs.FilesystemImpl.PrependPath.
- Add type parameter Filesystem to vfs/genericfstree, which is required to provide `ancestryMu sync.RWMutex`, and add such a RWMutex to all FSImpls that use genericfstree. - Modify genericfstree.PrependPath() and genericfstree.IsDescendant() to use ancestryMu to ensure atomicity. For callers of genericfstree.PrependPath(), this means that (broader) FSImpl locks no longer need to be held during the call. For callers of genericfstree.IsDescendant(), this means that IsDescendant() now has the same atomicity as Linux's fs/dcache.c:is_subdir(), so remove documentation warnings about its non-atomicity. PiperOrigin-RevId: 696273175
1 parent 4f92c28 commit 905c667

29 files changed

+223
-185
lines changed

pkg/sentry/fsimpl/erofs/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ go_template_instance(
1414
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
1515
types = {
1616
"Dentry": "dentry",
17+
"Filesystem": "filesystem",
1718
},
1819
)
1920

@@ -70,6 +71,7 @@ go_library(
7071
"//pkg/sentry/memmap",
7172
"//pkg/sentry/socket/unix/transport",
7273
"//pkg/sentry/vfs",
74+
"//pkg/sync",
7375
"//pkg/usermem",
7476
],
7577
)

pkg/sentry/fsimpl/erofs/erofs.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"os"
2020
"runtime"
2121
"strconv"
22-
"sync"
2322
"sync/atomic"
2423

2524
"gvisor.dev/gvisor/pkg/abi/linux"
@@ -30,6 +29,7 @@ import (
3029
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
3130
"gvisor.dev/gvisor/pkg/sentry/memmap"
3231
"gvisor.dev/gvisor/pkg/sentry/vfs"
32+
"gvisor.dev/gvisor/pkg/sync"
3333
)
3434

3535
// Name is the filesystem name. It is part of the interface used by users,
@@ -72,6 +72,9 @@ type filesystem struct {
7272
// reduce the lock contention. Bucket is chosen based on the hash calculation
7373
// on nid in filesystem.inodeBucket.
7474
inodeBuckets []inodeBucket
75+
76+
// ancestryMu is required by genericfstree.
77+
ancestryMu sync.RWMutex `state:"nosave"`
7578
}
7679

7780
// InternalFilesystemOptions may be passed as

pkg/sentry/fsimpl/erofs/filesystem.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -430,15 +430,15 @@ func (fs *filesystem) RemoveXattrAt(ctx context.Context, rp *vfs.ResolvingPath,
430430

431431
// PrependPath implements vfs.FilesystemImpl.PrependPath.
432432
func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
433-
return genericPrependPath(vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
433+
return genericPrependPath(fs, vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
434+
}
435+
436+
// IsDescendant implements vfs.FilesystemImpl.IsDescendant.
437+
func (fs *filesystem) IsDescendant(vfsroot, vd vfs.VirtualDentry) bool {
438+
return genericIsDescendant(fs, vfsroot.Dentry(), vd.Dentry().Impl().(*dentry))
434439
}
435440

436441
// MountOptions implements vfs.FilesystemImpl.MountOptions.
437442
func (fs *filesystem) MountOptions() string {
438443
return fs.mopts
439444
}
440-
441-
// IsDescendant implements vfs.FilesystemImpl.IsDescendant.
442-
func (fs *filesystem) IsDescendant(vfsroot, vd vfs.VirtualDentry) bool {
443-
return genericIsDescendant(vfsroot.Dentry(), vd.Dentry().Impl().(*dentry))
444-
}

pkg/sentry/fsimpl/gofer/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ go_template_instance(
4949
template = "//pkg/sentry/vfs/genericfstree:generic_fstree",
5050
types = {
5151
"Dentry": "dentry",
52+
"Filesystem": "filesystem",
5253
},
5354
)
5455

pkg/sentry/fsimpl/gofer/dentry_impl.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -538,14 +538,14 @@ func (d *dentry) restoreFile(ctx context.Context, opts *vfs.CompleteRestoreOptio
538538
inode, err := controlFD.Walk(ctx, d.name)
539539
if err != nil {
540540
if !dt.isDir() || !dt.forMountpoint {
541-
return fmt.Errorf("failed to walk %q of type %x: %w", genericDebugPathname(d), dt.fileType(), err)
541+
return fmt.Errorf("failed to walk %q of type %x: %w", genericDebugPathname(d.fs, d), dt.fileType(), err)
542542
}
543543

544544
// Recreate directories that were created during volume mounting, since
545545
// during restore we don't attempt to remount them.
546546
inode, err = controlFD.MkdirAt(ctx, d.name, linux.FileMode(d.mode.Load()), lisafs.UID(d.uid.Load()), lisafs.GID(d.gid.Load()))
547547
if err != nil {
548-
return fmt.Errorf("failed to create mountpoint directory at %q: %w", genericDebugPathname(d), err)
548+
return fmt.Errorf("failed to create mountpoint directory at %q: %w", genericDebugPathname(d.fs, d), err)
549549
}
550550
}
551551
return dt.restoreFile(ctx, &inode, opts)
@@ -558,21 +558,21 @@ func (d *dentry) restoreFile(ctx context.Context, opts *vfs.CompleteRestoreOptio
558558
})
559559
if err != nil {
560560
if !dt.isDir() || !dt.forMountpoint {
561-
return fmt.Errorf("failed to walk %q of type %x: %w", genericDebugPathname(d), dt.fileType(), err)
561+
return fmt.Errorf("failed to walk %q of type %x: %w", genericDebugPathname(d.fs, d), dt.fileType(), err)
562562
}
563563

564564
// Recreate directories that were created during volume mounting, since
565565
// during restore we don't attempt to remount them.
566566
if err := unix.Mkdirat(controlFD, d.name, d.mode.Load()); err != nil {
567-
return fmt.Errorf("failed to create mountpoint directory at %q: %w", genericDebugPathname(d), err)
567+
return fmt.Errorf("failed to create mountpoint directory at %q: %w", genericDebugPathname(d.fs, d), err)
568568
}
569569

570570
// Try again...
571571
childFD, err = tryOpen(func(flags int) (int, error) {
572572
return unix.Openat(controlFD, d.name, flags, 0)
573573
})
574574
if err != nil {
575-
return fmt.Errorf("failed to open %q: %w", genericDebugPathname(d), err)
575+
return fmt.Errorf("failed to open %q: %w", genericDebugPathname(d.fs, d), err)
576576
}
577577
}
578578
return dt.restoreFile(ctx, childFD, opts)

pkg/sentry/fsimpl/gofer/directfs_dentry.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ func (d *directfsDentry) getCreatedChild(name string, uid, gid int, isDir bool)
452452
deleteChild := func() {
453453
// Best effort attempt to remove the newly created child on failure.
454454
if err := unix.Unlinkat(d.controlFD, name, unlinkFlags); err != nil {
455-
log.Warningf("error unlinking newly created child %q after failure: %v", filepath.Join(genericDebugPathname(&d.dentry), name), err)
455+
log.Warningf("error unlinking newly created child %q after failure: %v", filepath.Join(genericDebugPathname(d.fs, &d.dentry), name), err)
456456
}
457457
}
458458

@@ -518,7 +518,7 @@ func (d *directfsDentry) bindAt(ctx context.Context, name string, creds *auth.Cr
518518
hbep := opts.Endpoint.(transport.HostBoundEndpoint)
519519
if err := hbep.SetBoundSocketFD(ctx, boundSocketFD); err != nil {
520520
if err := unix.Unlinkat(d.controlFD, name, 0); err != nil {
521-
log.Warningf("error unlinking newly created socket %q after failure: %v", filepath.Join(genericDebugPathname(&d.dentry), name), err)
521+
log.Warningf("error unlinking newly created socket %q after failure: %v", filepath.Join(genericDebugPathname(d.fs, &d.dentry), name), err)
522522
}
523523
return nil, err
524524
}
@@ -593,7 +593,7 @@ func (d *directfsDentry) getDirentsLocked(recordDirent func(name string, key ino
593593
// TODO(gvisor.dev/issue/6665): Get rid of per-dirent stat.
594594
stat, err := fsutil.StatAt(d.controlFD, name)
595595
if err != nil {
596-
log.Warningf("Getdent64: skipping file %q with failed stat, err: %v", path.Join(genericDebugPathname(&d.dentry), name), err)
596+
log.Warningf("Getdent64: skipping file %q with failed stat, err: %v", path.Join(genericDebugPathname(d.fs, &d.dentry), name), err)
597597
return
598598
}
599599
recordDirent(name, inoKeyFromStat(&stat), ftype)
@@ -650,7 +650,7 @@ func (d *directfsDentry) restoreFile(ctx context.Context, controlFD int, opts *v
650650
var stat unix.Stat_t
651651
if err := unix.Fstat(controlFD, &stat); err != nil {
652652
_ = unix.Close(controlFD)
653-
return fmt.Errorf("failed to stat %q: %w", genericDebugPathname(&d.dentry), err)
653+
return fmt.Errorf("failed to stat %q: %w", genericDebugPathname(d.fs, &d.dentry), err)
654654
}
655655

656656
d.controlFD = controlFD
@@ -672,12 +672,12 @@ func (d *directfsDentry) restoreFile(ctx context.Context, controlFD int, opts *v
672672
if d.isRegularFile() {
673673
if opts.ValidateFileSizes {
674674
if d.size.RacyLoad() != uint64(stat.Size) {
675-
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(&d.dentry), d.size.Load(), stat.Size)}
675+
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d.fs, &d.dentry), d.size.Load(), stat.Size)}
676676
}
677677
}
678678
if opts.ValidateFileModificationTimestamps {
679679
if want := dentryTimestampFromUnix(stat.Mtim); d.mtime.RacyLoad() != want {
680-
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(&d.dentry), linux.NsecToStatxTimestamp(d.mtime.RacyLoad()), linux.NsecToStatxTimestamp(want))}
680+
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d.fs, &d.dentry), linux.NsecToStatxTimestamp(d.mtime.RacyLoad()), linux.NsecToStatxTimestamp(want))}
681681
}
682682
}
683683
}
@@ -687,7 +687,7 @@ func (d *directfsDentry) restoreFile(ctx context.Context, controlFD int, opts *v
687687

688688
if rw, ok := d.fs.savedDentryRW[&d.dentry]; ok {
689689
if err := d.ensureSharedHandle(ctx, rw.read, rw.write, false /* trunc */); err != nil {
690-
return fmt.Errorf("failed to restore file handles (read=%t, write=%t) for %q: %w", rw.read, rw.write, genericDebugPathname(&d.dentry), err)
690+
return fmt.Errorf("failed to restore file handles (read=%t, write=%t) for %q: %w", rw.read, rw.write, genericDebugPathname(d.fs, &d.dentry), err)
691691
}
692692
}
693693

pkg/sentry/fsimpl/gofer/directory.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@ func (d *dentry) isDir() bool {
5151
// +checklocks:d.childrenMu
5252
func (d *dentry) cacheNewChildLocked(child *dentry, name string) {
5353
d.IncRef() // reference held by child on its parent
54-
child.parent.Store(d)
55-
child.name = name
54+
genericSetParentAndName(d.fs, child, d, name)
5655
if d.children == nil {
5756
d.children = make(map[string]*dentry)
5857
} else if c, ok := d.children[name]; ok {

pkg/sentry/fsimpl/gofer/filesystem.go

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1418,7 +1418,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
14181418
return err
14191419
}
14201420
if renamed.isDir() {
1421-
if renamed == newParent || genericIsAncestorDentry(renamed, newParent) {
1421+
if renamed == newParent || genericIsAncestorDentry(fs, renamed, newParent) {
14221422
return linuxerr.EINVAL
14231423
}
14241424
if oldParent != newParent {
@@ -1456,7 +1456,7 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
14561456
if !renamed.isDir() {
14571457
return linuxerr.EISDIR
14581458
}
1459-
if genericIsAncestorDentry(replaced, renamed) {
1459+
if genericIsAncestorDentry(fs, replaced, renamed) {
14601460
return linuxerr.ENOTEMPTY
14611461
}
14621462
} else {
@@ -1738,9 +1738,12 @@ func (fs *filesystem) RemoveXattrAt(ctx context.Context, rp *vfs.ResolvingPath,
17381738

17391739
// PrependPath implements vfs.FilesystemImpl.PrependPath.
17401740
func (fs *filesystem) PrependPath(ctx context.Context, vfsroot, vd vfs.VirtualDentry, b *fspath.Builder) error {
1741-
fs.renameMu.RLock()
1742-
defer fs.renameMu.RUnlock()
1743-
return genericPrependPath(vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
1741+
return genericPrependPath(fs, vfsroot, vd.Mount(), vd.Dentry().Impl().(*dentry), b)
1742+
}
1743+
1744+
// IsDescendant implements vfs.FilesystemImpl.IsDescendant.
1745+
func (fs *filesystem) IsDescendant(vfsroot, vd vfs.VirtualDentry) bool {
1746+
return genericIsDescendant(fs, vfsroot.Dentry(), vd.Dentry().Impl().(*dentry))
17441747
}
17451748

17461749
type mopt struct {
@@ -1804,8 +1807,3 @@ func (fs *filesystem) MountOptions() string {
18041807
}
18051808
return strings.Join(opts, ",")
18061809
}
1807-
1808-
// IsDescendant implements vfs.FilesystemImpl.IsDescendant.
1809-
func (fs *filesystem) IsDescendant(vfsroot, vd vfs.VirtualDentry) bool {
1810-
return genericIsDescendant(vfsroot.Dentry(), vd.Dentry().Impl().(*dentry))
1811-
}

pkg/sentry/fsimpl/gofer/gofer.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,12 @@
2525
// dentry.childrenMu
2626
// filesystem.syncMu
2727
// dentry.metadataMu
28-
// *** "memmap.Mappable locks" below this point
28+
// *** "memmap.Mappable/MappingIdentity locks" below this point
2929
// dentry.mapsMu
3030
// *** "memmap.Mappable locks taken by Translate" below this point
3131
// dentry.handleMu
3232
// dentry.dataMu
33+
// filesystem.ancestryMu
3334
// filesystem.inoMu
3435
// specialFileFD.mu
3536
// specialFileFD.bufMu
@@ -218,6 +219,10 @@ type filesystem struct {
218219
// it is reachable from its parent).
219220
renameMu sync.RWMutex `state:"nosave"`
220221

222+
// ancestryMu additionally protects dentry.parent and dentry.name as
223+
// required by genericfstree.
224+
ancestryMu sync.RWMutex `state:"nosave"`
225+
221226
dentryCache *dentryCache
222227

223228
// syncableDentries contains all non-synthetic dentries. specialFileFDs

pkg/sentry/fsimpl/gofer/lisafs_dentry.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -547,18 +547,18 @@ func (d *lisafsDentry) restoreFile(ctx context.Context, inode *lisafs.Inode, opt
547547
if d.isRegularFile() {
548548
if opts.ValidateFileSizes {
549549
if inode.Stat.Mask&linux.STATX_SIZE == 0 {
550-
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(&d.dentry))}
550+
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: file size not available", genericDebugPathname(d.fs, &d.dentry))}
551551
}
552552
if d.size.RacyLoad() != inode.Stat.Size {
553-
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(&d.dentry), d.size.Load(), inode.Stat.Size)}
553+
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: file size validation failed: size changed from %d to %d", genericDebugPathname(d.fs, &d.dentry), d.size.Load(), inode.Stat.Size)}
554554
}
555555
}
556556
if opts.ValidateFileModificationTimestamps {
557557
if inode.Stat.Mask&linux.STATX_MTIME == 0 {
558-
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(&d.dentry))}
558+
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime not available", genericDebugPathname(d.fs, &d.dentry))}
559559
}
560560
if want := dentryTimestamp(inode.Stat.Mtime); d.mtime.RacyLoad() != want {
561-
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(&d.dentry), linux.NsecToStatxTimestamp(d.mtime.RacyLoad()), linux.NsecToStatxTimestamp(want))}
561+
return vfs.ErrCorruption{fmt.Errorf("gofer.dentry(%q).restoreFile: mtime validation failed: mtime changed from %+v to %+v", genericDebugPathname(d.fs, &d.dentry), linux.NsecToStatxTimestamp(d.mtime.RacyLoad()), linux.NsecToStatxTimestamp(want))}
562562
}
563563
}
564564
}
@@ -568,7 +568,7 @@ func (d *lisafsDentry) restoreFile(ctx context.Context, inode *lisafs.Inode, opt
568568

569569
if rw, ok := d.fs.savedDentryRW[&d.dentry]; ok {
570570
if err := d.ensureSharedHandle(ctx, rw.read, rw.write, false /* trunc */); err != nil {
571-
return fmt.Errorf("failed to restore file handles (read=%t, write=%t) for %q: %w", rw.read, rw.write, genericDebugPathname(&d.dentry), err)
571+
return fmt.Errorf("failed to restore file handles (read=%t, write=%t) for %q: %w", rw.read, rw.write, genericDebugPathname(d.fs, &d.dentry), err)
572572
}
573573
}
574574

0 commit comments

Comments
 (0)