From e7bbe70f79aa9308c2eb54b057ee5779b22f478e Mon Sep 17 00:00:00 2001 From: Ayush Ranjan Date: Mon, 12 Oct 2020 16:27:33 -0700 Subject: [vfs] kernfs: Fix inode memory leak issue. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change aims to fix the memory leak issue reported inĀ #3933. Background: VFS2 kernfs kept accumulating invalid dentries if those dentries were not walked on. After substantial consideration of the problem by our team, we decided to have an LRU cache solution. This change is the first part to that solution, where we don't cache anything. The LRU cache can be added on top of this. What has changed: - Introduced the concept of an inode tree in kernfs.OrderedChildren. This is helpful is cases where the lifecycle of an inode is different from that of a dentry. - OrderedChildren now deals with initialized inodes instead of initialized dentries. It now implements Lookup() where it constructs a new dentry using the inode. - OrderedChildren holds a ref on all its children inodes. With this change, now an inode can "outlive" a dentry pointing to it. See comments in kernfs.OrderedChildren. - The kernfs dentry tree is solely maintained by kernfs only. Inode implementations can not modify the dentry tree. - Dentries that reach ref count 0 are removed from the dentry tree. - revalidateChildLocked now defer-DecRefs the newly created dentry from Inode.Lookup(), limiting its life to the current filesystem operation. If refs are picked on the dentry during the FS op (via an FD or something), then it will stick around and will be removed when the FD is closed. So there is essentially _no caching_ for Look()ed up dentries. - kernfs.DecRef does not have the precondition that fs.mu must be locked. Fixes #3933 PiperOrigin-RevId: 336768576 --- pkg/sentry/fsimpl/sys/sys.go | 48 ++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 26 deletions(-) (limited to 'pkg/sentry/fsimpl/sys/sys.go') diff --git a/pkg/sentry/fsimpl/sys/sys.go b/pkg/sentry/fsimpl/sys/sys.go index 1568c581f..5a06f4e1c 100644 --- a/pkg/sentry/fsimpl/sys/sys.go +++ b/pkg/sentry/fsimpl/sys/sys.go @@ -64,15 +64,15 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt } fs.VFSFilesystem().Init(vfsObj, &fsType, fs) - root := fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{ + root := fs.newDir(creds, defaultSysDirMode, map[string]kernfs.Inode{ "block": fs.newDir(creds, defaultSysDirMode, nil), "bus": fs.newDir(creds, defaultSysDirMode, nil), - "class": fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{ + "class": fs.newDir(creds, defaultSysDirMode, map[string]kernfs.Inode{ "power_supply": fs.newDir(creds, defaultSysDirMode, nil), }), "dev": fs.newDir(creds, defaultSysDirMode, nil), - "devices": fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{ - "system": fs.newDir(creds, defaultSysDirMode, map[string]*kernfs.Dentry{ + "devices": fs.newDir(creds, defaultSysDirMode, map[string]kernfs.Inode{ + "system": fs.newDir(creds, defaultSysDirMode, map[string]kernfs.Inode{ "cpu": cpuDir(ctx, fs, creds), }), }), @@ -82,13 +82,15 @@ func (fsType FilesystemType) GetFilesystem(ctx context.Context, vfsObj *vfs.Virt "module": fs.newDir(creds, defaultSysDirMode, nil), "power": fs.newDir(creds, defaultSysDirMode, nil), }) - return fs.VFSFilesystem(), root.VFSDentry(), nil + var rootD kernfs.Dentry + rootD.Init(&fs.Filesystem, root) + return fs.VFSFilesystem(), rootD.VFSDentry(), nil } -func cpuDir(ctx context.Context, fs *filesystem, creds *auth.Credentials) *kernfs.Dentry { +func cpuDir(ctx context.Context, fs *filesystem, creds *auth.Credentials) kernfs.Inode { k := kernel.KernelFromContext(ctx) maxCPUCores := k.ApplicationCores() - children := map[string]*kernfs.Dentry{ + children := map[string]kernfs.Inode{ "online": fs.newCPUFile(creds, maxCPUCores, linux.FileMode(0444)), "possible": fs.newCPUFile(creds, maxCPUCores, linux.FileMode(0444)), "present": fs.newCPUFile(creds, maxCPUCores, linux.FileMode(0444)), @@ -99,14 +101,14 @@ func cpuDir(ctx context.Context, fs *filesystem, creds *auth.Credentials) *kernf return fs.newDir(creds, defaultSysDirMode, children) } -func kernelDir(ctx context.Context, fs *filesystem, creds *auth.Credentials) *kernfs.Dentry { +func kernelDir(ctx context.Context, fs *filesystem, creds *auth.Credentials) kernfs.Inode { // If kcov is available, set up /sys/kernel/debug/kcov. Technically, debugfs // should be mounted at debug/, but for our purposes, it is sufficient to // keep it in sys. - var children map[string]*kernfs.Dentry + var children map[string]kernfs.Inode if coverage.KcovAvailable() { - children = map[string]*kernfs.Dentry{ - "debug": fs.newDir(creds, linux.FileMode(0700), map[string]*kernfs.Dentry{ + children = map[string]kernfs.Inode{ + "debug": fs.newDir(creds, linux.FileMode(0700), map[string]kernfs.Inode{ "kcov": fs.newKcovFile(ctx, creds), }), } @@ -125,27 +127,23 @@ func (fs *filesystem) Release(ctx context.Context) { // +stateify savable type dir struct { dirRefs + kernfs.InodeAlwaysValid kernfs.InodeAttrs - kernfs.InodeNoDynamicLookup kernfs.InodeNotSymlink kernfs.InodeDirectoryNoNewChildren + kernfs.InodeTemporary kernfs.OrderedChildren locks vfs.FileLocks - - dentry kernfs.Dentry } -func (fs *filesystem) newDir(creds *auth.Credentials, mode linux.FileMode, contents map[string]*kernfs.Dentry) *kernfs.Dentry { +func (fs *filesystem) newDir(creds *auth.Credentials, mode linux.FileMode, contents map[string]kernfs.Inode) kernfs.Inode { d := &dir{} d.InodeAttrs.Init(creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), linux.ModeDirectory|0755) d.OrderedChildren.Init(kernfs.OrderedChildrenOptions{}) d.EnableLeakCheck() - d.dentry.Init(d) - - d.IncLinks(d.OrderedChildren.Populate(&d.dentry, contents)) - - return &d.dentry + d.IncLinks(d.OrderedChildren.Populate(contents)) + return d } // SetStat implements kernfs.Inode.SetStat not allowing inode attributes to be changed. @@ -165,8 +163,8 @@ func (d *dir) Open(ctx context.Context, rp *vfs.ResolvingPath, kd *kernfs.Dentry } // DecRef implements kernfs.Inode.DecRef. -func (d *dir) DecRef(context.Context) { - d.dirRefs.DecRef(d.Destroy) +func (d *dir) DecRef(ctx context.Context) { + d.dirRefs.DecRef(func() { d.Destroy(ctx) }) } // StatFS implements kernfs.Inode.StatFS. @@ -190,12 +188,10 @@ func (c *cpuFile) Generate(ctx context.Context, buf *bytes.Buffer) error { return nil } -func (fs *filesystem) newCPUFile(creds *auth.Credentials, maxCores uint, mode linux.FileMode) *kernfs.Dentry { +func (fs *filesystem) newCPUFile(creds *auth.Credentials, maxCores uint, mode linux.FileMode) kernfs.Inode { c := &cpuFile{maxCores: maxCores} c.DynamicBytesFile.Init(creds, linux.UNNAMED_MAJOR, fs.devMinor, fs.NextIno(), c, mode) - d := &kernfs.Dentry{} - d.Init(c) - return d + return c } // +stateify savable -- cgit v1.2.3