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; 253 int ret, err = 0; 254 bool matches_record = !type; 255 regmatch_t matches[2]; 256 regex_t regex; 257 258 ret = regcomp(®ex, pattern, 0); 259 if (ret) 260 return -EINVAL; 261 262 do { 263 memset(&msg, 0, sizeof(msg)); 264 err = audit_recv(audit_fd, &msg); 265 if (err) 266 goto out; 267 268 if (msg.header.nlmsg_type == type) 269 matches_record = true; 270 } while (!matches_record); 271 272 ret = regexec(®ex, msg.data, ARRAY_SIZE(matches), matches, 0); 273 if (ret) { 274 printf("DATA: %s\n", msg.data); 275 printf("ERROR: no match for pattern: %s\n", pattern); 276 err = -ENOENT; 277 } 278 279 if (domain_id) { 280 *domain_id = 0; 281 if (matches[1].rm_so != -1) { 282 int match_len = matches[1].rm_eo - matches[1].rm_so; 283 /* The maximal characters of a 2^64 hexadecimal number is 17. */ 284 char dom_id[18]; 285 286 if (match_len > 0 && match_len < sizeof(dom_id)) { 287 memcpy(dom_id, msg.data + matches[1].rm_so, 288 match_len); 289 dom_id[match_len] = '\0'; 290 if (domain_id) 291 *domain_id = strtoull(dom_id, NULL, 16); 292 } 293 } 294 } 295 296 out: 297 regfree(®ex); 298 return err; 299 } 300 301 static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid, 302 __u64 *domain_id) 303 { 304 static const char log_template[] = REGEX_LANDLOCK_PREFIX 305 " status=allocated mode=enforcing pid=%d uid=[0-9]\\+" 306 " exe=\"[^\"]\\+\" comm=\".*_test\"$"; 307 char log_match[sizeof(log_template) + 10]; 308 int log_match_len; 309 310 log_match_len = 311 snprintf(log_match, sizeof(log_match), log_template, pid); 312 if (log_match_len > sizeof(log_match)) 313 return -E2BIG; 314 315 return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, 316 domain_id); 317 } 318 319 static int __maybe_unused matches_log_domain_deallocated( 320 int audit_fd, unsigned int num_denials, __u64 *domain_id) 321 { 322 static const char log_template[] = REGEX_LANDLOCK_PREFIX 323 " status=deallocated denials=%u$"; 324 char log_match[sizeof(log_template) + 10]; 325 int log_match_len; 326 327 log_match_len = snprintf(log_match, sizeof(log_match), log_template, 328 num_denials); 329 if (log_match_len > sizeof(log_match)) 330 return -E2BIG; 331 332 return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, 333 domain_id); 334 } 335 336 struct audit_records { 337 size_t access; 338 size_t domain; 339 }; 340 341 static int audit_count_records(int audit_fd, struct audit_records *records) 342 { 343 struct audit_message msg; 344 int err; 345 346 records->access = 0; 347 records->domain = 0; 348 349 do { 350 memset(&msg, 0, sizeof(msg)); 351 err = audit_recv(audit_fd, &msg); 352 if (err) { 353 if (err == -EAGAIN) 354 return 0; 355 else 356 return err; 357 } 358 359 switch (msg.header.nlmsg_type) { 360 case AUDIT_LANDLOCK_ACCESS: 361 records->access++; 362 break; 363 case AUDIT_LANDLOCK_DOMAIN: 364 records->domain++; 365 break; 366 } 367 } while (true); 368 369 return 0; 370 } 371 372 static int audit_init(void) 373 { 374 int fd, err; 375 376 fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT); 377 if (fd < 0) 378 return -errno; 379 380 err = audit_set_status(fd, AUDIT_STATUS_ENABLED, 1); 381 if (err) 382 return err; 383 384 err = audit_set_status(fd, AUDIT_STATUS_PID, getpid()); 385 if (err) 386 return err; 387 388 /* Sets a timeout for negative tests. */ 389 err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default, 390 sizeof(audit_tv_default)); 391 if (err) 392 return -errno; 393 394 return fd; 395 } 396 397 static int audit_init_filter_exe(struct audit_filter *filter, const char *path) 398 { 399 char *absolute_path = NULL; 400 401 /* It is assume that there is not already filtering rules. */ 402 filter->record_type = AUDIT_EXE; 403 if (!path) { 404 int ret = readlink("/proc/self/exe", filter->exe, 405 sizeof(filter->exe) - 1); 406 if (ret < 0) 407 return -errno; 408 409 filter->exe_len = ret; 410 return 0; 411 } 412 413 absolute_path = realpath(path, NULL); 414 if (!absolute_path) 415 return -errno; 416 417 /* No need for the terminating NULL byte. */ 418 filter->exe_len = strlen(absolute_path); 419 if (filter->exe_len > sizeof(filter->exe)) 420 return -E2BIG; 421 422 memcpy(filter->exe, absolute_path, filter->exe_len); 423 free(absolute_path); 424 return 0; 425 } 426 427 static int audit_cleanup(int audit_fd, struct audit_filter *filter) 428 { 429 struct audit_filter new_filter; 430 431 if (audit_fd < 0 || !filter) { 432 int err; 433 434 /* 435 * Simulates audit_init_with_exe_filter() when called from 436 * FIXTURE_TEARDOWN_PARENT(). 437 */ 438 audit_fd = audit_init(); 439 if (audit_fd < 0) 440 return audit_fd; 441 442 filter = &new_filter; 443 err = audit_init_filter_exe(filter, NULL); 444 if (err) 445 return err; 446 } 447 448 /* Filters might not be in place. */ 449 audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE); 450 audit_filter_drop(audit_fd, AUDIT_DEL_RULE); 451 452 /* 453 * Because audit_cleanup() might not be called by the test auditd 454 * process, it might not be possible to explicitly set it. Anyway, 455 * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd 456 * process will exit. 457 */ 458 return close(audit_fd); 459 } 460 461 static int audit_init_with_exe_filter(struct audit_filter *filter) 462 { 463 int fd, err; 464 465 fd = audit_init(); 466 if (fd < 0) 467 return fd; 468 469 err = audit_init_filter_exe(filter, NULL); 470 if (err) 471 return err; 472 473 err = audit_filter_exe(fd, filter, AUDIT_ADD_RULE); 474 if (err) 475 return err; 476 477 return fd; 478 } 479