mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
landlock: Split denial logging from audit into common framework
Tracepoint emission requires the denial framework (layer identification, request validation) without depending on CONFIG_AUDIT. Separate the denial logging infrastructure from the audit-specific code by introducing a common log framework. Create CONFIG_SECURITY_LANDLOCK_LOG, enabled by default when CONFIG_AUDIT is set; a following commit extends it to CONFIG_TRACEPOINTS when the first tracepoint consumer is added. Move the common framework (the request types, the layer identification and request validation, and the landlock_log_denial() and landlock_log_free_domain() entry points) into log.c and log.h, and keep the audit-specific record formatting in audit.c. log.o is built for CONFIG_SECURITY_LANDLOCK_LOG and audit.o for CONFIG_AUDIT, so the common framework is available to a tracepoints-only build. The entry points dispatch to no-op static inline audit stubs without CONFIG_AUDIT, so the call sites stay unconditional. Rename the former landlock_log_drop_domain() to landlock_log_free_domain() to match the landlock_free_domain tracepoint added in a following commit. landlock_log_denial() counts denials even without audit, so its declaration and no-op stub are guarded by CONFIG_SECURITY_LANDLOCK_LOG, not CONFIG_AUDIT; a CONFIG_AUDIT guard would expose the stub and clash with log.c's definition in a tracepoints-only build. Widen the ID allocation (id.o and the landlock_init_id() / landlock_get_id_range() declarations) and the log-state representation (the domain_exec and log_subdomains_off credential fields, the landlock_hierarchy log fields, and the code that maintains them) from CONFIG_AUDIT to CONFIG_SECURITY_LANDLOCK_LOG, so each field and its writer share one guard and are available to tracing without audit support. Widen the denial-path state that feeds the per-denial logging decision the same way, so the "logged" verdict is computed identically whether or not CONFIG_AUDIT is set. Widening fown_layer is what keeps the file-owner-signal path valid without audit: otherwise hook_file_send_sigiotask() would leave layer_plus_one at zero, tripping the is_valid_request() canary and dropping the LANDLOCK_SCOPE_SIGNAL denial from tracing. The ruleset-level quiet_masks stays on no CONFIG guard: it is builder state validated and stored from user input, kept available so LANDLOCK_ADD_RULE_QUIET flags are accepted and ignored, not rejected, when CONFIG_SECURITY_LANDLOCK_LOG is disabled. Cc: Günther Noack <gnoack@google.com> Link: https://patch.msgid.link/20260811094338.288094-5-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
@@ -21,6 +21,11 @@ config SECURITY_LANDLOCK
|
||||
you should also prepend "landlock," to the content of CONFIG_LSM to
|
||||
enable Landlock at boot time.
|
||||
|
||||
config SECURITY_LANDLOCK_LOG
|
||||
bool
|
||||
depends on SECURITY_LANDLOCK
|
||||
default y if AUDIT
|
||||
|
||||
config SECURITY_LANDLOCK_KUNIT_TEST
|
||||
bool "KUnit tests for Landlock" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT=y
|
||||
|
||||
@@ -13,6 +13,8 @@ landlock-y := \
|
||||
|
||||
landlock-$(CONFIG_INET) += net.o
|
||||
|
||||
landlock-$(CONFIG_AUDIT) += \
|
||||
landlock-$(CONFIG_SECURITY_LANDLOCK_LOG) += \
|
||||
id.o \
|
||||
audit.o
|
||||
log.o
|
||||
|
||||
landlock-$(CONFIG_AUDIT) += audit.o
|
||||
|
||||
@@ -74,13 +74,13 @@ struct layer_mask {
|
||||
* @access: The unfulfilled access rights for this layer.
|
||||
*/
|
||||
access_mask_t access : LANDLOCK_NUM_ACCESS_MAX;
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
/**
|
||||
* @quiet: Whether we have encountered a rule with the quiet flag for
|
||||
* this layer. Used to control logging.
|
||||
*/
|
||||
access_mask_t quiet : 1;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
} __packed __aligned(sizeof(access_mask_t));
|
||||
|
||||
/*
|
||||
|
||||
+19
-466
@@ -5,7 +5,6 @@
|
||||
* Copyright © 2023-2025 Microsoft Corporation
|
||||
*/
|
||||
|
||||
#include <kunit/test.h>
|
||||
#include <linux/audit.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/lsm_audit.h>
|
||||
@@ -18,7 +17,7 @@
|
||||
#include "cred.h"
|
||||
#include "domain.h"
|
||||
#include "limits.h"
|
||||
#include "ruleset.h"
|
||||
#include "log.h"
|
||||
|
||||
static const char *const fs_access_strings[] = {
|
||||
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = "fs.execute",
|
||||
@@ -137,393 +136,6 @@ static void log_domain(struct landlock_hierarchy *const hierarchy)
|
||||
WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED);
|
||||
}
|
||||
|
||||
static struct landlock_hierarchy *
|
||||
get_hierarchy(const struct landlock_domain *const domain, const size_t layer)
|
||||
{
|
||||
struct landlock_hierarchy *hierarchy = domain->hierarchy;
|
||||
ssize_t i;
|
||||
|
||||
if (WARN_ON_ONCE(layer >= domain->num_layers))
|
||||
return hierarchy;
|
||||
|
||||
for (i = domain->num_layers - 1; i > layer; i--) {
|
||||
if (WARN_ON_ONCE(!hierarchy->parent))
|
||||
break;
|
||||
|
||||
hierarchy = hierarchy->parent;
|
||||
}
|
||||
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static void test_get_hierarchy(struct kunit *const test)
|
||||
{
|
||||
struct landlock_hierarchy dom0_hierarchy = {
|
||||
.id = 10,
|
||||
};
|
||||
struct landlock_hierarchy dom1_hierarchy = {
|
||||
.parent = &dom0_hierarchy,
|
||||
.id = 20,
|
||||
};
|
||||
struct landlock_hierarchy dom2_hierarchy = {
|
||||
.parent = &dom1_hierarchy,
|
||||
.id = 30,
|
||||
};
|
||||
struct landlock_domain dom2 = {
|
||||
.hierarchy = &dom2_hierarchy,
|
||||
.num_layers = 3,
|
||||
};
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
|
||||
KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
|
||||
KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
|
||||
/* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
/* Get the youngest layer that denied the access_request. */
|
||||
static size_t get_denied_layer(const struct landlock_domain *const domain,
|
||||
access_mask_t *const access_request,
|
||||
const struct layer_masks *masks)
|
||||
{
|
||||
for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
|
||||
if (masks->layers[i].access & *access_request) {
|
||||
*access_request &= masks->layers[i].access;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Not found - fall back to default values */
|
||||
*access_request = 0;
|
||||
return domain->num_layers - 1;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static void test_get_denied_layer(struct kunit *const test)
|
||||
{
|
||||
const struct landlock_domain dom = {
|
||||
.num_layers = 5,
|
||||
};
|
||||
const struct layer_masks masks = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
|
||||
};
|
||||
access_mask_t access;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_EXECUTE;
|
||||
KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_READ_FILE;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_READ_DIR;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_WRITE_FILE;
|
||||
KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, 0);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
static size_t
|
||||
get_layer_from_deny_masks(access_mask_t *const access_request,
|
||||
const access_mask_t all_existing_optional_access,
|
||||
const deny_masks_t deny_masks,
|
||||
optional_access_t quiet_optional_accesses,
|
||||
bool *quiet)
|
||||
{
|
||||
const unsigned long access_opt = all_existing_optional_access;
|
||||
const unsigned long access_req = *access_request;
|
||||
access_mask_t missing = 0;
|
||||
size_t youngest_layer = 0;
|
||||
size_t access_index = 0;
|
||||
unsigned long access_bit;
|
||||
bool should_quiet = false;
|
||||
|
||||
/* This will require change with new object types. */
|
||||
WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
|
||||
|
||||
for_each_set_bit(access_bit, &access_opt,
|
||||
BITS_PER_TYPE(access_mask_t)) {
|
||||
if (access_req & BIT(access_bit)) {
|
||||
const size_t layer =
|
||||
(deny_masks >>
|
||||
(access_index *
|
||||
HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
|
||||
(LANDLOCK_MAX_NUM_LAYERS - 1);
|
||||
const bool layer_has_quiet =
|
||||
!!(quiet_optional_accesses & BIT(access_index));
|
||||
|
||||
if (layer > youngest_layer) {
|
||||
youngest_layer = layer;
|
||||
missing = BIT(access_bit);
|
||||
should_quiet = layer_has_quiet;
|
||||
} else if (layer == youngest_layer) {
|
||||
missing |= BIT(access_bit);
|
||||
/*
|
||||
* Whether the layer has rules with quiet flag
|
||||
* covering the file accessed does not depend on
|
||||
* the access, and so the following
|
||||
* WARN_ON_ONCE() should not fail.
|
||||
*/
|
||||
WARN_ON_ONCE(should_quiet && !layer_has_quiet);
|
||||
should_quiet = layer_has_quiet;
|
||||
}
|
||||
}
|
||||
access_index++;
|
||||
}
|
||||
|
||||
*access_request = missing;
|
||||
*quiet = should_quiet;
|
||||
return youngest_layer;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static void test_get_layer_from_deny_masks(struct kunit *const test)
|
||||
{
|
||||
deny_masks_t deny_mask;
|
||||
access_mask_t access;
|
||||
optional_access_t quiet_optional_accesses;
|
||||
bool quiet;
|
||||
|
||||
/* truncate:0 ioctl_dev:2 */
|
||||
deny_mask = 0x20;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* layer denying truncate: quiet, ioctl: not quiet */
|
||||
quiet_optional_accesses = 0b01;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* Reverse order - truncate:2 ioctl_dev:0 */
|
||||
deny_mask = 0x02;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* layer denying truncate: quiet, ioctl: not quiet */
|
||||
quiet_optional_accesses = 0b01;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
/* layer denying truncate: not quiet, ioctl: quiet */
|
||||
quiet_optional_accesses = 0b10;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* truncate:15 ioctl_dev:15 */
|
||||
deny_mask = 0xff;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* Both quiet (same layer so quietness must be the same) */
|
||||
quiet_optional_accesses = 0b11;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
static bool is_valid_request(const struct landlock_request *const request)
|
||||
{
|
||||
if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
|
||||
return false;
|
||||
|
||||
if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
|
||||
return false;
|
||||
|
||||
if (request->access) {
|
||||
if (WARN_ON_ONCE(!(!!request->layer_masks ^
|
||||
!!request->all_existing_optional_access)))
|
||||
return false;
|
||||
} else {
|
||||
if (WARN_ON_ONCE(request->layer_masks ||
|
||||
request->all_existing_optional_access))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request->deny_masks) {
|
||||
if (WARN_ON_ONCE(!request->all_existing_optional_access))
|
||||
return false;
|
||||
static_assert(sizeof(request->all_existing_optional_access) ==
|
||||
sizeof(u32));
|
||||
if (WARN_ON_ONCE(
|
||||
request->quiet_optional_accesses >=
|
||||
BIT(hweight32(
|
||||
request->all_existing_optional_access))))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static access_mask_t
|
||||
pick_access_mask_for_request_type(const enum landlock_request_type type,
|
||||
const struct access_masks access_masks)
|
||||
@@ -541,62 +153,27 @@ pick_access_mask_for_request_type(const enum landlock_request_type type,
|
||||
}
|
||||
|
||||
/**
|
||||
* landlock_log_denial - Create audit records related to a denial
|
||||
* landlock_audit_denial - Create an audit record for a denied access request
|
||||
*
|
||||
* @subject: The Landlock subject's credential denying an action.
|
||||
* @request: Detail of the user space request.
|
||||
* @youngest_denied: The youngest hierarchy node that denied the access.
|
||||
* @youngest_layer: The layer index of @youngest_denied.
|
||||
* @missing: The set of denied access rights.
|
||||
* @object_quiet_flag: Whether the object denied by @youngest_denied is
|
||||
* covered by a quiet rule in that layer.
|
||||
*
|
||||
* Called from landlock_log_denial() with the same arguments.
|
||||
*/
|
||||
void landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request)
|
||||
void landlock_audit_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request,
|
||||
struct landlock_hierarchy *const youngest_denied,
|
||||
const size_t youngest_layer,
|
||||
const access_mask_t missing,
|
||||
const bool object_quiet_flag)
|
||||
{
|
||||
struct audit_buffer *ab;
|
||||
struct landlock_hierarchy *youngest_denied;
|
||||
size_t youngest_layer;
|
||||
access_mask_t missing;
|
||||
bool object_quiet_flag = false, quiet_applicable_to_access = false;
|
||||
|
||||
if (WARN_ON_ONCE(!subject || !subject->domain ||
|
||||
!subject->domain->hierarchy || !request))
|
||||
return;
|
||||
|
||||
if (!is_valid_request(request))
|
||||
return;
|
||||
|
||||
missing = request->access;
|
||||
if (missing) {
|
||||
/* Gets the nearest domain that denies the request. */
|
||||
if (request->layer_masks) {
|
||||
youngest_layer = get_denied_layer(subject->domain,
|
||||
&missing,
|
||||
request->layer_masks);
|
||||
object_quiet_flag =
|
||||
request->layer_masks->layers[youngest_layer]
|
||||
.quiet;
|
||||
} else {
|
||||
youngest_layer = get_layer_from_deny_masks(
|
||||
&missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
request->deny_masks,
|
||||
request->quiet_optional_accesses,
|
||||
&object_quiet_flag);
|
||||
}
|
||||
youngest_denied =
|
||||
get_hierarchy(subject->domain, youngest_layer);
|
||||
} else {
|
||||
youngest_layer = request->layer_plus_one - 1;
|
||||
youngest_denied =
|
||||
get_hierarchy(subject->domain, youngest_layer);
|
||||
}
|
||||
|
||||
if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Consistently keeps track of the number of denied access requests
|
||||
* even if audit is currently disabled, or if audit rules currently
|
||||
* exclude this record type, or if landlock_restrict_self(2)'s flags
|
||||
* quiet logs.
|
||||
*/
|
||||
atomic64_inc(&youngest_denied->num_denials);
|
||||
bool quiet_applicable_to_access = false;
|
||||
|
||||
if (!audit_enabled)
|
||||
return;
|
||||
@@ -675,23 +252,19 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
}
|
||||
|
||||
/**
|
||||
* landlock_log_drop_domain - Create an audit record on domain deallocation
|
||||
* landlock_audit_free_domain - Create an audit record on domain deallocation
|
||||
*
|
||||
* @hierarchy: The domain's hierarchy being deallocated.
|
||||
*
|
||||
* Only domains which previously appeared in the audit logs are logged again.
|
||||
* This is useful to know when a domain will never show again in the audit log.
|
||||
*
|
||||
* Called in a work queue scheduled by landlock_put_domain_deferred() called by
|
||||
* hook_cred_free().
|
||||
* Called from landlock_log_free_domain().
|
||||
*/
|
||||
void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
|
||||
void landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy)
|
||||
{
|
||||
struct audit_buffer *ab;
|
||||
|
||||
if (WARN_ON_ONCE(!hierarchy))
|
||||
return;
|
||||
|
||||
if (!audit_enabled)
|
||||
return;
|
||||
|
||||
@@ -712,23 +285,3 @@ void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
|
||||
hierarchy->id, atomic64_read(&hierarchy->num_denials));
|
||||
audit_log_end(ab);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static struct kunit_case test_cases[] = {
|
||||
/* clang-format off */
|
||||
KUNIT_CASE(test_get_hierarchy),
|
||||
KUNIT_CASE(test_get_denied_layer),
|
||||
KUNIT_CASE(test_get_layer_from_deny_masks),
|
||||
{}
|
||||
/* clang-format on */
|
||||
};
|
||||
|
||||
static struct kunit_suite test_suite = {
|
||||
.name = "landlock_audit",
|
||||
.test_cases = test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(test_suite);
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
+16
-45
@@ -8,68 +8,39 @@
|
||||
#ifndef _SECURITY_LANDLOCK_AUDIT_H
|
||||
#define _SECURITY_LANDLOCK_AUDIT_H
|
||||
|
||||
#include <linux/audit.h>
|
||||
#include <linux/lsm_audit.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include "access.h"
|
||||
|
||||
struct landlock_cred_security;
|
||||
struct landlock_hierarchy;
|
||||
|
||||
enum landlock_request_type {
|
||||
LANDLOCK_REQUEST_PTRACE = 1,
|
||||
LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
|
||||
LANDLOCK_REQUEST_FS_ACCESS,
|
||||
LANDLOCK_REQUEST_NET_ACCESS,
|
||||
LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
LANDLOCK_REQUEST_SCOPE_SIGNAL,
|
||||
};
|
||||
|
||||
/*
|
||||
* We should be careful to only use a variable of this type for
|
||||
* landlock_log_denial(). This way, the compiler can remove it entirely if
|
||||
* CONFIG_AUDIT is not set.
|
||||
*/
|
||||
struct landlock_request {
|
||||
/* Mandatory fields. */
|
||||
enum landlock_request_type type;
|
||||
struct common_audit_data audit;
|
||||
|
||||
/**
|
||||
* layer_plus_one: First layer level that denies the request + 1. The
|
||||
* extra one is useful to detect uninitialized field.
|
||||
*/
|
||||
size_t layer_plus_one;
|
||||
|
||||
/* Required field for configurable access control. */
|
||||
access_mask_t access;
|
||||
|
||||
/* Required fields for requests with layer masks. */
|
||||
const struct layer_masks *layer_masks;
|
||||
|
||||
/* Required fields for requests with deny masks. */
|
||||
const access_mask_t all_existing_optional_access;
|
||||
deny_masks_t deny_masks;
|
||||
optional_access_t quiet_optional_accesses;
|
||||
};
|
||||
struct landlock_request;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
|
||||
void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy);
|
||||
void landlock_audit_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request,
|
||||
struct landlock_hierarchy *const youngest_denied,
|
||||
const size_t youngest_layer,
|
||||
const access_mask_t missing,
|
||||
const bool object_quiet_flag);
|
||||
|
||||
void landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request);
|
||||
void landlock_audit_free_domain(
|
||||
const struct landlock_hierarchy *const hierarchy);
|
||||
|
||||
#else /* CONFIG_AUDIT */
|
||||
|
||||
static inline void
|
||||
landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
|
||||
landlock_audit_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request,
|
||||
struct landlock_hierarchy *const youngest_denied,
|
||||
const size_t youngest_layer, const access_mask_t missing,
|
||||
const bool object_quiet_flag)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void
|
||||
landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request)
|
||||
landlock_audit_free_domain(const struct landlock_hierarchy *const hierarchy)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ static void hook_cred_free(struct cred *const cred)
|
||||
landlock_put_domain_deferred(dom);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
|
||||
static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
|
||||
{
|
||||
@@ -50,16 +50,16 @@ static int hook_bprm_creds_for_exec(struct linux_binprm *const bprm)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
static struct security_hook_list landlock_hooks[] __ro_after_init = {
|
||||
LSM_HOOK_INIT(cred_prepare, hook_cred_prepare),
|
||||
LSM_HOOK_INIT(cred_transfer, hook_cred_transfer),
|
||||
LSM_HOOK_INIT(cred_free, hook_cred_free),
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
LSM_HOOK_INIT(bprm_creds_for_exec, hook_bprm_creds_for_exec),
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
};
|
||||
|
||||
__init void landlock_add_cred_hooks(void)
|
||||
|
||||
@@ -36,7 +36,7 @@ struct landlock_cred_security {
|
||||
*/
|
||||
struct landlock_domain *domain;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
/**
|
||||
* @domain_exec: Bitmask identifying the domain layers that were enforced by
|
||||
* the current task's executed file (i.e. no new execve(2) since
|
||||
@@ -50,17 +50,17 @@ struct landlock_cred_security {
|
||||
* not require a current domain.
|
||||
*/
|
||||
u8 log_subdomains_off : 1;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
} __packed;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
|
||||
/* Makes sure all layer executions can be stored. */
|
||||
static_assert(BITS_PER_TYPE(typeof_member(struct landlock_cred_security,
|
||||
domain_exec)) >=
|
||||
LANDLOCK_MAX_NUM_LAYERS);
|
||||
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
static inline struct landlock_cred_security *
|
||||
landlock_cred(const struct cred *cred)
|
||||
|
||||
+10
-10
@@ -172,11 +172,11 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
||||
/* Clear the bits where the layer in the rule grants access. */
|
||||
masks->layers[layer->level - 1].access &= ~layer->access;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
/* Collect rule flags for each layer. */
|
||||
if (layer->flags.quiet)
|
||||
masks->layers[layer->level - 1].quiet = true;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||
@@ -239,16 +239,16 @@ landlock_init_layer_masks(const struct landlock_domain *const domain,
|
||||
|
||||
masks->layers[i].access = access_request & handled;
|
||||
handled_accesses |= masks->layers[i].access;
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
masks->layers[i].quiet = false;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
}
|
||||
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
|
||||
i++) {
|
||||
masks->layers[i].access = 0;
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
masks->layers[i].quiet = false;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
}
|
||||
|
||||
return handled_accesses;
|
||||
@@ -465,14 +465,14 @@ landlock_merge_ruleset(struct landlock_domain *const parent,
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
new_dom->hierarchy->quiet_masks = ruleset->quiet_masks;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
return no_free_ptr(new_dom);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
|
||||
/**
|
||||
* get_current_exe - Get the current's executable path, if any
|
||||
@@ -754,4 +754,4 @@ kunit_test_suite(test_suite);
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "access.h"
|
||||
#include "audit.h"
|
||||
#include "log.h"
|
||||
#include "ruleset.h"
|
||||
|
||||
enum landlock_log_status {
|
||||
@@ -85,7 +85,7 @@ struct landlock_hierarchy {
|
||||
*/
|
||||
refcount_t usage;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
/**
|
||||
* @log_status: Whether this domain should be logged or not. Because
|
||||
* concurrent log entries may be created at the same time, it is still
|
||||
@@ -120,10 +120,10 @@ struct landlock_hierarchy {
|
||||
* logged) if the related object is marked as quiet.
|
||||
*/
|
||||
struct access_masks quiet_masks;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
};
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
|
||||
deny_masks_t
|
||||
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
||||
@@ -146,7 +146,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
|
||||
kfree(hierarchy->details);
|
||||
}
|
||||
|
||||
#else /* CONFIG_AUDIT */
|
||||
#else /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
static inline int
|
||||
landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy)
|
||||
@@ -159,7 +159,7 @@ landlock_free_hierarchy_details(struct landlock_hierarchy *const hierarchy)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
static inline void
|
||||
landlock_get_hierarchy(struct landlock_hierarchy *const hierarchy)
|
||||
@@ -173,7 +173,7 @@ static inline void landlock_put_hierarchy(struct landlock_hierarchy *hierarchy)
|
||||
while (hierarchy && refcount_dec_and_test(&hierarchy->usage)) {
|
||||
const struct landlock_hierarchy *const freeme = hierarchy;
|
||||
|
||||
landlock_log_drop_domain(hierarchy);
|
||||
landlock_log_free_domain(hierarchy);
|
||||
landlock_free_hierarchy_details(hierarchy);
|
||||
hierarchy = hierarchy->parent;
|
||||
kfree(freeme);
|
||||
|
||||
+14
-13
@@ -43,12 +43,12 @@
|
||||
#include <uapi/linux/landlock.h>
|
||||
|
||||
#include "access.h"
|
||||
#include "audit.h"
|
||||
#include "common.h"
|
||||
#include "cred.h"
|
||||
#include "domain.h"
|
||||
#include "fs.h"
|
||||
#include "limits.h"
|
||||
#include "log.h"
|
||||
#include "object.h"
|
||||
#include "ruleset.h"
|
||||
#include "setup.h"
|
||||
@@ -933,10 +933,11 @@ jump_up:
|
||||
path_put(&walker_path);
|
||||
|
||||
/*
|
||||
* Check CONFIG_AUDIT to enable elision of log_request_parent* and
|
||||
* associated caller's stack variables thanks to dead code elimination.
|
||||
* Check CONFIG_SECURITY_LANDLOCK_LOG to enable elision of
|
||||
* log_request_parent* and associated caller's stack variables thanks to
|
||||
* dead code elimination.
|
||||
*/
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
if (!allowed_parent1 && log_request_parent1) {
|
||||
log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
|
||||
log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
|
||||
@@ -952,7 +953,7 @@ jump_up:
|
||||
log_request_parent2->access = access_masked_parent2;
|
||||
log_request_parent2->layer_masks = layer_masks_parent2;
|
||||
}
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
return allowed_parent1 && allowed_parent2;
|
||||
}
|
||||
@@ -1824,14 +1825,14 @@ static int hook_file_open(struct file *const file)
|
||||
* file access rights in the opened struct file.
|
||||
*/
|
||||
landlock_file(file)->allowed_access = allowed_access;
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
landlock_file(file)->deny_masks = landlock_get_deny_masks(
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks);
|
||||
landlock_file(file)->quiet_optional_accesses =
|
||||
landlock_get_quiet_optional_accesses(
|
||||
_LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
landlock_file(file)->deny_masks, &layer_masks);
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
if (access_mask_subset(open_access_request, allowed_access))
|
||||
return 0;
|
||||
@@ -1865,10 +1866,10 @@ static int hook_file_truncate(struct file *const file)
|
||||
},
|
||||
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
.access = LANDLOCK_ACCESS_FS_TRUNCATE,
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
.deny_masks = landlock_file(file)->deny_masks,
|
||||
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
});
|
||||
return -EACCES;
|
||||
}
|
||||
@@ -1905,10 +1906,10 @@ static int hook_file_ioctl_common(const struct file *const file,
|
||||
},
|
||||
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
.access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
.deny_masks = landlock_file(file)->deny_masks,
|
||||
.quiet_optional_accesses = landlock_file(file)->quiet_optional_accesses,
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
});
|
||||
return -EACCES;
|
||||
}
|
||||
@@ -1984,9 +1985,9 @@ static void hook_file_set_fowner(struct file *file)
|
||||
prev_tg = landlock_file(file)->fown_tg;
|
||||
landlock_file(file)->fown_subject = fown_subject;
|
||||
landlock_file(file)->fown_tg = fown_tg;
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
landlock_file(file)->fown_layer = fown_layer;
|
||||
#endif /* CONFIG_AUDIT*/
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
/* May be called in an RCU read-side critical section. */
|
||||
landlock_put_domain_deferred(prev_dom);
|
||||
|
||||
@@ -57,7 +57,7 @@ struct landlock_file_security {
|
||||
*/
|
||||
access_mask_t allowed_access;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
/**
|
||||
* @deny_masks: Domain layer levels that deny an optional access (see
|
||||
* _LANDLOCK_ACCESS_FS_OPTIONAL).
|
||||
@@ -75,7 +75,7 @@ struct landlock_file_security {
|
||||
* LANDLOCK_SCOPE_SIGNAL.
|
||||
*/
|
||||
u8 fown_layer;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
/**
|
||||
* @fown_subject: Landlock credential of the task that set the PID that
|
||||
@@ -97,7 +97,7 @@ struct landlock_file_security {
|
||||
struct pid *fown_tg;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
|
||||
/* Makes sure all layers can be identified. */
|
||||
/* clang-format off */
|
||||
@@ -113,7 +113,7 @@ static_assert(BITS_PER_TYPE(typeof_member(struct landlock_file_security,
|
||||
quiet_optional_accesses)) >=
|
||||
HWEIGHT(_LANDLOCK_ACCESS_FS_OPTIONAL));
|
||||
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
/**
|
||||
* struct landlock_superblock_security - Superblock security blob
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
#ifndef _SECURITY_LANDLOCK_ID_H
|
||||
#define _SECURITY_LANDLOCK_ID_H
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
|
||||
void __init landlock_init_id(void);
|
||||
|
||||
u64 landlock_get_id_range(size_t number_of_ids);
|
||||
|
||||
#else /* CONFIG_AUDIT */
|
||||
#else /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
static inline void __init landlock_init_id(void)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
#endif /* _SECURITY_LANDLOCK_ID_H */
|
||||
|
||||
@@ -0,0 +1,502 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Landlock - Log helpers
|
||||
*
|
||||
* Copyright © 2023-2025 Microsoft Corporation
|
||||
*/
|
||||
|
||||
#include <kunit/test.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <uapi/linux/landlock.h>
|
||||
|
||||
#include "access.h"
|
||||
#include "audit.h"
|
||||
#include "common.h"
|
||||
#include "cred.h"
|
||||
#include "domain.h"
|
||||
#include "limits.h"
|
||||
#include "log.h"
|
||||
#include "ruleset.h"
|
||||
|
||||
static struct landlock_hierarchy *
|
||||
get_hierarchy(const struct landlock_domain *const domain, const size_t layer)
|
||||
{
|
||||
struct landlock_hierarchy *hierarchy = domain->hierarchy;
|
||||
ssize_t i;
|
||||
|
||||
if (WARN_ON_ONCE(layer >= domain->num_layers))
|
||||
return hierarchy;
|
||||
|
||||
for (i = domain->num_layers - 1; i > layer; i--) {
|
||||
if (WARN_ON_ONCE(!hierarchy->parent))
|
||||
break;
|
||||
|
||||
hierarchy = hierarchy->parent;
|
||||
}
|
||||
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static void test_get_hierarchy(struct kunit *const test)
|
||||
{
|
||||
struct landlock_hierarchy dom0_hierarchy = {
|
||||
.id = 10,
|
||||
};
|
||||
struct landlock_hierarchy dom1_hierarchy = {
|
||||
.parent = &dom0_hierarchy,
|
||||
.id = 20,
|
||||
};
|
||||
struct landlock_hierarchy dom2_hierarchy = {
|
||||
.parent = &dom1_hierarchy,
|
||||
.id = 30,
|
||||
};
|
||||
struct landlock_domain dom2 = {
|
||||
.hierarchy = &dom2_hierarchy,
|
||||
.num_layers = 3,
|
||||
};
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
|
||||
KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
|
||||
KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
|
||||
/* KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); */
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
/* Get the youngest layer that denied the access_request. */
|
||||
static size_t get_denied_layer(const struct landlock_domain *const domain,
|
||||
access_mask_t *const access_request,
|
||||
const struct layer_masks *masks)
|
||||
{
|
||||
for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
|
||||
if (masks->layers[i].access & *access_request) {
|
||||
*access_request &= masks->layers[i].access;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/* Not found - fall back to default values */
|
||||
*access_request = 0;
|
||||
return domain->num_layers - 1;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static void test_get_denied_layer(struct kunit *const test)
|
||||
{
|
||||
const struct landlock_domain dom = {
|
||||
.num_layers = 5,
|
||||
};
|
||||
const struct layer_masks masks = {
|
||||
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||
.layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
|
||||
};
|
||||
access_mask_t access;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_EXECUTE;
|
||||
KUNIT_EXPECT_EQ(test, 0, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_EXECUTE);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_READ_FILE;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_FILE);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_READ_DIR;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_READ_DIR);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_DIR;
|
||||
KUNIT_EXPECT_EQ(test, 1, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_READ_DIR);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_WRITE_FILE;
|
||||
KUNIT_EXPECT_EQ(test, 4, get_denied_layer(&dom, &access, &masks));
|
||||
KUNIT_EXPECT_EQ(test, access, 0);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
static size_t
|
||||
get_layer_from_deny_masks(access_mask_t *const access_request,
|
||||
const access_mask_t all_existing_optional_access,
|
||||
const deny_masks_t deny_masks,
|
||||
optional_access_t quiet_optional_accesses,
|
||||
bool *quiet)
|
||||
{
|
||||
const unsigned long access_opt = all_existing_optional_access;
|
||||
const unsigned long access_req = *access_request;
|
||||
access_mask_t missing = 0;
|
||||
size_t youngest_layer = 0;
|
||||
size_t access_index = 0;
|
||||
unsigned long access_bit;
|
||||
bool should_quiet = false;
|
||||
|
||||
/* This will require change with new object types. */
|
||||
WARN_ON_ONCE(access_opt != _LANDLOCK_ACCESS_FS_OPTIONAL);
|
||||
|
||||
for_each_set_bit(access_bit, &access_opt,
|
||||
BITS_PER_TYPE(access_mask_t)) {
|
||||
if (access_req & BIT(access_bit)) {
|
||||
const size_t layer =
|
||||
(deny_masks >>
|
||||
(access_index *
|
||||
HWEIGHT(LANDLOCK_MAX_NUM_LAYERS - 1))) &
|
||||
(LANDLOCK_MAX_NUM_LAYERS - 1);
|
||||
const bool layer_has_quiet =
|
||||
!!(quiet_optional_accesses & BIT(access_index));
|
||||
|
||||
if (layer > youngest_layer) {
|
||||
youngest_layer = layer;
|
||||
missing = BIT(access_bit);
|
||||
should_quiet = layer_has_quiet;
|
||||
} else if (layer == youngest_layer) {
|
||||
missing |= BIT(access_bit);
|
||||
/*
|
||||
* Whether the layer has rules with quiet flag
|
||||
* covering the file accessed does not depend on
|
||||
* the access, and so the following
|
||||
* WARN_ON_ONCE() should not fail.
|
||||
*/
|
||||
WARN_ON_ONCE(should_quiet && !layer_has_quiet);
|
||||
should_quiet = layer_has_quiet;
|
||||
}
|
||||
}
|
||||
access_index++;
|
||||
}
|
||||
|
||||
*access_request = missing;
|
||||
*quiet = should_quiet;
|
||||
return youngest_layer;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static void test_get_layer_from_deny_masks(struct kunit *const test)
|
||||
{
|
||||
deny_masks_t deny_mask;
|
||||
access_mask_t access;
|
||||
optional_access_t quiet_optional_accesses;
|
||||
bool quiet;
|
||||
|
||||
/* truncate:0 ioctl_dev:2 */
|
||||
deny_mask = 0x20;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* layer denying truncate: quiet, ioctl: not quiet */
|
||||
quiet_optional_accesses = 0b01;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* Reverse order - truncate:2 ioctl_dev:0 */
|
||||
deny_mask = 0x02;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* layer denying truncate: quiet, ioctl: not quiet */
|
||||
quiet_optional_accesses = 0b01;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
/* layer denying truncate: not quiet, ioctl: quiet */
|
||||
quiet_optional_accesses = 0b10;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 0,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 2,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* truncate:15 ioctl_dev:15 */
|
||||
deny_mask = 0xff;
|
||||
quiet_optional_accesses = 0;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, false);
|
||||
|
||||
/* Both quiet (same layer so quietness must be the same) */
|
||||
quiet_optional_accesses = 0b11;
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access, LANDLOCK_ACCESS_FS_TRUNCATE);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
|
||||
access = LANDLOCK_ACCESS_FS_TRUNCATE | LANDLOCK_ACCESS_FS_IOCTL_DEV;
|
||||
KUNIT_EXPECT_EQ(test, 15,
|
||||
get_layer_from_deny_masks(
|
||||
&access, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
deny_mask, quiet_optional_accesses, &quiet));
|
||||
KUNIT_EXPECT_EQ(test, access,
|
||||
LANDLOCK_ACCESS_FS_TRUNCATE |
|
||||
LANDLOCK_ACCESS_FS_IOCTL_DEV);
|
||||
KUNIT_EXPECT_EQ(test, quiet, true);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
|
||||
static bool is_valid_request(const struct landlock_request *const request)
|
||||
{
|
||||
if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
|
||||
return false;
|
||||
|
||||
if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access)))
|
||||
return false;
|
||||
|
||||
if (request->access) {
|
||||
if (WARN_ON_ONCE(!(!!request->layer_masks ^
|
||||
!!request->all_existing_optional_access)))
|
||||
return false;
|
||||
} else {
|
||||
if (WARN_ON_ONCE(request->layer_masks ||
|
||||
request->all_existing_optional_access))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request->deny_masks) {
|
||||
if (WARN_ON_ONCE(!request->all_existing_optional_access))
|
||||
return false;
|
||||
static_assert(sizeof(request->all_existing_optional_access) ==
|
||||
sizeof(u32));
|
||||
if (WARN_ON_ONCE(
|
||||
request->quiet_optional_accesses >=
|
||||
BIT(hweight32(
|
||||
request->all_existing_optional_access))))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* landlock_log_denial - Log a denied access
|
||||
*
|
||||
* @subject: The Landlock subject's credential denying an action.
|
||||
* @request: Detail of the user space request.
|
||||
*/
|
||||
void landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request)
|
||||
{
|
||||
struct landlock_hierarchy *youngest_denied;
|
||||
size_t youngest_layer;
|
||||
access_mask_t missing;
|
||||
bool object_quiet_flag = false;
|
||||
|
||||
if (WARN_ON_ONCE(!subject || !subject->domain ||
|
||||
!subject->domain->hierarchy || !request))
|
||||
return;
|
||||
|
||||
if (!is_valid_request(request))
|
||||
return;
|
||||
|
||||
missing = request->access;
|
||||
if (missing) {
|
||||
/* Gets the nearest domain that denies the request. */
|
||||
if (request->layer_masks) {
|
||||
youngest_layer = get_denied_layer(subject->domain,
|
||||
&missing,
|
||||
request->layer_masks);
|
||||
object_quiet_flag =
|
||||
request->layer_masks->layers[youngest_layer]
|
||||
.quiet;
|
||||
} else {
|
||||
youngest_layer = get_layer_from_deny_masks(
|
||||
&missing, _LANDLOCK_ACCESS_FS_OPTIONAL,
|
||||
request->deny_masks,
|
||||
request->quiet_optional_accesses,
|
||||
&object_quiet_flag);
|
||||
}
|
||||
youngest_denied =
|
||||
get_hierarchy(subject->domain, youngest_layer);
|
||||
} else {
|
||||
youngest_layer = request->layer_plus_one - 1;
|
||||
youngest_denied =
|
||||
get_hierarchy(subject->domain, youngest_layer);
|
||||
}
|
||||
|
||||
if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Consistently keeps track of the number of denied access requests even
|
||||
* if audit is currently disabled, or if audit rules currently exclude
|
||||
* this record type, or if landlock_restrict_self(2)'s flags quiet logs.
|
||||
*/
|
||||
atomic64_inc(&youngest_denied->num_denials);
|
||||
|
||||
landlock_audit_denial(subject, request, youngest_denied, youngest_layer,
|
||||
missing, object_quiet_flag);
|
||||
}
|
||||
|
||||
/**
|
||||
* landlock_log_free_domain - Log domain deallocation
|
||||
*
|
||||
* @hierarchy: The domain's hierarchy being deallocated.
|
||||
*
|
||||
* Called in a work queue scheduled by landlock_put_domain_deferred() called by
|
||||
* hook_cred_free().
|
||||
*/
|
||||
void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy)
|
||||
{
|
||||
if (WARN_ON_ONCE(!hierarchy))
|
||||
return;
|
||||
|
||||
landlock_audit_free_domain(hierarchy);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
|
||||
|
||||
static struct kunit_case test_cases[] = {
|
||||
/* clang-format off */
|
||||
KUNIT_CASE(test_get_hierarchy),
|
||||
KUNIT_CASE(test_get_denied_layer),
|
||||
KUNIT_CASE(test_get_layer_from_deny_masks),
|
||||
{}
|
||||
/* clang-format on */
|
||||
};
|
||||
|
||||
static struct kunit_suite test_suite = {
|
||||
.name = "landlock_log",
|
||||
.test_cases = test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(test_suite);
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||
@@ -0,0 +1,77 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Landlock - Log helpers
|
||||
*
|
||||
* Copyright © 2023-2025 Microsoft Corporation
|
||||
*/
|
||||
|
||||
#ifndef _SECURITY_LANDLOCK_LOG_H
|
||||
#define _SECURITY_LANDLOCK_LOG_H
|
||||
|
||||
#include <linux/lsm_audit.h>
|
||||
|
||||
#include "access.h"
|
||||
|
||||
struct landlock_cred_security;
|
||||
struct landlock_hierarchy;
|
||||
|
||||
enum landlock_request_type {
|
||||
LANDLOCK_REQUEST_PTRACE = 1,
|
||||
LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
|
||||
LANDLOCK_REQUEST_FS_ACCESS,
|
||||
LANDLOCK_REQUEST_NET_ACCESS,
|
||||
LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET,
|
||||
LANDLOCK_REQUEST_SCOPE_SIGNAL,
|
||||
};
|
||||
|
||||
/*
|
||||
* We should be careful to only use a variable of this type for
|
||||
* landlock_log_denial(). This way, the compiler can remove it entirely if
|
||||
* CONFIG_SECURITY_LANDLOCK_LOG is not set.
|
||||
*/
|
||||
struct landlock_request {
|
||||
/* Mandatory fields. */
|
||||
enum landlock_request_type type;
|
||||
struct common_audit_data audit;
|
||||
|
||||
/**
|
||||
* layer_plus_one: First layer level that denies the request + 1. The
|
||||
* extra one is useful to detect uninitialized field.
|
||||
*/
|
||||
size_t layer_plus_one;
|
||||
|
||||
/* Required field for configurable access control. */
|
||||
access_mask_t access;
|
||||
|
||||
/* Required fields for requests with layer masks. */
|
||||
const struct layer_masks *layer_masks;
|
||||
|
||||
/* Required fields for requests with deny masks. */
|
||||
const access_mask_t all_existing_optional_access;
|
||||
deny_masks_t deny_masks;
|
||||
optional_access_t quiet_optional_accesses;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
|
||||
void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy);
|
||||
|
||||
void landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request);
|
||||
|
||||
#else /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
static inline void
|
||||
landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void
|
||||
landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request)
|
||||
{
|
||||
}
|
||||
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
#endif /* _SECURITY_LANDLOCK_LOG_H */
|
||||
@@ -12,11 +12,11 @@
|
||||
#include <linux/socket.h>
|
||||
#include <net/ipv6.h>
|
||||
|
||||
#include "audit.h"
|
||||
#include "common.h"
|
||||
#include "cred.h"
|
||||
#include "domain.h"
|
||||
#include "limits.h"
|
||||
#include "log.h"
|
||||
#include "net.h"
|
||||
#include "ruleset.h"
|
||||
|
||||
|
||||
@@ -583,11 +583,11 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
|
||||
|
||||
new_llcred = landlock_cred(new_cred);
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
prev_log_subdomains = !new_llcred->log_subdomains_off;
|
||||
new_llcred->log_subdomains_off = !prev_log_subdomains ||
|
||||
!log_subdomains;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
/*
|
||||
* The only case when a ruleset may not be set is if
|
||||
@@ -609,20 +609,20 @@ SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
|
||||
return PTR_ERR(new_dom);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
new_dom->hierarchy->log_same_exec = log_same_exec;
|
||||
new_dom->hierarchy->log_new_exec = log_new_exec;
|
||||
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
|
||||
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
|
||||
/* Replaces the old (prepared) domain. */
|
||||
landlock_put_domain(new_llcred->domain);
|
||||
new_llcred->domain = new_dom;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
}
|
||||
|
||||
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
#include <net/af_unix.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
#include "audit.h"
|
||||
#include "common.h"
|
||||
#include "cred.h"
|
||||
#include "domain.h"
|
||||
#include "fs.h"
|
||||
#include "log.h"
|
||||
#include "ruleset.h"
|
||||
#include "setup.h"
|
||||
#include "task.h"
|
||||
@@ -446,9 +446,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk,
|
||||
.type = LSM_AUDIT_DATA_TASK,
|
||||
.u.tsk = tsk,
|
||||
},
|
||||
#ifdef CONFIG_AUDIT
|
||||
#ifdef CONFIG_SECURITY_LANDLOCK_LOG
|
||||
.layer_plus_one = landlock_file(fown->file)->fown_layer + 1,
|
||||
#endif /* CONFIG_AUDIT */
|
||||
#endif /* CONFIG_SECURITY_LANDLOCK_LOG */
|
||||
});
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user