From: "Günther Noack" <gnoack3000@gmail.com>
To: Norbert Szetei <norbert@doyensec.com>
Cc: "Mickaël Salaün" <mic@digikod.net>,
"Günther Noack" <gnoack@google.com>,
"Paul Moore" <paul@paul-moore.com>,
"James Morris" <jmorris@namei.org>,
"Serge E. Hallyn" <serge@hallyn.com>,
linux-security-module@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: Re: [PATCH] landlock: Fix use-after-free of the source's parent directory
Date: Sat, 5 Sep 2026 21:49:55 +0200 [thread overview]
Message-ID: <20260905.2e30c1b0adfe@gnoack.org> (raw)
In-Reply-To: <E9CDD9E6-E960-4DE2-B1AC-5667D52ABB3E@doyensec.com>
On Sat, Aug 22, 2026 at 02:29:00PM +0200, Norbert Szetei wrote:
> current_check_refer_path() reads old_dentry->d_parent without holding a
> reference nor a lock on it, and then dereferences it in
> collect_domain_accesses() and in the audit record.
>
> A reference on a child does not pin its parent: __d_move() reassigns
> dentry->d_parent and drops the reference the child held on its former
> parent. hook_path_rename() is not affected because the rename path calls
> lock_rename() before the hook, so the source cannot be reparented under
> it. hook_path_link() has no such protection: do_linkat() holds a
> reference on the source dentry but neither locks nor references its
> parent, so a concurrent rename(2) can reparent the source while
> security_path_link() runs, and the former parent can then be removed and
> freed while the hook walks it.
>
> Any process able to sandbox itself with LANDLOCK_ACCESS_FS_REFER can
> trigger this with a linkat(2) loop racing rename(2) and rmdir(2):
>
> BUG: KASAN: slab-use-after-free in collect_domain_accesses+0x278/0x290
> Read of size 4 at addr ffff888160bd53f4 by task llrepro2/549
> collect_domain_accesses+0x278/0x290
> current_check_refer_path+0x952/0x1120
> security_path_link+0x1be/0x320
> filename_linkat+0x342/0x6d0
> __x64_sys_linkat+0xfa/0x150
> Freed by task 562:
> kmem_cache_free+0x139/0x4c0
> i_callback+0x4b/0x80
> rcu_core+0x7dc/0x10a0
>
> Take a reference on the parent with dget_parent(), and release it once
> the hierarchy walk and the audit record are done.
>
> Cc: stable@vger.kernel.org
> Fixes: b91c3e4ea756 ("landlock: Add support for file reparenting with LANDLOCK_ACCESS_FS_REFER")
> Signed-off-by: Norbert Szetei <norbert@doyensec.com>
> ---
> security/landlock/fs.c | 18 ++++++++++++------
> 1 file changed, 12 insertions(+), 6 deletions(-)
>
> diff --git a/security/landlock/fs.c b/security/landlock/fs.c
> index 30aa6ce13590..200c83372bbe 100644
> --- a/security/landlock/fs.c
> +++ b/security/landlock/fs.c
> @@ -1298,11 +1298,12 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> /*
> * old_dentry may be the root of the common mount point and
> * !IS_ROOT(old_dentry) at the same time (e.g. with open_tree() and
> - * OPEN_TREE_CLONE). We do not need to call dget(old_parent) because
> - * we keep a reference to old_dentry.
> + * OPEN_TREE_CLONE). Pins the parent in both cases: a reference on
> + * old_dentry does not pin its parent, which may then be freed after a
> + * concurrent rename(2).
> */
> - old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry :
> - old_dentry->d_parent;
> + old_parent = (old_dentry == mnt_dir.dentry) ? dget(old_dentry) :
> + dget_parent(old_dentry);
>
> /* new_dir->dentry is equal to new_dentry->d_parent */
> allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
> @@ -1311,8 +1312,10 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
> new_dir->dentry,
> &layer_masks_parent2);
> - if (allow_parent1 && allow_parent2)
> + if (allow_parent1 && allow_parent2) {
> + dput(old_parent);
> return 0;
> + }
>
> /*
> * To be able to compare source and destination domain access rights,
> @@ -1324,8 +1327,10 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> subject->domain, &mnt_dir, access_request_parent1,
> &layer_masks_parent1, &request1, old_dentry,
> access_request_parent2, &layer_masks_parent2, &request2,
> - exchange ? new_dentry : NULL))
> + exchange ? new_dentry : NULL)) {
> + dput(old_parent);
> return 0;
> + }
>
> if (request1.access) {
> request1.audit.u.path.dentry = old_parent;
> @@ -1335,6 +1340,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
> request2.audit.u.path.dentry = new_dir->dentry;
> landlock_log_denial(subject, &request2);
> }
> + dput(old_parent);
>
> /*
> * This prioritizes EACCES over EXDEV for all actions, including
> --
> 2.55.0
Reviewed-by: Günther Noack <gnoack3000@gmail.com>
Tested-by: Günther Noack <gnoack3000@gmail.com>
Thank you for the bug report and patch, Norbert! Excellent finding!
I can validate the bug and that your patch fixes the problem.
–Günther
next prev parent reply other threads:[~2026-09-05 19:50 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-22 12:29 [PATCH] landlock: Fix use-after-free of the source's parent directory Norbert Szetei
2026-09-05 19:49 ` Günther Noack [this message]
2026-09-07 9:54 ` Mickaël Salaün
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260905.2e30c1b0adfe@gnoack.org \
--to=gnoack3000@gmail.com \
--cc=gnoack@google.com \
--cc=jmorris@namei.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=mic@digikod.net \
--cc=norbert@doyensec.com \
--cc=paul@paul-moore.com \
--cc=serge@hallyn.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.