Skip to content

Commit 7f62db6

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 7f62db6

File tree

1 file changed

+62
-32
lines changed

1 file changed

+62
-32
lines changed

nimble/controller/src/ble_ll_isoal.c

Lines changed: 62 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,74 @@ 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+
BLE_LL_ASSERT(mux->sdu_q_len > 0);
232+
233+
/* Remove the SDU from the queue, as we are about to free some buffers from a chain.
234+
* Otherwise, it will result in invalid head pointed by sdu_q entry.
235+
*/
236+
OS_ENTER_CRITICAL(sr);
237+
STAILQ_REMOVE_HEAD(&mux->sdu_q, omp_next);
238+
mux->sdu_q_len--;
239+
OS_EXIT_CRITICAL(sr);
240+
217241
om = OS_MBUF_PKTHDR_TO_MBUF(pkthdr);
218242

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

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

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

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

235264
os_mbuf_adj(om, frag_len);
236265

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;
266+
/* If the SDU fragment does not fit in the PDU, set the SC flag. */
267+
if (OS_MBUF_PKTLEN(om) > frag_len) {
268+
mux->sc = 1;
242269
} else {
243-
sc = 1;
270+
mux->sc = 0;
244271
}
245272
}
246273

274+
om = ble_ll_isoal_sdu_trim(om, &pkt_freed);
275+
if (OS_MBUF_PKTLEN(om) > 0) {
276+
/* If there is still data in the SDU chain, place the SDU back in the queue. */
277+
OS_ENTER_CRITICAL(sr);
278+
STAILQ_INSERT_HEAD(&mux->sdu_q, pkthdr, omp_next);
279+
mux->sdu_q_len++;
280+
OS_EXIT_CRITICAL(sr);
281+
} else {
282+
/* If there are no more data in the SDU chain, free the SDU chain. */
283+
os_mbuf_free_chain(om);
284+
num_sdu--;
285+
}
286+
247287
if (num_pdu == 0) {
248288
break;
249289
}
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);
259290
}
260291

261292
mux->sdu_in_event = 0;
262-
mux->sc = sc;
263293

264294
return pkt_freed;
265295
}

0 commit comments

Comments
 (0)