1 /* SPDX-License-Identifier: GPL-2.0 */ 2 /* 3 * Landlock audit helpers 4 * 5 * Copyright © 2024-2025 Microsoft Corporation 6 */ 7 8 #define _GNU_SOURCE 9 #include <errno.h> 10 #include <linux/audit.h> 11 #include <linux/limits.h> 12 #include <linux/netlink.h> 13 #include <regex.h> 14 #include <stdbool.h> 15 #include <stdint.h> 16 #include <stdio.h> 17 #include <stdlib.h> 18 #include <string.h> 19 #include <sys/socket.h> 20 #include <sys/time.h> 21 #include <unistd.h> 22 23 #include "kselftest.h" 24 25 #ifndef ARRAY_SIZE 26 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 27 #endif 28 29 #define REGEX_LANDLOCK_PREFIX "^audit([0-9.:]\\+): domain=\\([0-9a-f]\\+\\)" 30 31 struct audit_filter { 32 __u32 record_type; 33 size_t exe_len; 34 char exe[PATH_MAX]; 35 }; 36 37 struct audit_message { 38 struct nlmsghdr header; 39 union { 40 struct audit_status status; 41 struct audit_features features; 42 struct audit_rule_data rule; 43 struct nlmsgerr err; 44 char data[PATH_MAX + 200]; 45 }; 46 }; 47 48 static const struct timeval audit_tv_dom_drop = { 49 /* 50 * Because domain deallocation is tied to asynchronous credential 51 * freeing, receiving such event may take some time. In practice, 52 * on a small VM, it should not exceed 100k usec, but let's wait up 53 * to 1 second to be safe. 54 */ 55 .tv_sec = 1, 56 }; 57 58 static const struct timeval audit_tv_default = { 59 .tv_usec = 1, 60 }; 61 62 static int audit_send(const int fd, const struct audit_message *const msg) 63 { 64 struct sockaddr_nl addr = { 65 .nl_family = AF_NETLINK, 66 }; 67 int ret; 68 69 do { 70 ret = sendto(fd, msg, msg->header.nlmsg_len, 0, 71 (struct sockaddr *)&addr, sizeof(addr)); 72 } while (ret < 0 && errno == EINTR); 73 74 if (ret < 0) 75 return -errno; 76 77 if (ret != msg->header.nlmsg_len) 78 return -E2BIG; 79 80 return 0; 81 } 82 83 static int audit_recv(const int fd, struct audit_message *msg) 84 { 85 struct sockaddr_nl addr; 86 socklen_t addrlen = sizeof(addr); 87 struct audit_message msg_tmp; 88 int err; 89 90 if (!msg) 91 msg = &msg_tmp; 92 93 do { 94 err = recvfrom(fd, msg, sizeof(*msg), 0, 95 (struct sockaddr *)&addr, &addrlen); 96 } while (err < 0 && errno == EINTR); 97 98 if (err < 0) 99 return -errno; 100 101 if (addrlen != sizeof(addr) || addr.nl_pid != 0) 102 return -EINVAL; 103 104 /* Checks Netlink error or end of messages. */ 105 if (msg->header.nlmsg_type == NLMSG_ERROR) 106 return msg->err.error; 107 108 return 0; 109 } 110 111 static int audit_request(const int fd, 112 const struct audit_message *const request, 113 struct audit_message *reply) 114 { 115 struct audit_message msg_tmp; 116 bool first_reply = true; 117 int err; 118 119 err = audit_send(fd, request); 120 if (err) 121 return err; 122 123 if (!reply) 124 reply = &msg_tmp; 125 126 do { 127 if (first_reply) 128 first_reply = false; 129 else 130 reply = &msg_tmp; 131 132 err = audit_recv(fd, reply); 133 if (err) 134 return err; 135 } while (reply->header.nlmsg_type != NLMSG_ERROR && 136 reply->err.msg.nlmsg_type != request->header.nlmsg_type); 137 138 return reply->err.error; 139 } 140 141 static int audit_filter_exe(const int audit_fd, 142 const struct audit_filter *const filter, 143 const __u16 type) 144 { 145 struct audit_message msg = { 146 .header = { 147 .nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)) + 148 NLMSG_ALIGN(filter->exe_len), 149 .nlmsg_type = type, 150 .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, 151 }, 152 .rule = { 153 .flags = AUDIT_FILTER_EXCLUDE, 154 .action = AUDIT_NEVER, 155 .field_count = 1, 156 .fields[0] = filter->record_type, 157 .fieldflags[0] = AUDIT_NOT_EQUAL, 158 .values[0] = filter->exe_len, 159 .buflen = filter->exe_len, 160 } 161 }; 162 163 if (filter->record_type != AUDIT_EXE) 164 return -EINVAL; 165 166 memcpy(msg.rule.buf, filter->exe, filter->exe_len); 167 return audit_request(audit_fd, &msg, NULL); 168 } 169 170 static int audit_filter_drop(const int audit_fd, const __u16 type) 171 { 172 struct audit_message msg = { 173 .header = { 174 .nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)), 175 .nlmsg_type = type, 176 .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, 177 }, 178 .rule = { 179 .flags = AUDIT_FILTER_EXCLUDE, 180 .action = AUDIT_NEVER, 181 .field_count = 1, 182 .fields[0] = AUDIT_MSGTYPE, 183 .fieldflags[0] = AUDIT_NOT_EQUAL, 184 .values[0] = AUDIT_LANDLOCK_DOMAIN, 185 } 186 }; 187 188 return audit_request(audit_fd, &msg, NULL); 189 } 190 191 static int audit_set_status(int fd, __u32 key, __u32 val) 192 { 193 const struct audit_message msg = { 194 .header = { 195 .nlmsg_len = NLMSG_SPACE(sizeof(msg.status)), 196 .nlmsg_type = AUDIT_SET, 197 .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, 198 }, 199 .status = { 200 .mask = key, 201 .enabled = key == AUDIT_STATUS_ENABLED ? val : 0, 202 .pid = key == AUDIT_STATUS_PID ? val : 0, 203 } 204 }; 205 206 return audit_request(fd, &msg, NULL); 207 } 208 209 /* Returns a pointer to the last filled character of @dst, which is `\0`. */ 210 static __maybe_unused char *regex_escape(const char *const src, char *dst, 211 size_t dst_size) 212 { 213 char *d = dst; 214 215 for (const char *s = src; *s; s++) { 216 switch (*s) { 217 case '$': 218 case '*': 219 case '.': 220 case '[': 221 case '\\': 222 case ']': 223 case '^': 224 if (d >= dst + dst_size - 2) 225 return (char *)-ENOMEM; 226 227 *d++ = '\\'; 228 *d++ = *s; 229 break; 230 default: 231 if (d >= dst + dst_size - 1) 232 return (char *)-ENOMEM; 233 234 *d++ = *s; 235 } 236 } 237 if (d >= dst + dst_size - 1) 238 return (char *)-ENOMEM; 239 240 *d = '\0'; 241 return d; 242 } 243 244 /* 245 * @domain_id: The domain ID extracted from the audit message (if the first part 246 * of @pattern is REGEX_LANDLOCK_PREFIX). It is set to 0 if the domain ID is 247 * not found. 248 */ 249 static int audit_match_record(int audit_fd, const __u16 type, 250 const char *const pattern, __u64 *domain_id) 251 { 252 struct audit_message msg, last_mismatch = {}; 253 int ret, err = 0; 254 int num_type_match = 0; 255 regmatch_t matches[2]; 256 regex_t regex; 257 258 ret = regcomp(®ex, pattern, 0); 259 if (ret) 260 return -EINVAL; 261 262 /* 263 * Reads records until one matches both the expected type and the 264 * pattern. Type-matching records with non-matching content are 265 * silently consumed, which handles stale domain deallocation records 266 * from a previous test emitted asynchronously by kworker threads. 267 */ 268 while (true) { 269 memset(&msg, 0, sizeof(msg)); 270 err = audit_recv(audit_fd, &msg); 271 if (err) { 272 if (num_type_match) { 273 printf("DATA: %s\n", last_mismatch.data); 274 printf("ERROR: %d record(s) matched type %u" 275 " but not pattern: %s\n", 276 num_type_match, type, pattern); 277 } 278 goto out; 279 } 280 281 if (type && msg.header.nlmsg_type != type) 282 continue; 283 284 ret = regexec(®ex, msg.data, ARRAY_SIZE(matches), matches, 285 0); 286 if (!ret) 287 break; 288 289 num_type_match++; 290 last_mismatch = msg; 291 } 292 293 if (domain_id) { 294 *domain_id = 0; 295 if (matches[1].rm_so != -1) { 296 int match_len = matches[1].rm_eo - matches[1].rm_so; 297 /* The maximal characters of a 2^64 hexadecimal number is 17. */ 298 char dom_id[18]; 299 300 if (match_len > 0 && match_len < sizeof(dom_id)) { 301 memcpy(dom_id, msg.data + matches[1].rm_so, 302 match_len); 303 dom_id[match_len] = '\0'; 304 if (domain_id) 305 *domain_id = strtoull(dom_id, NULL, 16); 306 } 307 } 308 } 309 310 out: 311 regfree(®ex); 312 return err; 313 } 314 315 static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid, 316 __u64 *domain_id) 317 { 318 static const char log_template[] = REGEX_LANDLOCK_PREFIX 319 " status=allocated mode=enforcing pid=%d uid=[0-9]\\+" 320 " exe=\"[^\"]\\+\" comm=\".*_test\"$"; 321 char log_match[sizeof(log_template) + 10]; 322 int log_match_len; 323 324 log_match_len = 325 snprintf(log_match, sizeof(log_match), log_template, pid); 326 if (log_match_len >= sizeof(log_match)) 327 return -E2BIG; 328 329 return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, 330 domain_id); 331 } 332 333 /* 334 * Matches a domain deallocation record. When expected_domain_id is non-zero, 335 * the pattern includes the specific domain ID so that stale deallocation 336 * records from a previous test (with a different domain ID) are skipped by 337 * audit_match_record(), and the socket timeout is temporarily increased to 338 * audit_tv_dom_drop to wait for the asynchronous kworker deallocation. 339 */ 340 static int __maybe_unused 341 matches_log_domain_deallocated(int audit_fd, unsigned int num_denials, 342 __u64 expected_domain_id, __u64 *domain_id) 343 { 344 static const char log_template[] = REGEX_LANDLOCK_PREFIX 345 " status=deallocated denials=%u$"; 346 static const char log_template_with_id[] = 347 "^audit([0-9.:]\\+): domain=\\(%llx\\)" 348 " status=deallocated denials=%u$"; 349 char log_match[sizeof(log_template_with_id) + 32]; 350 int log_match_len, err; 351 352 if (expected_domain_id) 353 log_match_len = snprintf(log_match, sizeof(log_match), 354 log_template_with_id, 355 (unsigned long long)expected_domain_id, 356 num_denials); 357 else 358 log_match_len = snprintf(log_match, sizeof(log_match), 359 log_template, num_denials); 360 361 if (log_match_len >= sizeof(log_match)) 362 return -E2BIG; 363 364 if (expected_domain_id) 365 setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, 366 &audit_tv_dom_drop, sizeof(audit_tv_dom_drop)); 367 368 err = audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, 369 domain_id); 370 371 if (expected_domain_id) 372 setsockopt(audit_fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default, 373 sizeof(audit_tv_default)); 374 375 return err; 376 } 377 378 struct audit_records { 379 size_t access; 380 size_t domain; 381 }; 382 383 /* 384 * WARNING: Do not assert records.domain == 0 without a preceding 385 * audit_match_record() call. Domain deallocation records are emitted 386 * asynchronously from kworker threads and can arrive after the drain in 387 * audit_init(), corrupting the domain count. A preceding audit_match_record() 388 * call consumes stale records while scanning, making the assertion safe in 389 * practice because stale deallocation records arrive before the expected access 390 * records. 391 */ 392 static int audit_count_records(int audit_fd, struct audit_records *records) 393 { 394 struct audit_message msg; 395 int err; 396 397 records->access = 0; 398 records->domain = 0; 399 400 do { 401 memset(&msg, 0, sizeof(msg)); 402 err = audit_recv(audit_fd, &msg); 403 if (err) { 404 if (err == -EAGAIN) 405 return 0; 406 else 407 return err; 408 } 409 410 switch (msg.header.nlmsg_type) { 411 case AUDIT_LANDLOCK_ACCESS: 412 records->access++; 413 break; 414 case AUDIT_LANDLOCK_DOMAIN: 415 records->domain++; 416 break; 417 } 418 } while (true); 419 420 return 0; 421 } 422 423 static int audit_init(void) 424 { 425 int fd, err; 426 427 fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT); 428 if (fd < 0) 429 return -errno; 430 431 err = audit_set_status(fd, AUDIT_STATUS_ENABLED, 1); 432 if (err) 433 goto err_close; 434 435 err = audit_set_status(fd, AUDIT_STATUS_PID, getpid()); 436 if (err) 437 goto err_close; 438 439 /* Sets a timeout for negative tests. */ 440 err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default, 441 sizeof(audit_tv_default)); 442 if (err) { 443 err = -errno; 444 goto err_close; 445 } 446 447 /* 448 * Drains stale audit records that accumulated in the kernel backlog 449 * while no audit daemon socket was open. This happens when non-audit 450 * Landlock tests generate records while audit_enabled is non-zero (e.g. 451 * from boot configuration), or when domain deallocation records arrive 452 * asynchronously after a previous test's socket was closed. 453 */ 454 while (audit_recv(fd, NULL) == 0) 455 ; 456 457 return fd; 458 459 err_close: 460 close(fd); 461 return err; 462 } 463 464 static int audit_init_filter_exe(struct audit_filter *filter, const char *path) 465 { 466 char *absolute_path = NULL; 467 468 /* It is assume that there is not already filtering rules. */ 469 filter->record_type = AUDIT_EXE; 470 if (!path) { 471 int ret = readlink("/proc/self/exe", filter->exe, 472 sizeof(filter->exe) - 1); 473 if (ret < 0) 474 return -errno; 475 476 filter->exe_len = ret; 477 return 0; 478 } 479 480 absolute_path = realpath(path, NULL); 481 if (!absolute_path) 482 return -errno; 483 484 /* No need for the terminating NULL byte. */ 485 filter->exe_len = strlen(absolute_path); 486 if (filter->exe_len > sizeof(filter->exe)) 487 return -E2BIG; 488 489 memcpy(filter->exe, absolute_path, filter->exe_len); 490 free(absolute_path); 491 return 0; 492 } 493 494 static int audit_cleanup(int audit_fd, struct audit_filter *filter) 495 { 496 struct audit_filter new_filter; 497 498 if (audit_fd < 0 || !filter) { 499 int err; 500 501 /* 502 * Simulates audit_init_with_exe_filter() when called from 503 * FIXTURE_TEARDOWN_PARENT(). 504 */ 505 audit_fd = audit_init(); 506 if (audit_fd < 0) 507 return audit_fd; 508 509 filter = &new_filter; 510 err = audit_init_filter_exe(filter, NULL); 511 if (err) { 512 close(audit_fd); 513 return err; 514 } 515 } 516 517 /* Filters might not be in place. */ 518 audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE); 519 audit_filter_drop(audit_fd, AUDIT_DEL_RULE); 520 521 /* 522 * Because audit_cleanup() might not be called by the test auditd 523 * process, it might not be possible to explicitly set it. Anyway, 524 * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd 525 * process will exit. 526 */ 527 return close(audit_fd); 528 } 529 530 static int audit_init_with_exe_filter(struct audit_filter *filter) 531 { 532 int fd, err; 533 534 fd = audit_init(); 535 if (fd < 0) 536 return fd; 537 538 err = audit_init_filter_exe(filter, NULL); 539 if (err) 540 goto err_close; 541 542 err = audit_filter_exe(fd, filter, AUDIT_ADD_RULE); 543 if (err) 544 goto err_close; 545 546 return fd; 547 548 err_close: 549 close(fd); 550 return err; 551 } 552