xref: /freebsd/contrib/file/src/seccomp.c (revision a259b98fa211ed87bfee58c575de4e2de94ee0fa)
1 /*
2  * Redistribution and use in source and binary forms, with or without
3  * modification, are permitted provided that the following conditions
4  * are met:
5  * 1. Redistributions of source code must retain the above copyright
6  *    notice immediately at the beginning of the file, without modification,
7  *    this list of conditions, and the following disclaimer.
8  * 2. Redistributions in binary form must reproduce the above copyright
9  *    notice, this list of conditions and the following disclaimer in the
10  *    documentation and/or other materials provided with the distribution.
11  *
12  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
16  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22  * SUCH DAMAGE.
23  */
24 /*
25  * libseccomp hooks.
26  */
27 #include "file.h"
28 
29 #ifndef	lint
30 FILE_RCSID("@(#)$File: seccomp.c,v 1.37 2026/05/11 16:06:03 christos Exp $")
31 #endif	/* lint */
32 
33 #if HAVE_LIBSECCOMP
34 #include "magic.h"
35 #include <seccomp.h> /* libseccomp */
36 #include <sys/prctl.h> /* prctl */
37 #include <sys/socket.h>
38 // See: https://sourceware.org/bugzilla/show_bug.cgi?id=32806
39 #include <asm/termbits.h>
40 #include <sys/ioctl.h>
41 #include <fcntl.h>
42 #include <stdlib.h>
43 #include <errno.h>
44 #include <unistd.h>
45 
46 #define DENY_RULE(call) \
47     do \
48 	if (seccomp_rule_add (ctx, SCMP_ACT_KILL, SCMP_SYS(call), 0) == -1) \
49 	    goto out; \
50     while (/*CONSTCOND*/0)
51 #define ALLOW_RULE(call) \
52     do \
53 	if (seccomp_rule_add (ctx, SCMP_ACT_ALLOW, SCMP_SYS(call), 0) == -1) \
54 	    goto out; \
55     while (/*CONSTCOND*/0)
56 /* ENOSYS makes glibc try an older syscall instead of dying. */
57 #define ERRNO_RULE(call) \
58     do \
59 	if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(ENOSYS), SCMP_SYS(call), 0) \
60 	    == -1) goto out; \
61     while (/*CONSTCOND*/0)
62 
63 #define ALLOW_IOCTL_RULE(param) \
64     do \
65 	if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 1, \
66 	    SCMP_CMP(1, SCMP_CMP_EQ, (scmp_datum_t)param, \
67 		     (scmp_datum_t)0)) == -1) \
68 		goto out; \
69     while (/*CONSTCOND*/0)
70 
71 static scmp_filter_ctx ctx;
72 
73 int
74 enable_sandbox(int flags, int action)
75 {
76 	struct stat sb;
77 	int needs_write;
78 
79 	/* Writes are needed when building the magic file (-C, -c, -l)
80 	   or when stdin is a pipe and we copy it to a tempfile. */
81 	needs_write = (action == FILE_COMPILE || action == FILE_CHECK ||
82 	    action == FILE_LIST);
83 	if (!needs_write && fstat(STDIN_FILENO, &sb) == 0 &&
84 	    S_ISFIFO(sb.st_mode))
85 		needs_write = 1;
86 
87 	if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1)
88 		return -1;
89 
90 #if 0
91 	if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == -1)
92 		return -1;
93 #endif
94 
95 	ctx = seccomp_init(SCMP_ACT_KILL);
96 	if (ctx == NULL)
97 		return -1;
98 
99 	ALLOW_RULE(access);
100 	ALLOW_RULE(brk);
101 	ALLOW_RULE(close);
102 	ALLOW_RULE(dup2);
103 	ALLOW_RULE(exit);
104 	ALLOW_RULE(exit_group);
105 #ifdef __NR_faccessat
106 	ALLOW_RULE(faccessat);
107 #endif
108 	ALLOW_RULE(fcntl);
109  	ALLOW_RULE(fcntl64);
110 #ifdef __NR_fstat
111 	ALLOW_RULE(fstat);
112 #endif
113  	ALLOW_RULE(fstat64);
114 #ifdef __NR_fstatat64
115 	ALLOW_RULE(fstatat64);
116 #endif
117 	ALLOW_RULE(futex);
118 	ALLOW_RULE(getdents);
119 #ifdef __NR_getdents64
120 	ALLOW_RULE(getdents64);
121 #endif
122 	ALLOW_RULE(getpid);	// Used by glibc in file_pipe2file()
123 	ALLOW_RULE(getrandom);	// Used by glibc in file_pipe2file()
124 #ifdef __NR_getcwd
125 	ALLOW_RULE(getcwd);	// GCONV_PATH=
126 #endif
127 #ifdef FIONREAD
128 	// called in src/compress.c under sread
129 	ALLOW_IOCTL_RULE(FIONREAD);
130 #endif
131 #ifdef TIOCGWINSZ
132 	// musl libc may call ioctl TIOCGWINSZ on stdout
133 	ALLOW_IOCTL_RULE(TIOCGWINSZ);
134 #endif
135 #ifdef TCGETS
136 	// glibc may call ioctl TCGETS on stdout on physical terminal
137 	ALLOW_IOCTL_RULE(TCGETS);
138 #endif
139 #ifdef TCGETS2
140 	// glibc may call ioctl TCGETS2 on stdout on physical terminal
141 	ALLOW_IOCTL_RULE(TCGETS2);
142 #endif
143 	ALLOW_RULE(lseek);
144  	ALLOW_RULE(_llseek);
145 	ALLOW_RULE(lstat);
146  	ALLOW_RULE(lstat64);
147 	ALLOW_RULE(madvise);
148 	ALLOW_RULE(mmap);
149  	ALLOW_RULE(mmap2);
150 	ALLOW_RULE(mprotect);
151 	ALLOW_RULE(mremap);
152 	ALLOW_RULE(munmap);
153 #ifdef __NR_newfstatat
154 	ALLOW_RULE(newfstatat);
155 #endif
156 
157 	/* Read-only opens are always fine. Writes are allowed when we
158 	   need them, otherwise return EACCES instead of killing. */
159 	if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1,
160 	    SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, 0)) == -1)
161 		goto out;
162 	if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 1,
163 	    SCMP_CMP(2, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, 0)) == -1)
164 		goto out;
165 	{
166 		uint32_t act = needs_write ? SCMP_ACT_ALLOW
167 		                           : SCMP_ACT_ERRNO(EACCES);
168 		if (seccomp_rule_add(ctx, act, SCMP_SYS(open), 1,
169 		    SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)) == -1)
170 			goto out;
171 		if (seccomp_rule_add(ctx, act, SCMP_SYS(open), 1,
172 		    SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)) == -1)
173 			goto out;
174 		if (seccomp_rule_add(ctx, act, SCMP_SYS(openat), 1,
175 		    SCMP_CMP(2, SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)) == -1)
176 			goto out;
177 		if (seccomp_rule_add(ctx, act, SCMP_SYS(openat), 1,
178 		    SCMP_CMP(2, SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)) == -1)
179 			goto out;
180 	}
181 	ALLOW_RULE(pread64);
182 	ALLOW_RULE(read);
183 	ALLOW_RULE(readlink);
184 #ifdef __NR_readlinkat
185 	ALLOW_RULE(readlinkat);
186 #endif
187 	ALLOW_RULE(rseq);	// Used by glibc to randomize malloc
188 	ALLOW_RULE(rt_sigaction);
189 	ALLOW_RULE(rt_sigprocmask);
190 	ALLOW_RULE(rt_sigreturn);
191 	ALLOW_RULE(select);
192 	ALLOW_RULE(stat);
193 	ALLOW_RULE(statx);
194 	ALLOW_RULE(stat64);
195 	ALLOW_RULE(sysinfo);
196 	if (needs_write) {
197 		ALLOW_RULE(umask);	/* used in file_pipe2file() */
198 		ALLOW_RULE(unlinkat);	/* used in file_pipe2file() */
199 #ifdef __NR_unlink
200 		ALLOW_RULE(unlink);
201 #endif
202 	}
203 	if (flags & MAGIC_PRESERVE_ATIME) {
204 		/* glibc 2.28+ uses utimes() via utimensat. */
205 		ALLOW_RULE(utimensat);
206 #ifdef __NR_utimes
207 		ALLOW_RULE(utimes);
208 #endif
209 	}
210 	/* Always allow writes. The kernel checks the fd, and restricting
211 	   here would just break stdout/stderr and the tempfile. */
212 	ALLOW_RULE(write);
213 	ALLOW_RULE(writev);
214 
215 	/* Newer variants we don't use. ENOSYS lets glibc fall back. */
216 	ERRNO_RULE(openat2);
217 	ERRNO_RULE(faccessat2);
218 	ERRNO_RULE(close_range);
219 
220 
221 #if 0
222 	// needed by valgrind
223 	ALLOW_RULE(gettid);
224 	ALLOW_RULE(rt_sigtimedwait);
225 #endif
226 
227 #if 0
228 	 /* special restrictions for socket, only allow AF_UNIX/AF_LOCAL */
229 	 if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
230 	     SCMP_CMP(0, SCMP_CMP_EQ, AF_UNIX)) == -1)
231 	 	goto out;
232 
233 	 if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 1,
234 	     SCMP_CMP(0, SCMP_CMP_EQ, AF_LOCAL)) == -1)
235 	 	goto out;
236 
237 
238 	 /* special restrictions for open, prevent opening files for writing */
239 	 if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 1,
240 	     SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY | O_RDWR, 0)) == -1)
241 	 	goto out;
242 
243 	 if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1,
244 	     SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)) == -1)
245 	 	goto out;
246 
247 	 if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(open), 1,
248 	     SCMP_CMP(1, SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)) == -1)
249 	 	goto out;
250 
251 
252 	 /* allow stderr */
253 	 if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1,
254 	     SCMP_CMP(0, SCMP_CMP_EQ, 2)) == -1)
255 		 goto out;
256 #endif
257 
258 #if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME)
259 	/* allow glibc to name malloc areas */
260 	if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 2,
261 	    SCMP_CMP32(0, SCMP_CMP_EQ, PR_SET_VMA),
262 	    SCMP_CMP64(1, SCMP_CMP_EQ, PR_SET_VMA_ANON_NAME)) == -1)
263 		goto out;
264 #endif
265 
266 	// applying filter...
267 	if (seccomp_load(ctx) == -1)
268 		goto out;
269 	// free ctx after the filter has been loaded into the kernel
270 	seccomp_release(ctx);
271 	return 0;
272 
273 out:
274 	// something went wrong
275 	seccomp_release(ctx);
276 	return -1;
277 }
278 #endif
279