Fix etags local command injection vulnerability (CVE-2022-48337) Backported from emacs-28 branch https://bugs.gentoo.org/897950 https://debbugs.gnu.org/59817 commit e339926272a598bd9ee7e02989c1662b89e64cf0 Author: Xi Lu Date: Tue Dec 6 15:42:40 2022 +0800 Fix etags local command injection vulnerability --- emacs-27.2/lib-src/etags.c +++ emacs-27.2/lib-src/etags.c @@ -398,6 +398,7 @@ 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 *); @@ -1670,13 +1671,16 @@ else { #if MSDOS || defined (DOS_NT) - char *cmd1 = concat (compr->command, " \"", real_name); - char *cmd = concat (cmd1, "\" > ", tmp_name); + 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 *cmd1 = concat (compr->command, " '", real_name); - char *cmd = concat (cmd1, "' > ", tmp_name); + 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 - free (cmd1); int tmp_errno; if (system (cmd) == -1) { @@ -7124,6 +7128,55 @@ 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) {