mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-09-18 23:09:29 +02:00
Add per-type tracepoints emitted from landlock_log_denial() when an access is denied: landlock_deny_access_fs for filesystem denials and landlock_deny_access_net for network denials. They use the "deny_" prefix (rather than "check_") to mark that they fire only on a denial, and they complement the check_rule events by making the denial-by-absence case explicit (when no rule matches, no check_rule event fires). Unlike the audit records, these events fire regardless of the audit configuration and the domain's log flags: the user's "disable logging" intent applies to audit records, not to kernel tracing. The logged field records whether the domain's log policy would submit the denial to audit; it is the decision computed once by landlock_log_denial() and passed to both the audit and the tracing emitter, so a stateless ftrace filter can select the audit-visible denials with logged==1. TP_PROTO passes the denying hierarchy node, not the task's current domain, so domain_id reports the specific node that blocked the access, matching audit record semantics. (check_rule instead passes the current domain, which it needs to size its per-layer array.) same_exec is also passed explicitly because it is computed from the credential bitmask and is not derivable from the hierarchy pointer alone. The denial field is named blockers to match the audit record field. The filesystem path comes from the request's audit data. Its type selects which union member holds the object, exactly as dump_common_audit_data() selects it (a path, a file's path, an ioctl op's path, or a bare dentry); reading the wrong member would dereference garbage, so every reachable type has an explicit case and an unexpected one is flagged with WARN_ONCE() instead of misread. Path-backed types resolve via d_absolute_path() (as landlock_add_rule_fs does) and the bare-dentry case via dentry_path_raw(). The inode number is read defensively. A filesystem denial can carry a negative dentry (no backing inode), for example a denied creation, so the event mirrors the guard in dump_common_audit_data() and reports inode 0 rather than dereferencing a NULL inode. The sibling fs tracepoints do not need the guard: a dentry that matches a rule during an access check, or one opened to add a rule, always has a backing inode. Landlock tracepoints are reachable by unprivileged sandboxees, so a denial on a negative dentry with the event enabled must not fault the kernel. Cc: Günther Noack <gnoack@google.com> Cc: Justin Suess <utilityemal77@gmail.com> Cc: Masami Hiramatsu <mhiramat@kernel.org> Cc: Mathieu Desnoyers <mathieu.desnoyers@efficios.com> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tingmao Wang <m@maowtm.org> Link: https://patch.msgid.link/20260811094338.288094-13-mic@digikod.net Signed-off-by: Mickaël Salaün <mic@digikod.net>
588 lines
18 KiB
C
588 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Landlock - Log helpers
|
|
*
|
|
* Copyright © 2023-2025 Microsoft Corporation
|
|
* Copyright © 2026 Cloudflare, Inc.
|
|
*/
|
|
|
|
#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"
|
|
#include "trace.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;
|
|
}
|
|
|
|
static access_mask_t
|
|
pick_access_mask_for_request_type(const enum landlock_request_type type,
|
|
const struct access_masks access_masks)
|
|
{
|
|
switch (type) {
|
|
case LANDLOCK_REQUEST_FS_ACCESS:
|
|
return access_masks.fs;
|
|
case LANDLOCK_REQUEST_NET_ACCESS:
|
|
return access_masks.net;
|
|
default:
|
|
WARN_ONCE(1, "Invalid request type %d passed to %s", type,
|
|
__func__);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Whether a quiet rule silences the denial: the rule must cover the whole
|
|
* denied access in the layer that denied it (a quiet rule in a non-denying
|
|
* layer does not suppress the denial).
|
|
*/
|
|
static bool
|
|
is_denial_quieted(const struct landlock_request *const request,
|
|
const struct landlock_hierarchy *const youngest_denied,
|
|
const access_mask_t missing, const bool object_quiet_flag)
|
|
{
|
|
if (object_quiet_flag) {
|
|
const access_mask_t quiet_mask =
|
|
pick_access_mask_for_request_type(
|
|
request->type, youngest_denied->quiet_masks);
|
|
|
|
return (quiet_mask & missing) == missing;
|
|
}
|
|
|
|
/*
|
|
* Either the object is not quiet, or this is a scope request. We check
|
|
* request->type to distinguish between the two cases.
|
|
*/
|
|
switch (request->type) {
|
|
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
|
|
return !!(youngest_denied->quiet_masks.scope &
|
|
LANDLOCK_SCOPE_SIGNAL);
|
|
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
|
|
return !!(youngest_denied->quiet_masks.scope &
|
|
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
|
/*
|
|
* Leave LANDLOCK_REQUEST_PTRACE and LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY
|
|
* unhandled for now - they are never quiet.
|
|
*/
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Computes whether a denial from youngest_denied is selected for logging by the
|
|
* domain's policy: its logging must not be disabled (by both per-execution
|
|
* flags being off, or by an ancestor's
|
|
* LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF), the per-execution flag matching
|
|
* same_exec must be set, and no quiet rule may cover the denied access.
|
|
* landlock_log_denial() computes this once and passes it to
|
|
* landlock_audit_denial(), which additionally requires audit_enabled.
|
|
*/
|
|
static bool
|
|
is_denial_logged(const struct landlock_request *const request,
|
|
const struct landlock_hierarchy *const youngest_denied,
|
|
const access_mask_t missing, const bool same_exec,
|
|
const bool object_quiet_flag)
|
|
{
|
|
if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
|
|
return false;
|
|
|
|
if (!(same_exec ? youngest_denied->log_same_exec :
|
|
youngest_denied->log_new_exec))
|
|
return false;
|
|
|
|
return !is_denial_quieted(request, youngest_denied, missing,
|
|
object_quiet_flag);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
const bool same_exec = !!(subject->domain_exec & BIT(youngest_layer));
|
|
const bool logged = is_denial_logged(request, youngest_denied, missing,
|
|
same_exec, object_quiet_flag);
|
|
|
|
/*
|
|
* 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_trace_denial(request, youngest_denied, missing, same_exec,
|
|
logged);
|
|
landlock_audit_denial(request, youngest_denied, missing, logged);
|
|
}
|
|
|
|
/**
|
|
* landlock_log_free_domain - Log domain deallocation
|
|
*
|
|
* @hierarchy: The domain's hierarchy being deallocated.
|
|
*
|
|
* Called from landlock_put_domain_deferred() (via a work queue scheduled by
|
|
* hook_cred_free()) or directly from landlock_put_domain().
|
|
*/
|
|
void landlock_log_free_domain(const struct landlock_hierarchy *const hierarchy)
|
|
{
|
|
if (WARN_ON_ONCE(!hierarchy))
|
|
return;
|
|
|
|
landlock_trace_free_domain(hierarchy);
|
|
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 */
|