fix: handle NULL return of as_dl_tbf() and as_ul_tbf()

Go through all callers of as_dl_tbf() and as_ul_tbf(), and make sure
they can handle the possible NULL return value.

OS#5205 reports a NULL deref crash of osmo-pcu at pdch.cpp:525. The
immediate cause is that as_dl_tbf() may well return NULL, which this
caller does not handle and instead dereferences immediately.
This is a code path that apparently assumes that a DL-TBF should always
be present. The higher level cause for the NULL DL-TBF has not been
identified.

Related: OS#5205 SYS#5561
Change-Id: I8ce21be6836549b47a606c00b793d6f005964c5c
This commit is contained in:
Neels Hofmeyr 2021-08-09 18:30:23 +02:00 committed by laforge
parent 112c63e9b4
commit d8e8ea9c8f
6 changed files with 57 additions and 37 deletions

View File

@ -1162,22 +1162,21 @@ void bts_update_tbf_ta(struct gprs_rlcmac_bts *bts, const char *p, uint32_t fn,
{ {
struct gprs_rlcmac_pdch *pdch = &bts->trx[trx_no].pdch[ts]; struct gprs_rlcmac_pdch *pdch = &bts->trx[trx_no].pdch[ts];
struct pdch_ulc_node *poll = pdch_ulc_get_node(pdch->ulc, fn); struct pdch_ulc_node *poll = pdch_ulc_get_node(pdch->ulc, fn);
struct gprs_rlcmac_ul_tbf *tbf; struct gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(poll->tbf_poll.poll_tbf);
if (!poll || poll->type !=PDCH_ULC_NODE_TBF_POLL || if (!poll || poll->type !=PDCH_ULC_NODE_TBF_POLL ||
poll->tbf_poll.poll_tbf->direction != GPRS_RLCMAC_UL_TBF) poll->tbf_poll.poll_tbf->direction != GPRS_RLCMAC_UL_TBF)
LOGP(DL1IF, LOGL_DEBUG, "[%s] update TA = %u ignored due to " LOGP(DL1IF, LOGL_DEBUG, "[%s] update TA = %u ignored due to "
"unknown UL TBF on TRX = %d, TS = %d, FN = %d\n", "unknown UL TBF on TRX = %d, TS = %d, FN = %d\n",
p, ta, trx_no, ts, fn); p, ta, trx_no, ts, fn);
else { else if (ul_tbf) {
tbf = as_ul_tbf(poll->tbf_poll.poll_tbf);
/* we need to distinguish TA information provided by L1 /* we need to distinguish TA information provided by L1
* from PH-DATA-IND and PHY-RA-IND so that we can properly * from PH-DATA-IND and PHY-RA-IND so that we can properly
* update TA for given TBF * update TA for given TBF
*/ */
if (is_rach) if (is_rach)
set_tbf_ta(tbf, (uint8_t)ta); set_tbf_ta(ul_tbf, (uint8_t)ta);
else else
update_tbf_ta(tbf, ta); update_tbf_ta(ul_tbf, ta);
} }
} }

View File

@ -335,9 +335,13 @@ static void ms_attach_dl_tbf(struct GprsMs *ms, struct gprs_rlcmac_dl_tbf *tbf)
void ms_attach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf) void ms_attach_tbf(struct GprsMs *ms, struct gprs_rlcmac_tbf *tbf)
{ {
if (tbf_direction(tbf) == GPRS_RLCMAC_DL_TBF) struct gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf);
struct gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf);
/* cannot be both DL and UL */
OSMO_ASSERT(!(dl_tbf && ul_tbf));
if (dl_tbf)
ms_attach_dl_tbf(ms, as_dl_tbf(tbf)); ms_attach_dl_tbf(ms, as_dl_tbf(tbf));
else if (ul_tbf)
ms_attach_ul_tbf(ms, as_ul_tbf(tbf)); ms_attach_ul_tbf(ms, as_ul_tbf(tbf));
} }

View File

