ext4: work around deleting a file with i_nlink == 0 safely
diff mbox series

Message ID 20191112032903.8828-1-tytso@mit.edu
State Awaiting Upstream
Headers show
Series
  • ext4: work around deleting a file with i_nlink == 0 safely
Related show

Commit Message

Theodore Y. Ts'o Nov. 12, 2019, 3:29 a.m. UTC
If the file system is corrupted such that a file's i_links_count is
too small, then it's possible that when unlinking that file, i_nlink
will already be zero.  Previously we were working around this kind of
corruption by forcing i_nlink to one; but we were doing this before
trying to delete the directory entry --- and if the file system is
corrupted enough that ext4_delete_entry() fails, then we exit with
i_nlink elevated, and this causes the orphan inode list handling to be
FUBAR'ed, such that when we unmount the file system, the orphan inode
list can get corrupted.

A better way to fix this is to simply skip trying to call drop_nlink()
if i_nlink is already zero, thus moving the check to the place where
it makes the most sense.

https://bugzilla.kernel.org/show_bug.cgi?id=205433

Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Cc: stable@kernel.org
---
 fs/ext4/namei.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

Comments

Andreas Dilger Nov. 13, 2019, 9:25 p.m. UTC | #1
On Nov 11, 2019, at 8:29 PM, Theodore Ts'o <tytso@mit.edu> wrote:
> 
> If the file system is corrupted such that a file's i_links_count is
> too small, then it's possible that when unlinking that file, i_nlink
> will already be zero.  Previously we were working around this kind of
> corruption by forcing i_nlink to one; but we were doing this before
> trying to delete the directory entry --- and if the file system is
> corrupted enough that ext4_delete_entry() fails, then we exit with
> i_nlink elevated, and this causes the orphan inode list handling to be
> FUBAR'ed, such that when we unmount the file system, the orphan inode
> list can get corrupted.
> 
> A better way to fix this is to simply skip trying to call drop_nlink()
> if i_nlink is already zero, thus moving the check to the place where
> it makes the most sense.
> 
> https://bugzilla.kernel.org/show_bug.cgi?id=205433
> 
> Signed-off-by: Theodore Ts'o <tytso@mit.edu>

Reviewed-by: Andreas Dilger <adilger@dilger.ca>

> Cc: stable@kernel.org
> ---
> fs/ext4/namei.c | 11 +++++------
> 1 file changed, 5 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
> index a67cae3c8ff5..a856997d87b5 100644
> --- a/fs/ext4/namei.c
> +++ b/fs/ext4/namei.c
> @@ -3196,18 +3196,17 @@ static int ext4_unlink(struct inode *dir, struct dentry *dentry)
> 	if (IS_DIRSYNC(dir))
> 		ext4_handle_sync(handle);
> 
> -	if (inode->i_nlink == 0) {
> -		ext4_warning_inode(inode, "Deleting file '%.*s' with no links",
> -				   dentry->d_name.len, dentry->d_name.name);
> -		set_nlink(inode, 1);
> -	}
> 	retval = ext4_delete_entry(handle, dir, de, bh);
> 	if (retval)
> 		goto end_unlink;
> 	dir->i_ctime = dir->i_mtime = current_time(dir);
> 	ext4_update_dx_flag(dir);
> 	ext4_mark_inode_dirty(handle, dir);
> -	drop_nlink(inode);
> +	if (inode->i_nlink == 0)
> +		ext4_warning_inode(inode, "Deleting file '%.*s' with no links",
> +				   dentry->d_name.len, dentry->d_name.name);
> +	else
> +		drop_nlink(inode);
> 	if (!inode->i_nlink)
> 		ext4_orphan_add(handle, inode);
> 	inode->i_ctime = current_time(inode);
> --
> 2.23.0
> 


Cheers, Andreas

Patch
diff mbox series

diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c
index a67cae3c8ff5..a856997d87b5 100644
--- a/fs/ext4/namei.c
+++ b/fs/ext4/namei.c
@@ -3196,18 +3196,17 @@  static int ext4_unlink(struct inode *dir, struct dentry *dentry)
 	if (IS_DIRSYNC(dir))
 		ext4_handle_sync(handle);
 
-	if (inode->i_nlink == 0) {
-		ext4_warning_inode(inode, "Deleting file '%.*s' with no links",
-				   dentry->d_name.len, dentry->d_name.name);
-		set_nlink(inode, 1);
-	}
 	retval = ext4_delete_entry(handle, dir, de, bh);
 	if (retval)
 		goto end_unlink;
 	dir->i_ctime = dir->i_mtime = current_time(dir);
 	ext4_update_dx_flag(dir);
 	ext4_mark_inode_dirty(handle, dir);
-	drop_nlink(inode);
+	if (inode->i_nlink == 0)
+		ext4_warning_inode(inode, "Deleting file '%.*s' with no links",
+				   dentry->d_name.len, dentry->d_name.name);
+	else
+		drop_nlink(inode);
 	if (!inode->i_nlink)
 		ext4_orphan_add(handle, inode);
 	inode->i_ctime = current_time(inode);