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 #ifndef ARRAY_SIZE 24 #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 25 #endif 26 27 #ifndef __maybe_unused 28 #define __maybe_unused __attribute__((__unused__)) 29 #endif 30 31 #define REGEX_LANDLOCK_PREFIX "^audit([0-9.:]\\+): domain=\\([0-9a-f]\\+\\)" 32 33 struct audit_filter { 34 __u32 record_type; 35 size_t exe_len; 36 char exe[PATH_MAX]; 37 }; 38 39 struct audit_message { 40 struct nlmsghdr header; 41 union { 42 struct audit_status status; 43 struct audit_features features; 44 struct audit_rule_data rule; 45 struct nlmsgerr err; 46 char data[PATH_MAX + 200]; 47 }; 48 }; 49 50 static const struct timeval audit_tv_dom_drop = { 51 /* 52 * Because domain deallocation is tied to asynchronous credential 53 * freeing, receiving such event may take some time. In practice, 54 * on a small VM, it should not exceed 100k usec, but let's wait up 55 * to 1 second to be safe. 56 */ 57 .tv_sec = 1, 58 }; 59 60 static const struct timeval audit_tv_default = { 61 .tv_usec = 1, 62 }; 63 64 static int audit_send(const int fd, const struct audit_message *const msg) 65 { 66 struct sockaddr_nl addr = { 67 .nl_family = AF_NETLINK, 68 }; 69 int ret; 70 71 do { 72 ret = sendto(fd, msg, msg->header.nlmsg_len, 0, 73 (struct sockaddr *)&addr, sizeof(addr)); 74 } while (ret < 0 && errno == EINTR); 75 76 if (ret < 0) 77 return -errno; 78 79 if (ret != msg->header.nlmsg_len) 80 return -E2BIG; 81 82 return 0; 83 } 84 85 static int audit_recv(const int fd, struct audit_message *msg) 86 { 87 struct sockaddr_nl addr; 88 socklen_t addrlen = sizeof(addr); 89 struct audit_message msg_tmp; 90 int err; 91 92 if (!msg) 93 msg = &msg_tmp; 94 95 do { 96 err = recvfrom(fd, msg, sizeof(*msg), 0, 97 (struct sockaddr *)&addr, &addrlen); 98 } while (err < 0 && errno == EINTR); 99 100 if (err < 0) 101 return -errno; 102 103 if (addrlen != sizeof(addr) || addr.nl_pid != 0) 104 return -EINVAL; 105 106 /* Checks Netlink error or end of messages. */ 107 if (msg->header.nlmsg_type == NLMSG_ERROR) 108 return msg->err.error; 109 110 return 0; 111 } 112 113 static int audit_request(const int fd, 114 const struct audit_message *const request, 115 struct audit_message *reply) 116 { 117 struct audit_message msg_tmp; 118 bool first_reply = true; 119 int err; 120 121 err = audit_send(fd, request); 122 if (err) 123 return err; 124 125 if (!reply) 126 reply = &msg_tmp; 127 128 do { 129 if (first_reply) 130 first_reply = false; 131 else 132 reply = &msg_tmp; 133 134 err = audit_recv(fd, reply); 135 if (err) 136 return err; 137 } while (reply->header.nlmsg_type != NLMSG_ERROR && 138 reply->err.msg.nlmsg_type != request->header.nlmsg_type); 139 140 return reply->err.error; 141 } 142 143 static int audit_filter_exe(const int audit_fd, 144 const struct audit_filter *const filter, 145 const __u16 type) 146 { 147 struct audit_message msg = { 148 .header = { 149 .nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)) + 150 NLMSG_ALIGN(filter->exe_len), 151 .nlmsg_type = type, 152 .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, 153 }, 154 .rule = { 155 .flags = AUDIT_FILTER_EXCLUDE, 156 .action = AUDIT_NEVER, 157 .field_count = 1, 158 .fields[0] = filter->record_type, 159 .fieldflags[0] = AUDIT_NOT_EQUAL, 160 .values[0] = filter->exe_len, 161 .buflen = filter->exe_len, 162 } 163 }; 164 165 if (filter->record_type != AUDIT_EXE) 166 return -EINVAL; 167 168 memcpy(msg.rule.buf, filter->exe, filter->exe_len); 169 return audit_request(audit_fd, &msg, NULL); 170 } 171 172 static int audit_filter_drop(const int audit_fd, const __u16 type) 173 { 174 struct audit_message msg = { 175 .header = { 176 .nlmsg_len = NLMSG_SPACE(sizeof(msg.rule)), 177 .nlmsg_type = type, 178 .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, 179 }, 180 .rule = { 181 .flags = AUDIT_FILTER_EXCLUDE, 182 .action = AUDIT_NEVER, 183 .field_count = 1, 184 .fields[0] = AUDIT_MSGTYPE, 185 .fieldflags[0] = AUDIT_NOT_EQUAL, 186 .values[0] = AUDIT_LANDLOCK_DOMAIN, 187 } 188 }; 189 190 return audit_request(audit_fd, &msg, NULL); 191 } 192 193 static int audit_set_status(int fd, __u32 key, __u32 val) 194 { 195 const struct audit_message msg = { 196 .header = { 197 .nlmsg_len = NLMSG_SPACE(sizeof(msg.status)), 198 .nlmsg_type = AUDIT_SET, 199 .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, 200 }, 201 .status = { 202 .mask = key, 203 .enabled = key == AUDIT_STATUS_ENABLED ? val : 0, 204 .pid = key == AUDIT_STATUS_PID ? val : 0, 205 } 206 }; 207 208 return audit_request(fd, &msg, NULL); 209 } 210 211 /* Returns a pointer to the last filled character of @dst, which is `\0`. */ 212 static __maybe_unused char *regex_escape(const char *const src, char *dst, 213 size_t dst_size) 214 { 215 char *d = dst; 216 217 for (const char *s = src; *s; s++) { 218 switch (*s) { 219 case '$': 220 case '*': 221 case '.': 222 case '[': 223 case '\\': 224 case ']': 225 case '^': 226 if (d >= dst + dst_size - 2) 227 return (char *)-ENOMEM; 228 229 *d++ = '\\'; 230 *d++ = *s; 231 break; 232 default: 233 if (d >= dst + dst_size - 1) 234 return (char *)-ENOMEM; 235 236 *d++ = *s; 237 } 238 } 239 if (d >= dst + dst_size - 1) 240 return (char *)-ENOMEM; 241 242 *d = '\0'; 243 return d; 244 } 245 246 /* 247 * @domain_id: The domain ID extracted from the audit message (if the first part 248 * of @pattern is REGEX_LANDLOCK_PREFIX). It is set to 0 if the domain ID is 249 * not found. 250 */ 251 static int audit_match_record(int audit_fd, const __u16 type, 252 const char *const pattern, __u64 *domain_id) 253 { 254 struct audit_message msg; 255 int ret, err = 0; 256 bool matches_record = !type; 257 regmatch_t matches[2]; 258 regex_t regex; 259 260 ret = regcomp(®ex, pattern, 0); 261 if (ret) 262 return -EINVAL; 263 264 do { 265 memset(&msg, 0, sizeof(msg)); 266 err = audit_recv(audit_fd, &msg); 267 if (err) 268 goto out; 269 270 if (msg.header.nlmsg_type == type) 271 matches_record = true; 272 } while (!matches_record); 273 274 ret = regexec(®ex, msg.data, ARRAY_SIZE(matches), matches, 0); 275 if (ret) { 276 printf("DATA: %s\n", msg.data); 277 printf("ERROR: no match for pattern: %s\n", pattern); 278 err = -ENOENT; 279 } 280 281 if (domain_id) { 282 *domain_id = 0; 283 if (matches[1].rm_so != -1) { 284 int match_len = matches[1].rm_eo - matches[1].rm_so; 285 /* The maximal characters of a 2^64 hexadecimal number is 17. */ 286 char dom_id[18]; 287 288 if (match_len > 0 && match_len < sizeof(dom_id)) { 289 memcpy(dom_id, msg.data + matches[1].rm_so, 290 match_len); 291 dom_id[match_len] = '\0'; 292 if (domain_id) 293 *domain_id = strtoull(dom_id, NULL, 16); 294 } 295 } 296 } 297 298 out: 299 regfree(®ex); 300 return err; 301 } 302 303 static int __maybe_unused matches_log_domain_allocated(int audit_fd, pid_t pid, 304 __u64 *domain_id) 305 { 306 static const char log_template[] = REGEX_LANDLOCK_PREFIX 307 " status=allocated mode=enforcing pid=%d uid=[0-9]\\+" 308 " exe=\"[^\"]\\+\" comm=\".*_test\"$"; 309 char log_match[sizeof(log_template) + 10]; 310 int log_match_len; 311 312 log_match_len = 313 snprintf(log_match, sizeof(log_match), log_template, pid); 314 if (log_match_len > sizeof(log_match)) 315 return -E2BIG; 316 317 return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, 318 domain_id); 319 } 320 321 static int __maybe_unused matches_log_domain_deallocated( 322 int audit_fd, unsigned int num_denials, __u64 *domain_id) 323 { 324 static const char log_template[] = REGEX_LANDLOCK_PREFIX 325 " status=deallocated denials=%u$"; 326 char log_match[sizeof(log_template) + 10]; 327 int log_match_len; 328 329 log_match_len = snprintf(log_match, sizeof(log_match), log_template, 330 num_denials); 331 if (log_match_len > sizeof(log_match)) 332 return -E2BIG; 333 334 return audit_match_record(audit_fd, AUDIT_LANDLOCK_DOMAIN, log_match, 335 domain_id); 336 } 337 338 struct audit_records { 339 size_t access; 340 size_t domain; 341 }; 342 343 static int audit_count_records(int audit_fd, struct audit_records *records) 344 { 345 struct audit_message msg; 346 int err; 347 348 records->access = 0; 349 records->domain = 0; 350 351 do { 352 memset(&msg, 0, sizeof(msg)); 353 err = audit_recv(audit_fd, &msg); 354 if (err) { 355 if (err == -EAGAIN) 356 return 0; 357 else 358 return err; 359 } 360 361 switch (msg.header.nlmsg_type) { 362 case AUDIT_LANDLOCK_ACCESS: 363 records->access++; 364 break; 365 case AUDIT_LANDLOCK_DOMAIN: 366 records->domain++; 367 break; 368 } 369 } while (true); 370 371 return 0; 372 } 373 374 static int audit_init(void) 375 { 376 int fd, err; 377 378 fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_AUDIT); 379 if (fd < 0) 380 return -errno; 381 382 err = audit_set_status(fd, AUDIT_STATUS_ENABLED, 1); 383 if (err) 384 return err; 385 386 err = audit_set_status(fd, AUDIT_STATUS_PID, getpid()); 387 if (err) 388 return err; 389 390 /* Sets a timeout for negative tests. */ 391 err = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &audit_tv_default, 392 sizeof(audit_tv_default)); 393 if (err) 394 return -errno; 395 396 return fd; 397 } 398 399 static int audit_init_filter_exe(struct audit_filter *filter, const char *path) 400 { 401 char *absolute_path = NULL; 402 403 /* It is assume that there is not already filtering rules. */ 404 filter->record_type = AUDIT_EXE; 405 if (!path) { 406 int ret = readlink("/proc/self/exe", filter->exe, 407 sizeof(filter->exe) - 1); 408 if (ret < 0) 409 return -errno; 410 411 filter->exe_len = ret; 412 return 0; 413 } 414 415 absolute_path = realpath(path, NULL); 416 if (!absolute_path) 417 return -errno; 418 419 /* No need for the terminating NULL byte. */ 420 filter->exe_len = strlen(absolute_path); 421 if (filter->exe_len > sizeof(filter->exe)) 422 return -E2BIG; 423 424 memcpy(filter->exe, absolute_path, filter->exe_len); 425 free(absolute_path); 426 return 0; 427 } 428 429 static int audit_cleanup(int audit_fd, struct audit_filter *filter) 430 { 431 struct audit_filter new_filter; 432 433 if (audit_fd < 0 || !filter) { 434 int err; 435 436 /* 437 * Simulates audit_init_with_exe_filter() when called from 438 * FIXTURE_TEARDOWN_PARENT(). 439 */ 440 audit_fd = audit_init(); 441 if (audit_fd < 0) 442 return audit_fd; 443 444 filter = &new_filter; 445 err = audit_init_filter_exe(filter, NULL); 446 if (err) 447 return err; 448 } 449 450 /* Filters might not be in place. */ 451 audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE); 452 audit_filter_drop(audit_fd, AUDIT_DEL_RULE); 453 454 /* 455 * Because audit_cleanup() might not be called by the test auditd 456 * process, it might not be possible to explicitly set it. Anyway, 457 * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd 458 * process will exit. 459 */ 460 return close(audit_fd); 461 } 462 463 static int audit_init_with_exe_filter(struct audit_filter *filter) 464 { 465 int fd, err; 466 467 fd = audit_init(); 468 if (fd < 0) 469 return fd; 470 471 err = audit_init_filter_exe(filter, NULL); 472 if (err) 473 return err; 474 475 err = audit_filter_exe(fd, filter, AUDIT_ADD_RULE); 476 if (err) 477 return err; 478 479 return fd; 480 } 481