xref: /linux/tools/testing/selftests/landlock/audit.h (revision 8804d970fab45726b3c7cd7f240b31122aa94219)
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 
audit_send(const int fd,const struct audit_message * const msg)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 
audit_recv(const int fd,struct audit_message * msg)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 
audit_request(const int fd,const struct audit_message * const request,struct audit_message * reply)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 
audit_filter_exe(const int audit_fd,const struct audit_filter * const filter,const __u16 type)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 
audit_filter_drop(const int audit_fd,const __u16 type)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 
audit_set_status(int fd,__u32 key,__u32 val)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`.  */
regex_escape(const char * const src,char * dst,size_t dst_size)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  */
audit_match_record(int audit_fd,const __u16 type,const char * const pattern,__u64 * domain_id)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(&regex, 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(&regex, 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(&regex);
298 	return err;
299 }
300 
matches_log_domain_allocated(int audit_fd,pid_t pid,__u64 * domain_id)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 
matches_log_domain_deallocated(int audit_fd,unsigned int num_denials,__u64 * domain_id)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 
audit_count_records(int audit_fd,struct audit_records * records)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 
audit_init(void)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 
audit_init_filter_exe(struct audit_filter * filter,const char * path)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 
audit_cleanup(int audit_fd,struct audit_filter * filter)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 
audit_init_with_exe_filter(struct audit_filter * filter)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