diff mbox

[1/4] ipv4: add option to drop unicast encapsulated in L2 multicast

Message ID 1428652454-1224-1-git-send-email-johannes@sipsolutions.net (mailing list archive)
State Not Applicable
Delegated to: Kalle Valo
Headers show

Commit Message

Johannes Berg April 10, 2015, 7:54 a.m. UTC
From: Johannes Berg <johannes.berg@intel.com>

In order to solve a problem with 802.11, the so-called hole-196 attack,
add an option (sysctl) called "drop_unicast_in_l2_multicast" which, if
enabled, causes the stack to drop IPv4 unicast packets encapsulated in
link-layer multi- or broadcast frames. Such frames can (as an attack)
be created by any member of the same wireless network and transmitted
as valid encrypted frames since the symmetric key for broadcast frames
is shared between all stations.

Additionally, enabling this option provides compliance with a SHOULD
clause of RFC 1122.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 Documentation/networking/ip-sysctl.txt |  7 +++++++
 include/uapi/linux/ip.h                |  1 +
 net/ipv4/devinet.c                     |  2 ++
 net/ipv4/route.c                       | 20 ++++++++++++++++++++
 4 files changed, 30 insertions(+)

Comments

Julian Anastasov April 10, 2015, 12:19 p.m. UTC | #1
Hello,

On Fri, 10 Apr 2015, Johannes Berg wrote:

> From: Johannes Berg <johannes.berg@intel.com>
> 
> In order to solve a problem with 802.11, the so-called hole-196 attack,
> add an option (sysctl) called "drop_unicast_in_l2_multicast" which, if
> enabled, causes the stack to drop IPv4 unicast packets encapsulated in
> link-layer multi- or broadcast frames. Such frames can (as an attack)
> be created by any member of the same wireless network and transmitted
> as valid encrypted frames since the symmetric key for broadcast frames
> is shared between all stations.
> 
> Additionally, enabling this option provides compliance with a SHOULD
> clause of RFC 1122.
> 

> +++ b/net/ipv4/route.c
> @@ -1727,6 +1727,26 @@ static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
>  	if (res.type == RTN_BROADCAST)
>  		goto brd_input;
>  
> +	/* RFC 1122 3.3.6:
> +	 *
> +	 *   When a host sends a datagram to a link-layer broadcast address,
> +	 *   the IP destination address MUST be a legal IP broadcast or IP
> +	 *   multicast address.
> +	 *
> +	 *   A host SHOULD silently discard a datagram that is received via
> +	 *   a link-layer broadcast (see Section 2.4) but does not specify
> +	 *   an IP multicast or broadcast destination address.
> +	 *
> +	 * This doesn't explicitly say L2 *broadcast*, but broadcast is in a
> +	 * way a form of multicast and the most common use case for this is
> +	 * 802.11 protecting against cross-station spoofing (the so-called
> +	 * "hole-196" attack) so do it for both.
> +	 */
> +	if (IN_DEV_CONF_GET(in_dev, DROP_UNICAST_IN_L2_MULTICAST) &&

	For this flag IN_DEV_ORCONF can be used, by this way
all/drop_unicast_in_l2_multicast=1 can enable it for all
interfaces.

> +	    (skb->pkt_type == PACKET_BROADCAST ||
> +	     skb->pkt_type == PACKET_MULTICAST))
> +		goto e_inval;
> +

	So, this is the same patch as the 2014-Aug version
but this time with flag? But how the previous problems were
addressed? May be something is changed in kernel afterwards?

	So, if your are back at step 1 can you check again
the problems with this implementation?:

http://marc.info/?l=linux-netdev&m=140865079120355&w=2

Thread:
http://marc.info/?t=140864197300004&r=1&w=2

	In short:

- no way to select correct skb->pkt_type in inet_rtm_getroute
before ip_route_input, this is a chiken-egg problem, of course,
skb->pkt_type = PACKET_HOST can work for now

- ip_route_input is called also for ARP, so incoming ARP
broadcasts are not replied anymore

- CLUSTERIP

Regards

