/* Trusted-ICMP * Copyright(C) 2001 Salvatore Sanfilippo * * This code is under the GPL version 2 license. * * This kernel module for Linux 2.4 implements a quite sane way to * trust ICMP "DF set but fragmentation required" (DFSFR) packets. * Don't expect maximum security here since we must fight with * the standard IP header and the compatibility issue. * * COMMENTS: * * An attacker can spoof a DFSFR packet with the effect of a * fake PMTU discovery. This DoS is hard to avoid, and AFAIK * even IPSEC don't fix this. A way to make the attack harder to do * is to sign the outgoing packet IP header, that will be * quoted in the ICMP, and check the signature before to accept it. * * NOTE: this module improves security only against the ICMP * DF set but fragmentation required spoofing, not more. * * The problem is to find some space to store the signature. * A way may be to store it in a new IP option. Fully standard * TCP/IP stacks should skip the option and accept the packet, * but unfortunatelly many firewalls will drop this packets anyway. * Another problem is that to do this we need to expand the * packet, and this can result in a packet bigger that the interface MTU. * Finally if the IP header already contains 40 bytes of options * there is no room for our option. * * Fortunatelly the ID field of the IP protocol isn't useful * when the DF bit is set in the IP header. The ID is used in fragmentation, * but the DF bit prevents the fragmentation. Note also that only * packets with the DF bit set can result in a DFSFR ICMP. * The problem here is that the ID field is only 16 bit. This will * make the attack about 2^16 times harder to do (in the middle case * 2^15 packets are required), it isn't so good, but better that * nothing, considering that all this don't break the RFC in any way * (read more to see how for implementation details the attacker * will need only 2^14 packets in the middle case to mount the attack). * * As an additional protection this module can use the TOS field * to reach better security. This isn't ok since it breaks the TOS * information, and is disabled by default. * * DETAILS * * This module performs a very simple work: * It generates a key using the Linux's pseudo-random built-in generator. * The key is refreshed after a timeout, or after a given number * of packets sent. For every outgoing packet with the DF bit set * the IP ID (and the TOS if the option is enabled) is replaced * with an HMAC, that uses a weaker/faster version of MD4. * This should not be a problem since the attacker can't collect * more than one signature for every IP address he own, and since * the key is changed in a short period. The 16-bit-only signature * is the real problem here. * * When an DFSFR ICMP packet is received we check the signature, * and drop it if don't match. We need to check the signature against * the current key and the last key, since the key may be no longer * the same when we get the ICMP: this isn't good... since gives * to the attacker double chances to guess the 16-bit HMAC. Actually * the attacker needs only 2^14 packets in the middle case to * perform the attack, but I can't get better than this. * * NOTE: This patch will work better if the PMTU cache expire time * is very short. * * More comments inside the code. * * DISCLAIMERS * * I tested this patch against my not-SMP linux 2.4 box for some day. * I feel that it is stable and works without problems but I can't ensure this. * USE IT AT YOUR RISK. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 #define TI_DEBUG printk #else #define TI_DEBUG(x, y...) #endif /* An overkill, may compensate the entropy overstimation but is here just * to fit the block size of the weak-MD4 hash function */ #define KEY_LEN 40 /* This means 40 *bytes* */ /* This time should be the maximum lifetime of an internet segment * 2. * Anyway we truncated it at 30 seconds that is quite realistic -- * Note that this can't create big problems even if we got * the ICMP after more than 30 seconds. * The KEY_TTL_PACKETS timeout is here just to enforce our policy * but every attacker can collect just one-ID-for-IP-address for every * different key, so should be useless */ #define KEY_TTL_TIME 30*HZ #define KEY_TTL_PACKETS 10000 /* Do you want that the TOS field is used to store additional * 8 bit of the hash? * This makes the protection more secure but isn't ok under the * RFC state of view. Anyway: * 2^16 = 65536, this leaves the attack quite realistic. * 2^24 = 16777216, times more strong. * * WARNING: Set it to zero if unsure */ static int use_tos = 0; static __u32 key[KEY_LEN/4]; static __u32 old_key[KEY_LEN/4]; static unsigned long key_timeout; /* We use an atomic counter for the sent-packet key expire, * and a read/write spinlock for the two keys and the key timestamp */ static atomic_t key_sent; static rwlock_t key_lock = RW_LOCK_UNLOCKED; /* The output filter signs the packets, while * the input filter checks the signature */ struct nf_hook_ops output_filter, input_filter; static __u32 halfMD4Transform (__u32 const buf[4], __u32 const in[8]); static void put_sign(__u8 *dest, struct iphdr *ip, __u8 *key); static void get_new_key(void); static unsigned int output_handler( unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct iphdr *ip; __u32 hash; if ((*skb)->len < sizeof(struct iphdr)) goto accept; ip = (*skb)->nh.iph; /* Don't sign packets with DF unset, for this packets * the IP ID should not be modified */ if ((ntohs(ip->frag_off) & IP_DF) == 0) goto accept; /* Get a new key if the old one expired */ read_lock_bh(&key_lock); /* protect key_timeout */ if (jiffies >= key_timeout) { read_unlock_bh(&key_lock); get_new_key(); } else { read_unlock_bh(&key_lock); if (atomic_read(&key_sent) >= KEY_TTL_PACKETS) get_new_key(); atomic_inc(&key_sent); } put_sign((__u8*)&hash, ip, (__u8*)key); /* No endianess convertion in the following code, since * the sender is the only receiver of this information. */ ip->id = hash & 0xFFFF; if (use_tos) ip->tos = (hash >> 16) & 0xFF; ip_send_check(ip); /* Compute the IP checksum */ accept: return NF_ACCEPT; } static unsigned int input_handler( unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct iphdr *ip, *qip; struct icmphdr *icmp; int len; __u32 hash; ip = (*skb)->nh.iph; len = (*skb)->len; /* IP header len sanity check */ if (len < sizeof(struct iphdr)) goto drop; /* We are interested only in ICMP */ if (ip->protocol != IPPROTO_ICMP) goto accept; /* ICMP header len sanity check */ if (len < ((ip->ihl * 4) + sizeof(struct icmphdr))) goto drop; icmp = (struct icmphdr*) ((void*)ip + (ip->ihl * 4)); /* Accept all the others ICMPs */ if (icmp->type != ICMP_DEST_UNREACH || icmp->code != ICMP_FRAG_NEEDED) goto accept; /* Jump to the quoted IP packet */ qip = (struct iphdr*) ((void*)icmp + sizeof(struct icmphdr)); len -= (ip->ihl * 4) + sizeof(struct icmphdr); /* quoted IP header len sanity check */ if (len < sizeof(struct iphdr)) goto drop; /* Check the signature: * We need to check with both the new and the old key */ put_sign((__u8*)&hash, qip, (__u8*)key); if (qip->id == (hash & 0xFFFF) && (!use_tos || qip->tos == ((hash >> 16) & 0xFF))) { TI_DEBUG("trusted-icmp: good ICMP received (new key)\n"); goto accept; } put_sign((__u8*)&hash, qip, (__u8*)old_key); if (qip->id == (hash & 0xFFFF) && (!use_tos || qip->tos == ((hash >> 16) & 0xFF))) { TI_DEBUG("trusted-icmp: good ICMP received (old key)\n"); goto accept; } if (net_ratelimit()) printk("trusted-icmp: Spoofed ICMP from %d.%d.%d.%d " "(ref %d.%d.%d.%d -> %d.%d.%d.%d proto %d)\n", NIPQUAD(ip->saddr), NIPQUAD(qip->saddr), NIPQUAD(qip->daddr), qip->protocol); drop: return NF_DROP; accept: return NF_ACCEPT; } /* Weaker&Faster MD4 hash function -- grepped from the Linux random driver */ /* F, G and H are basic MD4 functions: selection, majority, parity */ #define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z))) #define H(x, y, z) ((x) ^ (y) ^ (z)) /* * The generic round function. The application is so specific that * we don't bother protecting all the arguments with parens, as is generally * good macro practice, in favor of extra legibility. * Rotation is separate from addition to prevent recomputation */ #define ROUND(f, a, b, c, d, x, s) \ (a += f(b, c, d) + x, a = (a << s) | (a >> (32-s))) #define K1 0 #define K2 013240474631UL #define K3 015666365641UL /* * Basic cut-down MD4 transform. Returns only 32 bits of result. */ static __u32 halfMD4Transform (__u32 const buf[4], __u32 const in[8]) { __u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; /* Round 1 */ ROUND(F, a, b, c, d, in[0] + K1, 3); ROUND(F, d, a, b, c, in[1] + K1, 7); ROUND(F, c, d, a, b, in[2] + K1, 11); ROUND(F, b, c, d, a, in[3] + K1, 19); ROUND(F, a, b, c, d, in[4] + K1, 3); ROUND(F, d, a, b, c, in[5] + K1, 7); ROUND(F, c, d, a, b, in[6] + K1, 11); ROUND(F, b, c, d, a, in[7] + K1, 19); /* Round 2 */ ROUND(G, a, b, c, d, in[1] + K2, 3); ROUND(G, d, a, b, c, in[3] + K2, 5); ROUND(G, c, d, a, b, in[5] + K2, 9); ROUND(G, b, c, d, a, in[7] + K2, 13); ROUND(G, a, b, c, d, in[0] + K2, 3); ROUND(G, d, a, b, c, in[2] + K2, 5); ROUND(G, c, d, a, b, in[4] + K2, 9); ROUND(G, b, c, d, a, in[6] + K2, 13); /* Round 3 */ ROUND(H, a, b, c, d, in[3] + K3, 3); ROUND(H, d, a, b, c, in[7] + K3, 9); ROUND(H, c, d, a, b, in[2] + K3, 11); ROUND(H, b, c, d, a, in[6] + K3, 15); ROUND(H, a, b, c, d, in[1] + K3, 3); ROUND(H, d, a, b, c, in[5] + K3, 9); ROUND(H, c, d, a, b, in[0] + K3, 11); ROUND(H, b, c, d, a, in[4] + K3, 15); return buf[1] + b; /* "most hashed" word */ /* Alternative: return sum of all words? */ } #undef ROUND #undef F #undef G #undef H #undef K1 #undef K2 #undef K3 static void put_sign(__u8 *dest, struct iphdr *ip, __u8 *key) { __u32 secret[12], hash; secret[0] = ip->saddr; secret[1] = ip->daddr; memcpy(secret+2, key, KEY_LEN); read_lock_bh(&key_lock); hash = halfMD4Transform(secret+8, secret); read_unlock_bh(&key_lock); memcpy(dest, &hash, 4); } static void get_new_key(void) { write_lock_bh(&key_lock); memcpy(old_key, key, KEY_LEN); get_random_bytes(key, KEY_LEN); key_timeout = jiffies + KEY_TTL_TIME; write_unlock_bh(&key_lock); atomic_set(&key_sent, 0); TI_DEBUG("trusted-icmp: new key generated\n"); } int init_module(void) { int result; get_new_key(); /* At init the old and current key must be tha same */ memcpy(old_key, key, KEY_LEN); output_filter.list.next = NULL; output_filter.list.prev = NULL; output_filter.hook = output_handler; output_filter.pf = PF_INET; /* IPv4 */ output_filter.hooknum = NF_IP_POST_ROUTING; input_filter.list.next = NULL; input_filter.list.prev = NULL; input_filter.hook = input_handler; input_filter.pf = PF_INET; /* IPv4 */ input_filter.hooknum = NF_IP_LOCAL_IN; result = nf_register_hook(&output_filter); if (result) return result; result = nf_register_hook(&input_filter); if (result) { nf_unregister_hook(&output_filter); return result; } printk(KERN_INFO "trusted-icmp: module loaded\n"); return 0; } void cleanup_module(void) { nf_unregister_hook(&output_filter); nf_unregister_hook(&input_filter); printk(KERN_INFO "trusted-icmp: module removed\n"); }