CVE-2026-45915
Description
In the Linux kernel, the following vulnerability has been resolved:
fat: avoid parent link count underflow in rmdir
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.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
FAT filesystem driver in Linux kernel had an rmdir parent link count underflow that could be triggered by corrupted directory inodes.
Vulnerability
In the Linux kernel's FAT filesystem driver, both vfat_rmdir() and msdos_rmdir() unconditionally called drop_nlink(dir) when removing a directory entry. A corrupted FAT image could present a directory inode with an incorrect i_nlink value (e.g., 2 even when subdirectories exist). Calling drop_nlink() in such cases could drive the link count to 0, triggering the WARN_ON in drop_nlink() [1]. The fix adds a sanity check so the link count is only decremented when it is at least 3; otherwise an error is reported [1].
Exploitation
An attacker would need the ability to mount a crafted FAT filesystem image on a system running an affected kernel. The image must contain a directory inode with an i_nlink value artificially set to 2 (or another value <3) while having child subdirectories. When a user or process runs rmdir on that directory, the kernel triggers the bug [1]. No special privileges beyond mounting a filesystem (typically requiring root or CAP_SYS_ADMIN) are needed after the crafted image is placed.
Impact
On success, the attacker causes a kernel WARN_ON splat. While the patch notes do not describe a broader denial-of-service or memory corruption, the triggered warning could cause system instability or be used to fill kernel logs. The primary impact is availability degradation via the warning [1].
Mitigation
The fix was committed in Linux kernel stable branches. Users should update their kernel to a version containing commit cd569b87378b (or its backport) [1]. No workaround is available other than avoiding the use of untrusted FAT filesystem images. The issue is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog as of publication date.
AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
168cafcb881364fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f9f..262ec1b790b560 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 4f3cc2b3089e74..8bf5f7a9fd2382 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -804,7 +804,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); -- cgit 1.3-korg
7fe0de287e93fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 9d062886fbc19d..63a323be9179df 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 9bc7d1602c15b7..2a0d9a9c2c8f79 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -808,7 +808,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); -- cgit 1.3-korg
9894c79fd946fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index efba301d68aec8..fba3a07d39478d 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index e69e2a4f99b928..d8e328e390d6b8 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -808,7 +808,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); -- cgit 1.3-korg
cd569b87378bfat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index efba301d68aec8..fba3a07d39478d 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 93fa8ddcf41452..3a9a849bc7885a 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -806,7 +806,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); -- cgit 1.3-korg
d3b7ffa90f61fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 2116c486843b7d..e189fbf95fcace 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 3cf22a6727f1b6..7d7ac30c6eff81 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -806,7 +806,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); -- cgit 1.3-korg
955c5d670b5afat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index f06f6ba643cc8c..c75e3791514a23 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 15bf32c21ac0db..31d37e2b4f6793 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -806,7 +806,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); -- cgit 1.3-korg
17866f8a0822fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f9f..262ec1b790b560 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 5dbc4cbb8fce3d..47ff083cfc7e66 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); -- cgit 1.3-korg
d0bb592fa9defat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f9f..262ec1b790b560 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 5dbc4cbb8fce3d..47ff083cfc7e66 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); -- cgit 1.3-korg
8cafcb881364fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f9f..262ec1b790b560 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 4f3cc2b3089e74..8bf5f7a9fd2382 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -804,7 +804,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); -- cgit 1.3-korg
17866f8a0822fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f9f..262ec1b790b560 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 5dbc4cbb8fce3d..47ff083cfc7e66 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); -- cgit 1.3-korg
7fe0de287e93fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 9d062886fbc19d..63a323be9179df 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 9bc7d1602c15b7..2a0d9a9c2c8f79 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -808,7 +808,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); -- cgit 1.3-korg
955c5d670b5afat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index f06f6ba643cc8c..c75e3791514a23 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 15bf32c21ac0db..31d37e2b4f6793 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -806,7 +806,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); -- cgit 1.3-korg
9894c79fd946fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index efba301d68aec8..fba3a07d39478d 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index e69e2a4f99b928..d8e328e390d6b8 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -808,7 +808,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); -- cgit 1.3-korg
cd569b87378bfat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index efba301d68aec8..fba3a07d39478d 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 93fa8ddcf41452..3a9a849bc7885a 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -806,7 +806,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); -- cgit 1.3-korg
d0bb592fa9defat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 0b920ee40a7f9f..262ec1b790b560 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 5dbc4cbb8fce3d..47ff083cfc7e66 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); -- cgit 1.3-korg
d3b7ffa90f61fat: avoid parent link count underflow in rmdir
2 files changed · +12 −3
fs/fat/namei_msdos.c+6 −1 modifieddiff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index 2116c486843b7d..e189fbf95fcace 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);
fs/fat/namei_vfat.c+6 −2 modifieddiff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 3cf22a6727f1b6..7d7ac30c6eff81 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -806,7 +806,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); -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"Missing sanity check on parent directory i_nlink before decrementing in rmdir allows link count underflow on corrupted FAT images."
Attack vector
An attacker who can mount a corrupted FAT image (or provide one to a victim) can craft the filesystem metadata so that a directory inode has an incorrect `i_nlink` value — for example, `i_nlink` is 2 even though subdirectories exist [patch_id=2661399]. When a user or process calls `rmdir` on a subdirectory under that parent, the kernel unconditionally calls `drop_nlink(dir)`, which decrements `i_nlink` from 2 to 1, and then on a subsequent rmdir from 1 to 0, triggering the `WARN_ON` in `drop_nlink()` and potentially causing a kernel crash or denial of service. No special privileges beyond the ability to mount a crafted FAT filesystem are required.
Affected code
The vulnerability is in the `vfat_rmdir()` function in `fs/fat/namei_vfat.c` and the `msdos_rmdir()` function in `fs/fat/namei_msdos.c` [patch_id=2661399]. Both functions unconditionally called `drop_nlink(dir)` after removing directory entries, without checking whether the parent directory's `i_nlink` was already too low.
What the fix does
The patch adds a guard in both `vfat_rmdir()` and `msdos_rmdir()`: `drop_nlink(dir)` is only called when `dir->i_nlink >= 3` [patch_id=2661399]. If the link count is below 3 (the minimum expected for a directory with a `..` entry and at least one subdirectory), the code instead calls `fat_fs_error(sb, "parent dir link count too low (%u)", dir->i_nlink)` to log a filesystem error and skips the decrement. This prevents the `i_nlink` underflow that would trigger the `WARN_ON` in `drop_nlink()`.
Preconditions
- inputAttacker must supply a corrupted FAT filesystem image where a directory inode has an i_nlink value less than 3 (e.g., 2) despite having subdirectories
- configThe victim must mount the crafted FAT image and perform rmdir operations on subdirectories
Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
8- git.kernel.org/stable/c/17866f8a0822d414cb02e621cf003a7d04396ef8nvd
- git.kernel.org/stable/c/7fe0de287e931e07cb96ecf1f449b2ebdb0e1115nvd
- git.kernel.org/stable/c/8cafcb881364af5ef3a8b9fed4db254054033d8anvd
- git.kernel.org/stable/c/955c5d670b5ae07c78f4345e23a895638db96ce1nvd
- git.kernel.org/stable/c/9894c79fd9466612d0514be157b5c30cd93aa645nvd
- git.kernel.org/stable/c/cd569b87378b9c33ae13c23d6bb9d205d66f7c4bnvd
- git.kernel.org/stable/c/d0bb592fa9def2bace90ac8926c0a1d6fa8c1aa0nvd
- git.kernel.org/stable/c/d3b7ffa90f613938128432c7b2f35b7aa4bdd86bnvd
News mentions
0No linked articles in our index yet.