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