Skip to content

Commit 9e498da

Browse files
ayushr2gvisor-bot
authored andcommitted
goferfs: Add S/R support for open FDs to deleted files.
This support is only needed when the gofer mount in question is writable. By default, the rootfs has an overlayfs applied, so the gofer lower layer is not writabled. But if you are using --overlay2=none, then this change should allow you to save sandbox with open FDs to deleted files in rootfs. Fixes #11425 PiperOrigin-RevId: 728549889
1 parent 84670a4 commit 9e498da

File tree

9 files changed

+177
-25
lines changed

9 files changed

+177
-25
lines changed

pkg/sentry/fsimpl/gofer/dentry_impl.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,12 @@ func (d *dentry) symlink(ctx context.Context, name, target string, creds *auth.C
393393
}
394394

395395
// Precondition: !d.isSynthetic().
396-
func (d *dentry) openCreate(ctx context.Context, name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) {
396+
func (d *dentry) openCreate(ctx context.Context, name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, handle, error) {
397397
switch dt := d.impl.(type) {
398398
case *lisafsDentry:
399-
return dt.openCreate(ctx, name, accessFlags, mode, uid, gid)
399+
return dt.openCreate(ctx, name, accessFlags, mode, uid, gid, createDentry)
400400
case *directfsDentry:
401-
return dt.openCreate(name, accessFlags, mode, uid, gid)
401+
return dt.openCreate(name, accessFlags, mode, uid, gid, createDentry)
402402
default:
403403
panic("unknown dentry implementation")
404404
}

pkg/sentry/fsimpl/gofer/directfs_dentry.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ func (d *directfsDentry) getXattr(ctx context.Context, name string, size uint64)
453453

454454
// getCreatedChild opens the newly created child, sets its uid/gid, constructs
455455
// a disconnected dentry and returns it.
456-
func (d *directfsDentry) getCreatedChild(name string, uid auth.KUID, gid auth.KGID, isDir bool) (*dentry, error) {
456+
func (d *directfsDentry) getCreatedChild(name string, uid auth.KUID, gid auth.KGID, isDir bool, createDentry bool) (*dentry, error) {
457457
unlinkFlags := 0
458458
extraOpenFlags := 0
459459
if isDir {
@@ -481,12 +481,15 @@ func (d *directfsDentry) getCreatedChild(name string, uid auth.KUID, gid auth.KG
481481
return nil, err
482482
}
483483

484-
child, err := d.fs.newDirectfsDentry(childFD)
485-
if err != nil {
486-
// Ownership of childFD was passed to newDirectDentry(), so no need to
487-
// clean that up.
488-
deleteChild()
489-
return nil, err
484+
var child *dentry
485+
if createDentry {
486+
child, err = d.fs.newDirectfsDentry(childFD)
487+
if err != nil {
488+
// Ownership of childFD was passed to newDirectDentry(), so no need to
489+
// clean that up.
490+
deleteChild()
491+
return nil, err
492+
}
490493
}
491494
return child, nil
492495
}
@@ -506,7 +509,7 @@ func (d *directfsDentry) mknod(ctx context.Context, name string, creds *auth.Cre
506509
if err := unix.Mknodat(d.controlFD, name, uint32(opts.Mode), 0); err != nil {
507510
return nil, err
508511
}
509-
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */)
512+
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */, true /* createDentry */)
510513
}
511514

512515
// Precondition: opts.Endpoint != nil and is transport.HostBoundEndpoint type.
@@ -531,7 +534,7 @@ func (d *directfsDentry) bindAt(ctx context.Context, name string, creds *auth.Cr
531534
return nil, err
532535
}
533536
// Socket already has the right UID/GID set, so use uid = gid = -1.
534-
child, err := d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */)
537+
child, err := d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */, true /* createDentry */)
535538
if err != nil {
536539
hbep.ResetBoundSocketFD(ctx)
537540
return nil, err
@@ -559,31 +562,31 @@ func (d *directfsDentry) link(target *directfsDentry, name string) (*dentry, err
559562
// link. The original file already has the right owner.
560563
// TODO(gvisor.dev/issue/6739): Hard linked dentries should share the same
561564
// inode fields.
562-
return d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */)
565+
return d.getCreatedChild(name, auth.NoID /* uid */, auth.NoID /* gid */, false /* isDir */, true /* createDentry */)
563566
}
564567

565568
func (d *directfsDentry) mkdir(name string, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, error) {
566569
if err := unix.Mkdirat(d.controlFD, name, uint32(mode)); err != nil {
567570
return nil, err
568571
}
569-
return d.getCreatedChild(name, uid, gid, true /* isDir */)
572+
return d.getCreatedChild(name, uid, gid, true /* isDir */, true /* createDentry */)
570573
}
571574

572575
func (d *directfsDentry) symlink(name, target string, creds *auth.Credentials) (*dentry, error) {
573576
if err := unix.Symlinkat(target, d.controlFD, name); err != nil {
574577
return nil, err
575578
}
576-
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */)
579+
return d.getCreatedChild(name, creds.EffectiveKUID, creds.EffectiveKGID, false /* isDir */, true /* createDentry */)
577580
}
578581

