From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: lux Newsgroups: gmane.emacs.bugs Subject: bug#59817: [PATCH] Fix etags local command injection vulnerability Date: Sun, 4 Dec 2022 21:51:13 +0800 Message-ID: Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="MP_/nUhn8RG=Y4UPID575FtLTji" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="2826"; mail-complaints-to="usenet@ciao.gmane.io" To: 59817@debbugs.gnu.org Original-X-From: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Sun Dec 04 14:52:16 2022 Return-path: Envelope-to: geb-bug-gnu-emacs@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1p1pPj-0000Xe-Vi for geb-bug-gnu-emacs@m.gmane-mx.org; Sun, 04 Dec 2022 14:52:16 +0100 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1p1pPb-0002Ki-AL; Sun, 04 Dec 2022 08:52:07 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p1pPW-0002KO-KX for bug-gnu-emacs@gnu.org; Sun, 04 Dec 2022 08:52:02 -0500 Original-Received: from debbugs.gnu.org ([209.51.188.43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1p1pPW-0006gL-D3 for bug-gnu-emacs@gnu.org; Sun, 04 Dec 2022 08:52:02 -0500 Original-Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1p1pPV-0008Nu-Rw for bug-gnu-emacs@gnu.org; Sun, 04 Dec 2022 08:52:01 -0500 X-Loop: help-debbugs@gnu.org Resent-From: lux Original-Sender: "Debbugs-submit" Resent-CC: bug-gnu-emacs@gnu.org Resent-Date: Sun, 04 Dec 2022 13:52:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 59817 X-GNU-PR-Package: emacs X-GNU-PR-Keywords: patch X-Debbugs-Original-To: bug-gnu-emacs@gnu.org Original-Received: via spool by submit@debbugs.gnu.org id=B.167016189632215 (code B ref -1); Sun, 04 Dec 2022 13:52:01 +0000 Original-Received: (at submit) by debbugs.gnu.org; 4 Dec 2022 13:51:36 +0000 Original-Received: from localhost ([127.0.0.1]:57792 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1p1pP5-0008NX-QO for submit@debbugs.gnu.org; Sun, 04 Dec 2022 08:51:36 -0500 Original-Received: from lists.gnu.org ([209.51.188.17]:38760) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1p1pOz-0008NP-Jl for submit@debbugs.gnu.org; Sun, 04 Dec 2022 08:51:34 -0500 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p1pOz-0002EL-Cr for bug-gnu-emacs@gnu.org; Sun, 04 Dec 2022 08:51:29 -0500 Original-Received: from out162-62-57-210.mail.qq.com ([162.62.57.210]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1p1pOw-0006Sb-2M for bug-gnu-emacs@gnu.org; Sun, 04 Dec 2022 08:51:29 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=qq.com; s=s201512; t=1670161877; bh=BsLGHQdBxKQHaOQNnaQn657Z05oea+dw25MGKeFD2I8=; h=Date:From:To:Subject; b=OARw1klic1uu7RovdL/3vVUVycF3rshkSMS8wzAYMbfYilivd0dlRG0LCQyAbznYe RLPxC2EuIZXBfzWv+KJbsTEsT5+brxEpGfrrizPZrOQXAeFPvXREpYCdK3TWEUdi4i g2vYJTGkoFAKvs3uXPq4fGOmmEQ0l0Mf83S+96MQ= Original-Received: from lx-pc ([240e:399:e6f:ee32:f815:4044:ba50:97f9]) by newxmesmtplogicsvrszc2-0.qq.com (NewEsmtp) with SMTP id CCF3960E; Sun, 04 Dec 2022 21:51:15 +0800 X-QQ-mid: xmsmtpt1670161875t0wihs13i X-QQ-XMAILINFO: OKKHiI6c9SH3nVOafSLwHZ2zjVIrWeLpHJfpJf439X9761FCS94izUgUZgchHn ecGaNqxQBjpyDKdbY+sgq+ctTpZ3g2PekHUbi8DoyTvKZO/D+AHO3nZCrQpG6xRa+Ez1lqvfC2cr zg6Cj7A8gfOYL/optD4phZscweDP0C4fhCwe5vETGDznPbE63IsQtoeeJMy61YN31/gbK6+PcdKr c2ChHBWtTBnpQw4QeFmft0GSjfBZeeN+5DfsupfGAwU0sqH4JAfnWQ5vpTOWYgYhNvwTD9Fur0j6 gNtS1pKlIj7/Eex/TPn20ZWjmx2d8FjvnLO4NxLQzpOzEGtx4rMkNhTx8GhjGqN5qEyDQtFBWBte Q5Jx5fQNbD8RYlRLDNnpQV9y7PKl0QN9oO7gGkuDff0QdN6XXeRJ4rw2DDzN8oophmSun5edPC7E uaGguQxw6H8Lww35IOMfW+Z0DjWakSaZAi+Sqv8f3gp2yoX4gi46qy84cPk+1yfTVMm/wbWhuFQt a/Eqt3qEgdeZ2dZdu+9hgoIoYdlYg/oQYi3lxtHlFd7MhkkIyH7NWKxzAxboJ8Q24aEjWE4Rr9t1 g3/1DMKGJTeNuX0rycI6sebEO4Aypa2QLW/VKfty3leEaSUZ3Pp2nMIitn+t4wbhUKatdsA6Mm23 eAsi1rFWP6ToQbrbSGdZuqyBYOiEfpwQ9cJCblOqiGO0g1nPLTVSsztDeyUPudQGuXvDDwj9xD4Y Uj+YTaTgmIIzAh18LKY4xSRatabv5+nxbniGi1ZsAqfhLK6FQwlZcUnTlDYgbo2ZZHN3wqeMfm0B SZkC2oNjcYl2chcKoppyUwqJn3YppUocLwuVpv1K X-OQ-MSGID: <20221204215113.6f003c5f@lx-pc> X-Mailer: Claws Mail 4.1.1 (GTK 3.24.35; x86_64-redhat-linux-gnu) Received-SPF: none client-ip=162.62.57.210; envelope-from=lx@shellcodes.org; helo=out162-62-57-210.mail.qq.com X-Spam_score_int: 10 X-Spam_score: 1.0 X-Spam_bar: + X-Spam_report: (1.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, HELO_DYNAMIC_IPADDR=1.951, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H2=-0.001, RDNS_DYNAMIC=0.982, SPF_HELO_NONE=0.001, SPF_NONE=0.001 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: bug-gnu-emacs@gnu.org List-Id: "Bug reports for GNU Emacs, the Swiss army knife of text editors" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Original-Sender: bug-gnu-emacs-bounces+geb-bug-gnu-emacs=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.bugs:249940 Archived-At: --MP_/nUhn8RG=Y4UPID575FtLTji Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Content-Disposition: inline Hi, this patch fix a new local command injection vulnerability in the etags.c. This vulnerability occurs in the following code: #if MSDOS || defined (DOS_NT) char *cmd1 = concat (compr->command, " \"", real_name); char *cmd = concat (cmd1, "\" > ", tmp_name); #else char *cmd1 = concat (compr->command, " '", real_name); char *cmd = concat (cmd1, "' > ", tmp_name); #endif free (cmd1); inf = (system (cmd) == -1 ? NULL : fopen (tmp_name, "r" FOPEN_BINARY)); free (cmd); } Vulnerability #1: for tmp_name variable, the value from the etags_mktmp() function, this function takes the value from the environment variable `TMPDIR`, `TEMP` or `TMP`, but without checking the value. So, if then hacker can control these environment variables, can execute the shell code. Attack example: $ ls etags.c $ zip etags.z etags.c adding: etags.c (deflated 72%) $ tmpdir="/tmp/;uname -a;/" $ mkdir $tmpdir $ TMPDIR=$tmpdir etags * sh: line 1: /tmp/: Is a directory Linux mypc 6.0.10-300.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Nov 26 16:55:13 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux sh: line 1: /etECggCJ: No such file or directory etags: skipping inclusion of TAGS in self. Vulnerability #2: If the target file is a compressed file, execute system commands (such as gzip, etc.), but do not check the file name. Attack example: $ ls etags.c $ zip "';uname -a;'test.z" etags.c <--- inject the shell code to filename adding: etags.c (deflated 72%) $ etags * gzip: .gz: No such file or directory Linux mypc 6.0.10-300.fc37.x86_64 #1 SMP PREEMPT_DYNAMIC Sat Nov 26 16:55:13 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux sh: line 1: test.z: command not found I fix this vulnerability. By create a process, instead of call the sh or cmd.exe, and this patch work the Linux, BSD and Windows. --MP_/nUhn8RG=Y4UPID575FtLTji Content-Type: text/x-patch Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=0001-Fix-etags-local-command-injection-vulnerability.patch >From 30cec9dcbd67998ce2360d191044e8d97992f794 Mon Sep 17 00:00:00 2001 From: lu4nx Date: Sun, 4 Dec 2022 21:18:29 +0800 Subject: [PATCH] Fix etags local command injection vulnerability * lib-src/etags.c: (decompress_file): New function --- lib-src/etags.c | 155 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 121 insertions(+), 34 deletions(-) diff --git a/lib-src/etags.c b/lib-src/etags.c index d1d20858cd..7c5faf8d6b 100644 --- a/lib-src/etags.c +++ b/lib-src/etags.c @@ -97,6 +97,7 @@ Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2022 Free Software #ifdef WINDOWSNT # include +# include # undef HAVE_NTGUI # undef DOS_NT # define DOS_NT @@ -124,6 +125,7 @@ Copyright (C) 1984, 1987-1989, 1993-1995, 1998-2022 Free Software #include #include #include +#include /* Define CTAGS to make the program "ctags" compatible with the usual one. Leave it undefined to make the program "etags", which makes emacs-style @@ -255,7 +257,7 @@ #define xrnew(op, n, m) ((op) = xnrealloc (op, n, (m) * sizeof *(op))) typedef struct { const char *suffix; /* file name suffix for this compressor */ - const char *command; /* takes one arg and decompresses to stdout */ + const char *command; /* uncompress command */ } compressor; typedef struct @@ -391,6 +393,7 @@ #define xrnew(op, n, m) ((op) = xnrealloc (op, n, (m) * sizeof *(op))) static _Noreturn void pfatal (const char *); static void add_node (node *, node **); +static bool decompress_file (const char *, const char *, const char *); static void process_file_name (char *, language *); static void process_file (FILE *, char *, language *); static void find_entries (FILE *); @@ -527,16 +530,16 @@ #define STDIN 0x1001 /* returned by getopt_long on --parse-stdin */ }; static compressor compressors[] = -{ - { "z", "gzip -d -c"}, - { "Z", "gzip -d -c"}, - { "gz", "gzip -d -c"}, - { "GZ", "gzip -d -c"}, - { "bz2", "bzip2 -d -c" }, - { "xz", "xz -d -c" }, - { "zst", "zstd -d -c" }, - { NULL } -}; + { + { "z", "gzip" }, + { "Z", "gzip" }, + { "gz", "gzip" }, + { "GZ", "gzip" }, + { "bz2", "bzip2" }, + { "xz", "xz" }, + { "zst", "zstd" }, + { NULL } + }; /* * Language stuff. @@ -1621,7 +1624,6 @@ process_file_name (char *file, language *lang) compressor *compr; char *compressed_name, *uncompressed_name; char *ext, *real_name UNINIT, *tmp_name UNINIT; - int retval; canonicalize_filename (file); if (streq (file, tagfile) && !streq (tagfile, "-")) @@ -1712,37 +1714,29 @@ process_file_name (char *file, language *lang) inf = NULL; else { -#if MSDOS || defined (DOS_NT) - char *cmd1 = concat (compr->command, " \"", real_name); - char *cmd = concat (cmd1, "\" > ", tmp_name); -#else - char *cmd1 = concat (compr->command, " '", real_name); - char *cmd = concat (cmd1, "' > ", tmp_name); -#endif - free (cmd1); - inf = (system (cmd) == -1 - ? NULL - : fopen (tmp_name, "r" FOPEN_BINARY)); - free (cmd); - } - - if (!inf) - { - perror (real_name); - goto cleanup; + inf = (!decompress_file (compr->command, real_name, tmp_name) + ? NULL + : fopen (tmp_name, "r" FOPEN_BINARY)); + if (!inf) + { + perror (real_name); + goto cleanup; + } } } process_file (inf, uncompressed_name, lang); - retval = fclose (inf); + if (fclose (inf) < 0) + pfatal (file); + if (real_name == compressed_name) { - remove (tmp_name); + if (remove (tmp_name) == -1) + pfatal (tmp_name); + free (tmp_name); } - if (retval < 0) - pfatal (file); cleanup: if (compressed_name != file) @@ -1754,6 +1748,99 @@ process_file_name (char *file, language *lang) return; } +/* + * Specify a decompression command and write the decompression content to a new file. + * On success, true is returned. + */ +static bool +decompress_file (const char *command, const char *input_file, const char *output_file) +{ +#ifdef DOS_NT + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof (SECURITY_ATTRIBUTES); + sa.lpSecurityDescriptor = NULL; + sa.bInheritHandle = true; + + HANDLE hFile = CreateFile (output_file, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (hFile == INVALID_HANDLE_VALUE) + { + perror (output_file); + return false; + } + + PROCESS_INFORMATION pi; + STARTUPINFO si; + ZeroMemory (&si, sizeof (si)); + ZeroMemory (&pi, sizeof (pi)); + si.cb = sizeof (STARTUPINFO); + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = NULL; + si.hStdOutput = hFile; + si.wShowWindow = SW_HIDE; + + char *cmd = concat (command, " -d -c ", input_file); + if (!CreateProcess (NULL, cmd, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) + { + perror ("CreateProcess error"); + return false; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + CloseHandle (pi.hProcess); + CloseHandle (pi.hThread); + CloseHandle (hFile); + return true; +#else + int out_f; + if ((out_f = open (output_file, O_CREAT | O_WRONLY)) == -1) + { + perror (output_file); + return false; + } + + pid_t pid = fork (); + if (pid == -1) + { + perror ("fork"); + return false; + } + + if (pid == 0) + { + if (dup2 (out_f, STDOUT_FILENO) == -1) + { + perror ("dup2 stdout error"); + exit (EXIT_FAILURE); + } + + char *command_args[] = { (char *) command, (char *) "-d", (char *) "-c", (char *) input_file, NULL }; + if (execvp (command, command_args) == -1) + { + perror ("cannot execute the decompress command"); + exit (EXIT_FAILURE); + } + + exit (EXIT_SUCCESS); + } + + if (waitpid (pid, NULL, 0) == -1) + { + perror ("waitpid error"); + return false; + } + + if (close (out_f) == -1) + { + perror ("close error"); + return false; + } + + return true; +#endif +} + static void process_file (FILE *fh, char *fn, language *lang) { -- 2.38.1 --MP_/nUhn8RG=Y4UPID575FtLTji--