@ -50,7 +50,8 @@ static void get_ctrl_msg_tbf_candidates(const struct gprs_rlcmac_pdch *pdch,
llist_for_each_entry(pos, &pdch->trx->ul_tbfs, list) { llist_for_each_entry(pos, &pdch->trx->ul_tbfs, list) {
ul_tbf = as_ul_tbf((struct gprs_rlcmac_tbf *)pos->entry); ul_tbf = as_ul_tbf((struct gprs_rlcmac_tbf *)pos->entry);
OSMO_ASSERT(ul_tbf); if (!ul_tbf)
continue;
/* this trx, this ts */ /* this trx, this ts */
if (!ul_tbf->is_control_ts(pdch->ts_no)) if (!ul_tbf->is_control_ts(pdch->ts_no))
continue; continue;
@ -69,7 +70,8 @@ states? */
} }
llist_for_each_entry(pos, &pdch->trx->dl_tbfs, list) { llist_for_each_entry(pos, &pdch->trx->dl_tbfs, list) {
dl_tbf = as_dl_tbf((struct gprs_rlcmac_tbf *)pos->entry); dl_tbf = as_dl_tbf((struct gprs_rlcmac_tbf *)pos->entry);
OSMO_ASSERT(dl_tbf); if (!dl_tbf)
continue;
/* this trx, this ts */ /* this trx, this ts */
if (!dl_tbf->is_control_ts(pdch->ts_no)) if (!dl_tbf->is_control_ts(pdch->ts_no))
continue; continue;
@ -459,6 +461,7 @@ int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts,
"single block allocation at FN=%d\n", fn, block_nr, sba->fn); "single block allocation at FN=%d\n", fn, block_nr, sba->fn);
/* else, check uplink resource for polling */ /* else, check uplink resource for polling */
} else if ((poll_tbf = pdch_ulc_get_tbf_poll(pdch->ulc, poll_fn))) { } else if ((poll_tbf = pdch_ulc_get_tbf_poll(pdch->ulc, poll_fn))) {
struct gprs_rlcmac_ul_tbf *ul_tbf;
LOGPDCH(pdch, DRLCMACSCHED, LOGL_DEBUG, "Received RTS for PDCH: FN=%d " LOGPDCH(pdch, DRLCMACSCHED, LOGL_DEBUG, "Received RTS for PDCH: FN=%d "
"block_nr=%d scheduling free USF for polling at FN=%d of %s\n", "block_nr=%d scheduling free USF for polling at FN=%d of %s\n",
fn, block_nr, poll_fn, tbf_name(poll_tbf)); fn, block_nr, poll_fn, tbf_name(poll_tbf));
@ -466,8 +469,9 @@ int gprs_rlcmac_rcv_rts_block(struct gprs_rlcmac_bts *bts,
* let's set its USF in the DL msg. This is not really needed, * let's set its USF in the DL msg. This is not really needed,
* but it helps understand better the flow when looking at * but it helps understand better the flow when looking at
* pcaps. */ * pcaps. */
if (poll_tbf->direction == GPRS_RLCMAC_UL_TBF && as_ul_tbf(poll_tbf)->m_usf[ts] != USF_INVALID) ul_tbf = as_ul_tbf(poll_tbf);
usf_tbf = as_ul_tbf(poll_tbf); if (ul_tbf && poll_tbf->direction == GPRS_RLCMAC_UL_TBF && ul_tbf->m_usf[ts] != USF_INVALID)
usf_tbf = ul_tbf;
/* else, search for uplink tbf */ /* else, search for uplink tbf */
} else if ((usf_tbf = sched_select_uplink(pdch, require_gprs_only))) { } else if ((usf_tbf = sched_select_uplink(pdch, require_gprs_only))) {
LOGPDCH(pdch, DRLCMACSCHED, LOGL_DEBUG, "Received RTS for PDCH: FN=%d " LOGPDCH(pdch, DRLCMACSCHED, LOGL_DEBUG, "Received RTS for PDCH: FN=%d "

View File

@ -357,6 +357,8 @@ int alloc_algorithm_a(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf_,
struct GprsMs *ms = tbf_->ms(); struct GprsMs *ms = tbf_->ms();
const gprs_rlcmac_tbf *tbf = tbf_; const gprs_rlcmac_tbf *tbf = tbf_;
gprs_rlcmac_trx *trx = ms_current_trx(ms); gprs_rlcmac_trx *trx = ms_current_trx(ms);
struct gprs_rlcmac_dl_tbf *dl_tbf;
struct gprs_rlcmac_ul_tbf *ul_tbf;
LOGPAL(tbf, "A", single, use_trx, LOGL_DEBUG, "Alloc start\n"); LOGPAL(tbf, "A", single, use_trx, LOGL_DEBUG, "Alloc start\n");
@ -406,12 +408,15 @@ int alloc_algorithm_a(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf_,
/* The allocation will be successful, so the system state and tbf_/ms_ /* The allocation will be successful, so the system state and tbf_/ms_
* may be modified from now on. */ * may be modified from now on. */
if (tbf->direction == GPRS_RLCMAC_UL_TBF) { dl_tbf = as_dl_tbf(tbf_);
struct gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf_); ul_tbf = as_ul_tbf(tbf_);
/* cannot be both DL and UL */
OSMO_ASSERT(!(dl_tbf && ul_tbf));
if (ul_tbf) {
LOGPSL(tbf, LOGL_DEBUG, "Assign uplink TS=%d TFI=%d USF=%d\n", ts, tfi, usf); LOGPSL(tbf, LOGL_DEBUG, "Assign uplink TS=%d TFI=%d USF=%d\n", ts, tfi, usf);
assign_uplink_tbf_usf(pdch, ul_tbf, tfi, usf); assign_uplink_tbf_usf(pdch, ul_tbf, tfi, usf);
} else { }
struct gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf_); if (dl_tbf) {
LOGPSL(tbf, LOGL_DEBUG, "Assign downlink TS=%d TFI=%d\n", ts, tfi); LOGPSL(tbf, LOGL_DEBUG, "Assign downlink TS=%d TFI=%d\n", ts, tfi);
assign_dlink_tbf(pdch, dl_tbf, tfi); assign_dlink_tbf(pdch, dl_tbf, tfi);
} }
@ -878,6 +883,8 @@ int alloc_algorithm_b(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf_,
struct GprsMs *ms = tbf_->ms(); struct GprsMs *ms = tbf_->ms();
const gprs_rlcmac_tbf *tbf = tbf_; const gprs_rlcmac_tbf *tbf = tbf_;
gprs_rlcmac_trx *trx; gprs_rlcmac_trx *trx;
struct gprs_rlcmac_dl_tbf *dl_tbf;
struct gprs_rlcmac_ul_tbf *ul_tbf;
LOGPAL(tbf, "B", single, use_trx, LOGL_DEBUG, "Alloc start\n"); LOGPAL(tbf, "B", single, use_trx, LOGL_DEBUG, "Alloc start\n");
@ -960,10 +967,14 @@ int alloc_algorithm_b(struct gprs_rlcmac_bts *bts, struct gprs_rlcmac_tbf *tbf_,
tbf_->first_common_ts = first_common_ts; tbf_->first_common_ts = first_common_ts;
tbf_->first_ts = first_ts; tbf_->first_ts = first_ts;
if (tbf->direction == GPRS_RLCMAC_DL_TBF) dl_tbf = as_dl_tbf(tbf_);
assign_dl_tbf_slots(as_dl_tbf(tbf_), trx, dl_slots, tfi); ul_tbf = as_ul_tbf(tbf_);
else /* cannot be both DL and UL */
assign_ul_tbf_slots(as_ul_tbf(tbf_), trx, ul_slots, tfi, usf); OSMO_ASSERT(!(dl_tbf && ul_tbf));
if (dl_tbf)
assign_dl_tbf_slots(dl_tbf, trx, dl_slots, tfi);
if (ul_tbf)
assign_ul_tbf_slots(ul_tbf, trx, ul_slots, tfi, usf);
bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_ALGO_B); bts_do_rate_ctr_inc(bts, CTR_TBF_ALLOC_ALGO_B);

View File

@ -452,7 +452,7 @@ void gprs_rlcmac_pdch::rcv_control_dl_ack_nack(Packet_Downlink_Ack_Nack_t *ack_n
return; return;
} }
tbf = as_dl_tbf(poll->tbf_poll.poll_tbf); tbf = as_dl_tbf(poll->tbf_poll.poll_tbf);
if (tbf->tfi() != tfi) { if (!tbf || tbf->tfi() != tfi) {
LOGPTBFDL(tbf, LOGL_NOTICE, LOGPTBFDL(tbf, LOGL_NOTICE,
"PACKET DOWNLINK ACK with wrong TFI=%d, ignoring!\n", tfi); "PACKET DOWNLINK ACK with wrong TFI=%d, ignoring!\n", tfi);
return; return;
@ -522,7 +522,7 @@ void gprs_rlcmac_pdch::rcv_control_egprs_dl_ack_nack(EGPRS_PD_AckNack_t *ack_nac
return; return;
} }
tbf = as_dl_tbf(poll->tbf_poll.poll_tbf); tbf = as_dl_tbf(poll->tbf_poll.poll_tbf);
if (tbf->tfi() != tfi) { if (!tbf || tbf->tfi() != tfi) {
LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "EGPRS PACKET DOWNLINK ACK with " LOGPDCH(this, DRLCMAC, LOGL_NOTICE, "EGPRS PACKET DOWNLINK ACK with "
"wrong TFI=%d, ignoring!\n", tfi); "wrong TFI=%d, ignoring!\n", tfi);
return; return;
@ -1063,8 +1063,8 @@ void gprs_rlcmac_pdch::attach_tbf(gprs_rlcmac_tbf *tbf)
m_tbfs[tbf->direction][tbf->tfi()]->name()); m_tbfs[tbf->direction][tbf->tfi()]->name());
m_num_tbfs[tbf->direction] += 1; m_num_tbfs[tbf->direction] += 1;
if (tbf->direction == GPRS_RLCMAC_UL_TBF) { ul_tbf = as_ul_tbf(tbf);
ul_tbf = as_ul_tbf(tbf); if (ul_tbf) {
m_assigned_usf |= 1 << ul_tbf->m_usf[ts_no]; m_assigned_usf |= 1 << ul_tbf->m_usf[ts_no];
} }
m_assigned_tfi[tbf->direction] |= 1UL << tbf->tfi(); m_assigned_tfi[tbf->direction] |= 1UL << tbf->tfi();
@ -1083,8 +1083,8 @@ void gprs_rlcmac_pdch::detach_tbf(gprs_rlcmac_tbf *tbf)
OSMO_ASSERT(m_num_tbfs[tbf->direction] > 0); OSMO_ASSERT(m_num_tbfs[tbf->direction] > 0);
m_num_tbfs[tbf->direction] -= 1; m_num_tbfs[tbf->direction] -= 1;
if (tbf->direction == GPRS_RLCMAC_UL_TBF) { ul_tbf = as_ul_tbf(tbf);
ul_tbf = as_ul_tbf(tbf); if (ul_tbf) {
m_assigned_usf &= ~(1 << ul_tbf->m_usf[ts_no]); m_assigned_usf &= ~(1 << ul_tbf->m_usf[ts_no]);
} }
m_assigned_tfi[tbf->direction] &= ~(1UL << tbf->tfi()); m_assigned_tfi[tbf->direction] &= ~(1UL << tbf->tfi());

View File

@ -270,15 +270,18 @@ static void tbf_unlink_pdch(struct gprs_rlcmac_tbf *tbf)
void tbf_free(struct gprs_rlcmac_tbf *tbf) void tbf_free(struct gprs_rlcmac_tbf *tbf)
{ {
/* update counters */ /* update counters */
if (tbf->direction == GPRS_RLCMAC_UL_TBF) { gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf);
gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf); gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(tbf);
/* cannot be both DL and UL */
OSMO_ASSERT(!(dl_tbf && ul_tbf));
if (ul_tbf) {
bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_FREED); bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_FREED);
if (tbf->state_is(TBF_ST_FLOW)) if (tbf->state_is(TBF_ST_FLOW))
bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_ABORTED); bts_do_rate_ctr_inc(tbf->bts, CTR_TBF_UL_ABORTED);
rate_ctr_group_free(ul_tbf->m_ul_egprs_ctrs); rate_ctr_group_free(ul_tbf->m_ul_egprs_ctrs);
rate_ctr_group_free(ul_tbf->m_ul_gprs_ctrs); rate_ctr_group_free(ul_tbf->m_ul_gprs_ctrs);
} else { }
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf); if (dl_tbf) {
if (tbf->is_egprs_enabled()) { if (tbf->is_egprs_enabled()) {
rate_ctr_group_free(dl_tbf->m_dl_egprs_ctrs); rate_ctr_group_free(dl_tbf->m_dl_egprs_ctrs);
} else { } else {
@ -291,9 +294,7 @@ void tbf_free(struct gprs_rlcmac_tbf *tbf)
/* Give final measurement report */ /* Give final measurement report */
gprs_rlcmac_rssi_rep(tbf); gprs_rlcmac_rssi_rep(tbf);
if (tbf->direction == GPRS_RLCMAC_DL_TBF) { if (dl_tbf) {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(tbf);
dl_tbf->abort(); dl_tbf->abort();
dl_tbf->cleanup(); dl_tbf->cleanup();
} }
@ -623,7 +624,10 @@ void gprs_rlcmac_tbf::set_polling(uint32_t new_poll_fn, uint8_t ts, enum pdch_ul
void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason) void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_fn, enum pdch_ulc_tbf_poll_reason reason)
{ {
uint16_t pgroup; uint16_t pgroup;
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this);
gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(this); gprs_rlcmac_ul_tbf *ul_tbf = as_ul_tbf(this);
/* cannot be both DL and UL */
OSMO_ASSERT(!(dl_tbf && ul_tbf));
LOGPTBF(this, LOGL_NOTICE, "poll timeout for FN=%d, TS=%d (curr FN %d)\n", LOGPTBF(this, LOGL_NOTICE, "poll timeout for FN=%d, TS=%d (curr FN %d)\n",
poll_fn, pdch->ts_no, bts_current_frame_number(bts)); poll_fn, pdch->ts_no, bts_current_frame_number(bts));
@ -690,9 +694,7 @@ void gprs_rlcmac_tbf::poll_timeout(struct gprs_rlcmac_pdch *pdch, uint32_t poll_
/* Timeout waiting for CTRL ACK acking Pkt Cell Change Continue */ /* Timeout waiting for CTRL ACK acking Pkt Cell Change Continue */
osmo_fsm_inst_dispatch(m_ms->nacc->fi, NACC_EV_TIMEOUT_CELL_CHG_CONTINUE, NULL); osmo_fsm_inst_dispatch(m_ms->nacc->fi, NACC_EV_TIMEOUT_CELL_CHG_CONTINUE, NULL);
return; return;
} else if (direction == GPRS_RLCMAC_DL_TBF) { } else if (dl_tbf) {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this);
if (!(dl_tbf->state_flags & (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK))) { if (!(dl_tbf->state_flags & (1 << GPRS_RLCMAC_FLAG_TO_DL_ACK))) {
LOGPTBF(this, LOGL_NOTICE, LOGPTBF(this, LOGL_NOTICE,
"Timeout for polling PACKET DOWNLINK ACK: %s\n", "Timeout for polling PACKET DOWNLINK ACK: %s\n",
@ -784,6 +786,7 @@ static void tbf_timer_cb(void *_tbf)
void gprs_rlcmac_tbf::handle_timeout() void gprs_rlcmac_tbf::handle_timeout()
{ {
int current_fn = bts_current_frame_number(bts); int current_fn = bts_current_frame_number(bts);
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this);
LOGPTBF(this, LOGL_DEBUG, "timer 0 expired. cur_fn=%d\n", current_fn); LOGPTBF(this, LOGL_DEBUG, "timer 0 expired. cur_fn=%d\n", current_fn);
@ -798,8 +801,7 @@ void gprs_rlcmac_tbf::handle_timeout()
} }
/* Finish waiting after IMM.ASS confirm timer for CCCH assignment (see timer X2002) */ /* Finish waiting after IMM.ASS confirm timer for CCCH assignment (see timer X2002) */
if ((state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))) { if (dl_tbf && (state_flags & (1 << GPRS_RLCMAC_FLAG_CCCH))) {
gprs_rlcmac_dl_tbf *dl_tbf = as_dl_tbf(this);
dl_tbf->m_wait_confirm = 0; dl_tbf->m_wait_confirm = 0;
if (dl_tbf->state_is(TBF_ST_ASSIGN)) { if (dl_tbf->state_is(TBF_ST_ASSIGN)) {
tbf_assign_control_ts(dl_tbf); tbf_assign_control_ts(dl_tbf);