On Thu, Jun 21, 2018 at 07:46:12PM +0200, Sebastien Marie wrote:
> Hi,
> 
> m...@netbsd.org 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 */
> 

Reply via email to