File Index
Files and directories
Sign in
FILE

audit.h

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/landlock/audit.h C
  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
 31struct audit_filter {
 32	__u32 record_type;
 33	size_t exe_len;
 34	char exe[PATH_MAX];
 35};
 36
 37struct 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
 48static 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
 58static const struct timeval audit_tv_default = {
 59	.tv_usec = 1,
 60};
 61
 62static 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
 83static 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
111static 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
141static 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
170static 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
191static 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`.  */
210static __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 */
249static 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
296out:
297	regfree(&regex);
298	return err;
299}
300
301static 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
319static 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
336struct audit_records {
337	size_t access;
338	size_t domain;
339};
340
341static 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
372static 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
397static 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
427static 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
461static 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}
~/ Read-only file preview.