From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f172.google.com (mail-pg1-f172.google.com [209.85.215.172]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0E16E3F4128 for ; Thu, 30 Jul 2026 09:22:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.172 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785403323; cv=none; b=ONr35wwSqLEsdV2V78XtLDvzT0YSYpESSpNUK1NTQvjiQOeVhcpT+MMVvf5CIm4odEIXLeKviaDAyT+893XyqOdF6shA3hZlyVqKJSDIey4gaZqh7PXecJwQ3LbpyJRbyYsScQ5zyDm7qn3DtPVaTKNr8nCN6H3ByYFj+L2+Evc= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785403323; c=relaxed/simple; bh=Q60EjgnQFgQyJMwRu6mnd0RkI8EMdd8vEQR7+IsD8Kk=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=hzFS9VjtkkWROr91+Nc2KA0B4BpGddQjYdr7kNI4Z/5ZF01SZHaOhvdV5pQlF/X/TA+1mqt7FJA+pDC+4KaCBWJu9HBVl1T3JCS0JHF/rAS/NWe1PIcFEiYfK+RB/liM+gj0DEEGIiqttIc6TTXbNbXP7apF1ECLMTOo56Qw16c= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=byBF35SN; arc=none smtp.client-ip=209.85.215.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="byBF35SN" Received: by mail-pg1-f172.google.com with SMTP id 41be03b00d2f7-c99eaa1f020so1600313a12.2 for ; Thu, 30 Jul 2026 02:21:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1785403319; x=1786008119; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=kfqgevbHtSrpPZ8NfhZXA/af8cJEWUQxpiR7rVpSzlc=; b=byBF35SNyTy/abN1gxokgrVqursTkCOC/iE511gTAgeNfRu2j9Zkgy3bJbzjVAwYSS kAwpM/lwEy2ZyS6tA5V58wQF4ZIC+UCfSMquYN8c7A1X7r1i5g4AR8N6dGAUPr25yqdF xD7BHpX1CqWMlPWwygMy+PWV6nKxFzB9l6brcuPCp8GHw2wAB0gnaLAOdCANo6H91Ka+ uPveofUpnt4S3hFX+rBVu2ELjqG4lJ5EkBb3gjoBqvQKPwijuG984OdXcxrz3tquhyqk HSP/pOQ0xamFxmuLj1hL7jOtJUaBHi7QAiBwEEawQe0qEuoTCBAvVcN+Ke7HP8ZlRMTC M0cw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1785403319; x=1786008119; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=kfqgevbHtSrpPZ8NfhZXA/af8cJEWUQxpiR7rVpSzlc=; b=imO9Q+y9bwo+P3sk+8QuA/aN6WQWV1pFtwcGOnXB/4T+bG0/NlzYNwHRYkdWXSgYVB aIdIrNuy560ttrEYFf2uTUdpo7daQAOrPVdabNAfWVR3u+OAZ4vXZBLSEoT9Wa9ZjMBM RhMNkWmIbtLNtHIzGxVGnonFmGwZSjQSLhrvxkogI6zPR62N9f9hdqaW4hjjqi5h50OP acG9em6vzaXcDSfvgVcUIZwnfl4dXmc8NLecewYchnMcZjPaLEGnAEP1+rqAdEdlyI93 v5zkIfDa2jbwqYqEAn9o9lttiZ7pyv9UOp8uyJsiqP9fQ5yrjnZZ+9fEUPLEhsKBGmpY KQHg== X-Forwarded-Encrypted: i=1; AHgh+RrMzoKlO0+nd04ZHIh93Q+ylUTiRs+/GtuMLb8ynv0qMQzPNyln/c/TKEFQE/DfPMRFA/2O4q5aotgYiRY=@vger.kernel.org X-Gm-Message-State: AOJu0YzEIsD1P7jRBvlCNK6x0F/GcH1p60Ysb026Hqdk9rddKnRhXS0S 8oOys/DgbBv8KC3JgHC7HXhcVtQkA9w1JsPAOIoWyvUGLMuYwhEAUt3O4DPQsEOp X-Gm-Gg: AR+sD13z1YtPUK4pH0hlDh8eK5UFjT8rji/ARIeyFmuxkw9TF91psZJIMr1Y7UfONzh cyh8WwKJp0dAesm31+OECAwAxASgzaVsJZ7evGOmqZAnqAcoCp1DI2GbjgAUiXoPBGiFn7FjfJb O4D+tRKGoatR30jJMhLfOTo6h7bwHYsTvb2AimOuDJ88AWjll94ZvpEZ7IeIGxVVWgi28jcIYDb rMeV2oGjD1ibwOQg/7MX9atVHOvcb1V3sdQLTBnHYnOmo+BEK6SJr4w76VVMJh9l0LMoU2sdUtG rnxxx63ksmwnudRD3u6xdILHaZqDghN3FKAVVmKgDAa+3sbTbcRDr1efcnG/8YQk4dg8ULOBS0b f25hTfVBNopFmv0t4mdH7bshbLjep35UxDJtLyOuSJ0M3dPiW1Vd4TGUxBmYgITEZrY8scvN8Dv X3gvt5iTYaaCOW2639hRS+JChrk9rJEYxwPzVNmf1+ywyZ1XJd2R13sBgdlCn2/jEfrk7fTnOU6 iNjBg== X-Received: by 2002:a05:6a21:6088:b0:3c0:b4f8:bbfb with SMTP id adf61e73a8af0-3c9004f61f2mr1873655637.22.1785403318411; Thu, 30 Jul 2026 02:21:58 -0700 (PDT) Received: from Default ([2409:40f4:100d:2c91:aff5:d71c:30c2:c60a]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-13e7273cd9esm19452885c88.13.2026.07.30.02.21.52 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 30 Jul 2026 02:21:57 -0700 (PDT) From: Jeffin Philip To: a.hindborg@kernel.org Cc: leitao@debian.org, viro@zeniv.linux.org.uk, sage@newdream.net, linux-kernel@vger.kernel.org, Jeffin Philip , syzbot+8358d1f3d9c15bdf1c9a@syzkaller.appspotmail.com Subject: [PATCH v2] configfs: fix race between symlink and rmdir Date: Thu, 30 Jul 2026 14:50:46 +0530 Message-ID: <20260730092046.13344-1-jeffinphilip14@gmail.com> X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit A race condition between configfs_rmdir() and configfs_symlink() can lead to a use-after-free of a config_item. When a directory is removed, configfs_rmdir() frees the associated config_item but leaves the dentry hashed until vfs_rmdir() calls d_delete(). This creates a window where a concurrent symlinkat() can find the hashed dentry, see that !d_unhashed(dentry) is true, and attempt to increment the refcount of the already freed config_item, triggering a warning: refcount_t: addition on 0; use-after-free. WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x9f/0x110 ... Call Trace: __refcount_add include/linux/refcount.h:-1 [inline] __refcount_inc include/linux/refcount.h:366 [inline] refcount_inc include/linux/refcount.h:383 [inline] kref_get include/linux/kref.h:45 [inline] config_item_get+0x88/0x90 fs/configfs/item.c:104 configfs_get_config_item fs/configfs/configfs_internal.h:127 [inline] get_target fs/configfs/symlink.c:128 [inline] configfs_symlink+0x4ab/0x1030 fs/configfs/symlink.c:185 vfs_symlink+0x18b/0x330 fs/namei.c:5660 filename_symlinkat+0x1cd/0x420 fs/namei.c:5685 __do_sys_symlinkat fs/namei.c:5705 [inline] __se_sys_symlinkat+0x4e/0x2b0 fs/namei.c:5700 To fix this, add d_drop(dentry) in configfs_rmdir() immediately after detaching the group/item and before unlinking the object. This ensures that any concurrent kern_path() will either not find the dentry, or configfs_get_config_item() will see d_unhashed(dentry) == true and safely return NULL. However, adding d_drop(dentry) exposes a secondary use-after-free bug in create_link(). configfs_symlink() calls get_target(), which resolves the path, gets the config_item, and then immediately drops the path reference. If configfs_rmdir() runs concurrently, it drops its config_item reference, and vfs_rmdir() calls d_delete_notify(). Since the dentry has only 1 reference left, dentry_unlink_inode() is called, freeing the configfs_dirent. Later, create_link() accesses the freed configfs_dirent. To fix this secondary issue, modify get_target() to return the path, and hold the path reference in configfs_symlink() until after create_link() finishes. By holding the path reference, the dentry's refcount remains >= 2, preventing d_delete() from calling dentry_unlink_inode(), which keeps the configfs_dirent alive. The path is safely cleaned up using the __free(path_put) attribute in configfs_symlink(). Fixes: 98702467f829 ("configfs: remove unnecessary dentry_unhash on rmdir, dir rename") Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot Reported-by: syzbot+8358d1f3d9c15bdf1c9a@syzkaller.appspotmail.com Tested-by: Jeffin Philip Reviewed-by: Jeffin Philip Signed-off-by: Jeffin Philip Closes: https://syzkaller.appspot.com/bug?extid=8358d1f3d9c15bdf1c9a Link: https://syzkaller.appspot.com/ai_job?id=13fec684-23f7-4f06-99db-ed37ae92a405 Cc: Breno Leitao --- Changelog: -Change author as requested in v1 discussion https://lore.kernel.org/all/20260730090549.11930-1-jeffinphilip14@gmail.com/T/#u fs/configfs/dir.c | 2 ++ fs/configfs/symlink.c | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fs/configfs/dir.c b/fs/configfs/dir.c index 3c88f13f1ca2..b0812432f6c5 100644 --- a/fs/configfs/dir.c +++ b/fs/configfs/dir.c @@ -1552,12 +1552,14 @@ static int configfs_rmdir(struct inode *dir, struct dentry *dentry) if (sd->s_type & CONFIGFS_USET_DIR) { configfs_detach_group(dentry); + d_drop(dentry); mutex_lock(&subsys->su_mutex); client_disconnect_notify(parent_item, item); unlink_group(to_config_group(item)); } else { configfs_detach_item(dentry); + d_drop(dentry); mutex_lock(&subsys->su_mutex); client_disconnect_notify(parent_item, item); diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index 31eb28b27309..c8a2d9ad6cac 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -114,18 +114,17 @@ static int create_link(struct config_item *parent_item, } -static int get_target(const char *symname, struct config_item **target, - struct super_block *sb) +static int get_target(const char *symname, struct path *path, + struct config_item **target, struct super_block *sb) { - struct path path __free(path_put) = {}; int ret; - ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &path); + ret = kern_path(symname, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, path); if (ret) return ret; - if (path.dentry->d_sb != sb) + if (path->dentry->d_sb != sb) return -EPERM; - *target = configfs_get_config_item(path.dentry); + *target = configfs_get_config_item(path->dentry); if (!*target) return -ENOENT; return 0; @@ -136,6 +135,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, const char *symname) { int ret; + struct path path __free(path_put) = {}; struct configfs_dirent *sd; struct config_item *parent_item; struct config_item *target_item = NULL; @@ -182,7 +182,7 @@ int configfs_symlink(struct mnt_idmap *idmap, struct inode *dir, * AV, a thoroughly annoyed bastard. */ inode_unlock(dir); - ret = get_target(symname, &target_item, dentry->d_sb); + ret = get_target(symname, &path, &target_item, dentry->d_sb); inode_lock(dir); if (ret) goto out_put; -- 2.55.0