aha/net/bridge/br_input.c
Stephen Hemminger 85967bb46d [BRIDGE]: prevent bad forwarding table updates
Avoid poisoning of the bridge forwarding table by frames that have been
dropped by filtering. This prevents spoofed source addresses on hostile
side of bridge from causing packet leakage, a small but possible security
risk.

Signed-off-by: Stephen Hemminger <shemminger@osdl.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-05-29 14:15:55 -07:00

146 lines
3.2 KiB
C

/*
* Handle incoming frames
* Linux ethernet bridge
*
* Authors:
* Lennert Buytenhek <buytenh@gnu.org>
*
* $Id: br_input.c,v 1.10 2001/12/24 04:50:20 davem Exp $
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/netfilter_bridge.h>
#include "br_private.h"
const unsigned char bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 };
static int br_pass_frame_up_finish(struct sk_buff *skb)
{
#ifdef CONFIG_NETFILTER_DEBUG
skb->nf_debug = 0;
#endif
netif_rx(skb);
return 0;
}
static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
{
struct net_device *indev;
br->statistics.rx_packets++;
br->statistics.rx_bytes += skb->len;
indev = skb->dev;
skb->dev = br->dev;
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
br_pass_frame_up_finish);
}
/* note: already called with rcu_read_lock (preempt_disabled) */
int br_handle_frame_finish(struct sk_buff *skb)
{
const unsigned char *dest = eth_hdr(skb)->h_dest;
struct net_bridge_port *p = skb->dev->br_port;
struct net_bridge *br = p->br;
struct net_bridge_fdb_entry *dst;
int passedup = 0;
/* insert into forwarding database after filtering to avoid spoofing */
br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
if (br->dev->flags & IFF_PROMISC) {
struct sk_buff *skb2;
skb2 = skb_clone(skb, GFP_ATOMIC);
if (skb2 != NULL) {
passedup = 1;
br_pass_frame_up(br, skb2);
}
}
if (dest[0] & 1) {
br_flood_forward(br, skb, !passedup);
if (!passedup)
br_pass_frame_up(br, skb);
goto out;
}
dst = __br_fdb_get(br, dest);
if (dst != NULL && dst->is_local) {
if (!passedup)
br_pass_frame_up(br, skb);
else
kfree_skb(skb);
goto out;
}
if (dst != NULL) {
br_forward(dst->dst, skb);
goto out;
}
br_flood_forward(br, skb, 0);
out:
return 0;
}
/*
* Called via br_handle_frame_hook.
* Return 0 if *pskb should be processed furthur
* 1 if *pskb is handled
* note: already called with rcu_read_lock (preempt_disabled)
*/
int br_handle_frame(struct net_bridge_port *p, struct sk_buff **pskb)
{
struct sk_buff *skb = *pskb;
const unsigned char *dest = eth_hdr(skb)->h_dest;
if (p->state == BR_STATE_DISABLED)
goto err;
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto err;
if (p->state == BR_STATE_LEARNING)
br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
if (p->br->stp_enabled &&
!memcmp(dest, bridge_ula, 5) &&
!(dest[5] & 0xF0)) {
if (!dest[5]) {
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, skb->dev,
NULL, br_stp_handle_bpdu);
return 1;
}
}
else if (p->state == BR_STATE_FORWARDING) {
if (br_should_route_hook) {
if (br_should_route_hook(pskb))
return 0;
skb = *pskb;
dest = eth_hdr(skb)->h_dest;
}
if (!memcmp(p->br->dev->dev_addr, dest, ETH_ALEN))
skb->pkt_type = PACKET_HOST;
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
return 1;
}
err:
kfree_skb(skb);
return 1;
}