From: Greg Kroah-Hartman Date: Fri, 7 Aug 2015 05:35:08 +0000 (-0700) Subject: 4.1-stable patches X-Git-Tag: v4.1.5~21 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4f8b8bf009e98d9d9f67e40f301ddc8b84a65eca;p=thirdparty%2Fkernel%2Fstable-queue.git 4.1-stable patches added patches: freeing-unlinked-file-indefinitely-delayed.patch --- diff --git a/queue-4.1/freeing-unlinked-file-indefinitely-delayed.patch b/queue-4.1/freeing-unlinked-file-indefinitely-delayed.patch new file mode 100644 index 00000000000..214f41710b1 --- /dev/null +++ b/queue-4.1/freeing-unlinked-file-indefinitely-delayed.patch @@ -0,0 +1,107 @@ +From 75a6f82a0d10ef8f13cd8fe7212911a0252ab99e Mon Sep 17 00:00:00 2001 +From: Al Viro +Date: Wed, 8 Jul 2015 02:42:38 +0100 +Subject: freeing unlinked file indefinitely delayed + +From: Al Viro + +commit 75a6f82a0d10ef8f13cd8fe7212911a0252ab99e upstream. + + Normally opening a file, unlinking it and then closing will have +the inode freed upon close() (provided that it's not otherwise busy and +has no remaining links, of course). However, there's one case where that +does *not* happen. Namely, if you open it by fhandle with cold dcache, +then unlink() and close(). + + In normal case you get d_delete() in unlink(2) notice that dentry +is busy and unhash it; on the final dput() it will be forcibly evicted from +dcache, triggering iput() and inode removal. In this case, though, we end +up with *two* dentries - disconnected (created by open-by-fhandle) and +regular one (used by unlink()). The latter will have its reference to inode +dropped just fine, but the former will not - it's considered hashed (it +is on the ->s_anon list), so it will stay around until the memory pressure +will finally do it in. As the result, we have the final iput() delayed +indefinitely. It's trivial to reproduce - + +void flush_dcache(void) +{ + system("mount -o remount,rw /"); +} + +static char buf[20 * 1024 * 1024]; + +main() +{ + int fd; + union { + struct file_handle f; + char buf[MAX_HANDLE_SZ]; + } x; + int m; + + x.f.handle_bytes = sizeof(x); + chdir("/root"); + mkdir("foo", 0700); + fd = open("foo/bar", O_CREAT | O_RDWR, 0600); + close(fd); + name_to_handle_at(AT_FDCWD, "foo/bar", &x.f, &m, 0); + flush_dcache(); + fd = open_by_handle_at(AT_FDCWD, &x.f, O_RDWR); + unlink("foo/bar"); + write(fd, buf, sizeof(buf)); + system("df ."); /* 20Mb eaten */ + close(fd); + system("df ."); /* should've freed those 20Mb */ + flush_dcache(); + system("df ."); /* should be the same as #2 */ +} + +will spit out something like +Filesystem 1K-blocks Used Available Use% Mounted on +/dev/root 322023 303843 1131 100% / +Filesystem 1K-blocks Used Available Use% Mounted on +/dev/root 322023 303843 1131 100% / +Filesystem 1K-blocks Used Available Use% Mounted on +/dev/root 322023 283282 21692 93% / +- inode gets freed only when dentry is finally evicted (here we trigger +than by remount; normally it would've happened in response to memory +pressure hell knows when). + +Acked-by: J. Bruce Fields +Signed-off-by: Al Viro +Signed-off-by: Greg Kroah-Hartman + +--- + fs/dcache.c | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +--- a/fs/dcache.c ++++ b/fs/dcache.c +@@ -642,7 +642,7 @@ static inline bool fast_dput(struct dent + + /* + * If we have a d_op->d_delete() operation, we sould not +- * let the dentry count go to zero, so use "put__or_lock". ++ * let the dentry count go to zero, so use "put_or_lock". + */ + if (unlikely(dentry->d_flags & DCACHE_OP_DELETE)) + return lockref_put_or_lock(&dentry->d_lockref); +@@ -697,7 +697,7 @@ static inline bool fast_dput(struct dent + */ + smp_rmb(); + d_flags = ACCESS_ONCE(dentry->d_flags); +- d_flags &= DCACHE_REFERENCED | DCACHE_LRU_LIST; ++ d_flags &= DCACHE_REFERENCED | DCACHE_LRU_LIST | DCACHE_DISCONNECTED; + + /* Nothing to do? Dropping the reference was all we needed? */ + if (d_flags == (DCACHE_REFERENCED | DCACHE_LRU_LIST) && !d_unhashed(dentry)) +@@ -776,6 +776,9 @@ repeat: + if (unlikely(d_unhashed(dentry))) + goto kill_it; + ++ if (unlikely(dentry->d_flags & DCACHE_DISCONNECTED)) ++ goto kill_it; ++ + if (unlikely(dentry->d_flags & DCACHE_OP_DELETE)) { + if (dentry->d_op->d_delete(dentry)) + goto kill_it; diff --git a/queue-4.1/series b/queue-4.1/series index f1d5c650e2b..5633f3f605f 100644 --- a/queue-4.1/series +++ b/queue-4.1/series @@ -18,3 +18,4 @@ can-rcar_can-fix-irq-check.patch can-c_can-fix-default-pinmux-glitch-at-init.patch can-rcar_can-print-signed-irq.patch can-mcp251x-fix-resume-when-device-is-down.patch +freeing-unlinked-file-indefinitely-delayed.patch