summaryrefslogtreecommitdiff
path: root/lib-src
diff options
context:
space:
mode:
Diffstat (limited to 'lib-src')
-rw-r--r--lib-src/Makefile.in73
-rw-r--r--lib-src/emacsclient.c226
-rw-r--r--lib-src/etags.c647
-rw-r--r--lib-src/movemail.c14
-rw-r--r--lib-src/seccomp-filter.c370
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);
+}