Skip to content

Commit

Permalink
Fix close_on_exec multithreaded decompression issue.
Browse files Browse the repository at this point in the history
  • Loading branch information
zoulasc committed Dec 8, 2020
1 parent 3dc9066 commit 81f15c2
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 8 deletions.
5 changes: 5 additions & 0 deletions ChangeLog
@@ -1,3 +1,8 @@
2020-12-08 16:24 Christos Zoulas <christos@zoulas.com>

* fix multithreaded decompression file descriptor issue
by using close-on-exec (Denys Vlasenko)

2020-06-27 11:58 Christos Zoulas <christos@zoulas.com>

* Exclude surrogate pairs from utf-8 detection (Michael Liu)
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Expand Up @@ -166,7 +166,7 @@ else
fi])

dnl Checks for functions
AC_CHECK_FUNCS(strndup mkstemp mkostemp utimes utime wcwidth strtof newlocale uselocale freelocale memmem)
AC_CHECK_FUNCS(strndup mkstemp mkostemp utimes utime wcwidth strtof newlocale uselocale freelocale memmem pipe2)

dnl Provide implementation of some required functions if necessary
AC_REPLACE_FUNCS(getopt_long asprintf vasprintf strlcpy strlcat getline ctime_r asctime_r localtime_r gmtime_r pread strcasestr fmtcheck dprintf)
Expand Down
25 changes: 22 additions & 3 deletions src/compress.c
Expand Up @@ -35,7 +35,7 @@
#include "file.h"

#ifndef lint
FILE_RCSID("@(#)$File: compress.c,v 1.128 2020/10/18 22:00:13 christos Exp $")
FILE_RCSID("@(#)$File: compress.c,v 1.129 2020/12/08 21:26:00 christos Exp $")
#endif

