Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a8db7b2d authored by Florian Westphal's avatar Florian Westphal Committed by Pablo Neira Ayuso
Browse files

netfilter: nf_queue: fix queueing of bridged gro skbs



When trying to nf_queue GRO/GSO skbs, nf_queue uses skb_gso_segment
to split the skb.

However, if nf_queue is called via bridge netfilter, the mac header
won't be preserved -- packets will thus contain a bogus mac header.

Fix this by setting skb->data to the mac header when skb->nf_bridge
is set and restoring skb->data afterwards for all segments.

Signed-off-by: default avatarFlorian Westphal <fw@strlen.de>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
parent e0aac52e
Loading
Loading
Loading
Loading
+32 −8
Original line number Original line Diff line number Diff line
@@ -203,6 +203,27 @@ static int __nf_queue(struct sk_buff *skb,
	return status;
	return status;
}
}


#ifdef CONFIG_BRIDGE_NETFILTER
/* When called from bridge netfilter, skb->data must point to MAC header
 * before calling skb_gso_segment(). Else, original MAC header is lost
 * and segmented skbs will be sent to wrong destination.
 */
static void nf_bridge_adjust_skb_data(struct sk_buff *skb)
{
	if (skb->nf_bridge)
		__skb_push(skb, skb->network_header - skb->mac_header);
}

static void nf_bridge_adjust_segmented_data(struct sk_buff *skb)
{
	if (skb->nf_bridge)
		__skb_pull(skb, skb->network_header - skb->mac_header);
}
#else
#define nf_bridge_adjust_skb_data(s) do {} while (0)
#define nf_bridge_adjust_segmented_data(s) do {} while (0)
#endif

int nf_queue(struct sk_buff *skb,
int nf_queue(struct sk_buff *skb,
	     struct list_head *elem,
	     struct list_head *elem,
	     u_int8_t pf, unsigned int hook,
	     u_int8_t pf, unsigned int hook,
@@ -212,7 +233,7 @@ int nf_queue(struct sk_buff *skb,
	     unsigned int queuenum)
	     unsigned int queuenum)
{
{
	struct sk_buff *segs;
	struct sk_buff *segs;
	int err;
	int err = -EINVAL;
	unsigned int queued;
	unsigned int queued;


	if (!skb_is_gso(skb))
	if (!skb_is_gso(skb))
@@ -228,23 +249,25 @@ int nf_queue(struct sk_buff *skb,
		break;
		break;
	}
	}


	nf_bridge_adjust_skb_data(skb);
	segs = skb_gso_segment(skb, 0);
	segs = skb_gso_segment(skb, 0);
	/* Does not use PTR_ERR to limit the number of error codes that can be
	/* Does not use PTR_ERR to limit the number of error codes that can be
	 * returned by nf_queue.  For instance, callers rely on -ECANCELED to mean
	 * returned by nf_queue.  For instance, callers rely on -ECANCELED to mean
	 * 'ignore this hook'.
	 * 'ignore this hook'.
	 */
	 */
	if (IS_ERR(segs))
	if (IS_ERR(segs))
		return -EINVAL;
		goto out_err;

	queued = 0;
	queued = 0;
	err = 0;
	err = 0;
	do {
	do {
		struct sk_buff *nskb = segs->next;
		struct sk_buff *nskb = segs->next;


		segs->next = NULL;
		segs->next = NULL;
		if (err == 0)
		if (err == 0) {
			nf_bridge_adjust_segmented_data(segs);
			err = __nf_queue(segs, elem, pf, hook, indev,
			err = __nf_queue(segs, elem, pf, hook, indev,
					   outdev, okfn, queuenum);
					   outdev, okfn, queuenum);
		}
		if (err == 0)
		if (err == 0)
			queued++;
			queued++;
		else
		else
@@ -252,11 +275,12 @@ int nf_queue(struct sk_buff *skb,
		segs = nskb;
		segs = nskb;
	} while (segs);
	} while (segs);


	/* also free orig skb if only some segments were queued */
	if (queued) {
	if (unlikely(err && queued))
		err = 0;
	if (err == 0)
		kfree_skb(skb);
		kfree_skb(skb);
		return 0;
	}
  out_err:
	nf_bridge_adjust_segmented_data(skb);
	return err;
	return err;
}
}