diff options
Diffstat (limited to 'src/emacs.c')
-rw-r--r-- | src/emacs.c | 400 |
1 files changed, 341 insertions, 59 deletions
diff --git a/src/emacs.c b/src/emacs.c index fd08667f3fd..866e43fda94 100644 --- a/src/emacs.c +++ b/src/emacs.c @@ -37,6 +37,7 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include <fcntl.h> #include <sys/socket.h> #include <mbstring.h> +#include <filename.h> /* for IS_ABSOLUTE_FILE_NAME */ #include "w32.h" #include "w32heap.h" #endif @@ -61,6 +62,21 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ # include <sys/socket.h> #endif +#if defined HAVE_LINUX_SECCOMP_H && defined HAVE_LINUX_FILTER_H \ + && HAVE_DECL_SECCOMP_SET_MODE_FILTER \ + && HAVE_DECL_SECCOMP_FILTER_FLAG_TSYNC +# define SECCOMP_USABLE 1 +#else +# define SECCOMP_USABLE 0 +#endif + +#if SECCOMP_USABLE +# include <linux/seccomp.h> +# include <linux/filter.h> +# include <sys/prctl.h> +# include <sys/syscall.h> +#endif + #ifdef HAVE_WINDOW_SYSTEM #include TERM_HEADER #endif /* HAVE_WINDOW_SYSTEM */ @@ -241,6 +257,11 @@ Initialization options:\n\ --dump-file FILE read dumped state from FILE\n\ ", #endif +#if SECCOMP_USABLE + "\ +--sandbox=FILE read Seccomp BPF filter from FILE\n\ +" +#endif "\ --no-build-details do not add build details such as time stamps\n\ --no-desktop do not load a saved desktop\n\ @@ -418,9 +439,9 @@ terminate_due_to_signal (int sig, int backtrace_limit) /* This shouldn't be executed, but it prevents a warning. */ exit (1); } + /* Code for dealing with Lisp access to the Unix command line. */ - static void init_cmdargs (int argc, char **argv, int skip_args, char const *original_pwd) { @@ -462,8 +483,8 @@ init_cmdargs (int argc, char **argv, int skip_args, char const *original_pwd) if (NILP (Vinvocation_directory)) { Lisp_Object found; - int yes = openp (Vexec_path, Vinvocation_name, - Vexec_suffixes, &found, make_fixnum (X_OK), false); + int yes = openp (Vexec_path, Vinvocation_name, Vexec_suffixes, + &found, make_fixnum (X_OK), false, false); if (yes == 1) { /* Add /: to the front of the name @@ -649,7 +670,9 @@ argmatch (char **argv, int argc, const char *sstr, const char *lstr, } arglen = (valptr != NULL && (p = strchr (arg, '=')) != NULL ? p - arg : strlen (arg)); - if (lstr == 0 || arglen < minlen || strncmp (arg, lstr, arglen) != 0) + if (!lstr) + return 0; + if (arglen < minlen || strncmp (arg, lstr, arglen) != 0) return 0; else if (valptr == NULL) { @@ -718,15 +741,29 @@ load_pdump_find_executable (char const *argv0, ptrdiff_t *candidate_size) implementation of malloc, since the caller calls our free. */ #ifdef WINDOWSNT char *prog_fname = w32_my_exename (); + if (prog_fname) + *candidate_size = strlen (prog_fname) + 1; return prog_fname ? xstrdup (prog_fname) : NULL; #else /* !WINDOWSNT */ char *candidate = NULL; /* If the executable name contains a slash, we have some kind of - path already, so just copy it. */ + path already, so just resolve symlinks and return the result. */ eassert (argv0); if (strchr (argv0, DIRECTORY_SEP)) - return xstrdup (argv0); + { + char *real_name = realpath (argv0, NULL); + + if (real_name) + { + *candidate_size = strlen (real_name) + 1; + return real_name; + } + + char *val = xstrdup (argv0); + *candidate_size = strlen (val) + 1; + return val; + } ptrdiff_t argv0_length = strlen (argv0); const char *path = getenv ("PATH"); @@ -763,7 +800,22 @@ load_pdump_find_executable (char const *argv0, ptrdiff_t *candidate_size) struct stat st; if (file_access_p (candidate, X_OK) && stat (candidate, &st) == 0 && S_ISREG (st.st_mode)) - return candidate; + { + /* People put on PATH a symlink to the real Emacs + executable, with all the auxiliary files where the real + executable lives. Support that. */ + if (lstat (candidate, &st) == 0 && S_ISLNK (st.st_mode)) + { + char *real_name = realpath (candidate, NULL); + + if (real_name) + { + *candidate_size = strlen (real_name) + 1; + return real_name; + } + } + return candidate; + } *candidate = '\0'; } while (*path++ != '\0'); @@ -777,6 +829,7 @@ load_pdump (int argc, char **argv) { const char *const suffix = ".pdmp"; int result; + char *emacs_executable = argv[0]; const char *strip_suffix = #if defined DOS_NT || defined CYGWIN ".exe" @@ -784,6 +837,13 @@ load_pdump (int argc, char **argv) NULL #endif ; + const char *argv0_base = +#ifdef NS_SELF_CONTAINED + "Emacs" +#else + "emacs" +#endif + ; /* TODO: maybe more thoroughly scrub process environment in order to make this use case (loading a dump file in an unexeced emacs) @@ -806,9 +866,19 @@ load_pdump (int argc, char **argv) skip_args++; } + /* Where's our executable? */ + ptrdiff_t bufsize, exec_bufsize; + emacs_executable = load_pdump_find_executable (argv[0], &bufsize); + exec_bufsize = bufsize; + + /* If we couldn't find our executable, go straight to looking for + the dump in the hardcoded location. */ + if (!(emacs_executable && *emacs_executable)) + goto hardcoded; + if (dump_file) { - result = pdumper_load (dump_file); + result = pdumper_load (dump_file, emacs_executable); if (result != PDUMPER_LOAD_SUCCESS) fatal ("could not load dump file \"%s\": %s", @@ -822,65 +892,46 @@ load_pdump (int argc, char **argv) so we can't use decode_env_path. We're working in whatever encoding the system natively uses for filesystem access, so there's no need for character set conversion. */ - ptrdiff_t bufsize; - dump_file = load_pdump_find_executable (argv[0], &bufsize); - - /* If we couldn't find our executable, go straight to looking for - the dump in the hardcoded location. */ - if (dump_file && *dump_file) + ptrdiff_t exenamelen = strlen (emacs_executable); + if (strip_suffix) { -#ifdef WINDOWSNT - /* w32_my_exename resolves symlinks internally, so no need to - call realpath. */ -#else - char *real_exename = realpath (dump_file, NULL); - if (!real_exename) - fatal ("could not resolve realpath of \"%s\": %s", - dump_file, strerror (errno)); - xfree (dump_file); - dump_file = real_exename; -#endif - ptrdiff_t exenamelen = strlen (dump_file); -#ifndef WINDOWSNT - bufsize = exenamelen + 1; -#endif - if (strip_suffix) - { - ptrdiff_t strip_suffix_length = strlen (strip_suffix); - ptrdiff_t prefix_length = exenamelen - strip_suffix_length; - if (0 <= prefix_length - && !memcmp (&dump_file[prefix_length], strip_suffix, - strip_suffix_length)) - exenamelen = prefix_length; - } - ptrdiff_t needed = exenamelen + strlen (suffix) + 1; - if (bufsize < needed) - dump_file = xpalloc (dump_file, &bufsize, needed - bufsize, -1, 1); - strcpy (dump_file + exenamelen, suffix); - result = pdumper_load (dump_file); - if (result == PDUMPER_LOAD_SUCCESS) - goto out; - - if (result != PDUMPER_LOAD_FILE_NOT_FOUND) - fatal ("could not load dump file \"%s\": %s", - dump_file, dump_error_to_string (result)); + ptrdiff_t strip_suffix_length = strlen (strip_suffix); + ptrdiff_t prefix_length = exenamelen - strip_suffix_length; + if (0 <= prefix_length + && !memcmp (&emacs_executable[prefix_length], strip_suffix, + strip_suffix_length)) + exenamelen = prefix_length; } + ptrdiff_t needed = exenamelen + strlen (suffix) + 1; + dump_file = xpalloc (NULL, &bufsize, needed - bufsize, -1, 1); + memcpy (dump_file, emacs_executable, exenamelen); + strcpy (dump_file + exenamelen, suffix); + result = pdumper_load (dump_file, emacs_executable); + if (result == PDUMPER_LOAD_SUCCESS) + goto out; + + if (result != PDUMPER_LOAD_FILE_NOT_FOUND) + fatal ("could not load dump file \"%s\": %s", + dump_file, dump_error_to_string (result)); + + hardcoded: #ifdef WINDOWSNT /* On MS-Windows, PATH_EXEC normally starts with a literal "%emacs_dir%", so it will never work without some tweaking. */ path_exec = w32_relocate (path_exec); +#elif defined (HAVE_NS) + path_exec = ns_relocate (path_exec); #endif /* Look for "emacs.pdmp" in PATH_EXEC. We hardcode "emacs" in "emacs.pdmp" so that the Emacs binary still works if the user copies and renames it. */ - const char *argv0_base = "emacs"; - ptrdiff_t needed = (strlen (path_exec) - + 1 - + strlen (argv0_base) - + strlen (suffix) - + 1); + needed = (strlen (path_exec) + + 1 + + strlen (argv0_base) + + strlen (suffix) + + 1); if (bufsize < needed) { xfree (dump_file); @@ -888,7 +939,23 @@ load_pdump (int argc, char **argv) } sprintf (dump_file, "%s%c%s%s", path_exec, DIRECTORY_SEP, argv0_base, suffix); - result = pdumper_load (dump_file); +#if !defined (NS_SELF_CONTAINED) + /* Assume the Emacs binary lives in a sibling directory as set up by + the default installation configuration. */ + const char *go_up = "../../../../bin/"; + needed += (strip_suffix ? strlen (strip_suffix) : 0) + - strlen (suffix) + strlen (go_up); + if (exec_bufsize < needed) + { + xfree (emacs_executable); + emacs_executable = xpalloc (NULL, &exec_bufsize, needed - exec_bufsize, + -1, 1); + } + sprintf (emacs_executable, "%s%c%s%s%s", + path_exec, DIRECTORY_SEP, go_up, argv0_base, + strip_suffix ? strip_suffix : ""); +#endif + result = pdumper_load (dump_file, emacs_executable); if (result == PDUMPER_LOAD_FILE_NOT_FOUND) { @@ -923,7 +990,7 @@ load_pdump (int argc, char **argv) #endif sprintf (dump_file, "%s%c%s%s", path_exec, DIRECTORY_SEP, argv0_base, suffix); - result = pdumper_load (dump_file); + result = pdumper_load (dump_file, emacs_executable); } if (result != PDUMPER_LOAD_SUCCESS) @@ -935,9 +1002,185 @@ load_pdump (int argc, char **argv) out: xfree (dump_file); + xfree (emacs_executable); } #endif /* HAVE_PDUMPER */ +#if SECCOMP_USABLE + +/* Wrapper function for the `seccomp' system call on GNU/Linux. This + system call usually doesn't have a wrapper function. See the + manual page of `seccomp' for the signature. */ + +static int +emacs_seccomp (unsigned int operation, unsigned int flags, void *args) +{ +#ifdef SYS_seccomp + return syscall (SYS_seccomp, operation, flags, args); +#else + errno = ENOSYS; + return -1; +#endif +} + +/* Read SIZE bytes into BUFFER. Return the number of bytes read, or + -1 if reading failed altogether. */ + +static ptrdiff_t +read_full (int fd, void *buffer, ptrdiff_t size) +{ + eassert (0 <= fd); + eassert (buffer != NULL); + eassert (0 <= size); + enum + { + /* See MAX_RW_COUNT in sysdep.c. */ +#ifdef MAX_RW_COUNT + max_size = MAX_RW_COUNT +#else + max_size = INT_MAX >> 18 << 18 +#endif + }; + if (PTRDIFF_MAX < size || max_size < size) + { + errno = EFBIG; + return -1; + } + char *ptr = buffer; + ptrdiff_t read = 0; + while (size != 0) + { + ptrdiff_t n = emacs_read (fd, ptr, size); + if (n < 0) + return -1; + if (n == 0) + break; /* Avoid infinite loop on encountering EOF. */ + eassert (n <= size); + size -= n; + ptr += n; + read += n; + } + return read; +} + +/* Attempt to load Secure Computing filters from FILE. Return false + if that doesn't work for some reason. */ + +static bool +load_seccomp (const char *file) +{ + bool success = false; + void *buffer = NULL; + int fd + = emacs_open_noquit (file, O_RDONLY | O_CLOEXEC | O_BINARY, 0); + if (fd < 0) + { + emacs_perror ("open"); + goto out; + } + struct stat stat; + if (fstat (fd, &stat) != 0) + { + emacs_perror ("fstat"); + goto out; + } + if (! S_ISREG (stat.st_mode)) + { + fprintf (stderr, "seccomp file %s is not regular\n", file); + goto out; + } + struct sock_fprog program; + if (stat.st_size <= 0 || SIZE_MAX <= stat.st_size + || PTRDIFF_MAX <= stat.st_size + || stat.st_size % sizeof *program.filter != 0) + { + fprintf (stderr, "seccomp filter %s has invalid size %ld\n", + file, (long) stat.st_size); + goto out; + } + size_t size = stat.st_size; + size_t count = size / sizeof *program.filter; + eassert (0 < count && count < SIZE_MAX); + if (USHRT_MAX < count) + { + fprintf (stderr, "seccomp filter %s is too big\n", file); + goto out; + } + /* Try reading one more byte to detect file size changes. */ + buffer = malloc (size + 1); + if (buffer == NULL) + { + emacs_perror ("malloc"); + goto out; + } + ptrdiff_t read = read_full (fd, buffer, size + 1); + if (read < 0) + { + emacs_perror ("read"); + goto out; + } + eassert (read <= SIZE_MAX); + if (read != size) + { + fprintf (stderr, + "seccomp filter %s changed size while reading\n", + file); + goto out; + } + if (emacs_close (fd) != 0) + emacs_perror ("close"); /* not a fatal error */ + fd = -1; + program.len = count; + program.filter = buffer; + + /* See man page of `seccomp' why this is necessary. Note that we + intentionally don't check the return value: a parent process + might have made this call before, in which case it would fail; + or, if enabling privilege-restricting mode fails, the `seccomp' + syscall will fail anyway. */ + prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + /* Install the filter. Make sure that potential other threads can't + escape it. */ + if (emacs_seccomp (SECCOMP_SET_MODE_FILTER, + SECCOMP_FILTER_FLAG_TSYNC, &program) + != 0) + { + emacs_perror ("seccomp"); + goto out; + } + success = true; + + out: + if (0 <= fd) + emacs_close (fd); + free (buffer); + return success; +} + +/* Load Secure Computing filter from file specified with the --seccomp + option. Exit if that fails. */ + +static void +maybe_load_seccomp (int argc, char **argv) +{ + int skip_args = 0; + char *file = NULL; + while (skip_args < argc - 1) + { + if (argmatch (argv, argc, "-seccomp", "--seccomp", 9, &file, + &skip_args) + || argmatch (argv, argc, "--", NULL, 2, NULL, &skip_args)) + break; + ++skip_args; + } + if (file == NULL) + return; + if (! load_seccomp (file)) + fatal ("cannot enable seccomp filter from %s", file); +} + +#endif /* SECCOMP_USABLE */ + int main (int argc, char **argv) { @@ -945,6 +1188,13 @@ main (int argc, char **argv) for pointers. */ void *stack_bottom_variable; + /* First, check whether we should apply a seccomp filter. This + should come at the very beginning to allow the filter to protect + the initialization phase. */ +#if SECCOMP_USABLE + maybe_load_seccomp (argc, argv); +#endif + bool no_loadup = false; char *junk = 0; char *dname_arg = 0; @@ -1607,6 +1857,9 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem init_json (); #endif + if (!initialized) + syms_of_comp (); + no_loadup = argmatch (argv, argc, "-nl", "--no-loadup", 6, NULL, &skip_args); @@ -1778,7 +2031,8 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem /* Init buffer storage and default directory of main buffer. */ init_buffer (); - init_callproc_1 (); /* Must precede init_cmdargs and init_sys_modes. */ + /* Must precede init_cmdargs and init_sys_modes. */ + init_callproc_1 (); /* Must precede init_lread. */ init_cmdargs (argc, argv, skip_args, original_pwd); @@ -1958,6 +2212,11 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem #endif keys_of_keyboard (); + +#ifdef HAVE_NATIVE_COMP + /* Must be after the last defsubr has run. */ + hash_native_abi (); +#endif } else { @@ -2047,6 +2306,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem Vdump_mode = build_string (dump_mode); /* Enter editor command loop. This never returns. */ + set_initial_minibuffer_mode (); Frecursive_edit (); eassume (false); } @@ -2133,12 +2393,15 @@ static const struct standard_args standard_args[] = { "-color", "--color", 5, 0}, { "-no-splash", "--no-splash", 3, 0 }, { "-no-desktop", "--no-desktop", 3, 0 }, - /* The following two must be just above the file-name args, to get + /* The following three must be just above the file-name args, to get them out of our way, but without mixing them with file names. */ { "-temacs", "--temacs", 1, 1 }, #ifdef HAVE_PDUMPER { "-dump-file", "--dump-file", 1, 1 }, #endif +#if SECCOMP_USABLE + { "-seccomp", "--seccomp", 1, 1 }, +#endif #ifdef HAVE_NS { "-NSAutoLaunch", 0, 5, 1 }, { "-NXAutoLaunch", 0, 5, 1 }, @@ -2393,6 +2656,10 @@ all of which are called before Emacs is actually killed. */ unlink (SSDATA (listfile)); } +#ifdef HAVE_NATIVE_COMP + eln_load_path_final_clean_up (); +#endif + if (FIXNUMP (arg)) exit_code = (XFIXNUM (arg) < 0 ? XFIXNUM (arg) | INT_MIN @@ -2705,7 +2972,11 @@ decode_env_path (const char *evarname, const char *defalt, bool empty) path = 0; if (!path) { +#ifdef NS_SELF_CONTAINED + path = ns_relocate (defalt); +#else path = defalt; +#endif #ifdef WINDOWSNT defaulted = 1; #endif @@ -3043,7 +3314,18 @@ because they do not depend on external libraries and are always available. Also note that this is not a generic facility for accessing external libraries; only those already known by Emacs will be loaded. */); +#ifdef WINDOWSNT + /* FIXME: We may need to load libgccjit when dumping before + term/w32-win.el defines `dynamic-library-alist`. This will fail + if that variable is empty, so add libgccjit-0.dll to it. */ + if (will_dump_p ()) + Vdynamic_library_alist = list1 (list2 (Qgccjit, + build_string ("libgccjit-0.dll"))); + else + Vdynamic_library_alist = Qnil; +#else Vdynamic_library_alist = Qnil; +#endif Fput (intern_c_string ("dynamic-library-alist"), Qrisky_local_variable, Qt); #ifdef WINDOWSNT |