ecore - don't do anything with heap between fork and exec

this avoids a possibgle deadlock if a malloc impl is holding a lock
and has not released it at the time we fork.

@fix
This commit is contained in:
Carsten Haitzler 2020-08-14 15:22:17 +01:00
parent 29554c2f44
commit 41296effc6
3 changed files with 271 additions and 59 deletions

View File

@ -218,6 +218,13 @@ _impl_ecore_exe_run_priority_get(void)
return run_pri;
}
#if defined (__FreeBSD__) || defined (__OpenBSD__)
# include <dlfcn.h>
static char ***_dl_environ;
#else
extern char **environ;
#endif
Eo *
_impl_ecore_exe_efl_object_finalize(Eo *obj, Ecore_Exe_Data *exe)
{
@ -294,7 +301,29 @@ _impl_ecore_exe_efl_object_finalize(Eo *obj, Ecore_Exe_Data *exe)
else if (pid == 0) /* child */
{
#ifdef HAVE_SYSTEMD
unsetenv("NOTIFY_SOCKET");
char **env = NULL, **e;
# if defined (__FreeBSD__) || defined (__OpenBSD__)
_dl_environ = dlsym(NULL, "environ");
env = *_dl_environ;
# else
env = environ;
# endif
// find NOTIFY_SOCKET env var and remove it without any heap work
if (env)
{
Eina_Bool shuffle = EINA_FALSE;
for (e = env; *e; e++)
{
if (!shuffle)
{
if (!strncmp(e[0], "NOTIFY_SOCKET=", 14))
shuffle = EINA_TRUE;
}
if (shuffle) e[0] = e[1];
}
}
#endif
if (run_pri != ECORE_EXE_PRIORITY_INHERIT)
{

View File

@ -357,8 +357,10 @@ _efl_exe_efl_task_run(Eo *obj, Efl_Exe_Data *pd)
return EINA_FALSE;
#else
Eo *loop;
Efl_Task_Data *tdl, *td = efl_data_scope_get(obj, EFL_TASK_CLASS);
Efl_Task_Data *tdl = NULL, *td = efl_data_scope_get(obj, EFL_TASK_CLASS);
Eina_Iterator *itr = NULL, *itr2 = NULL;
const char *cmd;
char **newenv, **env = NULL, **e;
int devnull;
int pipe_stdin[2];
int pipe_stdout[2];
@ -428,9 +430,17 @@ _efl_exe_efl_task_run(Eo *obj, Efl_Exe_Data *pd)
}
_ecore_signal_pid_lock();
// get these before the fork to avoid heap malloc deadlocks
loop = efl_provider_find(obj, EFL_LOOP_CLASS);
if (loop) tdl = efl_data_scope_get(loop, EFL_TASK_CLASS);
if (pd->env) itr = efl_core_env_content_get(pd->env);
if (pd->env) itr2 = efl_core_env_content_get(pd->env);
pd->pid = fork();
if (pd->pid != 0)
{
if (itr) eina_iterator_free(itr);
if (itr2) eina_iterator_free(itr2);
// parent process is here inside this if block
if (td->flags & EFL_TASK_FLAGS_USE_STDIN) close(pipe_stdin[0]);
if (td->flags & EFL_TASK_FLAGS_USE_STDOUT) close(pipe_stdout[1]);
@ -513,58 +523,77 @@ _efl_exe_efl_task_run(Eo *obj, Efl_Exe_Data *pd)
close(devnull);
}
if (!(loop = efl_provider_find(obj, EFL_LOOP_CLASS))) exit(1);
if (!(tdl = efl_data_scope_get(loop, EFL_TASK_CLASS))) exit(1);
if (!tdl) exit(1);
// clear systemd notify socket... only relevant for systemd world,
// otherwise shouldn't be trouble
putenv("NOTIFY_SOCKET=");
# if defined (__FreeBSD__) || defined (__OpenBSD__)
_dl_environ = dlsym(NULL, "environ");
if (_dl_environ) env = *_dl_environ;
# else
env = environ;
# endif
if (env)
{
Eina_Bool shuffle = EINA_FALSE;
for (e = env; *e; e++)
{
if (!shuffle)
{
if (!strncmp(e[0], "NOTIFY_SOCKET=", 14))
shuffle = EINA_TRUE;
}
if (shuffle) e[0] = e[1];
}
}
// actually setenv the env object (clear what was there before so it is
// the only env there)
if (pd->env)
{
Eina_Iterator *itr;
const char *key;
int count = 0, i = 0;
# ifdef HAVE_CLEARENV
clearenv();
# else
# if defined (__FreeBSD__) || defined (__OpenBSD__)
_dl_environ = dlsym(NULL, "environ");
if (_dl_environ) *_dl_environ = NULL;
else ERR("Can't find envrion symbol");
# else
environ = NULL;
# endif
# endif
itr = efl_core_env_content_get(pd->env);
// use 2nd throw-away itr to count
EINA_ITERATOR_FOREACH(itr2, key)
{
count++;
}
// object which we don't free (sitting in hash table in env obj)
newenv = alloca(sizeof(char *) * (count + 1));
// use 2st iter to walk and fill new env
EINA_ITERATOR_FOREACH(itr, key)
{
setenv(key, efl_core_env_get(pd->env, key) , 1);
newenv[i] = (char *)efl_core_env_get(pd->env, key);
i++;
}
efl_unref(pd->env);
pd->env = NULL;
// yes - we dont free itr or itr2 - we're going to exec below or exit
// also put newenv array on stack pointign to the strings in the env
# if defined (__FreeBSD__) || defined (__OpenBSD__)
if (_dl_environ) *_dl_environ = newenv;
else ERR("Can't find envrion symbol");
# else
environ = newenv;
# endif
}
// close all fd's other than the first 3 (0, 1, 2) and exited write fd
int except[2] = { 0, -1 };
except[0] = pd->fd.exited_write;
eina_file_close_from(3, except);
#ifdef HAVE_PRCTL
# ifdef HAVE_PRCTL
if ((pd->flags & EFL_EXE_FLAGS_TERM_WITH_PARENT))
{
prctl(PR_SET_PDEATHSIG, SIGTERM);
}
#elif defined(HAVE_PROCCTL)
# elif defined(HAVE_PROCCTL)
if ((pd->flags & EFL_EXE_FLAGS_TERM_WITH_PARENT))
{
int sig = SIGTERM;
procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &sig);
}
#endif
# endif
// actually execute!
_exec(cmd, pd->flags, td->flags);
// we couldn't exec... uh oh. HAAAAAAAALP!

View File

@ -21,6 +21,10 @@
# include "config.h"
#endif
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
@ -1252,6 +1256,47 @@ eina_file_statat(void *container, Eina_File_Direct_Info *info, Eina_Stat *st)
return 0;
}
///////////////////////////////////////////////////////////////////////////
// this below is funky avoiding opendir to avoid heap allocations thus
// getdents and all the os specific stuff as this is intendedf for use
// between fork and exec normally ... this is important
#if defined(__FreeBSD__)
# define do_getdents(fd, buf, size) getdents(fd, buf, size)
typedef struct
{
ino_t d_ino;
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
unsigned char ____pad0;
unsigned short d_namlen;
unsigned short ____pad1;
char d_name[4096];
} Dirent;
#elif defined(__OpenBSD__)
# define do_getdents(fd, buf, size) getdents(fd, buf, size)
typedef struct
{
__ino_t d_ino;
__off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
unsigned char d_namlen;
unsigned char ____pad[4];
char d_name[4096];
} Dirent;
#elif defined(__linux__)
# define do_getdents(fd, buf, size) getdents64(fd, buf, size)
typedef struct
{
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[4096];
} Dirent;
#endif
EAPI void
eina_file_close_from(int fd, int *except_fd)
{
@ -1259,62 +1304,171 @@ eina_file_close_from(int fd, int *except_fd)
// XXX: what do to here? anything?
#else
#ifdef HAVE_DIRENT_H
//# if 0
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__linux__)
int dirfd;
Dirent *d;
char buf[4096];
int *closes = NULL;
int num_closes = 0, i, j, clo, num;
const char *fname;
ssize_t pos, ret;
Eina_Bool do_read;
dirfd = open("/proc/self/fd", O_RDONLY | O_DIRECTORY);
if (dirfd < 0) dirfd = open("/dev/fd", O_RDONLY | O_DIRECTORY);
if (dirfd >= 0)
{
// count # of closes - the dir list should/will not change as its
// the fd's we have open so we can read it twice with no changes
// to it
do_read = EINA_TRUE;
for (;;)
{
skip:
if (do_read)
{
pos = 0;
ret = do_getdents(dirfd, buf, sizeof(buf));
if (ret <= 0) break;
do_read = EINA_FALSE;
}
d = (Dirent *)(buf + pos);
fname = d->d_name;
pos += d->d_reclen;
if (pos >= ret) do_read = EINA_TRUE;
if (!((fname[0] >= '0') && (fname[0] <= '9'))) continue;
num = atoi(fname);
if (num < fd) continue;
if (except_fd)
{
for (j = 0; except_fd[j] >= 0; j++)
{
if (except_fd[j] == num) goto skip;
}
}
num_closes++;
}
// alloc closes list and walk again to fill it - on stack to avoid
// heap allocs
closes = alloca(num_closes * sizeof(int));
if ((closes) && (num_closes > 0))
{
clo = 0;
lseek(dirfd, 0, SEEK_SET);
do_read = EINA_TRUE;
for (;;)
{
skip2:
if (do_read)
{
pos = 0;
ret = do_getdents(dirfd, buf, sizeof(buf));
if (ret <= 0) break;
do_read = EINA_FALSE;
}
d = (Dirent *)(buf + pos);
fname = d->d_name;
pos += d->d_reclen;
if (pos >= ret) do_read = EINA_TRUE;
if (!((fname[0] >= '0') && (fname[0] <= '9'))) continue;
num = atoi(fname);
if (num < fd) continue;
if (except_fd)
{
for (j = 0; except_fd[j] >= 0; j++)
{
if (except_fd[j] == num) goto skip2;
}
}
if (clo < num_closes) closes[clo] = num;
clo++;
}
}
close(dirfd);
// now go close all those fd's - some may be invalide like the dir
// reading fd above... that's ok.
for (i = 0; i < num_closes; i++)
{
close(closes[i]);
}
return;
}
# else
DIR *dir;
int *closes = NULL;
int num_closes = 0, i, j, clo, num;
struct dirent *dp;
const char *fname;
dir = opendir("/proc/self/fd");
if (!dir) dir = opendir("/dev/fd");
if (dir)
{
struct dirent *dp;
const char *fname;
int *closes = NULL;
int num_closes = 0, i;
// count # of closes - the dir list should/will not change as its
// the fd's we have open so we can read it twice with no changes
// to it
for (;;)
{
skip:
dp = readdir(dir);
if (!dp) break;
if (!(dp = readdir(dir))) break;
fname = dp->d_name;
if ((fname[0] >= '0') && (fname[0] <= '9'))
if (!((fname[0] >= '0') && (fname[0] <= '9'))) continue;
num = atoi(fname);
if (num < fd) continue;
if (except_fd)
{
int num = atoi(fname);
if (num >= fd)
for (j = 0; except_fd[j] >= 0; j++)
{
if (except_fd)
if (except_fd[j] == num) goto skip;
}
}
num_closes++;
}
// alloc closes list and walk again to fill it - on stack to avoid
// heap allocs
closes = alloca(num_closes * sizeof(int));
if ((closes) && (num_closes > 0))
{
clo = 0;
seekdir(dir, 0);
for (;;)
{
skip2:
if (!(dp = readdir(dir))) break;
fname = dp->d_name;
if (!((fname[0] >= '0') && (fname[0] <= '9'))) continue;
num = atoi(fname);
if (num < fd) continue;
if (except_fd)
{
for (j = 0; except_fd[j] >= 0; j++)
{
int j;
for (j = 0; except_fd[j] >= 0; j++)
{
if (except_fd[j] == num) goto skip;
}
}
num_closes++;
int *tmp = realloc(closes, num_closes * sizeof(int));
if (!tmp) num_closes--;
else
{
closes = tmp;
closes[num_closes - 1] = num;
if (except_fd[j] == num) goto skip2;
}
}
if (clo < num_closes) closes[clo] = num;
clo++;
}
}
closedir(dir);
for (i = 0; i < num_closes; i++) close(closes[i]);
free(closes);
// now go close all those fd's - some may be invalide like the dir
// reading fd above... that's ok.
for (i = 0; i < num_closes; i++)
{
close(closes[i]);
}
return;
}
# endif
#endif
int i, max = 1024;
int max = 1024;
# ifdef HAVE_SYS_RESOURCE_H
#ifdef HAVE_SYS_RESOURCE_H
struct rlimit lim;
if (getrlimit(RLIMIT_NOFILE, &lim) < 0) return;
max = lim.rlim_max;
# endif
#endif
for (i = fd; i < max;)
{
if (except_fd)
@ -1323,11 +1477,11 @@ skip:
for (j = 0; except_fd[j] >= 0; j++)
{
if (except_fd[j] == i) goto skip2;
if (except_fd[j] == i) goto skip3;
}
}
close(i);
skip2:
skip3:
i++;
}
#endif