Skip to content

Commit d000106

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 86abc85 commit d000106

File tree

6 files changed

+162
-25
lines changed

6 files changed

+162
-25
lines changed

pkg/sentry/fsimpl/gofer/dentry_impl.go

+3-3
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

+17-14
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

+1-1
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

+8
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

+8-5
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

+125-2
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ import (
2121

2222
"gvisor.dev/gvisor/pkg/abi/linux"
2323
"gvisor.dev/gvisor/pkg/atomicbitops"
24+
"gvisor.dev/gvisor/pkg/cleanup"
2425
"gvisor.dev/gvisor/pkg/context"
2526
"gvisor.dev/gvisor/pkg/errors/linuxerr"
2627
"gvisor.dev/gvisor/pkg/fdnotifier"
2728
"gvisor.dev/gvisor/pkg/hostarch"
29+
"gvisor.dev/gvisor/pkg/log"
2830
"gvisor.dev/gvisor/pkg/refs"
2931
"gvisor.dev/gvisor/pkg/safemem"
32+
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
3033
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
3134
"gvisor.dev/gvisor/pkg/sentry/vfs"
3235
)
@@ -50,6 +53,7 @@ func (fs *filesystem) PrepareSave(ctx context.Context) error {
5053
fs.renameMu.Lock()
5154
fs.evictAllCachedDentriesLocked(ctx)
5255
fs.renameMu.Unlock()
56+
fs.savedDentryRW = make(map[*dentry]savedDentryRW)
5357

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

6781
// Flush local state to the remote filesystem.
6882
if err := fs.Sync(ctx); err != nil {
6983
return err
7084
}
7185

72-
fs.savedDentryRW = make(map[*dentry]savedDentryRW)
7386
return fs.root.prepareSaveRecursive(ctx)
7487
}
7588

@@ -98,6 +111,62 @@ func (fd *specialFileFD) savePipeData(ctx context.Context) error {
98111
return nil
99112
}
100113

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

132201
// beforeSave is invoked by stateify.
133202
func (d *dentry) beforeSave() {
134-
if d.vfsd.IsDead() {
203+
if d.vfsd.IsDead() && d.deletedDataSR == nil {
135204
panic(fmt.Sprintf("gofer.dentry(%q).beforeSave: deleted and invalidated dentries can't be restored", genericDebugPathname(d.fs, d)))
136205
}
137206
}
138207

139208
// BeforeResume implements vfs.FilesystemImplSaveRestoreExtension.BeforeResume.
140209
func (fs *filesystem) BeforeResume(ctx context.Context) {
210+
for d := range fs.savedDeletedOpenDentries {
211+
d.deletedDataSR = nil
212+
}
213+
fs.savedDeletedOpenDentries = nil
141214
fs.savedDentryRW = nil
142215
}
143216

@@ -236,7 +309,15 @@ func (fs *filesystem) CompleteRestore(ctx context.Context, opts vfs.CompleteRest
236309
}
237310
}
238311

312+
// Restore deleted files which are still accessible via open application FDs.
313+
for d := range fs.savedDeletedOpenDentries {
314+
if err := d.restoreDead(ctx, &opts); err != nil {
315+
return err
316+
}
317+
}
318+
239319
// Discard state only required during restore.
320+
fs.savedDeletedOpenDentries = nil
240321
fs.savedDentryRW = nil
241322

242323
return nil
@@ -263,6 +344,48 @@ func (d *dentry) restoreDescendantsRecursive(ctx context.Context, opts *vfs.Comp
263344
return nil
264345
}
265346

347+
// restoreDead restores a deleted regular file.
348+
//
349+
// Preconditions: d.deletedDataSR != nil.
350+
func (d *dentry) restoreDead(ctx context.Context, opts *vfs.CompleteRestoreOptions) error {
351+
// Recreate the file on the host filesystem (this is temporary).
352+
parent := d.parent.Load()
353+
_, 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 */)
354+
if err != nil {
355+
return fmt.Errorf("failed to re-create deleted file %q: %w", genericDebugPathname(d.fs, d), err)
356+
}
357+
defer h.close(ctx)
358+
// In case of errors, clean up the recreated file.
359+
cu := cleanup.Make(func() {
360+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
361+
log.Warningf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
362+
}
363+
})
364+
defer cu.Clean()
365+
// Write the file data to the recreated file.
366+
n, err := h.writeFromBlocksAt(ctx, safemem.BlockSeqOf(safemem.BlockFromSafeSlice(d.deletedDataSR)), 0)
367+
if err != nil {
368+
return fmt.Errorf("failed to write deleted file %q: %w", genericDebugPathname(d.fs, d), err)
369+
}
370+
if n != uint64(len(d.deletedDataSR)) {
371+
return fmt.Errorf("failed to write all of deleted file %q: wrote %d bytes, expected %d", genericDebugPathname(d.fs, d), n, len(d.deletedDataSR))
372+
}
373+
d.deletedDataSR = nil
374+
// Restore the file. Note that timestamps may not match since we re-created
375+
// the file on the host.
376+
recreateOpts := *opts
377+
recreateOpts.ValidateFileModificationTimestamps = false
378+
if err := d.restoreFile(ctx, &recreateOpts); err != nil {
379+
return err
380+
}
381+
// Finally, unlink the recreated file.
382+
cu.Release()
383+
if err := parent.unlink(ctx, d.name, 0 /* flags */); err != nil {
384+
return fmt.Errorf("failed to clean up recreated deleted file %q: %v", genericDebugPathname(d.fs, d), err)
385+
}
386+
return nil
387+
}
388+
266389
func (fd *specialFileFD) completeRestore(ctx context.Context) error {
267390
d := fd.dentry()
268391
h, err := d.openHandle(ctx, fd.vfsfd.IsReadable(), fd.vfsfd.IsWritable(), false /* trunc */)

0 commit comments

Comments
 (0)