From: Matthew Jordan <mjordan@digium.com>
Date: Wed, 2 Jan 2013 15:16:10 +0000
Subject: Resolve crashes due to large stack allocations when using TCP
Origin: http://svnview.digium.com/svn/asterisk?view=rev&rev=378269
CVE: CVE-2012-5976
Bug: https://issues.asterisk.org/jira/browse/ASTERISK-20658

Asterisk had several places where messages received over various network
transports may be copied in a single stack allocation. In the case of TCP,
since multiple packets in a stream may be concatenated together, this can
lead to large allocations that overflow the stack.

This patch modifies those portions of Asterisk using TCP to either
favor heap allocations or use an upper bound to ensure that the stack will not
overflow:
 * For SIP, the allocation now has an upper limit
 * For HTTP, the allocation is now a heap allocation instead of a stack
   allocation
 * For XMPP (in res_jabber), the allocation has been eliminated since it was
   unnecesary.

Note that the HTTP portion of this issue was independently found by Brandon
Edwards of Exodus Intelligence.

Reported by: wdoekes, Brandon Edwards
Tested by: mmichelson, wdoekes
See also: http://downloads.asterisk.org/pub/security/AST-2012-014.html

---
 channels/chan_sip.c        |   58 +++++++++++++++++++++++++++++++-------------
 channels/sip/include/sip.h |    1 +
 main/http.c                |   20 ++++++++++++---
 res/res_jabber.c           |    5 ++--
 4 files changed, 60 insertions(+), 24 deletions(-)

--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -2504,6 +2504,7 @@ static void *_sip_tcp_helper_thread(stru
 	int res, cl, timeout = -1, authenticated = 0, flags, after_poll = 0, need_poll = 1;
 	time_t start;
 	struct sip_request req = { 0, } , reqcpy = { 0, };
+	size_t datalen;
 	struct sip_threadinfo *me = NULL;
 	char buf[1024] = "";
 	struct pollfd fds[2] = { { 0 }, { 0 }, };
@@ -2640,8 +2641,9 @@ static void *_sip_tcp_helper_thread(stru
 			}
 			req.socket.fd = tcptls_session->fd;
 
+			datalen = ast_str_strlen(req.data);
 			/* Read in headers one line at a time */
-			while (ast_str_strlen(req.data) < 4 || strncmp(REQ_OFFSET_TO_STR(&req, data->used - 4), "\r\n\r\n", 4)) {
+			while (datalen < 4 || strncmp(REQ_OFFSET_TO_STR(&req, data->used - 4), "\r\n\r\n", 4)) {
 				if (!tcptls_session->client && !authenticated ) {
 					if ((timeout = sip_check_authtimeout(start)) < 0) {
 						goto cleanup;
@@ -2688,6 +2690,14 @@ static void *_sip_tcp_helper_thread(stru
 					 goto cleanup;
 				}
 				ast_str_append(&req.data, 0, "%s", buf);
+				datalen = ast_str_strlen(req.data);
+				if (datalen > SIP_MAX_PACKET_SIZE) {
+					ast_log(LOG_WARNING, "Rejecting SIP %s packet from '%s' because way too large: %zu\n",
+							tcptls_session->ssl ? "SSL" : "TCP",
+							ast_sockaddr_stringify(&tcptls_session->remote_address),
+							datalen);
+					goto cleanup;
+				}
 			}
 			copy_request(&reqcpy, &req);
 			parse_request(&reqcpy);
--- a/channels/sip/include/sip.h
+++ b/channels/sip/include/sip.h
@@ -96,6 +96,7 @@
 
 #define SIP_MAX_HEADERS           64     /*!< Max amount of SIP headers to read */
 #define SIP_MAX_LINES             256    /*!< Max amount of lines in SIP attachment (like SDP) */
+#define SIP_MAX_PACKET_SIZE       20480  /*!< Max SIP packet size */
 #define SIP_MIN_PACKET            4096   /*!< Initialize size of memory to allocate for packets */
 #define MAX_HISTORY_ENTRIES		  50	 /*!< Max entires in the history list for a sip_pvt */
 
--- a/main/http.c
+++ b/main/http.c
@@ -622,6 +622,7 @@ struct ast_variable *ast_http_get_post_v
 	int content_length = 0;
 	struct ast_variable *v, *post_vars=NULL, *prev = NULL;
 	char *buf, *var, *val;
+	int res;
 
 	for (v = headers; v; v = v->next) {
 		if (!strcasecmp(v->name, "Content-Type")) {
@@ -634,21 +635,27 @@ struct ast_variable *ast_http_get_post_v
 
 	for (v = headers; v; v = v->next) {
 		if (!strcasecmp(v->name, "Content-Length")) {
-			content_length = atoi(v->value) + 1;
+			content_length = atoi(v->value);
 			break;
 		}
 	}
 
-	if (!content_length) {
+	if (content_length <= 0) {
 		return NULL;
 	}
 
-	if (!(buf = alloca(content_length))) {
+	buf = ast_malloc(content_length + 1);
+	if (!buf) {
 		return NULL;
 	}
-	if (!fgets(buf, content_length, ser->f)) {
-		return NULL;
+
+	res = fread(buf, 1, content_length, ser->f);
+	if (res < content_length) {
+		/* Error, distinguishable by ferror() or feof(), but neither
+		 * is good. */
+		goto done;
 	}
+	buf[content_length] = '\0';
 
 	while ((val = strsep(&buf, "&"))) {
 		var = strsep(&val, "=");
@@ -667,6 +674,9 @@ struct ast_variable *ast_http_get_post_v
 			prev = v;
 		}
 	}
+	
+done:
+	ast_free(buf);
 	return post_vars;
 }
 
--- a/res/res_jabber.c
+++ b/res/res_jabber.c
@@ -768,7 +768,7 @@ static struct ast_custom_function jabber
  */
 static int acf_jabberreceive_read(struct ast_channel *chan, const char *name, char *data, char *buf, size_t buflen)
 {
-	char *aux = NULL, *parse = NULL;
+	char *parse = NULL;
 	int timeout;
 	int jidlen, resourcelen;
 	struct timeval start;
@@ -885,7 +885,7 @@ static int acf_jabberreceive_read(struct
 				continue;
 			}
 			found = 1;
-			aux = ast_strdupa(tmp->message);
+			ast_copy_string(buf, tmp->message, buflen);
 			AST_LIST_REMOVE_CURRENT(list);
 			aji_message_destroy(tmp);
 			break;
@@ -910,7 +910,6 @@ static int acf_jabberreceive_read(struct
 		ast_log(LOG_NOTICE, "Timed out : no message received from %s\n", args.jid);
 		return -1;
 	}
-	ast_copy_string(buf, aux, buflen);
 
 	return 0;
 }
