diff options
Diffstat (limited to 'lib-src')
-rw-r--r-- | lib-src/Makefile.in | 73 | ||||
-rw-r--r-- | lib-src/emacsclient.c | 226 | ||||
-rw-r--r-- | lib-src/etags.c | 647 | ||||
-rw-r--r-- | lib-src/movemail.c | 14 | ||||
-rw-r--r-- | lib-src/seccomp-filter.c | 370 |
5 files changed, 1176 insertions, 154 deletions
diff --git a/lib-src/Makefile.in b/lib-src/Makefile.in index 0a6dd826c10..7af89eb380d 100644 --- a/lib-src/Makefile.in +++ b/lib-src/Makefile.in @@ -44,33 +44,8 @@ WERROR_CFLAGS = @WERROR_CFLAGS@ # Program name transformation. TRANSFORM = @program_transform_name@ -# 'make' verbosity. -AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ - -AM_V_CC = $(am__v_CC_@AM_V@) -am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) -am__v_CC_0 = @echo " CC " $@; -am__v_CC_1 = - -AM_V_CCLD = $(am__v_CCLD_@AM_V@) -am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) -am__v_CCLD_0 = @echo " CCLD " $@; -am__v_CCLD_1 = - -AM_V_GEN = $(am__v_GEN_@AM_V@) -am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) -am__v_GEN_0 = @echo " GEN " $@; -am__v_GEN_1 = - -AM_V_RC = $(am__v_RC_@AM_V@) -am__v_RC_ = $(am__v_RC_@AM_DEFAULT_V@) -am__v_RC_0 = @echo " RC " $@; -am__v_RC_1 = - -AM_V_at = $(am__v_at_@AM_V@) -am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) -am__v_at_0 = @ -am__v_at_1 = +top_builddir = @top_builddir@ +-include ${top_builddir}/src/verbose.mk # ==================== Where To Install Things ==================== @@ -214,6 +189,30 @@ LIB_WSOCK32=@LIB_WSOCK32@ ## Extra libraries for etags LIBS_ETAGS = $(LIB_CLOCK_GETTIME) $(LIB_GETRANDOM) +HAVE_SECCOMP=@HAVE_SECCOMP@ +HAVE_LIBSECCOMP=@HAVE_LIBSECCOMP@ +LIBSECCOMP_LIBS=@LIBSECCOMP_LIBS@ +LIBSECCOMP_CFLAGS=@LIBSECCOMP_CFLAGS@ + +# Currently, we can only generate seccomp filter files for x86-64. +ifeq ($(HAVE_SECCOMP),yes) +ifeq ($(HAVE_LIBSECCOMP),yes) +ifeq ($(shell uname -m),x86_64) +# We require SECCOMP_RET_KILL_PROCESS, which is only available in +# Linux 4.14 and later. +ifeq ($(shell { echo 4.14; uname -r | cut -d . -f 1-2; } | \ + sort -C -t . -n -k 1,1 -k 2,2 && \ + echo 1),1) +SECCOMP_FILTER=1 +endif +endif +endif +endif + +ifeq ($(SECCOMP_FILTER),1) +DONT_INSTALL += seccomp-filter$(EXEEXT) +endif + ## Extra libraries to use when linking movemail. LIBS_MOVE = $(LIBS_MAIL) $(KRB4LIB) $(DESLIB) $(KRB5LIB) $(CRYPTOLIB) \ $(COM_ERRLIB) $(LIBHESIOD) $(LIBRESOLV) $(LIB_WSOCK32) $(LIBS_ETAGS) @@ -243,6 +242,10 @@ config_h = ../src/config.h $(srcdir)/../src/conf_post.h all: ${EXE_FILES} ${SCRIPTS} +ifeq ($(SECCOMP_FILTER),1) +all: seccomp-filter.bpf seccomp-filter-exec.bpf +endif + .PHONY: all need-blessmail maybe-blessmail LOADLIBES = ../lib/libgnu.a $(LIBS_SYSTEM) @@ -312,7 +315,7 @@ $(DESTDIR)${archlibdir}: all fi .PHONY: install uninstall mostlyclean clean distclean maintainer-clean -.PHONY: bootstrap-clean extraclean check tags +.PHONY: bootstrap-clean check tags install: $(DESTDIR)${archlibdir} @echo @@ -340,6 +343,7 @@ mostlyclean: rm -f core ./*.o ./*.res clean: mostlyclean + -rm -f seccomp-filter.bpf seccomp-filter.pfc seccomp-filter-exec.bpf seccomp-filter-exec.pfc rm -f ${EXE_FILES} distclean: clean @@ -347,8 +351,6 @@ distclean: clean bootstrap-clean maintainer-clean: distclean -extraclean: maintainer-clean - rm -f ./*~ \#* ## Test the contents of the directory. check: @@ -425,4 +427,15 @@ update-game-score${EXEEXT}: ${srcdir}/update-game-score.c $(NTLIB) $(config_h) emacsclient.res: ../nt/emacsclient.rc $(NTINC)/../icons/emacs.ico $(AM_V_RC)$(WINDRES) -O coff --include-dir=$(NTINC)/.. -o $@ $< +ifeq ($(SECCOMP_FILTER),1) +seccomp-filter$(EXEEXT): $(srcdir)/seccomp-filter.c $(config_h) + $(AM_V_CCLD)$(CC) $(ALL_CFLAGS) $(LIBSECCOMP_CFLAGS) $< \ + $(LIBSECCOMP_LIBS) -o $@ + +seccomp-filter.bpf seccomp-filter.pfc seccomp-filter-exec.bpf seccomp-filter-exec.pfc: seccomp-filter$(EXEEXT) + $(AM_V_GEN)./seccomp-filter$(EXEEXT) \ + seccomp-filter.bpf seccomp-filter.pfc \ + seccomp-filter-exec.bpf seccomp-filter-exec.pfc +endif + ## Makefile ends here. diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 12ced4aadbd..8346524a3eb 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -80,6 +80,9 @@ char *w32_getenv (const char *); #include <sys/stat.h> #include <unistd.h> +#ifndef WINDOWSNT +# include <acl.h> +#endif #include <filename.h> #include <intprops.h> #include <min-max.h> @@ -91,6 +94,10 @@ char *w32_getenv (const char *); # pragma GCC diagnostic ignored "-Wformat-truncation=2" #endif +#if !defined O_PATH && !defined WINDOWSNT +# define O_PATH O_SEARCH +#endif + /* Name used to invoke this program. */ static char const *progname; @@ -1128,24 +1135,74 @@ process_grouping (void) #ifdef SOCKETS_IN_FILE_SYSTEM -/* Return the file status of NAME, ordinarily a socket. - It should be owned by UID. Return one of the following: - >0 - 'stat' failed with this errno value - -1 - isn't owned by us - 0 - success: none of the above */ +/* A local socket address. The union avoids the need to cast. */ +union local_sockaddr +{ + struct sockaddr_un un; + struct sockaddr sa; +}; + +/* Relative to the directory DIRFD, connect the socket file named ADDR + to the socket S. Return 0 if successful, -1 if DIRFD is not + AT_FDCWD and DIRFD's permissions would allow a symlink attack, an + errno otherwise. */ static int -socket_status (const char *name, uid_t uid) +connect_socket (int dirfd, char const *addr, int s, uid_t uid) { - struct stat statbfr; + int sock_status = 0; - if (stat (name, &statbfr) != 0) - return errno; + union local_sockaddr server; + if (sizeof server.un.sun_path <= strlen (addr)) + return ENAMETOOLONG; + server.un.sun_family = AF_UNIX; + strcpy (server.un.sun_path, addr); - if (statbfr.st_uid != uid) - return -1; + /* If -1, WDFD is not set yet. If nonnegative, WDFD is a file + descriptor for the initial working directory. Otherwise -1 - WDFD is + the error number for the initial working directory. */ + static int wdfd = -1; - return 0; + if (dirfd != AT_FDCWD) + { + /* Fail if DIRFD's permissions are bogus. */ + struct stat st; + if (fstat (dirfd, &st) != 0) + return errno; + if (st.st_uid != uid || (st.st_mode & (S_IWGRP | S_IWOTH))) + return -1; + + if (wdfd == -1) + { + /* Save the initial working directory. */ + wdfd = open (".", O_PATH | O_CLOEXEC); + if (wdfd < 0) + wdfd = -1 - errno; + } + if (wdfd < 0) + return -1 - wdfd; + if (fchdir (dirfd) != 0) + return errno; + + /* Fail if DIRFD has an ACL, which means its permissions are + almost surely bogus. */ + int has_acl = file_has_acl (".", &st); + if (has_acl) + sock_status = has_acl < 0 ? errno : -1; + } + + if (!sock_status) + sock_status = connect (s, &server.sa, sizeof server.un) == 0 ? 0 : errno; + + /* Fail immediately if we cannot change back to the initial working + directory, as that can mess up the rest of execution. */ + if (dirfd != AT_FDCWD && fchdir (wdfd) != 0) + { + message (true, "%s: .: %s\n", progname, strerror (errno)); + exit (EXIT_FAILURE); + } + + return sock_status; } @@ -1322,32 +1379,49 @@ act_on_signals (HSOCKET emacs_socket) } } -/* Create in SOCKNAME (of size SOCKNAMESIZE) a name for a local socket. - The first TMPDIRLEN bytes of SOCKNAME are already initialized to be - the name of a temporary directory. Use UID and SERVER_NAME to - concoct the name. Return the total length of the name if successful, - -1 if it does not fit (and store a truncated name in that case). - Fail if TMPDIRLEN is out of range. */ +enum { socknamesize = sizeof ((struct sockaddr_un *) NULL)->sun_path }; + +/* Given a local socket S, create in *SOCKNAME a name for a local socket + and connect to that socket. The first TMPDIRLEN bytes of *SOCKNAME are + already initialized to be the name of a temporary directory. + Use UID and SERVER_NAME to concoct the name. Return 0 if + successful, -1 if the socket's parent directory is not safe, and an + errno if there is some other problem. */ static int -local_sockname (char *sockname, int socknamesize, int tmpdirlen, - uintmax_t uid, char const *server_name) +local_sockname (int s, char sockname[socknamesize], int tmpdirlen, + uid_t uid, char const *server_name) { /* If ! (0 <= TMPDIRLEN && TMPDIRLEN < SOCKNAMESIZE) the truncated temporary directory name is already in SOCKNAME, so nothing more need be stored. */ - if (0 <= tmpdirlen) - { - int remaining = socknamesize - tmpdirlen; - if (0 < remaining) - { - int suffixlen = snprintf (&sockname[tmpdirlen], remaining, - "/emacs%"PRIuMAX"/%s", uid, server_name); - if (0 <= suffixlen && suffixlen < remaining) - return tmpdirlen + suffixlen; - } - } - return -1; + if (! (0 <= tmpdirlen && tmpdirlen < socknamesize)) + return ENAMETOOLONG; + + /* Put the full address name into the buffer, since the caller might + need it for diagnostics. But don't overrun the buffer. */ + uintmax_t uidmax = uid; + int emacsdirlen; + int suffixlen = snprintf (sockname + tmpdirlen, socknamesize - tmpdirlen, + "/emacs%"PRIuMAX"%n/%s", uidmax, &emacsdirlen, + server_name); + if (! (0 <= suffixlen && suffixlen < socknamesize - tmpdirlen)) + return ENAMETOOLONG; + + /* Make sure the address's parent directory is not a symlink and is + this user's directory and does not let others write to it; this + fends off some symlink attacks. To avoid races, keep the parent + directory open while checking. */ + char *emacsdirend = sockname + tmpdirlen + emacsdirlen; + *emacsdirend = '\0'; + int dir = openat (AT_FDCWD, sockname, + O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + *emacsdirend = '/'; + if (dir < 0) + return errno; + int sock_status = connect_socket (dir, server_name, s, uid); + close (dir); + return sock_status; } /* Create a local socket for SERVER_NAME and connect it to Emacs. If @@ -1358,28 +1432,43 @@ local_sockname (char *sockname, int socknamesize, int tmpdirlen, static HSOCKET set_local_socket (char const *server_name) { - union { - struct sockaddr_un un; - struct sockaddr sa; - } server = {{ .sun_family = AF_UNIX }}; + union local_sockaddr server; + int sock_status; char *sockname = server.un.sun_path; - enum { socknamesize = sizeof server.un.sun_path }; int tmpdirlen = -1; int socknamelen = -1; uid_t uid = geteuid (); bool tmpdir_used = false; + int s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0); + if (s < 0) + { + message (true, "%s: can't create socket: %s\n", + progname, strerror (errno)); + fail (); + } if (strchr (server_name, '/') || (ISSLASH ('\\') && strchr (server_name, '\\'))) - socknamelen = snprintf (sockname, socknamesize, "%s", server_name); + { + socknamelen = snprintf (sockname, socknamesize, "%s", server_name); + sock_status = (0 <= socknamelen && socknamelen < socknamesize + ? connect_socket (AT_FDCWD, sockname, s, 0) + : ENAMETOOLONG); + } else { /* socket_name is a file name component. */ + sock_status = ENOENT; char const *xdg_runtime_dir = egetenv ("XDG_RUNTIME_DIR"); if (xdg_runtime_dir) - socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s", - xdg_runtime_dir, server_name); - else + { + socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s", + xdg_runtime_dir, server_name); + sock_status = (0 <= socknamelen && socknamelen < socknamesize + ? connect_socket (AT_FDCWD, sockname, s, 0) + : ENAMETOOLONG); + } + if (sock_status == ENOENT) { char const *tmpdir = egetenv ("TMPDIR"); if (tmpdir) @@ -1398,23 +1487,24 @@ set_local_socket (char const *server_name) if (tmpdirlen < 0) tmpdirlen = snprintf (sockname, socknamesize, "/tmp"); } - socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, + sock_status = local_sockname (s, sockname, tmpdirlen, uid, server_name); tmpdir_used = true; } } - if (! (0 <= socknamelen && socknamelen < socknamesize)) + if (sock_status == 0) + return s; + + if (sock_status == ENAMETOOLONG) { message (true, "%s: socket-name %s... too long\n", progname, sockname); fail (); } - /* See if the socket exists, and if it's owned by us. */ - int sock_status = socket_status (sockname, uid); - if (sock_status) + if (tmpdir_used) { - /* Failing that, see if LOGNAME or USER exist and differ from + /* See whether LOGNAME or USER exist and differ from our euid. If so, look for a socket based on the UID associated with the name. This is reminiscent of the logic that init_editfns uses to set the global Vuser_full_name. */ @@ -1431,48 +1521,26 @@ set_local_socket (char const *server_name) if (pw && pw->pw_uid != uid) { /* We're running under su, apparently. */ - socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, + sock_status = local_sockname (s, sockname, tmpdirlen, pw->pw_uid, server_name); - if (socknamelen < 0) + if (sock_status == 0) + return s; + if (sock_status == ENAMETOOLONG) { message (true, "%s: socket-name %s... too long\n", progname, sockname); exit (EXIT_FAILURE); } - - sock_status = socket_status (sockname, uid); } } } - if (sock_status == 0) - { - HSOCKET s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0); - if (s < 0) - { - message (true, "%s: socket: %s\n", progname, strerror (errno)); - return INVALID_SOCKET; - } - if (connect (s, &server.sa, sizeof server.un) != 0) - { - message (true, "%s: connect: %s\n", progname, strerror (errno)); - CLOSE_SOCKET (s); - return INVALID_SOCKET; - } + close (s); - struct stat connect_stat; - if (fstat (s, &connect_stat) != 0) - sock_status = errno; - else if (connect_stat.st_uid == uid) - return s; - else - sock_status = -1; - - CLOSE_SOCKET (s); - } - - if (sock_status < 0) - message (true, "%s: Invalid socket owner\n", progname); + if (sock_status == -1) + message (true, + "%s: Invalid permissions on parent directory of socket: %s\n", + progname, sockname); else if (sock_status == ENOENT) { if (tmpdir_used) @@ -1502,7 +1570,7 @@ set_local_socket (char const *server_name) } } else - message (true, "%s: can't stat %s: %s\n", + message (true, "%s: can't connect to %s: %s\n", progname, sockname, strerror (sock_status)); return INVALID_SOCKET; diff --git a/lib-src/etags.c b/lib-src/etags.c index b5c18e0e019..88b49f803e9 100644 --- a/lib-src/etags.c +++ b/lib-src/etags.c @@ -142,7 +142,14 @@ University of California, as described above. */ # define CTAGS false #endif -/* Copy to DEST from SRC (containing LEN bytes), and append a NUL byte. */ +/* Define MERCURY_HEURISTICS_RATIO as it was necessary to disambiguate + Mercury from Objective C, which have same file extensions .m + See comments before function test_objc_is_mercury for details. */ +#ifndef MERCURY_HEURISTICS_RATIO +# define MERCURY_HEURISTICS_RATIO 0.5 +#endif + +/* COPY to DEST from SRC (containing LEN bytes), and append a NUL byte. */ static void memcpyz (void *dest, void const *src, ptrdiff_t len) { @@ -333,7 +340,6 @@ typedef struct regexp struct re_pattern_buffer *pat; /* the compiled pattern */ struct re_registers regs; /* re registers */ bool error_signaled; /* already signaled for this regexp */ - bool force_explicit_name; /* do not allow implicit tag name */ bool ignore_case; /* ignore case when matching */ bool multi_line; /* do a multi-line match on the whole file */ } regexp; @@ -359,6 +365,7 @@ static void HTML_labels (FILE *); static void Lisp_functions (FILE *); static void Lua_functions (FILE *); static void Makefile_targets (FILE *); +static void Mercury_functions (FILE *); static void Pascal_functions (FILE *); static void Perl_functions (FILE *); static void PHP_functions (FILE *); @@ -366,6 +373,7 @@ static void PS_functions (FILE *); static void Prolog_functions (FILE *); static void Python_functions (FILE *); static void Ruby_functions (FILE *); +static void Rust_entries (FILE *); static void Scheme_functions (FILE *); static void TeX_commands (FILE *); static void Texinfo_nodes (FILE *); @@ -378,6 +386,7 @@ static ptrdiff_t readline_internal (linebuffer *, FILE *, char const *); static bool nocase_tail (const char *); static void get_tag (char *, char **); static void get_lispy_tag (char *); +static void test_objc_is_mercury (char *, language **); static void analyze_regex (char *); static void free_regexps (void); @@ -683,10 +692,22 @@ static const char Makefile_help [] = "In makefiles, targets are tags; additionally, variables are tags\n\ unless you specify '--no-globals'."; +/* Mercury and Objective C share the same .m file extensions. */ +static const char *Mercury_suffixes [] = + {"m", + NULL}; +static const char Mercury_help [] = + "In Mercury code, tags are all declarations beginning a line with ':-'\n\ +and optionally Prolog-like definitions (first rule for a predicate or \ +function).\n\ +To enable this behavior, run etags using --declarations."; +static bool with_mercury_definitions = false; +float mercury_heuristics_ratio = MERCURY_HEURISTICS_RATIO; + static const char *Objc_suffixes [] = - { "lm", /* Objective lex file */ - "m", /* Objective C file */ - NULL }; + { "lm", /* Objective lex file */ + "m", /* By default, Objective C file will be assumed. */ + NULL}; static const char Objc_help [] = "In Objective C code, tags include Objective C definitions for classes,\n\ class categories, methods and protocols. Tags for variables and\n\ @@ -752,6 +773,12 @@ a line generate a tag. Constants also generate a tag."; static const char *Ruby_interpreters [] = { "ruby", NULL }; +static const char *Rust_suffixes [] = + { "rs", NULL }; +static const char Rust_help [] = + "In Rust code, tags anything defined with 'fn', 'enum', \n\ +'struct' or 'macro_rules!'."; + /* Can't do the `SCM' or `scm' prefix with a version number. */ static const char *Scheme_suffixes [] = { "oak", "sch", "scheme", "SCM", "scm", "SM", "sm", "ss", "t", NULL }; @@ -824,7 +851,9 @@ static language lang_names [] = { "lisp", Lisp_help, Lisp_functions, Lisp_suffixes }, { "lua", Lua_help,Lua_functions,Lua_suffixes,NULL,Lua_interpreters}, { "makefile", Makefile_help,Makefile_targets,NULL,Makefile_filenames}, + /* objc listed before mercury as it is a better default for .m extensions. */ { "objc", Objc_help, plain_C_entries, Objc_suffixes }, + { "mercury", Mercury_help, Mercury_functions, Mercury_suffixes }, { "pascal", Pascal_help, Pascal_functions, Pascal_suffixes }, { "perl",Perl_help,Perl_functions,Perl_suffixes,NULL,Perl_interpreters}, { "php", PHP_help, PHP_functions, PHP_suffixes }, @@ -836,6 +865,7 @@ static language lang_names [] = NULL, Python_interpreters }, { "ruby", Ruby_help, Ruby_functions, Ruby_suffixes, Ruby_filenames, Ruby_interpreters }, + { "rust", Rust_help, Rust_entries, Rust_suffixes }, { "scheme", Scheme_help, Scheme_functions, Scheme_suffixes }, { "tex", TeX_help, TeX_commands, TeX_suffixes }, { "texinfo", Texinfo_help, Texinfo_nodes, Texinfo_suffixes }, @@ -950,6 +980,9 @@ Relative ones are stored relative to the output file's directory.\n"); puts ("\tand create tags for extern variables unless --no-globals is used."); + puts ("In Mercury, tag both declarations starting a line with ':-' and first\n\ + predicates or functions in clauses."); + if (CTAGS) puts ("-d, --defines\n\ Create tag entries for C #define constants and enum constants, too."); @@ -1775,6 +1808,11 @@ find_entries (FILE *inf) if (parser == NULL) { lang = get_language_from_filename (curfdp->infname, true); + + /* Disambiguate file names between Objc and Mercury. */ + if (lang != NULL && strcmp (lang->name, "objc") == 0) + test_objc_is_mercury (curfdp->infname, &lang); + if (lang != NULL && lang->function != NULL) { curfdp->lang = lang; @@ -5021,6 +5059,49 @@ Ruby_functions (FILE *inf) /* + * Rust support + * Look for: + * - fn: Function + * - struct: Structure + * - enum: Enumeration + * - macro_rules!: Macro + */ +static void +Rust_entries (FILE *inf) +{ + char *cp, *name; + bool is_func = false; + + LOOP_ON_INPUT_LINES(inf, lb, cp) + { + cp = skip_spaces(cp); + name = cp; + + // Skip 'pub' keyworld + (void)LOOKING_AT (cp, "pub"); + + // Look for define + if ((is_func = LOOKING_AT (cp, "fn")) + || LOOKING_AT (cp, "enum") + || LOOKING_AT (cp, "struct") + || (is_func = LOOKING_AT (cp, "macro_rules!"))) + { + cp = skip_spaces (cp); + name = cp; + + while (!notinname (*cp)) + cp++; + + make_tag (name, cp - name, is_func, + lb.buffer, cp - lb.buffer + 1, + lineno, linecharno); + is_func = false; + } + } +} + + +/* * PHP support * Look for: * - /^[ \t]*function[ \t\n]+[^ \t\n(]+/ @@ -5999,10 +6080,10 @@ prolog_atom (char *s, size_t pos) pos++; if (s[pos] != '\'') break; - pos++; /* A double quote */ + pos++; /* A double quote */ } else if (s[pos] == '\0') - /* Multiline quoted atoms are ignored. */ + /* Multiline quoted atoms are ignored. */ return 0; else if (s[pos] == '\\') { @@ -6021,6 +6102,510 @@ prolog_atom (char *s, size_t pos) /* + * Support for Mercury + * + * Assumes that the declarations start at column 0. + * Original code by Sunichirou Sugou (1989) for Prolog. + * Rewritten by Anders Lindgren (1996) for Prolog. + * Adapted by Fabrice Nicol (2021) for Mercury. + * Note: Prolog-support behavior is preserved if + * --declarations is used, corresponding to + * with_mercury_definitions=true. + */ + +static ptrdiff_t mercury_pr (char *, char *, ptrdiff_t); +static void mercury_skip_comment (linebuffer *, FILE *); +static bool is_mercury_type = false; +static bool is_mercury_quantifier = false; +static bool is_mercury_declaration = false; +typedef struct +{ + size_t pos; /* Position reached in parsing tag name. */ + size_t namelength; /* Length of tag name */ + size_t totlength; /* Total length of parsed tag: this field is currently + reserved for control and debugging. */ +} mercury_pos_t; + +/* + * Objective-C and Mercury have identical file extension .m. + * To disambiguate between Objective C and Mercury, parse file + * with the following heuristics hook: + * - if line starts with :-, choose Mercury unconditionally; + * - if line starts with #, @, choose Objective-C; + * - otherwise compute the following ratio: + * + * r = (number of lines with :- + * or % in non-commented parts or . at trimmed EOL) + * / (number of lines - number of lines starting by any amount + * of whitespace, optionally followed by comment(s)) + * + * Note: strings are neglected in counts. + * + * If r > mercury_heuristics_ratio, choose Mercury. + * Experimental tests show that a possibly optimal default value for + * this floor value is around 0.5. This is the default value for + * MERCURY_HEURISTICS_RATIO, defined in the first lines of this file. + * The closer r is to 0.5, the closer the source code to pure Prolog. + * Idiomatic Mercury is scored either with r = 1.0 or higher. + * Objective-C is scored with r = 0.0. When this fails, the r-score + * never rose above 0.1 in Objective-C tests. + */ + +static void +test_objc_is_mercury (char *this_file, language **lang) +{ + if (this_file == NULL) return; + FILE* fp = fopen (this_file, "r"); + if (fp == NULL) + pfatal (this_file); + + bool blank_line = false; /* Line starting with any amount of white space + followed by optional comment(s). */ + bool commented_line = false; + bool found_dot = false; + bool only_space_before = true; + bool start_of_line = true; + int c; + intmax_t lines = 1; + intmax_t mercury_dots = 0; + intmax_t percentage_signs = 0; + intmax_t rule_signs = 0; + float ratio = 0; + + while ((c = fgetc (fp)) != EOF) + { + switch (c) + { + case '\n': + if (! blank_line) ++lines; + blank_line = true; + commented_line = false; + start_of_line = true; + if (found_dot) ++mercury_dots; + found_dot = false; + only_space_before = true; + break; + case '.': + found_dot = ! commented_line; + only_space_before = false; + break; + case '%': /* More frequent in Mercury. May be modulo in Obj.-C. */ + if (! commented_line) + { + ++percentage_signs; + /* Cannot tell if it is a comment or modulo yet for sure. + Yet works for heuristic purposes. */ + commented_line = true; + } + found_dot = false; + start_of_line = false; + only_space_before = false; + break; + case '/': + { + int d = fgetc (fp); + found_dot = false; + only_space_before = false; + if (! commented_line) + { + if (d == '*') + commented_line = true; + else + /* If d == '/', cannot tell if it is an Obj.-C comment: + may be Mercury integ. division. */ + blank_line = false; + } + } + FALLTHROUGH; + case ' ': + case '\t': + start_of_line = false; + break; + case ':': + c = fgetc (fp); + if (start_of_line) + { + if (c == '-') + { + ratio = 1.0; /* Failsafe, not an operator in Obj.-C. */ + goto out; + } + start_of_line = false; + } + else + { + /* p :- q. Frequent in Mercury. + Rare or in quoted exprs in Obj.-C. */ + if (c == '-' && ! commented_line) + ++rule_signs; + } + blank_line = false; + found_dot = false; + only_space_before = false; + break; + case '@': + case '#': + if (start_of_line || only_space_before) + { + ratio = 0.0; + goto out; + } + FALLTHROUGH; + default: + start_of_line = false; + blank_line = false; + found_dot = false; + only_space_before = false; + } + } + + /* Fallback heuristic test. Not failsafe but errless in pratice. */ + ratio = ((float) rule_signs + percentage_signs + mercury_dots) / lines; + + out: + if (fclose (fp) == EOF) + pfatal (this_file); + + if (ratio > mercury_heuristics_ratio) + { + /* Change the language from Objective-C to Mercury. */ + static language lang0 = { "mercury", Mercury_help, Mercury_functions, + Mercury_suffixes }; + *lang = &lang0; + } +} + +static void +Mercury_functions (FILE *inf) +{ + char *cp, *last = NULL; + ptrdiff_t lastlen = 0, allocated = 0; + if (declarations) with_mercury_definitions = true; + + LOOP_ON_INPUT_LINES (inf, lb, cp) + { + if (cp[0] == '\0') /* Empty line. */ + continue; + else if (c_isspace (cp[0]) || cp[0] == '%') + /* A Prolog-type comment or anything other than a declaration. */ + continue; + else if (cp[0] == '/' && cp[1] == '*') /* Mercury C-type comment. */ + mercury_skip_comment (&lb, inf); + else + { + is_mercury_declaration = (cp[0] == ':' && cp[1] == '-'); + + if (is_mercury_declaration + || with_mercury_definitions) + { + ptrdiff_t len = mercury_pr (cp, last, lastlen); + if (0 < len) + { + /* Store the declaration to avoid generating duplicate + tags later. */ + if (allocated <= len) + { + xrnew (last, len + 1, 1); + allocated = len + 1; + } + memcpyz (last, cp, len); + lastlen = len; + } + } + } + } + free (last); +} + +static void +mercury_skip_comment (linebuffer *plb, FILE *inf) +{ + char *cp; + + do + { + for (cp = plb->buffer; *cp != '\0'; ++cp) + if (cp[0] == '*' && cp[1] == '/') + return; + readline (plb, inf); + } + while (perhaps_more_input (inf)); +} + +/* + * A declaration is added if it matches: + * <beginning of line>:-<whitespace><Mercury Term><whitespace>( + * If with_mercury_definitions == true, we also add: + * <beginning of line><Mercury item><whitespace>( + * or <beginning of line><Mercury item><whitespace>:- + * As for Prolog support, different arities and types are not taken into + * consideration. + * Item is added to the tags database if it doesn't match the + * name of the previous declaration. + * + * Consume a Mercury declaration. + * Return the number of bytes consumed, or 0 if there was an error. + * + * A Mercury declaration must be one of: + * :- type + * :- solver type + * :- pred + * :- func + * :- inst + * :- mode + * :- typeclass + * :- instance + * :- pragma + * :- promise + * :- initialise + * :- finalise + * :- mutable + * :- module + * :- interface + * :- implementation + * :- import_module + * :- use_module + * :- include_module + * :- end_module + * followed on the same line by an alphanumeric sequence, starting with a lower + * case letter or by a single-quoted arbitrary string. + * Single quotes can escape themselves. Backslash quotes everything. + * + * Return the size of the name of the declaration or 0 if no header was found. + * As quantifiers may precede functions or predicates, we must list them too. + */ + +static const char *Mercury_decl_tags[] = {"type", "solver type", "pred", + "func", "inst", "mode", "typeclass", "instance", "pragma", "promise", + "initialise", "finalise", "mutable", "module", "interface", "implementation", + "import_module", "use_module", "include_module", "end_module", "some", "all"}; + +static mercury_pos_t +mercury_decl (char *s, size_t pos) +{ + mercury_pos_t null_pos = {0, 0, 0}; + + if (s == NULL) return null_pos; + + size_t origpos; + origpos = pos; + + while (s + pos != NULL && (c_isalnum (s[pos]) || s[pos] == '_')) ++pos; + + unsigned char decl_type_length = pos - origpos; + char buf[decl_type_length + 1]; + memset (buf, 0, decl_type_length + 1); + + /* Mercury declaration tags. Consume them, then check the declaration item + following :- is legitimate, then go on as in the prolog case. */ + + memcpy (buf, &s[origpos], decl_type_length); + + bool found_decl_tag = false; + + if (is_mercury_quantifier) + { + if (strcmp (buf, "pred") != 0 && strcmp (buf, "func") != 0) /* Bad syntax. */ + return null_pos; + + is_mercury_quantifier = false; /* Reset to base value. */ + found_decl_tag = true; + } + else + { + for (int j = 0; j < sizeof (Mercury_decl_tags) / sizeof (char*); ++j) + { + if (strcmp (buf, Mercury_decl_tags[j]) == 0) + { + found_decl_tag = true; + if (strcmp (buf, "type") == 0) + is_mercury_type = true; + + if (strcmp (buf, "some") == 0 + || strcmp (buf, "all") == 0) + { + is_mercury_quantifier = true; + } + + break; /* Found declaration tag of rank j. */ + } + else + /* 'solver type' has a blank in the middle, + so this is the hard case. */ + if (strcmp (buf, "solver") == 0) + { + ++pos; + while (s + pos != NULL && (c_isalnum (s[pos]) || s[pos] == '_')) + ++pos; + + decl_type_length = pos - origpos; + char buf2[decl_type_length + 1]; + memset (buf2, 0, decl_type_length + 1); + memcpy (buf2, &s[origpos], decl_type_length); + + if (strcmp (buf2, "solver type") == 0) + { + found_decl_tag = false; + break; /* Found declaration tag of rank j. */ + } + } + } + } + + /* If with_mercury_definitions == false + * this is a Mercury syntax error, ignoring... */ + + if (with_mercury_definitions) + { + if (found_decl_tag) + pos = skip_spaces (s + pos) - s; /* Skip len blanks again. */ + else + /* Prolog-like behavior + * we have parsed the predicate once, yet inappropriately + * so restarting again the parsing step. */ + pos = 0; + } + else + { + if (found_decl_tag) + pos = skip_spaces (s + pos) - s; /* Skip len blanks again. */ + else + return null_pos; + } + + /* From now on it is the same as for Prolog except for module dots. */ + + size_t start_of_name = pos; + + if (c_islower (s[pos]) || s[pos] == '_' ) + { + /* The name is unquoted. + Do not confuse module dots with end-of-declaration dots. */ + int module_dot_pos = 0; + + while (c_isalnum (s[pos]) + || s[pos] == '_' + || (s[pos] == '.' /* A module dot. */ + && s + pos + 1 != NULL + && (c_isalnum (s[pos + 1]) || s[pos + 1] == '_') + && (module_dot_pos = pos))) /* Record module dot position. + Erase module from name. */ + ++pos; + + if (module_dot_pos) + { + start_of_name = module_dot_pos + 2; + ++pos; + } + + mercury_pos_t position = {pos, pos - start_of_name + 1, pos - origpos}; + return position; + } + else if (s[pos] == '\'') + { + ++pos; + for (;;) + { + if (s[pos] == '\'') + { + ++pos; + if (s[pos] != '\'') + break; + ++pos; /* A double quote. */ + } + else if (s[pos] == '\0') /* Multiline quoted atoms are ignored. */ + return null_pos; + else if (s[pos] == '\\') + { + if (s[pos+1] == '\0') + return null_pos; + pos += 2; + } + else + ++pos; + } + + mercury_pos_t position = {pos, pos - start_of_name + 1, pos - origpos}; + return position; + } + else if (is_mercury_quantifier && s[pos] == '[') /* :- some [T] pred/func. */ + { + for (++pos; s + pos != NULL && s[pos] != ']'; ++pos) {} + if (s + pos == NULL) return null_pos; + ++pos; + pos = skip_spaces (s + pos) - s; + mercury_pos_t position = mercury_decl (s, pos); + position.totlength += pos - origpos; + return position; + } + else if (s[pos] == '.') /* as in ':- interface.' */ + { + mercury_pos_t position = {pos, pos - origpos + 1, pos - origpos}; + return position; + } + else + return null_pos; +} + +static ptrdiff_t +mercury_pr (char *s, char *last, ptrdiff_t lastlen) +{ + size_t len0 = 0; + is_mercury_type = false; + is_mercury_quantifier = false; + bool stop_at_rule = false; + + if (is_mercury_declaration) + { + /* Skip len0 blanks only for declarations. */ + len0 = skip_spaces (s + 2) - s; + } + + mercury_pos_t position = mercury_decl (s, len0); + size_t pos = position.pos; + int offset = 0; /* may be < 0 */ + if (pos == 0) return 0; + + /* Skip white space for: + a. rules in definitions before :- + b. 0-arity predicates with inlined modes. + c. possibly multiline type definitions */ + + while (c_isspace (s[pos])) { ++pos; ++offset; } + + if (( ((s[pos] == '.' && (pos += 1)) /* case 1 + This is a statement dot, + not a module dot. */ + || c_isalnum(s[pos]) /* 0-arity procedures */ + || (s[pos] == '(' && (pos += 1)) /* case 2: arity > 0 */ + || ((s[pos] == ':') /* case 3: rules */ + && s[pos + 1] == '-' && (stop_at_rule = true))) + && (lastlen != pos || memcmp (s, last, pos) != 0) + ) + /* Types are often declared on several lines so keeping just + the first line. */ + + || is_mercury_type) /* When types are implemented. */ + { + size_t namelength = position.namelength; + if (stop_at_rule && offset) --offset; + + /* Left-trim type definitions. */ + + while (pos > namelength + offset + && c_isspace (s[pos - namelength - offset])) + --offset; + + make_tag (s + pos - namelength - offset, namelength - 1, true, + s, pos - offset - 1, lineno, linecharno); + return pos; + } + + return 0; +} + + +/* * Support for Erlang * * Generates tags for functions, defines, and records. @@ -6324,7 +6909,6 @@ add_regex (char *regexp_pattern, language *lang) struct re_pattern_buffer *patbuf; regexp *rp; bool - force_explicit_name = true, /* do not use implicit tag names */ ignore_case = false, /* case is significant */ multi_line = false, /* matches are done one line at a time */ single_line = false; /* dot does not match newline */ @@ -6363,7 +6947,8 @@ add_regex (char *regexp_pattern, language *lang) case 'N': if (modifiers == name) error ("forcing explicit tag name but no name, ignoring"); - force_explicit_name = true; + /* This option has no effect and is present only for backward + compatibility. */ break; case 'i': ignore_case = true; @@ -6418,7 +7003,6 @@ add_regex (char *regexp_pattern, language *lang) p_head->pat = patbuf; p_head->name = savestr (name); p_head->error_signaled = false; - p_head->force_explicit_name = force_explicit_name; p_head->ignore_case = ignore_case; p_head->multi_line = multi_line; } @@ -6558,20 +7142,15 @@ regex_tag_multiline (void) name = NULL; else /* make a named tag */ name = substitute (buffer, rp->name, &rp->regs); - if (rp->force_explicit_name) - { - /* Force explicit tag name, if a name is there. */ - pfnote (name, true, buffer + linecharno, - charno - linecharno + 1, lineno, linecharno); - - if (debug) - fprintf (stderr, "%s on %s:%"PRIdMAX": %s\n", - name ? name : "(unnamed)", curfdp->taggedfname, - lineno, buffer + linecharno); - } - else - make_tag (name, strlen (name), true, buffer + linecharno, - charno - linecharno + 1, lineno, linecharno); + + /* Force explicit tag name, if a name is there. */ + pfnote (name, true, buffer + linecharno, + charno - linecharno + 1, lineno, linecharno); + + if (debug) + fprintf (stderr, "%s on %s:%"PRIdMAX": %s\n", + name ? name : "(unnamed)", curfdp->taggedfname, + lineno, buffer + linecharno); break; } } @@ -6885,18 +7464,14 @@ readline (linebuffer *lbp, FILE *stream) name = NULL; else /* make a named tag */ name = substitute (lbp->buffer, rp->name, &rp->regs); - if (rp->force_explicit_name) - { - /* Force explicit tag name, if a name is there. */ - pfnote (name, true, lbp->buffer, match, lineno, linecharno); - if (debug) - fprintf (stderr, "%s on %s:%"PRIdMAX": %s\n", - name ? name : "(unnamed)", curfdp->taggedfname, - lineno, lbp->buffer); - } - else - make_tag (name, strlen (name), true, - lbp->buffer, match, lineno, linecharno); + + /* Force explicit tag name, if a name is there. */ + pfnote (name, true, lbp->buffer, match, lineno, linecharno); + + if (debug) + fprintf (stderr, "%s on %s:%"PRIdMAX": %s\n", + name ? name : "(unnamed)", curfdp->taggedfname, + lineno, lbp->buffer); break; } } diff --git a/lib-src/movemail.c b/lib-src/movemail.c index cfdebccb8d0..e683da179df 100644 --- a/lib-src/movemail.c +++ b/lib-src/movemail.c @@ -270,6 +270,7 @@ main (int argc, char **argv) You might also wish to verify that your system is one which uses lock files for this purpose. Some systems use other methods. */ + bool lockname_unlinked = false; inname_len = strlen (inname); lockname = xmalloc (inname_len + sizeof ".lock"); strcpy (lockname, inname); @@ -312,15 +313,10 @@ main (int argc, char **argv) Five minutes should be good enough to cope with crashes and wedgitude, and long enough to avoid being fooled by time differences between machines. */ - if (stat (lockname, &st) >= 0) - { - time_t now = time (0); - if (st.st_ctime < now - 300) - { - unlink (lockname); - lockname = 0; - } - } + if (!lockname_unlinked + && stat (lockname, &st) == 0 + && st.st_ctime < time (0) - 300) + lockname_unlinked = unlink (lockname) == 0 || errno == ENOENT; } delete_lockname = lockname; diff --git a/lib-src/seccomp-filter.c b/lib-src/seccomp-filter.c new file mode 100644 index 00000000000..dc568e035b5 --- /dev/null +++ b/lib-src/seccomp-filter.c @@ -0,0 +1,370 @@ +/* Generate a Secure Computing filter definition file. + +Copyright (C) 2020-2021 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your +option) any later version. + +GNU Emacs is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs. If not, see +<https://www.gnu.org/licenses/>. */ + +/* This program creates a small Secure Computing filter usable for a +typical minimal Emacs sandbox. See the man page for `seccomp' for +details about Secure Computing filters. This program requires the +`libseccomp' library. However, the resulting filter file requires +only a Linux kernel supporting the Secure Computing extension. + +Usage: + + seccomp-filter out.bpf out.pfc out-exec.bpf out-exec.pfc + +This writes the raw `struct sock_filter' array to out.bpf and a +human-readable representation to out.pfc. Additionally, it writes +variants of those files that can be used to sandbox Emacs before +'execve' to out-exec.bpf and out-exec.pfc. */ + +#include "config.h" + +#include <assert.h> +#include <errno.h> +#include <limits.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <time.h> + +#include <asm/prctl.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <linux/futex.h> +#include <linux/filter.h> +#include <linux/seccomp.h> +#include <fcntl.h> +#include <sched.h> +#include <seccomp.h> +#include <unistd.h> + +#include "verify.h" + +#ifndef ARCH_CET_STATUS +#define ARCH_CET_STATUS 0x3001 +#endif + +static ATTRIBUTE_FORMAT_PRINTF (2, 3) _Noreturn void +fail (int error, const char *format, ...) +{ + va_list ap; + va_start (ap, format); + if (error == 0) + { + vfprintf (stderr, format, ap); + fputc ('\n', stderr); + } + else + { + char buffer[1000]; + vsnprintf (buffer, sizeof buffer, format, ap); + errno = error; + perror (buffer); + } + va_end (ap); + fflush (NULL); + exit (EXIT_FAILURE); +} + +/* This binary is trivial, so we use a single global filter context + object that we release using `atexit'. */ + +static scmp_filter_ctx ctx; + +static void +release_context (void) +{ + seccomp_release (ctx); +} + +/* Wrapper functions and macros for libseccomp functions. We exit + immediately upon any error to avoid error checking noise. */ + +static void +set_attribute (enum scmp_filter_attr attr, uint32_t value) +{ + int status = seccomp_attr_set (ctx, attr, value); + if (status < 0) + fail (-status, "seccomp_attr_set (ctx, %u, %u)", attr, value); +} + +/* Like `seccomp_rule_add (ACTION, SYSCALL, ...)', except that you + don't have to specify the number of comparator arguments, and any + failure will exit the process. */ + +#define RULE(action, syscall, ...) \ + do \ + { \ + const struct scmp_arg_cmp arg_array[] = {__VA_ARGS__}; \ + enum { arg_cnt = sizeof arg_array / sizeof *arg_array }; \ + int status = seccomp_rule_add_array (ctx, (action), (syscall), \ + arg_cnt, arg_array); \ + if (status < 0) \ + fail (-status, "seccomp_rule_add_array (%s, %s, %d, {%s})", \ + #action, #syscall, arg_cnt, #__VA_ARGS__); \ + } \ + while (false) + +static void +export_filter (const char *file, + int (*function) (const scmp_filter_ctx, int), + const char *name) +{ + int fd = TEMP_FAILURE_RETRY ( + open (file, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_CLOEXEC, + 0644)); + if (fd < 0) + fail (errno, "open %s", file); + int status = function (ctx, fd); + if (status < 0) + fail (-status, "%s", name); + if (close (fd) != 0) + fail (errno, "close"); +} + +#define EXPORT_FILTER(file, function) \ + export_filter ((file), (function), #function) + +int +main (int argc, char **argv) +{ + if (argc != 5) + fail (0, "usage: %s out.bpf out.pfc out-exec.bpf out-exec.pfc", + argv[0]); + + /* Any unhandled syscall should abort the Emacs process. */ + ctx = seccomp_init (SCMP_ACT_KILL_PROCESS); + if (ctx == NULL) + fail (0, "seccomp_init"); + atexit (release_context); + + /* We want to abort immediately if the architecture is unknown. */ + set_attribute (SCMP_FLTATR_ACT_BADARCH, SCMP_ACT_KILL_PROCESS); + set_attribute (SCMP_FLTATR_CTL_NNP, 1); + set_attribute (SCMP_FLTATR_CTL_TSYNC, 1); + + verify (CHAR_BIT == 8); + verify (sizeof (int) == 4 && INT_MIN == INT32_MIN + && INT_MAX == INT32_MAX); + verify (sizeof (long) == 8 && LONG_MIN == INT64_MIN + && LONG_MAX == INT64_MAX); + verify (sizeof (void *) == 8); + assert ((uintptr_t) NULL == 0); + + /* Allow a clean exit. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (exit)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (exit_group)); + + /* Allow `mmap' and friends. This is necessary for dynamic loading, + reading the portable dump file, and thread creation. We don't + allow pages to be both writable and executable. */ + verify (MAP_PRIVATE != 0); + verify (MAP_SHARED != 0); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (mmap), + SCMP_A2_32 (SCMP_CMP_MASKED_EQ, + ~(PROT_NONE | PROT_READ | PROT_WRITE)), + /* Only support known flags. MAP_DENYWRITE is ignored, but + some versions of the dynamic loader still use it. Also + allow allocating thread stacks. */ + SCMP_A3_32 (SCMP_CMP_MASKED_EQ, + ~(MAP_SHARED | MAP_PRIVATE | MAP_FILE + | MAP_ANONYMOUS | MAP_FIXED | MAP_DENYWRITE + | MAP_STACK | MAP_NORESERVE), + 0)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (mmap), + SCMP_A2_32 (SCMP_CMP_MASKED_EQ, + ~(PROT_NONE | PROT_READ | PROT_EXEC)), + /* Only support known flags. MAP_DENYWRITE is ignored, but + some versions of the dynamic loader still use it. */ + SCMP_A3_32 (SCMP_CMP_MASKED_EQ, + ~(MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED + | MAP_DENYWRITE), + 0)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (munmap)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (mprotect), + /* Don't allow making pages executable. */ + SCMP_A2_32 (SCMP_CMP_MASKED_EQ, + ~(PROT_NONE | PROT_READ | PROT_WRITE), 0)); + + /* Futexes are used everywhere. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (futex), + SCMP_A1_32 (SCMP_CMP_EQ, FUTEX_WAKE_PRIVATE)); + + /* Allow basic dynamic memory management. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (brk)); + + /* Allow some status inquiries. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (uname)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (getuid)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (geteuid)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (getpid)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (getpgrp)); + + /* Allow operations on open file descriptors. File descriptors are + capabilities, and operating on them shouldn't cause security + issues. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (read)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (write)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (close)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (lseek)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (dup)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (dup2)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (fstat)); + + /* Allow read operations on the filesystem. If necessary, these + should be further restricted using mount namespaces. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (access)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (faccessat)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (stat)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (stat64)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (lstat)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (lstat64)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (fstatat64)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (newfstatat)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (readlink)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (readlinkat)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (getcwd)); + + /* Allow opening files, assuming they are only opened for + reading. */ + verify (O_WRONLY != 0); + verify (O_RDWR != 0); + verify (O_CREAT != 0); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (open), + SCMP_A1_32 (SCMP_CMP_MASKED_EQ, + ~(O_RDONLY | O_BINARY | O_CLOEXEC | O_PATH + | O_DIRECTORY | O_NOFOLLOW), + 0)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (openat), + SCMP_A2_32 (SCMP_CMP_MASKED_EQ, + ~(O_RDONLY | O_BINARY | O_CLOEXEC | O_PATH + | O_DIRECTORY | O_NOFOLLOW), + 0)); + + /* Allow `tcgetpgrp'. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (ioctl), + SCMP_A0_32 (SCMP_CMP_EQ, STDIN_FILENO), + SCMP_A1_32 (SCMP_CMP_EQ, TIOCGPGRP)); + + /* Allow reading (but not setting) file flags. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (fcntl), + SCMP_A1_32 (SCMP_CMP_EQ, F_GETFL)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (fcntl64), + SCMP_A1_32 (SCMP_CMP_EQ, F_GETFL)); + + /* Allow reading random numbers from the kernel. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (getrandom)); + + /* Changing the umask is uncritical. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (umask)); + + /* Allow creation of pipes. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (pipe)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (pipe2)); + + /* Allow reading (but not changing) resource limits. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (getrlimit)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (prlimit64), + SCMP_A0_32 (SCMP_CMP_EQ, 0) /* pid == 0 (current process) */, + SCMP_A2_64 (SCMP_CMP_EQ, 0) /* new_limit == NULL */); + + /* Block changing resource limits, but don't crash. */ + RULE (SCMP_ACT_ERRNO (EPERM), SCMP_SYS (prlimit64), + SCMP_A0_32 (SCMP_CMP_EQ, 0) /* pid == 0 (current process) */, + SCMP_A2_64 (SCMP_CMP_NE, 0) /* new_limit != NULL */); + + /* Emacs installs signal handlers, which is harmless. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (sigaction)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (rt_sigaction)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (sigprocmask)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (rt_sigprocmask)); + + /* Allow reading the current time. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (clock_gettime), + SCMP_A0_32 (SCMP_CMP_EQ, CLOCK_REALTIME)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (time)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (gettimeofday)); + + /* Allow timer support. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (timer_create)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (timerfd_create)); + + /* Allow thread creation. See the NOTES section in the manual page + for the `clone' function. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (clone), + SCMP_A0_64 (SCMP_CMP_MASKED_EQ, + /* Flags needed to create threads. See + create_thread in libc. */ + ~(CLONE_VM | CLONE_FS | CLONE_FILES + | CLONE_SYSVSEM | CLONE_SIGHAND | CLONE_THREAD + | CLONE_SETTLS | CLONE_PARENT_SETTID + | CLONE_CHILD_CLEARTID), + 0)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (sigaltstack)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (set_robust_list)); + + /* Allow setting the process name for new threads. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (prctl), + SCMP_A0_32 (SCMP_CMP_EQ, PR_SET_NAME)); + + /* Allow some event handling functions used by glib. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (eventfd)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (eventfd2)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (wait4)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (poll)); + + /* Don't allow creating sockets (network access would be extremely + dangerous), but also don't crash. */ + RULE (SCMP_ACT_ERRNO (EACCES), SCMP_SYS (socket)); + + EXPORT_FILTER (argv[1], seccomp_export_bpf); + EXPORT_FILTER (argv[2], seccomp_export_pfc); + + /* When applying a Seccomp filter before executing the Emacs binary + (e.g. using the `bwrap' program), we need to allow further system + calls. Firstly, the wrapper binary will need to `execve' the + Emacs binary. Furthermore, the C library requires some system + calls at startup time to set up thread-local storage. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (execve)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (set_tid_address)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (arch_prctl), + SCMP_A0_32 (SCMP_CMP_EQ, ARCH_SET_FS)); + RULE (SCMP_ACT_ERRNO (EINVAL), SCMP_SYS (arch_prctl), + SCMP_A0_32 (SCMP_CMP_EQ, ARCH_CET_STATUS)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (statfs)); + + /* We want to allow starting the Emacs binary itself with the + --seccomp flag, so we need to allow the `prctl' and `seccomp' + system calls. */ + RULE (SCMP_ACT_ALLOW, SCMP_SYS (prctl), + SCMP_A0_32 (SCMP_CMP_EQ, PR_SET_NO_NEW_PRIVS), + SCMP_A1_64 (SCMP_CMP_EQ, 1), SCMP_A2_64 (SCMP_CMP_EQ, 0), + SCMP_A3_64 (SCMP_CMP_EQ, 0), SCMP_A4_64 (SCMP_CMP_EQ, 0)); + RULE (SCMP_ACT_ALLOW, SCMP_SYS (seccomp), + SCMP_A0_32 (SCMP_CMP_EQ, SECCOMP_SET_MODE_FILTER), + SCMP_A1_32 (SCMP_CMP_EQ, SECCOMP_FILTER_FLAG_TSYNC)); + + EXPORT_FILTER (argv[3], seccomp_export_bpf); + EXPORT_FILTER (argv[4], seccomp_export_pfc); +} |