From: "David M. Lee" <dlee@digium.com>
Date: Mon, 16 Dec 2013 16:36:52 +0000
Bug: https://issues.asterisk.org/jira/browse/ASTERISK-22905
Subject: Inhibit execution of privilege escalating functions
Origin: http://svnview.digium.com/svn/asterisk?view=rev&rev=403913

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.

Review: http://reviewboard.digium.internal/r/432/

---
 README-SERIOUSLY.bestpractices.txt |   24 ++++
 UPGRADE.txt                        |    8 ++
 configs/asterisk.conf.sample       |    6 +
 funcs/func_db.c                    |   20 ++-
 funcs/func_env.c                   |   28 +++-
 funcs/func_lock.c                  |   21 ++-
 funcs/func_realtime.c              |   62 ++++++---
 funcs/func_shell.c                 |   18 ++-
 include/asterisk/pbx.h             |   54 ++++++++
 main/asterisk.c                    |    5 +
 main/pbx.c                         |  254 +++++++++++++++++++++++++++++++++++-
 main/tcptls.c                      |   11 ++
 12 files changed, 473 insertions(+), 38 deletions(-)

--- 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,24 @@ same as the class authorization "system"
 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.
+
+For backwards compatibility, live_dangerously defaults to yes, and must be
+explicitly set to no to enable this privilege escalation protection.
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -18,6 +18,15 @@
 ===
 ===========================================================
 
+from 1.6.2.9-2+squeeze11 to 1.6.2.9-2+squeeze12 (backported from 1.8.24.1):
+* 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).
+
 From 1.6.1 to 1.6.2:
 
 * SIP no longer sends the 183 progress message for early media by
--- a/funcs/func_db.c
+++ b/funcs/func_db.c
@@ -93,6 +93,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 			<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>
@@ -238,10 +244,22 @@ static int function_db_delete(struct ast
 	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)
@@ -261,7 +279,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);
 
 	return res;
 }
--- a/funcs/func_env.c
+++ b/funcs/func_env.c
@@ -65,6 +65,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 			<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">
@@ -83,6 +88,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 			</parameter>
 		</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>
  ***/
@@ -259,8 +269,8 @@ 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_escalating(&stat_function, AST_CFE_READ);
+	res |= ast_custom_function_register_escalating(&file_function, AST_CFE_READ);
 
 	return res;
 }
--- a/funcs/func_lock.c
+++ b/funcs/func_lock.c
@@ -55,6 +55,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 			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">
@@ -68,6 +73,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 			<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">
@@ -82,6 +92,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 			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>
  ***/
@@ -484,9 +499,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);
 	ast_pthread_create_background(&broker_tid, NULL, lock_broker, NULL);
 	return res;
 }
--- a/funcs/func_realtime.c
+++ b/funcs/func_realtime.c
@@ -99,6 +99,12 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 		<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>
 	</function>
 	<function name="REALTIME_FIELD" language="en_US">
@@ -398,28 +404,32 @@ static int function_realtime_readdestroy
 		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 (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;
-	}
+		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);
@@ -430,6 +440,15 @@ static int function_realtime_readdestroy
 	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);
