aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Stephani2020-12-14 21:25:11 +0100
committerPhilipp Stephani2021-04-05 17:01:40 +0200
commit1c4fcdb924c2cfb53bf7c6b620f2407f0efd3ba4 (patch)
tree16e872c36fb21c5efbd80fc1a52237ef88ef429b
parent0342354c155728f8d55005bd34a66e1ab3179cc7 (diff)
downloademacs-1c4fcdb924c2cfb53bf7c6b620f2407f0efd3ba4.tar.gz
emacs-1c4fcdb924c2cfb53bf7c6b620f2407f0efd3ba4.zip
Add support for --seccomp command-line option.
When passing this option on GNU/Linux, Emacs installs a Secure Computing kernel system call filter. See Bug#45198. * configure.ac: Check for seccomp header. * src/emacs.c (usage_message): Document --seccomp option. (emacs_seccomp): New wrapper for 'seccomp' syscall. (load_seccomp, maybe_load_seccomp): New helper functions. (main): Potentially load seccomp filters during startup. (standard_args): Add --seccomp option. * lisp/startup.el (command-line): Detect and ignore --seccomp option. * test/src/emacs-tests.el (emacs-tests/seccomp/absent-file) (emacs-tests/seccomp/empty-file) (emacs-tests/seccomp/file-too-large) (emacs-tests/seccomp/invalid-file-size): New unit tests. (emacs-tests--with-temp-file): New helper macro. * etc/NEWS: Document new --seccomp option.
-rw-r--r--configure.ac8
-rw-r--r--etc/NEWS10
-rw-r--r--lisp/startup.el5
-rw-r--r--src/emacs.c150
-rw-r--r--test/src/emacs-tests.el131
5 files changed, 298 insertions, 6 deletions
diff --git a/configure.ac b/configure.ac
index 2c62a9fe6f7..5be28a65f30 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4179,6 +4179,8 @@ fi
4179AC_SUBST([BLESSMAIL_TARGET]) 4179AC_SUBST([BLESSMAIL_TARGET])
4180AC_SUBST([LIBS_MAIL]) 4180AC_SUBST([LIBS_MAIL])
4181 4181
4182AC_CHECK_HEADERS([linux/seccomp.h], [HAVE_SECCOMP=yes])
4183
4182OLD_LIBS=$LIBS 4184OLD_LIBS=$LIBS
4183LIBS="$LIB_PTHREAD $LIB_MATH $LIBS" 4185LIBS="$LIB_PTHREAD $LIB_MATH $LIBS"
4184AC_CHECK_FUNCS(accept4 fchdir gethostname \ 4186AC_CHECK_FUNCS(accept4 fchdir gethostname \
@@ -5672,9 +5674,9 @@ optsep=
5672emacs_config_features= 5674emacs_config_features=
5673for opt in ACL CAIRO DBUS FREETYPE GCONF GIF GLIB GMP GNUTLS GPM GSETTINGS \ 5675for opt in ACL CAIRO DBUS FREETYPE GCONF GIF GLIB GMP GNUTLS GPM GSETTINGS \
5674 HARFBUZZ IMAGEMAGICK JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 \ 5676 HARFBUZZ IMAGEMAGICK JPEG JSON LCMS2 LIBOTF LIBSELINUX LIBSYSTEMD LIBXML2 \
5675 M17N_FLT MODULES NOTIFY NS OLDXMENU PDUMPER PNG RSVG SOUND THREADS TIFF \ 5677 M17N_FLT MODULES NOTIFY NS OLDXMENU PDUMPER PNG RSVG SECCOMP SOUND \
5676 TOOLKIT_SCROLL_BARS UNEXEC X11 XAW3D XDBE XFT XIM XPM XWIDGETS X_TOOLKIT \ 5678 THREADS TIFF TOOLKIT_SCROLL_BARS UNEXEC X11 XAW3D XDBE XFT XIM XPM \
5677 ZLIB; do 5679 XWIDGETS X_TOOLKIT ZLIB; do
5678 5680
5679 case $opt in 5681 case $opt in
5680 PDUMPER) val=${with_pdumper} ;; 5682 PDUMPER) val=${with_pdumper} ;;
diff --git a/etc/NEWS b/etc/NEWS
index 1421efcaa07..ee3e0a96484 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -90,6 +90,16 @@ lacks the terminfo database, you can instruct Emacs to support 24-bit
90true color by setting 'COLORTERM=truecolor' in the environment. This is 90true color by setting 'COLORTERM=truecolor' in the environment. This is
91useful on systems such as FreeBSD which ships only with "etc/termcap". 91useful on systems such as FreeBSD which ships only with "etc/termcap".
92 92
93** On GNU/Linux systems, Emacs now supports loading a Secure Computing
94filter. To use this, you can pass a --seccomp=FILE command-line
95option to Emacs. FILE must name a binary file containing an array of
96'struct sock_filter' structures. Emacs will then install that list of
97Secure Computing filters into its own process early during the startup
98process. You can use this functionality to put an Emacs process in a
99sandbox to avoid security issues when executing untrusted code. See
100the manual page for 'seccomp' for details about Secure Computing
101filters.
102
93 103
94* Changes in Emacs 28.1 104* Changes in Emacs 28.1
95 105
diff --git a/lisp/startup.el b/lisp/startup.el
index b173d619733..4d4c65e6c41 100644
--- a/lisp/startup.el
+++ b/lisp/startup.el
@@ -1097,7 +1097,7 @@ please check its value")
1097 ("--no-x-resources") ("--debug-init") 1097 ("--no-x-resources") ("--debug-init")
1098 ("--user") ("--iconic") ("--icon-type") ("--quick") 1098 ("--user") ("--iconic") ("--icon-type") ("--quick")
1099 ("--no-blinking-cursor") ("--basic-display") 1099 ("--no-blinking-cursor") ("--basic-display")
1100 ("--dump-file") ("--temacs"))) 1100 ("--dump-file") ("--temacs") ("--seccomp")))
1101 (argi (pop args)) 1101 (argi (pop args))
1102 (orig-argi argi) 1102 (orig-argi argi)
1103 argval) 1103 argval)
@@ -1149,7 +1149,8 @@ please check its value")
1149 (push '(visibility . icon) initial-frame-alist)) 1149 (push '(visibility . icon) initial-frame-alist))
1150 ((member argi '("-nbc" "-no-blinking-cursor")) 1150 ((member argi '("-nbc" "-no-blinking-cursor"))
1151 (setq no-blinking-cursor t)) 1151 (setq no-blinking-cursor t))
1152 ((member argi '("-dump-file" "-temacs")) ; Handled in C 1152 ((member argi '("-dump-file" "-temacs" "-seccomp"))
1153 ;; Handled in C
1153 (or argval (pop args)) 1154 (or argval (pop args))
1154 (setq argval nil)) 1155 (setq argval nil))
1155 ;; Push the popped arg back on the list of arguments. 1156 ;; Push the popped arg back on the list of arguments.
diff --git a/src/emacs.c b/src/emacs.c
index fd08667f3fd..35caf2ba3bb 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -61,6 +61,14 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
61# include <sys/socket.h> 61# include <sys/socket.h>
62#endif 62#endif
63 63
64#ifdef HAVE_LINUX_SECCOMP_H
65# include <linux/seccomp.h>
66# include <linux/filter.h>
67# include <sys/prctl.h>
68# include <sys/syscall.h>
69# include <stdio.h>
70#endif
71
64#ifdef HAVE_WINDOW_SYSTEM 72#ifdef HAVE_WINDOW_SYSTEM
65#include TERM_HEADER 73#include TERM_HEADER
66#endif /* HAVE_WINDOW_SYSTEM */ 74#endif /* HAVE_WINDOW_SYSTEM */
@@ -241,6 +249,11 @@ Initialization options:\n\
241--dump-file FILE read dumped state from FILE\n\ 249--dump-file FILE read dumped state from FILE\n\
242", 250",
243#endif 251#endif
252#ifdef HAVE_LINUX_SECCOMP_H
253 "\
254--sandbox=FILE read Seccomp BPF filter from FILE\n\
255"
256#endif
244 "\ 257 "\
245--no-build-details do not add build details such as time stamps\n\ 258--no-build-details do not add build details such as time stamps\n\
246--no-desktop do not load a saved desktop\n\ 259--no-desktop do not load a saved desktop\n\
@@ -938,6 +951,131 @@ load_pdump (int argc, char **argv)
938} 951}
939#endif /* HAVE_PDUMPER */ 952#endif /* HAVE_PDUMPER */
940 953
954#ifdef HAVE_LINUX_SECCOMP_H
955
956/* Wrapper function for the `seccomp' system call on GNU/Linux. This
957 system call usually doesn't have a wrapper function. See the
958 manual page of `seccomp' for the signature. */
959
960static int
961emacs_seccomp (unsigned int operation, unsigned int flags, void *args)
962{
963#ifdef SYS_seccomp
964 return syscall (SYS_seccomp, operation, flags, args);
965#else
966 errno = ENOSYS;
967 return -1;
968#endif
969}
970
971/* Attempt to load Secure Computing filters from FILE. Return false
972 if that doesn't work for some reason. */
973
974static bool
975load_seccomp (const char *file)
976{
977 bool success = false;
978 struct sock_fprog program = {0, NULL};
979 FILE *stream = fopen (file, "rb");
980 if (file == NULL)
981 {
982 emacs_perror ("fopen");
983 goto out;
984 }
985 struct stat stat;
986 if (fstat (fileno (stream), &stat) != 0)
987 {
988 emacs_perror ("fstat");
989 goto out;
990 }
991 if (! S_ISREG (stat.st_mode))
992 {
993 fprintf (stderr, "seccomp file %s is not regular\n", file);
994 goto out;
995 }
996 if (stat.st_size <= 0 || SIZE_MAX <= stat.st_size
997 || stat.st_size % sizeof *program.filter != 0)
998 {
999 fprintf (stderr, "seccomp filter %s has invalid size %ld\n",
1000 file, (long) stat.st_size);
1001 goto out;
1002 }
1003 size_t count = (size_t) stat.st_size / sizeof *program.filter;
1004 eassert (0 < count && count < SIZE_MAX);
1005 if (USHRT_MAX < count)
1006 {
1007 fprintf (stderr, "seccomp filter %s is too big\n", file);
1008 goto out;
1009 }
1010 /* Try reading one more element to detect file size changes. */
1011 program.filter = calloc (count + 1, sizeof *program.filter);
1012 if (program.filter == NULL)
1013 {
1014 emacs_perror ("calloc");
1015 goto out;
1016 }
1017 size_t read = fread (program.filter, sizeof *program.filter,
1018 count + 1, stream);
1019 if (read != count || ferror (stream) != 0 || feof (stream) == 0)
1020 {
1021 fprintf (stderr,
1022 "seccomp filter %s changed size while reading\n",
1023 file);
1024 goto out;
1025 }
1026 if (fclose (stream) != 0)
1027 perror ("fclose"); /* not a fatal error */
1028 stream = NULL;
1029 program.len = count;
1030
1031 /* See man page of `seccomp' why this is necessary. Note that we
1032 intentionally don't check the return value: a parent process
1033 might have made this call before, in which case it would fail;
1034 or, if enabling privilege-restricting mode fails, the `seccomp'
1035 syscall will fail anyway. */
1036 prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
1037 /* Install the filter. Make sure that potential other threads can't
1038 escape it. */
1039 if (emacs_seccomp (SECCOMP_SET_MODE_FILTER,
1040 SECCOMP_FILTER_FLAG_TSYNC, &program)
1041 != 0)
1042 {
1043 emacs_perror ("seccomp");
1044 goto out;
1045 }
1046 success = true;
1047
1048 out:
1049 if (stream != NULL)
1050 fclose (stream);
1051 free (program.filter);
1052 return success;
1053}
1054
1055/* Load Secure Computing filter from file specified with the --seccomp
1056 option. Exit if that fails. */
1057
1058static void
1059maybe_load_seccomp (int argc, char **argv)
1060{
1061 int skip_args = 0;
1062 char *file = NULL;
1063 while (skip_args < argc - 1)
1064 {
1065 if (argmatch (argv, argc, "-seccomp", "--seccomp", 9, &file,
1066 &skip_args)
1067 || argmatch (argv, argc, "--", NULL, 2, NULL, &skip_args))
1068 break;
1069 ++skip_args;
1070 }
1071 if (file == NULL)
1072 return;
1073 if (!load_seccomp (file))
1074 fatal ("cannot enable seccomp filter from %s", file);
1075}
1076
1077#endif /* HAVE_LINUX_SECCOMP_H */
1078
941int 1079int
942main (int argc, char **argv) 1080main (int argc, char **argv)
943{ 1081{
@@ -945,6 +1083,13 @@ main (int argc, char **argv)
945 for pointers. */ 1083 for pointers. */
946 void *stack_bottom_variable; 1084 void *stack_bottom_variable;
947 1085
1086 /* First, check whether we should apply a seccomp filter. This
1087 should come at the very beginning to allow the filter to protect
1088 the initialization phase. */
1089#ifdef HAVE_LINUX_SECCOMP_H
1090 maybe_load_seccomp (argc, argv);
1091#endif
1092
948 bool no_loadup = false; 1093 bool no_loadup = false;
949 char *junk = 0; 1094 char *junk = 0;
950 char *dname_arg = 0; 1095 char *dname_arg = 0;
@@ -2133,12 +2278,15 @@ static const struct standard_args standard_args[] =
2133 { "-color", "--color", 5, 0}, 2278 { "-color", "--color", 5, 0},
2134 { "-no-splash", "--no-splash", 3, 0 }, 2279 { "-no-splash", "--no-splash", 3, 0 },
2135 { "-no-desktop", "--no-desktop", 3, 0 }, 2280 { "-no-desktop", "--no-desktop", 3, 0 },
2136 /* The following two must be just above the file-name args, to get 2281 /* The following three must be just above the file-name args, to get
2137 them out of our way, but without mixing them with file names. */ 2282 them out of our way, but without mixing them with file names. */
2138 { "-temacs", "--temacs", 1, 1 }, 2283 { "-temacs", "--temacs", 1, 1 },
2139#ifdef HAVE_PDUMPER 2284#ifdef HAVE_PDUMPER
2140 { "-dump-file", "--dump-file", 1, 1 }, 2285 { "-dump-file", "--dump-file", 1, 1 },
2141#endif 2286#endif
2287#ifdef HAVE_LINUX_SECCOMP_H
2288 { "-seccomp", "--seccomp", 1, 1 },
2289#endif
2142#ifdef HAVE_NS 2290#ifdef HAVE_NS
2143 { "-NSAutoLaunch", 0, 5, 1 }, 2291 { "-NSAutoLaunch", 0, 5, 1 },
2144 { "-NXAutoLaunch", 0, 5, 1 }, 2292 { "-NXAutoLaunch", 0, 5, 1 },
diff --git a/test/src/emacs-tests.el b/test/src/emacs-tests.el
new file mode 100644
index 00000000000..7618a9c6752
--- /dev/null
+++ b/test/src/emacs-tests.el
@@ -0,0 +1,131 @@
1;;; emacs-tests.el --- unit tests for emacs.c -*- lexical-binding: t; -*-
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
8;; it under the terms of the GNU General Public License as published
9;; by the Free Software Foundation, either version 3 of the License,
10;; or (at your 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 <https://www.gnu.org/licenses/>.
19
20;;; Commentary:
21
22;; Unit tests for src/emacs.c.
23
24;;; Code:
25
26(require 'cl-lib)
27(require 'ert)
28(require 'rx)
29
30(ert-deftest emacs-tests/seccomp/absent-file ()
31 (skip-unless (string-match-p (rx bow "SECCOMP" eow)
32 system-configuration-features))
33 (let ((emacs
34 (expand-file-name invocation-name invocation-directory))
35 (process-environment nil))
36 (skip-unless (file-executable-p emacs))
37 (should-not (file-exists-p "/does-not-exist.bpf"))
38 (should-not
39 (eql (call-process emacs nil nil nil
40 "--quick" "--batch"
41 "--seccomp=/does-not-exist.bpf")
42 0))))
43
44(cl-defmacro emacs-tests--with-temp-file
45 (var (prefix &optional suffix text) &rest body)
46 "Evaluate BODY while a new temporary file exists.
47Bind VAR to the name of the file. Pass PREFIX, SUFFIX, and TEXT
48to `make-temp-file', which see."
49 (declare (indent 2) (debug (symbolp (form form form) body)))
50 (cl-check-type var symbol)
51 ;; Use an uninterned symbol so that the code still works if BODY
52 ;; changes VAR.
53 (let ((filename (make-symbol "filename")))
54 `(let ((,filename (make-temp-file ,prefix nil ,suffix ,text)))
55 (unwind-protect
56 (let ((,var ,filename))
57 ,@body)
58 (delete-file ,filename)))))
59
60(ert-deftest emacs-tests/seccomp/empty-file ()
61 (skip-unless (string-match-p (rx bow "SECCOMP" eow)
62 system-configuration-features))
63 (let ((emacs
64 (expand-file-name invocation-name invocation-directory))
65 (process-environment nil))
66 (skip-unless (file-executable-p emacs))
67 (emacs-tests--with-temp-file filter ("seccomp-invalid-" ".bpf")
68 ;; The --seccomp option is processed early, without filename
69 ;; handlers. Therefore remote or quoted filenames wouldn't
70 ;; work.
71 (should-not (file-remote-p filter))
72 (cl-callf file-name-unquote filter)
73 ;; According to the Seccomp man page, a filter must have at
74 ;; least one element, so Emacs should reject an empty file.
75 (should-not
76 (eql (call-process emacs nil nil nil
77 "--quick" "--batch"
78 (concat "--seccomp=" filter))
79 0)))))
80
81(ert-deftest emacs-tests/seccomp/file-too-large ()
82 (skip-unless (string-match-p (rx bow "SECCOMP" eow)
83 system-configuration-features))
84 (let ((emacs
85 (expand-file-name invocation-name invocation-directory))
86 (process-environment nil)
87 ;; This value should be correct on all supported systems.
88 (ushort-max #xFFFF)
89 ;; Either 8 or 16, but 16 should be large enough in all cases.
90 (filter-size 16))
91 (skip-unless (file-executable-p emacs))
92 (emacs-tests--with-temp-file
93 filter ("seccomp-too-large-" ".bpf"
94 (make-string (* (1+ ushort-max) filter-size) ?a))
95 ;; The --seccomp option is processed early, without filename
96 ;; handlers. Therefore remote or quoted filenames wouldn't
97 ;; work.
98 (should-not (file-remote-p filter))
99 (cl-callf file-name-unquote filter)
100 ;; The filter count must fit into an `unsigned short'. A bigger
101 ;; file should be rejected.
102 (should-not
103 (eql (call-process emacs nil nil nil
104 "--quick" "--batch"
105 (concat "--seccomp=" filter))
106 0)))))
107
108(ert-deftest emacs-tests/seccomp/invalid-file-size ()
109 (skip-unless (string-match-p (rx bow "SECCOMP" eow)
110 system-configuration-features))
111 (let ((emacs
112 (expand-file-name invocation-name invocation-directory))
113 (process-environment nil))
114 (skip-unless (file-executable-p emacs))
115 (emacs-tests--with-temp-file filter ("seccomp-invalid-" ".bpf"
116 "123456")
117 ;; The --seccomp option is processed early, without filename
118 ;; handlers. Therefore remote or quoted filenames wouldn't
119 ;; work.
120 (should-not (file-remote-p filter))
121 (cl-callf file-name-unquote filter)
122 ;; The Seccomp filter file must have a file size that's a
123 ;; multiple of the size of struct sock_filter, which is 8 or 16,
124 ;; but never 6.
125 (should-not
126 (eql (call-process emacs nil nil nil
127 "--quick" "--batch"
128 (concat "--seccomp=" filter))
129 0)))))
130
131;;; emacs-tests.el ends here