579-
func (d *directfsDentry) openCreate(name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) {
582+
func (d *directfsDentry) openCreate(name string, accessFlags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, handle, error) {
580583
createFlags := unix.O_CREAT | unix.O_EXCL | int(accessFlags) | hostOpenFlags
581584
childHandleFD, err := unix.Openat(d.controlFD, name, createFlags, uint32(mode&^linux.FileTypeMask))
582585
if err != nil {
583586
return nil, noHandle, err
584587
}
585588

586-
child, err := d.getCreatedChild(name, uid, gid, false /* isDir */)
589+
child, err := d.getCreatedChild(name, uid, gid, false /* isDir */, createDentry)
587590
if err != nil {
588591
_ = unix.Close(childHandleFD)
589592
return nil, noHandle, err

pkg/sentry/fsimpl/gofer/filesystem.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1271,7 +1271,7 @@ func (d *dentry) createAndOpenChildLocked(ctx context.Context, rp *vfs.Resolving
12711271
kgid = auth.KGID(d.gid.Load())
12721272
}
12731273

1274-
child, h, err := d.openCreate(ctx, name, opts.Flags&linux.O_ACCMODE, opts.Mode, creds.EffectiveKUID, kgid)
1274+
child, h, err := d.openCreate(ctx, name, opts.Flags&linux.O_ACCMODE, opts.Mode, creds.EffectiveKUID, kgid, true /* createDentry */)
12751275
if err != nil {
12761276
return nil, err
12771277
}

pkg/sentry/fsimpl/gofer/gofer.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ type filesystem struct {
246246
// savedDentryRW records open read/write handles during save/restore.
247247
savedDentryRW map[*dentry]savedDentryRW
248248

249+
// savedDeletedOpenDentries records deleted dentries that are saved during
250+
// save/restore. These are still accessible via open application FDs.
251+
savedDeletedOpenDentries map[*dentry]struct{}
252+
249253
// released is nonzero once filesystem.Release has been called.
250254
released atomicbitops.Int32
251255
}
@@ -978,6 +982,10 @@ type dentry struct {
978982
// tracks dirty segments in cache. dirty is protected by dataMu.
979983
dirty fsutil.DirtySet
980984

985+
// If this dentry represents a deleted regular file, deletedDataSR is used to
986+
// store file data for save/restore.
987+
deletedDataSR []byte
988+
981989
// pf implements memmap.File for mappings of hostFD.
982990
pf dentryPlatformFile
983991

pkg/sentry/fsimpl/gofer/lisafs_dentry.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ func (d *lisafsDentry) symlink(ctx context.Context, name, target string, creds *
448448
return d.newChildDentry(ctx, &symlinkInode, name)
449449
}
450450

451-
func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID) (*dentry, handle, error) {
451+
func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32, mode linux.FileMode, uid auth.KUID, gid auth.KGID, createDentry bool) (*dentry, handle, error) {
452452
ino, openFD, hostFD, err := d.controlFD.OpenCreateAt(ctx, name, flags, mode, lisafs.UID(uid), lisafs.GID(gid))
453453
if err != nil {
454454
return nil, noHandle, err
@@ -458,10 +458,13 @@ func (d *lisafsDentry) openCreate(ctx context.Context, name string, flags uint32
458458
fdLisa: d.fs.client.NewFD(openFD),
459459
fd: int32(hostFD),
460460
}
461-
child, err := d.fs.newLisafsDentry(ctx, &ino)
462-
if err != nil {
463-
h.close(ctx)
464-
return nil, noHandle, err
461+
var child *dentry
462+
if createDentry {
463+
child, err = d.fs.newLisafsDentry(ctx, &ino)
464+
if err != nil {
465+
h.close(ctx)
466+
return nil, noHandle, err
467+
}
465468
}
466469
return child, h, nil
467470
}

pkg/sentry/fsimpl/gofer/save_restore.go

Lines changed: 119 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ import (
2525
"gvisor.dev/gvisor/pkg/errors/linuxerr"
2626
"gvisor.dev/gvisor/pkg/fdnotifier"
2727
"gvisor.dev/gvisor/pkg/hostarch"
28+
"gvisor.dev/gvisor/pkg/log"
2829
"gvisor.dev/gvisor/pkg/refs"
2930
"gvisor.dev/gvisor/pkg/safemem"
31+
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
3032
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
3133
"gvisor.dev/gvisor/pkg/sentry/vfs"
3234
)
3335

36+
var _ vfs.FilesystemImplSaveRestoreExtension = (*filesystem)(nil)
37+
3438
// +stateify savable
3539
type savedDentryRW struct {
3640
read bool
@@ -48,6 +52,7 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
4852
fs.renameMu.Lock()
4953
fs.evictAllCachedDentriesLocked(ctx)
5054
fs.renameMu.Unlock()
55+
fs.savedDentryRW = make(map[*dentry]savedDentryRW)
5156

5257
// Buffer pipe data so that it's available for reading after restore. (This
5358
// is a legacy VFS1 feature.)
@@ -60,14 +65,23 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
6065
}
6166
}
6267
}
68+
// Save file data for deleted regular files which are still accessible via
69+
// open application FDs.
70+
for sd := fs.syncableDentries.Front(); sd != nil; sd = sd.Next() {
71+
if sd.d.vfsd.IsDead() {
72+
if err := sd.d.prepareSaveDead(ctx); err != nil {
73+
fs.syncMu.Unlock()
74+
return err
75+
}
76+
}
77+
}
6378
fs.syncMu.Unlock()
6479

6580
// Flush local state to the remote filesystem.
6681
if err := fs.Sync(ctx); err != nil {
6782
return err
6883
}
6984

70-
fs.savedDentryRW = make(map[*dentry]savedDentryRW)
7185
return fs.root.prepareSaveRecursive(ctx)
7286
}
7387

@@ -96,6 +110,59 @@ func (fd *specialFileFD) savePipeData(ctx context.Context) error {
96110
return nil
97111
}
98112

113+
func (d *dentry) prepareSaveDead(ctx context.Context) error {
114+
if !d.isRegularFile() {
115+
return fmt.Errorf("gofer.dentry(%q).prepareSaveDead: only regular deleted dentries can be saved, got %s", genericDebugPathname(d.fs, d), linux.FileMode(d.mode.Load()))
116+
}
117+
if !d.isDeleted() {
118+
return fmt.Errorf("gofer.dentry(%q).prepareSaveDead: invalidated dentries can't be saved", genericDebugPathname(d.fs, d))
119+
}
120+
if !d.cachedMetadataAuthoritative() {
121+
if err := d.updateMetadata(ctx); err != nil {
122+
return err
123+
}
124+
}
125+
if d.isReadHandleOk() || d.isWriteHandleOk() {
126+
d.fs.savedDentryRW[d] = savedDentryRW{
127+
read: d.isReadHandleOk(),
128+
write: d.isWriteHandleOk(),
129+
}
130+
}
131+
d.handleMu.RLock()
132+
defer d.handleMu.RUnlock()
133+
var h handle
134+
if d.isReadHandleOk() {
135+
h = d.readHandle()
136+
} else {
137+
var err error
138+
h, err = d.openHandle(ctx, true /* read */, false /* write */, false /* trunc */)
139+
if err != nil {
140+
return fmt.Errorf("failed to open read handle for deleted file %q: %w", genericDebugPathname(d.fs, d), err)
141+
}
142+
defer h.close(ctx)
143+
}
144+
d.dataMu.RLock()
145+
defer d.dataMu.RUnlock()
146+
d.deletedDataSR = make([]byte, d.size.Load())
147+
done := uint64(0)
148+
for done < uint64(len(d.deletedDataSR)) {
149+
n, err := h.readToBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.deletedDataSR[done:])), done)
150+
done += n
151+
if err != nil {
152+
if err == io.EOF {
153+
break
154+
}
155+
return fmt.Errorf("failed to read deleted file %q: %w", genericDebugPathname(d.fs, d), err)
156+
}
157+
}
158+
d.deletedDataSR = d.deletedDataSR[:done]
159+
if d.fs.savedDeletedOpenDentries == nil {
160+
d.fs.savedDeletedOpenDentries = make(map[*dentry]struct{})
161+
}
162+
d.fs.savedDeletedOpenDentries[d] = struct{}{}
163+
return nil
164+
}
165+
99166
func (d *dentry) prepareSaveRecursive(ctx context.Context) error {
100167
if d.isRegularFile() && !d.cachedMetadataAuthoritative() {
101168
// Get updated metadata for d in case we need to perform metadata
@@ -129,11 +196,20 @@ func (d *dentry) prepareSaveRecursive(ctx context.Context) error {
129196

130197
// beforeSave is invoked by stateify.
131198
func (d *dentry) beforeSave() {
132-
if d.vfsd.IsDead() {
199+
if d.vfsd.IsDead() && d.deletedDataSR == nil {
133200
panic(fmt.Sprintf("gofer.dentry(%q).beforeSave: deleted and invalidated dentries can't be restored", genericDebugPathname(d.fs, d)))
134201
}
135202
}
136203

204+
// PostSave implements vfs.FilesystemImplSaveRestoreExtension.PostSave.
205+
func (fs *filesystem) PostSave(ctx context.Context) {
206+
for d := range fs.savedDeletedOpenDentries {
207+
d.deletedDataSR = nil
208+
}
209+
fs.savedDeletedOpenDentries = nil
210+
fs.savedDentryRW = nil
211+
}
212+
137213
// afterLoad is invoked by stateify.
138214
func (fs *filesystem) afterLoad(ctx goContext.Context) {
139215
fs.mf = pgalloc.MemoryFileFromContext(ctx)
@@ -229,7 +305,17 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
229305
}
230306
}
231307

308+
// Restore deleted files which are still accessible via open application FDs.
309+
for d := range fs.savedDeletedOpenDentries {
310+
if d.deletedDataSR != nil {
311+
if err := d.restoreDead(ctx, &opts); err != nil {
312+
return err
313+
}
314+
}
315+
}
316+
232317
// Discard state only required during restore.
318+
fs.savedDeletedOpenDentries = nil
233319
fs.savedDentryRW = nil
234320

235321
return nil
@@ -256,6 +342,37 @@ func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.Comp
256342
return nil
257343
}
258344

345+
// Preconditions: d.deletedDataSR != nil.
346+
func (d *dentry) restoreDead(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
347+
parent := d.parent.Load()
348+
// This is a deleted regular file. Recreate it on the host filesystem
349+
// temporarily, fill it with data and then proceed with the restore.
350+
_, h, err := parent.openCreate(ctx, d.name, linux.O_WRONLY, linux.FileMode(d.mode.Load()), auth.KUID(d.uid.Load()), auth.KGID(d.gid.Load()), false /* createDentry */)
351+
if err != nil {
352+
return fmt.Errorf("failed to re-create deleted file %q: %w", genericDebugPathname(d.fs, d), err)
353+
}
354+
defer h.close(ctx)
355+
n, err := h.writeFromBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.deletedDataSR)), 0)
356+
if err != nil {
357+
return fmt.Errorf("failed to write deleted file %q: %w", genericDebugPathname(d.fs, d), err)
358+
}
359+
if n != uint64(len(d.deletedDataSR)) {
360+
return fmt.Errorf("failed to write all of deleted file %q: wrote %d bytes, expected %d", genericDebugPathname(d.fs, d), n, len(d.deletedDataSR))
361+
}
362+
d.deletedDataSR = nil
363+
if err := d.restoreFile(ctx, opts); err != nil {
364+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
365+
// Log warning, give preference to the restore error.
366+
log.Warningf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
367+
}
368+
return err
369+
}
370+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
371+
return fmt.Errorf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
372+
}
373+
return nil
374+
}
375+
259376
func (fd *specialFileFD) completeRestore(ctx context.Context) error {
260377
d := fd.dentry()
261378
h, err := d.openHandle(ctx, fd.vfsfd.IsReadable(), fd.vfsfd.IsWritable(), false /* trunc */)

pkg/sentry/fsimpl/tmpfs/save_restore.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"gvisor.dev/gvisor/pkg/sentry/vfs"
2424
)
2525

26+
var _ vfs.FilesystemImplSaveRestoreExtension = (*filesystem)(nil)
27+
2628
// saveMf is called by stateify.
2729
func (fs *filesystem) saveMf() string {
2830
if !fs.mf.IsSavable() {
@@ -75,6 +77,9 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
7577
return nil
7678
}
7779

80+
// PostSave implements vfs.FilesystemImplSaveRestoreExtension.PostSave.
81+
func (fs *filesystem) PostSave(ctx context.Context) {}
82+
7883
// CompleteRestore implements
7984
// vfs.FilesystemImplSaveRestoreExtension.CompleteRestore.
8085
func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRestoreOptions) error {

pkg/sentry/kernel/kernel.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,8 @@ func (k *Kernel) SaveTo(ctx context.Context, w, pagesMetadata io.Writer, pagesFi
698698
return mfSaveErr
699699
}
700700

701+
k.vfs.PostSave(ctx)
702+
701703
log.Infof("Overall save took [%s].", time.Since(saveStart))
702704
return nil
703705
}

0 commit comments

Comments
 (0)