FAILED: patch "[PATCH] landlock: Fix use-after-free of the source's parent directory" failed to apply to 6.18-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 2c6dc792538260a8087ac5b22c31b3b8e47c85d6
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026091639-trophy-refract-3155@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 2c6dc792538260a8087ac5b22c31b3b8e47c85d6 Mon Sep 17 00:00:00 2001
From: Norbert Szetei <[email protected]>
Date: Sat, 22 Aug 2026 14:29:00 +0200
Subject: [PATCH] landlock: Fix use-after-free of the source's parent directory
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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: filename_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.
A process can trigger this after entering a Landlock domain that handles at least one filesystem access right. The process can then race a linkat(2) loop against 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 dentry selected as the source parent, using dget() for the common-mount-root case and dget_parent() otherwise. Release it after the hierarchy walk and synchronous audit logging.
security/landlock/fs.c +12 -6
@@ -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). Pin the dentry used as old_parent in either case. +* Otherwise, dget_parent() safely fetches and pins the current parent +* against 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
Sasha Levin
maintainerPATCH 1/5 landlock: Clarify documentation for the IOCTL access right
From: Günther Noack <[email protected]>
[ Upstream commit 6abbb8703aeeb645a681ab6ad155e0b450413787 ]
Move the description of the LANDLOCK_ACCESS_FS_IOCTL_DEV access right together with the file access rights.
This group of access rights applies to files (in this case device files), and they can be added to file or directory inodes using landlock_add_rule(2). The check for that works the same for all file access rights, including LANDLOCK_ACCESS_FS_IOCTL_DEV.
Invoking ioctl(2) on directory FDs can not currently be restricted with Landlock. Having it grouped separately in the documentation is a remnant from earlier revisions of the LANDLOCK_ACCESS_FS_IOCTL_DEV patch set.
Stable-dep-of: 2c6dc7925382 ("landlock: Fix use-after-free of the source's parent directory")
--- include/uapi/linux/landlock.h | 37 ++++++++++++++++------------------- 1 file changed, 17 insertions(+), 20 deletions(-)
include/uapi/linux/landlock.h +17 -20
@@ -216,6 +216,23 @@ struct landlock_net_port_attr {* :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with * ``O_TRUNC``. This access right is available since the third version of the * Landlock ABI. +* - %LANDLOCK_ACCESS_FS_IOCTL_DEV: Invoke :manpage:`ioctl(2)` commands on an opened +* character or block device. +* +* This access right applies to all `ioctl(2)` commands implemented by device +* drivers. However, the following common IOCTL commands continue to be +* invokable independent of the %LANDLOCK_ACCESS_FS_IOCTL_DEV right: +* +* * IOCTL commands targeting file descriptors (``FIOCLEX``, ``FIONCLEX``), +* * IOCTL commands targeting file descriptions (``FIONBIO``, ``FIOASYNC``), +* * IOCTL commands targeting file systems (``FIFREEZE``, ``FITHAW``, +* ``FIGETBSZ``, ``FS_IOC_GETFSUUID``, ``FS_IOC_GETFSSYSFSPATH``) +* * Some IOCTL commands which do not make sense when used with devices, but +* whose implementations are safe and return the right error codes +* (``FS_IOC_FIEMAP``, ``FICLONE``, ``FICLONERANGE``, ``FIDEDUPERANGE``) +* +* This access right is available since the fifth version of the Landlock +* ABI. * * Whether an opened file can be truncated with :manpage:`ftruncate(2)` or used * with `ioctl(2)` is determined during :manpage:`open(2)`, in the same way as
@@ -276,26 +293,6 @@ struct landlock_net_port_attr {* If multiple requirements are not met, the ``EACCES`` error code takes * precedence over ``EXDEV``. * -* The following access right applies both to files and directories: -* -* - %LANDLOCK_ACCESS_FS_IOCTL_DEV: Invoke :manpage:`ioctl(2)` commands on an opened -* character or block device. -* -* This access right applies to all `ioctl(2)` commands implemented by device -* drivers. However, the following common IOCTL commands continue to be -* invokable independent of the %LANDLOCK_ACCESS_FS_IOCTL_DEV right: -* -* * IOCTL commands targeting file descriptors (``FIOCLEX``, ``FIONCLEX``), -* * IOCTL commands targeting file descriptions (``FIONBIO``, ``FIOASYNC``), -* * IOCTL commands targeting file systems (``FIFREEZE``, ``FITHAW``, -* ``FIGETBSZ``, ``FS_IOC_GETFSUUID``, ``FS_IOC_GETFSSYSFSPATH``) -* * Some IOCTL commands which do not make sense when used with devices, but -* whose implementations are safe and return the right error codes -* (``FS_IOC_FIEMAP``, ``FICLONE``, ``FICLONERANGE``, ``FIDEDUPERANGE``) -* -* This access right is available since the fifth version of the Landlock -* ABI. -* * .. warning:: * * It is currently not possible to restrict some file-related actions
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/5 landlock: Add access_mask_subset() helper
From: Günther Noack <[email protected]>
[ Upstream commit 45f2a2926b2187d1b08132d2728af50785b007a7 ]
This helper function checks whether an access_mask_t has a subset of the bits enabled than another one. This expresses the intent a bit smoother in the code and does not cost us anything when it gets inlined.
Stable-dep-of: 2c6dc7925382 ("landlock: Fix use-after-free of the source's parent directory")
--- security/landlock/access.h | 7 +++++++ security/landlock/fs.c | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-)
security/landlock/access.h +7 -0
@@ -97,4 +97,11 @@ landlock_upgrade_handled_access_masks(struct access_masks access_masks) return access_masks; } +/* Checks the subset relation between access masks. */ +static inline bool access_mask_subset (access_mask_t subset, +access_mask_t superset) +{ +return (subset | superset) == superset; +} +#endif /* _SECURITY_LANDLOCK_ACCESS_H */
security/landlock/fs.c +2 -2
@@ -332,7 +332,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,/* Files only get access rights that make sense. */ if (!d_is_dir (path->dentry) && - (access_rights |ACCESS_FILE ) !=ACCESS_FILE ) + !access_mask_subset (access_rights,ACCESS_FILE ))return -EINVAL ;if (WARN_ON_ONCE (ruleset->num_layers !=1 ))return -EINVAL ;
@@ -1717,7 +1717,7 @@ static int hook_file_open(struct file *const file) ARRAY_SIZE(layer_masks));#endif /* CONFIG_AUDIT */ -if ((open_access_request & allowed_access) == open_access_request) +if (access_mask_subset (open_access_request, allowed_access))return 0 ;/* Sets access to reflect the actual request. */
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/5 landlock: Transpose the layer masks data structure
In reply to Sasha LevinFrom: Günther Noack <[email protected]>
[ Upstream commit 65b691f84dba54a446518c51b25d3d4f1739dec5 ]
The layer masks data structure tracks the requested but unfulfilled access rights during an operation's security check. It stores one bit for each combination of access right and layer index. If the bit is set, that access right is not granted (yet) in the given layer and we have to traverse the path further upwards to grant it.
Previously, the layer masks were stored as arrays mapping from access right indices to layer_mask_t. The layer_mask_t value then indicates all layers in which the given access right is still (tentatively) denied.
This patch introduces struct layer_access_masks instead: This struct contains an array with the access_mask_t of each (tentatively) denied access right in that layer.
The hypothesis of this patch is that this simplifies the code enough so that the resulting code will run faster:
* We can use bitwise operations in multiple places where we previously looped over bits individually with macros. (Should require less branch speculation and lends itself to better loop unrolling.)
* Code is ~75 lines smaller.
Other noteworthy changes:
* In no_more_access(), call a new helper function may_refer(), which only solves the asymmetric case. Previously, the code interleaved the checks for the two symmetric cases in RENAME_EXCHANGE. It feels that the code is clearer when renames without RENAME_EXCHANGE are more obviously the normal case.
Tradeoffs:
This change improves performance, at a slight size increase to the layer masks data structure.
This fixes the size of the data structure at 32 bytes for all types of access rights. (64, once we introduce a 17th filesystem access right).
For filesystem access rights, at the moment, the data structure has the same size as before, but once we introduce the 17th filesystem access right, it will double in size (from 32 to 64 bytes), as access_mask_t grows from 16 to 32 bit [1].
Stable backport notes:
Resolve the landlock_unmask_layers() comment conflict by keeping the per-layer access-mask implementation while preserving the stable tree's other ruleset changes.
Keep the forward and RENAME_EXCHANGE restriction checks inside the existing no_more_access() function instead of adding may_refer(). Both checks retain the per-layer subset comparison and file-only filtering. No new functions are introduced by this backport.
Remove the extra blank line before the combined parent-access success check in current_check_refer_path() to match the context required by 2c6dc792538260a8087ac5b22c31b3b8e47c85d6. The parent-dentry reference fix itself remains in that target commit.
Stable-dep-of: 2c6dc7925382 ("landlock: Fix use-after-free of the source's parent directory")
--- security/landlock/access.h | 28 ++- security/landlock/audit.c | 81 +++------ security/landlock/audit.h | 3 +- security/landlock/domain.c | 44 ++--- security/landlock/domain.h | 3 +- security/landlock/fs.c | 331 ++++++++++++++++-------------------- security/landlock/net.c | 9 +- security/landlock/ruleset.c | 91 ++++------ security/landlock/ruleset.h | 6 +- 9 files changed, 260 insertions(+), 336 deletions(-)
security/landlock/access.h +22 -6
@@ -61,14 +61,30 @@ union access_masks_all {static_assert (sizeof (typeof_member (union access_masks_all ,masks )) ==sizeof (typeof_member (union access_masks_all ,all ))); -typedef u16 layer_mask_t ; - -/* Makes sure all layers can be checked. */ -static_assert (BITS_PER_TYPE (layer_mask_t) >=LANDLOCK_MAX_NUM_LAYERS ); +/** +* struct layer_access_masks - A boolean matrix of layers and access rights +* +* This has a bit for each combination of layer numbers and access rights. +* During access checks, it is used to represent the access rights for each +* layer which still need to be fulfilled. When all bits are 0, the access +* request is considered to be fulfilled. +*/ +struct layer_access_masks { +/** +* @access: The unfulfilled access rights for each layer. +*/ +access_mask_t access[LANDLOCK_MAX_NUM_LAYERS ]; +};/* -* Tracks domains responsible of a denied access. This is required to avoid -* storing in each object the full layer_masks[] required by update_request(). +* Tracks domains responsible of a denied access. This avoids storing in each +* object the full matrix of per-layer unfulfilled access rights, which is +* required by update_request(). +* +* Each nibble represents the layer index of the newest layer which denied a +* certain access right. For file system access rights, the upper four bits are +* the index of the layer which denies LANDLOCK_ACCESS_FS_IOCTL_DEV and the +* lower nibble represents LANDLOCK_ACCESS_FS_TRUNCATE. */ typedef u8 deny_masks_t ;
security/landlock/audit.c +25 -56
134 lines not on this page · raw message
security/landlock/audit.h +1 -2
@@ -43,8 +43,7 @@ struct landlock_request {access_mask_t access;/* Required fields for requests with layer masks. */ -const layer_mask_t (*layer_masks)[]; -size_t layer_masks_size; +const struct layer_access_masks *layer_masks;/* Required fields for requests with deny masks. */ const access_mask_t all_existing_optional_access;
security/landlock/domain.c +24 -20
84 lines not on this page · raw message
security/landlock/domain.h +1 -2
@@ -119,8 +119,7 @@ struct landlock_hierarchy {deny_masks_t landlock_get_deny_masks (const access_mask_t all_existing_optional_access,const access_mask_t optional_access, -const layer_mask_t (*const layer_masks)[], -size_t layer_masks_size); +const struct layer_access_masks *const masks);int landlock_init_hierarchy_log (struct landlock_hierarchy *const hierarchy);
security/landlock/fs.c +145 -186
524 lines not on this page · raw message
security/landlock/net.c +5 -4
@@ -48,7 +48,7 @@ static int current_check_access_socket(struct socket *const sock, {unsigned short sock_family;__be16 port; -layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_NET ] = {}; +struct layer_access_masks layer_masks = {};const struct landlock_rule *rule;struct landlock_id id = { .type =LANDLOCK_KEY_NET_PORT ,
@@ -201,8 +201,10 @@ static int current_check_access_socket(struct socket *const sock, access_request =landlock_init_layer_masks (subject->domain, access_request, &layer_masks,LANDLOCK_KEY_NET_PORT ); -if (landlock_unmask_layers (rule, access_request, &layer_masks, -ARRAY_SIZE (layer_masks))) +if (!access_request) +return 0 ; + +if (landlock_unmask_layers (rule, &layer_masks))return 0 ; audit_net.family = address->sa_family;
@@ -214,7 +216,6 @@ static int current_check_access_socket(struct socket *const sock, .audit.u.net = &audit_net, .access = access_request, .layer_masks = &layer_masks, - .layer_masks_size = ARRAY_SIZE(layer_masks), });return -EACCES ; }
security/landlock/ruleset.c +35 -56
148 lines not on this page · raw message
security/landlock/ruleset.h +2 -4
@@ -302,14 +302,12 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset, }bool landlock_unmask_layers (const struct landlock_rule *const rule, -const access_mask_t access_request, -layer_mask_t (*const layer_masks)[], -const size_t masks_array_size); +struct layer_access_masks *masks);access_mask_t landlock_init_layer_masks (const struct landlock_ruleset *const domain,const access_mask_t access_request, -layer_mask_t (*const layer_masks)[], +struct layer_access_masks *masks,const enum landlock_key_type key_type);#endif /* _SECURITY_LANDLOCK_RULESET_H */
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/5 landlock: Use mem_is_zero() in is_layer_masks_allowed()
In reply to Sasha LevinFrom: Günther Noack <[email protected]>
[ Upstream commit 1c4fe87364782d1b058cc9d15fc8acef44bce033 ]
This is equivalent, but expresses the intent a bit clearer.
Stable-dep-of: 2c6dc7925382 ("landlock: Fix use-after-free of the source's parent directory")
--- security/landlock/fs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
security/landlock/fs.c +1 -1
@@ -559,7 +559,7 @@ static void test_no_more_access(struct kunit *const test)static bool is_layer_masks_allowed (const struct layer_access_masks *masks) { -return !memchr_inv (&masks->access,0 ,sizeof (masks->access)); +return mem_is_zero (&masks->access,sizeof (masks->access)); }/*
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/5 landlock: Fix use-after-free of the source's parent directory
In reply to Sasha LevinFrom: Norbert Szetei <[email protected]>
[ Upstream commit 2c6dc792538260a8087ac5b22c31b3b8e47c85d6 ]
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: filename_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.
A process can trigger this after entering a Landlock domain that handles at least one filesystem access right. The process can then race a linkat(2) loop against 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 dentry selected as the source parent, using dget() for the common-mount-root case and dget_parent() otherwise. Release it after the hierarchy walk and synchronous audit logging.
--- security/landlock/fs.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-)
security/landlock/fs.c +12 -6
@@ -1196,11 +1196,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). Pin the dentry used as old_parent in either case. +* Otherwise, dget_parent() safely fetches and pins the current parent +* against 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,
@@ -1209,8 +1210,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,
@@ -1222,8 +1225,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;
@@ -1233,6 +1238,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
Signature · 1 line
2.53.0