From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C38E03F6C2A for ; Sun, 13 Sep 2026 18:50:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325420; cv=none; b=JBamjuXONMBWSSDrynLx2eJ7HrWb6alQUoBhFLkxvv50CMOuC77FYqPTXwGjCTk0k0CZmq70+/2x3lXUu3FBB8vq9qqAbgR1/5MZg0XPqmlmVNFJtXbVWqH3O206zcNUYw9/nMxbpkfjFoLGgyNHkSIqMUHnj4f2kMLwOMOiVxQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325420; c=relaxed/simple; bh=lYHNhghDqhHsjjnzaKnpHZ1Yj+9CZfCiOT3Sdjnr29M=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ibqogao0230C278vZ+dizQVetlJhcVggy2kcEN0hcG5Tg6G644ts1mikG5DEB6vEPUjr+B1sJ7Vymem9QpU3LS86j1HU+TOVY43Af0GiZqHy7XeUzke13prDSZcPAGGSXd7x7O0Rjq5IlEN0pCkmTP1N+OoCbiV+7FQgg7686So= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=TMeFNUx0; arc=none smtp.client-ip=195.121.94.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="TMeFNUx0" X-KPN-MessageId: cce791ff-afa3-11f1-83b4-005056ab378f Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id cce791ff-afa3-11f1-83b4-005056ab378f; Sun, 13 Sep 2026 20:49:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=vNEE7ynNmmy0RLTjcVOI7+EkabxMyxI+8yp5sxRccrg=; b=TMeFNUx0cSPY1PKF1AqiagTF6ZlpxI/QsncdPKv0WWLhfS03ngxOVOx2ypE8RDtrJZBbK5Iv+vpUC HrgQ81tlVbGRFDCC91C3Ca34dOjL/ghHyMps6+ncJ0xO6H5qBQowOVkjP1D6NZX7CLifqEySthCIWz xVl85q+Zd1LETleYL2CtNP7SzkQdL1kcrr8RdRInqvtNGhSwNt3Fa8cdODJ6EjakiC5Xiaxi7cdyCJ yJ4YV9tNqno+tNiHqehZSlxy9rDU+Wjg7OqPzSg06+z+LM8DPke9hiGWPVZ2eanrDruLFWHlUbRwHo UtLzPzcpUpbvBdebcJtL/3Io7FKxjWg== X-KPN-MID: 33|QMax/3FaIf/4wM/xQHbcP+J+3/ibaJYlJtXvgyzwZ/2KekrViWGMRMWNQVzcV/S /aNoIZXt6+BblJIzkzMtXyOheE1Y0WlBMi8OcKHLaSuI= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|GTfEyWXGERy4CA1ZkiB5z4mcfaCiV8u8nN5wMtg6E5YumrEy0Fye+91NFGIzCPE LpZQeUnGYKv4uNI4vedSXLg== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id ccaaf1df-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:07 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 01/12] fs/namei.c: use trailing_slashes() Date: Sun, 13 Sep 2026 20:50:05 +0200 Message-ID: <20260913185016.523376-2-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" There are several places in fs/namei.c that can use the trailing_slashes() function to improve context. To allow this broader use its signature is changed to take a struct qstr instead of a struct nameidata. Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 20a6534ea3ef..ab1302b38f46 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2781,9 +2781,16 @@ static const char *path_init(struct nameidata *nd, u= nsigned flags) return s; } =20 +static inline bool trailing_slashes(const struct qstr *last) +{ + /* last->len is set by hash_name() to the length of the current + * component ->name, terminating with '/' or a NUL character. */ + return (bool)last->name[last->len]; +} + static inline const char *lookup_last(struct nameidata *nd) { - if (nd->last_type =3D=3D LAST_NORM && nd->last.name[nd->last.len]) + if (nd->last_type =3D=3D LAST_NORM && trailing_slashes(&nd->last)) nd->flags |=3D LOOKUP_FOLLOW | LOOKUP_DIRECTORY; =20 return walk_component(nd, WALK_TRAILING); @@ -4695,17 +4702,12 @@ struct file *vfs_lookup_open(struct path *parent, s= truct qstr *last, } EXPORT_SYMBOL_FOR_MODULES(vfs_lookup_open, "nfsd"); =20 -static inline bool trailing_slashes(struct nameidata *nd) -{ - return (bool)nd->last.name[nd->last.len]; -} - static struct dentry *lookup_fast_for_open(struct nameidata *nd, int open_= flag) { struct dentry *dentry; =20 if (open_flag & O_CREAT) { - if (trailing_slashes(nd)) + if (trailing_slashes(&nd->last)) return ERR_PTR(-EISDIR); =20 /* Don't bother on an O_EXCL create */ @@ -4713,7 +4715,7 @@ static struct dentry *lookup_fast_for_open(struct nam= eidata *nd, int open_flag) return NULL; } =20 - if (trailing_slashes(nd)) + if (trailing_slashes(&nd->last)) nd->flags |=3D LOOKUP_FOLLOW | LOOKUP_DIRECTORY; =20 dentry =3D lookup_fast(nd); @@ -5087,7 +5089,7 @@ static struct dentry *filename_create(int dfd, struct= filename *name, * Do the final lookup. Suppress 'create' if there is a trailing * '/', and a directory wasn't requested. */ - if (last.name[last.len] && !want_dir) + if (trailing_slashes(&last) && !want_dir) create_flags &=3D ~LOOKUP_CREATE; dentry =3D start_dirop(path->dentry, &last, reval_flag | create_flags); if (IS_ERR(dentry)) @@ -5703,7 +5705,7 @@ int filename_unlinkat(int dfd, struct filename *name) goto exit_drop_write; =20 /* Why not before? Because we want correct error value */ - if (unlikely(last.name[last.len])) { + if (unlikely(trailing_slashes(&last))) { if (d_is_dir(dentry)) error =3D -EISDIR; else @@ -6305,16 +6307,16 @@ int filename_renameat2(int olddfd, struct filename = *from, if (flags & RENAME_EXCHANGE) { if (!d_is_dir(rd.new_dentry)) { error =3D -ENOTDIR; - if (new_last.name[new_last.len]) + if (trailing_slashes(&new_last)) goto exit_unlock; } } /* unless the source is a directory trailing slashes give -ENOTDIR */ if (!d_is_dir(rd.old_dentry)) { error =3D -ENOTDIR; - if (old_last.name[old_last.len]) + if (trailing_slashes(&old_last)) goto exit_unlock; - if (!(flags & RENAME_EXCHANGE) && new_last.name[new_last.len]) + if (!(flags & RENAME_EXCHANGE) && trailing_slashes(&new_last)) goto exit_unlock; } =20 --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2F6DE3F9F2E for ; Sun, 13 Sep 2026 18:50:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325423; cv=none; b=i5eA+ajtTHbxXeIhD1tegj7Dk69RpZOCoCyAYvHDFIGG5iQYKWVBrr8h0NeFmJ2IjMDTBUP1MmXldUS8GLx5rH2hxxTeXSbU3ULDX0xh//UbhlXq8bIkoSQoksREl1Fn37IsW4Qtao8p96bX4NlFwaYYXDycAihJYKxaEQEQQcI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325423; c=relaxed/simple; bh=nucgdV4mNEmYrMJHxpmwiU0YhgkIr0SBgSG4/hH2Ous=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=AELIsqaHESw6HspizvmEq2s88lx3MdFalmqg6bxVUXqrgpj9ujAd7rt4lAqvJmrKTE66Fd0PeykFuOuc/dyKu3OScNUipi3ysbNcOyh4n3RWP/V5ybEu/y9to6+8FVfoCqOQeVGtfPGGLYGXsyrtPuNRMMy8QuISg5vKob9XB/c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=Cb2RDh2B; arc=none smtp.client-ip=195.121.94.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="Cb2RDh2B" X-KPN-MessageId: cdfd343c-afa3-11f1-a059-005056abad63 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id cdfd343c-afa3-11f1-a059-005056abad63; Sun, 13 Sep 2026 20:49:09 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=FlXU4SBy6RoYNqXkd7secHUCvokLAIMbonJXzD2/4Z0=; b=Cb2RDh2BI19mG7uKJOjuhqDyGWjJlPJeP2yC5qhw1y452rWGhX+oVZgjLEAm7mSLPDokLw5OBpeBq hmVtAQqIFZOM6rAebqdX9eHr8+YNne+PD6RgMV2KptwJM6uxBbqy/Ub2xYJBmth3Uj4yIUZXeFiBNH KIbxnGrTPN3y9480dCKLRaxzHGCgjFlXxK9kLCCp3ilcKfxKdGRRiip0u106JYO0kPZAKPdlcIyfwH 8bgiwRLy8u28YxpBj8ic5Y4BuS7n3pLdjpjtQc7qSrrC3XDjJajEWaHswu4sg2Stq1KkpeDq0ALMfz B59eXAdlJrWRgRSPUZZPFsvAAEvNqaA== X-KPN-MID: 33|mxX592kQSy43fxwnxheixYb0Hz52W4Cowqrk8kDcGU1zJwMR0WeF3a+tacE/QEE 2xemoH40wIxfIW/WN33FegAISF1PGy0Au8lcXnNMohI4= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|xRTrjB7Hakujodgfn7IfDSiRqTup9HkdPKV2CaGrclBtfD94fOjOuw+puvfgMdI eV14bClkMk/CMyd/hdiUrNQ== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id cdc6c7e3-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:09 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 02/12] vfs: prepare vfs_creat|mkdir_no_perm for reuse in lookup_open() Date: Sun, 13 Sep 2026 20:50:06 +0200 Message-ID: <20260913185016.523376-3-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" To implement O_CREAT|O_DIRECTORY we will have to repeat some of the logic that is now in vfs_mkdir() (e.g. do error checks in the same order). Separate this out in vfs_mkdir_no_perm(), which does all the non-permission related work of vfs_mkdir(). Permission checking for the lookup_open() path is timed differently because we may just be doing an open and no create. Similar considerations give rise to vfs_create_no_perm(). Signed-off-by: Jori Koolstra --- fs/namei.c | 77 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 24 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index ab1302b38f46..d8401aa3100d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4166,6 +4166,24 @@ static inline umode_t vfs_prepare_mode(struct mnt_id= map *idmap, return mode; } =20 +static inline +int vfs_create_no_perm(struct mnt_idmap *idmap, struct dentry *dentry, + umode_t mode, struct delegated_inode *di) +{ + struct inode *dir =3D d_inode(dentry->d_parent); + int error; + + error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); + if (error) + return error; + + error =3D dir->i_op->create(idmap, dir, dentry, mode); + if (!error) + fsnotify_create(dir, dentry); + + return error; +} + /** * vfs_create - create new file * @idmap: idmap of the mount the inode was found from @@ -4198,13 +4216,8 @@ int vfs_create(struct mnt_idmap *idmap, struct dentr= y *dentry, umode_t mode, error =3D security_inode_create(dir, dentry, mode); if (error) return error; - error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); - if (error) - return error; - error =3D dir->i_op->create(idmap, dir, dentry, mode); - if (!error) - fsnotify_create(dir, dentry); - return error; + + return vfs_create_no_perm(idmap, dentry, mode, di); } EXPORT_SYMBOL(vfs_create); =20 @@ -4418,6 +4431,7 @@ static struct dentry *atomic_open(const struct path *= path, struct dentry *dentry dput(dentry); dentry =3D ERR_PTR(error); } + return dentry; } =20 @@ -4544,6 +4558,7 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, dentry =3D res; } } + if (dentry->d_inode || !(op->open_flag & O_CREAT)) { /* * No need to create a file. If lookup returned a positive @@ -5358,6 +5373,33 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename= , umode_t, mode, unsigned, d return filename_mknodat(AT_FDCWD, name, mode, dev); } =20 +static inline +struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *idmap, struct inode *di= r, + struct dentry *dentry, umode_t mode, + struct delegated_inode *di) +{ + unsigned max_links =3D dir->i_sb->s_max_links; + struct dentry *de; + int error; + + if (max_links && dir->i_nlink >=3D max_links) + return ERR_PTR(-EMLINK); + + error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); + if (error) + return ERR_PTR(error); + + de =3D dir->i_op->mkdir(idmap, dir, dentry, mode); + if (IS_ERR(de)) + return de; + if (de) { + dput(dentry); + dentry =3D de; + } + fsnotify_mkdir(dir, dentry); + return dentry; +} + /** * vfs_mkdir - create directory returning correct dentry if possible * @idmap: idmap of the mount the inode was found from @@ -5385,7 +5427,6 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, str= uct inode *dir, struct delegated_inode *delegated_inode) { int error; - unsigned max_links =3D dir->i_sb->s_max_links; struct dentry *de; =20 error =3D may_create_dentry(idmap, dir, dentry); @@ -5401,24 +5442,12 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, s= truct inode *dir, if (error) goto err; =20 - error =3D -EMLINK; - if (max_links && dir->i_nlink >=3D max_links) - goto err; - - error =3D try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, delegated_inode); - if (error) + de =3D vfs_mkdir_no_perm(idmap, dir, dentry, mode, delegated_inode); + if (IS_ERR(de)) { + error =3D PTR_ERR(de); goto err; - - de =3D dir->i_op->mkdir(idmap, dir, dentry, mode); - error =3D PTR_ERR(de); - if (IS_ERR(de)) - goto err; - if (de) { - dput(dentry); - dentry =3D de; } - fsnotify_mkdir(dir, dentry); - return dentry; + return de; =20 err: end_creating(dentry); --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CF3E53F99F4 for ; Sun, 13 Sep 2026 18:50:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325426; cv=none; b=laZMDQOZYy1WYw4VqfgPIaz3dRah9E0B+I6C4o1pezRL78KYzeuvKrnl6HX5mWbpaNIlr3g7Es5SpGjqrmhBsA82ICdta1u/yCN0Ii1V/eE2R8hYG4q+qf1L/G/LgptTyJ55u4f+kuBeT1JRIn1xO6XUf52BzKTqpLeqSFtZhIg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325426; c=relaxed/simple; bh=IDCWIVvwPzVh483KqrhHhS1Jo9ADNrPQA6Q4j5SruiQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UXNql7ujDPvo7zKPo/uu3cuhyBFPfEd6/Opr22ryhUArxGqs/nODxsBQXJuVzZah6KcXsqNla2wanUjd7FPiudUP7HqHjiejvjUONsOqqnn1DyF8UrQ3cnNmFkk1SI98ERNKUhLpeJ2t7vz6hiv3hYSPYJHrRQuzrJsd9F9078I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=LEkdT6Bd; arc=none smtp.client-ip=195.121.94.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="LEkdT6Bd" X-KPN-MessageId: cfcddf74-afa3-11f1-a059-005056abad63 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id cfcddf74-afa3-11f1-a059-005056abad63; Sun, 13 Sep 2026 20:49:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=HzSnbSwHRiOGXhA5N5V5Bak1BWFtNPQDJwmZ8P6MHG4=; b=LEkdT6BdTO3eHUkCPKcYswWGxnPOn1NJrTwRf7X1dRkCmmBm5weS6T4GSl4TItqwpiqDgQE5p1PsR jTi5+74Bu7HVmJiuO85cB12hlboTpNUrQ+aodhKo33KMzyEZZYCsO6w48M56gNauuVm5wNe6Ja3V8Q OkEKaaZtsHKBiGD2Ntbxk2pmGB0E6SiAB24jRvmYlDxC6/PRykwuY1pvhMFAnCQGro3tOGtsVRiQ9B mf+RSDzelBGfGZ5iENcgCEK3GcNr5eVjz1yGkSX6ER3scjO0a0Ui588qNShMql5OGHXoXbszg6o9ru cqvkpDuVzX96pUCuiDou9qvQgDejrfA== X-KPN-MID: 33|Im5LeJN53oxuzg0326CLs04iHcapfF+j5omCYZe0rRxsy5kEuNJBCnyRsFQzZl9 iGC56e7kNtX76Laj/WeaMdK6ULVYnJA/Eyhc12RxQWMk= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|oqRSx64+PH58pDxUsqTkWkzMA9stfxcY16shhxbI/kMjcxiicYo/ZsWS7MZ2sEC Dna0QAwU2rbJXVkBpKEnV/A== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id cf93a9db-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:12 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 03/12] vfs: lookup_open(): move setting FMODE_CREATED down Date: Sun, 13 Sep 2026 20:50:07 +0200 Message-ID: <20260913185016.523376-4-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In preparation for using vfs_create_no_perm() in lookup_open() we need to move setting FMODE_CREATED on the file mode to either before or after that call, as currently it is in the middle. If try_break_deleg() fails it is currently not set, but vfs_create_no_perm() includes a try_break_deleg(). Going up the call chain of lookup_open() we see that it is only used in open_last_lookups() if no error is returned from lookup_open(), so we can safely move it to after the filesystem create() call. This also makes more sense when reading the code as you don't have to wonder what the implications are of setting FMODE_CREATED before the create() call. Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index d8401aa3100d..f1cc3ce979ef 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4580,7 +4580,6 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, if (error) goto out_dput; =20 - file->f_mode |=3D FMODE_CREATED; if (!dir_inode->i_op->create) { error =3D -EACCES; goto out_dput; @@ -4589,6 +4588,8 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, error =3D dir_inode->i_op->create(idmap, dir_inode, dentry, mode); if (error) goto out_dput; + + file->f_mode |=3D FMODE_CREATED; out: if (!IS_ERR(dentry)) { if (file->f_mode & FMODE_CREATED) --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.168]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4CA273BFE5C for ; Sun, 13 Sep 2026 18:49:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.168 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325373; cv=none; b=d6CyHkX8F3pywkGujqAlhDi+vZttFKZ+XUmyys4J15uepHVAIhoCWXPPDLcWHIrDe2F180UDBtZPl+GYUYUJb9+zN5Bx493F0McdXCR90wbf6ZhTcz39SVCnOZubHd1wjb1xg4xBULZk0MZsNq6xlCV9xOu71QTwM8mWUD0YJXw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325373; c=relaxed/simple; bh=7FryVKhEiffXzWSttHk8q6VLm4lqtV3X9GE9aGTs7FU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Fh9hoskDnOuNMeLAaqE/AUFjkk4Gm0lJA50zdKVPrE39k4LIctVST2YbKZrev/3iViNnuVel/W30uUE66Ynwil2NjxTVua1xb+80A43goM99r+ZQdPLwMVSXPmN15iuykz+pFXgD/YPkv64LUmmZT+0b74yfmYL158FXXoWDe0E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=XNTdPSj7; arc=none smtp.client-ip=195.121.94.168 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="XNTdPSj7" X-KPN-MessageId: d23e03a9-afa3-11f1-861e-005056aba152 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id d23e03a9-afa3-11f1-861e-005056aba152; Sun, 13 Sep 2026 20:49:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=Wr1Me+VIRQw82r+Sv6eXG48kxSckOOl6pDcP79CXgdo=; b=XNTdPSj7bpZq03Te6vK7ceCtf5YAwnY1S6WJWNS+pP08j+MdNz66tmYec7+prXAFXJUmr2lO5bKpb 13gw2icAZu566IbqIUAagz/YS2wXHV3qF+BHZgQcUO+nVSh1jyPVEgBzrby3e77PFPGtOINNcyegYZ hUjHbizdVUqijBFU/kANfnoH0iz8AJ16+njl55Yk9MQPZlw4QOG+0eG5IYC+a8Sv2Wzed5COXP9xwX 3SPqoGr7KSCYB/koV1q6Ik7kyCZaDy09tDf9mTNihFqN0TtILEXrPVkNj0MD0ngGgv6Mk2qANyQsEx LQ56mL7EUcPOyOHnNEJnrJ4HuFJ/cVg== X-KPN-MID: 33|/jz+1nw7Pnwn25NNNDD7yBbkrE4ZlPsKatZ27un8zQ7Xc35T0y8N58wIT9rOl4F TR7qHBHuLNEKdOvBFVsUml4T0PjL6vFEdodBVUDdLcGI= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|0C0heCk1lrUB+LKVl/C9TsvNFuvq1UVEHdYPWWx62jNsuyYPrO5Gkq2VCEAWPGr h3v+PJrjssDn0bdppVIoEsw== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id d208387d-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:16 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 04/12] vfs: move ->create check in lookup_open() to before try_break_deleg() Date: Sun, 13 Sep 2026 20:50:08 +0200 Message-ID: <20260913185016.523376-5-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The i_op->create check in lookup_open() takes place after the try_break_deleg() call. This does not match the order when doing a regular file create via mknod(2). There the call order is: filename_mknodat() vfs_create() i_op->create check try_break_deleg() Move the i_op->create check to before try_break_deleg() in lookup_open(). Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index f1cc3ce979ef..e1cdece42c9b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4576,15 +4576,15 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, goto out_dput; } =20 - error =3D try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, &delegated_i= node); - if (error) - goto out_dput; - if (!dir_inode->i_op->create) { error =3D -EACCES; goto out_dput; } =20 + error =3D try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, &delegated_i= node); + if (error) + goto out_dput; + error =3D dir_inode->i_op->create(idmap, dir_inode, dentry, mode); if (error) goto out_dput; --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id AEC8B3B8BD7 for ; Sun, 13 Sep 2026 18:49:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325369; cv=none; b=TiYvwok/YPNFZbRIpRptTpwZxfTIKBfGHfFeVg02OG+2dVN4CyYnKPl/25ydsRQUSEc3/wUJv9Bk335d6bgPkoRpzwprBg0T3hzU1zwYX2WLSTTBKHQ5KMzUfo5+4rLGq57Y3KFlqvYEwEiDe8k8Od0HaIUwuGsEfj10qIww9lM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325369; c=relaxed/simple; bh=XqjTRSAeusxjIvb88nvpx2e0wRq44xzIeWwFjEgozjw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=UxkAEHmmOd+EOBd/lnZC9gF6ZOhWV9LaQeXf0btgqXw9qmwWQsehmxsu63XqiNOi8vKqSzzDGB0W/JFMDrwcKsnLwwGBooxkJPuTiLuT+v19c0TBXZ6sT4fdZexHgL5PIUQ+xVHhu6j9ehYPc2NJf5M9OmhRL7CgTZY+hgRg3Fw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=hqAGKD2C; arc=none smtp.client-ip=195.121.94.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="hqAGKD2C" X-KPN-MessageId: d4d3bcc3-afa3-11f1-83b4-005056ab378f Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id d4d3bcc3-afa3-11f1-83b4-005056ab378f; Sun, 13 Sep 2026 20:49:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=cy7joNni2CrQLvwbxj313gtPlwa2FaN78EUx0NBwmA8=; b=hqAGKD2C3uKTXolIotZ/b6MsW0k+V52RGdI1uUFcACNckFpjcp5EPfiF2+mY2tGsvBPN7QWEzVSDw Jh0MHT85d2Y7UjirEVsvn22sjI1f2dH/5lJVjNoIt2J0oPlzA/X0Bmft7Z3ajTDloDAhbk+aQ/E5hp jZOtMVwqFhaxr2ud4zq9HmZA0frI2l5n8UHDqadxmvJxg9Bc8wBOLNesGNX6Y81Lz6KpGstruTQWwm 3rJqzkzIPep2JBCuTJxJt7NtT/a9ZD72KeIbtBvecZ42t4GyLvBKoKvNCGgLabuFgSzF+G45PJ5yBs kjUD5kbZFzzZKzjPTxYoHrqkq59qknA== X-KPN-MID: 33|zsZ60YH4g4xW5cNEZAWv56vNMlKRW86APWQ0Oe+ZcC+/WGOyA4hFA73cNpZZxSt Hz/Qremx1HaWhevxZ5rlGeaw27dr6rxVWW+31Iimch+o= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|H8sS7t4Kh9QsZRaMGpmZrnZ4tpYncHUSMyoM33kJtj1L839lf+uffQwaXY27yGK B3EQITgRBFcTVFZKoRZBQOA== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id d499945f-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:20 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 05/12] vfs: lookup_open(): use vfs_create_no_perm() Date: Sun, 13 Sep 2026 20:50:09 +0200 Message-ID: <20260913185016.523376-6-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" We can replace the code in the no create_error/negative dentry found from lookup case in lookup_open() with the vfs_create_no_perm() helper. Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index e1cdece42c9b..90c8be0257be 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4430,8 +4430,14 @@ static struct dentry *atomic_open(const struct path = *path, struct dentry *dentry } dput(dentry); dentry =3D ERR_PTR(error); + } else { + if (file->f_mode & FMODE_CREATED) + fsnotify_create(dir_inode, dentry); + if (file->f_mode & FMODE_OPENED) + fsnotify_open(file); } =20 + return dentry; } =20 @@ -4581,22 +4587,12 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, goto out_dput; } =20 - error =3D try_break_deleg(dir_inode, LEASE_BREAK_DIR_CREATE, &delegated_i= node); - if (error) - goto out_dput; - - error =3D dir_inode->i_op->create(idmap, dir_inode, dentry, mode); + error =3D vfs_create_no_perm(idmap, dentry, mode, &delegated_inode); if (error) goto out_dput; =20 file->f_mode |=3D FMODE_CREATED; out: - if (!IS_ERR(dentry)) { - if (file->f_mode & FMODE_CREATED) - fsnotify_create(dir_inode, dentry); - if (file->f_mode & FMODE_OPENED) - fsnotify_open(file); - } if ((open_flag & O_CREAT) || create_error) inode_unlock(dir_inode); else @@ -5216,13 +5212,6 @@ struct file *dentry_create(struct path *path, int fl= ags, umode_t mode, /* Drop the extra reference */ dput(orig_dentry); =20 - if (!error) { - if (file->f_mode & FMODE_CREATED) - fsnotify_create(dir->d_inode, dentry); - if (file->f_mode & FMODE_OPENED) - fsnotify_open(file); - } - path->dentry =3D dentry; =20 } else { --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DF2FD3A641B for ; Sun, 13 Sep 2026 18:49:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325374; cv=none; b=r2qfPWSvQqujFcJ9c2ZRf/n6DD5LoQBrroObq60QNiWDdsaH1h2veIuDnHDU6A+7G6khWXcuqDxUxZcfX8BYOfVy7t2obD8FV6Gg2xUbvo3rAD1HsREHetvmCIPEnowhdJLM+MaqJEO2QeKCdP+KOZ+KWGhtO3XfX6LSi7gT6Kg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325374; c=relaxed/simple; bh=C4D1/qb2Wwro6tn+oPvBlRjy4O2Ewb6QU4b5MpGFGls=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=WaCM+nB0ipO5m0s0zGSVzCSBlsfXTqWW5DjA5K5DD2Yo+UQIv5lJDT5qtdFEGRtuHZ3O2aAUBeGsEq1USuldWm2RBVcd5vDKVD3LXoEXF37UaDAsWrAwKmmqBp9QwINVmpNaKYoSd0zH+JUoIDbAa6O/InhgtCAyYzO8m3PeqHk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=kCYtwrau; arc=none smtp.client-ip=195.121.94.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="kCYtwrau" X-KPN-MessageId: d6df4ab4-afa3-11f1-a059-005056abad63 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id d6df4ab4-afa3-11f1-a059-005056abad63; Sun, 13 Sep 2026 20:49:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=gaMm1MCPfV8HqhSz+lohLsmVbeKgTk3fNDTF3rakLhw=; b=kCYtwrauGLpplkQUiK9AB1g+BvKZJsA4spAumCxeV10qJQ5ns33cok0s4REnkOlf6opjLDYSMxaWY 7b9JIh/Mxr+wypZ6+NzC+rqolftBbCozMalBCqXaTAoqFEZE6a7UZzQfbNjRy6mRMb66PRPTh74WAp 2kTEkKC2XsjsqFxVlOkY0u3npAtxUoLvAD1pBefccsrB9blkT7yR8+IN69JYU//nnR+iI3FUoSZf5U mv5q1n0+aThhRMrYwtWXDU3r+kplGV7wFHxw2AJyjSdw9dNoUUkNOn54Q0LeD0WAKj8o8wNQTpGR/D WzidNfIuHr6lahgvmT2Zv0KmNIlOGFA== X-KPN-MID: 33|1jkdTId/MWzF0abILpxJoeOSfNeTOI2S3xbjb8yleOGoGKyIQKgFgju02r44sha TfFHl0ewiaJAgFiL/HwUEWd3+Ko0UEhDYARZKAlASBDc= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|89qOjYjq9+hSS5rdYNM3I8MCNUUhxVP6x/QoQdKzLMHmARJqLV9jYi5sMogjveh 5sYFBP075yu4kG/ZA0/SQ/w== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id d6a4097b-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:24 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 06/12] vfs: lookup_open(): lock the parent as I_MUTEX_PARENT Date: Sun, 13 Sep 2026 20:50:10 +0200 Message-ID: <20260913185016.523376-7-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" lookup_open() takes the parent directory's i_rwsem with inode_lock(), that is, in the I_MUTEX_NORMAL class, before creating the last component. Every other VFS path that locks a directory in order to create something in it goes through start_dirop() and friends, which use inode_lock_nested(dir, I_MUTEX_PARENT). Lock the parent as I_MUTEX_PARENT here too, so that lookup_open() matches the rest of the VFS before it starts calling ->mkdir(), which until now was only ever reached with the parent held that way. No functional change intended. Signed-off-by: Jori Koolstra --- fs/namei.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/namei.c b/fs/namei.c index 90c8be0257be..0efd395a1a65 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4482,7 +4482,7 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, */ } if (open_flag & O_CREAT) - inode_lock(dir_inode); + inode_lock_nested(dir_inode, I_MUTEX_PARENT); else inode_lock_shared(dir_inode); =20 --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.167]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BD4DD3A2E2B for ; Sun, 13 Sep 2026 18:49:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.167 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325378; cv=none; b=E2qUMmjo4YQRSI0aob4dgDDU8iEQE1Ip0iaDvYwjW55OomARtQOaAZUWs8QbhmBK7BViKsgWG2vYW4UsoJdKglIBW5Ypg3es4TVR09E0N7JBiqTc6T6Au9i5kYIt2UoPvFHPuH7yImI0GyNfyNhsO8SfK8HUC8FMAxTCGbC2NnU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325378; c=relaxed/simple; bh=p0xYvk/om91bpR/yV/UDm5IF5412apSCT3wOUORWMtc=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HM2X6nEQSRn+7vu/JjDa/Y3wALgoHvU/XTQpW149sKTsc8b9l+Zbp8/qabPIEphGXTQE3YGp6nVqAg6v35J5qN5FP09EUKwb192hbrFt6BgP8ExBLeKZVQKWfjA7sYedlc4k2SwKq3cH1T81BVFPIPnkdLs33dx7Oe1kgaFE9z0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=KtNretv7; arc=none smtp.client-ip=195.121.94.167 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="KtNretv7" X-KPN-MessageId: d9185199-afa3-11f1-b2a8-005056abbe64 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id d9185199-afa3-11f1-b2a8-005056abbe64; Sun, 13 Sep 2026 20:49:28 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=vwQbQoYrhJ5UmsIqRNjEqKHIv2uOajaTvKsLR/kgvRs=; b=KtNretv712zh8v5stYm+nNSyPk8ksjbUP4ZiGKB0+ex/Km9MzN2frLloLZ9hhMJyY3l8J7K5VEIMa 5i9k0s8wKGjaB66+bGrq6kSmrqruvYdVjblWsTjKeytXBCCVpXYPUNae1VS+NAJS8qOYYyyfkHa3Qg AEvQmHGSBI+7ke1zvNPy7dA9bVFtRmi1L+K0sV3bc1+Y4sOFt/qpr9K4ilTjExqlcH3CGGY5kL8Mau 29JhOYmlAolMV/jNKVxVv4jDtY567ZopZb6C0jwv0l9yqpWe7tjT6GusQ/G3UnLbPLhk/EXmtDUbXo Spzcp+FjRvzu41byjAlG+POM16kPCPA== X-KPN-MID: 33|s4jkRdsdqpVQSggjrLbcwjJ1VVPFnnF13BzVFvFj/DGnGrCKvhZehi8KaB5ks4q /VzaaVFx4sN63lLX2ko9XixcTmgYdgEF/yk1Ni4LUt2s= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|fpJAQToWFJpW96sToU6Pw9TL1t1TJ4vA9xOzKErGaWP+XaEpU0rRbN6H1ACXezZ BrKfmAN7FB/6+BGXC2a1tuQ== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id d8dd8c26-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:28 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 07/12] vfs: add O_CREAT|O_DIRECTORY to open*(2) Date: Sun, 13 Sep 2026 20:50:11 +0200 Message-ID: <20260913185016.523376-8-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Currently there is no way to race-freely create and open a directory. For regular files we have open(O_CREAT) for creating a new file inode, and returning a pinning fd to it. The lack of such functionality for directories means that when populating a directory tree there's always a race involved: the inodes first need to be created, and then opened to adjust their permissions/ownership/labels/timestamps/acls/xattrs/..., but in the time window between the creation and the opening they might be replaced by something else. Addressing this race without a proper API is only partially possible: the caller can immediately fstat() what was opened to verify that it has the expected inode type, owner and mode. But besides being easy to get wrong, this cannot establish who created the directory: a directory created by another process with identical credentials is indistinguishable from one the caller created itself, so the caller cannot tell whether the directory is its own to manage. Historically, the O_CREAT|O_DIRECTORY behaviour was to return ENOTDIR if a regular file exists at the open path; EISDIR if a directory exists at the path; and to create a regular file if no file exists at the path. This behaviour changed accidentally with commit 973d4b73fbaf ("do_last(): rejoin the common path even earlier in FMODE_{OPENED,CREATED} case") causing ENOTDIR to return in the last case while still creating the file. As this change was not detected for a long time, Brauner proposed to adopt the more consistent NetBSD behaviour, i.e. to return EINVAL on the O_CREAT|O_DIRECTORY combination. This change was applied in commit 43b450632676 ("open: return EINVAL for O_DIRECTORY | O_CREAT") in March, 2023. As the EINVAL behaviour has been in the kernel for about 3 years now, no rollback is expected as a result of userspace reliance on old behaviour, leaving us free to reassign the O_CREAT|O_DIRECTORY semantics. O_CREAT|O_DIRECTORY is made to reduce to a lookup on ->atomic_open() filesystems. These filesystems currently cannot handle O_CREAT|O_DIRECTORY without protocol extensions and therefore are forced into a fallback mode by stripping the O_CREAT bit. This causes existing directories to be successfully opened, while for targets that should have been created, -ENOENT is returned. This -ENOENT is then converted to -EOPNOTSUPP in later atomic_open(). The simple option of just returning -EOPNOTSUPP directly leads to inconsistent behaviour: before ->atomic_open() is called in lookup_open(), the dcache is queried. So returning -EOPNOTSUPP immediately would make O_CREAT|O_DIRECTORY dependent on the cache state of the dentry. There is no separate sysctl for directory creation implemented currently. Therefore, for the S_ISDIR case, disabling sysctl_protected_regular is not enough to allow creating a directory in a sticky folder, because that may surprise users not expecting that O_CREAT|O_DIRECTORY is possible on newer kernels. This feature idea (and some of its description) is taken from the UAPI group: https://github.com/uapi-group/kernel-features?tab=3Dreadme-ov-file#race-fre= e-creation-and-opening-of-non-file-inodes Signed-off-by: Jori Koolstra --- fs/namei.c | 116 +++++++++++++++++++++++++++++++++++------- fs/open.c | 25 +++++---- include/linux/fcntl.h | 6 +++ 3 files changed, 117 insertions(+), 30 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 0efd395a1a65..6ff0a3c04f02 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1382,13 +1382,13 @@ int may_linkat(struct mnt_idmap *idmap, const struc= t path *link) =20 /** * may_create_in_sticky - Check whether an O_CREAT open in a sticky direct= ory - * should be allowed, or not, on files that already - * exist. + * should be allowed, or not, on files/directories that + * already exist. * @idmap: idmap of the mount the inode was found from * @nd: nameidata pathwalk data * @inode: the inode of the file to open * - * Block an O_CREAT open of a FIFO (or a regular file) when: + * Block an O_CREAT open of a FIFO (or a regular file/directory) when: * - sysctl_protected_fifos (or sysctl_protected_regular) is enabled * - the file already exists * - we are in a sticky directory @@ -1416,6 +1416,14 @@ static int may_create_in_sticky(struct mnt_idmap *id= map, struct nameidata *nd, if (likely(!(dir_mode & S_ISVTX))) return 0; =20 + /* + * There is no separate sysctl for directory creation in sticky + * folders. Therefore, for the S_ISDIR case, disabling + * sysctl_protected_regular is not enough to allow creating a + * directory in a sticky folder, because that may surprise users + * not expecting that O_CREAT|O_DIRECTORY is possible on newer + * kernels. + */ if (S_ISREG(inode->i_mode) && !sysctl_protected_regular) return 0; =20 @@ -1447,6 +1455,12 @@ static int may_create_in_sticky(struct mnt_idmap *id= map, struct nameidata *nd, "sticky_create_regular"); return -EACCES; } + + if (S_ISDIR(inode->i_mode)) { + audit_log_path_denied(AUDIT_ANOM_CREAT, + "sticky_create_dir"); + return -EACCES; + } } =20 return 0; @@ -4334,21 +4348,43 @@ static inline int open_to_namei_flags(int flag) =20 static int may_o_create(struct mnt_idmap *idmap, const struct path *dir, struct dentry *dentry, - umode_t mode) + int open_flag, umode_t mode) { - int error =3D security_path_mknod(dir, dentry, mode, 0); + struct inode *dir_inode =3D dir->dentry->d_inode; + bool create_dir =3D O_IS_MKDIR(open_flag); + int error; + + WARN_ON_ONCE(create_dir && !(mode & S_IFDIR)); + + if (create_dir) + error =3D security_path_mkdir(dir, dentry, mode); + else + error =3D security_path_mknod(dir, dentry, mode, 0); if (error) return error; =20 if (!fsuidgid_has_mapping(dir->dentry->d_sb, idmap)) return -EOVERFLOW; =20 - error =3D inode_permission(idmap, dir->dentry->d_inode, - MAY_WRITE | MAY_EXEC); + error =3D inode_permission(idmap, dir_inode, MAY_WRITE | MAY_EXEC); if (error) return error; =20 - return security_inode_create(dir->dentry->d_inode, dentry, mode); + if (create_dir) + error =3D security_inode_mkdir(dir_inode, dentry, mode); + else + error =3D security_inode_create(dir_inode, dentry, mode); + + return error; +} + +static inline umode_t o_create_mode(struct mnt_idmap *idmap, + const struct inode *dir, int open_flag, umode_t mode) +{ + if (O_IS_MKDIR(open_flag)) + return vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR); + else + return vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); } =20 /** @@ -4384,8 +4420,9 @@ static struct dentry *atomic_open(const struct path *= path, struct dentry *dentry =20 file->__f_path.dentry =3D DENTRY_NOT_SET; file->__f_path.mnt =3D path->mnt; + error =3D dir_inode->i_op->atomic_open(dir_inode, dentry, file, - open_to_namei_flags(open_flag), mode); + open_to_namei_flags(open_flag), mode); d_lookup_done(dentry); =20 if (!error) { @@ -4427,12 +4464,32 @@ static struct dentry *atomic_open(const struct path= *path, struct dentry *dentry */ audit_inode_child(dir_inode, dentry, AUDIT_TYPE_CHILD_CREATE); error =3D create_error; + } else if (O_IS_MKDIR(open_flag) && error =3D=3D -ENOENT) { + /* + * If the underlying filesystem does not implement + * O_CREAT|O_DIRECTORY, it strips the O_CREAT bit and + * continues as a lookup. We can't simply return + * -EOPNOTSUPP from unsupported ->atomic_open() + * implementations because the dentry might be in the + * dcache. In that case, lookup_open() returns before + * reaching ->atomic_open(), and hence whether you get + * -EOPNOTSUPP on O_CREAT|O_DIRECTORY would not only + * depend on the underlying filesystem, but also on + * the state of the dcache. Still, we must make an + * effort to differentiate a regular -ENOENT from the + * unsupported O_CREAT|O_DIRECTORY case. + */ + error =3D -EOPNOTSUPP; } dput(dentry); dentry =3D ERR_PTR(error); } else { - if (file->f_mode & FMODE_CREATED) - fsnotify_create(dir_inode, dentry); + if (file->f_mode & FMODE_CREATED) { + if (d_is_dir(dentry)) + fsnotify_mkdir(dir_inode, dentry); + else + fsnotify_create(dir_inode, dentry); + } if (file->f_mode & FMODE_OPENED) fsnotify_open(file); } @@ -4441,6 +4498,9 @@ static struct dentry *atomic_open(const struct path *= path, struct dentry *dentry return dentry; } =20 +static inline +struct dentry *vfs_mkdir_no_perm(struct mnt_idmap *, struct inode *, struc= t dentry *, + umode_t, struct delegated_inode *); /* * Look up and maybe create and open the last component. * @@ -4462,6 +4522,7 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, struct mnt_idmap *idmap; struct dentry *dir =3D nd->path.dentry; struct inode *dir_inode =3D dir->d_inode; + bool create_dir =3D O_IS_MKDIR(op->open_flag); int open_flag; struct dentry *dentry; int error, create_error; @@ -4474,6 +4535,9 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, mode =3D op->mode; create_error =3D 0; =20 + if (create_dir && dir_inode->i_op->atomic_open) + open_flag &=3D ~O_CREAT; + if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) { got_write =3D !mnt_want_write(nd->path.mnt); /* @@ -4534,10 +4598,10 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, if (open_flag & O_CREAT) { if (open_flag & O_EXCL) open_flag &=3D ~O_TRUNC; - mode =3D vfs_prepare_mode(idmap, dir_inode, mode, mode, mode); + mode =3D o_create_mode(idmap, dir_inode, open_flag, mode); if (likely(got_write)) create_error =3D may_o_create(idmap, &nd->path, - dentry, mode); + dentry, open_flag, mode); else create_error =3D -EROFS; } @@ -4582,12 +4646,25 @@ static struct dentry *lookup_open(struct nameidata = *nd, struct file *file, goto out_dput; } =20 - if (!dir_inode->i_op->create) { + /* mimic operation missing errnos of vfs_mkdir/vfs_create */ + if (create_dir && !dir_inode->i_op->mkdir) { + error =3D -EPERM; + goto out_dput; + } + if (!create_dir && !dir_inode->i_op->create) { error =3D -EACCES; goto out_dput; } =20 - error =3D vfs_create_no_perm(idmap, dentry, mode, &delegated_inode); + if (create_dir) { + struct dentry *res =3D vfs_mkdir_no_perm(idmap, dir_inode, dentry, mode, + &delegated_inode); + error =3D PTR_ERR_OR_ZERO(res); + if (!error) + dentry =3D res; + } else { + error =3D vfs_create_no_perm(idmap, dentry, mode, &delegated_inode); + } if (error) goto out_dput; =20 @@ -4719,7 +4796,7 @@ static struct dentry *lookup_fast_for_open(struct nam= eidata *nd, int open_flag) struct dentry *dentry; =20 if (open_flag & O_CREAT) { - if (trailing_slashes(&nd->last)) + if (trailing_slashes(&nd->last) && !(open_flag & O_DIRECTORY)) return ERR_PTR(-EISDIR); =20 /* Don't bother on an O_EXCL create */ @@ -4820,8 +4897,9 @@ static int do_open(struct nameidata *nd, if (open_flag & O_CREAT) { if ((open_flag & O_EXCL) && !(file->f_mode & FMODE_CREATED)) return -EEXIST; - if (d_is_dir(nd->path.dentry)) + if (!(open_flag & O_DIRECTORY) && d_is_dir(nd->path.dentry)) return -EISDIR; + error =3D may_create_in_sticky(idmap, nd, d_backing_inode(nd->path.dentry)); if (unlikely(error)) @@ -5159,7 +5237,7 @@ inline struct dentry *start_creating_user_path( EXPORT_SYMBOL(start_creating_user_path); =20 /** - * dentry_create - Create and open a file + * dentry_create - Create and open a regular file * @path: path to create * @flags: O\_ flags * @mode: mode bits for new file @@ -5196,7 +5274,7 @@ struct file *dentry_create(struct path *path, int fla= gs, umode_t mode, path->dentry =3D dir; mode =3D vfs_prepare_mode(idmap, dir_inode, mode, S_IALLUGO, S_IFREG); =20 - create_error =3D may_o_create(idmap, path, dentry, mode); + create_error =3D may_o_create(idmap, path, dentry, flags, mode); if (create_error) flags &=3D ~O_CREAT; =20 diff --git a/fs/open.c b/fs/open.c index 6b1c14e684a9..189af02a2425 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1239,29 +1239,30 @@ inline int build_open_flags(const struct open_how *= how, struct open_flags *op) if (WILL_CREATE(flags)) { if (how->mode & ~S_IALLUGO) return -EINVAL; - op->mode =3D how->mode | S_IFREG; + if (O_IS_MKDIR(flags)) + op->mode =3D how->mode | S_IFDIR; + else + op->mode =3D how->mode | S_IFREG; } else { if (how->mode !=3D 0) return -EINVAL; op->mode =3D 0; } =20 - /* - * Block bugs where O_DIRECTORY | O_CREAT created regular files. - * Note, that blocking O_DIRECTORY | O_CREAT here also protects - * O_TMPFILE below which requires O_DIRECTORY being raised. - */ - if ((flags & (O_DIRECTORY | O_CREAT)) =3D=3D (O_DIRECTORY | O_CREAT)) - return -EINVAL; - /* Now handle the creative implementation of O_TMPFILE. */ if (flags & __O_TMPFILE) { /* * In order to ensure programs get explicit errors when trying * to use O_TMPFILE on old kernels we enforce that O_DIRECTORY - * is raised alongside __O_TMPFILE. + * is raised alongside __O_TMPFILE, but without O_CREAT. The + * reason for disallowing O_CREAT|O_TMPFILE is that + * O_DIRECTORY|O_CREAT used to work and created a regular file + * if nothing existed at the open path. Hence, allowing the + * combination would have caused O_CREAT|O_TMPFILE to create a + * regular (non-temporary) file on old kernels, while the caller + * would believe they created an actual O_TMPFILE. */ - if (!(flags & O_DIRECTORY)) + if (!(flags & O_DIRECTORY) || (flags & O_CREAT)) return -EINVAL; if (!(acc_mode & MAY_WRITE)) return -EINVAL; @@ -1319,6 +1320,8 @@ inline int build_open_flags(const struct open_how *ho= w, struct open_flags *op) op->intent =3D flags & O_PATH ? 0 : LOOKUP_OPEN; =20 if (flags & O_CREAT) { + if ((flags & O_DIRECTORY) && (acc_mode & MAY_WRITE)) + return -EISDIR; op->intent |=3D LOOKUP_CREATE; if (flags & O_EXCL) { op->intent |=3D LOOKUP_EXCL; diff --git a/include/linux/fcntl.h b/include/linux/fcntl.h index 6ad6b9e7a226..204e16bbe263 100644 --- a/include/linux/fcntl.h +++ b/include/linux/fcntl.h @@ -30,6 +30,12 @@ */ #define __O_REGULAR (1 << 30) =20 +#define O_MKDIR_MASK (O_CREAT | O_DIRECTORY) +static inline bool O_IS_MKDIR(unsigned int flags) +{ + return (flags & O_MKDIR_MASK) =3D=3D O_MKDIR_MASK; +} + /* List of all valid flags for the how->resolve argument: */ #define VALID_RESOLVE_FLAGS \ (RESOLVE_NO_XDEV | RESOLVE_NO_MAGICLINKS | RESOLVE_NO_SYMLINKS | \ --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.167]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 75BA73B83E8 for ; Sun, 13 Sep 2026 18:49:36 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.167 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325378; cv=none; b=m5AwAdSP+dPj/xI/tRW2PNrWBA9Rvl6Et1Myz6QdS7NC2lFfWfDCmoSacmaR5L2LMtLe1CWhQw36qMzaVHD15Igb26XPRT/OSE79E8719TSxjjjUD3eknnrf3rvNb5p7vE29H5O7Yb3sAQsi2ypGVUPJSnWdpDnpMhrUS8yHlWY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325378; c=relaxed/simple; bh=m7XSGu+/MVEE/A4Ak5fwrpyK+p674yeU60dUk0JjCls=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jREw7fej+dLgyH7jo4J6kuRvGQMDwnVzFlBCT7QZY6YvR6/GoQ0XJ1Vvnl129p/DjiWG1mt8bqo1pqt/JRT9lVQrzCQYiPP05NPBd72f8HvaE40iMISFUdtXwRGoCOBEcBp/Fy9x+j7II70kpoQBcxGQDUusEvmPFomak+N8bbA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=tcukTA6Y; arc=none smtp.client-ip=195.121.94.167 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="tcukTA6Y" X-KPN-MessageId: db16b9c8-afa3-11f1-b2a8-005056abbe64 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id db16b9c8-afa3-11f1-b2a8-005056abbe64; Sun, 13 Sep 2026 20:49:31 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=gmYuawAZ/peOxaHNONnX0nilthKlpl6zgZFvwjd7FvA=; b=tcukTA6YlIp6mALHut6LsAu65HA8QuG6XFZvIwQMmle3KUMDAeeHsT4pXLGEKHZ3v962R2na5BNYU ZVgrD38ZEaRVjrD7goe2TxotKjjkNpr8a7uS7oWTPvcub9tlBXdi4NHwj8UuXhtm19ZAklR6mMvzaG 1dkip0Aa/YHTOmYyLwiX1F8lcpsdHuxvUjj5+kjh+tEiHu1pjJHULp7p76wa4dWN/RLhHZofH7gL0C cE9VhOgoLIdOvma9w3OIOGKMDFH2ZD1UA+wio6ciCCavddkRQsHhpuov9byFs45RUO+1R8kpncnEmZ HB76OgNRhd560xe7gOQr3RWK9cF805A== X-KPN-MID: 33|a6Y2HhULI4OyTNThlAru9K0mKxscrxCJtJSxYGTbklqwQ1uzCpaGn7M+K0PzzJW LO3mNPin4kV3WQxxDmn9lhm7XDON1hfVHutH6AmM6BlY= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|zyee0ZaljSniYLVyteu+u9KZIjDdLFQKhVO6WQgpoJI3qLNUnfb1cSJjlNtMQ8E OtcyUOs2a0Ume9TpDROwP3A== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id daded0f1-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:31 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 08/12] vfs: change ->create/->mkdir operations unavailable errno Date: Sun, 13 Sep 2026 20:50:12 +0200 Message-ID: <20260913185016.523376-9-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Currently filesystems return -EACCES for missing ->create and -EPERM for missing ->mkdir in vfs_create/vfs_mkdir. Instead of duplicating these dubious and inconsistent error codes to lookup_open(), change all to -EOPNOTSUPP. Signed-off-by: Jori Koolstra --- fs/namei.c | 14 +++++--------- include/uapi/asm-generic/errno.h | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 6ff0a3c04f02..f31046f8b3ba 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4224,7 +4224,7 @@ int vfs_create(struct mnt_idmap *idmap, struct dentry= *dentry, umode_t mode, return error; =20 if (!dir->i_op->create) - return -EACCES; /* shouldn't it be ENOSYS? */ + return -EOPNOTSUPP; =20 mode =3D vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); error =3D security_inode_create(dir, dentry, mode); @@ -4646,13 +4646,9 @@ static struct dentry *lookup_open(struct nameidata *= nd, struct file *file, goto out_dput; } =20 - /* mimic operation missing errnos of vfs_mkdir/vfs_create */ - if (create_dir && !dir_inode->i_op->mkdir) { - error =3D -EPERM; - goto out_dput; - } - if (!create_dir && !dir_inode->i_op->create) { - error =3D -EACCES; + if ((create_dir && !dir_inode->i_op->mkdir) + || (!create_dir && !dir_inode->i_op->create)) { + error =3D -EOPNOTSUPP; goto out_dput; } =20 @@ -5501,7 +5497,7 @@ struct dentry *vfs_mkdir(struct mnt_idmap *idmap, str= uct inode *dir, if (error) goto err; =20 - error =3D -EPERM; + error =3D -EOPNOTSUPP; if (!dir->i_op->mkdir) goto err; =20 diff --git a/include/uapi/asm-generic/errno.h b/include/uapi/asm-generic/er= rno.h index bd78e69e0a43..c84ebf89c8b6 100644 --- a/include/uapi/asm-generic/errno.h +++ b/include/uapi/asm-generic/errno.h @@ -76,7 +76,7 @@ #define ENOPROTOOPT 92 /* Protocol not available */ #define EPROTONOSUPPORT 93 /* Protocol not supported */ #define ESOCKTNOSUPPORT 94 /* Socket type not supported */ -#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ +#define EOPNOTSUPP 95 /* Operation not supported */ #define EPFNOSUPPORT 96 /* Protocol family not supported */ #define EAFNOSUPPORT 97 /* Address family not supported by protocol */ #define EADDRINUSE 98 /* Address already in use */ --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 18EEC3F58D7 for ; Sun, 13 Sep 2026 18:49:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325381; cv=none; b=rf7twZmYM8Ax3qz2ZRqji6O2DZgDIh/4WDj/1IXM5RMDc+Ac3uiVzI3B4mKslXSmPix6nXBgBUllmhP9v8vSywcmqOI6dSKGdt1huJ2KZwXYrJCqVtw9OQ7+clVTmQ7911f+zsdZLxLNPkvTIHA5euwL3czeFYpHlQ9CHynTtxY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325381; c=relaxed/simple; bh=juPNHvpWR92dCr36HgIiioaSqiCioGjAOnDebjSe7Mk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SJ4ypd98oOGz+bQNLLMBd7JvjETZ6O9PjzQZkTisrsv4JEJbhPW25yi8WjEWtFG8+YGbNd4e3HQY8oprDNQSaYu6tTgSac6/Ua6zuOLuz9BKdsVLcAsxlKqnWYPzy3ukEPClBRG4YXjkVjf5Ud7oKSDXiA9fdBH10jck4J72fS0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=fjxZWtBm; arc=none smtp.client-ip=195.121.94.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="fjxZWtBm" X-KPN-MessageId: dd536b56-afa3-11f1-a059-005056abad63 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id dd536b56-afa3-11f1-a059-005056abad63; Sun, 13 Sep 2026 20:49:35 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=/y+qiZvJ5no0j7LSJlCXAfcdzUGAl8ZOlwquBX6X96E=; b=fjxZWtBmIAiIuCUJm9JVIfJM2xJ4QfZkIkKRAHElHHWRooGpw1z3scJ7fq1N22s4O3aXx5TBe62EC C6epsT5R0rseHMh7U3HV6BSVmDhb2XlKsOgPU7ExrE2okSxiLcq+RvnZUI9Yk+WPEH7I6h8ikha1EL 2ip9GoR+aQRQLJ35jGbQczdfA9ka4gJk05SRe23WuZ27F7GAgEgOJSvQgZRbKGsInl9cEpe2MG8gVe KYZ02wcrbfp6i/VvxY14UuacSSPvD3BSdJsXULkvD/0du743k5O3v5iDwzu9CogZu0dWD/38k+JtaK lPLQ422izqGxgnS5P/trebmRCsDiFIA== X-KPN-MID: 33|WN5pIgXInmzLTLvj6H3i2BvrAdDZK4C99Gp7o8mPo3KWYG/bQC8GqCLbSMV0Pj0 xCEzOu5bzPayRpIR8MRSPl5ytugCuyxch/e3R6Op93u4= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|DAqJoBDPbmQGg2vwGqVACLJ4pUCSZoEKTZCJriJsftrneTUqU60iBzDso7lHjvN dnFMb+pBXxEmaI51GV220kw== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id dd19800c-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:35 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 09/12] vfs: move O_IS_MKDIR check from lookup_open() into individual filesystems Date: Sun, 13 Sep 2026 20:50:13 +0200 Message-ID: <20260913185016.523376-10-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Individual filesystems that implement ->atomic_open() need to get the chance to implement O_CREAT|O_DIRECTORY or not, rather than decide this at the VFS level in lookup_open(). Signed-off-by: Jori Koolstra --- fs/9p/vfs_inode.c | 5 +++++ fs/9p/vfs_inode_dotl.c | 5 +++++ fs/ceph/file.c | 5 +++++ fs/fuse/dir.c | 5 +++++ fs/gfs2/inode.c | 5 +++++ fs/namei.c | 3 --- fs/nfs/dir.c | 10 ++++++++++ fs/smb/client/dir.c | 5 +++++ fs/vboxsf/dir.c | 5 +++++ 9 files changed, 45 insertions(+), 3 deletions(-) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 3829554ca369..dd810904ff6c 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -776,6 +776,11 @@ v9fs_vfs_atomic_open(struct inode *dir, struct dentry = *dentry, struct inode *inode; int p9_omode; =20 + if (O_IS_MKDIR(flags)) { + flags &=3D ~O_CREAT; + mode =3D 0; + } + if (d_in_lookup(dentry)) { struct dentry *res =3D v9fs_vfs_lookup(dir, dentry, 0); if (res || d_really_is_positive(dentry)) diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 116b29e95f21..9308184aee61 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -238,6 +238,11 @@ v9fs_vfs_atomic_open_dotl(struct inode *dir, struct de= ntry *dentry, struct v9fs_session_info *v9ses; struct posix_acl *pacl =3D NULL, *dacl =3D NULL; =20 + if (O_IS_MKDIR(flags)) { + flags &=3D ~O_CREAT; + omode =3D 0; + } + if (d_in_lookup(dentry)) { struct dentry *res =3D v9fs_vfs_lookup(dir, dentry, 0); if (res || d_really_is_positive(dentry)) diff --git a/fs/ceph/file.c b/fs/ceph/file.c index bd3e3f5c269e..9235143edd9a 100644 --- a/fs/ceph/file.c +++ b/fs/ceph/file.c @@ -812,6 +812,11 @@ int ceph_atomic_open(struct inode *dir, struct dentry = *dentry, dir, ceph_vinop(dir), dentry, dentry, d_unhashed(dentry) ? "unhashed" : "hashed", flags, mode); =20 + if (O_IS_MKDIR(flags)) { + flags &=3D ~O_CREAT; + mode =3D 0; + } + if (dentry->d_name.len > NAME_MAX) return -ENAMETOOLONG; =20 diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index e49b4e874b15..a3e7daba61dc 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -944,6 +944,11 @@ static int fuse_atomic_open(struct inode *dir, struct = dentry *entry, struct mnt_idmap *idmap =3D file_mnt_idmap(file); struct fuse_conn *fc =3D get_fuse_conn(dir); =20 + if (O_IS_MKDIR(flags)) { + flags &=3D ~O_CREAT; + mode =3D 0; + } + if (fuse_is_bad(dir)) return -EIO; =20 diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index f361876c5583..69e100a68e2c 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1386,6 +1386,11 @@ static int gfs2_atomic_open(struct inode *dir, struc= t dentry *dentry, { bool excl =3D !!(flags & O_EXCL); =20 + if (O_IS_MKDIR(flags)) { + flags &=3D ~O_CREAT; + mode =3D 0; + } + if (d_in_lookup(dentry)) { struct dentry *d =3D __gfs2_lookup(dir, dentry, file); if (file->f_mode & FMODE_OPENED) { diff --git a/fs/namei.c b/fs/namei.c index f31046f8b3ba..a990c9c8bddf 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4535,9 +4535,6 @@ static struct dentry *lookup_open(struct nameidata *n= d, struct file *file, mode =3D op->mode; create_error =3D 0; =20 - if (create_dir && dir_inode->i_op->atomic_open) - open_flag &=3D ~O_CREAT; - if (open_flag & (O_CREAT | O_TRUNC | O_WRONLY | O_RDWR)) { got_write =3D !mnt_want_write(nd->path.mnt); /* diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 49394123bd09..4b1404d6859e 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2121,6 +2121,11 @@ int nfs_atomic_open(struct inode *dir, struct dentry= *dentry, dfprintk(VFS, "NFS: atomic_open(%s/%llu), %pd\n", dir->i_sb->s_id, dir->i_ino, dentry); =20 + if (O_IS_MKDIR(open_flags)) { + open_flags &=3D ~O_CREAT; + mode =3D 0; + } + err =3D nfs_check_flags(open_flags); if (err) return err; @@ -2317,6 +2322,11 @@ int nfs_atomic_open_v23(struct inode *dir, struct de= ntry *dentry, */ int error =3D 0; =20 + if (O_IS_MKDIR(open_flags)) { + open_flags &=3D ~O_CREAT; + mode =3D 0; + } + if (dentry->d_name.len > NFS_SERVER(dir)->namelen) return -ENAMETOOLONG; =20 diff --git a/fs/smb/client/dir.c b/fs/smb/client/dir.c index 6fa6d48fdfd3..f75095a48fdb 100644 --- a/fs/smb/client/dir.c +++ b/fs/smb/client/dir.c @@ -534,6 +534,11 @@ int cifs_atomic_open(struct inode *dir, struct dentry = *direntry, if (unlikely(cifs_forced_shutdown(cifs_sb))) return smb_EIO(smb_eio_trace_forced_shutdown); =20 + if (O_IS_MKDIR(oflags)) { + oflags &=3D ~O_CREAT; + mode =3D 0; + } + /* * Posix open is only called (at lookup time) for file create now. For * opens (rather than creates), because we do not know if it is a file diff --git a/fs/vboxsf/dir.c b/fs/vboxsf/dir.c index 0b9eab157432..f20b61f6d8da 100644 --- a/fs/vboxsf/dir.c +++ b/fs/vboxsf/dir.c @@ -318,6 +318,11 @@ static int vboxsf_dir_atomic_open(struct inode *parent= , struct dentry *dentry, u64 handle; int err; =20 + if (O_IS_MKDIR(flags)) { + flags &=3D ~O_CREAT; + mode =3D 0; + } + if (d_in_lookup(dentry)) { struct dentry *res =3D vboxsf_dir_lookup(parent, dentry, 0); if (res || d_really_is_positive(dentry)) --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.167]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 611A83F99F4 for ; Sun, 13 Sep 2026 18:49:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.167 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325384; cv=none; b=bht77DluOR2EDujRmgnh5Cak7xzY3TXidabwCmqONmLXFxmGEdokgAqj6BFBokMCaux3AdHqvlr6v+EFPRXWEqzvzepzu8DXFPhGFbS0MeYBmNKz6EAPkrvNRs+Drp1McPb1SmwU9Vx3Ro3Bwaptv+QE7qvRm4i48m5575MIq2c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325384; c=relaxed/simple; bh=17eTtnSWW7iGPbqJe2nrFaKLmvqhueUxRZU3/BTcefU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Ozmy4ZzLjvGkaxv0tc5sESqWSaROb2Q5oekHw64z1i7/RLPc+LDNG7WuR6hG8w2EbfYsRMt0UHthciECTMAVHOklym6lE0I5pVGQUEosUuMYVvyPeRCjkd5ZpRlBFhcBVklr3HB7sU61ilmzO5ki/T8XxdJtxQoD/Tv4cyxTYH0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=fyJUPGma; arc=none smtp.client-ip=195.121.94.167 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="fyJUPGma" X-KPN-MessageId: df7a3ed6-afa3-11f1-b2a8-005056abbe64 Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id df7a3ed6-afa3-11f1-b2a8-005056abbe64; Sun, 13 Sep 2026 20:49:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=VevmlAFlgqBr1KihxzNewL9ukoTBFxyRRj2gCZd4PMo=; b=fyJUPGmams0stY42r2gsJMgm4qaCkFPhHdKZPSuXitVWDuBJ/p6qbDz1K7t7bWNILqFW5BZCyJSYy GJAdGD6y7CWx/uhelrkXASECvH+BenDMRC0/rIKUdfJgkKrHDjnKEvz+pzkDtOs1d036VNKL0jbURi e3Em5Pf4BHPWp28GoDxhEgRapG5BGEtd/fpwD2Kg5J+3rKd3oGacwpUKhb/agBonyukCRruVZl9ZZb TT43Hu11B1j3Y1vU+dxQu54yUH6aofj2IIbU9aS1uUEPcRRE2jL6y6BM0Ayt5EwmE2gLdNn9Xdp8vE vkbNZTZAv7J5ZFTg43uvj29r8DPMVOQ== X-KPN-MID: 33|ZwGBvn9DlFIcdIEAEEll6kuRPfWpoqVVpCb0AFjpIfhy3An8W5EycFTsIZvmQtF zyHAKOn5cHSSNok0dCDO6/SXqAx3OW9cTulj+HJeZlQo= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|YDYG3yZu3l1PlIUtjAwE3dhDYt/ZMOcU7XmRcHFaXvvTagjHuCLdQNgOaCguAj0 oKKbxXfMbXxmQm0JRRv5rwA== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id df44500a-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:38 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 10/12] vfs: refuse O_CREAT for directories through a dangling symlink Date: Sun, 13 Sep 2026 20:50:14 +0200 Message-ID: <20260913185016.523376-11-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" open(O_CREAT) without O_EXCL follows a trailing symlink and, when the symlink target does not exist, creates it. Refuse to create through a dangling symlink for directories. In lookup_open() a negative target reached with nd->depth > 0 was arrived at by following a trailing symlink; since the dentry is negative the symlink is dangling. Set create_error to -EEXIST in that case (matching the errno returned by mkdir(2).) Reusing the existing create_error path strips O_CREAT for both the generic and ->atomic_open create paths and only reports the error when the target is actually negative. Thus opening an existing target through a symlink, interior symlinks, and O_EXCL (which never follows the trailing link) are all unaffected. Suggested-by: Christian Brauner (Amutable) Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/namei.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index a990c9c8bddf..fce3aaa37365 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4601,6 +4601,11 @@ static struct dentry *lookup_open(struct nameidata *= nd, struct file *file, dentry, open_flag, mode); else create_error =3D -EROFS; + /* Refuse to create a directory through a dangling (trailing) + * symlink. For regular files this has been allowed historically + * on O_CREAT without O_EXCL. */ + if (unlikely(nd->depth) && create_dir && !create_error) + create_error =3D -EEXIST; } if (create_error) open_flag &=3D ~O_CREAT; --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2F8613F9F45 for ; Sun, 13 Sep 2026 18:49:46 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325388; cv=none; b=H7Hj+xu7vG3dfVIF1JAN4t++hYHdV6Ig90wMRQZuGBAVEMdghvwqIc3IvivSl8C+PrGQPVEyRBqRvJMHrJenf5GIwXcbyUewilyboZoCw9ELYooD2D7djnPPEPW9orIZEDTlpfN7PToCqoxUMUiEmmx+F9TC7aWnSnKszFcuAqY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325388; c=relaxed/simple; bh=xTMn6Xtc95suaBBQjPM3n6PoFjJzRupWsASzDVEqQBs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=qWaAbqYjMn8ieUOIyRpIP3K2UEqLHPIHLHFkT7DVIHoKfRkL8kJvtJa5aYw/glZqT8Zs86zm/lsQG2tQEKijkvGrav0Ccp3qZp+4jeYobAyyuVChDKBaPmGwWWDwtcNo0GeuG6Z9NWdoz07YQ3tZkUN3dq8tyPMRxIrfBP6mA90= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=p1+8X0Jf; arc=none smtp.client-ip=195.121.94.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="p1+8X0Jf" X-KPN-MessageId: e1a1d730-afa3-11f1-83b4-005056ab378f Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id e1a1d730-afa3-11f1-83b4-005056ab378f; Sun, 13 Sep 2026 20:49:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=2PSPPEoGvrfvVZ6TTPZf20x7aN1ff0yNDpWtXOdoUoI=; b=p1+8X0Jfp6d6++DF1K/RXsxxKj2osx15SkI2q53lQtXVlFoI1w6y+8Lki5aqSqquqYXTBepVZLJat LxCSqvdqUMBuriYeW1/c/rD+q4XXBzZJpZ4drLHSHhuv/fqte9e5W5j9U5MiES7T+yxr8cLUN1aCr4 2DFCtscTPPy8XFFItOrOCra/DpUcFM7e+Z2TkLnKHPfS6j3kdp+S4WklY39G4wGzw6o0dznJGqy0rH bFTd8zVbkTeXN0J0AJSm7i0fdguaJTNOfoPsiPiYNOC/jR6u00usWUuaiBd9D5JD9iXQfZMRSLH7Nu wB1RbOiwIGy8FSvX4S1GGprBonhrvyg== X-KPN-MID: 33|46oqDKJdgWIbw8NdzWltlJ025bVsM19F8dn5TEgzi0o/jh2nxOeNo1IwJFKcRti NTWIejy2WsRdNjoG87yjANTl9N0NqhngTrttfYqnhARM= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|hkCPKSVq2auj0IBpub8LXc4zSTrEJgitzmHQtqzXvC5kZ1v3mpK3aYjDbI2I+Au 99aICGj6I8gw7WTd7NbuZCA== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id e167be68-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:42 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 11/12] vfs: short-circuit MAY_WRITE access for O_DIRECTORY opens Date: Sun, 13 Sep 2026 20:50:15 +0200 Message-ID: <20260913185016.523376-12-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Requesting write access on a directory can never succeed. Rather than performing a path-walk to determine whether the target is actually a directory (-EISDIR) or not (-ENOTDIR), or does not exist (-ENOENT), etc., we short-circuit to -ENOTDIR. Currently O_WRONLY for directories is only blocked in may_open(), which happens after we have the inode for the target, so after any create via O_CREAT|O_DIRECTORY. The advantage of short-circuiting is that we don't have to add even more logic to lookup_open() to differentiate -EISDIR/-ENOTDIR. Also, for filesystems that define ->atomic_open, handling this cannot even be done at the VFS level, as we can't know ahead what the result of the lookup will be. Suggested-by: Christian Brauner (Amutable) Reviewed-by: NeilBrown Signed-off-by: Jori Koolstra --- fs/open.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/fs/open.c b/fs/open.c index 189af02a2425..6cb5e2ad781f 100644 --- a/fs/open.c +++ b/fs/open.c @@ -1319,9 +1319,16 @@ inline int build_open_flags(const struct open_how *h= ow, struct open_flags *op) =20 op->intent =3D flags & O_PATH ? 0 : LOOKUP_OPEN; =20 + /* + * Requesting write access on a directory can never succeed. Rather + * than performing a path-walk to determine whether the target is + * actually a directory (-EISDIR) or not (-ENOTDIR), we short-circuit + * to -ENOTDIR. + */ + if ((flags & O_DIRECTORY) && !(flags & __O_TMPFILE) && (acc_mode & MAY_WR= ITE)) + return -ENOTDIR; + if (flags & O_CREAT) { - if ((flags & O_DIRECTORY) && (acc_mode & MAY_WRITE)) - return -EISDIR; op->intent |=3D LOOKUP_CREATE; if (flags & O_EXCL) { op->intent |=3D LOOKUP_EXCL; --=20 2.55.0 From nobody Fri Sep 25 11:10:09 2026 Received: from ewsoutbound.kpnmail.nl (ewsoutbound.kpnmail.nl [195.121.94.170]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9531E3FB7E1 for ; Sun, 13 Sep 2026 18:49:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.121.94.170 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325393; cv=none; b=pssI+N6yQ0kIz+ftAeuSe7MSIbp3zDqJzaEohhpwrA7Rg/x4tMKSMe02VngsE4m/fofz/br0WS7CdalfavZfctrEvWU/UpiubFqLNruF/QNOeJjEE1PdOzKeatvhF3TrlkTBTgd4L7/yanHQ5XjDJxuPeU55syv0L5y8UHCmVXk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789325393; c=relaxed/simple; bh=gj8FpkHlNchhad14rh8EiQmkgwFXQ7brK5SW03B67jw=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bT6bR2S3bs+R7DUMDd0TYPcFW8eipLgV7PdecJEX3wzjXbGAn0oJrIzF0dvJLlAk8B4ZXmfFD7pgVQazodEOqDJVJZMi5Bgu/WZYgClT7E77wQ7lMAxZRwq2eV+8G0f3GfPcPpAfBKbBVpmUdHossXtkbL2TRk36Cz+9yDyWrgY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl; spf=pass smtp.mailfrom=xs4all.nl; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b=YgI9kZ/K; arc=none smtp.client-ip=195.121.94.170 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=xs4all.nl Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=xs4all.nl header.i=@xs4all.nl header.b="YgI9kZ/K" X-KPN-MessageId: e3c6b669-afa3-11f1-83b4-005056ab378f Received: from smtp.kpnmail.nl (unknown [10.31.155.38]) by ewsoutbound.so.kpn.org (Halon) with ESMTPS id e3c6b669-afa3-11f1-83b4-005056ab378f; Sun, 13 Sep 2026 20:49:46 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=xs4all.nl; s=xs4all01; h=mime-version:message-id:date:subject:to:from; bh=1IpoTYMP5XRS0Wf6GLJilFPg/98gMh0ulmw5TiujizI=; b=YgI9kZ/KoM+R3qGqyCTf3RLqmocCUA9WRPMH69B4uU4xnHTagYqSjKaFOTdROFxn6DH/gCi1ZaZht Np8eo9LM0+QoBK3+2gXplT5UYbhNtDwMIEYxaI5tPi/lI5ahh61vxgitrEjL/7q453MNBKSBP2bPfd AYXeN1jA8djzPVs3coAm0o4g+x6pwRQnE5+snGTGBSyW/tL6ShurKoXgsKcEMroG/VKSxhhnteqOHb zIrBadalXDxdF0NXzm+R0otBuvPN1Qfqo6UJ+zV67QKj1tJg5KnaFM4IN/rk+Mvj4ywbLiZ3DdCJO3 sY9Vyqwu6mjhqBvXNB4n1s6kpu2jwJg== X-KPN-MID: 33|ldoESv9g3OVOzxiIcxj7sWh6WP0D2bmUBHncWy/7ao0bf0otTwKKD7tla01awFw NMPCMGxK1ATK23pEgWCu2ZbsfGP1MIvHSzdL/Ekqm6Kg= X-KPN-VerifiedSender: Yes X-CMASSUN: 33|lsZE0lg7Soh+BV+Erg8/LCoEAP3VSULJbzBQQIYRlOoJlh/i2hHyIQC3hRFabKG qAOSmDNiGYr0SFP+HZJCyJQ== Received: from daedalus.home (unknown [178.231.250.207]) by smtp.xs4all.nl (Halon) with ESMTPSA id e38b5477-afa3-11f1-bd2b-005056abf0db; Sun, 13 Sep 2026 20:49:46 +0200 (CEST) From: Jori Koolstra To: Christian Brauner , Jeff Layton , Al Viro , Aleksa Sarai , NeilBrown , Amir Goldstein , Jan Kara , linux-fsdevel@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Jori Koolstra Subject: [PATCH v6 12/12] selftest: add tests for open*(O_CREAT|O_DIRECTORY) Date: Sun, 13 Sep 2026 20:50:16 +0200 Message-ID: <20260913185016.523376-13-jkoolstra@xs4all.nl> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260913185016.523376-1-jkoolstra@xs4all.nl> References: <20260913185016.523376-1-jkoolstra@xs4all.nl> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Add some tests for the new valid O_CREAT|O_DIRECTORY flag combination for open*(2) to test compliance and to showcase its behaviour. Signed-off-by: Jori Koolstra --- .../testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 2 +- .../filesystems/open_o_creat_o_dir.c | 296 ++++++++++++++++++ .../testing/selftests/filesystems/wrappers.h | 11 + 4 files changed, 309 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/filesystems/open_o_creat_o_dir.c diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing= /selftests/filesystems/.gitignore index 9eb185fb2f9d..01c588d4c84f 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -1,4 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only +open_o_creat_o_dir dnotify_test devpts_pts fclog diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/s= elftests/filesystems/Makefile index 03be337c1f35..0959bd26875a 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 =20 CFLAGS +=3D $(KHDR_INCLUDES) -TEST_GEN_PROGS :=3D devpts_pts file_stressor anon_inode_test kernfs_test f= clog ustat_test +TEST_GEN_PROGS :=3D open_o_creat_o_dir devpts_pts file_stressor anon_inode= _test kernfs_test fclog ustat_test TEST_GEN_PROGS +=3D idmapped_tmpfile TEST_GEN_PROGS_EXTENDED :=3D dnotify_test =20 diff --git a/tools/testing/selftests/filesystems/open_o_creat_o_dir.c b/too= ls/testing/selftests/filesystems/open_o_creat_o_dir.c new file mode 100644 index 000000000000..538a7f7c5803 --- /dev/null +++ b/tools/testing/selftests/filesystems/open_o_creat_o_dir.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include + +#include "kselftest_harness.h" +#include "wrappers.h" + +#define openat_o_mkdir_checked_flags(dfd, pathname, flags) ({ \ + struct stat __st; \ + int __fd =3D openat_o_mkdir(dfd, pathname, flags, S_IRWXU); \ + ASSERT_GE(__fd, 0); \ + ASSERT_EQ(fstat(__fd, &__st), 0); \ + EXPECT_TRUE(S_ISDIR(__st.st_mode)); \ + __fd; \ +}) + +#define openat_o_mkdir_checked(dfd, pathname) \ + openat_o_mkdir_checked_flags(dfd, pathname, O_RDONLY) + +FIXTURE(open_o_creat_o_dir) { + char dirpath[PATH_MAX]; + int dfd; +}; + +FIXTURE_SETUP(open_o_creat_o_dir) +{ + strcpy(self->dirpath, "/tmp/open_o_creat_o_dir_test.XXXXXX"); + ASSERT_NE(mkdtemp(self->dirpath), NULL); + self->dfd =3D open(self->dirpath, O_DIRECTORY); + ASSERT_GE(self->dfd, 0); +} + +FIXTURE_TEARDOWN(open_o_creat_o_dir) +{ + close(self->dfd); + rmdir(self->dirpath); +} + +/* Does open_o_creat_o_dir return a fd at all? */ +TEST_F(open_o_creat_o_dir, returns_fd) +{ + int fd =3D openat_o_mkdir_checked(self->dfd, "newdir"); + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0); +} + +/* The fd must refer to the directory that was just created. */ +TEST_F(open_o_creat_o_dir, fd_is_created_dir) +{ + int fd; + struct stat st_via_fd, st_via_path; + char path[PATH_MAX]; + + fd =3D openat_o_mkdir_checked(self->dfd, "checkdir"); + + ASSERT_EQ(fstat(fd, &st_via_fd), 0); + + snprintf(path, sizeof(path), "%s/checkdir", self->dirpath); + ASSERT_EQ(stat(path, &st_via_path), 0); + + EXPECT_EQ(st_via_fd.st_ino, st_via_path.st_ino); + EXPECT_EQ(st_via_fd.st_dev, st_via_path.st_dev); + + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(rmdir(path), 0); +} + +/* Missing parent component must fail with ENOENT. */ +TEST_F(open_o_creat_o_dir, enoent_missing_parent) +{ + EXPECT_EQ(openat_o_mkdir(self->dfd, "nonexistent/child", O_RDONLY, S_IRWX= U), -1); + EXPECT_EQ(errno, ENOENT); +} + +/* An invalid dfd must fail with EBADF. */ +TEST_F(open_o_creat_o_dir, ebadf) +{ + EXPECT_EQ(openat_o_mkdir(FD_INVALID, "badfdir", O_RDONLY, S_IRWXU), -1); + EXPECT_EQ(errno, EBADF); +} + +/* A dfd that points to a file (not a directory) must fail with ENOTDIR. */ +TEST_F(open_o_creat_o_dir, enotdir_dfd) +{ + int file_fd; + + file_fd =3D openat(self->dfd, "file", + O_CREAT | O_RDONLY, S_IRWXU); + ASSERT_GE(file_fd, 0); + + EXPECT_EQ(openat_o_mkdir(file_fd, "subdir", O_RDONLY, S_IRWXU), -1); + EXPECT_EQ(errno, ENOTDIR); + + EXPECT_EQ(close(file_fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "file", 0), 0); +} + +/* + * O_EXCL together with O_CREAT|O_DIRECTORY should succeed if the target + * directory does not yet exist. After directory creation, repeating this + * call must fail with EEXIST, but should succeed if the O_EXCL is dropped. + */ +TEST_F(open_o_creat_o_dir, o_excl_eexist) +{ + int excldir_fd; + + excldir_fd =3D openat_o_mkdir_checked_flags(self->dfd, "excldir", O_EXCL); + + EXPECT_EQ(openat_o_mkdir(self->dfd, "excldir", O_EXCL, S_IRWXU), -1); + EXPECT_EQ(errno, EEXIST); + + int excldir_reopen_fd =3D openat_o_mkdir_checked(self->dfd, "excldir"); + + EXPECT_EQ(close(excldir_reopen_fd), 0); + EXPECT_EQ(close(excldir_fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "excldir", AT_REMOVEDIR), 0); +} + +/* + * O_CREAT|O_DIRECTORY on a path that already exists as a regular file + * must fail with ENOTDIR. + */ +TEST_F(open_o_creat_o_dir, existing_file_enotdir) +{ + int file_fd; + + file_fd =3D openat(self->dfd, "regfile", + O_CREAT | O_RDONLY, S_IRWXU); + ASSERT_GE(file_fd, 0); + EXPECT_EQ(close(file_fd), 0); + + EXPECT_EQ(openat_o_mkdir(self->dfd, "regfile", O_RDONLY, S_IRWXU), -1); + EXPECT_EQ(errno, ENOTDIR); + + EXPECT_EQ(unlinkat(self->dfd, "regfile", 0), 0); +} + +/* + * O_CREAT|O_DIRECTORY combined with a writable access mode must be + * rejected: a directory cannot be opened for writing. + */ +TEST_F(open_o_creat_o_dir, rejects_writable_acc_mode) +{ + EXPECT_EQ(openat_o_mkdir(self->dfd, "rdwrdir", O_RDWR, S_IRWXU), -1); + EXPECT_EQ(errno, ENOTDIR); + /* Clean up if the kernel created the directory anyway. */ + unlinkat(self->dfd, "rdwrdir", AT_REMOVEDIR); +} + +/* + * openat(O_CREAT|O_DIRECTORY) with a trailing slash should work. + */ +TEST_F(open_o_creat_o_dir, trailing_slash) +{ + int fd =3D openat_o_mkdir_checked(self->dfd, "newdir/"); + EXPECT_EQ(close(fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "newdir", AT_REMOVEDIR), 0); +} + +/* + * openat(O_CREAT) with a trailing slash but without O_DIRECTORY + * must fail with EISDIR and must not create anything at the path. + */ +TEST_F(open_o_creat_o_dir, trailing_slash_no_o_dir) +{ + int fd; + struct stat st; + + fd =3D openat(self->dfd, "trailing/", O_CREAT | O_RDONLY, S_IRWXU); + EXPECT_EQ(fd, -1); + EXPECT_EQ(errno, EISDIR); + + EXPECT_EQ(fstatat(self->dfd, "trailing", &st, 0), -1); + EXPECT_EQ(errno, ENOENT); + + /* Best-effort cleanup in case the kernel left a file behind. */ + if (fd >=3D 0) + close(fd); + unlinkat(self->dfd, "trailing", 0); +} + +/* + * The returned fd must be usable as a dfd for further *at() calls. + */ +TEST_F(open_o_creat_o_dir, fd_usable_as_dfd) +{ + int parent_fd, child_fd; + char path[PATH_MAX]; + + parent_fd =3D openat_o_mkdir_checked(self->dfd, "parent"); + child_fd =3D openat_o_mkdir_checked(parent_fd, "child"); + + EXPECT_EQ(close(child_fd), 0); + EXPECT_EQ(close(parent_fd), 0); + + snprintf(path, sizeof(path), "%s/parent/child", self->dirpath); + EXPECT_EQ(rmdir(path), 0); + snprintf(path, sizeof(path), "%s/parent", self->dirpath); + EXPECT_EQ(rmdir(path), 0); +} + +/* + * O_CREAT|O_DIRECTORY must refuse to create through a dangling trailing + * symlink, and must not create anything at the symlink target. + */ +TEST_F(open_o_creat_o_dir, dangling_symlink_eexist) +{ + struct stat st; + + ASSERT_EQ(symlinkat("danglink_target", self->dfd, "danglink"), 0); + + EXPECT_EQ(openat_o_mkdir(self->dfd, "danglink", O_RDONLY, S_IRWXU), -1); + EXPECT_EQ(errno, EEXIST); + + /* Nothing must have been created at the target. */ + EXPECT_EQ(fstatat(self->dfd, "danglink_target", &st, 0), -1); + EXPECT_EQ(errno, ENOENT); + + EXPECT_EQ(unlinkat(self->dfd, "danglink", 0), 0); +} + +/* + * A trailing symlink that resolves to an existing directory must still op= en. + */ +TEST_F(open_o_creat_o_dir, symlink_not_dangling_ok) +{ + int fd; + + ASSERT_EQ(mkdirat(self->dfd, "realdir", 0700), 0); + ASSERT_EQ(symlinkat("realdir", self->dfd, "dirlink"), 0); + + /* Trailing symlink resolving to an existing directory. */ + fd =3D openat_o_mkdir_checked(self->dfd, "dirlink"); + EXPECT_EQ(close(fd), 0); + + EXPECT_EQ(unlinkat(self->dfd, "dirlink", 0), 0); + EXPECT_EQ(unlinkat(self->dfd, "realdir", AT_REMOVEDIR), 0); +} + +/* + * An O_CREAT|O_DIRECTORY open of an existing directory owned by someone e= lse, + * inside a sticky world-writable directory, must be refused. + */ +TEST_F(open_o_creat_o_dir, sticky_dir_eacces) +{ + int sticky_fd, fd; + + if (geteuid() !=3D 0) + SKIP(return, "needs root for fchownat"); + + ASSERT_EQ(mkdirat(self->dfd, "sticky", 01777), 0); + ASSERT_EQ(fchmodat(self->dfd, "sticky", 01777, 0), 0); + sticky_fd =3D openat(self->dfd, "sticky", O_DIRECTORY | O_RDONLY); + ASSERT_GE(sticky_fd, 0); + + ASSERT_EQ(mkdirat(sticky_fd, "otherdir", 0700), 0); + if (fchownat(sticky_fd, "otherdir", 1, 1, 0)) { + int err =3D errno; + + unlinkat(sticky_fd, "otherdir", AT_REMOVEDIR); + close(sticky_fd); + unlinkat(self->dfd, "sticky", AT_REMOVEDIR); + SKIP(return, "cannot chown to uid 1: %s", strerror(err)); + } + + EXPECT_EQ(openat_o_mkdir(sticky_fd, "otherdir", O_RDONLY, S_IRWXU), -1); + EXPECT_EQ(errno, EACCES); + + /* Without O_CREAT the very same open must still succeed. */ + fd =3D openat(sticky_fd, "otherdir", O_DIRECTORY | O_RDONLY); + EXPECT_GE(fd, 0); + if (fd >=3D 0) { + EXPECT_EQ(close(fd), 0); + } + + EXPECT_EQ(unlinkat(sticky_fd, "otherdir", AT_REMOVEDIR), 0); + EXPECT_EQ(close(sticky_fd), 0); + EXPECT_EQ(unlinkat(self->dfd, "sticky", AT_REMOVEDIR), 0); +} + +/* + * O_TMPFILE is encoded as __O_TMPFILE|O_DIRECTORY. Now that O_CREAT is no + * longer rejected alongside O_DIRECTORY, O_TMPFILE|O_CREAT must still be + * rejected explicitly so that it cannot create a persistent file on kerne= ls + * that predate O_TMPFILE. + */ +TEST_F(open_o_creat_o_dir, tmpfile_with_o_creat_einval) +{ + EXPECT_EQ(openat(self->dfd, ".", O_TMPFILE | O_CREAT | O_RDWR, S_IRWXU), + -1); + EXPECT_EQ(errno, EINVAL); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/filesystems/wrappers.h b/tools/testing= /selftests/filesystems/wrappers.h index 420ae4f908cf..abe5b85cebdc 100644 --- a/tools/testing/selftests/filesystems/wrappers.h +++ b/tools/testing/selftests/filesystems/wrappers.h @@ -13,6 +13,10 @@ #define STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id = */ #endif =20 +#ifndef FD_INVALID +#define FD_INVALID -10009 +#endif + static inline int sys_fsopen(const char *fsname, unsigned int flags) { return syscall(__NR_fsopen, fsname, flags); @@ -105,4 +109,11 @@ static inline int sys_open_tree(int dfd, const char *f= ilename, unsigned int flag return syscall(__NR_open_tree, dfd, filename, flags); } =20 +static inline int openat_o_mkdir(int dfd, const char *pathname, + unsigned int flags, mode_t mode) +{ + return syscall(__NR_openat, dfd, pathname, + flags | O_DIRECTORY | O_CREAT, mode); +} + #endif --=20 2.55.0