diff --git a/README-SERIOUSLY.bestpractices.txt b/README-SERIOUSLY.bestpractices.txt index b470fd66cf57f13aaa0fc559b8b407186d979e6f..108adce8f5ccb14c78a1df6849b6462f0cc21598 100644 --- a/README-SERIOUSLY.bestpractices.txt +++ b/README-SERIOUSLY.bestpractices.txt @@ -26,6 +26,9 @@ Sections * Manager Class Authorizations: Recognizing potential issues with certain classes of authorization +* Avoid Privilege Escalations: + Disable the ability to execute functions that may escalate privileges + ---------------- Additional Links ---------------- @@ -344,3 +347,23 @@ same as the class authorization "system". Good system configuration, such as not running Asterisk as root, can prevent serious problems from arising when allowing external connections to originate calls into Asterisk. +=========================== +Avoid Privilege Escalations +=========================== + +External control protocols, such as Manager, often have the ability to get and +set channel variables; which allows the execution of dialplan functions. + +Dialplan functions within Asterisk are incredibly powerful, which is wonderful +for building applications using Asterisk. But during the read or write +execution, certain diaplan functions do much more. For example, reading the +SHELL() function can execute arbitrary commands on the system Asterisk is +running on. Writing to the FILE() function can change any file that Asterisk has +write access to. + +When these functions are executed from an external protocol, that execution +could result in a privilege escalation. Asterisk can inhibit the execution of +these functions, if live_dangerously in the [options] section of asterisk.conf +is set to no. + +In Asterisk 12 and later, live_dangerously defaults to no. diff --git a/UPGRADE-12.txt b/UPGRADE-12.txt index 426b1a979dfa542effcef648187d0771106e053d..6486f3e474dd6ee3eea34ed30825380de1e4ad1f 100644 --- a/UPGRADE-12.txt +++ b/UPGRADE-12.txt @@ -351,6 +351,16 @@ CEL: - BLINDTRANSFER/ATTENDEDTRANSFER events now report the peer as NULL and additional information in the extra string field. +Dialplan Functions: + + - Certain dialplan functions have been marked as 'dangerous', and may only be + executed from the dialplan. Execution from extenal sources (AMI's GetVar and + SetVar actions; etc.) may be inhibited by setting live_dangerously in the + [options] section of asterisk.conf to no. SHELL(), channel locking, and + direct file read/write functions are marked as dangerous. DB_DELETE() and + REALTIME_DESTROY() are marked as dangerous for reads, but can now safely + accept writes (which ignore the provided value). + Dialplan: - All channel and global variable names are evaluated in a case-sensitive manner. In previous versions of Asterisk, variables created and evaluated in diff --git a/configs/asterisk.conf.sample b/configs/asterisk.conf.sample index 404ea30dee41284b195ea4c4533dd7aebe7e9291..f7cda268f323402738fce7395081680a6456af72 100644 --- a/configs/asterisk.conf.sample +++ b/configs/asterisk.conf.sample @@ -83,6 +83,12 @@ documentation_language = en_US ; Set the language you want documentation ; gosub - Invoke the stdexten using a gosub as ; documented in extensions.conf.sample. ; Default gosub. +;live_dangerously = no ; Enable the execution of 'dangerous' dialplan + ; functions from external sources (AMI, + ; etc.) These functions (such as SHELL) are + ; considered dangerous because they can allow + ; privilege escalation. + ; Default yes, for backward compatability. ; Changing the following lines may compromise your security. ;[files] diff --git a/funcs/func_db.c b/funcs/func_db.c index 20c8829a616b1cf7c2ee40748de5bab051f2f73a..ebe58f02e3844caa72114913da6bf4fd4ea2d4ad 100644 --- a/funcs/func_db.c +++ b/funcs/func_db.c @@ -110,6 +110,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>This function will retrieve a value from the Asterisk database and then remove that key from the database. <variable>DB_RESULT</variable> will be set to the key's value if it exists.</para> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be read from the + dialplan, and not directly from external protocols. It can, however, be + executed as a write operation (<literal>DB_DELETE(family, key)=ignored</literal>)</para> + </note> </description> <see-also> <ref type="application">DBdel</ref> @@ -311,10 +317,22 @@ static int function_db_delete(struct ast_channel *chan, const char *cmd, return 0; } +/*! + * \brief Wrapper to execute DB_DELETE from a write operation. Allows execution + * even if live_dangerously is disabled. + */ +static int function_db_delete_write(struct ast_channel *chan, const char *cmd, char *parse, + const char *value) +{ + /* Throwaway to hold the result from the read */ + char buf[128]; + return function_db_delete(chan, cmd, parse, buf, sizeof(buf)); +} static struct ast_custom_function db_delete_function = { .name = "DB_DELETE", .read = function_db_delete, + .write = function_db_delete_write, }; static int unload_module(void) @@ -335,7 +353,7 @@ static int load_module(void) res |= ast_custom_function_register(&db_function); res |= ast_custom_function_register(&db_exists_function); - res |= ast_custom_function_register(&db_delete_function); + res |= ast_custom_function_register_escalating(&db_delete_function, AST_CFE_READ); res |= ast_custom_function_register(&db_keys_function); return res; diff --git a/funcs/func_env.c b/funcs/func_env.c index f413607e0dd59a0580674f17fec210d267a6461e..a2f7c2bd25341e17a46a05ffda04abbfb923b013 100644 --- a/funcs/func_env.c +++ b/funcs/func_env.c @@ -71,6 +71,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <parameter name="filename" required="true" /> </syntax> <description> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> </function> <function name="FILE" language="en_US"> @@ -167,6 +172,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para> Set(FILE(/tmp/foo.txt,-1,,l)=bar)</para> <para> ; Append "bar" to the file with a newline</para> <para> Set(FILE(/tmp/foo.txt,,,al)=bar)</para> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> <see-also> <ref type="function">FILE_COUNT_LINE</ref> @@ -197,6 +207,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") </syntax> <description> <para>Returns the number of lines, or <literal>-1</literal> on error.</para> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> <see-also> <ref type="function">FILE</ref> @@ -216,6 +231,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>'d' - DOS "\r\n" format</para> <para>'m' - Macintosh "\r" format</para> <para>'x' - Cannot be determined</para> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> <see-also> <ref type="function">FILE</ref> @@ -1259,10 +1279,10 @@ static int load_module(void) int res = 0; res |= ast_custom_function_register(&env_function); - res |= ast_custom_function_register(&stat_function); - res |= ast_custom_function_register(&file_function); - res |= ast_custom_function_register(&file_count_line_function); - res |= ast_custom_function_register(&file_format_function); + res |= ast_custom_function_register_escalating(&stat_function, AST_CFE_READ); + res |= ast_custom_function_register_escalating(&file_function, AST_CFE_BOTH); + res |= ast_custom_function_register_escalating(&file_count_line_function, AST_CFE_READ); + res |= ast_custom_function_register_escalating(&file_format_function, AST_CFE_READ); return res; } diff --git a/funcs/func_lock.c b/funcs/func_lock.c index d8db10e842a07ea5d84ff3af1bfb22290bc26950..2102d5c9a288c2a15e868c57a242be87d25eabf6 100644 --- a/funcs/func_lock.c +++ b/funcs/func_lock.c @@ -59,6 +59,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") Returns <literal>1</literal> if the lock was obtained or <literal>0</literal> on error.</para> <note><para>To avoid the possibility of a deadlock, LOCK will only attempt to obtain the lock for 3 seconds if the channel already has another lock.</para></note> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> </function> <function name="TRYLOCK" language="en_US"> @@ -72,6 +77,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <para>Attempts to grab a named lock exclusively, and prevents other channels from obtaining the same lock. Returns <literal>1</literal> if the lock was available or <literal>0</literal> otherwise.</para> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> </function> <function name="UNLOCK" language="en_US"> @@ -86,6 +96,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") had a lock or <literal>0</literal> otherwise.</para> <note><para>It is generally unnecessary to unlock in a hangup routine, as any locks held are automatically freed when the channel is destroyed.</para></note> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> </function> ***/ @@ -502,9 +517,9 @@ static int unload_module(void) static int load_module(void) { - int res = ast_custom_function_register(&lock_function); - res |= ast_custom_function_register(&trylock_function); - res |= ast_custom_function_register(&unlock_function); + int res = ast_custom_function_register_escalating(&lock_function, AST_CFE_READ); + res |= ast_custom_function_register_escalating(&trylock_function, AST_CFE_READ); + res |= ast_custom_function_register_escalating(&unlock_function, AST_CFE_READ); if (ast_pthread_create_background(&broker_tid, NULL, lock_broker, NULL)) { ast_log(LOG_ERROR, "Failed to start lock broker thread. Unloading func_lock module.\n"); diff --git a/funcs/func_realtime.c b/funcs/func_realtime.c index 886b5b4566c749035ae7a35cae3293e04285fdff..a870ab450875c6667700f91f46e9dbe4bbcd2d9d 100644 --- a/funcs/func_realtime.c +++ b/funcs/func_realtime.c @@ -115,6 +115,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") <description> <para>This function acts in the same way as REALTIME(....) does, except that it destroys the matched record in the RT engine.</para> + <note> + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be read from the + dialplan, and not directly from external protocols. It can, however, be + executed as a write operation (<literal>REALTIME_DESTROY(family, fieldmatch)=ignored</literal>)</para> + </note> </description> <see-also> <ref type="function">REALTIME</ref> @@ -439,28 +445,32 @@ static int function_realtime_readdestroy(struct ast_channel *chan, const char *c return -1; } - resultslen = 0; - n = 0; - for (var = head; var; n++, var = var->next) - resultslen += strlen(var->name) + strlen(var->value); - /* add space for delimiters and final '\0' */ - resultslen += n * (strlen(args.delim1) + strlen(args.delim2)) + 1; - - if (resultslen > len) { - /* Unfortunately this does mean that we cannot destroy the row - * anymore. But OTOH, we're not destroying someones data without - * giving him the chance to look at it. */ - ast_log(LOG_WARNING, "Failed to fetch/destroy. Realtime data is too large: need %zu, have %zu.\n", resultslen, len); - return -1; - } + if (len > 0) { + resultslen = 0; + n = 0; + for (var = head; var; n++, var = var->next) { + resultslen += strlen(var->name) + strlen(var->value); + } + /* add space for delimiters and final '\0' */ + resultslen += n * (strlen(args.delim1) + strlen(args.delim2)) + 1; + + if (resultslen > len) { + /* Unfortunately this does mean that we cannot destroy + * the row anymore. But OTOH, we're not destroying + * someones data without giving him the chance to look + * at it. */ + ast_log(LOG_WARNING, "Failed to fetch/destroy. Realtime data is too large: need %zu, have %zu.\n", resultslen, len); + return -1; + } - /* len is going to be sensible, so we don't need to check for stack - * overflows here. */ - out = ast_str_alloca(resultslen); - for (var = head; var; var = var->next) { - ast_str_append(&out, 0, "%s%s%s%s", var->name, args.delim2, var->value, args.delim1); + /* len is going to be sensible, so we don't need to check for + * stack overflows here. */ + out = ast_str_alloca(resultslen); + for (var = head; var; var = var->next) { + ast_str_append(&out, 0, "%s%s%s%s", var->name, args.delim2, var->value, args.delim1); + } + ast_copy_string(buf, ast_str_buffer(out), len); } - ast_copy_string(buf, ast_str_buffer(out), len); ast_destroy_realtime(args.family, args.fieldmatch, args.value, SENTINEL); ast_variables_destroy(head); @@ -471,6 +481,15 @@ static int function_realtime_readdestroy(struct ast_channel *chan, const char *c return 0; } +/*! + * \brief Wrapper to execute REALTIME_DESTROY from a write operation. Allows + * execution even if live_dangerously is disabled. + */ +static int function_realtime_writedestroy(struct ast_channel *chan, const char *cmd, char *data, const char *value) +{ + return function_realtime_readdestroy(chan, cmd, data, NULL, 0); +} + static struct ast_custom_function realtime_function = { .name = "REALTIME", .read = function_realtime_read, @@ -496,6 +515,7 @@ static struct ast_custom_function realtime_store_function = { static struct ast_custom_function realtime_destroy_function = { .name = "REALTIME_DESTROY", .read = function_realtime_readdestroy, + .write = function_realtime_writedestroy, }; static int unload_module(void) @@ -514,7 +534,7 @@ static int load_module(void) int res = 0; res |= ast_custom_function_register(&realtime_function); res |= ast_custom_function_register(&realtime_store_function); - res |= ast_custom_function_register(&realtime_destroy_function); + res |= ast_custom_function_register_escalating(&realtime_destroy_function, AST_CFE_READ); res |= ast_custom_function_register(&realtimefield_function); res |= ast_custom_function_register(&realtimehash_function); return res; diff --git a/funcs/func_shell.c b/funcs/func_shell.c index bad10b381e269019623f68efbe70252f75e8d0ad..e403efc2e82cf3a4d5d24e1b6e53869bfb9ec1e2 100644 --- a/funcs/func_shell.c +++ b/funcs/func_shell.c @@ -88,11 +88,17 @@ static int shell_helper(struct ast_channel *chan, const char *cmd, char *data, </syntax> <description> <para>Collects the output generated by a command executed by the system shell</para> - <para>Example: <literal>Set(foo=${SHELL(echo \bar\)})</literal></para> - <note><para>The command supplied to this function will be executed by the - system's shell, typically specified in the SHELL environment variable. There - are many different system shells available with somewhat different behaviors, - so the output generated by this function may vary between platforms.</para></note> + <para>Example: <literal>Set(foo=${SHELL(echo bar)})</literal></para> + <note> + <para>The command supplied to this function will be executed by the + system's shell, typically specified in the SHELL environment variable. There + are many different system shells available with somewhat different behaviors, + so the output generated by this function may vary between platforms.</para> + + <para>If <literal>live_dangerously</literal> in <literal>asterisk.conf</literal> + is set to <literal>no</literal>, this function can only be executed from the + dialplan, and not directly from external protocols.</para> + </note> </description> </function> @@ -109,7 +115,7 @@ static int unload_module(void) static int load_module(void) { - return ast_custom_function_register(&shell_function); + return ast_custom_function_register_escalating(&shell_function, AST_CFE_READ); } AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Collects the output generated by a command executed by the system shell"); diff --git a/include/asterisk/pbx.h b/include/asterisk/pbx.h index dfdb23f80ac343933b609d40b24867425fe6add0..77694a54cc5a59d35de7ff941b1b97b3b7742bf1 100644 --- a/include/asterisk/pbx.h +++ b/include/asterisk/pbx.h @@ -140,6 +140,15 @@ struct ast_custom_function { /*! Write function, if write is supported */ ast_acf_write_fn_t write; /*!< Write function, if write is supported */ struct ast_module *mod; /*!< Module this custom function belongs to */ + unsigned int read_escalates:1; /*!< The read function is to be considered + * 'dangerous', and should not be run directly + * from external interfaces (AMI, ARI, etc.) + * \since 12 */ + unsigned int write_escalates:1; /*!< The write function is to be considerd + * 'dangerous', and should not be run directly + * from external interfaces (AMI, ARI, etc.) + * \since 12 */ + AST_RWLIST_ENTRY(ast_custom_function) acflist; }; @@ -1366,16 +1375,44 @@ struct ast_custom_function* ast_custom_function_find(const char *name); */ int ast_custom_function_unregister(struct ast_custom_function *acf); +/*! + * \brief Description of the ways in which a function may escalate privileges. + */ +enum ast_custom_function_escalation { + AST_CFE_NONE, + AST_CFE_READ, + AST_CFE_WRITE, + AST_CFE_BOTH, +}; + /*! * \brief Register a custom function */ #define ast_custom_function_register(acf) __ast_custom_function_register(acf, ast_module_info->self) +/*! + * \brief Register a custom function which requires escalated privileges. + * + * Examples would be SHELL() (for which a read needs permission to execute + * arbitrary code) or FILE() (for which write needs permission to change files + * on the filesystem). + */ +#define ast_custom_function_register_escalating(acf, escalation) __ast_custom_function_register_escalating(acf, escalation, ast_module_info->self) + /*! * \brief Register a custom function */ int __ast_custom_function_register(struct ast_custom_function *acf, struct ast_module *mod); +/*! + * \brief Register a custom function which requires escalated privileges. + * + * Examples would be SHELL() (for which a read needs permission to execute + * arbitrary code) or FILE() (for which write needs permission to change files + * on the filesystem). + */ +int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod); + /*! * \brief Retrieve the number of active calls */ @@ -1489,6 +1526,32 @@ unsigned int ast_hashtab_hash_contexts(const void *obj); */ char *ast_complete_applications(const char *line, const char *word, int state); +/*! + * \brief Enable/disable the execution of 'dangerous' functions from external + * protocols (AMI, etc.). + * + * These dialplan functions (such as \c SHELL) provide an opportunity for + * privilege escalation. They are okay to invoke from the dialplan, but external + * protocols with permission controls should not normally invoke them. + * + * This function can globally enable/disable the execution of dangerous + * functions from external protocols. + * + * \param new_live_dangerously If true, enable the execution of escalating + * functions from external protocols. + */ +void pbx_live_dangerously(int new_live_dangerously); + +/*! + * \brief Inhibit (in the current thread) the execution of dialplan functions + * which cause privilege escalations. If pbx_live_dangerously() has been + * called, this function has no effect. + * + * \return 0 if successfuly marked current thread. + * \return Non-zero if marking current thread failed. + */ +int ast_thread_inhibit_escalations(void); + #if defined(__cplusplus) || defined(c_plusplus) } #endif diff --git a/main/asterisk.c b/main/asterisk.c index 74e36986fa410ec26abbd364705805b076c75c50..3ed085d280db9bb7b8b502ca4d175c42f8e52bac 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -3332,6 +3332,8 @@ static void ast_readconfig(void) unsigned int dbdir:1; unsigned int keydir:1; } found = { 0, 0 }; + /* Default to true for backward compatibility */ + int live_dangerously = 1; /* Set default value */ option_dtmfminduration = AST_MIN_DTMF_DURATION; @@ -3565,8 +3567,11 @@ static void ast_readconfig(void) v->value); ast_clear_flag(&ast_options, AST_OPT_FLAG_STDEXTEN_MACRO); } + } else if (!strcasecmp(v->name, "live_dangerously")) { + live_dangerously = ast_true(v->value); } } + pbx_live_dangerously(live_dangerously); for (v = ast_variable_browse(cfg, "compat"); v; v = v->next) { float version; if (sscanf(v->value, "%30f", &version) != 1) { diff --git a/main/pbx.c b/main/pbx.c index ee472d84225582ca982fe5aedf6b97d356193362..6b9bd5ebfd29e3639f297238809bbf3ef0cc1d16 100644 --- a/main/pbx.c +++ b/main/pbx.c @@ -845,6 +845,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 @@ -4001,6 +4012,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. @@ -4099,6 +4132,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' */ @@ -4120,6 +4180,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); @@ -4132,6 +4310,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); @@ -4169,6 +4349,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); @@ -4208,11 +4390,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) diff --git a/main/tcptls.c b/main/tcptls.c index 2b4842638583a951da25c522686ba979bd1bb4b4..76da5126071a2efb576aee87b3ba50b0afd2a02a 100644 --- a/main/tcptls.c +++ b/main/tcptls.c @@ -48,6 +48,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/options.h" #include "asterisk/manager.h" #include "asterisk/astobj2.h" +#include "asterisk/pbx.h" /*! \brief * replacement read/write functions for SSL support. @@ -164,6 +165,16 @@ static void *handle_tcptls_connection(void *data) char err[256]; #endif + /* TCP/TLS connections are associated with external protocols, and + * should not be allowed to execute 'dangerous' functions. This may + * need to be pushed down into the individual protocol handlers, but + * this seems like a good general policy. + */ + if (ast_thread_inhibit_escalations()) { + ast_log(LOG_ERROR, "Failed to inhibit privilege escalations; killing connection\n"); + return NULL; + } + /* * open a FILE * as appropriate. */