On Thu, Jun 21, 2018 at 07:46:12PM +0200, Sebastien Marie wrote:
> Hi,
>
> [email protected] has corrected an use-after-free on NetBSD on similar
> code we have.
>
> Fix use-after-free, m_cat can free m.
>
> http://cvsweb.netbsd.org/bsdweb.cgi/src/sys/net80211/ieee80211_input.c.diff?r1=1.111&r2=1.112
>
>
> >From code reading on us side, I think the same problem is present.
Yes, it is. More below.
> net80211/ieee80211_input.c
> 538 /*
> 539 * Handle defragmentation (see 9.5 and Annex C). We support the
> concurrent
> 540 * reception of fragments of three fragmented MSDUs or MMPDUs.
> 541 */
> 542 struct mbuf *
> 543 ieee80211_defrag(struct ieee80211com *ic, struct mbuf *m, int hdrlen)
> 544 {
> ...
> 597 /* strip 802.11 header and concatenate fragment */
> 598 m_adj(m, hdrlen);
> 599 m_cat(df->df_m, m);
> 600 df->df_m->m_pkthdr.len += m->m_pkthdr.len;
>
>
> the second argument of m_cat() `m' could be freed: m_cat() could call
> m_free() on it. so I assume it isn't safe to deference it to get
> `m_pkthdr.len`.
>
> Here m_cat() code:
>
> kern/uipc_mbuf.c
> 772 /*
> 773 * Concatenate mbuf chain n to m.
> 774 * n might be copied into m (when n->m_len is small), therefore data
> portion of
> 775 * n could be copied into an mbuf of different mbuf type.
> 776 * Therefore both chains should be of the same type (e.g. MT_DATA).
> 777 * Any m_pkthdr is not updated.
> 778 */
> 779 void
> 780 m_cat(struct mbuf *m, struct mbuf *n)
> 781 {
> 782 while (m->m_next)
> 783 m = m->m_next;
> 784 while (n) {
> 785 if (M_READONLY(m) || n->m_len > M_TRAILINGSPACE(m)) {
> 786 /* just join the two chains */
> 787 m->m_next = n;
> 788 return;
> 789 }
> 790 /* splat the data from one into the other */
> 791 memcpy(mtod(m, caddr_t) + m->m_len, mtod(n, caddr_t),
> 792 n->m_len);
> 793 m->m_len += n->m_len;
> 794 n = m_free(n);
> 795 }
> 796 }
>
>
> the NetBSD diff seems to mix 2 things. I understand the first (saving
> `m->m_pkthdr.len' in intermediate variable for adjust
> `df->df_m->m_pkthdr.len' later), but I am unsure to the second.
What the second part is about is that 'wh' equals 'm->m_data'.
There is another possibility for use-after-free by dereferencing
'wh' after 'm' was freed in case m->m_data equals m_ext.ext_buf.
For wireless frames in the Rx path this is very likely to be
the case, since e.g. iwm_rx_addbuf() requires M_EXT.
m_ext.ext_buf is freed in m_extfree(m) via m_free(m).
So I think we need to account for use-after-free via 'wh', too.
> The diff below contains only the mlen part.
> Index: net80211/ieee80211_input.c
> ===================================================================
> RCS file: /cvs/src/sys/net80211/ieee80211_input.c,v
> retrieving revision 1.201
> diff -u -p -r1.201 ieee80211_input.c
> --- net80211/ieee80211_input.c 5 May 2018 06:58:05 -0000 1.201
> +++ net80211/ieee80211_input.c 21 Jun 2018 17:37:41 -0000
> @@ -546,7 +546,7 @@ ieee80211_defrag(struct ieee80211com *ic
> struct ieee80211_defrag *df;
> u_int16_t rxseq, seq;
> u_int8_t frag;
> - int i;
> + int i, mlen;
>
> wh = mtod(m, struct ieee80211_frame *);
'wh' is assigned the value of 'm->m_data' here.
> rxseq = letoh16(*(const u_int16_t *)wh->i_seq);
> @@ -596,8 +596,9 @@ ieee80211_defrag(struct ieee80211com *ic
> df->df_frag = frag;
> /* strip 802.11 header and concatenate fragment */
> m_adj(m, hdrlen);
> + mlen = m->m_pkthdr.len;
> m_cat(df->df_m, m);
> - df->df_m->m_pkthdr.len += m->m_pkthdr.len;
> + df->df_m->m_pkthdr.len += mlen;
>
I think you should also do something about deferencing 'wh'
after m_cat() on this line:
> if (wh->i_fc[1] & IEEE80211_FC1_MORE_FRAG)
> return NULL; /* MSDU or MMPDU not yet complete */
>