Merge tag 'landlock-6.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux

Pull landlock updates from Mickaël Salaün:
 "This mainly fixes handling of disconnected directories and adds new
  tests"

* tag 'landlock-6.19-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux:
  selftests/landlock: Add disconnected leafs and branch test suites
  selftests/landlock: Add tests for access through disconnected paths
  landlock: Improve variable scope
  landlock: Fix handling of disconnected directories
  selftests/landlock: Fix makefile header list
  landlock: Make docs in cred.h and domain.h visible
  landlock: Minor comments improvements
This commit is contained in:
Linus Torvalds
2025-12-06 09:52:41 -08:00
7 changed files with 1536 additions and 27 deletions

View File

@@ -7,7 +7,7 @@ Landlock LSM: kernel documentation
==================================
:Author: Mickaël Salaün
:Date: March 2025
:Date: September 2025
Landlock's goal is to create scoped access-control (i.e. sandboxing). To
harden a whole system, this feature should be available to any process,
@@ -110,6 +110,12 @@ Filesystem
.. kernel-doc:: security/landlock/fs.h
:identifiers:
Process credential
------------------
.. kernel-doc:: security/landlock/cred.h
:identifiers:
Ruleset and domain
------------------
@@ -128,6 +134,9 @@ makes the reasoning much easier and helps avoid pitfalls.
.. kernel-doc:: security/landlock/ruleset.h
:identifiers:
.. kernel-doc:: security/landlock/domain.h
:identifiers:
Additional documentation
========================

View File

@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/**
* DOC: erratum_3
*
* Erratum 3: Disconnected directory handling
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* This fix addresses an issue with disconnected directories that occur when a
* directory is moved outside the scope of a bind mount. The change ensures
* that evaluated access rights include both those from the disconnected file
* hierarchy down to its filesystem root and those from the related mount point
* hierarchy. This prevents access right widening through rename or link
* actions.
*/
LANDLOCK_ERRATUM(3)

View File

@@ -714,7 +714,8 @@ static void test_is_eacces_with_write(struct kunit *const test)
* is_access_to_paths_allowed - Check accesses for requests with a common path
*
* @domain: Domain to check against.
* @path: File hierarchy to walk through.
* @path: File hierarchy to walk through. For refer checks, this would be
* the common mountpoint.
* @access_request_parent1: Accesses to check, once @layer_masks_parent1 is
* equal to @layer_masks_parent2 (if any). This is tied to the unique
* requested path for most actions, or the source in case of a refer action
@@ -837,7 +838,6 @@ static bool is_access_to_paths_allowed(
* restriction.
*/
while (true) {
struct dentry *parent_dentry;
const struct landlock_rule *rule;
/*
@@ -909,22 +909,34 @@ jump_up:
break;
}
}
if (unlikely(IS_ROOT(walker_path.dentry))) {
if (likely(walker_path.mnt->mnt_flags & MNT_INTERNAL)) {
/*
* Stops at disconnected root directories. Only allows
* access to internal filesystems (e.g. nsfs, which is
* reachable through /proc/<pid>/ns/<namespace>).
* Stops and allows access when reaching disconnected root
* directories that are part of internal filesystems (e.g. nsfs,
* which is reachable through /proc/<pid>/ns/<namespace>).
*/
if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
allowed_parent1 = true;
allowed_parent2 = true;
}
break;
}
parent_dentry = dget_parent(walker_path.dentry);
/*
* We reached a disconnected root directory from a bind mount.
* Let's continue the walk with the mount point we missed.
*/
dput(walker_path.dentry);
walker_path.dentry = walker_path.mnt->mnt_root;
dget(walker_path.dentry);
} else {
struct dentry *const parent_dentry =
dget_parent(walker_path.dentry);
dput(walker_path.dentry);
walker_path.dentry = parent_dentry;
}
}
path_put(&walker_path);
if (!allowed_parent1) {
@@ -1021,6 +1033,9 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
* file. While walking from @dir to @mnt_root, we record all the domain's
* allowed accesses in @layer_masks_dom.
*
* Because of disconnected directories, this walk may not reach @mnt_dir. In
* this case, the walk will continue to @mnt_dir after this call.
*
* This is similar to is_access_to_paths_allowed() but much simpler because it
* only handles walking on the same mount point and only checks one set of
* accesses.
@@ -1062,8 +1077,11 @@ static bool collect_domain_accesses(
break;
}
/* We should not reach a root other than @mnt_root. */
if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir)))
/*
* Stops at the mount point or the filesystem root for a disconnected
* directory.
*/
if (dir == mnt_root || unlikely(IS_ROOT(dir)))
break;
parent_dentry = dget_parent(dir);

View File

@@ -83,6 +83,10 @@ static void build_check_rule(void)
.num_layers = ~0,
};
/*
* Checks that .num_layers is large enough for at least
* LANDLOCK_MAX_NUM_LAYERS layers.
*/
BUILD_BUG_ON(rule.num_layers < LANDLOCK_MAX_NUM_LAYERS);
}
@@ -290,6 +294,10 @@ static void build_check_layer(void)
.access = ~0,
};
/*
* Checks that .level and .access are large enough to contain their expected
* maximum values.
*/
BUILD_BUG_ON(layer.level < LANDLOCK_MAX_NUM_LAYERS);
BUILD_BUG_ON(layer.access < LANDLOCK_MASK_ACCESS_FS);
}
@@ -644,8 +652,8 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
bool is_empty;
/*
* Records in @layer_masks which layer grants access to each
* requested access.
* Records in @layer_masks which layer grants access to each requested
* access: bit cleared if the related layer grants access.
*/
is_empty = true;
for_each_set_bit(access_bit, &access_req, masks_array_size) {

View File

@@ -27,7 +27,7 @@ struct landlock_hierarchy;
*/
struct landlock_layer {
/**
* @level: Position of this layer in the layer stack.
* @level: Position of this layer in the layer stack. Starts from 1.
*/
u16 level;
/**

View File

@@ -4,7 +4,7 @@
CFLAGS += -Wall -O2 $(KHDR_INCLUDES)
LOCAL_HDRS += common.h
LOCAL_HDRS += $(wildcard *.h)
src_test := $(wildcard *_test.c)

File diff suppressed because it is too large Load Diff