Skip to content

Commit d48bb48

Browse files
lu4nxEli-Zaretskii
authored andcommitted
Fixed ctags local command execute vulnerability
* lib-src/etags.c: (clean_matched_file_tag): New function (do_move_file): New function (readline_internal): Add `leave_cr` parameter, if true, include the \r character * test/manual/etags/CTAGS.good_crlf: New file * test/manual/etags/CTAGS.good_update: New file * test/manual/etags/crlf: New file * test/manual/etags/Makefile: Add `ctags -u` test cases
1 parent bacba02 commit d48bb48

File tree

5 files changed

+9093
-36
lines changed

5 files changed

+9093
-36
lines changed

lib-src/etags.c

Lines changed: 113 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ static void just_read_file (FILE *);
375375

376376
static language *get_language_from_langname (const char *);
377377
static void readline (linebuffer *, FILE *);
378-
static ptrdiff_t readline_internal (linebuffer *, FILE *, char const *);
378+
static ptrdiff_t readline_internal (linebuffer *, FILE *, char const *, const bool);
379379
static bool nocase_tail (const char *);
380380
static void get_tag (char *, char **);
381381
static void get_lispy_tag (char *);
@@ -399,7 +399,9 @@ static void free_fdesc (fdesc *);
399399
static void pfnote (char *, bool, char *, ptrdiff_t, intmax_t, intmax_t);
400400
static void invalidate_nodes (fdesc *, node **);
401401
static void put_entries (node *);
402+
static void clean_matched_file_tag (char const * const, char const * const);
402403

404+
static void do_move_file (const char *, const char *);
403405
static char *concat (const char *, const char *, const char *);
404406
static char *skip_spaces (char *);
405407
static char *skip_non_spaces (char *);
@@ -1332,7 +1334,7 @@ main (int argc, char **argv)
13321334
if (parsing_stdin)
13331335
fatal ("cannot parse standard input "
13341336
"AND read file names from it");
1335-
while (readline_internal (&filename_lb, stdin, "-") > 0)
1337+
while (readline_internal (&filename_lb, stdin, "-", false) > 0)
13361338
process_file_name (filename_lb.buffer, lang);
13371339
}
13381340
else
@@ -1380,9 +1382,6 @@ main (int argc, char **argv)
13801382
/* From here on, we are in (CTAGS && !cxref_style) */
13811383
if (update)
13821384
{
1383-
char *cmd =
1384-
xmalloc (strlen (tagfile) + whatlen_max +
1385-
sizeof "mv..OTAGS;grep -Fv '\t\t' OTAGS >;rm OTAGS");
13861385
for (i = 0; i < current_arg; ++i)
13871386
{
13881387
switch (argbuffer[i].arg_type)
@@ -1393,17 +1392,8 @@ main (int argc, char **argv)
13931392
default:
13941393
continue; /* the for loop */
13951394
}
1396-
char *z = stpcpy (cmd, "mv ");
1397-
z = stpcpy (z, tagfile);
1398-
z = stpcpy (z, " OTAGS;grep -Fv '\t");
1399-
z = stpcpy (z, argbuffer[i].what);
1400-
z = stpcpy (z, "\t' OTAGS >");
1401-
z = stpcpy (z, tagfile);
1402-
strcpy (z, ";rm OTAGS");
1403-
if (system (cmd) != EXIT_SUCCESS)
1404-
fatal ("failed to execute shell command");
1395+
clean_matched_file_tag (tagfile, argbuffer[i].what);
14051396
}
1406-
free (cmd);
14071397
append_to_tagfile = true;
14081398
}
14091399

@@ -1448,6 +1438,51 @@ main (int argc, char **argv)
14481438
return EXIT_SUCCESS;
14491439
}
14501440

1441+
/*
1442+
* Equivalent to: mv tags OTAGS;grep -Fv ' filename ' OTAGS >tags;rm OTAGS
1443+
*/
1444+
static void
1445+
clean_matched_file_tag (const char* tagfile, const char* match_file_name)
1446+
{
1447+
FILE *otags_f = fopen ("OTAGS", "wb");
1448+
FILE *tag_f = fopen (tagfile, "rb");
1449+
1450+
if (otags_f == NULL)
1451+
pfatal ("OTAGS");
1452+
1453+
if (tag_f == NULL)
1454+
pfatal (tagfile);
1455+
1456+
int buf_len = strlen (match_file_name) + sizeof ("\t\t ") + 1;
1457+
char *buf = xmalloc (buf_len);
1458+
snprintf (buf, buf_len, "\t%s\t", match_file_name);
1459+
1460+
linebuffer line;
1461+
linebuffer_init (&line);
1462+
while (readline_internal (&line, tag_f, tagfile, true) > 0)
1463+
{
1464+
if (ferror (tag_f))
1465+
pfatal (tagfile);
1466+
1467+
if (strstr (line.buffer, buf) == NULL)
1468+
{
1469+
fprintf (otags_f, "%s\n", line.buffer);
1470+
if (ferror (tag_f))
1471+
pfatal (tagfile);
1472+
}
1473+
}
1474+
free (buf);
1475+
free (line.buffer);
1476+
1477+
if (fclose (otags_f) == EOF)
1478+
pfatal ("OTAGS");
1479+
1480+
if (fclose (tag_f) == EOF)
1481+
pfatal (tagfile);
1482+
1483+
do_move_file ("OTAGS", tagfile);
1484+
return;
1485+
}
14511486