+}
+
 struct ast_custom_function realtime_function = {
 	.name = "REALTIME",
 	.read = function_realtime_read,
@@ -455,6 +474,7 @@ struct ast_custom_function realtime_stor
 struct ast_custom_function realtime_destroy_function = {
 	.name = "REALTIME_DESTROY",
 	.read = function_realtime_readdestroy,
+	.write = function_realtime_writedestroy,
 };
 
 static int unload_module(void)
@@ -473,7 +493,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;
--- a/funcs/func_shell.c
+++ b/funcs/func_shell.c
@@ -75,12 +75,17 @@ static int shell_helper(struct ast_chann
 		</syntax>
 		<description>
 			<para>Returns the value from a system command</para>
-			<para>Example:  <literal>Set(foo=${SHELL(echo \bar\)})</literal></para>
-			<note><para>When using the SHELL() dialplan function, your \SHELL\ is /bin/sh,
-			which may differ as to the underlying shell, depending upon your production
-			platform.  Also keep in mind that if you are using a common path, you should
-			be mindful of race conditions that could result from two calls running
-			SHELL() simultaneously.</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>
@@ -97,7 +102,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, "Returns the output of a shell command");
--- a/include/asterisk/pbx.h
+++ b/include/asterisk/pbx.h
@@ -1023,16 +1023,44 @@ struct ast_custom_function* ast_custom_f
 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
  */
 int ast_active_calls(void);
@@ -1129,6 +1157,32 @@ int ast_hashtab_compare_contexts(const v
 unsigned int ast_hashtab_hash_contexts(const void *obj);
 /*! @} */
 
+/*!
+ * \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
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -2828,6 +2828,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;
 
 	if (ast_opt_override_config) {
 		cfg = ast_config_load2(ast_config_AST_CONFIG_FILE, "" /* core, can't reload */, config_flags);
@@ -3033,8 +3035,11 @@ static void ast_readconfig(void)
 			ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_HIDE_CONSOLE_CONNECT);
 		} else if (!strcasecmp(v->name, "sendfullybooted")) {
 			ast_set2_flag(&ast_options, ast_true(v->value), AST_OPT_FLAG_SEND_FULLYBOOTED);
+		} 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) {
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -759,6 +759,17 @@ static struct ast_taskprocessor *device_
 
 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
@@ -1079,6 +1090,19 @@ static int totalcalls;
 
 static AST_RWLIST_HEAD_STATIC(acf_root, ast_custom_function);
 
+/*!
+ * \brief Extra information for an \ref ast_custom_function holding privilege
+ * escalation information. Kept in a separate structure for ABI compatibility.
+ */
+struct ast_custom_escalating_function {
+	AST_RWLIST_ENTRY(ast_custom_escalating_function) list;
+	const struct ast_custom_function *acf;
+	unsigned int read_escalates:1;
+	unsigned int write_escalates:1;
+};
+
+static AST_RWLIST_HEAD_STATIC(escalation_root, ast_custom_escalating_function);
+
 /*! \brief Declaration of builtin applications */
 static struct pbx_builtin {
 	char name[AST_MAX_APP];
@@ -3216,6 +3240,7 @@ struct ast_custom_function *ast_custom_f
 int ast_custom_function_unregister(struct ast_custom_function *acf)
 {
 	struct ast_custom_function *cur;
+	struct ast_custom_escalating_function *cur_escalation;
 
 	if (!acf) {
 		return -1;
@@ -3230,9 +3255,64 @@ int ast_custom_function_unregister(struc
 	}
 	AST_RWLIST_UNLOCK(&acf_root);
 
+	/* Remove from the escalation list */
+	AST_RWLIST_WRLOCK(&escalation_root);
+	AST_RWLIST_TRAVERSE_SAFE_BEGIN(&escalation_root, cur_escalation, list) {
+		if (cur_escalation->acf == acf) {
+			AST_RWLIST_REMOVE_CURRENT(list);
+			break;
+		}
+	}
+	AST_RWLIST_TRAVERSE_SAFE_END;
+	AST_RWLIST_UNLOCK(&escalation_root);
+
 	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) {
+	int res = 0;
+	struct ast_custom_escalating_function *cur_escalation;
+
+	AST_RWLIST_RDLOCK(&escalation_root);
+	AST_RWLIST_TRAVERSE(&escalation_root, cur_escalation, list) {
+		if (cur_escalation->acf == acf) {
+			res = cur_escalation->read_escalates;
+			break;
+		}
+	}
+	AST_RWLIST_UNLOCK(&escalation_root);
+	return res;
+}
+
+/*!
+ * \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) {
+	int res = 0;
+	struct ast_custom_escalating_function *cur_escalation;
+
+	AST_RWLIST_RDLOCK(&escalation_root);
+	AST_RWLIST_TRAVERSE(&escalation_root, cur_escalation, list) {
+		if (cur_escalation->acf == acf) {
+			res = cur_escalation->write_escalates;
+			break;
+		}
+	}
+	AST_RWLIST_UNLOCK(&escalation_root);
+	return res;
+}
+
 /*! \internal
  *  \brief Retrieve the XML documentation of a specified ast_custom_function,
  *         and populate ast_custom_function string fields.
@@ -3332,6 +3412,50 @@ int __ast_custom_function_register(struc
 	return 0;
 }
 
+int __ast_custom_function_register_escalating(struct ast_custom_function *acf, enum ast_custom_function_escalation escalation, struct ast_module *mod)
+{
+	struct ast_custom_escalating_function *acf_escalation = NULL;
+	int res;
+
+	res = __ast_custom_function_register(acf, mod);
+	if (res != 0) {
+		return -1;
+	}
+
+	if (escalation == AST_CFE_NONE) {
+		/* No escalations; no need to do anything else */
+		return 0;
+	}
+
+	acf_escalation = ast_calloc(1, sizeof(*acf_escalation));
+	if (!acf_escalation) {
+		ast_custom_function_unregister(acf);
+		return -1;
+	}
+
+	acf_escalation->acf = acf;
+	switch (escalation) {
+	case AST_CFE_NONE:
+		break;
+	case AST_CFE_READ:
+		acf_escalation->read_escalates = 1;
+		break;
+	case AST_CFE_WRITE:
+		acf_escalation->write_escalates = 1;
+		break;
+	case AST_CFE_BOTH:
+		acf_escalation->read_escalates = 1;
+		acf_escalation->write_escalates = 1;
+		break;
+	}
+
+	AST_RWLIST_WRLOCK(&escalation_root);
+	AST_RWLIST_INSERT_TAIL(&escalation_root, acf_escalation, list);
+	AST_RWLIST_UNLOCK(&escalation_root);
+
+	return 0;
+}
+
 /*! \brief return a pointer to the arguments of the function,
  * and terminates the function name with '\\0'
  */
@@ -3353,6 +3477,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);
@@ -3363,6 +3605,8 @@ int ast_func_read(struct ast_channel *ch
 		ast_log(LOG_ERROR, "Function %s not registered\n", copy);
 	else if (!acfptr->read)
 		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 {
 		int res;
 		struct ast_module_user *u = NULL;
@@ -3382,11 +3626,13 @@ int ast_func_write(struct ast_channel *c
 	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)
--- a/main/tcptls.c
+++ b/main/tcptls.c
@@ -44,6 +44,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revisi
 #include "asterisk/options.h"
 #include "asterisk/manager.h"
 #include "asterisk/astobj2.h"
+#include "asterisk/pbx.h"
 
 /*! \brief
  * replacement read/write functions for SSL support.
@@ -135,6 +136,16 @@ static void *handle_tcptls_connection(vo
 	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.
 	*/
