diff options
| author | Philipp Stephani | 2020-12-17 11:20:55 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2021-04-10 21:01:46 +0200 |
| commit | f3a7536aa29e6690c66f69174079ba51d40c0443 (patch) | |
| tree | 5f16989652dc6294245f5dc767bbf51cfd9a7983 | |
| parent | 2d17e0124e4232db6344b18cec466eb31920e675 (diff) | |
| download | emacs-scratch/seccomp-helper-binary.tar.gz emacs-scratch/seccomp-helper-binary.zip | |
Add a helper binary to create a basic Secure Computing filter.scratch/seccomp-helper-binary
The binary uses the 'seccomp' helper library. The library isn't
needed to load the generated Secure Computing filter.
* configure.ac: Check for 'seccomp' header and library.
* lib-src/seccomp-filter.c: New helper binary to generate a generic
Secure Computing filter for GNU/Linux.
* lib-src/Makefile.in (DONT_INSTALL): Add 'seccomp-filter' helper
binary if possible.
(all): Add Secure Computing filter file if possible.
(seccomp-filter$(EXEEXT)): Compile helper binary.
(seccomp-filter.bpf seccomp-filter.pfc): Generate filter files.
* test/src/emacs-tests.el (emacs-tests/seccomp/allows-stdout)
(emacs-tests/seccomp/forbids-subprocess): New unit tests.
* test/Makefile.in (src/emacs-tests.log): Add dependency on the helper
binary.
| -rw-r--r-- | .gitignore | 5 | ||||
| -rw-r--r-- | configure.ac | 5 | ||||
| -rw-r--r-- | lib-src/Makefile.in | 19 | ||||
| -rw-r--r-- | lib-src/seccomp-filter.c | 321 | ||||
| -rw-r--r-- | test/Makefile.in | 2 | ||||
| l--------- | test/src/emacs-resources/seccomp-filter.bpf | 1 | ||||
| -rw-r--r-- | test/src/emacs-tests.el | 49 |
7 files changed, 402 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index b653ef215b9..ecf768dc4d6 100644 --- a/.gitignore +++ b/.gitignore | |||
| @@ -188,6 +188,7 @@ lib-src/make-docfile | |||
| 188 | lib-src/make-fingerprint | 188 | lib-src/make-fingerprint |
| 189 | lib-src/movemail | 189 | lib-src/movemail |
| 190 | lib-src/profile | 190 | lib-src/profile |
| 191 | lib-src/seccomp-filter | ||
| 191 | lib-src/test-distrib | 192 | lib-src/test-distrib |
| 192 | lib-src/update-game-score | 193 | lib-src/update-game-score |
| 193 | nextstep/Cocoa/Emacs.base/Contents/Info.plist | 194 | nextstep/Cocoa/Emacs.base/Contents/Info.plist |
| @@ -301,3 +302,7 @@ nt/emacs.rc | |||
| 301 | nt/emacsclient.rc | 302 | nt/emacsclient.rc |
| 302 | src/gdb.ini | 303 | src/gdb.ini |
| 303 | /var/ | 304 | /var/ |
| 305 | |||
| 306 | # Seccomp filter files. | ||
| 307 | lib-src/seccomp-filter.bpf | ||
| 308 | lib-src/seccomp-filter.pfc | ||
diff --git a/configure.ac b/configure.ac index 684788a4d33..0c4772a2b96 100644 --- a/configure.ac +++ b/configure.ac | |||
| @@ -4181,6 +4181,11 @@ AC_SUBST([LIBS_MAIL]) | |||
| 4181 | 4181 | ||
| 4182 | AC_CHECK_HEADERS([linux/seccomp.h], [HAVE_SECCOMP=yes]) | 4182 | AC_CHECK_HEADERS([linux/seccomp.h], [HAVE_SECCOMP=yes]) |
| 4183 | 4183 | ||
| 4184 | LIBSECCOMP= | ||
| 4185 | AC_CHECK_HEADER([seccomp.h], | ||
| 4186 | [AC_CHECK_LIB([seccomp], [seccomp_init], [LIBSECCOMP=-lseccomp])]) | ||
| 4187 | AC_SUBST([LIBSECCOMP]) | ||
| 4188 | |||
| 4184 | OLD_LIBS=$LIBS | 4189 | OLD_LIBS=$LIBS |
| 4185 | LIBS="$LIB_PTHREAD $LIB_MATH $LIBS" | 4190 | LIBS="$LIB_PTHREAD $LIB_MATH $LIBS" |
| 4186 | AC_CHECK_FUNCS(accept4 fchdir gethostname \ | 4191 | AC_CHECK_FUNCS(accept4 fchdir gethostname \ |
diff --git a/lib-src/Makefile.in b/lib-src/Makefile.in index 05eb524d19b..1942882004e 100644 --- a/lib-src/Makefile.in +++ b/lib-src/Makefile.in | |||
| @@ -189,6 +189,12 @@ LIB_WSOCK32=@LIB_WSOCK32@ | |||
| 189 | ## Extra libraries for etags | 189 | ## Extra libraries for etags |
| 190 | LIBS_ETAGS = $(LIB_CLOCK_GETTIME) $(LIB_GETRANDOM) | 190 | LIBS_ETAGS = $(LIB_CLOCK_GETTIME) $(LIB_GETRANDOM) |
| 191 | 191 | ||
| 192 | LIBSECCOMP=@LIBSECCOMP@ | ||
| 193 | |||
| 194 | ifneq ($(LIBSECCOMP),) | ||
| 195 | DONT_INSTALL += seccomp-filter$(EXEEXT) | ||
| 196 | endif | ||
| 197 | |||
| 192 | ## Extra libraries to use when linking movemail. | 198 | ## Extra libraries to use when linking movemail. |
| 193 | LIBS_MOVE = $(LIBS_MAIL) $(KRB4LIB) $(DESLIB) $(KRB5LIB) $(CRYPTOLIB) \ | 199 | LIBS_MOVE = $(LIBS_MAIL) $(KRB4LIB) $(DESLIB) $(KRB5LIB) $(CRYPTOLIB) \ |
| 194 | $(COM_ERRLIB) $(LIBHESIOD) $(LIBRESOLV) $(LIB_WSOCK32) $(LIBS_ETAGS) | 200 | $(COM_ERRLIB) $(LIBHESIOD) $(LIBRESOLV) $(LIB_WSOCK32) $(LIBS_ETAGS) |
| @@ -218,6 +224,10 @@ config_h = ../src/config.h $(srcdir)/../src/conf_post.h | |||
| 218 | 224 | ||
| 219 | all: ${EXE_FILES} ${SCRIPTS} | 225 | all: ${EXE_FILES} ${SCRIPTS} |
| 220 | 226 | ||
| 227 | ifneq ($(LIBSECCOMP),) | ||
| 228 | all: seccomp-filter.bpf | ||
| 229 | endif | ||
| 230 | |||
| 221 | .PHONY: all need-blessmail maybe-blessmail | 231 | .PHONY: all need-blessmail maybe-blessmail |
| 222 | 232 | ||
| 223 | LOADLIBES = ../lib/libgnu.a $(LIBS_SYSTEM) | 233 | LOADLIBES = ../lib/libgnu.a $(LIBS_SYSTEM) |
| @@ -400,4 +410,13 @@ update-game-score${EXEEXT}: ${srcdir}/update-game-score.c $(NTLIB) $(config_h) | |||
| 400 | emacsclient.res: ../nt/emacsclient.rc $(NTINC)/../icons/emacs.ico | 410 | emacsclient.res: ../nt/emacsclient.rc $(NTINC)/../icons/emacs.ico |
| 401 | $(AM_V_RC)$(WINDRES) -O coff --include-dir=$(NTINC)/.. -o $@ $< | 411 | $(AM_V_RC)$(WINDRES) -O coff --include-dir=$(NTINC)/.. -o $@ $< |
| 402 | 412 | ||
| 413 | ifneq ($(LIBSECCOMP),) | ||
| 414 | seccomp-filter$(EXEEXT): $(srcdir)/seccomp-filter.c $(config_h) | ||
| 415 | $(AM_V_CCLD)$(CC) $(ALL_CFLAGS) $< $(LIBSECCOMP) -o $@ | ||
| 416 | |||
| 417 | seccomp-filter.bpf seccomp-filter.pfc: seccomp-filter$(EXEEXT) | ||
| 418 | $(AM_V_GEN)./seccomp-filter$(EXEEXT) \ | ||
| 419 | seccomp-filter.bpf seccomp-filter.pfc | ||
| 420 | endif | ||
| 421 | |||
| 403 | ## Makefile ends here. | 422 | ## Makefile ends here. |
diff --git a/lib-src/seccomp-filter.c b/lib-src/seccomp-filter.c new file mode 100644 index 00000000000..9918fb025ef --- /dev/null +++ b/lib-src/seccomp-filter.c | |||
| @@ -0,0 +1,321 @@ | |||
| 1 | /* Generate a Secure Computing filter definition file. | ||
| 2 | |||
| 3 | Copyright (C) 2020 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | This file is part of GNU Emacs. | ||
| 6 | |||
| 7 | GNU Emacs is free software: you can redistribute it and/or modify it | ||
| 8 | under the terms of the GNU General Public License as published by the | ||
| 9 | Free Software Foundation, either version 3 of the License, or (at your | ||
| 10 | option) any later version. | ||
| 11 | |||
| 12 | GNU Emacs is distributed in the hope that it will be useful, but | ||
| 13 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 15 | General Public License for more details. | ||
| 16 | |||
| 17 | You should have received a copy of the GNU General Public License | ||
| 18 | along with GNU Emacs. If not, see | ||
| 19 | <https://www.gnu.org/licenses/>. */ | ||
| 20 | |||
| 21 | /* This program creates a small Secure Computing filter usable for a | ||
| 22 | typical minimal Emacs sandbox. See the man page for `seccomp' for | ||
| 23 | details about Secure Computing filters. This program requires the | ||
| 24 | `libseccomp' library. However, the resulting filter file requires | ||
| 25 | only a Linux kernel supporting the Secure Computing extension. | ||
| 26 | |||
| 27 | Usage: | ||
| 28 | |||
| 29 | seccomp-filter out.bpf out.pfc | ||
| 30 | |||
| 31 | This writes the raw `struct sock_filter' array to out.bpf and a | ||
| 32 | human-readable representation to out.pfc. */ | ||
| 33 | |||
| 34 | #include "config.h" | ||
| 35 | |||
| 36 | #include <errno.h> | ||
| 37 | #include <limits.h> | ||
| 38 | #include <stdarg.h> | ||
| 39 | #include <stdbool.h> | ||
| 40 | #include <stdlib.h> | ||
| 41 | #include <stdint.h> | ||
| 42 | #include <stdio.h> | ||
| 43 | |||
| 44 | #include <sys/ioctl.h> | ||
| 45 | #include <sys/mman.h> | ||
| 46 | #include <sys/prctl.h> | ||
| 47 | #include <sys/types.h> | ||
| 48 | #include <sys/stat.h> | ||
| 49 | #include <linux/futex.h> | ||
| 50 | #include <fcntl.h> | ||
| 51 | #include <sched.h> | ||
| 52 | #include <seccomp.h> | ||
| 53 | #include <unistd.h> | ||
| 54 | |||
| 55 | #include "verify.h" | ||
| 56 | |||
| 57 | static ATTRIBUTE_FORMAT_PRINTF (2, 3) _Noreturn void | ||
| 58 | fail (int error, const char *format, ...) | ||
| 59 | { | ||
| 60 | va_list ap; | ||
| 61 | va_start (ap, format); | ||
| 62 | if (error == 0) | ||
| 63 | vfprintf (stderr, format, ap); | ||
| 64 | else | ||
| 65 | { | ||
| 66 | char buffer[1000]; | ||
| 67 | vsnprintf (buffer, sizeof buffer, format, ap); | ||
| 68 | errno = error; | ||
| 69 | perror (buffer); | ||
| 70 | } | ||
| 71 | va_end (ap); | ||
| 72 | fflush (NULL); | ||
| 73 | exit (EXIT_FAILURE); | ||
| 74 | } | ||
| 75 | |||
| 76 | /* This binary is trivial, so we use a single global filter context | ||
| 77 | object that we release using `atexit'. */ | ||
| 78 | |||
| 79 | static scmp_filter_ctx ctx; | ||
| 80 | |||
| 81 | static void | ||
| 82 | release_context (void) | ||
| 83 | { | ||
| 84 | seccomp_release (ctx); | ||
| 85 | } | ||
| 86 | |||
| 87 | /* Wrapper functions and macros for libseccomp functions. We exit | ||
| 88 | immediately upon any error to avoid error checking noise. */ | ||
| 89 | |||
| 90 | static void | ||
| 91 | set_attribute (enum scmp_filter_attr attr, uint32_t value) | ||
| 92 | { | ||
| 93 | int status = seccomp_attr_set (ctx, attr, value); | ||
| 94 | if (status < 0) | ||
| 95 | fail (-status, "seccomp_attr_set (ctx, %u, %u)", attr, value); | ||
| 96 | } | ||
| 97 | |||
| 98 | /* Like `seccomp_rule_add (ACTION, SYSCALL, ...)', except that you | ||
| 99 | don't have to specify the number of comparator arguments, and any | ||
| 100 | failure will exit the process. */ | ||
| 101 | |||
| 102 | #define RULE(action, syscall, ...) \ | ||
| 103 | do \ | ||
| 104 | { \ | ||
| 105 | const struct scmp_arg_cmp arg_array[] = {__VA_ARGS__}; \ | ||
| 106 | enum { arg_cnt = sizeof arg_array / sizeof *arg_array }; \ | ||
| 107 | int status = seccomp_rule_add_array (ctx, (action), (syscall), \ | ||
| 108 | arg_cnt, arg_array); \ | ||
| 109 | if (status < 0) \ | ||
| 110 | fail (-status, "seccomp_rule_add_array (%s, %s, %d, {%s})", \ | ||
| 111 | #action, #syscall, arg_cnt, #__VA_ARGS__); \ | ||
| 112 | } \ | ||
| 113 | while (false) | ||
| 114 | |||
| 115 | static void | ||
| 116 | export_filter (const char *file, | ||
| 117 | int (*function) (const scmp_filter_ctx, int), | ||
| 118 | const char *name) | ||
| 119 | { | ||
| 120 | int fd = TEMP_FAILURE_RETRY ( | ||
| 121 | open (file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, | ||
| 122 | 0644)); | ||
| 123 | if (fd < 0) | ||
| 124 | fail (errno, "open %s", file); | ||
| 125 | int status = function (ctx, fd); | ||
| 126 | if (status < 0) | ||
| 127 | fail (-status, "%s", name); | ||
| 128 | if (close (fd) != 0) | ||
| 129 | fail (errno, "close"); | ||
| 130 | } | ||
| 131 | |||
| 132 | #define EXPORT_FILTER(file, function) \ | ||
| 133 | export_filter ((file), (function), #function) | ||
| 134 | |||
| 135 | int | ||
| 136 | main (int argc, char **argv) | ||
| 137 | { | ||
| 138 | if (argc != 3) | ||
| 139 | fail (0, "usage: %s out.bpf out.pfc", argv[0]); | ||
| 140 | |||
| 141 | /* Any unhandled syscall should abort the Emacs process. */ | ||
| 142 | ctx = seccomp_init (SCMP_ACT_KILL_PROCESS); | ||
| 143 | if (ctx == NULL) | ||
| 144 | fail (0, "seccomp_init"); | ||
| 145 | atexit (release_context); | ||
| 146 | |||
| 147 | /* We want to abort immediately if the architecture is unknown. */ | ||
| 148 | set_attribute (SCMP_FLTATR_ACT_BADARCH, SCMP_ACT_KILL_PROCESS); | ||
| 149 | set_attribute (SCMP_FLTATR_CTL_NNP, 1); | ||
| 150 | set_attribute (SCMP_FLTATR_CTL_TSYNC, 1); | ||
| 151 | set_attribute (SCMP_FLTATR_CTL_LOG, 0); | ||
| 152 | |||
| 153 | verify (CHAR_BIT == 8); | ||
| 154 | verify (sizeof (int) == 4 && INT_MIN == INT32_MIN | ||
| 155 | && INT_MAX == INT32_MAX); | ||
| 156 | verify (sizeof (void *) == 8); | ||
| 157 | verify ((uintptr_t) NULL == 0); | ||
| 158 | |||
| 159 | /* Allow a clean exit. */ | ||
| 160 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (exit)); | ||
| 161 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (exit_group)); | ||
| 162 | |||
| 163 | /* Allow `mmap' and friends. This is necessary for dynamic loading, | ||
| 164 | reading the portable dump file, and thread creation. We don't | ||
| 165 | allow pages to be both writable and executable. */ | ||
| 166 | verify (MAP_PRIVATE != 0); | ||
| 167 | verify (MAP_SHARED != 0); | ||
| 168 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (mmap), | ||
| 169 | SCMP_A2_32 (SCMP_CMP_MASKED_EQ, | ||
| 170 | ~(PROT_NONE | PROT_READ | PROT_WRITE)), | ||
| 171 | /* Only support known flags. MAP_DENYWRITE is ignored, but | ||
| 172 | some versions of the dynamic loader still use it. Also | ||
| 173 | allow allocating thread stacks. */ | ||
| 174 | SCMP_A3_32 (SCMP_CMP_MASKED_EQ, | ||
| 175 | ~(MAP_PRIVATE | MAP_FILE | MAP_ANONYMOUS | ||
| 176 | | MAP_FIXED | MAP_DENYWRITE | MAP_STACK | ||
| 177 | | MAP_NORESERVE), | ||
| 178 | 0)); | ||
| 179 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (mmap), | ||
| 180 | SCMP_A2_32 (SCMP_CMP_MASKED_EQ, | ||
| 181 | ~(PROT_NONE | PROT_READ | PROT_EXEC)), | ||
| 182 | /* Only support known flags. MAP_DENYWRITE is ignored, but | ||
| 183 | some versions of the dynamic loader still use it. */ | ||
| 184 | SCMP_A3_32 (SCMP_CMP_MASKED_EQ, | ||
| 185 | ~(MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | ||
| 186 | | MAP_DENYWRITE), | ||
| 187 | 0)); | ||
| 188 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (munmap)); | ||
| 189 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (mprotect), | ||
| 190 | /* Don't allow making pages executable. */ | ||
| 191 | SCMP_A2_32 (SCMP_CMP_MASKED_EQ, | ||
| 192 | ~(PROT_NONE | PROT_READ | PROT_WRITE), 0)); | ||
| 193 | |||
| 194 | /* Futexes are used everywhere. */ | ||
| 195 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (futex), | ||
| 196 | SCMP_A1_32 (SCMP_CMP_EQ, FUTEX_WAKE_PRIVATE)); | ||
| 197 | |||
| 198 | /* Allow basic dynamic memory management. */ | ||
| 199 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (brk)); | ||
| 200 | |||
| 201 | /* Allow some status inquiries. */ | ||
| 202 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (uname)); | ||
| 203 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (getuid)); | ||
| 204 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (geteuid)); | ||
| 205 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (getpid)); | ||
| 206 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (getpgrp)); | ||
| 207 | |||
| 208 | /* Allow operations on open file descriptors. File descriptors are | ||
| 209 | capabilities, and operating on them shouldn't cause security | ||
| 210 | issues. */ | ||
| 211 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (read)); | ||
| 212 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (write)); | ||
| 213 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (close)); | ||
| 214 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (lseek)); | ||
| 215 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (dup)); | ||
| 216 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (dup2)); | ||
| 217 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (fstat)); | ||
| 218 | |||
| 219 | /* Allow read operations on the filesystem. If necessary, these | ||
| 220 | should be further restricted using mount namespaces. */ | ||
| 221 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (access)); | ||
| 222 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (faccessat)); | ||
| 223 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (stat)); | ||
| 224 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (stat64)); | ||
| 225 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (lstat)); | ||
| 226 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (lstat64)); | ||
| 227 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (fstatat64)); | ||
| 228 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (newfstatat)); | ||
| 229 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (readlink)); | ||
| 230 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (readlinkat)); | ||
| 231 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (getcwd)); | ||
| 232 | |||
| 233 | /* Allow opening files, assuming they are only opened for | ||
| 234 | reading. */ | ||
| 235 | verify (O_WRONLY != 0); | ||
| 236 | verify (O_RDWR != 0); | ||
| 237 | verify (O_CREAT != 0); | ||
| 238 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (open), | ||
| 239 | SCMP_A1_32 (SCMP_CMP_MASKED_EQ, | ||
| 240 | ~(O_RDONLY | O_BINARY | O_CLOEXEC | O_PATH | ||
| 241 | | O_DIRECTORY), | ||
| 242 | 0)); | ||
| 243 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (openat), | ||
| 244 | SCMP_A2_32 (SCMP_CMP_MASKED_EQ, | ||
| 245 | ~(O_RDONLY | O_BINARY | O_CLOEXEC | O_PATH | ||
| 246 | | O_DIRECTORY), | ||
| 247 | 0)); | ||
| 248 | |||
| 249 | /* Allow `tcgetpgrp'. */ | ||
| 250 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (ioctl), | ||
| 251 | SCMP_A0_32 (SCMP_CMP_EQ, STDIN_FILENO), | ||
| 252 | SCMP_A1_32 (SCMP_CMP_EQ, TIOCGPGRP)); | ||
| 253 | |||
| 254 | /* Allow reading (but not setting) file flags. */ | ||
| 255 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (fcntl), | ||
| 256 | SCMP_A1_32 (SCMP_CMP_EQ, F_GETFL)); | ||
| 257 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (fcntl64), | ||
| 258 | SCMP_A1_32 (SCMP_CMP_EQ, F_GETFL)); | ||
| 259 | |||
| 260 | /* Allow reading random numbers from the kernel. */ | ||
| 261 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (getrandom)); | ||
| 262 | |||
| 263 | /* Changing the umask is uncritical. */ | ||
| 264 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (umask)); | ||
| 265 | |||
| 266 | /* Allow creation of pipes. */ | ||
| 267 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (pipe)); | ||
| 268 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (pipe2)); | ||
| 269 | |||
| 270 | /* Allow reading (but not changing) resource limits. */ | ||
| 271 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (getrlimit)); | ||
| 272 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (prlimit64), | ||
| 273 | SCMP_A0_32 (SCMP_CMP_EQ, 0) /* pid == 0 (current process) */, | ||
| 274 | SCMP_A2_64 (SCMP_CMP_EQ, 0) /* new_limit == NULL */); | ||
| 275 | |||
| 276 | /* Block changing resource limits, but don't crash. */ | ||
| 277 | RULE (SCMP_ACT_ERRNO (EPERM), SCMP_SYS (prlimit64), | ||
| 278 | SCMP_A0_32 (SCMP_CMP_EQ, 0) /* pid == 0 (current process) */, | ||
| 279 | SCMP_A2_64 (SCMP_CMP_NE, 0) /* new_limit != NULL */); | ||
| 280 | |||
| 281 | /* Emacs installs signal handlers, which is harmless. */ | ||
| 282 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (sigaction)); | ||
| 283 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (rt_sigaction)); | ||
| 284 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (sigprocmask)); | ||
| 285 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (rt_sigprocmask)); | ||
| 286 | |||
| 287 | /* Allow timer support. */ | ||
| 288 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (timer_create)); | ||
| 289 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (timerfd_create)); | ||
| 290 | |||
| 291 | /* Allow thread creation. See the NOTES section in the manual page | ||
| 292 | for the `clone' function. */ | ||
| 293 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (clone), | ||
| 294 | SCMP_A0_64 (SCMP_CMP_MASKED_EQ, | ||
| 295 | /* Flags needed to create threads. See | ||
| 296 | create_thread in libc. */ | ||
| 297 | ~(CLONE_VM | CLONE_FS | CLONE_FILES | ||
| 298 | | CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_THREAD | ||
| 299 | | CLONE_SETTLS | CLONE_PARENT_SETTID | ||
| 300 | | CLONE_CHILD_CLEARTID), | ||
| 301 | 0)); | ||
| 302 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (sigaltstack)); | ||
| 303 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (set_robust_list)); | ||
| 304 | |||
| 305 | /* Allow setting the process name for new threads. */ | ||
| 306 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (prctl), | ||
| 307 | SCMP_A0_32 (SCMP_CMP_EQ, PR_SET_NAME)); | ||
| 308 | |||
| 309 | /* Allow some event handling functions used by glib. */ | ||
| 310 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (eventfd)); | ||
| 311 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (eventfd2)); | ||
| 312 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (wait4)); | ||
| 313 | RULE (SCMP_ACT_ALLOW, SCMP_SYS (poll)); | ||
| 314 | |||
| 315 | /* Don't allow creating sockets (network access would be extremely | ||
| 316 | dangerous), but also don't crash. */ | ||
| 317 | RULE (SCMP_ACT_ERRNO (EACCES), SCMP_SYS (socket)); | ||
| 318 | |||
| 319 | EXPORT_FILTER (argv[1], seccomp_export_bpf); | ||
| 320 | EXPORT_FILTER (argv[2], seccomp_export_pfc); | ||
| 321 | } | ||
diff --git a/test/Makefile.in b/test/Makefile.in index ba354289e28..91a8ea141c3 100644 --- a/test/Makefile.in +++ b/test/Makefile.in | |||
| @@ -276,6 +276,8 @@ $(test_module): $(test_module:${SO}=.c) ../src/emacs-module.h | |||
| 276 | $(srcdir)/../lib/timespec.c $(srcdir)/../lib/gettime.c | 276 | $(srcdir)/../lib/timespec.c $(srcdir)/../lib/gettime.c |
| 277 | endif | 277 | endif |
| 278 | 278 | ||
| 279 | src/emacs-tests.log: ../lib-src/seccomp-filter.c | ||
| 280 | |||
| 279 | ## Check that there is no 'automated' subdirectory, which would | 281 | ## Check that there is no 'automated' subdirectory, which would |
| 280 | ## indicate an incomplete merge from an older version of Emacs where | 282 | ## indicate an incomplete merge from an older version of Emacs where |
| 281 | ## the tests were arranged differently. | 283 | ## the tests were arranged differently. |
diff --git a/test/src/emacs-resources/seccomp-filter.bpf b/test/src/emacs-resources/seccomp-filter.bpf new file mode 120000 index 00000000000..b3d603d0aeb --- /dev/null +++ b/test/src/emacs-resources/seccomp-filter.bpf | |||
| @@ -0,0 +1 @@ | |||
| ../../../lib-src/seccomp-filter.bpf \ No newline at end of file | |||
diff --git a/test/src/emacs-tests.el b/test/src/emacs-tests.el index 7618a9c6752..89d811f8b4e 100644 --- a/test/src/emacs-tests.el +++ b/test/src/emacs-tests.el | |||
| @@ -25,7 +25,9 @@ | |||
| 25 | 25 | ||
| 26 | (require 'cl-lib) | 26 | (require 'cl-lib) |
| 27 | (require 'ert) | 27 | (require 'ert) |
| 28 | (require 'ert-x) | ||
| 28 | (require 'rx) | 29 | (require 'rx) |
| 30 | (require 'subr-x) | ||
| 29 | 31 | ||
| 30 | (ert-deftest emacs-tests/seccomp/absent-file () | 32 | (ert-deftest emacs-tests/seccomp/absent-file () |
| 31 | (skip-unless (string-match-p (rx bow "SECCOMP" eow) | 33 | (skip-unless (string-match-p (rx bow "SECCOMP" eow) |
| @@ -128,4 +130,51 @@ to `make-temp-file', which see." | |||
| 128 | (concat "--seccomp=" filter)) | 130 | (concat "--seccomp=" filter)) |
| 129 | 0))))) | 131 | 0))))) |
| 130 | 132 | ||
| 133 | (ert-deftest emacs-tests/seccomp/allows-stdout () | ||
| 134 | (skip-unless (string-match-p (rx bow "SECCOMP" eow) | ||
| 135 | system-configuration-features)) | ||
| 136 | (let ((emacs | ||
| 137 | (expand-file-name invocation-name invocation-directory)) | ||
| 138 | (filter (ert-resource-file "seccomp-filter.bpf")) | ||
| 139 | (process-environment nil)) | ||
| 140 | (skip-unless (file-executable-p emacs)) | ||
| 141 | (skip-unless (file-readable-p filter)) | ||
| 142 | ;; The --seccomp option is processed early, without filename | ||
| 143 | ;; handlers. Therefore remote or quoted filenames wouldn't work. | ||
| 144 | (should-not (file-remote-p filter)) | ||
| 145 | (cl-callf file-name-unquote filter) | ||
| 146 | (with-temp-buffer | ||
| 147 | (let ((status (call-process | ||
| 148 | emacs nil t nil | ||
| 149 | "--quick" "--batch" | ||
| 150 | (concat "--seccomp=" filter) | ||
| 151 | (format "--eval=%S" '(message "Hi"))))) | ||
| 152 | (ert-info ((format "Process output: %s" (buffer-string))) | ||
| 153 | (should (eql status 0))) | ||
| 154 | (should (equal (string-trim (buffer-string)) "Hi")))))) | ||
| 155 | |||
| 156 | (ert-deftest emacs-tests/seccomp/forbids-subprocess () | ||
| 157 | (skip-unless (string-match-p (rx bow "SECCOMP" eow) | ||
| 158 | system-configuration-features)) | ||
| 159 | (let ((emacs | ||
| 160 | (expand-file-name invocation-name invocation-directory)) | ||
| 161 | (filter (ert-resource-file "seccomp-filter.bpf")) | ||
| 162 | (process-environment nil)) | ||
| 163 | (skip-unless (file-executable-p emacs)) | ||
| 164 | (skip-unless (file-readable-p filter)) | ||
| 165 | ;; The --seccomp option is processed early, without filename | ||
| 166 | ;; handlers. Therefore remote or quoted filenames wouldn't work. | ||
| 167 | (should-not (file-remote-p filter)) | ||
| 168 | (cl-callf file-name-unquote filter) | ||
| 169 | (with-temp-buffer | ||
| 170 | (let ((status | ||
| 171 | (call-process | ||
| 172 | emacs nil t nil | ||
| 173 | "--quick" "--batch" | ||
| 174 | (concat "--seccomp=" filter) | ||
| 175 | (format "--eval=%S" `(call-process ,emacs nil nil nil | ||
| 176 | "--version"))))) | ||
| 177 | (ert-info ((format "Process output: %s" (buffer-string))) | ||
| 178 | (should-not (eql status 0))))))) | ||
| 179 | |||
| 131 | ;;; emacs-tests.el ends here | 180 | ;;; emacs-tests.el ends here |