#include "magic.h"
Expand Down Expand Up @@ -844,8 +844,23 @@ uncompressbuf(int fd, size_t bytes_max, size_t method, const unsigned char *old,
for (i = 0; i < __arraycount(fdp); i++)
fdp[i][0] = fdp[i][1] = -1;

if ((fd == -1 && pipe(fdp[STDIN_FILENO]) == -1) ||
pipe(fdp[STDOUT_FILENO]) == -1 || pipe(fdp[STDERR_FILENO]) == -1) {
/*
* There are multithreaded users who run magic_file()
* from dozens of threads. If two parallel magic_file() calls
* analyze two large compressed files, both will spawn
* an uncompressing child here, which writes out uncompressed data.
* We read some portion, then close the pipe, then waitpid() the child.
* If uncompressed data is larger, child shound get EPIPE and exit.
* However, with *parallel* calls OTHER child may unintentionally
* inherit pipe fds, thus keeping pipe open and making writes in
* our child block instead of failing with EPIPE!
* (For the bug to occur, two threads must mutually inherit their pipes,
* and both must have large outputs. Thus it happens not that often).
* To avoid this, be sure to create pipes with O_CLOEXEC.
*/
if ((fd == -1 && file_pipe_closexec(fdp[STDIN_FILENO]) == -1) ||
file_pipe_closexec(fdp[STDOUT_FILENO]) == -1 ||
file_pipe_closexec(fdp[STDERR_FILENO]) == -1) {
closep(fdp[STDIN_FILENO]);
closep(fdp[STDOUT_FILENO]);
return makeerror(newch, n, "Cannot create pipe, %s",
Expand Down Expand Up @@ -876,16 +891,20 @@ uncompressbuf(int fd, size_t bytes_max, size_t method, const unsigned char *old,
if (fdp[STDIN_FILENO][1] > 2)
(void) close(fdp[STDIN_FILENO][1]);
}
file_clear_closexec(STDIN_FILENO);

///FIXME: if one of the fdp[i][j] is 0 or 1, this can bomb spectacularly
if (copydesc(STDOUT_FILENO, fdp[STDOUT_FILENO][1]))
(void) close(fdp[STDOUT_FILENO][1]);
if (fdp[STDOUT_FILENO][0] > 2)
(void) close(fdp[STDOUT_FILENO][0]);
file_clear_closexec(STDOUT_FILENO);

if (copydesc(STDERR_FILENO, fdp[STDERR_FILENO][1]))
(void) close(fdp[STDERR_FILENO][1]);
if (fdp[STDERR_FILENO][0] > 2)
(void) close(fdp[STDERR_FILENO][0]);
file_clear_closexec(STDERR_FILENO);

(void)execvp(compr[method].argv[0],
RCAST(char *const *, RCAST(intptr_t, compr[method].argv)));
Expand Down
12 changes: 11 additions & 1 deletion src/file.h
Expand Up @@ -27,7 +27,7 @@
*/
/*
* file.h - definitions for file(1) program
* @(#)$File: file.h,v 1.222 2020/09/05 14:15:42 christos Exp $
* @(#)$File: file.h,v 1.223 2020/12/08 21:26:00 christos Exp $
*/

#ifndef __file_h__
Expand Down Expand Up @@ -143,6 +143,14 @@
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#endif

#ifndef O_CLOEXEC
# define O_CLOEXEC 0
#endif

#ifndef FD_CLOEXEC
# define FD_CLOEXEC 1
#endif

#define FILE_BADSIZE CAST(size_t, ~0ul)
#define MAXDESC 64 /* max len of text description/MIME type */
#define MAXMIME 80 /* max len of text MIME type */
Expand Down Expand Up @@ -540,6 +548,8 @@ protected char * file_printable(char *, size_t, const char *, size_t);
protected int file_os2_apptype(struct magic_set *, const char *, const void *,
size_t);
#endif /* __EMX__ */
protected int file_pipe_closexec(int *);
protected int file_clear_closexec(int);

protected void buffer_init(struct buffer *, int, const struct stat *,
const void *, size_t);
Expand Down
24 changes: 23 additions & 1 deletion src/funcs.c
Expand Up @@ -27,7 +27,7 @@
#include "file.h"

#ifndef lint
FILE_RCSID("@(#)$File: funcs.c,v 1.117 2020/06/25 16:52:48 christos Exp $")
FILE_RCSID("@(#)$File: funcs.c,v 1.118 2020/12/08 21:26:00 christos Exp $")
#endif /* lint */

#include "magic.h"
Expand All @@ -36,6 +36,9 @@ FILE_RCSID("@(#)$File: funcs.c,v 1.117 2020/06/25 16:52:48 christos Exp $")
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for pipe2() */
#endif
#if defined(HAVE_WCHAR_H)
#include <wchar.h>
#endif
Expand Down Expand Up @@ -784,3 +787,22 @@ file_print_guid(char *str, size_t len, const uint64_t *guid)
g->data4[2], g->data4[3], g->data4[4], g->data4[5],
g->data4[6], g->data4[7]);
}

protected int
file_pipe_closexec(int *fds)
{
#ifdef HAVE_PIPE2
return pipe2(fds, O_CLOEXEC);
#else
if (pipe(fds) == -1)
return -1;
(void)fcntl(fds[0], F_SETFD, FD_CLOEXEC);
(void)fcntl(fds[1], F_SETFD, FD_CLOEXEC);
return 0;
#endif
}

protected int
file_clear_closexec(int fd) {
return fcntl(fd, F_SETFD, 0);
}
7 changes: 5 additions & 2 deletions src/magic.c
Expand Up @@ -33,7 +33,7 @@
#include "file.h"

#ifndef lint
FILE_RCSID("@(#)$File: magic.c,v 1.112 2020/06/08 19:44:10 christos Exp $")
FILE_RCSID("@(#)$File: magic.c,v 1.113 2020/12/08 21:26:00 christos Exp $")
#endif /* lint */

#include "magic.h"
Expand Down Expand Up @@ -436,7 +436,7 @@ file_or_fd(struct magic_set *ms, const char *inname, int fd)
_setmode(STDIN_FILENO, O_BINARY);
#endif
if (inname != NULL) {
int flags = O_RDONLY|O_BINARY|O_NONBLOCK;
int flags = O_RDONLY|O_BINARY|O_NONBLOCK|O_CLOEXEC;
errno = 0;
if ((fd = open(inname, flags)) < 0) {
okstat = stat(inname, &sb) == 0;
Expand All @@ -460,6 +460,9 @@ file_or_fd(struct magic_set *ms, const char *inname, int fd)
rv = 0;
goto done;
}
#if O_CLOEXEC == 0
(void)fcntl(fd, F_SETFD, FD_CLOEXEC);
#endif
}

if (fd != -1) {
Expand Down

0 comments on commit 81f15c2

Please sign in to comment.