--
Julian Anastasov <ja@ssi.bg>
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Johannes Berg April 10, 2015, 1:10 p.m. UTC | #2
Hi
> 
> > +	if (IN_DEV_CONF_GET(in_dev, DROP_UNICAST_IN_L2_MULTICAST) &&
> 
> 	For this flag IN_DEV_ORCONF can be used, by this way
> all/drop_unicast_in_l2_multicast=1 can enable it for all
> interfaces.

Good point. I need to look into that for the other patches in this
series as well.

> 	So, this is the same patch as the 2014-Aug version
> but this time with flag? But how the previous problems were
> addressed? May be something is changed in kernel afterwards?
> 
> 	So, if your are back at step 1 can you check again
> the problems with this implementation?:

I think I am, sorry about that. I had focused on the CLUSTERIP problem
in your email, and decided that was solved by simply having the admin
for CLUSTERIP networks never set this flag.

> - no way to select correct skb->pkt_type in inet_rtm_getroute
> before ip_route_input, this is a chiken-egg problem, of course,
> skb->pkt_type = PACKET_HOST can work for now

This is interesting. Why does that even build an skb and route it, if it
really just cares about the struct rtable? Anyway, you're right, using
PACKET_HOST would work. I'm not really sure I see the chicken-and-egg
problem though, it's not like the result of this is used to route the
skb again or something, afaict?

> - ip_route_input is called also for ARP, so incoming ARP
> broadcasts are not replied anymore

Hm. I didn't see this in my testing so far, but I only enable this
temporarily, so perhaps by that time it was already done.

This looks like another case of using this function to not really route
anything but to just look up the route, similar to the
inet_rtm_getroute() function you pointed out. Perhaps that would warrant
splitting out somewhat.

On the other hand, there's not all that much reason not to put this into
ip_rcv_finish(), we already touch the rt->rt_type there for MIB
counters. I'll look into that.

> - CLUSTERIP

See above.

Thanks,
johannes


--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Julian Anastasov April 11, 2015, 10:39 a.m. UTC | #3
Hello,

On Fri, 10 Apr 2015, Johannes Berg wrote:

> > - no way to select correct skb->pkt_type in inet_rtm_getroute
> > before ip_route_input, this is a chiken-egg problem, of course,
> > skb->pkt_type = PACKET_HOST can work for now
> 
> This is interesting. Why does that even build an skb and route it, if it
> really just cares about the struct rtable? Anyway, you're right, using
> PACKET_HOST would work. I'm not really sure I see the chicken-and-egg
> problem though, it's not like the result of this is used to route the
> skb again or something, afaict?

	If we want to set correct pkt_type here we should
know the type of daddr (multicast => PACKET_MULTICAST,
broadcast => PACKET_BROADCAST, etc) and we can know it only
after routing (rt_type). I hope we will not do other checks
with pkt_type, so we can put some comment that
skb->pkt_type = PACKET_HOST is just to make
DROP_UNICAST_IN_L2_MULTICAST check happy.

> > - ip_route_input is called also for ARP, so incoming ARP
> > broadcasts are not replied anymore
> 
> Hm. I didn't see this in my testing so far, but I only enable this
> temporarily, so perhaps by that time it was already done.

	I didn't tested it myself, may be you can try:

other_host_on_LAN# arp -d patched_box
other_host_on_LAN# arping -c 1 -I eth0 patched_box

	There must be reply for broadcast requests.

> This looks like another case of using this function to not really route
> anything but to just look up the route, similar to the
> inet_rtm_getroute() function you pointed out. Perhaps that would warrant
> splitting out somewhat.

	Yes, ARP essentially performs rp_filter check (note
that arp_filter check is limited only to local targets, not
to proxy_arp) and target address type check.

	The solution would be to add skb->protocol == htons(ETH_P_IP)
check.

> On the other hand, there's not all that much reason not to put this into
> ip_rcv_finish(), we already touch the rt->rt_type there for MIB
> counters. I'll look into that.

	May be in ip_local_deliver_finish because:

- ip_forward() already has PACKET_HOST check.

- for broadcast/multicast dests we do not care

- CLUSTERIP works in LOCAL_IN (after ip_rcv_finish), LOCAL_IN
is here: ip_rcv_finish->dst_input->ip_local_deliver->
ip_local_deliver_finish

Regards

--
Julian Anastasov <ja@ssi.bg>
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Johannes Berg April 13, 2015, 10:57 a.m. UTC | #4
Hi Julian,

> > Hm. I didn't see this in my testing so far, but I only enable this
> > temporarily, so perhaps by that time it was already done.
> 
> 	I didn't tested it myself, may be you can try:
> 
> other_host_on_LAN# arp -d patched_box
> other_host_on_LAN# arping -c 1 -I eth0 patched_box
> 
> 	There must be reply for broadcast requests.

Sure; I would have expected this to have broken my network but as I
said, it was only enabled temporarily.

> > On the other hand, there's not all that much reason not to put this into
> > ip_rcv_finish(), we already touch the rt->rt_type there for MIB
> > counters. I'll look into that.
> 
> 	May be in ip_local_deliver_finish because:
> 
> - ip_forward() already has PACKET_HOST check.
> 
> - for broadcast/multicast dests we do not care
> 
> - CLUSTERIP works in LOCAL_IN (after ip_rcv_finish), LOCAL_IN
> is here: ip_rcv_finish->dst_input->ip_local_deliver->
> ip_local_deliver_finish

It's just that the later this is, the more nervous I get about it being
really effective. :)

