Blame SOURCES/0001-sdpd-Fix-leaking-buffers-stored-in-cstates-cache.patch

be035b
From 4e6a2402ed4f46ea026ad0929fbc14faecf3a475 Mon Sep 17 00:00:00 2001
be035b
From: Gopal Tiwari <gtiwari@redhat.com>
be035b
Date: Wed, 1 Dec 2021 12:18:24 +0530
be035b
Subject: [PATCH BlueZ] sdpd: Fix leaking buffers stored in cstates cache
be035b
be035b
commit e79417ed7185b150a056d4eb3a1ab528b91d2fc0
be035b
Author: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
be035b
Date:   Thu Jul 15 11:01:20 2021 -0700
be035b
be035b
    sdpd: Fix leaking buffers stored in cstates cache
be035b
be035b
    These buffer shall only be keep in cache for as long as they are
be035b
    needed so this would cleanup any client cstates in the following
be035b
    conditions:
be035b
be035b
     - There is no cstate on the response
be035b
     - No continuation can be found for cstate
be035b
     - Different request opcode
be035b
     - Respond with an error
be035b
     - Client disconnect
be035b
be035b
    Fixes: https://github.com/bluez/bluez/security/advisories/GHSA-3fqg-r8j5-f5xq
be035b
---
be035b
 src/sdpd-request.c | 170 ++++++++++++++++++++++++++++++++-------------
be035b
 src/sdpd-server.c  |  20 +++---
be035b
 src/sdpd.h         |   3 +
be035b
 unit/test-sdp.c    |   2 +-
be035b
 4 files changed, 135 insertions(+), 60 deletions(-)
