xref: /linux/security/landlock/audit.c (revision c56f649646ecec3dd1a2e400e6e5ec83439d940f)
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>
111d636984SMickaë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";
24*c56f6496SMickaël Salaün 
25*c56f6496SMickaël Salaün 	case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY:
26*c56f6496SMickaël Salaün 		return "fs.change_topology";
2733e65b0dSMickaël Salaün 	}
2833e65b0dSMickaël Salaün 
2933e65b0dSMickaël Salaün 	WARN_ON_ONCE(1);
3033e65b0dSMickaël Salaün 	return "unknown";
3133e65b0dSMickaël Salaün }
3233e65b0dSMickaël Salaün 
3333e65b0dSMickaël Salaün static void log_blockers(struct audit_buffer *const ab,
3433e65b0dSMickaël Salaün 			 const enum landlock_request_type type)
3533e65b0dSMickaël Salaün {
3633e65b0dSMickaël Salaün 	audit_log_format(ab, "%s", get_blocker(type));
3733e65b0dSMickaël Salaün }
3833e65b0dSMickaël Salaün 
391d636984SMickaël Salaün static void log_domain(struct landlock_hierarchy *const hierarchy)
401d636984SMickaël Salaün {
411d636984SMickaël Salaün 	struct audit_buffer *ab;
421d636984SMickaël Salaün 
431d636984SMickaël Salaün 	/* Ignores already logged domains.  */
441d636984SMickaël Salaün 	if (READ_ONCE(hierarchy->log_status) == LANDLOCK_LOG_RECORDED)
451d636984SMickaël Salaün 		return;
461d636984SMickaël Salaün 
471d636984SMickaël Salaün 	/* Uses consistent allocation flags wrt common_lsm_audit(). */
481d636984SMickaël Salaün 	ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
491d636984SMickaël Salaün 			     AUDIT_LANDLOCK_DOMAIN);
501d636984SMickaël Salaün 	if (!ab)
511d636984SMickaël Salaün 		return;
521d636984SMickaël Salaün 
531d636984SMickaël Salaün 	WARN_ON_ONCE(hierarchy->id == 0);
541d636984SMickaël Salaün 	audit_log_format(
551d636984SMickaël Salaün 		ab,
561d636984SMickaël Salaün 		"domain=%llx status=allocated mode=enforcing pid=%d uid=%u exe=",
571d636984SMickaël Salaün 		hierarchy->id, pid_nr(hierarchy->details->pid),
581d636984SMickaël Salaün 		hierarchy->details->uid);
591d636984SMickaël Salaün 	audit_log_untrustedstring(ab, hierarchy->details->exe_path);
601d636984SMickaël Salaün 	audit_log_format(ab, " comm=");
611d636984SMickaël Salaün 	audit_log_untrustedstring(ab, hierarchy->details->comm);
621d636984SMickaël Salaün 	audit_log_end(ab);
631d636984SMickaël Salaün 
641d636984SMickaël Salaün 	/*
651d636984SMickaël Salaün 	 * There may be race condition leading to logging of the same domain
661d636984SMickaël Salaün 	 * several times but that is OK.
671d636984SMickaël Salaün 	 */
681d636984SMickaël Salaün 	WRITE_ONCE(hierarchy->log_status, LANDLOCK_LOG_RECORDED);
691d636984SMickaël Salaün }
701d636984SMickaël Salaün 
7133e65b0dSMickaël Salaün static struct landlock_hierarchy *
7233e65b0dSMickaël Salaün get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer)
7333e65b0dSMickaël Salaün {
7433e65b0dSMickaël Salaün 	struct landlock_hierarchy *hierarchy = domain->hierarchy;
7533e65b0dSMickaël Salaün 	ssize_t i;
7633e65b0dSMickaël Salaün 
7733e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(layer >= domain->num_layers))
7833e65b0dSMickaël Salaün 		return hierarchy;
7933e65b0dSMickaël Salaün 
8033e65b0dSMickaël Salaün 	for (i = domain->num_layers - 1; i > layer; i--) {
8133e65b0dSMickaël Salaün 		if (WARN_ON_ONCE(!hierarchy->parent))
8233e65b0dSMickaël Salaün 			break;
8333e65b0dSMickaël Salaün 
8433e65b0dSMickaël Salaün 		hierarchy = hierarchy->parent;
8533e65b0dSMickaël Salaün 	}
8633e65b0dSMickaël Salaün 
8733e65b0dSMickaël Salaün 	return hierarchy;
8833e65b0dSMickaël Salaün }
8933e65b0dSMickaël Salaün 
9033e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
9133e65b0dSMickaël Salaün 
9233e65b0dSMickaël Salaün static void test_get_hierarchy(struct kunit *const test)
9333e65b0dSMickaël Salaün {
9433e65b0dSMickaël Salaün 	struct landlock_hierarchy dom0_hierarchy = {
9533e65b0dSMickaël Salaün 		.id = 10,
9633e65b0dSMickaël Salaün 	};
9733e65b0dSMickaël Salaün 	struct landlock_hierarchy dom1_hierarchy = {
9833e65b0dSMickaël Salaün 		.parent = &dom0_hierarchy,
9933e65b0dSMickaël Salaün 		.id = 20,
10033e65b0dSMickaël Salaün 	};
10133e65b0dSMickaël Salaün 	struct landlock_hierarchy dom2_hierarchy = {
10233e65b0dSMickaël Salaün 		.parent = &dom1_hierarchy,
10333e65b0dSMickaël Salaün 		.id = 30,
10433e65b0dSMickaël Salaün 	};
10533e65b0dSMickaël Salaün 	struct landlock_ruleset dom2 = {
10633e65b0dSMickaël Salaün 		.hierarchy = &dom2_hierarchy,
10733e65b0dSMickaël Salaün 		.num_layers = 3,
10833e65b0dSMickaël Salaün 	};
10933e65b0dSMickaël Salaün 
11033e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id);
11133e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id);
11233e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id);
11333e65b0dSMickaël Salaün 	KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id);
11433e65b0dSMickaël Salaün }
11533e65b0dSMickaël Salaün 
11633e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
11733e65b0dSMickaël Salaün 
11833e65b0dSMickaël Salaün static bool is_valid_request(const struct landlock_request *const request)
11933e65b0dSMickaël Salaün {
12033e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS))
12133e65b0dSMickaël Salaün 		return false;
12233e65b0dSMickaël Salaün 
12333e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(!request->layer_plus_one))
12433e65b0dSMickaël Salaün 		return false;
12533e65b0dSMickaël Salaün 
12633e65b0dSMickaël Salaün 	return true;
12733e65b0dSMickaël Salaün }
12833e65b0dSMickaël Salaün 
12933e65b0dSMickaël Salaün /**
13033e65b0dSMickaël Salaün  * landlock_log_denial - Create audit records related to a denial
13133e65b0dSMickaël Salaün  *
13233e65b0dSMickaël Salaün  * @subject: The Landlock subject's credential denying an action.
13333e65b0dSMickaël Salaün  * @request: Detail of the user space request.
13433e65b0dSMickaël Salaün  */
13533e65b0dSMickaël Salaün void landlock_log_denial(const struct landlock_cred_security *const subject,
13633e65b0dSMickaël Salaün 			 const struct landlock_request *const request)
13733e65b0dSMickaël Salaün {
13833e65b0dSMickaël Salaün 	struct audit_buffer *ab;
13933e65b0dSMickaël Salaün 	struct landlock_hierarchy *youngest_denied;
14033e65b0dSMickaël Salaün 	size_t youngest_layer;
14133e65b0dSMickaël Salaün 
14233e65b0dSMickaël Salaün 	if (WARN_ON_ONCE(!subject || !subject->domain ||
14333e65b0dSMickaël Salaün 			 !subject->domain->hierarchy || !request))
14433e65b0dSMickaël Salaün 		return;
14533e65b0dSMickaël Salaün 
14633e65b0dSMickaël Salaün 	if (!is_valid_request(request))
14733e65b0dSMickaël Salaün 		return;
14833e65b0dSMickaël Salaün 
14933e65b0dSMickaël Salaün 	youngest_layer = request->layer_plus_one - 1;
15033e65b0dSMickaël Salaün 	youngest_denied = get_hierarchy(subject->domain, youngest_layer);
15133e65b0dSMickaël Salaün 
1521d636984SMickaël Salaün 	/*
1531d636984SMickaël Salaün 	 * Consistently keeps track of the number of denied access requests
1541d636984SMickaël Salaün 	 * even if audit is currently disabled, or if audit rules currently
1551d636984SMickaël Salaün 	 * exclude this record type, or if landlock_restrict_self(2)'s flags
1561d636984SMickaël Salaün 	 * quiet logs.
1571d636984SMickaël Salaün 	 */
1581d636984SMickaël Salaün 	atomic64_inc(&youngest_denied->num_denials);
1591d636984SMickaël Salaün 
1601d636984SMickaël Salaün 	if (!audit_enabled)
1611d636984SMickaël Salaün 		return;
1621d636984SMickaël Salaün 
16333e65b0dSMickaël Salaün 	/* Ignores denials after an execution. */
16433e65b0dSMickaël Salaün 	if (!(subject->domain_exec & (1 << youngest_layer)))
16533e65b0dSMickaël Salaün 		return;
16633e65b0dSMickaël Salaün 
16733e65b0dSMickaël Salaün 	/* Uses consistent allocation flags wrt common_lsm_audit(). */
16833e65b0dSMickaël Salaün 	ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN,
16933e65b0dSMickaël Salaün 			     AUDIT_LANDLOCK_ACCESS);
17033e65b0dSMickaël Salaün 	if (!ab)
17133e65b0dSMickaël Salaün 		return;
17233e65b0dSMickaël Salaün 
17333e65b0dSMickaël Salaün 	audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id);
17433e65b0dSMickaël Salaün 	log_blockers(ab, request->type);
17533e65b0dSMickaël Salaün 	audit_log_lsm_data(ab, &request->audit);
17633e65b0dSMickaël Salaün 	audit_log_end(ab);
1771d636984SMickaël Salaün 
1781d636984SMickaël Salaün 	/* Logs this domain the first time it shows in log. */
1791d636984SMickaël Salaün 	log_domain(youngest_denied);
1801d636984SMickaël Salaün }
1811d636984SMickaël Salaün 
1821d636984SMickaël Salaün /**
1831d636984SMickaël Salaün  * landlock_log_drop_domain - Create an audit record on domain deallocation
1841d636984SMickaël Salaün  *
1851d636984SMickaël Salaün  * @hierarchy: The domain's hierarchy being deallocated.
1861d636984SMickaël Salaün  *
1871d636984SMickaël Salaün  * Only domains which previously appeared in the audit logs are logged again.
1881d636984SMickaël Salaün  * This is useful to know when a domain will never show again in the audit log.
1891d636984SMickaël Salaün  *
1901d636984SMickaël Salaün  * Called in a work queue scheduled by landlock_put_ruleset_deferred() called
1911d636984SMickaël Salaün  * by hook_cred_free().
1921d636984SMickaël Salaün  */
1931d636984SMickaël Salaün void landlock_log_drop_domain(const struct landlock_hierarchy *const hierarchy)
1941d636984SMickaël Salaün {
1951d636984SMickaël Salaün 	struct audit_buffer *ab;
1961d636984SMickaël Salaün 
1971d636984SMickaël Salaün 	if (WARN_ON_ONCE(!hierarchy))
1981d636984SMickaël Salaün 		return;
1991d636984SMickaël Salaün 
2001d636984SMickaël Salaün 	if (!audit_enabled)
2011d636984SMickaël Salaün 		return;
2021d636984SMickaël Salaün 
2031d636984SMickaël Salaün 	/* Ignores domains that were not logged.  */
2041d636984SMickaël Salaün 	if (READ_ONCE(hierarchy->log_status) != LANDLOCK_LOG_RECORDED)
2051d636984SMickaël Salaün 		return;
2061d636984SMickaël Salaün 
2071d636984SMickaël Salaün 	/*
2081d636984SMickaël Salaün 	 * If logging of domain allocation succeeded, warns about failure to log
2091d636984SMickaël Salaün 	 * domain deallocation to highlight unbalanced domain lifetime logs.
2101d636984SMickaël Salaün 	 */
2111d636984SMickaël Salaün 	ab = audit_log_start(audit_context(), GFP_KERNEL,
2121d636984SMickaël Salaün 			     AUDIT_LANDLOCK_DOMAIN);
2131d636984SMickaël Salaün 	if (!ab)
2141d636984SMickaël Salaün 		return;
2151d636984SMickaël Salaün 
2161d636984SMickaël Salaün 	audit_log_format(ab, "domain=%llx status=deallocated denials=%llu",
2171d636984SMickaël Salaün 			 hierarchy->id, atomic64_read(&hierarchy->num_denials));
2181d636984SMickaël Salaün 	audit_log_end(ab);
21933e65b0dSMickaël Salaün }
22033e65b0dSMickaël Salaün 
22133e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
22233e65b0dSMickaël Salaün 
22333e65b0dSMickaël Salaün static struct kunit_case test_cases[] = {
22433e65b0dSMickaël Salaün 	/* clang-format off */
22533e65b0dSMickaël Salaün 	KUNIT_CASE(test_get_hierarchy),
22633e65b0dSMickaël Salaün 	{}
22733e65b0dSMickaël Salaün 	/* clang-format on */
22833e65b0dSMickaël Salaün };
22933e65b0dSMickaël Salaün 
23033e65b0dSMickaël Salaün static struct kunit_suite test_suite = {
23133e65b0dSMickaël Salaün 	.name = "landlock_audit",
23233e65b0dSMickaël Salaün 	.test_cases = test_cases,
23333e65b0dSMickaël Salaün };
23433e65b0dSMickaël Salaün 
23533e65b0dSMickaël Salaün kunit_test_suite(test_suite);
23633e65b0dSMickaël Salaün 
23733e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
238