kern/60293: FreeBSD arp poison patch

Bruce M Simpson bms at spc.org
Sat Jul 3 09:06:18 PDT 2004


Here's a cleaned up version of your patch which works against FreeBSD-CURRENT.
I have mixed feelings about committing this; it raises the bar somewhat
but it does not completely close the hole.

Let's say Alice and Bob are talking to each other with IPv4 over Ethernet.

Making the arp cache entry permanent is probably a bad idea for Alice. She
should be allowed to expire out the entry at some point, which admittedly
provides a window for the cache poisoning attack, but the potential for
abuse is increased if the attacker is able to race Bob in creating the
original entry in Alice's cache.

I've attempted to mitigate this by using a simple linearly computed threshold,
but this still doesn't really solve the problem.

Might suggest we review the following papers on the subject:
	http://www.cs.utexas.edu/users/chuang/comnet0103.pdf
	http://www.acsac.org/1999/papers/fri-b-0830-dutta.pdf
	http://alor.antifork.org/projects/s-arp/article.pdf
And code:
	http://alor.antifork.org/projects/s-arp/sarpd-0.0.9-devel.tar.gz

The daemon above could probably be adapted for *BSD use by setting
IFF_NOARP on an Ethernet network interface, removing the subnet route
and re-adding it with RTF_XRESOLVE|RTX_CLONING, and adding bpf/rtsock
support to the userland code.

Also, please clarify your license regarding this code, I am assuming that
it was submitted under the BSD license.

Regards,
BMS
-------------- next part --------------
Index: src/sys/conf/options
===================================================================
RCS file: /home/ncvs/src/sys/conf/options,v
retrieving revision 1.457
diff -u -p -r1.457 options
--- src/sys/conf/options	29 Jun 2004 02:30:12 -0000	1.457
+++ src/sys/conf/options	3 Jul 2004 13:16:11 -0000
@@ -305,6 +305,7 @@ ALTQ_CDNR		opt_altq.h
 ALTQ_PRIQ		opt_altq.h
 ALTQ_NOPCC		opt_altq.h
 ALTQ_DEBUG		opt_altq.h
+ARP_VERIFY_REPLY	opt_inet.h
 BOOTP			opt_bootp.h
 BOOTP_COMPAT		opt_bootp.h
 BOOTP_NFSROOT		opt_bootp.h
Index: src/sys/netinet/if_ether.c
===================================================================
RCS file: /home/ncvs/src/sys/netinet/if_ether.c,v
retrieving revision 1.128
diff -u -p -r1.128 if_ether.c
--- src/sys/netinet/if_ether.c	13 Jun 2004 10:54:36 -0000	1.128
+++ src/sys/netinet/if_ether.c	3 Jul 2004 15:50:50 -0000
@@ -57,8 +57,8 @@
 #include <net/route.h>
 #include <net/netisr.h>
 #include <net/if_llc.h>
-#ifdef BRIDGE
 #include <net/ethernet.h>
+#ifdef BRIDGE
 #include <net/bridge.h>
 #endif
 
@@ -95,8 +95,16 @@ struct llinfo_arp {
 	struct	mbuf *la_hold;	/* last packet until resolved/timeout */
 	u_short	la_preempt;	/* countdown for pre-expiry arps */
 	u_short	la_asked;	/* #times we QUERIED following expiration */
-#define la_timer la_rt->rt_rmx.rmx_expire /* deletion time in seconds */
+#ifdef ARP_VERIFY_REPLY
+	struct	in_addr la_opaddr;	/* XXX original protocol level
+					 * address for verification */
+	u_short	la_ack;		/* #times original MAC acknowledged an
+				 * ARP reply which we then RE-QUERIED */
+	u_char	la_olladdr[ETHER_ADDR_LEN];	/* XXX original MAC for
+						 * verification */
+#endif
 };
+#define la_timer la_rt->rt_rmx.rmx_expire /* deletion time in seconds */
 
 static	LIST_HEAD(, llinfo_arp) llinfo_arp;
 
@@ -106,6 +114,9 @@ static int	arp_allocated;
 static int	arp_maxtries = 5;
 static int	useloopback = 1; /* use loopback interface for local traffic */
 static int	arp_proxyall = 0;
+#ifdef ARP_VERIFY_REPLY
+static int	arp_verifyrep = 0;
+#endif
 static struct callout arp_callout;
 
 SYSCTL_INT(_net_link_ether_inet, OID_AUTO, maxtries, CTLFLAG_RW,
@@ -114,6 +125,10 @@ SYSCTL_INT(_net_link_ether_inet, OID_AUT
 	   &useloopback, 0, "");
 SYSCTL_INT(_net_link_ether_inet, OID_AUTO, proxyall, CTLFLAG_RW,
 	   &arp_proxyall, 0, "");
+#ifdef ARP_VERIFY_REPLY
+SYSCTL_INT(_net_link_ether_inet, OID_AUTO, verify_reply, CTLFLAG_RW,
+	   &arp_verifyrep, 0, "Verify untrusted ARP traffic");
+#endif
 
 static void	arp_init(void);
 static void	arp_rtrequest(int, struct rtentry *, struct rt_addrinfo *);
@@ -605,18 +620,104 @@ match:
 				    ifp->if_xname);
 			goto reply;
 		}
