Subject: Re: netipsec m_makespace() overrun
To: Sean Boudreau <seanb@qnx.com>
From: Arnaud Degroote <degroote@netbsd.org>
List: tech-net
Date: 12/14/2007 17:36:52
On Fri, Dec 14, 2007 at 10:38:03AM -0500, Sean Boudreau wrote:
> Hi:
> 
> It's pretty easy to tickle the
> IPSEC_ASSERT(remain < MLEN, ("m_makespace: remainder too big: %u", remain));
> in m_makespace().  If not running DIAGNOSTIC an memcpy()
> past a buffer occurs.  The following is more generic and
> handles this case.  Any comments before I commit?

The patch seems ok. Maybe we can be a bit smarter in case where 
hlen > M_TRAILINGSPACE(m) + remain. As we already need to allocate at
least one mbuf for remain, we may try to preserve some space for hlen if
we can't put in m after that. It can save one mbuf allocation in some cases. Not
sure it is really important in fact. 

It would be nice if the patch can be pulled-up in NetBSD-4 (or must we
wait for 4.1 ?).


> 
> Index: ipsec_mbuf.c
> ===================================================================
> RCS file: /cvsroot/src/sys/netipsec/ipsec_mbuf.c,v
> retrieving revision 1.9
> diff -U 10 -r1.9 ipsec_mbuf.c
> --- ipsec_mbuf.c	4 Mar 2007 19:54:48 -0000	1.9
> +++ ipsec_mbuf.c	14 Dec 2007 15:12:49 -0000
> @@ -231,81 +231,82 @@
>  	/*
>  	 * At this point skip is the offset into the mbuf m
>  	 * where the new header should be placed.  Figure out
>  	 * if there's space to insert the new header.  If so,
>  	 * and copying the remainder makese sense then do so.
>  	 * Otherwise insert a new mbuf in the chain, splitting
>  	 * the contents of m as needed.
>  	 */
>  	remain = m->m_len - skip;		/* data to move */
>  	if (hlen > M_TRAILINGSPACE(m)) {
> -		struct mbuf *n;
> +		struct mbuf *n0, *n, **np;
> +		int todo, len, done, alloc;
> +
> +		n0 = NULL;
> +		np = &n0;
> +		alloc = 0;
> +		done = 0;
> +		todo = remain;
> +		while (todo > 0) {
> +			if (todo > MHLEN) {
> +				n = m_getcl(M_DONTWAIT, m->m_type, 0);
> +				len = MCLBYTES;
> +			}
> +			else {
> +				n = m_get(M_DONTWAIT, m->m_type);
> +				len = MHLEN;
> +			}
> +			if (n == NULL) {
> +				m_freem(n0);
> +				return NULL;
> +			}
> +			*np = n;
> +			np = &n->m_next;
> +			alloc++;
> +			len = min(todo, len);
> +			memcpy(n->m_data, mtod(m, char *) + skip + done, len);
> +			n->m_len = len;
> +			done += len;
> +			todo -= len;
> +		}
>  
> -		/* XXX code doesn't handle clusters XXX */
> -		IPSEC_ASSERT(remain < MLEN,
> -			("m_makespace: remainder too big: %u", remain));
> -		/*
> -		 * Not enough space in m, split the contents
> -		 * of m, inserting new mbufs as required.
> -		 *
> -		 * NB: this ignores mbuf types.
> -		 */
> -		MGET(n, M_DONTWAIT, MT_DATA);
> -		if (n == NULL)
> -			return (NULL);
> -		n->m_next = m->m_next;		/* splice new mbuf */
> -		m->m_next = n;
> -		newipsecstat.ips_mbinserted++;
>  		if (hlen <= M_TRAILINGSPACE(m) + remain) {
> -			/*
> -			 * New header fits in the old mbuf if we copy
> -			 * the remainder; just do the copy to the new
> -			 * mbuf and we're good to go.
> -			 */
> -			memcpy(mtod(n, char *),
> -			       mtod(m, char *) + skip, remain);
> -			n->m_len = remain;
>  			m->m_len = skip + hlen;
>  			*off = skip;
> -		} else {
> -			/*
> -			 * No space in the old mbuf for the new header.
> -			 * Make space in the new mbuf and check the
> -			 * remainder'd data fits too.  If not then we
> -			 * must allocate an additional mbuf (yech).
> -			 */
> -			n->m_len = 0;
> -			if (remain + hlen > M_TRAILINGSPACE(n)) {
> -				struct mbuf *n2;
> -
> -				MGET(n2, M_DONTWAIT, MT_DATA);
> -				/* NB: new mbuf is on chain, let caller free */
> -				if (n2 == NULL)
> -					return (NULL);
> -				n2->m_len = 0;
> -				memcpy(mtod(n2, char *),
> -				       mtod(m, char *) + skip, remain);
> -				n2->m_len = remain;
> -				/* splice in second mbuf */
> -				n2->m_next = n->m_next;
> -				n->m_next = n2;
> -				newipsecstat.ips_mbinserted++;
> -			} else {
> -				memcpy(mtod(n, char *) + hlen,
> -				       mtod(m, char *) + skip, remain);
> -				n->m_len += remain;
> +			if (n0 != NULL) {
> +				*np = m->m_next;
> +				m->m_next = n0;
>  			}
> -			m->m_len -= remain;
> -			n->m_len += hlen;
> +		}
> +		else {
> +			n = m_get(M_DONTWAIT, m->m_type);
> +			if (n == NULL) {
> +				m_freem(n0);
> +				return NULL;
> +			}
> +			alloc++;
> +
> +			if ((n->m_next = n0) == NULL)
> +				np = &n->m_next;
> +			n0 = n;
> +
> +			*np = m->m_next;
> +			m->m_next = n0;
> +
> +			n->m_len = hlen;
> +			m->m_len = skip;
> +
>  			m = n;			/* header is at front ... */
>  			*off = 0;		/* ... of new mbuf */
>  		}
> +
> +		newipsecstat.ips_mbinserted += alloc;
>  	} else {
>  		/*
>  		 * Copy the remainder to the back of the mbuf
>  		 * so there's space to write the new header.
>  		 */
>  		/* XXX can this be memcpy? does it handle overlap? */
>  		ovbcopy(mtod(m, char *) + skip,
>  			mtod(m, char *) + skip + hlen, remain);
>  		m->m_len += hlen;
>  		*off = skip;
> 

-- 
Arnaud Degroote
degroote@netbsd.org