14521487
/*
14531488
* Return a compressor given the file name. If EXTPTR is non-zero,
@@ -1831,7 +1866,7 @@ find_entries (FILE *inf)
18311866

18321867
/* Else look for sharp-bang as the first two characters. */
18331868
if (parser == NULL
1834-
&& readline_internal (&lb, inf, infilename) > 0
1869+
&& readline_internal (&lb, inf, infilename, false) > 0
18351870
&& lb.len >= 2
18361871
&& lb.buffer[0] == '#'
18371872
&& lb.buffer[1] == '!')
@@ -6878,7 +6913,7 @@ analyze_regex (char *regex_arg)
68786913
if (regexfp == NULL)
68796914
pfatal (regexfile);
68806915
linebuffer_init (&regexbuf);
6881-
while (readline_internal (&regexbuf, regexfp, regexfile) > 0)
6916+
while (readline_internal (&regexbuf, regexfp, regexfile, false) > 0)
68826917
analyze_regex (regexbuf.buffer);
68836918
free (regexbuf.buffer);
68846919
if (fclose (regexfp) != 0)
@@ -7226,19 +7261,21 @@ get_lispy_tag (register char *bp)
72267261

72277262
/*
72287263
* Read a line of text from `stream' into `lbp', excluding the
7229-
* newline or CR-NL, if any. Return the number of characters read from
7230-
* `stream', which is the length of the line including the newline.
7264+
* newline or CR-NL (if `leave_cr` is false), if any. Return the
7265+
* number of characters read from `stream', which is the length
7266+
* of the line including the newline.
72317267
*
7232-
* On DOS or Windows we do not count the CR character, if any before the
7233-
* NL, in the returned length; this mirrors the behavior of Emacs on those
7268+
* On DOS or Windows, if `leave_cr` is false, we do not count the
7269+
* CR character, if any before the NL, in the returned length;
7270+
* this mirrors the behavior of Emacs on those
72347271
* platforms (for text files, it translates CR-NL to NL as it reads in the
72357272
* file).
72367273
*
72377274
* If multi-line regular expressions are requested, each line read is
72387275
* appended to `filebuf'.
72397276
*/
72407277
static ptrdiff_t
7241-
readline_internal (linebuffer *lbp, FILE *stream, char const *filename)
7278+
readline_internal (linebuffer *lbp, FILE *stream, char const *filename, const bool leave_cr)
72427279
{
72437280
char *buffer = lbp->buffer;
72447281
char *p = lbp->buffer;
@@ -7268,19 +7305,19 @@ readline_internal (linebuffer *lbp, FILE *stream, char const *filename)
72687305
break;
72697306
}
72707307
if (c == '\n')
7271-
{
7272-
if (p > buffer && p[-1] == '\r')
7273-
{
7274-
p -= 1;
7275-
chars_deleted = 2;
7276-
}
7277-
else
7278-
{
7279-
chars_deleted = 1;
7280-
}
7281-
*p = '\0';
7282-
break;
7283-
}
7308+
{
7309+
if (!leave_cr && p > buffer && p[-1] == '\r')
7310+
{
7311+
p -= 1;
7312+
chars_deleted = 2;
7313+
}
7314+
else
7315+
{
7316+
chars_deleted = 1;
7317+
}
7318+
*p = '\0';
7319+
break;
7320+
}
72847321
*p++ = c;
72857322
}
72867323
lbp->len = p - buffer;
@@ -7311,7 +7348,7 @@ static void
73117348
readline (linebuffer *lbp, FILE *stream)
73127349
{
73137350
linecharno = charno; /* update global char number of line start */
7314-
ptrdiff_t result = readline_internal (lbp, stream, infilename);
7351+
ptrdiff_t result = readline_internal (lbp, stream, infilename, false);
73157352
lineno += 1; /* increment global line number */
73167353
charno += result; /* increment global char number */
73177354

@@ -7669,6 +7706,46 @@ etags_mktmp (void)
76697706
return templt;
76707707
}
76717708

7709+
static void
7710+
do_move_file(const char *src_file, const char *dst_file)
7711+
{
7712+
if (rename (src_file, dst_file) == 0)
7713+
return;
7714+
7715+
FILE *src_f = fopen (src_file, "rb");
7716+
FILE *dst_f = fopen (dst_file, "wb");
7717+
7718+
if (src_f == NULL)
7719+
pfatal (src_file);
7720+
7721+
if (dst_f == NULL)
7722+
pfatal (dst_file);
7723+
7724+
int c;
7725+
while ((c = fgetc (src_f)) != EOF)
7726+
{
7727+
if (ferror (src_f))
7728+
pfatal (src_file);
7729+
7730+
if (ferror (dst_f))
7731+
pfatal (dst_file);
7732+
7733+
if (fputc (c, dst_f) == EOF)
7734+
pfatal ("cannot write");
7735+
}
7736+
7737+
if (fclose (src_f) == EOF)
7738+
pfatal (src_file);
7739+
7740+
if (fclose (dst_f) == EOF)
7741+
pfatal (dst_file);
7742+
7743+
if (unlink (src_file) == -1)
7744+
pfatal ("unlink error");
7745+
7746+
return;
7747+
}
7748+
76727749
/* Return a newly allocated string containing the file name of FILE
76737750
relative to the absolute directory DIR (which should end with a slash). */
76747751
static char *

0 commit comments

Comments
 (0)