| From 4cf89575d3950ec5b2abed4e48d03f48d3b8797c Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 1 Jan 2026 19:11:48 +0800 |
| Subject: fat: avoid parent link count underflow in rmdir |
| |
| From: Zhiyu Zhang <zhiyuzhang999@gmail.com> |
| |
| [ Upstream commit 8cafcb881364af5ef3a8b9fed4db254054033d8a ] |
| |
| Corrupted FAT images can leave a directory inode with an incorrect |
| i_nlink (e.g. 2 even though subdirectories exist). rmdir then |
| unconditionally calls drop_nlink(dir) and can drive i_nlink to 0, |
| triggering the WARN_ON in drop_nlink(). |
| |
| Add a sanity check in vfat_rmdir() and msdos_rmdir(): only drop the |
| parent link count when it is at least 3, otherwise report a filesystem |
| error. |
| |
| Link: https://lkml.kernel.org/r/20260101111148.1437-1-zhiyuzhang999@gmail.com |
| Fixes: 9a53c3a783c2 ("[PATCH] r/o bind mounts: unlink: monitor i_nlink") |
| Signed-off-by: Zhiyu Zhang <zhiyuzhang999@gmail.com> |
| Reported-by: Zhiyu Zhang <zhiyuzhang999@gmail.com> |
| Closes: https://lore.kernel.org/linux-fsdevel/aVN06OKsKxZe6-Kv@casper.infradead.org/T/#t |
| Tested-by: Zhiyu Zhang <zhiyuzhang999@gmail.com> |
| Acked-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> |
| Cc: Al Viro <viro@zeniv.linux.org.uk> |
| Cc: Christian Brauner <brauner@kernel.org> |
| Cc: Jan Kara <jack@suse.cz> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| fs/fat/namei_msdos.c | 7 ++++++- |
| fs/fat/namei_vfat.c | 7 ++++++- |
| 2 files changed, 12 insertions(+), 2 deletions(-) |
| |
| diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c |
| index 0b920ee40a7f9..262ec1b790b56 100644 |
| --- a/fs/fat/namei_msdos.c |
| +++ b/fs/fat/namei_msdos.c |
| @@ -325,7 +325,12 @@ static int msdos_rmdir(struct inode *dir, struct dentry *dentry) |
| err = fat_remove_entries(dir, &sinfo); /* and releases bh */ |
| if (err) |
| goto out; |
| - drop_nlink(dir); |
| + if (dir->i_nlink >= 3) |
| + drop_nlink(dir); |
| + else { |
| + fat_fs_error(sb, "parent dir link count too low (%u)", |
| + dir->i_nlink); |
| + } |
| |
| clear_nlink(inode); |
| fat_truncate_time(inode, NULL, S_CTIME); |
| diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c |
| index 5dbc4cbb8fce3..47ff083cfc7e6 100644 |
| --- a/fs/fat/namei_vfat.c |
| +++ b/fs/fat/namei_vfat.c |
| @@ -803,7 +803,12 @@ static int vfat_rmdir(struct inode *dir, struct dentry *dentry) |
| err = fat_remove_entries(dir, &sinfo); /* and releases bh */ |
| if (err) |
| goto out; |
| - drop_nlink(dir); |
| + if (dir->i_nlink >= 3) |
| + drop_nlink(dir); |
| + else { |
| + fat_fs_error(sb, "parent dir link count too low (%u)", |
| + dir->i_nlink); |
| + } |
| |
| clear_nlink(inode); |
| fat_truncate_time(inode, NULL, S_ATIME|S_MTIME); |
| -- |
| 2.51.0 |
| |