Skip to content

Commit 211bfd2

Browse files
committed
ll: Fix use after free in ble_ll_isoal_mux_framed_event_done
mbuf needs to be removed from pkthdr list before being freed. Otherwise sdu_q list will operate on invalid data.
1 parent 6f36f8a commit 211bfd2

File tree

1 file changed

+59
-32
lines changed

1 file changed

+59
-32
lines changed

nimble/controller/src/ble_ll_isoal.c

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -174,20 +174,31 @@ ble_ll_isoal_mux_unframed_event_done(struct ble_ll_isoal_mux *mux)
174174
return pkt_freed;
175175
}
176176

177+
static struct os_mbuf *
178+
ble_ll_isoal_sdu_trim(struct os_mbuf *om, int *pkt_freed)
179+
{
180+
/* Count the number of packets that will be freed. */
181+
for (struct os_mbuf *mbuf = om; mbuf; mbuf = SLIST_NEXT(mbuf, om_next)) {
182+
if (mbuf->om_len == 0) {
183+
(*pkt_freed)++;
184+
}
185+
}
186+
187+
return os_mbuf_trim_front(om);
188+
}
189+
177190
static int
178191
ble_ll_isoal_mux_framed_event_done(struct ble_ll_isoal_mux *mux)
179192
{
180193
struct os_mbuf_pkthdr *pkthdr;
194+
struct os_mbuf_pkthdr *pkthdr_temp;
181195
struct os_mbuf *om;
182-
struct os_mbuf *om_next;
183196
uint8_t num_sdu;
184197
uint8_t num_pdu;
185-
uint8_t pdu_offset = 0;
198+
uint8_t pdu_len = 0;
186199
uint8_t frag_len = 0;
187-
uint8_t rem_len = 0;
188200
uint8_t hdr_len = 0;
189201
int pkt_freed = 0;
190-
bool sc = mux->sc;
191202
os_sr_t sr;
192203

193204
num_sdu = mux->sdu_in_event;
@@ -211,55 +222,71 @@ ble_ll_isoal_mux_framed_event_done(struct ble_ll_isoal_mux *mux)
211222
}
212223
#endif
213224

214-
/* Drop num_pdu PDUs */
215-
pkthdr = STAILQ_FIRST(&mux->sdu_q);
216-
while (pkthdr && num_sdu--) {
225+
/* Iterate over all queued SDUs. */
226+
STAILQ_FOREACH_SAFE(pkthdr, &mux->sdu_q, omp_next, pkthdr_temp) {
227+
if (num_sdu == 0) {
228+
break;
229+
}
230+
231+
/* Remove the SDU from the queue, as we are about to free some buffers from a chain.
232+
* Otherwise, it will result in invalid head pointed by sdu_q entry.
233+
*/
234+
OS_ENTER_CRITICAL(sr);
235+
STAILQ_REMOVE_HEAD(&mux->sdu_q, omp_next);
236+
BLE_LL_ASSERT(mux->sdu_q_len > 0);
237+
mux->sdu_q_len--;
238+
OS_EXIT_CRITICAL(sr);
239+
217240
om = OS_MBUF_PKTHDR_TO_MBUF(pkthdr);
218241

219-
while (om && num_pdu > 0) {
220-
rem_len = om->om_len;
221-
hdr_len = sc ? 2 /* Segmentation Header */
222-
: 5 /* Segmentation Header + TimeOffset */;
242+
/* Iterate over all buffers in the SDU chain. */
243+
while (num_pdu > 0 && OS_MBUF_PKTLEN(om) > 0) {
244+
hdr_len = mux->sc ? 2 /* Segmentation Header */
245+
: 5 /* Segmentation Header + TimeOffset */;
223246

224-
if (mux->max_pdu <= hdr_len + pdu_offset) {
225-
/* Advance to next PDU */
226-
pdu_offset = 0;
247+
/* If the next SDU fragment header size exceeds remaining space in the PDU, advance to next PDU. */
248+
if (mux->max_pdu <= hdr_len + pdu_len) {
249+
pdu_len = 0;
227250
num_pdu--;
228251
continue;
229252
}
230253

231-
frag_len = min(rem_len, mux->max_pdu - hdr_len - pdu_offset);
254+
/* "Put" the header in the PDU. */
255+
pdu_len += hdr_len;
256+
257+
/* SDU fragment length that can be fit in the PDU. */
258+
frag_len = min(OS_MBUF_PKTLEN(om), mux->max_pdu - pdu_len);
232259

233-
pdu_offset += hdr_len + frag_len;
260+
/* Increase PDU length by the length of the SDU fragment. */
261+
pdu_len += frag_len;
234262

235263
os_mbuf_adj(om, frag_len);
236264

237-
if (frag_len == rem_len) {
238-
om_next = SLIST_NEXT(om, om_next);
239-
os_mbuf_free(om);
240-
pkt_freed++;
241-
om = om_next;
265+
/* If the SDU fragment does not fit in the PDU, set the SC flag. */
266+
if (OS_MBUF_PKTLEN(om) > frag_len) {
267+
mux->sc = 1;
242268
} else {
243-
sc = 1;
269+
mux->sc = 0;
244270
}
245271
}
246272

273+
om = ble_ll_isoal_sdu_trim(om, &pkt_freed);
274+
if (OS_MBUF_PKTLEN(om) > 0) {
275+
/* If there is still data in the SDU chain, place the SDU back in the queue. */
276+
STAILQ_INSERT_HEAD(&mux->sdu_q, pkthdr, omp_next);
277+
mux->sdu_q_len++;
278+
} else {
279+
/* If there are no more data in the SDU chain, free the SDU chain. */
280+
os_mbuf_free_chain(om);
281+
num_sdu--;
282+
}
283+
247284
if (num_pdu == 0) {
248285
break;
249286
}
250-
251-
OS_ENTER_CRITICAL(sr);
252-
STAILQ_REMOVE_HEAD(&mux->sdu_q, omp_next);
253-
BLE_LL_ASSERT(mux->sdu_q_len > 0);
254-
mux->sdu_q_len--;
255-
OS_EXIT_CRITICAL(sr);
256-
257-
sc = 0;
258-
pkthdr = STAILQ_FIRST(&mux->sdu_q);
259287
}
260288

261289
mux->sdu_in_event = 0;
262-
mux->sc = sc;
263290

264291
return pkt_freed;
265292
}

0 commit comments

Comments
 (0)