mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 12:13:51 -04:00
landlock: Decouple the per-denial logging decision from CONFIG_AUDIT
Until now, whether a denial is logged was decided inside landlock_audit_denial(): a per-execution flag check (log_same_exec or log_new_exec, selected by the credential's domain_exec bitmask), preceded by a LANDLOCK_LOG_DISABLED early return in landlock_log_denial() for domains an ancestor fully quieted. Factor that decision into a single is_denial_logged() helper called once by landlock_log_denial(), and pass its result to landlock_audit_denial() as a "logged" boolean. A following commit passes the same boolean to the deny tracepoints, so audit and tracing share one decision that stays correct as new log state is added, and a tracepoints-only build (CONFIG_AUDIT=n) computes it identically. Computing the logged verdict once in the shared helper makes audit and tracing apply identical filtering, so they cannot report different logged= values for the same denial as log controls grow. Move the LANDLOCK_LOG_DISABLED gate out of landlock_log_denial() into the decision so num_denials counts every denial, including those a domain quiets. This was previously masked: the only reader of num_denials is the audit "domain deallocated" record, emitted only for domains that reached LANDLOCK_LOG_RECORDED; a fully quieted domain never records, so its undercount was never observable. A following commit adds a free_domain tracepoint that reports num_denials, which needs the full count. This is not a functional change for audit: the logged decision and the audit_enabled gate are preserved, so the emitted records are identical. Cc: Günther Noack <gnoack@google.com> Link: https://patch.msgid.link/20260811094338.288094-6-mic@digikod.net Reviewed-by: Tingmao Wang <m@maowtm.org> [mic: Update copyright] Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
@@ -136,104 +136,32 @@ static void log_domain(struct landlock_hierarchy *const hierarchy)
|
||||
WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @logged: Whether the denial is selected for logging, as computed by
|
||||
* landlock_log_denial() (domain policy and quiet rules).
|
||||
*
|
||||
* Called from landlock_log_denial() with the same arguments.
|
||||
* Emits the record when audit is enabled and the denial is selected for
|
||||
* logging.
|
||||
*/
|
||||
void landlock_audit_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request,
|
||||
void landlock_audit_denial(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)
|
||||
const access_mask_t missing, const bool logged)
|
||||
{
|
||||
struct audit_buffer *ab;
|
||||
bool quiet_applicable_to_access = false;
|
||||
|
||||
if (!audit_enabled)
|
||||
return;
|
||||
|
||||
/* Checks if the current exec was restricting itself. */
|
||||
if (subject->domain_exec & BIT(youngest_layer)) {
|
||||
/* Ignores denials for the same execution. */
|
||||
if (!youngest_denied->log_same_exec)
|
||||
return;
|
||||
} else {
|
||||
/* Ignores denials after a new execution. */
|
||||
if (!youngest_denied->log_new_exec)
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if the object is marked quiet by the layer that denied the
|
||||
* request. If it's a different layer that marked it as quiet, but that
|
||||
* layer is not the one that denied the request, we should still audit
|
||||
* log the denial.
|
||||
* Skips denials the domain's policy or a quiet rule excludes from
|
||||
* logging (folded into @logged by landlock_log_denial()).
|
||||
*/
|
||||
if (object_quiet_flag) {
|
||||
/*
|
||||
* We now check if the denied requests are all covered by the
|
||||
* layer's quiet access bits.
|
||||
*/
|
||||
const access_mask_t quiet_mask =
|
||||
pick_access_mask_for_request_type(
|
||||
request->type, youngest_denied->quiet_masks);
|
||||
|
||||
quiet_applicable_to_access = (quiet_mask & missing) == missing;
|
||||
} else {
|
||||
/*
|
||||
* Either the object is not quiet, or this is a scope request.
|
||||
* We check request->type to distinguish between the two cases.
|
||||
*/
|
||||
const access_mask_t quiet_mask =
|
||||
youngest_denied->quiet_masks.scope;
|
||||
|
||||
switch (request->type) {
|
||||
case LANDLOCK_REQUEST_SCOPE_SIGNAL:
|
||||
quiet_applicable_to_access =
|
||||
!!(quiet_mask & LANDLOCK_SCOPE_SIGNAL);
|
||||
break;
|
||||
case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET:
|
||||
quiet_applicable_to_access =
|
||||
!!(quiet_mask &
|
||||
LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET);
|
||||
break;
|
||||
/*
|
||||
* Leave LANDLOCK_REQUEST_PTRACE and
|
||||
* LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY unhandled for now - they
|
||||
* are never quiet.
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (quiet_applicable_to_access)
|
||||
if (!logged)
|
||||
return;
|
||||
|
||||
/* Uses consistent allocation flags wrt common_lsm_audit(). */
|
||||
|
||||
@@ -12,18 +12,14 @@
|
||||
|
||||
#include "access.h"
|
||||
|
||||
struct landlock_cred_security;
|
||||
struct landlock_hierarchy;
|
||||
struct landlock_request;
|
||||
|
||||
#ifdef CONFIG_AUDIT
|
||||
|
||||
void landlock_audit_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request,
|
||||
void landlock_audit_denial(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);
|
||||
const access_mask_t missing, const bool logged);
|
||||
|
||||
void landlock_audit_free_domain(
|
||||
const struct landlock_hierarchy *const hierarchy);
|
||||
@@ -31,11 +27,9 @@ void landlock_audit_free_domain(
|
||||
#else /* CONFIG_AUDIT */
|
||||
|
||||
static inline void
|
||||
landlock_audit_denial(const struct landlock_cred_security *const subject,
|
||||
const struct landlock_request *const request,
|
||||
landlock_audit_denial(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)
|
||||
const access_mask_t missing, const bool logged)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Landlock - Log helpers
|
||||
*
|
||||
* Copyright © 2023-2025 Microsoft Corporation
|
||||
* Copyright © 2026 Cloudflare, Inc.
|
||||
*/
|
||||
|
||||
#include <kunit/test.h>
|
||||
@@ -405,6 +406,86 @@ static bool is_valid_request(const struct landlock_request *const request)
|
||||
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
|
||||
*
|
||||
@@ -451,8 +532,9 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
get_hierarchy(subject->domain, youngest_layer);
|
||||
}
|
||||
|
||||
if (READ_ONCE(youngest_denied->log_status) == LANDLOCK_LOG_DISABLED)
|
||||
return;
|
||||
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
|
||||
@@ -461,8 +543,7 @@ void landlock_log_denial(const struct landlock_cred_security *const subject,
|
||||
*/
|
||||
atomic64_inc(&youngest_denied->num_denials);
|
||||
|
||||
landlock_audit_denial(subject, request, youngest_denied, youngest_layer,
|
||||
missing, object_quiet_flag);
|
||||
landlock_audit_denial(request, youngest_denied, missing, logged);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user