memfs_ll: fix rename overwrite use-after-free and nlink double-decrement

When memfs_rename overwrites an existing target, it called
remove_child(newname) -- which deletes the matched Dentry -- and then
dereferenced existing_dentry again via existing_dentry->get_inode() to
decrement the replaced inode's nlink, reading the freed Dentry: a
use-after-free. Resolve the target
inode before remove_child and use the saved pointer afterwards; the
inode stays alive through the Inodes map reference.

The same branch also decremented newparent's nlink twice when the target
was a directory: once explicitly and again inside remove_child(), which
already decrements a directory's parent. A directory-over-directory
rename therefore left newparent's link count one too low (rmdir
decrements the parent exactly once, via remove_child). Drop the
redundant explicit decrement.

Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
This commit is contained in:
Bernd Schubert
2026-06-17 13:11:33 +02:00
parent a22569e617
commit 2f6f54ba4c
+7 -7
View File
@@ -958,15 +958,15 @@ static void memfs_rename(fuse_req_t req, fuse_ino_t parent, const char *name,
existing_dentry = newparentInode->find_child_locked(newname);
if (existing_dentry) {
if (existing_dentry->is_dir()) {
if (!existing_dentry->get_inode()->is_empty()) {
error = ENOTEMPTY;
goto out_unlock;
}
newparentInode->dec_nlink();
Inode *existing_inode = existing_dentry->get_inode();
if (existing_inode->is_dir() && !existing_inode->is_empty()) {
error = ENOTEMPTY;
goto out_unlock;
}
// remove_child() frees the dentry and already decrements
// newparent's nlink for a dir child; resolve the inode first.
newparentInode->remove_child(newname);
existing_dentry->get_inode()->dec_nlink();
existing_inode->dec_nlink();
}
child_dentry_copy = new Dentry(newname, child_dentry->inode);