Subject: Re: crashes in ipfilter on i386
To: Greg Troxel <gdt@ir.bbn.com>
From: Darren Reed <darrenr@netbsd.org>
List: tech-net
Date: 09/15/2007 07:21:47
Greg Troxel wrote:
> So what I don't get is why
>
> 			if (frpr_pullup(fin, ICMP6ERR_MINPKTLEN) == -1)
> 				return;
>
> is adequate, since I think we need ICMP6ERR_IPICMPHLEN to get the
> included header.  But I do not understand how frpr_pullup and the data
> pointer interact.

If we look in frpr_pullup(), we see:
static INLINE int frpr_pullup(fin, plen)
fr_info_t *fin;
int plen;
{
        if (fin->fin_m != NULL) {
                if (fin->fin_dp != NULL)
                        plen += (char *)fin->fin_dp -
                                ((char *)fin->fin_ip + fin->fin_hlen);
                plen += fin->fin_hlen;
                if (M_LEN(fin->fin_m) < plen) {
                        if (fr_pullup(fin->fin_m, fin, plen) == NULL)
                                return -1;
                }
        }
        return 0;
}

fin_dp is set early in fr_makefrip():
        fin->fin_dp = (char *)ip + hlen;
...
        } else if (v == 6) {
                fin->fin_plen = ntohs(((ip6_t *)ip)->ip6_plen);
                fin->fin_dlen = fin->fin_plen;
                fin->fin_plen += hlen;

                if (frpr_ipv6hdr(fin) == -1)
                        return -1;
...

In this case hlen will be 40 (sizeof(ip6_t)).
So if the payload length of the IPv6 header says 64 bytes,
fin_plen because 64+40=104.

In frpr_icmp6(), we do:

static INLINE void frpr_icmp6(fin)     
fr_info_t *fin;
{
        int minicmpsz = sizeof(struct icmp6_hdr);
        struct icmp6_hdr *icmp6; 
                       
        if (frpr_pullup(fin, ICMP6ERR_MINPKTLEN - sizeof(ip6_t)) == -1)
                return;      

        if (fin->fin_dlen > 1) {     
                ip6_t *ip6;
                             
                icmp6 = fin->fin_dp;
...
                        if ((fin->fin_m != NULL) &&
                            (M_LEN(fin->fin_m) < fin->fin_plen)) {
                                if (fr_coalesce(fin) != 1)
                                        return;
                        }     
***                     if (frpr_pullup(fin, ICMP6ERR_MINPKTLEN) == -1)
                                return;

                        /*
                         * If the destination of this packet doesn't 
match the
                         * source of the original packet then this packet is
                         * not correct.
                         */
                        icmp6 = fin->fin_dp;
                        ip6 = (ip6_t *)((char *)icmp6 + ICMPERR_ICMPHLEN);
                        if (IP6_NEQ(&fin->fin_fi.fi_dst,
                                    (i6addr_t *)&ip6->ip6_src))
                                fin->fin_flx |= FI_BAD;


The call to frpr_pullup() here is redundant - the call to fr_coalesce()
before it should have put everything in a single buffer.

int fr_coalesce(fin)
fr_info_t *fin;
{
        if ((fin->fin_flx & FI_COALESCE) != 0)
                return 1;     
                        
        /*
         * If the mbuf pointers indicate that there is no mbuf to work with,
         * return but do not indicate success or failure.
         */
        if (fin->fin_m == NULL || fin->fin_mp == NULL)
                return 0;
                         
        if (fr_pullup(fin->fin_m, fin, fin->fin_plen) == NULL) {
                ATOMIC_INCL(fr_badcoalesces[fin->fin_out]);
                *fin->fin_mp = NULL;
                fin->fin_m = NULL;
                return -1;        
        }
        return 1;
}

So a flag is set to prevent it from being called more than once.
Looking at fr_pullup(), there's nothing too surprising: fin_ip
and fin_dp are updated accordinly.

void *fr_pullup(xmin, fin, len)
mb_t *xmin;
fr_info_t *fin;
int len;
{
        int out = fin->fin_out, dpoff, ipoff;
        mb_t *m = xmin;
        char *ip;

        if (m == NULL)
                return NULL;

        ip = (char *)fin->fin_ip;
        if ((fin->fin_flx & FI_COALESCE) != 0)
                return ip;

        ipoff = fin->fin_ipoff;
        if (fin->fin_dp != NULL)
                dpoff = (char *)fin->fin_dp - (char *)ip;
        else
                dpoff = 0;

        if (M_LEN(m) < len) {
#ifdef MHLEN
                /*
                 * Assume that M_PKTHDR is set and just work with what 
is left
                 * rather than check..
                 * Should not make any real difference, anyway.
                 */
                if (len > MHLEN)
#else
                if (len > MLEN)
#endif
                {
#ifdef HAVE_M_PULLDOWN
                        if (m_pulldown(m, 0, len, NULL) == NULL)
                                m = NULL;
#else
                        FREE_MB_T(*fin->fin_mp);
                        m = NULL;
#endif
                } else
                {
                        m = m_pullup(m, len);
                }
                *fin->fin_mp = m;
                fin->fin_m = m;
                if (m == NULL) {
                        ATOMIC_INCL(frstats[out].fr_pull[1]);
                        return NULL;
                }
                ip = MTOD(m, char *) + ipoff;
        }

        ATOMIC_INCL(frstats[out].fr_pull[0]);
        fin->fin_ip = (ip_t *)ip;
        if (fin->fin_dp != NULL)
                fin->fin_dp = (char *)fin->fin_ip + dpoff;

        if (len == fin->fin_plen)
                fin->fin_flx |= FI_COALESCE;
        return ip;
}

Or is there something I'm missing?

Darren