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