security: Inhibit execution of privilege escalating functions

This patch allows individual dialplan functions to be marked as
'dangerous', to inhibit their execution from external sources.

A 'dangerous' function is one which results in a privilege escalation.
For example, if one were to read the channel variable SHELL(rm -rf /)
Bad Things(TM) could happen; even if the external source has only read
permissions.

Execution from external sources may be enabled by setting
'live_dangerously' to 'yes' in the [options] section of asterisk.conf.
Although doing so is not recommended.

Also, the ABI was changed to something more reasonable, since Asterisk
12 does not yet have a public release.

(closes issue ASTERISK-22905)
Review: http://reviewboard.digium.internal/r/432/
........

Merged revisions 403913 from http://svn.asterisk.org/svn/asterisk/branches/1.8
........

Merged revisions 403917 from http://svn.asterisk.org/svn/asterisk/branches/11


git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/12@403959 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
David M. Lee
2013-12-16 18:31:27 +00:00
parent 3a3c7d088b
commit 66ee458f07
12 changed files with 418 additions and 37 deletions

View File

@@ -832,6 +832,17 @@ struct ast_app;
AST_THREADSTORAGE(switch_data);
AST_THREADSTORAGE(extensionstate_buf);
/*!
* \brief A thread local indicating whether the current thread can run
* 'dangerous' dialplan functions.
*/
AST_THREADSTORAGE(thread_inhibit_escalations_tl);
/*!
* \brief Set to true (non-zero) to globally allow all dangerous dialplan
* functions to run.
*/
static int live_dangerously;
/*!
\brief ast_exten: An extension
@@ -3988,6 +3999,28 @@ int ast_custom_function_unregister(struct ast_custom_function *acf)
return cur ? 0 : -1;
}
/*!
* \brief Returns true if given custom function escalates privileges on read.
*
* \param acf Custom function to query.
* \return True (non-zero) if reads escalate privileges.
* \return False (zero) if reads just read.
*/
static int read_escalates(const struct ast_custom_function *acf) {
return acf->read_escalates;
}
/*!
* \brief Returns true if given custom function escalates privileges on write.
*
* \param acf Custom function to query.
* \return True (non-zero) if writes escalate privileges.
* \return False (zero) if writes just write.
*/
static int write_escalates(const struct ast_custom_function *acf) {
return acf->write_escalates;
}
/*! \internal
* \brief Retrieve the XML documentation of a specified ast_custom_function,
* and populate ast_custom_function string fields.
@@ -4086,6 +4119,33 @@ int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_m
return 0;
}
int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
{
int res;
res = __ast_custom_function_register(acf, mod);
if (res != 0) {
return -1;
}
switch (escalation) {
case AST_CFE_NONE:
break;
case AST_CFE_READ:
acf->read_escalates = 1;
break;
case AST_CFE_WRITE:
acf->write_escalates = 1;
break;
case AST_CFE_BOTH:
acf->read_escalates = 1;
acf->write_escalates = 1;
break;
}
return 0;
}
/*! \brief return a pointer to the arguments of the function,
* and terminates the function name with '\\0'
*/
@@ -4107,6 +4167,124 @@ static char *func_args(char *function)
return args;
}
void pbx_live_dangerously(int new_live_dangerously)
{
if (new_live_dangerously && !live_dangerously) {
ast_log(LOG_WARNING, "Privilege escalation protection disabled!\n"
"See https://wiki.asterisk.org/wiki/x/1gKfAQ for more details.\n");
}
if (!new_live_dangerously && live_dangerously) {
ast_log(LOG_NOTICE, "Privilege escalation protection enabled.\n");
}
live_dangerously = new_live_dangerously;
}
int ast_thread_inhibit_escalations(void)
{
int *thread_inhibit_escalations;
thread_inhibit_escalations = ast_threadstorage_get(
&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
if (thread_inhibit_escalations == NULL) {
ast_log(LOG_ERROR, "Error inhibiting privilege escalations for current thread\n");
return -1;
}
*thread_inhibit_escalations = 1;
return 0;
}
/*!
* \brief Indicates whether the current thread inhibits the execution of
* dangerous functions.
*
* \return True (non-zero) if dangerous function execution is inhibited.
* \return False (zero) if dangerous function execution is allowed.
*/
static int thread_inhibits_escalations(void)
{
int *thread_inhibit_escalations;
thread_inhibit_escalations = ast_threadstorage_get(
&thread_inhibit_escalations_tl, sizeof(*thread_inhibit_escalations));
if (thread_inhibit_escalations == NULL) {
ast_log(LOG_ERROR, "Error checking thread's ability to run dangerous functions\n");
/* On error, assume that we are inhibiting */
return 1;
}
return *thread_inhibit_escalations;
}
/*!
* \brief Determines whether execution of a custom function's read function
* is allowed.
*
* \param acfptr Custom function to check
* \return True (non-zero) if reading is allowed.
* \return False (zero) if reading is not allowed.
*/
static int is_read_allowed(struct ast_custom_function *acfptr)
{
if (!acfptr) {
return 1;
}
if (!read_escalates(acfptr)) {
return 1;
}
if (!thread_inhibits_escalations()) {
return 1;
}
if (live_dangerously) {
/* Global setting overrides the thread's preference */
ast_debug(2, "Reading %s from a dangerous context\n",
acfptr->name);
return 1;
}
/* We have no reason to allow this function to execute */
return 0;
}
/*!
* \brief Determines whether execution of a custom function's write function
* is allowed.
*
* \param acfptr Custom function to check
* \return True (non-zero) if writing is allowed.
* \return False (zero) if writing is not allowed.
*/
static int is_write_allowed(struct ast_custom_function *acfptr)
{
if (!acfptr) {
return 1;
}
if (!write_escalates(acfptr)) {
return 1;
}
if (!thread_inhibits_escalations()) {
return 1;
}
if (live_dangerously) {
/* Global setting overrides the thread's preference */
ast_debug(2, "Writing %s from a dangerous context\n",
acfptr->name);
return 1;
}
/* We have no reason to allow this function to execute */
return 0;
}
int ast_func_read(struct ast_channel *chan, const char *function, char *workspace, size_t len)
{
char *copy = ast_strdupa(function);
@@ -4119,6 +4297,8 @@ int ast_func_read(struct ast_channel *chan, const char *function, char *workspac
ast_log(LOG_ERROR, "Function %s not registered\n", copy);
} else if (!acfptr->read && !acfptr->read2) {
ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
} else if (!is_read_allowed(acfptr)) {
ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
} else if (acfptr->read) {
if (acfptr->mod) {
u = __ast_module_user_add(acfptr->mod, chan);
@@ -4156,6 +4336,8 @@ int ast_func_read2(struct ast_channel *chan, const char *function, struct ast_st
ast_log(LOG_ERROR, "Function %s not registered\n", copy);
} else if (!acfptr->read && !acfptr->read2) {
ast_log(LOG_ERROR, "Function %s cannot be read\n", copy);
} else if (!is_read_allowed(acfptr)) {
ast_log(LOG_ERROR, "Dangerous function %s read blocked\n", copy);
} else {
if (acfptr->mod) {
u = __ast_module_user_add(acfptr->mod, chan);
@@ -4195,11 +4377,13 @@ int ast_func_write(struct ast_channel *chan, const char *function, const char *v
char *args = func_args(copy);
struct ast_custom_function *acfptr = ast_custom_function_find(copy);
if (acfptr == NULL)
if (acfptr == NULL) {
ast_log(LOG_ERROR, "Function %s not registered\n", copy);
else if (!acfptr->write)
} else if (!acfptr->write) {
ast_log(LOG_ERROR, "Function %s cannot be written to\n", copy);
else {
} else if (!is_write_allowed(acfptr)) {
ast_log(LOG_ERROR, "Dangerous function %s write blocked\n", copy);
} else {
int res;
struct ast_module_user *u = NULL;
if (acfptr->mod)