app.c: make sure that no non-async-signal-safe syscalls are used after

fork before exec

Posix does only allow async-signal-safe syscalls after fork before exec.
As asterisk ignores this, functions like TrySystem or System sometimes
end up in a deadlocked child process. The patch prevents the use of
non-async-signal-safe syscalls.

ASTERISK-28776

Change-Id: Idc76365c0592ee3f3b3bd72a4f48f7a098978e8e
This commit is contained in:
Pirmin Walthert
2020-04-14 18:02:19 +02:00
committed by George Joseph
parent 7fbfbe7da0
commit 6b2d945174
3 changed files with 75 additions and 58 deletions

View File

@@ -81,6 +81,9 @@ struct zombie {
static AST_LIST_HEAD_STATIC(zombies, zombie); static AST_LIST_HEAD_STATIC(zombies, zombie);
#ifdef HAVE_CAP
static cap_t child_cap;
#endif
/* /*
* @{ \brief Define \ref stasis topic objects * @{ \brief Define \ref stasis topic objects
*/ */
@@ -3003,12 +3006,7 @@ int ast_safe_fork(int stop_reaper)
} else { } else {
/* Child */ /* Child */
#ifdef HAVE_CAP #ifdef HAVE_CAP
cap_t cap = cap_from_text("cap_net_admin-eip"); cap_set_proc(child_cap);
if (cap_set_proc(cap)) {
ast_log(LOG_WARNING, "Unable to remove capabilities.\n");
}
cap_free(cap);
#endif #endif
/* Before we unblock our signals, return our trapped signals back to the defaults */ /* Before we unblock our signals, return our trapped signals back to the defaults */
@@ -3118,6 +3116,9 @@ struct stasis_topic *ast_queue_topic(const char *queuename)
static void app_cleanup(void) static void app_cleanup(void)
{ {
#ifdef HAS_CAP
cap_free(child_cap);
#endif
ao2_cleanup(queue_topic_pool); ao2_cleanup(queue_topic_pool);
queue_topic_pool = NULL; queue_topic_pool = NULL;
ao2_cleanup(queue_topic_all); ao2_cleanup(queue_topic_all);
@@ -3127,7 +3128,9 @@ static void app_cleanup(void)
int app_init(void) int app_init(void)
{ {
ast_register_cleanup(app_cleanup); ast_register_cleanup(app_cleanup);
#ifdef HAVE_CAP
child_cap = cap_from_text("cap_net_admin-eip");
#endif
queue_topic_all = stasis_topic_create("queue:all"); queue_topic_all = stasis_topic_create("queue:all");
if (!queue_topic_all) { if (!queue_topic_all) {
return -1; return -1;

View File

@@ -388,6 +388,10 @@ static int multi_thread_safe;
static char randompool[256]; static char randompool[256];
#ifdef HAVE_CAP
static cap_t child_cap;
#endif
static int sig_alert_pipe[2] = { -1, -1 }; static int sig_alert_pipe[2] = { -1, -1 };
static struct { static struct {
unsigned int need_reload:1; unsigned int need_reload:1;
@@ -1099,13 +1103,7 @@ static pid_t safe_exec_prep(int dualfork)
if (pid == 0) { if (pid == 0) {
#ifdef HAVE_CAP #ifdef HAVE_CAP
cap_t cap = cap_from_text("cap_net_admin-eip"); cap_set_proc(child_cap);
if (cap_set_proc(cap)) {
/* Careful with order! Logging cannot happen after we close FDs */
ast_log(LOG_WARNING, "Unable to remove capabilities.\n");
}
cap_free(cap);
#endif #endif
#ifdef HAVE_WORKING_FORK #ifdef HAVE_WORKING_FORK
if (ast_opt_high_priority) { if (ast_opt_high_priority) {
@@ -1804,10 +1802,8 @@ int ast_set_priority(int pri)
if (pri) { if (pri) {
sched.sched_priority = 10; sched.sched_priority = 10;
if (sched_setscheduler(0, SCHED_RR, &sched)) { if (sched_setscheduler(0, SCHED_RR, &sched)) {
ast_log(LOG_WARNING, "Unable to set high priority\n");
return -1; return -1;
} else }
ast_verb(1, "Set to realtime thread\n");
} else { } else {
sched.sched_priority = 0; sched.sched_priority = 0;
/* According to the manpage, these parameters can never fail. */ /* According to the manpage, these parameters can never fail. */
@@ -3920,8 +3916,14 @@ int main(int argc, char *argv[])
exit(1); exit(1);
} }
#ifdef HAVE_CAP
child_cap = cap_from_text("cap_net_admin-eip");
#endif
/* Not a remote console? Start the daemon. */ /* Not a remote console? Start the daemon. */
asterisk_daemon(isroot, runuser, rungroup); asterisk_daemon(isroot, runuser, rungroup);
#ifdef HAS_CAP
cap_free(child_cap);
#endif
return 0; return 0;
} }

View File

@@ -38,6 +38,8 @@
#include "asterisk/utils.h" #include "asterisk/utils.h"
#define POLL_SIZE 1024
#ifndef HAVE_STRSEP #ifndef HAVE_STRSEP
char *strsep(char **str, const char *delims) char *strsep(char **str, const char *delims)
{ {
@@ -426,59 +428,69 @@ int ffsll(long long n)
#ifndef HAVE_CLOSEFROM #ifndef HAVE_CLOSEFROM
void closefrom(int n) void closefrom(int n)
{ {
long x; int maxfd;
#ifndef _SC_OPEN_MAX
struct rlimit rl; struct rlimit rl;
DIR *dir; #endif
char path[16], *result; struct pollfd fds[POLL_SIZE];
struct dirent *entry; int fd=n, loopmax, i;
#ifndef STRICT_COMPAT
long flags;
#endif
snprintf(path, sizeof(path), "/proc/%d/fd", (int) getpid()); #ifndef _SC_OPEN_MAX
if ((dir = opendir(path))) { if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
while ((entry = readdir(dir))) { maxfd = -1;
/* Skip . and .. */ } else {
if (entry->d_name[0] == '.') { maxfd = rl.rlim_cur;
}
#else
maxfd = sysconf (_SC_OPEN_MAX);
#endif
if (maxfd == -1 || maxfd > 65536) {
/* A more reasonable value. Consider that the primary source of
* file descriptors in Asterisk are UDP sockets, of which we are
* limited to 65,535 per address. We additionally limit that down
* to about 10,000 sockets per protocol. While the kernel will
* allow us to set the fileno limit higher (up to 4.2 billion),
* there really is no practical reason for it to be that high.
*
* sysconf as well as getrlimit can return -1 on error. Let's set
* maxfd to the mentioned reasonable value of 65,535 in this case.
*/
maxfd = 65536;
}
while (fd < maxfd) {
loopmax = maxfd - fd;
if (loopmax > POLL_SIZE) {
loopmax = POLL_SIZE;
}
for (i = 0; i < loopmax; i++) {
fds[i].fd = fd+i;
fds[i].events = 0;
}
poll(fds, loopmax, 0);
for (i = 0; i < loopmax; i++) {
if (fds[i].revents == POLLNVAL) {
continue; continue;
} }
if ((x = strtol(entry->d_name, &result, 10)) && x >= n) {
#ifdef STRICT_COMPAT #ifdef STRICT_COMPAT
close(x); close(fds[i].fd);
#else #else
/* This isn't strictly compatible, but it's actually faster /* This isn't strictly compatible, but it's actually faster
* for our purposes to set the CLOEXEC flag than to close * for our purposes to set the CLOEXEC flag than to close
* file descriptors. * file descriptors.
*/
long flags = fcntl(x, F_GETFD);
if (flags == -1 && errno == EBADF) {
continue;
}
fcntl(x, F_SETFD, flags | FD_CLOEXEC);
#endif
}
}
closedir(dir);
} else {
getrlimit(RLIMIT_NOFILE, &rl);
if (rl.rlim_cur > 65535) {
/* A more reasonable value. Consider that the primary source of
* file descriptors in Asterisk are UDP sockets, of which we are
* limited to 65,535 per address. We additionally limit that down
* to about 10,000 sockets per protocol. While the kernel will
* allow us to set the fileno limit higher (up to 4.2 billion),
* there really is no practical reason for it to be that high.
*/ */
rl.rlim_cur = 65535; flags = fcntl(fds[i].fd, F_GETFD);
}
for (x = n; x < rl.rlim_cur; x++) {
#ifdef STRICT_COMPAT
close(x);
#else
long flags = fcntl(x, F_GETFD);
if (flags == -1 && errno == EBADF) { if (flags == -1 && errno == EBADF) {
continue; continue;
} }
fcntl(x, F_SETFD, flags | FD_CLOEXEC); fcntl(fds[i].fd, F_SETFD, flags | FD_CLOEXEC);
#endif #endif
} }
fd += loopmax;
} }
} }
#endif #endif