I'm willing to discount the CLUSTERIP case, it seems insane to want to
run CLUSTERIP over wifi on a network that explicitly limits multicast.

Other than that, do you see any reason for not putting it in
ip_rcv_finish()?

johannes

--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Julian Anastasov April 13, 2015, 12:04 p.m. UTC | #5
Hello,

On Mon, 13 Apr 2015, Johannes Berg wrote:

> > - CLUSTERIP works in LOCAL_IN (after ip_rcv_finish), LOCAL_IN
> > is here: ip_rcv_finish->dst_input->ip_local_deliver->
> > ip_local_deliver_finish
> 
> It's just that the later this is, the more nervous I get about it being
> really effective. :)
> 
> I'm willing to discount the CLUSTERIP case, it seems insane to want to
> run CLUSTERIP over wifi on a network that explicitly limits multicast.
> 
> Other than that, do you see any reason for not putting it in
> ip_rcv_finish()?

	ok, I don't remember for other cases.

Regards

--
Julian Anastasov <ja@ssi.bg>
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt
index 071fb18dc57c..f97ad76e6f82 100644
--- a/Documentation/networking/ip-sysctl.txt
+++ b/Documentation/networking/ip-sysctl.txt
@@ -1165,6 +1165,13 @@  promote_secondaries - BOOLEAN
 	promote a corresponding secondary IP address instead of
 	removing all the corresponding secondary IP addresses.
 
+drop_unicast_in_l2_multicast - BOOLEAN
+	Drop any unicast IP packets that are received in link-layer
+	multicast (or broadcast) frames.
+	This behavior (for multicast) is actually a SHOULD in RFC
+	1122, but is disabled by default for compatibility reasons.
+	Default: off (0)
+
 
 tag - INTEGER
 	Allows you to write a number, which can be used as required.
diff --git a/include/uapi/linux/ip.h b/include/uapi/linux/ip.h
index 411959405ab6..c0e594b209ff 100644
--- a/include/uapi/linux/ip.h
+++ b/include/uapi/linux/ip.h
@@ -164,6 +164,7 @@  enum
 	IPV4_DEVCONF_ROUTE_LOCALNET,
 	IPV4_DEVCONF_IGMPV2_UNSOLICITED_REPORT_INTERVAL,
 	IPV4_DEVCONF_IGMPV3_UNSOLICITED_REPORT_INTERVAL,
+	IPV4_DEVCONF_DROP_UNICAST_IN_L2_MULTICAST,
 	__IPV4_DEVCONF_MAX
 };
 
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c
index c6473f365ad1..b608407f96e7 100644
--- a/net/ipv4/devinet.c
+++ b/net/ipv4/devinet.c
@@ -2176,6 +2176,8 @@  static struct devinet_sysctl_table {
 					      "promote_secondaries"),
 		DEVINET_SYSCTL_FLUSHING_ENTRY(ROUTE_LOCALNET,
 					      "route_localnet"),
+		DEVINET_SYSCTL_FLUSHING_ENTRY(DROP_UNICAST_IN_L2_MULTICAST,
+					      "drop_unicast_in_l2_multicast"),
 	},
 };
 
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 652b92ebd7ba..5d5e03f9697b 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -1727,6 +1727,26 @@  static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
 	if (res.type == RTN_BROADCAST)
 		goto brd_input;
 
+	/* RFC 1122 3.3.6:
+	 *
+	 *   When a host sends a datagram to a link-layer broadcast address,
+	 *   the IP destination address MUST be a legal IP broadcast or IP
+	 *   multicast address.
+	 *
+	 *   A host SHOULD silently discard a datagram that is received via
+	 *   a link-layer broadcast (see Section 2.4) but does not specify
+	 *   an IP multicast or broadcast destination address.
+	 *
+	 * This doesn't explicitly say L2 *broadcast*, but broadcast is in a
+	 * way a form of multicast and the most common use case for this is
+	 * 802.11 protecting against cross-station spoofing (the so-called
+	 * "hole-196" attack) so do it for both.
+	 */
+	if (IN_DEV_CONF_GET(in_dev, DROP_UNICAST_IN_L2_MULTICAST) &&
+	    (skb->pkt_type == PACKET_BROADCAST ||
+	     skb->pkt_type == PACKET_MULTICAST))
+		goto e_inval;
+
 	if (res.type == RTN_LOCAL) {
 		err = fib_validate_source(skb, saddr, daddr, tos,
 					  0, dev, in_dev, &itag);