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