xref: /linux/security/landlock/audit.c (revision 1d636984e088b17e8587eb5ed9d9d7a80b656c4c)
133e65b0dSMickaël Salaün // SPDX-License-Identifier: GPL-2.0-only
233e65b0dSMickaël Salaün /*
333e65b0dSMickaël Salaün  * Landlock - Audit helpers
433e65b0dSMickaël Salaün  *
533e65b0dSMickaël Salaün  * Copyright © 2023-2025 Microsoft Corporation
633e65b0dSMickaël Salaün  */
733e65b0dSMickaël Salaün 
833e65b0dSMickaël Salaün #include <kunit/test.h>
933e65b0dSMickaël Salaün #include <linux/audit.h>
1033e65b0dSMickaël Salaün #include <linux/lsm_audit.h>
11*1d636984SMickaël Salaün #include <linux/pid.h>
1233e65b0dSMickaël Salaün 
1333e65b0dSMickaël Salaün #include "audit.h"
1433e65b0dSMickaël Salaün #include "cred.h"
1533e65b0dSMickaël Salaün #include "domain.h"
1633e65b0dSMickaël Salaün #include "limits.h"
1733e65b0dSMickaël Salaün #include "ruleset.h"
1833e65b0dSMickaël Salaün 
1933e65b0dSMickaël Salaün static const char *get_blocker(const enum landlock_request_type type)
2033e65b0dSMickaël Salaün {
2133e65b0dSMickaël Salaün 	switch (type) {
2233e65b0dSMickaël Salaün 	case LANDLOCK_REQUEST_PTRACE:
2333e65b0dSMickaël Salaün 		return "ptrace";
2433e65b0dSMickaël Salaün 	}
2533e65b0dSMickaël Salaün 
2633e65b0dSMickaël Salaün 	WARN_ON_ONCE(1);
2733e65b0dSMickaël Salaün 	return "unknown";
2833e65b0dSMickaël Salaün }
2933e65b0dSMickaël Salaün 
3033e65b0dSMickaël Salaün static void log_blockers(struct audit_buffer *const ab,
3133e65b0dSMickaël Salaün 			 const enum landlock_request_type type)
3233e65b0dSMickaël Salaün {
3333e65b0dSMickaël Salaün 	audit_log_format(ab, "%s", get_blocker(type));
3433e65b0dSMickaël Salaün }
3533e65b0dSMickaël Salaün 
36*1d636984SMickaël Salaün static void log_domain(struct landlock_hierarchy *const hierarchy)
37*1d636984SMickaël Salaün {
38*1d636984SMickaël Salaün 	struct audit_buffer *ab;
39*1d636984SMickaël Salaün 
40*1d636984SMickaël Salaün 	/* Ignores already logged domains.  */
41*1d636984SMickaël Salaün 	if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED)
42*1d636984SMickaël Salaün 		return;
43*1d636984SMickaël Salaün 
44*1d636984SMickaël Salaün 	/* Uses consistent allocation flags wrt common_lsm_audit(). */
45*1d636984SMickaël Salaün 	ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
46*1d636984SMickaël Salaün 			     AUDIT_LANDLOCK_DOMAIN);
47*1d636984SMickaël Salaün 	if (!ab)
48*1d636984SMickaël Salaün 		return;
49*1d636984SMickaël Salaün 
50*1d636984SMickaël Salaün 	WARN_ON_ONCE(hierarchy->id == 0);
51*1d636984SMickaël Salaün 	audit_log_format(
52*1d636984SMickaël Salaün 		ab,
53*1d636984SMickaël Salaün 		"domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=",
54*1d636984SMickaël Salaün 		hierarchy->id, pid_nr(hierarchy->details->pid),
55*1d636984SMickaël Salaün 		hierarchy->details->uid);
56*1d636984SMickaël Salaün 	audit_log_untrustedstring(ab, hierarchy->details->exe_path);
57*1d636984SMickaël Salaün 	audit_log_format(ab, " comm=");
58*1d636984SMickaël Salaün 	audit_log_untrustedstring(ab, hierarchy->details->comm);
59*1d636984SMickaël Salaün 	audit_log_end(ab);
60*1d636984SMickaël Salaün 
61*1d636984SMickaël Salaün 	/*
62*1d636984SMickaël Salaün 	 * There may be race condition leading to logging of the same domain
63*1d636984SMickaël Salaün 	 * several times but that is OK.
64*1d636984SMickaël Salaün 	 */
65*1d636984SMickaël Salaün 	WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED);
66*1d636984SMickaël Salaün }
67*1d636984SMickaël Salaün 
6833e65b0dSMickaël Salaün static struct landlock_hierarchy *
6933e65b0dSMickaël Salaün get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
7033e65b0dSMickaël Salaün {
7133e65b0dSMickaël Salaün 	struct landlock_hierarchy *hierarchy = domain->hierarchy;
7233e65b0dSMickaël Salaün 	ssize_t i;
7333e65b0dSMickaël Salaün 
7433e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(layer >= domain->num_layers))
7533e65b0dSMickaël Salaün 		return hierarchy;
7633e65b0dSMickaël Salaün 
7733e65b0dSMickaël Salaün 	for (i = domain->num_layers - 1; i > layer; i--) {
7833e65b0dSMickaël Salaün 		if (WARN_ON_ONCE(!hierarchy->parent))
7933e65b0dSMickaël Salaün 			break;
8033e65b0dSMickaël Salaün 
8133e65b0dSMickaël Salaün 		hierarchy = hierarchy->parent;
8233e65b0dSMickaël Salaün 	}
8333e65b0dSMickaël Salaün 
8433e65b0dSMickaël Salaün 	return hierarchy;
8533e65b0dSMickaël Salaün }
8633e65b0dSMickaël Salaün 
8733e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
8833e65b0dSMickaël Salaün 
8933e65b0dSMickaël Salaün static void test_get_hierarchy(struct kunit *const test)
9033e65b0dSMickaël Salaün {
9133e65b0dSMickaël Salaün 	struct landlock_hierarchy dom0_hierarchy = {
9233e65b0dSMickaël Salaün 		.id = 10,
9333e65b0dSMickaël Salaün 	};
9433e65b0dSMickaël Salaün 	struct landlock_hierarchy dom1_hierarchy = {
9533e65b0dSMickaël Salaün 		.parent = &dom0_hierarchy,
9633e65b0dSMickaël Salaün 		.id = 20,
9733e65b0dSMickaël Salaün 	};
9833e65b0dSMickaël Salaün 	struct landlock_hierarchy dom2_hierarchy = {
9933e65b0dSMickaël Salaün 		.parent = &dom1_hierarchy,
10033e65b0dSMickaël Salaün 		.id = 30,
10133e65b0dSMickaël Salaün 	};
10233e65b0dSMickaël Salaün 	struct landlock_ruleset dom2 = {
10333e65b0dSMickaël Salaün 		.hierarchy = &dom2_hierarchy,
10433e65b0dSMickaël Salaün 		.num_layers = 3,
10533e65b0dSMickaël Salaün 	};
10633e65b0dSMickaël Salaün 
10733e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
10833e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
10933e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
11033e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id);
11133e65b0dSMickaël Salaün }
11233e65b0dSMickaël Salaün 
11333e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
11433e65b0dSMickaël Salaün 
11533e65b0dSMickaël Salaün static bool is_valid_request(const struct landlock_request *const request)
11633e65b0dSMickaël Salaün {
11733e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
11833e65b0dSMickaël Salaün 		return false;
11933e65b0dSMickaël Salaün 
12033e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(!request->layer_plus_one))
12133e65b0dSMickaël Salaün 		return false;
12233e65b0dSMickaël Salaün 
12333e65b0dSMickaël Salaün 	return true;
12433e65b0dSMickaël Salaün }
12533e65b0dSMickaël Salaün 
12633e65b0dSMickaël Salaün /**
12733e65b0dSMickaël Salaün  * landlock_log_denial - Create audit records related to a denial
12833e65b0dSMickaël Salaün  *
12933e65b0dSMickaël Salaün  * @subject: The Landlock subject's credential denying an action.
13033e65b0dSMickaël Salaün  * @request: Detail of the user space request.
13133e65b0dSMickaël Salaün  */
13233e65b0dSMickaël Salaün void landlock_log_denial(const struct landlock_cred_security *const subject,
13333e65b0dSMickaël Salaün 			 const struct landlock_request *const request)
13433e65b0dSMickaël Salaün {
13533e65b0dSMickaël Salaün 	struct audit_buffer *ab;
13633e65b0dSMickaël Salaün 	struct landlock_hierarchy *youngest_denied;
13733e65b0dSMickaël Salaün 	size_t youngest_layer;
13833e65b0dSMickaël Salaün 
13933e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(!subject || !subject->domain ||
14033e65b0dSMickaël Salaün 			 !subject->domain->hierarchy || !request))
14133e65b0dSMickaël Salaün 		return;
14233e65b0dSMickaël Salaün 
14333e65b0dSMickaël Salaün 	if (!is_valid_request(request))
14433e65b0dSMickaël Salaün 		return;
14533e65b0dSMickaël Salaün 
14633e65b0dSMickaël Salaün 	youngest_layer = request->layer_plus_one - 1;
14733e65b0dSMickaël Salaün 	youngest_denied = get_hierarchy(subject->domain, youngest_layer);
14833e65b0dSMickaël Salaün 
149*1d636984SMickaël Salaün 	/*
150*1d636984SMickaël Salaün 	 * Consistently keeps track of the number of denied access requests
151*1d636984SMickaël Salaün 	 * even if audit is currently disabled, or if audit rules currently
152*1d636984SMickaël Salaün 	 * exclude this record type, or if landlock_restrict_self(2)'s flags
153*1d636984SMickaël Salaün 	 * quiet logs.
154*1d636984SMickaël Salaün 	 */
155*1d636984SMickaël Salaün 	atomic64_inc(&youngest_denied->num_denials);
156*1d636984SMickaël Salaün 
157*1d636984SMickaël Salaün 	if (!audit_enabled)
158*1d636984SMickaël Salaün 		return;
159*1d636984SMickaël Salaün 
16033e65b0dSMickaël Salaün 	/* Ignores denials after an execution. */
16133e65b0dSMickaël Salaün 	if (!(subject->domain_exec & (1 << youngest_layer)))
16233e65b0dSMickaël Salaün 		return;
16333e65b0dSMickaël Salaün 
16433e65b0dSMickaël Salaün 	/* Uses consistent allocation flags wrt common_lsm_audit(). */
16533e65b0dSMickaël Salaün 	ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
16633e65b0dSMickaël Salaün 			     AUDIT_LANDLOCK_ACCESS);
16733e65b0dSMickaël Salaün 	if (!ab)
16833e65b0dSMickaël Salaün 		return;
16933e65b0dSMickaël Salaün 
17033e65b0dSMickaël Salaün 	audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
17133e65b0dSMickaël Salaün 	log_blockers(ab, request->type);
17233e65b0dSMickaël Salaün 	audit_log_lsm_data(ab, &request->audit);
17333e65b0dSMickaël Salaün 	audit_log_end(ab);
174*1d636984SMickaël Salaün 
175*1d636984SMickaël Salaün 	/* Logs this domain the first time it shows in log. */
176*1d636984SMickaël Salaün 	log_domain(youngest_denied);
177*1d636984SMickaël Salaün }
178*1d636984SMickaël Salaün 
179*1d636984SMickaël Salaün /**
180*1d636984SMickaël Salaün  * landlock_log_drop_domain - Create an audit record on domain deallocation
181*1d636984SMickaël Salaün  *
182*1d636984SMickaël Salaün  * @hierarchy: The domain's hierarchy being deallocated.
183*1d636984SMickaël Salaün  *
184*1d636984SMickaël Salaün  * Only domains which previously appeared in the audit logs are logged again.
185*1d636984SMickaël Salaün  * This is useful to know when a domain will never show again in the audit log.
186*1d636984SMickaël Salaün  *
187*1d636984SMickaël Salaün  * Called in a work queue scheduled by landlock_put_ruleset_deferred() called
188*1d636984SMickaël Salaün  * by hook_cred_free().
189*1d636984SMickaël Salaün  */
190*1d636984SMickaël Salaün void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
191*1d636984SMickaël Salaün {
192*1d636984SMickaël Salaün 	struct audit_buffer *ab;
193*1d636984SMickaël Salaün 
194*1d636984SMickaël Salaün 	if (WARN_ON_ONCE(!hierarchy))
195*1d636984SMickaël Salaün 		return;
196*1d636984SMickaël Salaün 
197*1d636984SMickaël Salaün 	if (!audit_enabled)
198*1d636984SMickaël Salaün 		return;
199*1d636984SMickaël Salaün 
200*1d636984SMickaël Salaün 	/* Ignores domains that were not logged.  */
201*1d636984SMickaël Salaün 	if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED)
202*1d636984SMickaël Salaün 		return;
203*1d636984SMickaël Salaün 
204*1d636984SMickaël Salaün 	/*
205*1d636984SMickaël Salaün 	 * If logging of domain allocation succeeded, warns about failure to log
206*1d636984SMickaël Salaün 	 * domain deallocation to highlight unbalanced domain lifetime logs.
207*1d636984SMickaël Salaün 	 */
208*1d636984SMickaël Salaün 	ab = audit_log_start(audit_context(), GFP_KERNEL,
209*1d636984SMickaël Salaün 			     AUDIT_LANDLOCK_DOMAIN);
210*1d636984SMickaël Salaün 	if (!ab)
211*1d636984SMickaël Salaün 		return;
212*1d636984SMickaël Salaün 
213*1d636984SMickaël Salaün 	audit_log_format(ab, "domain=%llx status=deallocated denials=%llu",
214*1d636984SMickaël Salaün 			 hierarchy->id, atomic64_read(&hierarchy->num_denials));
215*1d636984SMickaël Salaün 	audit_log_end(ab);
21633e65b0dSMickaël Salaün }
21733e65b0dSMickaël Salaün 
21833e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
21933e65b0dSMickaël Salaün 
22033e65b0dSMickaël Salaün static struct kunit_case test_cases[] = {
22133e65b0dSMickaël Salaün 	/* clang-format off */
22233e65b0dSMickaël Salaün 	KUNIT_CASE(test_get_hierarchy),
22333e65b0dSMickaël Salaün 	{}
22433e65b0dSMickaël Salaün 	/* clang-format on */
22533e65b0dSMickaël Salaün };
22633e65b0dSMickaël Salaün 
22733e65b0dSMickaël Salaün static struct kunit_suite test_suite = {
22833e65b0dSMickaël Salaün 	.name = "landlock_audit",
22933e65b0dSMickaël Salaün 	.test_cases = test_cases,
23033e65b0dSMickaël Salaün };
23133e65b0dSMickaël Salaün 
23233e65b0dSMickaël Salaün kunit_test_suite(test_suite);
23333e65b0dSMickaël Salaün 
23433e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
235