File 01a4035c.patch of Package emacs.33335
From e339926272a598bd9ee7e02989c1662b89e64cf0 Mon Sep 17 00:00:00 2001
From: lu4nx <lx@shellcodes.org>
Date: Tue, 6 Dec 2022 15:42:40 +0800
Subject: [PATCH] Fix etags local command injection vulnerability
* lib-src/etags.c: (escape_shell_arg_string): New function.
(process_file_name): Use it to quote file names passed to the
shell. (Bug#59817)
(cherry picked from commit 01a4035c869b91c153af9a9132c87adb7669ea1c)
---
lib-src/etags.c | 241 ++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 185 insertions(+), 56 deletions(-)
--- lib-src/etags.c
+++ lib-src/etags.c 2023-02-21 11:12:06.012397649 +0000
@@ -142,6 +142,7 @@ char pot_etags_version[] = "@(#) pot rev
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
+#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <c-strcase.h>
@@ -370,6 +371,7 @@ static void invalidate_nodes (fdesc *, n
static void put_entries (node *);
static void clean_matched_file_tag (char const * const, char const * const);
+static char *escape_shell_arg_string (char *);
static void do_move_file (const char *, const char *);
static char *concat (const char *, const char *, const char *);
static char *skip_spaces (char *);
@@ -384,6 +386,7 @@ static char *absolute_filename (char *,
static char *absolute_dirname (char *, char *);
static bool filename_is_absolute (char *f);
static void canonicalize_filename (char *);
+static char *etags_mktmp (void);
static void linebuffer_init (linebuffer *);
static void linebuffer_setlen (linebuffer *, int);
static void *xmalloc (size_t);
@@ -1526,12 +1529,11 @@ get_language_from_filename (char *file,
static void
process_file_name (char *file, language *lang)
{
- struct stat stat_buf;
FILE *inf;
fdesc *fdp;
compressor *compr;
char *compressed_name, *uncompressed_name;
- char *ext, *real_name;
+ char *ext, *real_name, *tmp_name;
int retval;
canonicalize_filename (file);
@@ -1540,15 +1542,16 @@ process_file_name (char *file, language
error ("skipping inclusion of %s in self.", file);
return;
}
- if ((compr = get_compressor_from_suffix (file, &ext)) == NULL)
+ compr = get_compressor_from_suffix (file, &ext);
+ if (compr)
{
- compressed_name = NULL;
- real_name = uncompressed_name = savestr (file);
+ compressed_name = file;
+ uncompressed_name = savenstr (file, ext - file);
}
else
{
- real_name = compressed_name = savestr (file);
- uncompressed_name = savenstr (file, ext - file);
+ compressed_name = NULL;
+ uncompressed_name = file;
}
/* If the canonicalized uncompressed name
@@ -1560,86 +1563,117 @@ process_file_name (char *file, language
goto cleanup;
}
- if (stat (real_name, &stat_buf) != 0)
+ inf = fopen (file, "r");
+ if (inf)
+ real_name = file;
+ else
{
- /* Reset real_name and try with a different name. */
- real_name = NULL;
- if (compressed_name != NULL) /* try with the given suffix */
+ int file_errno = errno;
+ if (compressed_name)
{
- if (stat (uncompressed_name, &stat_buf) == 0)
+ /* Try with the given suffix. */
+ inf = fopen (uncompressed_name, "r");
+ if (inf)
real_name = uncompressed_name;
}
- else /* try all possible suffixes */
+ else
{
+ /* Try all possible suffixes. */
for (compr = compressors; compr->suffix != NULL; compr++)
{
compressed_name = concat (file, ".", compr->suffix);
- if (stat (compressed_name, &stat_buf) != 0)
+ inf = fopen (compressed_name, "r");
+ if (inf)
+ {
+ real_name = compressed_name;
+ break;
+ }
+ if (MSDOS)
{
- if (MSDOS)
+ char *suf = compressed_name + strlen (file);
+ size_t suflen = strlen (compr->suffix) + 1;
+ for ( ; suf[1]; suf++, suflen--)
{
- char *suf = compressed_name + strlen (file);
- size_t suflen = strlen (compr->suffix) + 1;
- for ( ; suf[1]; suf++, suflen--)
+ memmove (suf, suf + 1, suflen);
+ inf = fopen (compressed_name, "r");
+ if (inf)
{
- memmove (suf, suf + 1, suflen);
- if (stat (compressed_name, &stat_buf) == 0)
- {
- real_name = compressed_name;
- break;
- }
+ real_name = compressed_name;
+ break;
}
- if (real_name != NULL)
- break;
- } /* MSDOS */
- free (compressed_name);
- compressed_name = NULL;
- }
- else
- {
- real_name = compressed_name;
- break;
+ }
+ if (inf)
+ break;
}
+ free (compressed_name);
+ compressed_name = NULL;
}
}
- if (real_name == NULL)
+ if (! inf)
{
+ errno = file_errno;
perror (file);
goto cleanup;
}
- } /* try with a different name */
-
- if (!S_ISREG (stat_buf.st_mode))
- {
- error ("skipping %s: it is not a regular file.", real_name);
- goto cleanup;
}
+
if (real_name == compressed_name)
{
- char *cmd = concat (compr->command, " ", real_name);
- inf = (FILE *) popen (cmd, "r");
- free (cmd);
- }
- else
- inf = fopen (real_name, "r");
- if (inf == NULL)
- {
- perror (real_name);
- goto cleanup;
+ fclose (inf);
+ tmp_name = etags_mktmp ();
+ if (!tmp_name)
+ inf = NULL;
+ else
+ {
+#if MSDOS || defined (DOS_NT)
+ int buf_len = strlen (compr->command) + strlen (" \"\" > \"\"") + strlen (real_name) + strlen (tmp_name) + 1;
+ char *cmd = xmalloc (buf_len);
+ snprintf (cmd, buf_len, "%s \"%s\" > \"%s\"", compr->command, real_name, tmp_name);
+#else
+ char *new_real_name = escape_shell_arg_string (real_name);
+ char *new_tmp_name = escape_shell_arg_string (tmp_name);
+ int buf_len = strlen (compr->command) + strlen (" > ") + strlen (new_real_name) + strlen (new_tmp_name) + 1;
+ char *cmd = xmalloc (buf_len);
+ snprintf (cmd, buf_len, "%s %s > %s", compr->command, new_real_name, new_tmp_name);
+#endif
+ int tmp_errno;
+ if (system (cmd) == -1)
+ {
+ inf = NULL;
+ tmp_errno = EINVAL;
+ }
+ else
+ {
+ inf = fopen (tmp_name, "r");
+ tmp_errno = errno;
+ }
+ free (cmd);
+ errno = tmp_errno;
+ }
+
+ if (!inf)
+ {
+ perror (real_name);
+ goto cleanup;
+ }
}
process_file (inf, uncompressed_name, lang);
+ retval = fclose (inf);
if (real_name == compressed_name)
- retval = pclose (inf);
- else
- retval = fclose (inf);
+ {
+ remove (tmp_name);
+ free (tmp_name);
+ }
if (retval < 0)
pfatal (file);
cleanup:
- free (compressed_name);
- free (uncompressed_name);
+ if (compressed_name != file)
+ free (compressed_name);
+ if (uncompressed_name != file)
+ free (uncompressed_name);
last_node = NULL;
curfdp = NULL;
return;
@@ -6454,6 +6488,101 @@ etags_getcwd (void)
#endif /* not HAVE_GETCWD */
}
+/* Return a newly allocated string containing a name of a temporary file. */
+static char *
+etags_mktmp (void)
+{
+ const char *tmpdir = getenv ("TMPDIR");
+ const char *slash = "/";
+
+#if MSDOS || defined (DOS_NT)
+ if (!tmpdir)
+ tmpdir = getenv ("TEMP");
+ if (!tmpdir)
+ tmpdir = getenv ("TMP");
+ if (!tmpdir)
+ tmpdir = ".";
+ if (tmpdir[strlen (tmpdir) - 1] == '/'
+ || tmpdir[strlen (tmpdir) - 1] == '\\')
+ slash = "";
+#else
+ if (!tmpdir)
+ tmpdir = "/tmp";
+ if (tmpdir[strlen (tmpdir) - 1] == '/')
+ slash = "";
+#endif
+
+ char *templt = concat (tmpdir, slash, "etXXXXXX");
+ int fd = mkostemp (templt, O_CLOEXEC);
+ if (fd < 0 || close (fd) != 0)
+ {
+ int temp_errno = errno;
+ free (templt);
+ errno = temp_errno;
+ templt = NULL;
+ }
+
+#if defined (DOS_NT)
+ /* The file name will be used in shell redirection, so it needs to have
+ DOS-style backslashes, or else the Windows shell will barf. */
+ char *p;
+ for (p = templt; *p; p++)
+ if (*p == '/')
+ *p = '\\';
+#endif
+
+ return templt;
+}
+
+/*
+ * Adds single quotes around a string, if found single quotes, escaped it.
+ * Return a newly-allocated string.
+ *
+ * For example:
+ * escape_shell_arg_string("test.txt") => 'test.txt'
+ * escape_shell_arg_string("'test.txt") => ''\''test.txt'
+ */
+static char *
+escape_shell_arg_string (char *str)
+{
+ char *p = str;
+ int need_space = 2; /* ' at begin and end */
+
+ while (*p != '\0')
+ {
+ if (*p == '\'')
+ need_space += 4; /* ' to '\'', length is 4 */
+ else
+ need_space++;
+
+ p++;
+ }
+
+ char *new_str = xnew (need_space + 1, char);
+ new_str[0] = '\'';
+ new_str[need_space-1] = '\'';
+
+ int i = 1; /* skip first byte */
+ p = str;
+ while (*p != '\0')
+ {
+ new_str[i] = *p;
+ if (*p == '\'')
+ {
+ new_str[i+1] = '\\';
+ new_str[i+2] = '\'';
+ new_str[i+3] = '\'';
+ i += 3;
+ }
+
+ i++;
+ p++;
+ }
+
+ new_str[need_space] = '\0';
+ return new_str;
+}
+
static void
do_move_file(const char *src_file, const char *dst_file)
{