Commit de5817bb authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull landlock updates from Mickaël Salaün:
 "This mostly factors out some Landlock code and prepares for upcoming
  audit support.

  Because files with invalid modes might be visible after filesystem
  corruption, Landlock now handles those weird files too.

  A few sample and test issues are also fixed"

* tag 'landlock-6.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux:
  selftests/landlock: Add layout1.umount_sandboxer tests
  selftests/landlock: Add wrappers.h
  selftests/landlock: Fix error message
  landlock: Optimize file path walks and prepare for audit support
  selftests/landlock: Add test to check partial access in a mount tree
  landlock: Align partial refer access checks with final ones
  landlock: Simplify initially denied access rights
  landlock: Move access types
  landlock: Factor out check_access_path()
  selftests/landlock: Fix build with non-default pthread linking
  landlock: Use scoped guards for ruleset in landlock_add_rule()
  landlock: Use scoped guards for ruleset
  landlock: Constify get_mode_access()
  landlock: Handle weird files
  samples/landlock: Fix possible NULL dereference in parse_path()
  selftests/landlock: Remove unused macros in ptrace_test.c
parents 37b33c68 2a794ee6
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -91,6 +91,9 @@ static int parse_path(char *env_path, const char ***const path_list)
		}
	}
	*path_list = malloc(num_paths * sizeof(**path_list));
	if (!*path_list)
		return -1;

	for (i = 0; i < num_paths; i++)
		(*path_list)[i] = strsep(&env_path, ENV_DELIMITER);

@@ -127,6 +130,10 @@ static int populate_ruleset_fs(const char *const env_var, const int ruleset_fd,
	env_path_name = strdup(env_path_name);
	unsetenv(env_var);
	num_paths = parse_path(env_path_name, &path_list);
	if (num_paths < 0) {
		fprintf(stderr, "Failed to allocate memory\n");
		goto out_free_name;
	}
	if (num_paths == 1 && path_list[0][0] == '\0') {
		/*
		 * Allows to not use all possible restrictions (e.g. use
+77 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * Landlock LSM - Access types and helpers
 *
 * Copyright © 2016-2020 Mickaël Salaün <mic@digikod.net>
 * Copyright © 2018-2020 ANSSI
 * Copyright © 2024-2025 Microsoft Corporation
 */

#ifndef _SECURITY_LANDLOCK_ACCESS_H
#define _SECURITY_LANDLOCK_ACCESS_H

#include <linux/bitops.h>
#include <linux/build_bug.h>
#include <linux/kernel.h>
#include <uapi/linux/landlock.h>

#include "limits.h"

/*
 * All access rights that are denied by default whether they are handled or not
 * by a ruleset/layer.  This must be ORed with all ruleset->access_masks[]
 * entries when we need to get the absolute handled access masks, see
 * landlock_upgrade_handled_access_masks().
 */
/* clang-format off */
#define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \
	LANDLOCK_ACCESS_FS_REFER)
/* clang-format on */

typedef u16 access_mask_t;

/* Makes sure all filesystem access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS);
/* Makes sure all network access rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET);
/* Makes sure all scoped rights can be stored. */
static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE);
/* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */
static_assert(sizeof(unsigned long) >= sizeof(access_mask_t));

/* Ruleset access masks. */
struct access_masks {
	access_mask_t fs : LANDLOCK_NUM_ACCESS_FS;
	access_mask_t net : LANDLOCK_NUM_ACCESS_NET;
	access_mask_t scope : LANDLOCK_NUM_SCOPE;
};

union access_masks_all {
	struct access_masks masks;
	u32 all;
};

/* Makes sure all fields are covered. */
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);

/* Upgrades with all initially denied by default access rights. */
static inline struct access_masks
landlock_upgrade_handled_access_masks(struct access_masks access_masks)
{
	/*
	 * All access rights that are denied by default whether they are
	 * explicitly handled or not.
	 */
	if (access_masks.fs)
		access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED;

	return access_masks;
}

#endif /* _SECURITY_LANDLOCK_ACCESS_H */
+59 −55
Original line number Diff line number Diff line
@@ -36,6 +36,7 @@
#include <uapi/linux/fiemap.h>
#include <uapi/linux/landlock.h>

#include "access.h"
#include "common.h"
#include "cred.h"
#include "fs.h"
@@ -388,14 +389,6 @@ static bool is_nouser_or_private(const struct dentry *dentry)
		unlikely(IS_PRIVATE(d_backing_inode(dentry))));
}

