Fix sentry panic during path resolution from unlinked directories in tmpfs.

In tmpfs, child directories did not hold a reference to their parents. If a
directory was unlinked but kept alive, for example, cwd, its parent
could also be unlinked and have its refcount drop to 0 and destroyed.

When the process in the child directory subsequently tried
to `cd ..`, it resolved to the parent dir and called `IncRef` on it. Since
the parent directory was already destroyed, this triggered the
"non-positive count" panic.

Assisted-by: jetski
PiperOrigin-RevId: 922522308
This commit is contained in:
Jing Chen
2026-05-27 21:22:49 -07:00
committed by gVisor bot
parent cb75139da4
commit bbf8f769c6
4 changed files with 91 additions and 0 deletions
+4
View File
@@ -63,7 +63,11 @@ func (fs *filesystem) newDirectory(kuid auth.KUID, kgid auth.KGID, mode linux.Fi
// - filesystem.mu must be locked for writing.
// - dir must not already contain a child with the given name.
func (dir *directory) insertChildLocked(child *dentry, name string) {
isNew := child.parent.Load() == nil
genericSetParentAndName(dir.dentry.inode.fs, child, &dir.dentry, name)
if child.inode.isDir() && isNew {
dir.inode.incRef()
}
if dir.childMap == nil {
dir.childMap = make(map[string]*dentry)
}
+2
View File
@@ -683,6 +683,8 @@ func (fs *filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
if renamed.inode.isDir() {
oldParentDir.inode.decLinksLocked(ctx)
newParentDir.inode.incLinksLocked()
newParentDir.inode.incRef()
oldParentDir.inode.decRef(ctx)
}
newParentDir.inode.touchCMtime()
}
+4
View File
@@ -707,6 +707,10 @@ func (i *inode) decRef(ctx context.Context) {
i.watches.HandleDeletion(ctx)
// Remove pages used if child being removed is a SymLink or Regular File.
switch impl := i.impl.(type) {
case *directory:
if parent := impl.dentry.parent.Load(); parent != nil {
parent.inode.decRef(ctx)
}
case *symlink:
if len(impl.target) >= shortSymlinkLen {
impl.inode.fs.unaccountPages(1)
+81
View File
@@ -22,6 +22,7 @@ import (
"gvisor.dev/gvisor/pkg/atomicbitops"
"gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/fspath"
"gvisor.dev/gvisor/pkg/sentry/contexttest"
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
"gvisor.dev/gvisor/pkg/sentry/vfs"
)
@@ -196,3 +197,83 @@ func TestParseSize(t *testing.T) {
})
}
}
func TestRmdirParentOfWD(t *testing.T) {
ctx := contexttest.Context(t)
creds := auth.CredentialsFromContext(ctx)
vfsObj := &vfs.VirtualFilesystem{}
if err := vfsObj.Init(ctx); err != nil {
t.Fatalf("VFS init: %v", err)
}
vfsObj.MustRegisterFilesystemType("tmpfs", FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
AllowUserMount: true,
})
mntns, err := vfsObj.NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{}, nil)
if err != nil {
t.Fatalf("failed to create tmpfs root mount: %v", err)
}
defer mntns.DecRef(ctx)
root := mntns.Root(ctx)
defer root.DecRef(ctx)
ctx = vfs.WithMountNamespace(ctx, mntns)
if err := vfsObj.MkdirAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("parent"),
}, &vfs.MkdirOptions{
Mode: 0755,
}); err != nil {
t.Fatalf("failed to create parent dir: %v", err)
}
if err := vfsObj.MkdirAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("parent/child"),
}, &vfs.MkdirOptions{
Mode: 0755,
}); err != nil {
t.Fatalf("failed to create child dir: %v", err)
}
childVD, err := vfsObj.GetDentryAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("parent/child"),
}, &vfs.GetDentryOptions{})
if err != nil {
t.Fatalf("failed to open child dir: %v", err)
}
defer childVD.DecRef(ctx)
if err := vfsObj.RmdirAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("parent/child"),
}); err != nil {
t.Fatalf("failed to rmdir child: %v", err)
}
if err := vfsObj.RmdirAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: root,
Path: fspath.Parse("parent"),
}); err != nil {
t.Fatalf("failed to rmdir parent: %v", err)
}
parentVD, err := vfsObj.GetDentryAt(ctx, creds, &vfs.PathOperation{
Root: root,
Start: childVD,
Path: fspath.Parse(".."),
}, &vfs.GetDentryOptions{})
if err != nil {
t.Fatalf("GetDentryAt(..) failed: %v", err)
}
parentVD.DecRef(ctx)
}