diff options
| author | Philipp Stephani | 2020-12-14 21:25:11 +0100 |
|---|---|---|
| committer | Philipp Stephani | 2021-04-10 18:47:26 +0200 |
| commit | be8328acf9aa464f848e682e63e417a18529af9e (patch) | |
| tree | b7f8191920af5e326b2a2feac9ddbeb8551fadba /src | |
| parent | 53dfd85a7f971875e716a55f010ee508bce89eed (diff) | |
| download | emacs-be8328acf9aa464f848e682e63e417a18529af9e.tar.gz emacs-be8328acf9aa464f848e682e63e417a18529af9e.zip | |
Add support for --seccomp command-line option.scratch/seccomp-emacs-open
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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/emacs.c | 167 |
1 files changed, 166 insertions, 1 deletions
diff --git a/src/emacs.c b/src/emacs.c index fd08667f3fd..b956e9ca34b 100644 --- a/src/emacs.c +++ b/src/emacs.c | |||
| @@ -61,6 +61,13 @@ 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 | #endif | ||
| 70 | |||
| 64 | #ifdef HAVE_WINDOW_SYSTEM | 71 | #ifdef HAVE_WINDOW_SYSTEM |
| 65 | #include TERM_HEADER | 72 | #include TERM_HEADER |
| 66 | #endif /* HAVE_WINDOW_SYSTEM */ | 73 | #endif /* HAVE_WINDOW_SYSTEM */ |
| @@ -241,6 +248,11 @@ Initialization options:\n\ | |||
| 241 | --dump-file FILE read dumped state from FILE\n\ | 248 | --dump-file FILE read dumped state from FILE\n\ |
| 242 | ", | 249 | ", |
| 243 | #endif | 250 | #endif |
| 251 | #ifdef HAVE_LINUX_SECCOMP_H | ||
| 252 | "\ | ||
| 253 | --sandbox=FILE read Seccomp BPF filter from FILE\n\ | ||
| 254 | " | ||
| 255 | #endif | ||
| 244 | "\ | 256 | "\ |
| 245 | --no-build-details do not add build details such as time stamps\n\ | 257 | --no-build-details do not add build details such as time stamps\n\ |
| 246 | --no-desktop do not load a saved desktop\n\ | 258 | --no-desktop do not load a saved desktop\n\ |
| @@ -938,6 +950,149 @@ load_pdump (int argc, char **argv) | |||
| 938 | } | 950 | } |
| 939 | #endif /* HAVE_PDUMPER */ | 951 | #endif /* HAVE_PDUMPER */ |
| 940 | 952 | ||
| 953 | #ifdef HAVE_LINUX_SECCOMP_H | ||
| 954 | |||
| 955 | /* Wrapper function for the `seccomp' system call on GNU/Linux. This | ||
| 956 | system call usually doesn't have a wrapper function. See the | ||
| 957 | manual page of `seccomp' for the signature. */ | ||
| 958 | |||
| 959 | static int | ||
| 960 | emacs_seccomp (unsigned int operation, unsigned int flags, void *args) | ||
| 961 | { | ||
| 962 | #ifdef SYS_seccomp | ||
| 963 | return syscall (SYS_seccomp, operation, flags, args); | ||
| 964 | #else | ||
| 965 | errno = ENOSYS; | ||
| 966 | return -1; | ||
| 967 | #endif | ||
| 968 | } | ||
| 969 | |||
| 970 | /* Attempt to load Secure Computing filters from FILE. Return false | ||
| 971 | if that doesn't work for some reason. */ | ||
| 972 | |||
| 973 | static bool | ||
| 974 | load_seccomp (const char *file) | ||
| 975 | { | ||
| 976 | bool success = false; | ||
| 977 | void *buffer = NULL; | ||
| 978 | int fd | ||
| 979 | = emacs_open_noquit (file, O_RDONLY | O_CLOEXEC | O_BINARY, 0); | ||
| 980 | if (fd < 0) | ||
| 981 | { | ||
| 982 | emacs_perror ("open"); | ||
| 983 | goto out; | ||
| 984 | } | ||
| 985 | struct stat stat; | ||
| 986 | if (fstat (fd, &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 | enum | ||
| 997 | { | ||
| 998 | /* See MAX_RW_COUNT in sysdep.c. */ | ||
| 999 | #ifdef MAX_RW_COUNT | ||
| 1000 | max_read_size = MAX_RW_COUNT | ||
| 1001 | #else | ||
| 1002 | max_read_size = INT_MAX >> 18 << 18 | ||
| 1003 | #endif | ||
| 1004 | }; | ||
| 1005 | struct sock_fprog program; | ||
| 1006 | if (stat.st_size <= 0 || SIZE_MAX <= stat.st_size | ||
| 1007 | || PTRDIFF_MAX <= stat.st_size || max_read_size < stat.st_size | ||
| 1008 | || stat.st_size % sizeof *program.filter != 0) | ||
| 1009 | { | ||
| 1010 | fprintf (stderr, "seccomp filter %s has invalid size %ld\n", | ||
| 1011 | file, (long) stat.st_size); | ||
| 1012 | goto out; | ||
| 1013 | } | ||
| 1014 | size_t size = stat.st_size; | ||
| 1015 | size_t count = size / sizeof *program.filter; | ||
| 1016 | eassert (0 < count && count < SIZE_MAX); | ||
| 1017 | if (USHRT_MAX < count) | ||
| 1018 | { | ||
| 1019 | fprintf (stderr, "seccomp filter %s is too big\n", file); | ||
| 1020 | goto out; | ||
| 1021 | } | ||
| 1022 | /* Try reading one more byte to detect file size changes. */ | ||
| 1023 | buffer = malloc (size + 1); | ||
| 1024 | if (buffer == NULL) | ||
| 1025 | { | ||
| 1026 | emacs_perror ("malloc"); | ||
| 1027 | goto out; | ||
| 1028 | } | ||
| 1029 | ptrdiff_t read = emacs_read (fd, buffer, size + 1); | ||
| 1030 | if (read < 0) | ||
| 1031 | { | ||
| 1032 | emacs_perror ("read"); | ||
| 1033 | goto out; | ||
| 1034 | } | ||
| 1035 | if (read != count) | ||
| 1036 | { | ||
| 1037 | fprintf (stderr, | ||
| 1038 | "seccomp filter %s changed size while reading\n", | ||
| 1039 | file); | ||
| 1040 | goto out; | ||
| 1041 | } | ||
| 1042 | if (emacs_close (fd) < 0) | ||
| 1043 | emacs_perror ("close"); /* not a fatal error */ | ||
| 1044 | fd = -1; | ||
| 1045 | program.len = count; | ||
| 1046 | program.filter = buffer; | ||
| 1047 | |||
| 1048 | /* See man page of `seccomp' why this is necessary. Note that we | ||
| 1049 | intentionally don't check the return value: a parent process | ||
| 1050 | might have made this call before, in which case it would fail; | ||
| 1051 | or, if enabling privilege-restricting mode fails, the `seccomp' | ||
| 1052 | syscall will fail anyway. */ | ||
| 1053 | prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); | ||
| 1054 | /* Install the filter. Make sure that potential other threads can't | ||
| 1055 | escape it. */ | ||
| 1056 | if (emacs_seccomp (SECCOMP_SET_MODE_FILTER, | ||
| 1057 | SECCOMP_FILTER_FLAG_TSYNC, &program) | ||
| 1058 | != 0) | ||
| 1059 | { | ||
| 1060 | emacs_perror ("seccomp"); | ||
| 1061 | goto out; | ||
| 1062 | } | ||
| 1063 | success = true; | ||
| 1064 | |||
| 1065 | out: | ||
| 1066 | if (fd < 0) | ||
| 1067 | emacs_close (fd); | ||
| 1068 | free (buffer); | ||
| 1069 | return success; | ||
| 1070 | } | ||
| 1071 | |||
| 1072 | /* Load Secure Computing filter from file specified with the --seccomp | ||
| 1073 | option. Exit if that fails. */ | ||
| 1074 | |||
| 1075 | static void | ||
| 1076 | maybe_load_seccomp (int argc, char **argv) | ||
| 1077 | { | ||
| 1078 | int skip_args = 0; | ||
| 1079 | char *file = NULL; | ||
| 1080 | while (skip_args < argc - 1) | ||
| 1081 | { | ||
| 1082 | if (argmatch (argv, argc, "-seccomp", "--seccomp", 9, &file, | ||
| 1083 | &skip_args) | ||
| 1084 | || argmatch (argv, argc, "--", NULL, 2, NULL, &skip_args)) | ||
| 1085 | break; | ||
| 1086 | ++skip_args; | ||
| 1087 | } | ||
| 1088 | if (file == NULL) | ||
| 1089 | return; | ||
| 1090 | if (! load_seccomp (file)) | ||
| 1091 | fatal ("cannot enable seccomp filter from %s", file); | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | #endif /* HAVE_LINUX_SECCOMP_H */ | ||
| 1095 | |||
| 941 | int | 1096 | int |
| 942 | main (int argc, char **argv) | 1097 | main (int argc, char **argv) |
| 943 | { | 1098 | { |
| @@ -945,6 +1100,13 @@ main (int argc, char **argv) | |||
| 945 | for pointers. */ | 1100 | for pointers. */ |
| 946 | void *stack_bottom_variable; | 1101 | void *stack_bottom_variable; |
| 947 | 1102 | ||
| 1103 | /* First, check whether we should apply a seccomp filter. This | ||
| 1104 | should come at the very beginning to allow the filter to protect | ||
| 1105 | the initialization phase. */ | ||
| 1106 | #ifdef HAVE_LINUX_SECCOMP_H | ||
| 1107 | maybe_load_seccomp (argc, argv); | ||
| 1108 | #endif | ||
| 1109 | |||
| 948 | bool no_loadup = false; | 1110 | bool no_loadup = false; |
| 949 | char *junk = 0; | 1111 | char *junk = 0; |
| 950 | char *dname_arg = 0; | 1112 | char *dname_arg = 0; |
| @@ -2133,12 +2295,15 @@ static const struct standard_args standard_args[] = | |||
| 2133 | { "-color", "--color", 5, 0}, | 2295 | { "-color", "--color", 5, 0}, |
| 2134 | { "-no-splash", "--no-splash", 3, 0 }, | 2296 | { "-no-splash", "--no-splash", 3, 0 }, |
| 2135 | { "-no-desktop", "--no-desktop", 3, 0 }, | 2297 | { "-no-desktop", "--no-desktop", 3, 0 }, |
| 2136 | /* The following two must be just above the file-name args, to get | 2298 | /* 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. */ | 2299 | them out of our way, but without mixing them with file names. */ |
| 2138 | { "-temacs", "--temacs", 1, 1 }, | 2300 | { "-temacs", "--temacs", 1, 1 }, |
| 2139 | #ifdef HAVE_PDUMPER | 2301 | #ifdef HAVE_PDUMPER |
| 2140 | { "-dump-file", "--dump-file", 1, 1 }, | 2302 | { "-dump-file", "--dump-file", 1, 1 }, |
| 2141 | #endif | 2303 | #endif |
| 2304 | #ifdef HAVE_LINUX_SECCOMP_H | ||
| 2305 | { "-seccomp", "--seccomp", 1, 1 }, | ||
| 2306 | #endif | ||
| 2142 | #ifdef HAVE_NS | 2307 | #ifdef HAVE_NS |
| 2143 | { "-NSAutoLaunch", 0, 5, 1 }, | 2308 | { "-NSAutoLaunch", 0, 5, 1 }, |
| 2144 | { "-NXAutoLaunch", 0, 5, 1 }, | 2309 | { "-NXAutoLaunch", 0, 5, 1 }, |