static access_mask_t
get_handled_fs_accesses(const struct landlock_ruleset *const domain)
{
	/* Handles all initially denied by default access rights. */
	return landlock_union_access_masks(domain).fs |
	       LANDLOCK_ACCESS_FS_INITIALLY_DENIED;
}

static const struct access_masks any_fs = {
	.fs = ~0,
};
@@ -572,6 +565,12 @@ static void test_no_more_access(struct kunit *const test)
#undef NMA_TRUE
#undef NMA_FALSE

static bool is_layer_masks_allowed(
	layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
{
	return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
}

/*
 * Removes @layer_masks accesses that are not requested.
 *
@@ -589,7 +588,8 @@ scope_to_request(const access_mask_t access_request,

	for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks))
		(*layer_masks)[access_bit] = 0;
	return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));

	return is_layer_masks_allowed(layer_masks);
}

#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
@@ -778,16 +778,21 @@ static bool is_access_to_paths_allowed(
	if (WARN_ON_ONCE(domain->num_layers < 1 || !layer_masks_parent1))
		return false;

	allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);

	if (unlikely(layer_masks_parent2)) {
		if (WARN_ON_ONCE(!dentry_child1))
			return false;

		allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);

		/*
		 * For a double request, first check for potential privilege
		 * escalation by looking at domain handled accesses (which are
		 * a superset of the meaningful requested accesses).
		 */
		access_masked_parent1 = access_masked_parent2 =
			get_handled_fs_accesses(domain);
			landlock_union_access_masks(domain).fs;
		is_dom_check = true;
	} else {
		if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
@@ -847,15 +852,6 @@ static bool is_access_to_paths_allowed(
				     child1_is_directory, layer_masks_parent2,
				     layer_masks_child2,
				     child2_is_directory))) {
			allowed_parent1 = scope_to_request(
				access_request_parent1, layer_masks_parent1);
			allowed_parent2 = scope_to_request(
				access_request_parent2, layer_masks_parent2);

			/* Stops when all accesses are granted. */
			if (allowed_parent1 && allowed_parent2)
				break;

			/*
			 * Now, downgrades the remaining checks from domain
			 * handled accesses to requested accesses.
@@ -863,14 +859,31 @@ static bool is_access_to_paths_allowed(
			is_dom_check = false;
			access_masked_parent1 = access_request_parent1;
			access_masked_parent2 = access_request_parent2;

			allowed_parent1 =
				allowed_parent1 ||
				scope_to_request(access_masked_parent1,
						 layer_masks_parent1);
			allowed_parent2 =
				allowed_parent2 ||
				scope_to_request(access_masked_parent2,
						 layer_masks_parent2);

			/* Stops when all accesses are granted. */
			if (allowed_parent1 && allowed_parent2)
				break;
		}

		rule = find_rule(domain, walker_path.dentry);
		allowed_parent1 = landlock_unmask_layers(
			rule, access_masked_parent1, layer_masks_parent1,
		allowed_parent1 = allowed_parent1 ||
				  landlock_unmask_layers(
					  rule, access_masked_parent1,
					  layer_masks_parent1,
					  ARRAY_SIZE(*layer_masks_parent1));
		allowed_parent2 = landlock_unmask_layers(
			rule, access_masked_parent2, layer_masks_parent2,
		allowed_parent2 = allowed_parent2 ||
				  landlock_unmask_layers(
					  rule, access_masked_parent2,
					  layer_masks_parent2,
					  ARRAY_SIZE(*layer_masks_parent2));

		/* Stops when a rule from each layer grants access. */
@@ -895,8 +908,10 @@ static bool is_access_to_paths_allowed(
			 * access to internal filesystems (e.g. nsfs, which is
			 * reachable through /proc/<pid>/ns/<namespace>).
			 */
			allowed_parent1 = allowed_parent2 =
				!!(walker_path.mnt->mnt_flags & MNT_INTERNAL);
			if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
				allowed_parent1 = true;
				allowed_parent2 = true;
			}
			break;
		}
		parent_dentry = dget_parent(walker_path.dentry);
@@ -908,39 +923,29 @@ static bool is_access_to_paths_allowed(
	return allowed_parent1 && allowed_parent2;
}

static int check_access_path(const struct landlock_ruleset *const domain,
			     const struct path *const path,
static int current_check_access_path(const struct path *const path,
				     access_mask_t access_request)
{
	const struct landlock_ruleset *const dom = get_current_fs_domain();
	layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};

	access_request = landlock_init_layer_masks(
		domain, access_request, &layer_masks, LANDLOCK_KEY_INODE);
	if (is_access_to_paths_allowed(domain, path, access_request,
				       &layer_masks, NULL, 0, NULL, NULL))
	if (!dom)
		return 0;
	return -EACCES;
}

static int current_check_access_path(const struct path *const path,
				     const access_mask_t access_request)
{
	const struct landlock_ruleset *const dom = get_current_fs_domain();

	if (!dom)
	access_request = landlock_init_layer_masks(
		dom, access_request, &layer_masks, LANDLOCK_KEY_INODE);
	if (is_access_to_paths_allowed(dom, path, access_request, &layer_masks,
				       NULL, 0, NULL, NULL))
		return 0;
	return check_access_path(dom, path, access_request);

	return -EACCES;
}

static access_mask_t get_mode_access(const umode_t mode)
static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
{
	switch (mode & S_IFMT) {
	case S_IFLNK:
		return LANDLOCK_ACCESS_FS_MAKE_SYM;
	case 0:
		/* A zero mode translates to S_IFREG. */
	case S_IFREG:
		return LANDLOCK_ACCESS_FS_MAKE_REG;
	case S_IFDIR:
		return LANDLOCK_ACCESS_FS_MAKE_DIR;
	case S_IFCHR:
@@ -951,9 +956,12 @@ static access_mask_t get_mode_access(const umode_t mode)
		return LANDLOCK_ACCESS_FS_MAKE_FIFO;
	case S_IFSOCK:
		return LANDLOCK_ACCESS_FS_MAKE_SOCK;
	case S_IFREG:
	case 0:
		/* A zero mode translates to S_IFREG. */
	default:
		WARN_ON_ONCE(1);
		return 0;
		/* Treats weird files as regular files. */
		return LANDLOCK_ACCESS_FS_MAKE_REG;
	}
}

@@ -1414,11 +1422,7 @@ static int hook_path_mknod(const struct path *const dir,
			   struct dentry *const dentry, const umode_t mode,
			   const unsigned int dev)
{
	const struct landlock_ruleset *const dom = get_current_fs_domain();

	if (!dom)
		return 0;
	return check_access_path(dom, dir, get_mode_access(mode));
	return current_check_access_path(dir, get_mode_access(mode));
}

static int hook_path_symlink(const struct path *const dir,
+1 −0
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <linux/init.h>
#include <linux/rcupdate.h>

#include "access.h"
#include "ruleset.h"
#include "setup.h"

+13 −13
Original line number Diff line number Diff line
@@ -8,11 +8,13 @@

#include <linux/bits.h>
#include <linux/bug.h>
#include <linux/cleanup.h>
#include <linux/compiler_types.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/lockdep.h>
#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/rbtree.h>
#include <linux/refcount.h>
@@ -20,6 +22,7 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>

#include "access.h"
#include "limits.h"
#include "object.h"
#include "ruleset.h"
@@ -384,7 +387,8 @@ static int merge_ruleset(struct landlock_ruleset *const dst,
		err = -EINVAL;
		goto out_unlock;
	}
	dst->access_masks[dst->num_layers - 1] = src->access_masks[0];
	dst->access_masks[dst->num_layers - 1] =
		landlock_upgrade_handled_access_masks(src->access_masks[0]);

	/* Merges the @src inode tree. */
	err = merge_tree(dst, src, LANDLOCK_KEY_INODE);
@@ -537,7 +541,7 @@ struct landlock_ruleset *
landlock_merge_ruleset(struct landlock_ruleset *const parent,
		       struct landlock_ruleset *const ruleset)
{
	struct landlock_ruleset *new_dom;
	struct landlock_ruleset *new_dom __free(landlock_put_ruleset) = NULL;
	u32 num_layers;
	int err;

@@ -557,29 +561,25 @@ landlock_merge_ruleset(struct landlock_ruleset *const parent,
	new_dom = create_ruleset(num_layers);
	if (IS_ERR(new_dom))
		return new_dom;

	new_dom->hierarchy =
		kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL_ACCOUNT);
	if (!new_dom->hierarchy) {
		err = -ENOMEM;
		goto out_put_dom;
	}
	if (!new_dom->hierarchy)
		return ERR_PTR(-ENOMEM);

	refcount_set(&new_dom->hierarchy->usage, 1);

	/* ...as a child of @parent... */
	err = inherit_ruleset(parent, new_dom);
	if (err)
		goto out_put_dom;
		return ERR_PTR(err);

	/* ...and including @ruleset. */
	err = merge_ruleset(new_dom, ruleset);
	if (err)
		goto out_put_dom;

	return new_dom;

out_put_dom:
	landlock_put_ruleset(new_dom);
		return ERR_PTR(err);

	return no_free_ptr(new_dom);
}

/*
Loading