+#ifdef ARP_VERIFY_REPLY
+		if (arp_verifyrep && rt->rt_expire &&
+		    sdl->sdl_alen == ETHER_ADDR_LEN && /* IPv4 check ok */
+		    la->la_ack == 1 &&
+		    !bcmp(ar_sha(ah), LLADDR(sdl), ETHER_ADDR_LEN)) {
+			/*
+			 * We have seen gratuitous ARP from this MAC for this
+			 * protocol level address on the wire in the past.
+			 * Check if the learned address was verified by a
+			 * further unicast ARP reply/request sequence.
+			 */
+			if (!bcmp(ar_sha(ah), la->la_olladdr, ETHER_ADDR_LEN)) {
+				/*
+				 * The source MAC address of this reply
+				 * matches the previously learned entry.
+				 */
+				if (bootverbose && log_arp_movements)
+					log(LOG_INFO,
+"arp: reply for %s from %*D on %s verified ok\n",
+					    inet_ntoa(isaddr),
+					    ETHER_ADDR_LEN,
+					    (u_char *)ar_sha(ah), ":",
+					    ifp->if_xname);
+				la->la_ack = 0;
+			} else {
+				/*
+				 * Someone is now attempting to spoof a
+				 * learned protocol level address in the
+				 * current gratuitous reply being handled.
+				 *
+				 * Clamp expunge period to a threshold
+				 * derived from the following formula.
+				 */
+				int expire = (((uint32_t)arpt_keep +
+				    time_second) * la->la_ack) / 2;
+				rt->rt_expire = MAX(rt->rt_expire, expire);
+
+				/*
+				 * Log an appropriate message, and
+				 * discard the incoming reply.
+				 */
+				log(LOG_ERR,
+"arp: untrusted reply for %s from %*D on %s (learned MAC is %*D)\n",
+				    inet_ntoa(la->la_opaddr),
+				    ETHER_ADDR_LEN,
+				    (u_char *)&la->la_olladdr, ":",
+				    ifp->if_xname,
+				    ETHER_ADDR_LEN,
+				    (u_char *)ar_sha(ah), ":");
+				bzero(&la->la_olladdr, sizeof(la->la_olladdr));
+				la->la_ack = 0;
+				m_free(m);
+				return;
+			}
+		}
+#endif /* ARP_VERIFY_REPLY */
 		if (sdl->sdl_alen &&
 		    bcmp(ar_sha(ah), LLADDR(sdl), sdl->sdl_alen)) {
 			if (rt->rt_expire) {
-			    if (log_arp_movements)
-			        log(LOG_INFO, "arp: %s moved from %*D to %*D on %s\n",
-				    inet_ntoa(isaddr),
+#ifdef ARP_VERIFY_REPLY
+			if (arp_verifyrep) {
+				/*
+				 * If this protocol-level and link-level
+				 * address was not previously verified,
+				 * verify it now by sending another
+				 * unicast ARP request back to the
+				 * originator.
+				 */
+				if (la->la_ack == 0) {
+					if (bootverbose && log_arp_movements)
+						log(LOG_INFO,
+"arp: verifying reply for %s from %*D on %s\n", inet_ntoa(isaddr),
+						    ETHER_ADDR_LEN,
+						    (u_char *)ar_sha(ah), ":",
+						    ifp->if_xname);
+
+					arprequest(ifp, &myaddr, &isaddr,
+					    IF_LLADDR(ifp));
+					bcopy(ar_sha(ah), &la->la_olladdr,
+					    ETHER_ADDR_LEN);
+					bcopy(&isaddr, &la->la_opaddr,
+					    ETHER_ADDR_LEN);
+					la->la_ack++;
+					la->la_asked++;
+					m_free(m);
+					return;
+				}
+			} else
+#endif /* ARP_VERIFY_REPLY */
+			if (log_arp_movements)
+				log(LOG_INFO,
+"arp: %s moved from %*D to %*D on %s\n", inet_ntoa(isaddr),
 				    ifp->if_addrlen, (u_char *)LLADDR(sdl), ":",
 				    ifp->if_addrlen, (u_char *)ar_sha(ah), ":",
 				    ifp->if_xname);
 			} else {
 			    log(LOG_ERR,
-				"arp: %*D attempts to modify permanent entry for %s on %s\n",
+"arp: %*D attempts to modify permanent entry for %s on %s\n",
 				ifp->if_addrlen, (u_char *)ar_sha(ah), ":",
 				inet_ntoa(isaddr), ifp->if_xname);
 			    goto reply;


More information about the freebsd-net mailing list