1*33e65b0dSMickaël Salaün // SPDX-License-Identifier: GPL-2.0-only 2*33e65b0dSMickaël Salaün /* 3*33e65b0dSMickaël Salaün * Landlock - Audit helpers 4*33e65b0dSMickaël Salaün * 5*33e65b0dSMickaël Salaün * Copyright © 2023-2025 Microsoft Corporation 6*33e65b0dSMickaël Salaün */ 7*33e65b0dSMickaël Salaün 8*33e65b0dSMickaël Salaün #include <kunit/test.h> 9*33e65b0dSMickaël Salaün #include <linux/audit.h> 10*33e65b0dSMickaël Salaün #include <linux/lsm_audit.h> 11*33e65b0dSMickaël Salaün 12*33e65b0dSMickaël Salaün #include "audit.h" 13*33e65b0dSMickaël Salaün #include "cred.h" 14*33e65b0dSMickaël Salaün #include "domain.h" 15*33e65b0dSMickaël Salaün #include "limits.h" 16*33e65b0dSMickaël Salaün #include "ruleset.h" 17*33e65b0dSMickaël Salaün 18*33e65b0dSMickaël Salaün static const char *get_blocker(const enum landlock_request_type type) 19*33e65b0dSMickaël Salaün { 20*33e65b0dSMickaël Salaün switch (type) { 21*33e65b0dSMickaël Salaün case LANDLOCK_REQUEST_PTRACE: 22*33e65b0dSMickaël Salaün return "ptrace"; 23*33e65b0dSMickaël Salaün } 24*33e65b0dSMickaël Salaün 25*33e65b0dSMickaël Salaün WARN_ON_ONCE(1); 26*33e65b0dSMickaël Salaün return "unknown"; 27*33e65b0dSMickaël Salaün } 28*33e65b0dSMickaël Salaün 29*33e65b0dSMickaël Salaün static void log_blockers(struct audit_buffer *const ab, 30*33e65b0dSMickaël Salaün const enum landlock_request_type type) 31*33e65b0dSMickaël Salaün { 32*33e65b0dSMickaël Salaün audit_log_format(ab, "%s", get_blocker(type)); 33*33e65b0dSMickaël Salaün } 34*33e65b0dSMickaël Salaün 35*33e65b0dSMickaël Salaün static struct landlock_hierarchy * 36*33e65b0dSMickaël Salaün get_hierarchy(const struct landlock_ruleset *const domain, const size_t layer) 37*33e65b0dSMickaël Salaün { 38*33e65b0dSMickaël Salaün struct landlock_hierarchy *hierarchy = domain->hierarchy; 39*33e65b0dSMickaël Salaün ssize_t i; 40*33e65b0dSMickaël Salaün 41*33e65b0dSMickaël Salaün if (WARN_ON_ONCE(layer >= domain->num_layers)) 42*33e65b0dSMickaël Salaün return hierarchy; 43*33e65b0dSMickaël Salaün 44*33e65b0dSMickaël Salaün for (i = domain->num_layers - 1; i > layer; i--) { 45*33e65b0dSMickaël Salaün if (WARN_ON_ONCE(!hierarchy->parent)) 46*33e65b0dSMickaël Salaün break; 47*33e65b0dSMickaël Salaün 48*33e65b0dSMickaël Salaün hierarchy = hierarchy->parent; 49*33e65b0dSMickaël Salaün } 50*33e65b0dSMickaël Salaün 51*33e65b0dSMickaël Salaün return hierarchy; 52*33e65b0dSMickaël Salaün } 53*33e65b0dSMickaël Salaün 54*33e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 55*33e65b0dSMickaël Salaün 56*33e65b0dSMickaël Salaün static void test_get_hierarchy(struct kunit *const test) 57*33e65b0dSMickaël Salaün { 58*33e65b0dSMickaël Salaün struct landlock_hierarchy dom0_hierarchy = { 59*33e65b0dSMickaël Salaün .id = 10, 60*33e65b0dSMickaël Salaün }; 61*33e65b0dSMickaël Salaün struct landlock_hierarchy dom1_hierarchy = { 62*33e65b0dSMickaël Salaün .parent = &dom0_hierarchy, 63*33e65b0dSMickaël Salaün .id = 20, 64*33e65b0dSMickaël Salaün }; 65*33e65b0dSMickaël Salaün struct landlock_hierarchy dom2_hierarchy = { 66*33e65b0dSMickaël Salaün .parent = &dom1_hierarchy, 67*33e65b0dSMickaël Salaün .id = 30, 68*33e65b0dSMickaël Salaün }; 69*33e65b0dSMickaël Salaün struct landlock_ruleset dom2 = { 70*33e65b0dSMickaël Salaün .hierarchy = &dom2_hierarchy, 71*33e65b0dSMickaël Salaün .num_layers = 3, 72*33e65b0dSMickaël Salaün }; 73*33e65b0dSMickaël Salaün 74*33e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 10, get_hierarchy(&dom2, 0)->id); 75*33e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 20, get_hierarchy(&dom2, 1)->id); 76*33e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, 2)->id); 77*33e65b0dSMickaël Salaün KUNIT_EXPECT_EQ(test, 30, get_hierarchy(&dom2, -1)->id); 78*33e65b0dSMickaël Salaün } 79*33e65b0dSMickaël Salaün 80*33e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 81*33e65b0dSMickaël Salaün 82*33e65b0dSMickaël Salaün static bool is_valid_request(const struct landlock_request *const request) 83*33e65b0dSMickaël Salaün { 84*33e65b0dSMickaël Salaün if (WARN_ON_ONCE(request->layer_plus_one > LANDLOCK_MAX_NUM_LAYERS)) 85*33e65b0dSMickaël Salaün return false; 86*33e65b0dSMickaël Salaün 87*33e65b0dSMickaël Salaün if (WARN_ON_ONCE(!request->layer_plus_one)) 88*33e65b0dSMickaël Salaün return false; 89*33e65b0dSMickaël Salaün 90*33e65b0dSMickaël Salaün return true; 91*33e65b0dSMickaël Salaün } 92*33e65b0dSMickaël Salaün 93*33e65b0dSMickaël Salaün /** 94*33e65b0dSMickaël Salaün * landlock_log_denial - Create audit records related to a denial 95*33e65b0dSMickaël Salaün * 96*33e65b0dSMickaël Salaün * @subject: The Landlock subject's credential denying an action. 97*33e65b0dSMickaël Salaün * @request: Detail of the user space request. 98*33e65b0dSMickaël Salaün */ 99*33e65b0dSMickaël Salaün void landlock_log_denial(const struct landlock_cred_security *const subject, 100*33e65b0dSMickaël Salaün const struct landlock_request *const request) 101*33e65b0dSMickaël Salaün { 102*33e65b0dSMickaël Salaün struct audit_buffer *ab; 103*33e65b0dSMickaël Salaün struct landlock_hierarchy *youngest_denied; 104*33e65b0dSMickaël Salaün size_t youngest_layer; 105*33e65b0dSMickaël Salaün 106*33e65b0dSMickaël Salaün if (WARN_ON_ONCE(!subject || !subject->domain || 107*33e65b0dSMickaël Salaün !subject->domain->hierarchy || !request)) 108*33e65b0dSMickaël Salaün return; 109*33e65b0dSMickaël Salaün 110*33e65b0dSMickaël Salaün if (!is_valid_request(request)) 111*33e65b0dSMickaël Salaün return; 112*33e65b0dSMickaël Salaün 113*33e65b0dSMickaël Salaün if (!audit_enabled) 114*33e65b0dSMickaël Salaün return; 115*33e65b0dSMickaël Salaün 116*33e65b0dSMickaël Salaün youngest_layer = request->layer_plus_one - 1; 117*33e65b0dSMickaël Salaün youngest_denied = get_hierarchy(subject->domain, youngest_layer); 118*33e65b0dSMickaël Salaün 119*33e65b0dSMickaël Salaün /* Ignores denials after an execution. */ 120*33e65b0dSMickaël Salaün if (!(subject->domain_exec & (1 << youngest_layer))) 121*33e65b0dSMickaël Salaün return; 122*33e65b0dSMickaël Salaün 123*33e65b0dSMickaël Salaün /* Uses consistent allocation flags wrt common_lsm_audit(). */ 124*33e65b0dSMickaël Salaün ab = audit_log_start(audit_context(), GFP_ATOMIC | __GFP_NOWARN, 125*33e65b0dSMickaël Salaün AUDIT_LANDLOCK_ACCESS); 126*33e65b0dSMickaël Salaün if (!ab) 127*33e65b0dSMickaël Salaün return; 128*33e65b0dSMickaël Salaün 129*33e65b0dSMickaël Salaün audit_log_format(ab, "domain=%llx blockers=", youngest_denied->id); 130*33e65b0dSMickaël Salaün log_blockers(ab, request->type); 131*33e65b0dSMickaël Salaün audit_log_lsm_data(ab, &request->audit); 132*33e65b0dSMickaël Salaün audit_log_end(ab); 133*33e65b0dSMickaël Salaün } 134*33e65b0dSMickaël Salaün 135*33e65b0dSMickaël Salaün #ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST 136*33e65b0dSMickaël Salaün 137*33e65b0dSMickaël Salaün static struct kunit_case test_cases[] = { 138*33e65b0dSMickaël Salaün /* clang-format off */ 139*33e65b0dSMickaël Salaün KUNIT_CASE(test_get_hierarchy), 140*33e65b0dSMickaël Salaün {} 141*33e65b0dSMickaël Salaün /* clang-format on */ 142*33e65b0dSMickaël Salaün }; 143*33e65b0dSMickaël Salaün 144*33e65b0dSMickaël Salaün static struct kunit_suite test_suite = { 145*33e65b0dSMickaël Salaün .name = "landlock_audit", 146*33e65b0dSMickaël Salaün .test_cases = test_cases, 147*33e65b0dSMickaël Salaün }; 148*33e65b0dSMickaël Salaün 149*33e65b0dSMickaël Salaün kunit_test_suite(test_suite); 150*33e65b0dSMickaël Salaün 151*33e65b0dSMickaël Salaün #endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ 152