1 /*
2  * Copyright (c) 2017 SUSE.  All Rights Reserved.
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of version 2 or any later of the GNU General Public License
6  * as published by the Free Software Foundation.
7  *
8  * This program is distributed in the hope that it would be useful, but
9  * WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11  *
12  * Further, this software is distributed without any warranty that it is
13  * free of the rightful claim of any third person regarding infringement
14  * or the like.  Any license provided herein, whether implied or
15  * otherwise, applies only to this software file.  Patent licenses, if
16  * any, provided herein do not apply to combinations of this program with
17  * other software, or any other product whatsoever.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write the Free Software Foundation, Inc.,
21  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22  *
23  * Started by Jan Kara <jack@suse.cz>
24  *
25  * DESCRIPTION
26  *     Check that fanotify permission events are handled properly on instance
27  *     destruction.
28  *
29  * Kernel crashes should be fixed by:
30  *  96d41019e3ac "fanotify: fix list corruption in fanotify_get_response()"
31  *
32  * Kernel hangs should be fixed by:
33  *  05f0e38724e8 "fanotify: Release SRCU lock when waiting for userspace response"
34  */
35 #define _GNU_SOURCE
36 #include "config.h"
37 
38 #include <stdio.h>
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <sys/stat.h>
42 #include <sys/types.h>
43 #include <sys/fcntl.h>
44 #include <sys/wait.h>
45 #include <errno.h>
46 #include <string.h>
47 #include <signal.h>
48 #include <sys/syscall.h>
49 #include "tst_test.h"
50 #include "lapi/syscalls.h"
51 #include "fanotify.h"
52 
53 #if defined(HAVE_SYS_FANOTIFY_H)
54 #include <sys/fanotify.h>
55 
56 #define BUF_SIZE 256
57 static char fname[BUF_SIZE];
58 static char buf[BUF_SIZE];
59 static volatile int fd_notify;
60 
61 /* Number of children we start */
62 #define MAX_CHILDREN 16
63 static pid_t child_pid[MAX_CHILDREN];
64 
65 /* Number of children we don't respond to before stopping */
66 #define MAX_NOT_RESPONDED 4
67 
68 static void generate_events(void)
69 {
70 	int fd;
71 
72 	/*
73 	 * generate sequence of events
74 	 */
75 	if ((fd = open(fname, O_RDWR | O_CREAT, 0700)) == -1)
76 		exit(1);
77 
78 	/* Run until killed... */
79 	while (1) {
80 		lseek(fd, 0, SEEK_SET);
81 		if (read(fd, buf, BUF_SIZE) == -1)
82 			exit(3);
83 	}
84 }
85 
86 static void run_children(void)
87 {
88 	int i;
89 
90 	for (i = 0; i < MAX_CHILDREN; i++) {
91 		child_pid[i] = SAFE_FORK();
92 		if (!child_pid[i]) {
93 			/* Child will generate events now */
94 			close(fd_notify);
95 			generate_events();
96 			exit(0);
97 		}
98 	}
99 }
100 
101 static int stop_children(void)
102 {
103 	int child_ret;
104 	int i, ret = 0;
105 
106 	for (i = 0; i < MAX_CHILDREN; i++)
107 		SAFE_KILL(child_pid[i], SIGKILL);
108 
109 	for (i = 0; i < MAX_CHILDREN; i++) {
110 		SAFE_WAITPID(child_pid[i], &child_ret, 0);
111 		if (!WIFSIGNALED(child_ret))
112 			ret = 1;
113 	}
114 
115 	return ret;
116 }
117 
118 static int setup_instance(void)
119 {
120 	int fd;
121 
122 	fd = SAFE_FANOTIFY_INIT(FAN_CLASS_CONTENT, O_RDONLY);
123 
124 	if (fanotify_mark(fd, FAN_MARK_ADD, FAN_ACCESS_PERM, AT_FDCWD,
125 			  fname) < 0) {
126 		close(fd);
127 		if (errno == EINVAL) {
128 			tst_brk(TCONF | TERRNO,
129 				"CONFIG_FANOTIFY_ACCESS_PERMISSIONS not "
130 				"configured in kernel?");
131 		} else {
132 			tst_brk(TBROK | TERRNO,
133 				"fanotify_mark (%d, FAN_MARK_ADD, FAN_ACCESS_PERM, "
134 				"AT_FDCWD, %s) failed.", fd, fname);
135 		}
136 	}
137 
138 	return fd;
139 }
140 
141 static void loose_fanotify_events(void)
142 {
143 	int not_responded = 0;
144 
145 	/*
146 	 * check events
147 	 */
148 	while (not_responded < MAX_NOT_RESPONDED) {
149 		struct fanotify_event_metadata event;
150 		struct fanotify_response resp;
151 
152 		/* Get more events */
153 		SAFE_READ(1, fd_notify, &event, sizeof(event));
154 
155 		if (event.mask != FAN_ACCESS_PERM) {
156 			tst_res(TFAIL,
157 				"get event: mask=%llx (expected %llx) "
158 				"pid=%u fd=%u",
159 				(unsigned long long)event.mask,
160 				(unsigned long long)FAN_ACCESS_PERM,
161 				(unsigned)event.pid, event.fd);
162 			break;
163 		}
164 
165 		/*
166 		 * We respond to permission event with 95% percent
167 		 * probability. */
168 		if (random() % 100 > 5) {
169 			/* Write response to permission event */
170 			resp.fd = event.fd;
171 			resp.response = FAN_ALLOW;
172 			SAFE_WRITE(1, fd_notify, &resp, sizeof(resp));
173 		} else {
174 			not_responded++;
175 		}
176 		SAFE_CLOSE(event.fd);
177 	}
178 }
179 
180 static void test_fanotify(void)
181 {
182 	int newfd;
183 	int ret;
184 
185 	fd_notify = setup_instance();
186 	run_children();
187 	loose_fanotify_events();
188 
189 	/*
190 	 * Create and destroy another instance. This may hang if
191 	 * unanswered fanotify events block notification subsystem.
192 	 */
193 	newfd = setup_instance();
194 	if (close(newfd)) {
195 		tst_brk(TBROK | TERRNO, "close(%d) failed", newfd);
196 	}
197 
198 	tst_res(TPASS, "second instance destroyed successfully");
199 
200 	/*
201 	 * Now destroy the fanotify instance while there are permission
202 	 * events at various stages of processing. This may provoke
203 	 * kernel hangs or crashes.
204 	 */
205 	SAFE_CLOSE(fd_notify);
206 
207 	ret = stop_children();
208 	if (ret)
209 		tst_res(TFAIL, "child exited for unexpected reason");
210 	else
211 		tst_res(TPASS, "all children exited successfully");
212 }
213 
214 static void setup(void)
215 {
216 	sprintf(fname, "fname_%d", getpid());
217 	SAFE_FILE_PRINTF(fname, "%s", fname);
218 }
219 
220 static void cleanup(void)
221 {
222 	if (fd_notify > 0)
223 		SAFE_CLOSE(fd_notify);
224 }
225 
226 static struct tst_test test = {
227 	.test_all = test_fanotify,
228 	.setup = setup,
229 	.cleanup = cleanup,
230 	.needs_tmpdir = 1,
231 	.forks_child = 1,
232 	.needs_root = 1,
233 };
234 
235 #else
236 	TST_TEST_TCONF("system doesn't have required fanotify support");
237 #endif
238