xref: /linux/tools/testing/selftests/landlock/audit.h (revision 30e268185e59c3d5a1233416a2135cfda5630644)
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 
audit_send(const int fd,const struct audit_message * const msg)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 
audit_recv(const int fd,struct audit_message * msg)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 
audit_request(const int fd,const struct audit_message * const request,struct audit_message * reply)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 
audit_filter_exe(const int audit_fd,const struct audit_filter * const filter,const __u16 type)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 
audit_filter_drop(const int audit_fd,const __u16 type)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 
audit_set_status(int fd,__u32 key,__u32 val)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`.  */
regex_escape(const char * const src,char * dst,size_t dst_size)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  */
audit_match_record(int audit_fd,const __u16 type,const char * const pattern,__u64 * domain_id)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(&regex, 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(&regex, 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(&regex);
300 	return err;
301 }
302 
matches_log_domain_allocated(int audit_fd,pid_t pid,__u64 * domain_id)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 
matches_log_domain_deallocated(int audit_fd,unsigned int num_denials,__u64 * domain_id)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 
audit_count_records(int audit_fd,struct audit_records * records)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 
audit_init(void)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 
audit_init_filter_exe(struct audit_filter * filter,const char * path)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 		filter->exe_len = readlink("/proc/self/exe", filter->exe,
407 					   sizeof(filter->exe) - 1);
408 		if (filter->exe_len < 0)
409 			return -errno;
410 
411 		return 0;
412 	}
413 
414 	absolute_path = realpath(path, NULL);
415 	if (!absolute_path)
416 		return -errno;
417 
418 	/* No need for the terminating NULL byte. */
419 	filter->exe_len = strlen(absolute_path);
420 	if (filter->exe_len > sizeof(filter->exe))
421 		return -E2BIG;
422 
423 	memcpy(filter->exe, absolute_path, filter->exe_len);
424 	free(absolute_path);
425 	return 0;
426 }
427 
audit_cleanup(int audit_fd,struct audit_filter * filter)428 static int audit_cleanup(int audit_fd, struct audit_filter *filter)
429 {
430 	struct audit_filter new_filter;
431 
432 	if (audit_fd < 0 || !filter) {
433 		int err;
434 
435 		/*
436 		 * Simulates audit_init_with_exe_filter() when called from
437 		 * FIXTURE_TEARDOWN_PARENT().
438 		 */
439 		audit_fd = audit_init();
440 		if (audit_fd < 0)
441 			return audit_fd;
442 
443 		filter = &new_filter;
444 		err = audit_init_filter_exe(filter, NULL);
445 		if (err)
446 			return err;
447 	}
448 
449 	/* Filters might not be in place. */
450 	audit_filter_exe(audit_fd, filter, AUDIT_DEL_RULE);
451 	audit_filter_drop(audit_fd, AUDIT_DEL_RULE);
452 
453 	/*
454 	 * Because audit_cleanup() might not be called by the test auditd
455 	 * process, it might not be possible to explicitly set it.  Anyway,
456 	 * AUDIT_STATUS_ENABLED will implicitly be set to 0 when the auditd
457 	 * process will exit.
458 	 */
459 	return close(audit_fd);
460 }
461 
audit_init_with_exe_filter(struct audit_filter * filter)462 static int audit_init_with_exe_filter(struct audit_filter *filter)
463 {
464 	int fd, err;
465 
466 	fd = audit_init();
467 	if (fd < 0)
468 		return fd;
469 
470 	err = audit_init_filter_exe(filter, NULL);
471 	if (err)
472 		return err;
473 
474 	err = audit_filter_exe(fd, filter, AUDIT_ADD_RULE);
475 	if (err)
476 		return err;
477 
478 	return fd;
479 }
480