| Newsgroups |
gmane.os.netbsd.devel.pkgsrc.bugs |
| Message-ID |
<[email protected]> |
>Number: 60649
>Category: pkg
>Synopsis: editors/emacs21: two unbackported CVEs in etags and copy-file
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: pkg-manager
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Wed Aug 26 17:05:00 +0000 2026
>Originator: Showta Ishizaki
>Release: NetBSD 11.0, pkgsrc CVS as of 2026-08-27
>Organization:
>Environment:
System: NetBSD 11.0 amd64
>Description:
editors/emacs21 (21.4a) and editors/emacs21-nox11 carry two CVEs that were
fixed upstream but never reached the emacs-21 branch (the oldest branch on
the mirror is emacs-23). patch-CVE-2017-14482 is already in the tree; these
add the other two, plus a gets() cleanup.
CVE-2022-45939 / CVE-2022-48337 -- etags/ctags paste file names into shell
commands run through system(3)/popen(3) without quoting. All three call
sites are live in 21.4: the -u update loop walks argbuffer[] by current_arg,
"sort -o %s %s" over the -o output file is reached directly, and the
decompressor for a compressed input file pastes the file's own name into
"gzip -d -c NAME" for popen.
ctags -u -o 'tags;touch /tmp/pwned' file.c # -o path
etags 'a;touch GOTCHA;.gz' # decompressor path
ran the injected command before the fix and do nothing after it; a normal
"-o out.tags" and a normal "foo.c.gz" still work. Backport of upstream
01a4035c8 "Fix etags local command injection vulnerability" (Bug#59817) and
8a098f651 "Fix quoting of file names in 'ctags'"; the upstream
escape_shell_arg_string() is brought to the old source as shell_quote().
CVE-2017-1000383 -- copy-file creates the destination with creat(...,0666)
and only chmods it afterwards, so a copy of a private file is briefly
world-readable and a backup inherits the source's setuid/setgid bits. A
04755 source produced a 04755 backup before the fix and a 0755 one after.
Modern Emacs closes this with with-file-modes ?\700 in backup-buffer-copy
(files.el) and O_CREAT|O_EXCL plus ~06000 in Fcopy_file; this does the same
at the C level: create 0600 and mask the chmod with 01777.
patch-src_xrdb.c replaces gets() with fgets() in the [TESTRM] test driver,
backporting upstream e90a457c46ca. gets() is not compiled into the shipped
binary, so it changes nothing that runs; it is here to match upstream and to
keep the source compiling with newer toolchains.
pkg_admin audit already flags emacs21 for CVE-2017-1000383 and
CVE-2022-45939 (by version).
>How-To-Repeat:
Build editors/emacs21 (or -nox11). With the built ctags/etags:
printf 'int main(void){return 0;}\n' > file.c
ctags -u -o 'tags;touch /tmp/pwned' file.c
test -f /tmp/pwned && echo vulnerable
gzip -c file.c > 'a;touch GOTCHA;.gz'; etags 'a;touch GOTCHA;.gz'
test -f GOTCHA && echo vulnerable
With the built emacs and backup-by-copying:
printf secret > src; chmod 4755 src; umask 077
emacs -batch -q --eval '(copy-file "src" "dst" t)'
ls -l dst # setuid, world-readable backup before the fix
>Fix:
Three new patches -- patch-CVE-2022-45939, patch-CVE-2017-1000383 and
patch-src_xrdb.c -- against the shared editors/emacs21/patches, so both
emacs21 and emacs21-nox11 get them. PKGREVISION goes to 50 and 15.
Built from source on NetBSD 11.0/amd64; the two exploits above are closed and
ordinary etags and copy-file use is unchanged.
Index: editors/emacs21/Makefile
===================================================================
RCS file: /cvsroot/pkgsrc/editors/emacs21/Makefile,v
diff -u Makefile
--- editors/emacs21/Makefile
+++ editors/emacs21/Makefile
@@ -1,6 +1,6 @@
# $NetBSD: Makefile,v 1.51 2025/08/30 22:44:55 wiz Exp $
-PKGREVISION= 49
+PKGREVISION= 50
CATEGORIES= editors
CONFLICTS+= emacs21-nox11-[0-9]*
Index: editors/emacs21-nox11/Makefile
===================================================================
RCS file: /cvsroot/pkgsrc/editors/emacs21-nox11/Makefile,v
diff -u Makefile
--- editors/emacs21-nox11/Makefile
+++ editors/emacs21-nox11/Makefile
@@ -1,7 +1,7 @@
# $NetBSD: Makefile,v 1.15 2021/10/06 15:40:15 nia Exp $
PKGNAME= emacs21-nox11-${EMACSVERSION}a
-PKGREVISION= 14
+PKGREVISION= 15
CATEGORIES= editors
COMMENT_EXTRA= : non-X11 version
Index: editors/emacs21/distinfo
===================================================================
RCS file: /cvsroot/pkgsrc/editors/emacs21/distinfo,v
diff -u distinfo
--- editors/emacs21/distinfo
+++ editors/emacs21/distinfo
@@ -3,7 +3,9 @@
BLAKE2s (emacs-21.4a.tar.gz) = 8f66d92cce6f5cc673d615e034803f08ccc900bb041c6dbb98b5050d9250dbc1
SHA512 (emacs-21.4a.tar.gz) = 6932db498e7b6b904d90f817e335690c5c681510812332c514af1d84d91b9841ea3eff0357429c7bf866a55ab94d03193fc9cdbf403a9fe4b71d2673c23b863e
Size (emacs-21.4a.tar.gz) = 20403483 bytes
+SHA1 (patch-CVE-2017-1000383) = 44089b5ba550b56421d85edae1ff0b9a9231b483
SHA1 (patch-CVE-2017-14482) = b0e11974076dcb1cad83841c949d0f35fdaa2ca2
+SHA1 (patch-CVE-2022-45939) = 95bcdeafa760e68b7c6013c91d24cbc785fe3dea
SHA1 (patch-aa) = a7146ff6cc24de8d0caf56a0bc0ce057f659f536
SHA1 (patch-ab) = 884b8fe5643b06340948c1f10b7c5d643fad2bf6
SHA1 (patch-ac) = a2de7619fece50cb42b0e23b1651b5bf742ff69a
@@ -37,4 +39,5 @@
SHA1 (patch-src_alloc_c) = c9d2c7832b8b48835b81789f938540e3827f7cd8
SHA1 (patch-src_gmalloc.c) = 3d10e1ae9f9d98495a8da47e6051a51ac18aed10
SHA1 (patch-src_syntax.c) = c56c3b0d1e215cf56f29349bf7e9bf9cce8d0f02
+SHA1 (patch-src_xrdb.c) = bcfb7b85a0f6488afe55a5abf9a2f0ec57f4c3f3
SHA1 (patch-xx) = 6e5b55c1738c4e8ef58e45065b55629ec26c9b91
Index: editors/emacs21/patches/patch-CVE-2022-45939
===================================================================
RCS file: editors/emacs21/patches/patch-CVE-2022-45939
diff -N editors/emacs21/patches/patch-CVE-2022-45939
--- /dev/null
+++ editors/emacs21/patches/patch-CVE-2022-45939
+$NetBSD$
+
+CVE-2022-45939 and CVE-2022-48337: etags/ctags paste strings into shell
+commands and hand them to system(3)/popen(3) without quoting. Backport of
+
+ From 01a4035c869b91c153af9a9132c87adb7669ea1c Mon Sep 17 00:00:00 2001
+ From: lu4nx <[email protected]>
+ Date: Tue, 6 Dec 2022
+ Subject: Fix etags local command injection vulnerability (Bug#59817)
+
+ From 8a098f6517157ebe2364f08008b44ab49c2d1115 Mon Sep 17 00:00:00 2001
+ From: Eli Zaretskii <[email protected]>
+ Date: Fri, 1 Jul 2022
+ Subject: Fix quoting of file names in 'ctags'
+
+Upstream added escape_shell_arg_string() and quotes every file name it
+passes to the shell. 21.4 predates that by twenty years; this brings the
+same fix to the old K&R source, as shell_quote().
+
+All three call sites are live here. The per-file update loop walks
+argbuffer[] by current_arg (not optind..argc, which is what makes the same
+loop dead in mule), the "sort ... -o ..." over the -o output file is reached
+directly, and the decompressor for a compressed input file pastes the file's
+own name into "gzip -d -c NAME" for popen -- so a file named "a;cmd.gz"
+reached by "etags *" runs cmd. On NetBSD 11.0/amd64:
+
+ ctags -u -o 'tags;touch /tmp/pwned' file.c # -o path
+ etags 'a;touch GOTCHA;.gz' # decompressor path
+
+ran the injected command before the fix and do nothing after it. A normal
+"-o out.tags" and a normal "foo.c.gz" still work.
+
+--- lib-src/etags.c.orig
++++ lib-src/etags.c
+@@ -303,6 +303,7 @@
+ static void error P_((const char *, const char *));
+ static void suggest_asking_for_help P_((void));
+ void fatal P_((char *, char *));
++static char *shell_quote P_((char *));
+ static void pfatal P_((char *));
+ static void add_node P_((node *, node **));
+
+@@ -909,6 +910,41 @@
+ #endif /* VMS */
+
+
++/* Return a copy of STR wrapped in single quotes so that a shell passes it
++ through unchanged. An embedded single quote is closed, escaped and
++ reopened.
++
++ Without this a string reaches the shell as written, and one that contains
++ shell metacharacters runs whatever it likes. CVE-2022-45939 and
++ CVE-2022-48337. */
++
++static char *
++shell_quote (str)
++ char *str;
++{
++ char *p, *q, *out;
++ int n = 0;
++
++ for (p = str; *p != '\0'; p++)
++ n += (*p == '\'') ? 4 : 1;
++ out = xnew (n + 3, char);
++ q = out;
++ *q++ = '\'';
++ for (p = str; *p != '\0'; p++)
++ if (*p == '\'')
++ {
++ *q++ = '\'';
++ *q++ = '\\';
++ *q++ = '\'';
++ *q++ = '\'';
++ }
++ else
++ *q++ = *p;
++ *q++ = '\'';
++ *q = '\0';
++ return out;
++}
++
+ int
+ main (argc, argv)
+ int argc;
+@@ -1195,16 +1231,25 @@
+
+ if (update)
+ {
+- char cmd[BUFSIZ];
+ for (i = 0; i < current_arg; ++i)
+ {
++ char *pattern, *qpattern, *qtagfile, *cmd;
++
+ if (argbuffer[i].arg_type != at_filename)
+ continue;
+- sprintf (cmd,
+- "mv %s OTAGS;fgrep -v '\t%s\t' OTAGS >%s;rm OTAGS",
+- tagfile, argbuffer[i].what, tagfile);
++ pattern = xnew (strlen (argbuffer[i].what) + 3, char);
++ sprintf (pattern, "\t%s\t", argbuffer[i].what);
++ qpattern = shell_quote (pattern);
++ qtagfile = shell_quote (tagfile);
++ cmd = xnew (2 * strlen (qtagfile) + strlen (qpattern) + 32, char);
++ sprintf (cmd, "mv %s OTAGS;fgrep -v %s OTAGS >%s;rm OTAGS",
++ qtagfile, qpattern, qtagfile);
+ if (system (cmd) != GOOD)
+ fatal ("failed to execute shell command", (char *)NULL);
++ free (pattern);
++ free (qpattern);
++ free (qtagfile);
++ free (cmd);
+ }
+ append_to_tagfile = TRUE;
+ }
+@@ -1219,9 +1264,15 @@
+
+ if (update)
+ {
+- char cmd[2*BUFSIZ+10];
+- sprintf (cmd, "sort -o %.*s %.*s", BUFSIZ, tagfile, BUFSIZ, tagfile);
+- exit (system (cmd));
++ char *qtagfile = shell_quote (tagfile);
++ char *cmd = xnew (2 * strlen (qtagfile) + 16, char);
++ int rc;
++
++ sprintf (cmd, "sort %s -o %s", qtagfile, qtagfile);
++ rc = system (cmd);
++ free (qtagfile);
++ free (cmd);
++ exit (rc);
+ }
+ return GOOD;
+ }
+@@ -1455,8 +1506,13 @@
+ }
+ if (real_name == compressed_name)
+ {
+- char *cmd = concat (compr->command, " ", real_name);
++ /* Quote the file name: it is pasted into a shell command run by
++ popen, so a name like "x;cmd.gz" would otherwise run cmd.
++ CVE-2022-45939 / CVE-2022-48337. */
++ char *qname = shell_quote (real_name);
++ char *cmd = concat (compr->command, " ", qname);
+ inf = (FILE *) popen (cmd, "r");
++ free (qname);
+ free (cmd);
+ }
+ else
Index: editors/emacs21/patches/patch-CVE-2017-1000383
===================================================================
RCS file: editors/emacs21/patches/patch-CVE-2017-1000383
diff -N editors/emacs21/patches/patch-CVE-2017-1000383
--- /dev/null
+++ editors/emacs21/patches/patch-CVE-2017-1000383
+$NetBSD$
+
+CVE-2017-1000383: copy-file, which backup-buffer uses to make backups when
+backup-by-copying is in effect, creates the destination with creat(...,0666)
+and only chmods it to the wanted mode afterwards. A copy of a private file
+is briefly world-readable, and a backup inherits the source's setuid/setgid
+bits.
+
+Upstream closes this in two places that 20.7/21.4 predate: lisp/files.el's
+backup-buffer-copy wraps the copy in (with-file-modes ?\700 ...) -- the
+with-file-modes macro is
+
+ From d63d883a97e32aa5f0983b6577f7cbef8b6c3a3d Mon Sep 17 00:00:00 2001
+ Date: Wed, 14 May 2014
+ Subject: Add with-file-modes macro, and use it
+
+-- and modern src/fileio.c creates the copy with O_CREAT|O_EXCL and strips
+the setuid/setgid bits (preserved_permissions &= ~06000). This backport
+does the same at the C level for the old source: create 0600 so the file is
+never group/other-readable while it is being written, and mask the trailing
+chmod with 01777 (keeping the sticky and rwx bits, dropping setuid/setgid --
+the same effect as #o1777 in backup-buffer-copy).
+
+Reproduced on NetBSD 11.0/amd64 with emacs -batch and backup-by-copying: a
+04755 source produced a 04755 (setuid) backup before the fix and a 0755 one
+after. This is the same change already carried in zakinko/mule.
+
+--- src/fileio.c.orig
++++ src/fileio.c
+@@ -2417,7 +2417,11 @@
+ /* System's default file type was set to binary by _fmode in emacs.c. */
+ ofd = creat (XSTRING (encoded_newname)->data, S_IREAD | S_IWRITE);
+ #else /* not MSDOS */
+- ofd = creat (XSTRING (encoded_newname)->data, 0666);
++ /* Create the copy owner-readable/writable only. The final chmod below
++ widens it to the intended mode once the data is in place; creating it
++ 0666 (masked only by umask) meant a copy of a private file was briefly
++ world-readable while it was being written. CVE-2017-1000383. */
++ ofd = creat (XSTRING (encoded_newname)->data, 0600);
+ #endif /* not MSDOS */
+ #endif /* VMS */
+ if (ofd < 0)
+@@ -2450,7 +2454,11 @@
+ Fcons (newname, Qnil)));
+ }
+ #ifndef MSDOS
+- chmod (XSTRING (encoded_newname)->data, st.st_mode & 07777);
++ /* Mask off the setuid and setgid bits: a copy -- above all a backup
++ file -- must never inherit them from the original. 01777 keeps the
++ sticky bit and the rwx bits, matching what a modern Emacs does with
++ #o1777. CVE-2017-1000383. */
++ chmod (XSTRING (encoded_newname)->data, st.st_mode & 01777);
+ #else /* MSDOS */
+ #if defined (__DJGPP__) && __DJGPP__ > 1
+ /* In DJGPP v2.0 and later, fstat usually returns true file mode bits,
Index: editors/emacs21/patches/patch-src_xrdb.c
===================================================================
RCS file: editors/emacs21/patches/patch-src_xrdb.c
diff -N editors/emacs21/patches/patch-src_xrdb.c
--- /dev/null
+++ editors/emacs21/patches/patch-src_xrdb.c
+$NetBSD$
+
+Replace gets() in the [TESTRM] test driver with fgets(), backporting
+
+ From e90a457c46ca4c6a231a422b9b30f5e0d0f9d1c1 Mon Sep 17 00:00:00 2001
+ From: Eli Zaretskii <[email protected]>
+ Date: Thu, 8 Sep 2022
+ Subject: * src/xrdb.c (main) [TESTRM]: Replace gets with fgets.
+
+gets() has no bound and cannot be used safely; it was removed from C11.
+The call is inside the TESTRM stand-alone driver, which is not compiled
+into Emacs (the built binary has no gets symbol), so this changes no
+shipped code -- it is here to match upstream and to keep the source
+clean, since gets() is a hard error with newer toolchains.
+
+--- src/xrdb.c.orig
++++ src/xrdb.c
+@@ -788,14 +788,14 @@
+ char query_class[90];
+
+ printf ("Name: ");
+- gets (query_name);
++ fgets (query_name, 90, stdin);
+
+ if (strlen (query_name))
+ {
+ char *value;
+
+ printf ("Class: ");
+- gets (query_class);
++ fgets (query_class, 90, stdin);
+
+ value = x_get_string_resource (xdb, query_name, query_class);
+