be035b
be035b
diff --git a/src/sdpd-request.c b/src/sdpd-request.c
be035b
index 033d1e5bf..c8f5a2c72 100644
be035b
--- a/src/sdpd-request.c
be035b
+++ b/src/sdpd-request.c
be035b
@@ -42,48 +42,78 @@ typedef struct {
be035b
 
be035b
 #define MIN(x, y) ((x) < (y)) ? (x): (y)
be035b
 
be035b
-typedef struct _sdp_cstate_list sdp_cstate_list_t;
be035b
+typedef struct sdp_cont_info sdp_cont_info_t;
be035b
 
be035b
-struct _sdp_cstate_list {
be035b
-	sdp_cstate_list_t *next;
be035b
+struct sdp_cont_info {
be035b
+	int sock;
be035b
+	uint8_t opcode;
be035b
 	uint32_t timestamp;
be035b
 	sdp_buf_t buf;
be035b
 };
be035b
 
be035b
-static sdp_cstate_list_t *cstates;
be035b
+static sdp_list_t *cstates;
be035b
 
be035b
-/* FIXME: should probably remove it when it's found */
be035b
-static sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate)
be035b
+static int cstate_match(const void *data, const void *user_data)
be035b
 {
be035b
-	sdp_cstate_list_t *p;
be035b
+	const sdp_cont_info_t *cinfo = data;
be035b
+	const sdp_cont_state_t *cstate = user_data;
be035b
 
be035b
-	for (p = cstates; p; p = p->next) {
be035b
-		/* Check timestamp */
be035b
-		if (p->timestamp != cstate->timestamp)
be035b
-			continue;
be035b
+	/* Check timestamp */
be035b
+	return cinfo->timestamp - cstate->timestamp;
be035b
+}
be035b
+
be035b
+static void sdp_cont_info_free(sdp_cont_info_t *cinfo)
be035b
+{
be035b
+	if (!cinfo)
be035b
+		return;
be035b
+
be035b
+	cstates = sdp_list_remove(cstates, cinfo);
be035b
+	free(cinfo->buf.data);
be035b
+	free(cinfo);
be035b
+}
be035b
+
be035b
+static sdp_cont_info_t *sdp_get_cont_info(sdp_req_t *req,
be035b
+						sdp_cont_state_t *cstate)
be035b
+{
be035b
+	sdp_list_t *list;
be035b
+
be035b
+	list = sdp_list_find(cstates, cstate, cstate_match);
be035b
+	if (list) {
be035b
+		sdp_cont_info_t *cinfo = list->data;
be035b
 
be035b
-		/* Check if requesting more than available */
be035b
-		if (cstate->cStateValue.maxBytesSent < p->buf.data_size)
be035b
-			return &p->buf;
be035b
+		if (cinfo->opcode == req->opcode)
be035b
+			return cinfo;
be035b
+
be035b
+		/* Cleanup continuation if the opcode doesn't match since its
be035b
+		 * response buffer shall only be valid for the original requests
be035b
+		 */
be035b
+		sdp_cont_info_free(cinfo);
be035b
+		return NULL;
be035b
 	}
be035b
 
be035b
-	return 0;
be035b
+	/* Cleanup cstates if no continuation info could be found */
be035b
+	sdp_cstate_cleanup(req->sock);
be035b
+
be035b
+	return NULL;
be035b
 }
be035b
 
be035b
-static uint32_t sdp_cstate_alloc_buf(sdp_buf_t *buf)
be035b
+static uint32_t sdp_cstate_alloc_buf(sdp_req_t *req, sdp_buf_t *buf)
be035b
 {
be035b
-	sdp_cstate_list_t *cstate = malloc(sizeof(sdp_cstate_list_t));
be035b
+	sdp_cont_info_t *cinfo = malloc(sizeof(sdp_cont_info_t));
be035b
 	uint8_t *data = malloc(buf->data_size);
be035b
 
be035b
 	memcpy(data, buf->data, buf->data_size);
be035b
-	memset((char *)cstate, 0, sizeof(sdp_cstate_list_t));
be035b
-	cstate->buf.data = data;
be035b
-	cstate->buf.data_size = buf->data_size;
be035b
-	cstate->buf.buf_size = buf->data_size;
be035b
-	cstate->timestamp = sdp_get_time();
be035b
-	cstate->next = cstates;
be035b
-	cstates = cstate;
be035b
-	return cstate->timestamp;
be035b
+	memset(cinfo, 0, sizeof(sdp_cont_info_t));
be035b
+	cinfo->buf.data = data;
be035b
+	cinfo->buf.data_size = buf->data_size;
be035b
+	cinfo->buf.buf_size = buf->data_size;
be035b
+	cinfo->timestamp = sdp_get_time();
be035b
+	cinfo->sock = req->sock;
be035b
+	cinfo->opcode = req->opcode;
be035b
+
be035b
+	cstates = sdp_list_append(cstates, cinfo);
be035b
+
be035b
+	return cinfo->timestamp;
be035b
 }
be035b
 
be035b
 /* Additional values for checking datatype (not in spec) */
be035b
@@ -274,14 +304,16 @@ static int sdp_set_cstate_pdu(sdp_buf_t *buf, sdp_cont_state_t *cstate)
be035b
 	return length;
be035b
 }
be035b
 
be035b
-static int sdp_cstate_get(uint8_t *buffer, size_t len,
be035b
-						sdp_cont_state_t **cstate)
be035b
+static int sdp_cstate_get(sdp_req_t *req, uint8_t *buffer, size_t len,
be035b
+			sdp_cont_state_t **cstate, sdp_cont_info_t **cinfo)
be035b
 {
be035b
 	uint8_t cStateSize = *buffer;
be035b
 
be035b
 	SDPDBG("Continuation State size : %d", cStateSize);
be035b
 
be035b
 	if (cStateSize == 0) {
be035b
+		/* Cleanup cstates if request doesn't contain a cstate */
be035b
+		sdp_cstate_cleanup(req->sock);
be035b
 		*cstate = NULL;
be035b
 		return 0;
be035b
 	}
be035b
@@ -306,6 +338,8 @@ static int sdp_cstate_get(uint8_t *buffer, size_t len,
be035b
 	SDPDBG("Cstate TS : 0x%x", (*cstate)->timestamp);
be035b
 	SDPDBG("Bytes sent : %d", (*cstate)->cStateValue.maxBytesSent);
be035b
 
be035b
+	*cinfo = sdp_get_cont_info(req, *cstate);
be035b
+
be035b
 	return 0;
be035b
 }
be035b
 
be035b
@@ -360,6 +394,7 @@ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 	uint16_t expected, actual, rsp_count = 0;
be035b
 	uint8_t dtd;
be035b
 	sdp_cont_state_t *cstate = NULL;
be035b
+	sdp_cont_info_t *cinfo = NULL;
be035b
 	uint8_t *pCacheBuffer = NULL;
be035b
 	int handleSize = 0;
be035b
 	uint32_t cStateId = 0;
be035b
@@ -399,9 +434,9 @@ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 
be035b
 	/*
be035b
 	 * Check if continuation state exists, if yes attempt
be035b
-	 * to get rsp remainder from cache, else send error
be035b
+	 * to get rsp remainder from continuation info, else send error
be035b
 	 */
be035b
-	if (sdp_cstate_get(pdata, data_left, &cstate) < 0) {
be035b
+	if (sdp_cstate_get(req, pdata, data_left, &cstate, &cinfo) < 0) {
be035b
 		status = SDP_INVALID_SYNTAX;
be035b
 		goto done;
be035b
 	}
be035b
@@ -451,7 +486,7 @@ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 
be035b
 		if (rsp_count > actual) {
be035b
 			/* cache the rsp and generate a continuation state */
be035b
-			cStateId = sdp_cstate_alloc_buf(buf);
be035b
+			cStateId = sdp_cstate_alloc_buf(req, buf);
be035b
 			/*
be035b
 			 * subtract handleSize since we now send only
be035b
 			 * a subset of handles
be035b
@@ -459,6 +494,7 @@ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 			buf->data_size -= handleSize;
be035b
 		} else {
be035b
 			/* NULL continuation state */
be035b
+			sdp_cont_info_free(cinfo);
be035b
 			sdp_set_cstate_pdu(buf, NULL);
be035b
 		}
be035b
 	}
be035b
@@ -468,13 +504,15 @@ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 		short lastIndex = 0;
be035b
 
be035b
 		if (cstate) {
be035b
-			/*
be035b
-			 * Get the previous sdp_cont_state_t and obtain
be035b
-			 * the cached rsp
be035b
-			 */
be035b
-			sdp_buf_t *pCache = sdp_get_cached_rsp(cstate);
be035b
-			if (pCache) {
be035b
-				pCacheBuffer = pCache->data;
be035b
+			if (cinfo) {
be035b
+				/* Check if requesting more than available */
be035b
+				if (cstate->cStateValue.maxBytesSent >=
be035b
+						cinfo->buf.data_size) {
be035b
+					status = SDP_INVALID_CSTATE;
be035b
+					goto done;
be035b
+				}
be035b
+
be035b
+				pCacheBuffer = cinfo->buf.data;
be035b
 				/* get the rsp_count from the cached buffer */
be035b
 				rsp_count = get_be16(pCacheBuffer);
be035b
 
be035b
@@ -518,6 +556,7 @@ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 		if (i == rsp_count) {
be035b
 			/* set "null" continuationState */
be035b
 			sdp_set_cstate_pdu(buf, NULL);
be035b
+			sdp_cont_info_free(cinfo);
be035b
 		} else {
be035b
 			/*
be035b
 			 * there's more: set lastIndexSent to
be035b
@@ -540,6 +579,7 @@ static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 
be035b
 done:
be035b
 	free(cstate);
be035b
+
be035b
 	if (pattern)
be035b
 		sdp_list_free(pattern, free);
be035b
 
be035b
@@ -619,15 +659,21 @@ static int extract_attrs(sdp_record_t *rec, sdp_list_t *seq, sdp_buf_t *buf)
be035b
 }
be035b
 
be035b
 /* Build cstate response */
be035b
-static int sdp_cstate_rsp(sdp_cont_state_t *cstate, sdp_buf_t *buf,
be035b
-							uint16_t max)
be035b
+static int sdp_cstate_rsp(sdp_cont_info_t *cinfo, sdp_cont_state_t *cstate,
be035b
+					sdp_buf_t *buf, uint16_t max)
be035b
 {
be035b
-	/* continuation State exists -> get from cache */
be035b
-	sdp_buf_t *cache = sdp_get_cached_rsp(cstate);
be035b
+	sdp_buf_t *cache;
be035b
 	uint16_t sent;
be035b
 
be035b
-	if (!cache)
be035b
+	if (!cinfo)
be035b
+		return 0;
be035b
+
be035b
+	if (cstate->cStateValue.maxBytesSent >= cinfo->buf.data_size) {
be035b
+		sdp_cont_info_free(cinfo);
be035b
 		return 0;
be035b
+	}
be035b
+
be035b
+	cache = &cinfo->buf;
be035b
 
be035b
 	sent = MIN(max, cache->data_size - cstate->cStateValue.maxBytesSent);
be035b
 	memcpy(buf->data, cache->data + cstate->cStateValue.maxBytesSent, sent);
be035b
@@ -637,8 +683,10 @@ static int sdp_cstate_rsp(sdp_cont_state_t *cstate, sdp_buf_t *buf,
be035b
 	SDPDBG("Response size : %d sending now : %d bytes sent so far : %d",
be035b
 		cache->data_size, sent, cstate->cStateValue.maxBytesSent);
be035b
 
be035b
-	if (cstate->cStateValue.maxBytesSent == cache->data_size)
be035b
+	if (cstate->cStateValue.maxBytesSent == cache->data_size) {
be035b
+		sdp_cont_info_free(cinfo);
be035b
 		return sdp_set_cstate_pdu(buf, NULL);
be035b
+	}
be035b
 
be035b
 	return sdp_set_cstate_pdu(buf, cstate);
be035b
 }
be035b
@@ -652,6 +700,7 @@ static int sdp_cstate_rsp(sdp_cont_state_t *cstate, sdp_buf_t *buf,
be035b
 static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 {
be035b
 	sdp_cont_state_t *cstate = NULL;
be035b
+	sdp_cont_info_t *cinfo = NULL;
be035b
 	short cstate_size = 0;
be035b
 	sdp_list_t *seq = NULL;
be035b
 	uint8_t dtd = 0;
be035b
@@ -708,7 +757,7 @@ static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 	 * if continuation state exists, attempt
be035b
 	 * to get rsp remainder from cache, else send error
be035b
 	 */
be035b
-	if (sdp_cstate_get(pdata, data_left, &cstate) < 0) {
be035b
+	if (sdp_cstate_get(req, pdata, data_left, &cstate, &cinfo) < 0) {
be035b
 		status = SDP_INVALID_SYNTAX;
be035b
 		goto done;
be035b
 	}
be035b
@@ -737,7 +786,7 @@ static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 	buf->buf_size -= sizeof(uint16_t);
be035b
 
be035b
 	if (cstate) {
be035b
-		cstate_size = sdp_cstate_rsp(cstate, buf, max_rsp_size);
be035b
+		cstate_size = sdp_cstate_rsp(cinfo, cstate, buf, max_rsp_size);
be035b
 		if (!cstate_size) {
be035b
 			status = SDP_INVALID_CSTATE;
be035b
 			error("NULL cache buffer and non-NULL continuation state");
be035b
@@ -749,7 +798,7 @@ static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 			sdp_cont_state_t newState;
be035b
 
be035b
 			memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
be035b
-			newState.timestamp = sdp_cstate_alloc_buf(buf);
be035b
+			newState.timestamp = sdp_cstate_alloc_buf(req, buf);
be035b
 			/*
be035b
 			 * Reset the buffer size to the maximum expected and
be035b
 			 * set the sdp_cont_state_t
be035b
@@ -793,6 +842,7 @@ static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 	int scanned, rsp_count = 0;
be035b
 	sdp_list_t *pattern = NULL, *seq = NULL, *svcList;
be035b
 	sdp_cont_state_t *cstate = NULL;
be035b
+	sdp_cont_info_t *cinfo = NULL;
be035b
 	short cstate_size = 0;
be035b
 	uint8_t dtd = 0;
be035b
 	sdp_buf_t tmpbuf;
be035b
@@ -852,7 +902,7 @@ static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 	 * if continuation state exists attempt
be035b
 	 * to get rsp remainder from cache, else send error
be035b
 	 */
be035b
-	if (sdp_cstate_get(pdata, data_left, &cstate) < 0) {
be035b
+	if (sdp_cstate_get(req, pdata, data_left, &cstate, &cinfo) < 0) {
be035b
 		status = SDP_INVALID_SYNTAX;
be035b
 		goto done;
be035b
 	}
be035b
@@ -906,7 +956,7 @@ static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 			sdp_cont_state_t newState;
be035b
 
be035b
 			memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
be035b
-			newState.timestamp = sdp_cstate_alloc_buf(buf);
be035b
+			newState.timestamp = sdp_cstate_alloc_buf(req, buf);
be035b
 			/*
be035b
 			 * Reset the buffer size to the maximum expected and
be035b
 			 * set the sdp_cont_state_t
be035b
@@ -917,7 +967,7 @@ static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf)
be035b
 		} else
be035b
 			cstate_size = sdp_set_cstate_pdu(buf, NULL);
be035b
 	} else {
be035b
-		cstate_size = sdp_cstate_rsp(cstate, buf, max);
be035b
+		cstate_size = sdp_cstate_rsp(cinfo, cstate, buf, max);
be035b
 		if (!cstate_size) {
be035b
 			status = SDP_INVALID_CSTATE;
be035b
 			SDPDBG("Non-null continuation state, but null cache buffer");
be035b
@@ -974,6 +1024,9 @@ static void process_request(sdp_req_t *req)
be035b
 		status = SDP_INVALID_PDU_SIZE;
be035b
 		goto send_rsp;
be035b
 	}
be035b
+
be035b
+	req->opcode = reqhdr->pdu_id;
be035b
+
be035b
 	switch (reqhdr->pdu_id) {
be035b
 	case SDP_SVC_SEARCH_REQ:
be035b
 		SDPDBG("Got a svc srch req");
be035b
@@ -1020,6 +1073,8 @@ static void process_request(sdp_req_t *req)
be035b
 
be035b
 send_rsp:
be035b
 	if (status) {
be035b
+		/* Cleanup cstates on error */
be035b
+		sdp_cstate_cleanup(req->sock);
be035b
 		rsphdr->pdu_id = SDP_ERROR_RSP;
be035b
 		put_be16(status, rsp.data);
be035b
 		rsp.data_size = sizeof(uint16_t);
be035b
@@ -1108,3 +1163,20 @@ void handle_request(int sk, uint8_t *data, int len)
be035b
 
be035b
 	process_request(&req;;
be035b
 }
be035b
+
be035b
+void sdp_cstate_cleanup(int sock)
be035b
+{
be035b
+	sdp_list_t *list;
be035b
+
be035b
+	/* Remove any cinfo for the client */
be035b
+	for (list = cstates; list;) {
be035b
+		sdp_cont_info_t *cinfo = list->data;
be035b
+
be035b
+		list = list->next;
be035b
+
be035b
+		if (cinfo->sock != sock)
be035b
+			continue;
be035b
+
be035b
+		sdp_cont_info_free(cinfo);
be035b
+	}
be035b
+}
be035b
diff --git a/src/sdpd-server.c b/src/sdpd-server.c
be035b
index dfd8b1f00..66ee7ba14 100644
be035b
--- a/src/sdpd-server.c
be035b
+++ b/src/sdpd-server.c
be035b
@@ -146,16 +146,12 @@ static gboolean io_session_event(GIOChannel *chan, GIOCondition cond, gpointer d
be035b
 
be035b
 	sk = g_io_channel_unix_get_fd(chan);
be035b
 
be035b
-	if (cond & (G_IO_HUP | G_IO_ERR)) {
be035b
-		sdp_svcdb_collect_all(sk);
be035b
-		return FALSE;
be035b
-	}
be035b
+	if (cond & (G_IO_HUP | G_IO_ERR))
be035b
+		goto cleanup;
be035b
 
be035b
 	len = recv(sk, &hdr, sizeof(sdp_pdu_hdr_t), MSG_PEEK);
be035b
-	if (len < 0 || (unsigned int) len < sizeof(sdp_pdu_hdr_t)) {
be035b
-		sdp_svcdb_collect_all(sk);
be035b
-		return FALSE;
be035b
-	}
be035b
+	if (len < 0 || (unsigned int) len < sizeof(sdp_pdu_hdr_t))
be035b
+		goto cleanup;
be035b
 
be035b
 	size = sizeof(sdp_pdu_hdr_t) + ntohs(hdr.plen);
be035b
 	buf = malloc(size);
be035b
@@ -168,14 +164,18 @@ static gboolean io_session_event(GIOChannel *chan, GIOCondition cond, gpointer d
be035b
 	 * inside handle_request() in order to produce ErrorResponse.
be035b
 	 */
be035b
 	if (len <= 0) {
be035b
-		sdp_svcdb_collect_all(sk);
be035b
 		free(buf);
be035b
-		return FALSE;
be035b
+		goto cleanup;
be035b
 	}
be035b
 
be035b
 	handle_request(sk, buf, len);
be035b
 
be035b
 	return TRUE;
be035b
+
be035b
+cleanup:
be035b
+	sdp_svcdb_collect_all(sk);
be035b
+	sdp_cstate_cleanup(sk);
be035b
+	return FALSE;
be035b
 }
be035b
 
be035b
 static gboolean io_accept_event(GIOChannel *chan, GIOCondition cond, gpointer data)
be035b
diff --git a/src/sdpd.h b/src/sdpd.h
be035b
index 257411f03..4316aff67 100644
be035b
--- a/src/sdpd.h
be035b
+++ b/src/sdpd.h
be035b
@@ -27,8 +27,11 @@ typedef struct request {
be035b
 	int      flags;
be035b
 	uint8_t  *buf;
be035b
 	int      len;
be035b
+	uint8_t  opcode;
be035b
 } sdp_req_t;
be035b
 
be035b
+void sdp_cstate_cleanup(int sock);
be035b
+
be035b
 void handle_internal_request(int sk, int mtu, void *data, int len);
be035b
 void handle_request(int sk, uint8_t *data, int len);
be035b
 
be035b
diff --git a/unit/test-sdp.c b/unit/test-sdp.c
be035b
index d3a885f19..8f95fcb71 100644
be035b
--- a/unit/test-sdp.c
be035b
+++ b/unit/test-sdp.c
be035b
@@ -235,7 +235,7 @@ static gboolean client_handler(GIOChannel *channel, GIOCondition cond,
be035b
 	tester_monitor('>', 0x0000, 0x0001, buf, len);
be035b
 
be035b
 	g_assert(len > 0);
be035b
-	g_assert((size_t) len == rsp_pdu->raw_size + rsp_pdu->cont_len);
be035b
+	g_assert_cmpuint(len, ==, rsp_pdu->raw_size + rsp_pdu->cont_len);
be035b
 
be035b
 	g_assert(memcmp(buf, rsp_pdu->raw_data,	rsp_pdu->raw_size) == 0);
be035b
 
be035b
-- 
be035b
2.26.2
be035b