From: "Brandeburg, Jesse" <[EMAIL PROTECTED]>
Date: Wed, 29 Mar 2006 18:53:57 -0800
> To do this we have code like so in e1000_tso:
> 2529 if (skb_shinfo(skb)->tso_size) {
> 2530 if (skb_header_cloned(skb)) {
> 2531 err = pskb_expand_head(skb, 0, 0,
> GFP_ATOMIC);
> 2532 if (err)
> 2533 return err;
> 2534 }
I was wondering if that call could somehow mess up the
sk->sk_forward_alloc value later on.
But it can't, sk_forward_alloc is modified based upon the
skb->truesize value, but pskb_expand_head() does not change that.
So the things left to check in the generic networking are the
skb_shinfo() contents and ->dataref handling.
I considered whether pskb_expand_head() could corrupt the TSO
information in skb_shinfo(). But that's clearly not the case because
pskb_expand_head() explicitly copies it over:
memcpy(data + size, skb->end, sizeof(struct skb_shared_info));
And skb->end is set appropriately:
skb->end = data + size;
because skb_shinfo() is:
#define skb_shinfo(SKB) ((struct skb_shared_info *)((SKB)->end))
The only skb_shared_info that has to be explicitly setup is the
dataref, and pskb_expand_head() does that:
atomic_set(&skb_shinfo(skb)->dataref, 1);
So that all checks out.
I wonder if something funky is going on wrt. the skb_release_data()
that pskb_expand_head() does. We have that SKB_DATAREF_SHIFT thingy,
which will trigger in this case.
if (!skb->cloned ||
!atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 : 1,
&skb_shinfo(skb)->dataref)) {
When we enqueue a new TCP frame we do skb_header_release() which goes:
skb->nohdr = 1;
atomic_add(1 << SKB_DATAREF_SHIFT, &skb_shinfo(skb)->dataref);
Presumably the dataref is "1" already when we get here and do this.
We will clone, the clone will set ->nohdr to 0 and will increment the
dataref.
So at this point the dataref should be:
1 /* initial reference */
+ (1 << SKB_DATAREF_SHIFT) /* from skb_header_release() */
+ 1 /* from skb_clone */
This all works out because when the clone is freed up, skb->nohdr will
be zero, so we will subtract "1" from dataref. Later when the ACK
arrives we'll free up the non-clone and this will have skb->nohdr set
to "1" and thus we'll subtract
(1 << SKB_DATAREF_SHIFT) + 1
from dataref, as per skb_release_data().
Although maybe relevant here, I just noticed that __skb_linearize()
does not clear skb->nohdr. I bet that will cause a bunch of trouble
if the original SKB had skb->nohdr set, but I cannot see how that
can occur, we only send clones out to the device and those have
skb->nohdr clear (Herbert, double check this for me please).
Luckily that thing is used rarely. Only in the dev_queue_xmit()
path when the SKB has been configured in such a way that the
transmitting device does not support so it should not be relevant
here. Also I note that __skb_linearize() is not used at all
outside of net/core/dev.c, so we should mark it static some point
soon. In fact we should do that while fixing this fringe "nohdr"
bug in __skb_linearize().
All the other dataref accesses look safe.
Herbert do you see any holes here?
-
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at http://vger.kernel.org/majordomo-info.html