linux/net/bridge/br_stp_bpdu.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

209 lines
5.2 KiB
C

/*
* Spanning tree protocol; BPDU handling
* Linux ethernet bridge
*
* Authors:
* Lennert Buytenhek <buytenh@gnu.org>
*
* $Id: br_stp_bpdu.c,v 1.3 2001/11/10 02:35:25 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/netfilter_bridge.h>
#include "br_private.h"
#include "br_private_stp.h"
#define JIFFIES_TO_TICKS(j) (((j) << 8) / HZ)
#define TICKS_TO_JIFFIES(j) (((j) * HZ) >> 8)
static void br_send_bpdu(struct net_bridge_port *p, unsigned char *data, int length)
{
struct net_device *dev;
struct sk_buff *skb;
int size;
if (!p->br->stp_enabled)
return;
size = length + 2*ETH_ALEN + 2;
if (size < 60)
size = 60;
dev = p->dev;
if ((skb = dev_alloc_skb(size)) == NULL) {
printk(KERN_INFO "br: memory squeeze!\n");
return;
}
skb->dev = dev;
skb->protocol = htons(ETH_P_802_2);
skb->mac.raw = skb_put(skb, size);
memcpy(skb->mac.raw, bridge_ula, ETH_ALEN);
memcpy(skb->mac.raw+ETH_ALEN, dev->dev_addr, ETH_ALEN);
skb->mac.raw[2*ETH_ALEN] = 0;
skb->mac.raw[2*ETH_ALEN+1] = length;
skb->nh.raw = skb->mac.raw + 2*ETH_ALEN + 2;
memcpy(skb->nh.raw, data, length);
memset(skb->nh.raw + length, 0xa5, size - length - 2*ETH_ALEN - 2);
NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev,
dev_queue_xmit);
}
static __inline__ void br_set_ticks(unsigned char *dest, int jiff)
{
__u16 ticks;
ticks = JIFFIES_TO_TICKS(jiff);
dest[0] = (ticks >> 8) & 0xFF;
dest[1] = ticks & 0xFF;
}
static __inline__ int br_get_ticks(unsigned char *dest)
{
return TICKS_TO_JIFFIES((dest[0] << 8) | dest[1]);
}
/* called under bridge lock */
void br_send_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
{
unsigned char buf[38];
buf[0] = 0x42;
buf[1] = 0x42;
buf[2] = 0x03;
buf[3] = 0;
buf[4] = 0;
buf[5] = 0;
buf[6] = BPDU_TYPE_CONFIG;
buf[7] = (bpdu->topology_change ? 0x01 : 0) |
(bpdu->topology_change_ack ? 0x80 : 0);
buf[8] = bpdu->root.prio[0];
buf[9] = bpdu->root.prio[1];
buf[10] = bpdu->root.addr[0];
buf[11] = bpdu->root.addr[1];
buf[12] = bpdu->root.addr[2];
buf[13] = bpdu->root.addr[3];
buf[14] = bpdu->root.addr[4];
buf[15] = bpdu->root.addr[5];
buf[16] = (bpdu->root_path_cost >> 24) & 0xFF;
buf[17] = (bpdu->root_path_cost >> 16) & 0xFF;
buf[18] = (bpdu->root_path_cost >> 8) & 0xFF;
buf[19] = bpdu->root_path_cost & 0xFF;
buf[20] = bpdu->bridge_id.prio[0];
buf[21] = bpdu->bridge_id.prio[1];
buf[22] = bpdu->bridge_id.addr[0];
buf[23] = bpdu->bridge_id.addr[1];
buf[24] = bpdu->bridge_id.addr[2];
buf[25] = bpdu->bridge_id.addr[3];
buf[26] = bpdu->bridge_id.addr[4];
buf[27] = bpdu->bridge_id.addr[5];
buf[28] = (bpdu->port_id >> 8) & 0xFF;
buf[29] = bpdu->port_id & 0xFF;
br_set_ticks(buf+30, bpdu->message_age);
br_set_ticks(buf+32, bpdu->max_age);
br_set_ticks(buf+34, bpdu->hello_time);
br_set_ticks(buf+36, bpdu->forward_delay);
br_send_bpdu(p, buf, 38);
}
/* called under bridge lock */
void br_send_tcn_bpdu(struct net_bridge_port *p)
{
unsigned char buf[7];
buf[0] = 0x42;
buf[1] = 0x42;
buf[2] = 0x03;
buf[3] = 0;
buf[4] = 0;
buf[5] = 0;
buf[6] = BPDU_TYPE_TCN;
br_send_bpdu(p, buf, 7);
}
static const unsigned char header[6] = {0x42, 0x42, 0x03, 0x00, 0x00, 0x00};
/* NO locks */
int br_stp_handle_bpdu(struct sk_buff *skb)
{
struct net_bridge_port *p = skb->dev->br_port;
struct net_bridge *br = p->br;
unsigned char *buf;
/* insert into forwarding database after filtering to avoid spoofing */
br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
/* need at least the 802 and STP headers */
if (!pskb_may_pull(skb, sizeof(header)+1) ||
memcmp(skb->data, header, sizeof(header)))
goto err;
buf = skb_pull(skb, sizeof(header));
spin_lock_bh(&br->lock);
if (p->state == BR_STATE_DISABLED
|| !(br->dev->flags & IFF_UP)
|| !br->stp_enabled)
goto out;
if (buf[0] == BPDU_TYPE_CONFIG) {
struct br_config_bpdu bpdu;
if (!pskb_may_pull(skb, 32))
goto out;
buf = skb->data;
bpdu.topology_change = (buf[1] & 0x01) ? 1 : 0;
bpdu.topology_change_ack = (buf[1] & 0x80) ? 1 : 0;
bpdu.root.prio[0] = buf[2];
bpdu.root.prio[1] = buf[3];
bpdu.root.addr[0] = buf[4];
bpdu.root.addr[1] = buf[5];
bpdu.root.addr[2] = buf[6];
bpdu.root.addr[3] = buf[7];
bpdu.root.addr[4] = buf[8];
bpdu.root.addr[5] = buf[9];
bpdu.root_path_cost =
(buf[10] << 24) |
(buf[11] << 16) |
(buf[12] << 8) |
buf[13];
bpdu.bridge_id.prio[0] = buf[14];
bpdu.bridge_id.prio[1] = buf[15];
bpdu.bridge_id.addr[0] = buf[16];
bpdu.bridge_id.addr[1] = buf[17];
bpdu.bridge_id.addr[2] = buf[18];
bpdu.bridge_id.addr[3] = buf[19];
bpdu.bridge_id.addr[4] = buf[20];
bpdu.bridge_id.addr[5] = buf[21];
bpdu.port_id = (buf[22] << 8) | buf[23];
bpdu.message_age = br_get_ticks(buf+24);
bpdu.max_age = br_get_ticks(buf+26);
bpdu.hello_time = br_get_ticks(buf+28);
bpdu.forward_delay = br_get_ticks(buf+30);
br_received_config_bpdu(p, &bpdu);
}
else if (buf[0] == BPDU_TYPE_TCN) {
br_received_tcn_bpdu(p);
}
out:
spin_unlock_bh(&br->lock);
err:
kfree_skb(skb);
return 0;
}