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

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