diff options
| author | Alexey Kuznetsov <kuznet@ms2.inr.ac.ru> | 2002-11-04 15:18:11 -0800 |
|---|---|---|
| committer | David S. Miller <davem@nuts.ninka.net> | 2002-11-04 15:18:11 -0800 |
| commit | fefd92463dfde738bce403acf35ff05cac4dc6a0 (patch) | |
| tree | e46506bd7109d08aff73b401528566af3ea7b973 | |
| parent | b9346730a75d99030d15d041c2c4719780361f9f (diff) | |
[IPSEC]: Bug fixes and updates.
- Implement IP_IPSEC_POLICY setsockopt
- Rework input policy checks to use it
- dst->child destruction is repaired
- Fix tunnel mode IP header building.
| -rw-r--r-- | include/linux/in.h | 1 | ||||
| -rw-r--r-- | include/linux/ipsec.h | 11 | ||||
| -rw-r--r-- | include/net/dst.h | 10 | ||||
| -rw-r--r-- | include/net/protocol.h | 1 | ||||
| -rw-r--r-- | include/net/route.h | 1 | ||||
| -rw-r--r-- | include/net/sock.h | 1 | ||||
| -rw-r--r-- | include/net/xfrm.h | 15 | ||||
| -rw-r--r-- | net/core/dst.c | 55 | ||||
| -rw-r--r-- | net/ipv4/Kconfig | 4 | ||||
| -rw-r--r-- | net/ipv4/af_inet.c | 2 | ||||
| -rw-r--r-- | net/ipv4/ah.c | 8 | ||||
| -rw-r--r-- | net/ipv4/esp.c | 36 | ||||
| -rw-r--r-- | net/ipv4/ip_forward.c | 2 | ||||
| -rw-r--r-- | net/ipv4/ip_input.c | 26 | ||||
| -rw-r--r-- | net/ipv4/ip_output.c | 6 | ||||
| -rw-r--r-- | net/ipv4/ip_sockglue.c | 5 | ||||
| -rw-r--r-- | net/ipv4/raw.c | 8 | ||||
| -rw-r--r-- | net/ipv4/route.c | 9 | ||||
| -rw-r--r-- | net/ipv4/tcp_ipv4.c | 18 | ||||
| -rw-r--r-- | net/ipv4/udp.c | 11 | ||||
| -rw-r--r-- | net/ipv4/xfrm_input.c | 6 | ||||
| -rw-r--r-- | net/ipv4/xfrm_policy.c | 102 | ||||
| -rw-r--r-- | net/ipv4/xfrm_state.c | 37 | ||||
| -rw-r--r-- | net/ipv6/route.c | 6 | ||||
| -rw-r--r-- | net/ipv6/tcp_ipv6.c | 2 | ||||
| -rw-r--r-- | net/key/af_key.c | 103 | ||||
| -rw-r--r-- | net/netsyms.c | 1 |
27 files changed, 331 insertions, 156 deletions
diff --git a/include/linux/in.h b/include/linux/in.h index eebfb0306d1b..edea83ecf432 100644 --- a/include/linux/in.h +++ b/include/linux/in.h @@ -69,6 +69,7 @@ struct in_addr { #define IP_RECVTOS 13 #define IP_MTU 14 #define IP_FREEBIND 15 +#define IP_IPSEC_POLICY 16 /* BSD compatibility */ #define IP_RECVRETOPTS IP_RETOPTS diff --git a/include/linux/ipsec.h b/include/linux/ipsec.h index ae132602002a..d3c527616b5e 100644 --- a/include/linux/ipsec.h +++ b/include/linux/ipsec.h @@ -43,15 +43,4 @@ enum { #define IPSEC_REPLAYWSIZE 32 -#ifdef __KERNEL__ -struct sock; -struct sk_buff; - -static __inline__ int ipsec_sk_policy(struct sock *sk, struct sk_buff *skb) -{ - return 1; -} -#endif - - #endif /* _LINUX_IPSEC_H */ diff --git a/include/net/dst.h b/include/net/dst.h index 935fba293f31..c25cdac635ed 100644 --- a/include/net/dst.h +++ b/include/net/dst.h @@ -44,6 +44,7 @@ struct dst_entry #define DST_HOST 1 #define DST_NOXFRM 2 #define DST_NOPOLICY 4 +#define DST_NOHASH 8 unsigned long lastuse; unsigned long expires; @@ -138,8 +139,15 @@ struct dst_entry * dst_clone(struct dst_entry * dst) static inline void dst_release(struct dst_entry * dst) { - if (dst) + if (dst) { + if (atomic_read(&dst->__refcnt) < 1) { + __label__ __lbl; + printk("BUG: dst underflow %d: %p\n", + atomic_read(&dst->__refcnt), &&__lbl); +__lbl: + } atomic_dec(&dst->__refcnt); + } } /* Children define the path of the packet through the diff --git a/include/net/protocol.h b/include/net/protocol.h index 9ba874a3d18f..2063dfd55690 100644 --- a/include/net/protocol.h +++ b/include/net/protocol.h @@ -38,6 +38,7 @@ struct inet_protocol { int (*handler)(struct sk_buff *skb); void (*err_handler)(struct sk_buff *skb, u32 info); + int no_policy; }; #if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) diff --git a/include/net/route.h b/include/net/route.h index 6987a321075d..ad37024a0154 100644 --- a/include/net/route.h +++ b/include/net/route.h @@ -114,6 +114,7 @@ extern void ip_rt_advice(struct rtable **rp, int advice); extern void rt_cache_flush(int how); extern int __ip_route_output_key(struct rtable **, const struct flowi *flp); extern int ip_route_output_key(struct rtable **, struct flowi *flp); +extern int ip_route_output_flow(struct rtable **rp, struct flowi *flp, struct sock *sk, int flags); extern int ip_route_input(struct sk_buff*, u32 dst, u32 src, u8 tos, struct net_device *devin); extern unsigned short ip_rt_frag_needed(struct iphdr *iph, unsigned short new_mtu); extern void ip_rt_send_redirect(struct sk_buff *skb); diff --git a/include/net/sock.h b/include/net/sock.h index 4a4094b93d07..da4e817da7a7 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -110,6 +110,7 @@ struct sock { wait_queue_head_t *sleep; /* Sock wait queue */ struct dst_entry *dst_cache; /* Destination cache */ rwlock_t dst_lock; + struct xfrm_policy *policy[2]; atomic_t rmem_alloc; /* Receive queue bytes committed */ struct sk_buff_head receive_queue; /* Incoming packets */ atomic_t wmem_alloc; /* Transmit queue bytes committed */ diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 1d663c6839a6..2961e6616b7f 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -312,6 +312,7 @@ struct xfrm_mgr char *id; int (*notify)(struct xfrm_state *x, int event); int (*acquire)(struct xfrm_state *x, struct xfrm_tmpl *, struct xfrm_policy *xp, int dir); + struct xfrm_policy *(*compile_policy)(int opt, u8 *data, int len, int *dir); }; extern int xfrm_register_km(struct xfrm_mgr *km); @@ -397,13 +398,16 @@ secpath_put(struct sec_path *sp) __secpath_destroy(sp); } -extern int __xfrm_policy_check(int dir, struct sk_buff *skb); +extern int __xfrm_policy_check(struct sock *, int dir, struct sk_buff *skb); -static inline int xfrm_policy_check(int dir, struct sk_buff *skb) +static inline int xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) { + if (sk && sk->policy[XFRM_POLICY_IN]) + return __xfrm_policy_check(sk, dir, skb); + return !xfrm_policy_list[dir] || (skb->dst->flags & DST_NOPOLICY) || - __xfrm_policy_check(dir, skb); + __xfrm_policy_check(sk, dir, skb); } extern int __xfrm_route_forward(struct sk_buff *skb); @@ -431,6 +435,7 @@ extern int xfrm_replay_check(struct xfrm_state *x, u32 seq); extern void xfrm_replay_advance(struct xfrm_state *x, u32 seq); extern int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl); extern int xfrm4_rcv(struct sk_buff *skb); +extern int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen); struct xfrm_policy *xfrm_policy_alloc(void); extern int xfrm_policy_walk(int (*func)(struct xfrm_policy *, int, int, void*), void *); @@ -439,12 +444,12 @@ int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl); struct xfrm_policy *xfrm_policy_delete(int dir, struct xfrm_selector *sel); struct xfrm_policy *xfrm_policy_byid(int dir, u32 id, int delete); void xfrm_policy_flush(void); -int xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, - struct flowi *fl, struct dst_entry **dst_p); void xfrm_alloc_spi(struct xfrm_state *x, u32 minspi, u32 maxspi); struct xfrm_state * xfrm_find_acq(u8 mode, u16 reqid, u8 proto, u32 daddr, u32 saddr); extern void xfrm_policy_flush(void); extern void xfrm_policy_kill(struct xfrm_policy *); +extern int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol); +extern struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi *fl); extern wait_queue_head_t *km_waitq; extern void km_warn_expired(struct xfrm_state *x); diff --git a/net/core/dst.c b/net/core/dst.c index 398463b6b6bb..a566cbe03eeb 100644 --- a/net/core/dst.c +++ b/net/core/dst.c @@ -36,6 +36,7 @@ static spinlock_t dst_lock = SPIN_LOCK_UNLOCKED; static unsigned long dst_gc_timer_expires; static unsigned long dst_gc_timer_inc = DST_GC_MAX; static void dst_run_gc(unsigned long); +static void ___dst_free(struct dst_entry * dst); static struct timer_list dst_gc_timer = { data: DST_GC_MIN, function: dst_run_gc }; @@ -59,12 +60,26 @@ static void dst_run_gc(unsigned long dummy) delayed++; continue; } - if (dst->child) { - dst->child->next = dst->next; - *dstp = dst->child; - } else - *dstp = dst->next; - dst_destroy(dst); + *dstp = dst->next; + + dst = dst_destroy(dst); + if (dst) { + /* NOHASH and still referenced. Unless it is already + * on gc list, invalidate it and add to gc list. + * + * Note: this is temporary. Actually, NOHASH dst's + * must be obsoleted when parent is obsoleted. + * But we do not have state "obsoleted, but + * referenced by parent", so it is right. + */ + if (dst->obsolete > 1) + continue; + + ___dst_free(dst); + dst->next = *dstp; + *dstp = dst; + dstp = &dst->next; + } } if (!dst_garbage_list) { dst_gc_timer_inc = DST_GC_MAX; @@ -120,10 +135,8 @@ void * dst_alloc(struct dst_ops * ops) return dst; } -void __dst_free(struct dst_entry * dst) +static void ___dst_free(struct dst_entry * dst) { - spin_lock_bh(&dst_lock); - /* The first case (dev==NULL) is required, when protocol module is unloaded. */ @@ -132,6 +145,12 @@ void __dst_free(struct dst_entry * dst) dst->output = dst_blackhole; } dst->obsolete = 2; +} + +void __dst_free(struct dst_entry * dst) +{ + spin_lock_bh(&dst_lock); + ___dst_free(dst); dst->next = dst_garbage_list; dst_garbage_list = dst; if (dst_gc_timer_inc > DST_GC_INC) { @@ -141,7 +160,6 @@ void __dst_free(struct dst_entry * dst) dst_gc_timer.expires = jiffies + dst_gc_timer_expires; add_timer(&dst_gc_timer); } - spin_unlock_bh(&dst_lock); } @@ -177,10 +195,19 @@ again: kmem_cache_free(dst->ops->kmem_cachep, dst); dst = child; - if (dst && !atomic_read(&dst->__refcnt)) - goto again; - - return dst; + if (dst) { + if (atomic_dec_and_test(&dst->__refcnt)) { + /* We were real parent of this dst, so kill child. */ + if (dst->flags&DST_NOHASH) + goto again; + } else { + /* Child is still referenced, return it for freeing. */ + if (dst->flags&DST_NOHASH) + return dst; + /* Child is still in his hash table */ + } + } + return NULL; } static int dst_dev_event(struct notifier_block *this, unsigned long event, void *ptr) diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig index 4d5c8464b120..e8f334059cdd 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -349,14 +349,14 @@ config SYN_COOKIES If unsure, say N. config INET_AH - bool "IP: AH transformation" + tristate "IP: AH transformation" ---help--- Support for IPsec AH. If unsure, say Y. config INET_ESP - bool "IP: ESP transformation" + tristate "IP: ESP transformation" ---help--- Support for IPsec ESP. diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index 41909c8b507f..bdce83a90c6e 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -1038,11 +1038,13 @@ static struct inet_protocol igmp_protocol = { static struct inet_protocol tcp_protocol = { .handler = tcp_v4_rcv, .err_handler = tcp_v4_err, + .no_policy = 1, }; static struct inet_protocol udp_protocol = { .handler = udp_rcv, .err_handler = udp_err, + .no_policy = 1, }; static struct inet_protocol icmp_protocol = { diff --git a/net/ipv4/ah.c b/net/ipv4/ah.c index 57fe5d443ee9..178baf73bac2 100644 --- a/net/ipv4/ah.c +++ b/net/ipv4/ah.c @@ -176,12 +176,13 @@ int ah_output(struct sk_buff *skb) iph = skb->nh.iph; if (x->props.mode) { top_iph = (struct iphdr*)skb_push(skb, x->props.header_len); - top_iph->ihl = 4; - top_iph->version = 5; + top_iph->ihl = 5; + top_iph->version = 4; top_iph->tos = 0; top_iph->tot_len = htons(skb->len); - top_iph->id = inet_getid(((struct rtable*)dst)->peer, 0); top_iph->frag_off = 0; + if (!(iph->frag_off&htons(IP_DF))) + __ip_select_ident(top_iph, dst, 0); top_iph->ttl = 0; top_iph->protocol = IPPROTO_AH; top_iph->check = 0; @@ -379,6 +380,7 @@ static struct xfrm_type ah_type = static struct inet_protocol ah4_protocol = { .handler = xfrm4_rcv, .err_handler = ah4_err, + .no_policy = 1, }; int __init ah4_init(void) diff --git a/net/ipv4/esp.c b/net/ipv4/esp.c index cbaf651e8b51..509e88df29cc 100644 --- a/net/ipv4/esp.c +++ b/net/ipv4/esp.c @@ -187,33 +187,13 @@ esp_hmac_digest(struct esp_data *esp, struct sk_buff *skb, int offset, int len, u8 *auth_data) { struct crypto_tfm *tfm = esp->auth.tfm; - int i; - char tmp_digest[crypto_tfm_alg_digestsize(tfm)]; - char pad[crypto_tfm_alg_blocksize(tfm)]; + char digest[crypto_tfm_alg_digestsize(tfm)]; memset(auth_data, 0, esp->auth.authlen); - - memset(pad, 0, sizeof(pad)); - memcpy(pad, esp->auth.key, esp->auth.key_len); - for (i = 0; i < crypto_tfm_alg_blocksize(tfm); i++) - pad[i] ^= 0x36; - - crypto_digest_init(tfm); - tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, pad, sizeof(pad)); + crypto_hmac_init(tfm, esp->auth.key, &esp->auth.key_len); skb_digest_walk(skb, tfm, offset, len); - tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, skb->data+offset, len); - crypto_digest_final(tfm, tmp_digest); - - memset(pad, 0, sizeof(pad)); - memcpy(pad, esp->auth.key, esp->auth.key_len); - - for (i = 0; i < crypto_tfm_alg_blocksize(tfm); i++) - pad[i] ^= 0x5c; - - crypto_digest_init(tfm); - tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, pad, sizeof(pad)); - tfm->__crt_alg->cra_digest.dia_update(tfm->crt_ctx, tmp_digest, crypto_tfm_alg_digestsize(tfm)); - crypto_digest_final(tfm, auth_data); + crypto_hmac_final(tfm, esp->auth.key, &esp->auth.key_len, digest); + memcpy(auth_data, digest, crypto_tfm_alg_digestsize(tfm)); } /* Check that skb data bits are writable. If they are not, copy data @@ -390,12 +370,13 @@ int esp_output(struct sk_buff *skb) top_iph = (struct iphdr*)skb_push(skb, x->props.header_len); esph = (struct ip_esp_hdr*)(top_iph+1); *(u8*)(trailer->tail - 1) = IPPROTO_IP; - top_iph->ihl = 4; - top_iph->version = 5; + top_iph->ihl = 5; + top_iph->version = 4; top_iph->tos = iph->tos; /* DS disclosed */ top_iph->tot_len = htons(skb->len + esp->auth.authlen); - top_iph->id = inet_getid(((struct rtable*)dst)->peer, 0); top_iph->frag_off = iph->frag_off&htons(IP_DF); + if (!(top_iph->frag_off)) + ip_select_ident(top_iph, dst, 0); top_iph->ttl = iph->ttl; /* TTL disclosed */ top_iph->protocol = IPPROTO_ESP; top_iph->check = 0; @@ -668,6 +649,7 @@ static struct xfrm_type esp_type = static struct inet_protocol esp4_protocol = { .handler = xfrm4_rcv, .err_handler = esp4_err, + .no_policy = 1, }; int __init esp4_init(void) diff --git a/net/ipv4/ip_forward.c b/net/ipv4/ip_forward.c index 1b1ce0a33507..f9fa6a1cefcc 100644 --- a/net/ipv4/ip_forward.c +++ b/net/ipv4/ip_forward.c @@ -60,7 +60,7 @@ int ip_forward(struct sk_buff *skb) struct rtable *rt; /* Route we use */ struct ip_options * opt = &(IPCB(skb)->opt); - if (!xfrm_policy_check(XFRM_POLICY_FWD, skb)) + if (!xfrm_policy_check(NULL, XFRM_POLICY_FWD, skb)) goto drop; if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb)) diff --git a/net/ipv4/ip_input.c b/net/ipv4/ip_input.c index 4d63141e8edb..6c67950d4b4b 100644 --- a/net/ipv4/ip_input.c +++ b/net/ipv4/ip_input.c @@ -223,15 +223,6 @@ static inline int ip_local_deliver_finish(struct sk_buff *skb) struct inet_protocol *ipprot; resubmit: - - /* Fuck... This IS ugly. */ - if (protocol != IPPROTO_AH && - protocol != IPPROTO_ESP && - !xfrm_policy_check(XFRM_POLICY_IN, skb)) { - kfree_skb(skb); - return 0; - } - hash = protocol & (MAX_INET_PROTOS - 1); raw_sk = raw_v4_htable[hash]; @@ -242,7 +233,14 @@ static inline int ip_local_deliver_finish(struct sk_buff *skb) raw_v4_input(skb, skb->nh.iph, hash); if ((ipprot = inet_protos[hash]) != NULL) { - int ret = ipprot->handler(skb); + int ret; + + if (!ipprot->no_policy && + !xfrm_policy_check(NULL, XFRM_POLICY_IN, skb)) { + kfree_skb(skb); + return 0; + } + ret = ipprot->handler(skb); if (ret < 0) { protocol = -ret; goto resubmit; @@ -250,9 +248,11 @@ static inline int ip_local_deliver_finish(struct sk_buff *skb) IP_INC_STATS_BH(IpInDelivers); } else { if (!raw_sk) { - IP_INC_STATS_BH(IpInUnknownProtos); - icmp_send(skb, ICMP_DEST_UNREACH, - ICMP_PROT_UNREACH, 0); + if (xfrm_policy_check(NULL, XFRM_POLICY_IN, skb)) { + IP_INC_STATS_BH(IpInUnknownProtos); + icmp_send(skb, ICMP_DEST_UNREACH, + ICMP_PROT_UNREACH, 0); + } } else IP_INC_STATS_BH(IpInDelivers); kfree_skb(skb); diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c index 6f979dbe7f36..2ea609f62fdb 100644 --- a/net/ipv4/ip_output.c +++ b/net/ipv4/ip_output.c @@ -321,7 +321,7 @@ int ip_queue_xmit(struct sk_buff *skb) * keep trying until route appears or the connection times * itself out. */ - if (ip_route_output_key(&rt, &fl)) + if (ip_route_output_flow(&rt, &fl, sk, 0)) goto no_route; } __sk_dst_set(sk, &rt->u.dst); @@ -1220,6 +1220,10 @@ void ip_send_reply(struct sock *sk, struct sk_buff *skb, struct ip_reply_arg *ar { .daddr = daddr, .saddr = rt->rt_spec_dst, .tos = RT_TOS(skb->nh.iph->tos) } }, + /* Not quite clean, but right. */ + .uli_u = { .ports = + { .sport = skb->h.th->dest, + .dport = skb->h.th->source } }, .proto = sk->protocol }; if (ip_route_output_key(&rt, &fl)) return; diff --git a/net/ipv4/ip_sockglue.c b/net/ipv4/ip_sockglue.c index cab255f23f1f..252acda965e8 100644 --- a/net/ipv4/ip_sockglue.c +++ b/net/ipv4/ip_sockglue.c @@ -36,6 +36,7 @@ #include <linux/route.h> #include <linux/mroute.h> #include <net/route.h> +#include <net/xfrm.h> #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) #include <net/transp_v6.h> #endif @@ -624,6 +625,10 @@ int ip_setsockopt(struct sock *sk, int level, int optname, char *optval, int opt inet->freebind = !!val; break; + case IP_IPSEC_POLICY: + err = xfrm_user_policy(sk, optname, optval, optlen); + break; + default: #ifdef CONFIG_NETFILTER err = nf_setsockopt(sk, PF_INET, optname, optval, diff --git a/net/ipv4/raw.c b/net/ipv4/raw.c index f4bbe20272fa..263d39500558 100644 --- a/net/ipv4/raw.c +++ b/net/ipv4/raw.c @@ -64,6 +64,7 @@ #include <net/raw.h> #include <net/inet_common.h> #include <net/checksum.h> +#include <net/xfrm.h> #include <linux/netfilter_ipv4.h> struct sock *raw_v4_htable[RAWV4_HTABLE_SIZE]; @@ -237,6 +238,11 @@ static int raw_rcv_skb(struct sock * sk, struct sk_buff * skb) int raw_rcv(struct sock *sk, struct sk_buff *skb) { + if (xfrm_policy_check(sk, XFRM_POLICY_IN, skb)) { + kfree_skb(skb); + return NET_RX_DROP; + } + skb_push(skb, skb->data - skb->nh.raw); raw_rcv_skb(sk, skb); @@ -425,7 +431,7 @@ static int raw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, .saddr = saddr, .tos = tos } }, .proto = IPPROTO_RAW }; - err = ip_route_output_key(&rt, &fl); + err = ip_route_output_flow(&rt, &fl, sk, msg->msg_flags&MSG_DONTWAIT); } if (err) goto done; diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 7b103c6e9d97..4f10bf4c4f68 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -2034,6 +2034,15 @@ int ip_route_output_key(struct rtable **rp, struct flowi *flp) return flp->proto ? xfrm_lookup((struct dst_entry**)rp, flp, NULL, 0) : 0; } +int ip_route_output_flow(struct rtable **rp, struct flowi *flp, struct sock *sk, int flags) +{ + int err; + + if ((err = __ip_route_output_key(rp, flp)) != 0) + return err; + return flp->proto ? xfrm_lookup((struct dst_entry**)rp, flp, sk, flags) : 0; +} + static int rt_fill_info(struct sk_buff *skb, u32 pid, u32 seq, int event, int nowait) { diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index fbd5ea082bea..5fae95496824 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c @@ -64,11 +64,11 @@ #include <net/tcp.h> #include <net/ipv6.h> #include <net/inet_common.h> +#include <net/xfrm.h> #include <linux/inet.h> #include <linux/ipv6.h> #include <linux/stddef.h> -#include <linux/ipsec.h> extern int sysctl_ip_dynaddr; extern int sysctl_ip_default_ttl; @@ -1299,7 +1299,7 @@ static struct dst_entry* tcp_v4_route_req(struct sock *sk, { .sport = inet_sk(sk)->sport, .dport = req->rmt_port } } }; - if (ip_route_output_key(&rt, &fl)) { + if (ip_route_output_flow(&rt, &fl, sk, 0)) { IP_INC_STATS_BH(IpOutNoRoutes); return NULL; } @@ -1796,12 +1796,12 @@ int tcp_v4_rcv(struct sk_buff *skb) goto no_tcp_socket; process: - if (!ipsec_sk_policy(sk, skb)) - goto discard_and_relse; - if (sk->state == TCP_TIME_WAIT) goto do_time_wait; + if (!xfrm_policy_check(sk, XFRM_POLICY_IN, skb)) + goto discard_and_relse; + skb->dev = NULL; bh_lock_sock(sk); @@ -1818,6 +1818,9 @@ process: return ret; no_tcp_socket: + if (!xfrm_policy_check(NULL, XFRM_POLICY_IN, skb)) + goto discard_it; + if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) { bad_packet: TCP_INC_STATS_BH(TcpInErrs); @@ -1835,6 +1838,9 @@ discard_and_relse: goto discard_it; do_time_wait: + if (!xfrm_policy_check(NULL, XFRM_POLICY_IN, skb)) + goto discard_and_relse; + if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) { TCP_INC_STATS_BH(TcpInErrs); goto discard_and_relse; @@ -1950,7 +1956,7 @@ int tcp_v4_rebuild_header(struct sock *sk) { .sport = inet->sport, .dport = inet->dport } } }; - err = ip_route_output_key(&rt, &fl); + err = ip_route_output_flow(&rt, &fl, sk, 0); } if (!err) { __sk_dst_set(sk, &rt->u.dst); diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index c8bb729ab8ba..9eb378821111 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -104,6 +104,7 @@ #include <net/route.h> #include <net/inet_common.h> #include <net/checksum.h> +#include <net/xfrm.h> /* * Snmp MIB for the UDP layer @@ -600,7 +601,7 @@ int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, .uli_u = { .ports = { .sport = inet->sport, .dport = dport } } }; - err = ip_route_output_key(&rt, &fl); + err = ip_route_output_flow(&rt, &fl, sk, msg->msg_flags&MSG_DONTWAIT); if (err) goto out; @@ -943,6 +944,10 @@ static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb) /* * Charge it to the socket, dropping if the queue is full. */ + if (!xfrm_policy_check(NULL, XFRM_POLICY_IN, skb)) { + kfree_skb(skb); + return -1; + } #if defined(CONFIG_FILTER) if (sk->filter && skb->ip_summed != CHECKSUM_UNNECESSARY) { @@ -1070,6 +1075,9 @@ int udp_rcv(struct sk_buff *skb) return 0; } + if (!xfrm_policy_check(NULL, XFRM_POLICY_IN, skb)) + goto drop; + /* No socket. Drop packet silently, if checksum is wrong */ if (udp_checksum_complete(skb)) goto csum_error; @@ -1110,6 +1118,7 @@ csum_error: NIPQUAD(daddr), ntohs(uh->dest), ulen)); +drop: UDP_INC_STATS_BH(UdpInErrors); kfree_skb(skb); return(0); diff --git a/net/ipv4/xfrm_input.c b/net/ipv4/xfrm_input.c index 846c8278e8e6..e94f6759d0ff 100644 --- a/net/ipv4/xfrm_input.c +++ b/net/ipv4/xfrm_input.c @@ -123,8 +123,10 @@ int xfrm4_rcv(struct sk_buff *skb) skb->sp->len += xfrm_nr; if (decaps) { - dst_release(skb->dst); - skb->dst = NULL; + if (!(skb->dev->flags&IFF_LOOPBACK)) { + dst_release(skb->dst); + skb->dst = NULL; + } netif_rx(skb); return 0; } else { diff --git a/net/ipv4/xfrm_policy.c b/net/ipv4/xfrm_policy.c index 9d76f495f608..3d27f9ede817 100644 --- a/net/ipv4/xfrm_policy.c +++ b/net/ipv4/xfrm_policy.c @@ -397,7 +397,7 @@ out: struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl) { struct xfrm_policy *pol; - // Not now :-) u64 now = (unsigned long)xtime.tv_sec; + /* Not now :-) u64 now = (unsigned long)xtime.tv_sec; */ read_lock(&xfrm_policy_lock); for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) { @@ -412,6 +412,40 @@ struct xfrm_policy *xfrm_policy_lookup(int dir, struct flowi *fl) return pol; } +struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi *fl) +{ + struct xfrm_policy *pol; + /* Not now :-) u64 now = (unsigned long)xtime.tv_sec; */ + + read_lock(&xfrm_policy_lock); + for (pol = sk->policy[dir]; pol; pol = pol->next) { + struct xfrm_selector *sel = &pol->selector; + + if (xfrm4_selector_match(sel, fl) /* XXX && XXX now < pol->validtime */) { + atomic_inc(&pol->refcnt); + break; + } + } + read_unlock(&xfrm_policy_lock); + return pol; +} + +int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol) +{ + struct xfrm_policy *old_pol; + + write_lock_bh(&xfrm_policy_lock); + old_pol = sk->policy[dir]; + sk->policy[dir] = pol; + write_unlock_bh(&xfrm_policy_lock); + + if (old_pol) { + xfrm_policy_kill(old_pol); + xfrm_pol_put(old_pol); + } + return 0; +} + /* Resolve list of templates for the flow, given policy. */ static int @@ -482,12 +516,13 @@ static int xfrm_bundle_ok(struct xfrm_dst *xdst, struct flowi *fl) * all the metrics... Shortly, bundle a bundle. */ -int +static int xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, struct flowi *fl, struct dst_entry **dst_p) { struct dst_entry *dst, *dst_prev; - struct rtable *rt = (struct rtable*)(*dst_p); + struct rtable *rt0 = (struct rtable*)(*dst_p); + struct rtable *rt = rt0; u32 remote = fl->fl4_dst; u32 local = fl->fl4_src; int i; @@ -507,8 +542,11 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, dst1->xfrm = xfrm[i]; if (!dst) dst = dst1; - else + else { dst_prev->child = dst1; + dst1->flags |= DST_NOHASH; + dst_clone(dst1); + } dst_prev = dst1; if (xfrm[i]->props.mode) { remote = xfrm[i]->id.daddr.xfrm4_addr; @@ -523,11 +561,11 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, .saddr = local } } }; - err = ip_route_output_key(&rt, &fl_tunnel); + err = __ip_route_output_key(&rt, &fl_tunnel); if (err) goto error; - dst_release(*dst_p); - *dst_p = &rt->u.dst; + } else { + dst_clone(&rt->u.dst); } dst_prev->child = &rt->u.dst; for (dst_prev = dst; dst_prev != &rt->u.dst; dst_prev = dst_prev->child) { @@ -537,7 +575,7 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, dst_prev->dev = rt->u.dst.dev; if (rt->u.dst.dev) dev_hold(rt->u.dst.dev); - dst_prev->flags = DST_HOST; + dst_prev->flags |= DST_HOST; dst_prev->lastuse = jiffies; dst_prev->header_len = header_len; memcpy(&dst_prev->metrics, &rt->u.dst.metrics, sizeof(dst_prev->metrics)); @@ -550,13 +588,14 @@ xfrm_bundle_create(struct xfrm_policy *policy, struct xfrm_state **xfrm, if (rt->peer) atomic_inc(&rt->peer->refcnt); x->u.rt.peer = rt->peer; + /* Sheit... I remember I did this right. Apparently, + * it was magically lost, so this code needs audit */ x->u.rt.rt_flags = rt->rt_flags; x->u.rt.rt_type = rt->rt_type; - x->u.rt.rt_src = rt->rt_src; - x->u.rt.rt_src = rt->rt_src; - x->u.rt.rt_dst = rt->rt_dst; + x->u.rt.rt_src = rt0->rt_src; + x->u.rt.rt_dst = rt0->rt_dst; x->u.rt.rt_gateway = rt->rt_gateway; - x->u.rt.rt_spec_dst = rt->rt_spec_dst; + x->u.rt.rt_spec_dst = rt0->rt_spec_dst; header_len -= x->u.dst.xfrm->props.header_len; } *dst_p = dst; @@ -583,18 +622,24 @@ int xfrm_lookup(struct dst_entry **dst_p, struct flowi *fl, int err; u32 genid; - /* To accelerate a bit... */ - if ((rt->u.dst.flags & DST_NOXFRM) || !xfrm_policy_list[XFRM_POLICY_OUT]) - return 0; - fl->oif = rt->u.dst.dev->ifindex; fl->fl4_src = rt->rt_src; restart: genid = xfrm_policy_genid; - policy = flow_lookup(XFRM_POLICY_OUT, fl); - if (!policy) - return 0; + policy = NULL; + if (sk && sk->policy[1]) + policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl); + + if (!policy) { + /* To accelerate a bit... */ + if ((rt->u.dst.flags & DST_NOXFRM) || !xfrm_policy_list[XFRM_POLICY_OUT]) + return 0; + + policy = flow_lookup(XFRM_POLICY_OUT, fl); + if (!policy) + return 0; + } switch (policy->action) { case XFRM_POLICY_BLOCK: @@ -677,14 +722,13 @@ restart: write_unlock_bh(&policy->lock); xfrm_pol_put(policy); - if (dst) { - dst_release(dst); + if (dst) dst_free(dst); - } goto restart; } dst->next = policy->bundles; policy->bundles = dst; + dst_clone(dst); write_unlock_bh(&policy->lock); } *dst_p = dst; @@ -773,7 +817,7 @@ _decode_session(struct sk_buff *skb, struct flowi *fl) fl->fl4_src = iph->saddr; } -int __xfrm_policy_check(int dir, struct sk_buff *skb) +int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb) { struct xfrm_policy *pol; struct flowi fl; @@ -789,7 +833,12 @@ int __xfrm_policy_check(int dir, struct sk_buff *skb) } } - pol = flow_lookup(dir, &fl); + pol = NULL; + if (sk && sk->policy[dir]) + pol =xfrm_sk_policy_lookup(sk, dir, &fl); + + if (!pol) + pol = flow_lookup(dir, &fl); if (!pol) return 1; @@ -925,10 +974,13 @@ static int xfrm4_get_mss(struct dst_entry *dst, u32 mtu) do { struct xfrm_state *x = d->xfrm; if (x) { - if (x->type->get_max_size) + spin_lock_bh(&x->lock); + if (x->km.state == XFRM_STATE_VALID && + x->type && x->type->get_max_size) m = x->type->get_max_size(d->xfrm, m); else m += x->props.header_len; + spin_unlock_bh(&x->lock); } } while ((d = d->child) != NULL); diff --git a/net/ipv4/xfrm_state.c b/net/ipv4/xfrm_state.c index 9adb4dc5a27d..c672c9bea6b8 100644 --- a/net/ipv4/xfrm_state.c +++ b/net/ipv4/xfrm_state.c @@ -508,6 +508,43 @@ int km_query(struct xfrm_state *x, struct xfrm_tmpl *t, struct xfrm_policy *pol) return err; } +int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen) +{ + int err; + u8 *data; + struct xfrm_mgr *km; + struct xfrm_policy *pol = NULL; + + if (optlen <= 0 || optlen > PAGE_SIZE) + return -EMSGSIZE; + + data = kmalloc(optlen, GFP_KERNEL); + if (!data) + return -ENOMEM; + + err = -EFAULT; + if (copy_from_user(data, optval, optlen)) + goto out; + + err = -EINVAL; + read_lock(&xfrm_km_lock); + list_for_each_entry(km, &xfrm_km_list, list) { + pol = km->compile_policy(optname, data, optlen, &err); + if (err >= 0) + break; + } + read_unlock(&xfrm_km_lock); + + if (err >= 0) { + xfrm_sk_policy_insert(sk, err, pol); + err = 0; + } + +out: + kfree(data); + return err; +} + int xfrm_register_km(struct xfrm_mgr *km) { write_lock_bh(&xfrm_km_lock); diff --git a/net/ipv6/route.c b/net/ipv6/route.c index 6051c0677f49..1ff9ddc6ccdd 100644 --- a/net/ipv6/route.c +++ b/net/ipv6/route.c @@ -107,7 +107,8 @@ struct rt6_info ip6_null_entry = { .error = -ENETUNREACH, .input = ip6_pkt_discard, .output = ip6_pkt_discard, - .ops = &ip6_dst_ops + .ops = &ip6_dst_ops, + .path = (struct dst_entry*)&ip6_null_entry, } }, .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP), @@ -1252,6 +1253,9 @@ static int rt6_mtu_change_route(struct rt6_info *rt, void *p_arg) */ idev = __in6_dev_get(arg->dev); + if (idev == NULL) + return 0; + /* For administrative MTU increase, there is no way to discover IPv6 PMTU increase, so PMTU increase should be updated here. Since RFC 1981 doesn't include administrative MTU increase diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index ecc85a8df356..e041453d1d84 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -1638,8 +1638,6 @@ static int tcp_v6_rcv(struct sk_buff *skb) goto no_tcp_socket; process: - if(!ipsec_sk_policy(sk,skb)) - goto discard_and_relse; if(sk->state == TCP_TIME_WAIT) goto do_time_wait; diff --git a/net/key/af_key.c b/net/key/af_key.c index 4eee93ea6e91..0a953121af2a 100644 --- a/net/key/af_key.c +++ b/net/key/af_key.c @@ -1080,38 +1080,6 @@ static int pfkey_acquire(struct sock *sk, struct sk_buff *skb, struct sadb_msg * } -static int pfkey_update(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs) -{ - struct sk_buff *resp_skb; - int err; - - if (!ext_hdrs[SADB_EXT_SA-1] || - !present_and_same_family(ext_hdrs[SADB_EXT_ADDRESS_SRC-1], - ext_hdrs[SADB_EXT_ADDRESS_DST-1])) - return -EINVAL; - - if (hdr->sadb_msg_satype == SADB_SATYPE_ESP && - !ext_hdrs[SADB_EXT_KEY_ENCRYPT-1]) - return -EINVAL; - - if (hdr->sadb_msg_satype == SADB_SATYPE_AH && - !ext_hdrs[SADB_EXT_KEY_AUTH-1]) - return -EINVAL; - - if (!!ext_hdrs[SADB_EXT_LIFETIME_HARD-1] != - !!ext_hdrs[SADB_EXT_LIFETIME_SOFT-1]) - return -EINVAL; - - /* XXX Implement XXX */ - resp_skb = NULL; - err = -EOPNOTSUPP; - - if (!err) - pfkey_broadcast(resp_skb, GFP_KERNEL, BROADCAST_ONE, sk); - - return err; -} - static int pfkey_add(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, void **ext_hdrs) { struct sk_buff *out_skb; @@ -1125,7 +1093,7 @@ static int pfkey_add(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, /* XXX there is race condition */ x1 = pfkey_xfrm_state_lookup(hdr, ext_hdrs); - if (x1 != NULL) { + if (x1 && hdr->sadb_msg_type == SADB_ADD) { x->km.state = XFRM_STATE_DEAD; xfrm_state_put(x); xfrm_state_put(x1); @@ -1134,6 +1102,11 @@ static int pfkey_add(struct sock *sk, struct sk_buff *skb, struct sadb_msg *hdr, xfrm_state_insert(x); + if (x1 && hdr->sadb_msg_type != SADB_ADD) { + xfrm_state_delete(x1); + xfrm_state_put(x1); + } + out_skb = pfkey_xfrm_state2msg(x, 0, 3); if (IS_ERR(out_skb)) return PTR_ERR(out_skb); /* XXX Should we return 0 here ? */ @@ -1166,7 +1139,6 @@ static int pfkey_delete(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h return -ESRCH; xfrm_state_delete(x); - xfrm_state_put(x); pfkey_broadcast(skb_clone(skb, GFP_KERNEL), GFP_KERNEL, @@ -1382,7 +1354,7 @@ parse_ipsecrequest(struct xfrm_policy *xp, struct sadb_x_ipsecrequest *rq) struct sockaddr_in *addr; if (xp->xfrm_nr >= XFRM_MAX_DEPTH) - return -EINVAL; + return -ELOOP; if (rq->sadb_x_ipsecrequest_mode == 0) return -EINVAL; @@ -1408,7 +1380,7 @@ parse_ipsecrequests(struct xfrm_policy *xp, struct sadb_x_policy *pol) int len = pol->sadb_x_policy_len*8 - sizeof(struct sadb_x_policy); struct sadb_x_ipsecrequest *rq = (void*)(pol+1); - while (len > sizeof(struct sadb_x_ipsecrequest)) { + while (len >= sizeof(struct sadb_x_ipsecrequest)) { if ((err = parse_ipsecrequest(xp, rq)) < 0) return err; len -= rq->sadb_x_ipsecrequest_len; @@ -1647,7 +1619,7 @@ static int pfkey_spdadd(struct sock *sk, struct sk_buff *skb, struct sadb_msg *h pfkey_broadcast(out_skb, GFP_ATOMIC, BROADCAST_ALL, sk); return 0; - out: +out: kfree(xp); return err; } @@ -1823,7 +1795,7 @@ typedef int (*pfkey_handler)(struct sock *sk, struct sk_buff *skb, static pfkey_handler pfkey_funcs[SADB_MAX + 1] = { [SADB_RESERVED] = pfkey_reserved, [SADB_GETSPI] = pfkey_getspi, - [SADB_UPDATE] = pfkey_update, + [SADB_UPDATE] = pfkey_add, [SADB_ADD] = pfkey_add, [SADB_DELETE] = pfkey_delete, [SADB_GET] = pfkey_get, @@ -2084,6 +2056,58 @@ static int pfkey_send_acquire(struct xfrm_state *x, struct xfrm_tmpl *t, struct return 0; } +static struct xfrm_policy *pfkey_compile_policy(int opt, u8 *data, int len, int *dir) +{ + struct xfrm_policy *xp; + struct sadb_x_policy *pol = (struct sadb_x_policy*)data; + + if (opt != IP_IPSEC_POLICY) { + *dir = -EOPNOTSUPP; + return NULL; + } + + *dir = -EINVAL; + + if (len < sizeof(struct sadb_x_policy) || + pol->sadb_x_policy_len*8 > len || + pol->sadb_x_policy_type > IPSEC_POLICY_BYPASS || + (!pol->sadb_x_policy_dir || pol->sadb_x_policy_dir > IPSEC_DIR_OUTBOUND)) + return NULL; + + xp = xfrm_policy_alloc(); + if (xp == NULL) { + *dir = -ENOBUFS; + return NULL; + } + + xp->index = pol->sadb_x_policy_id; + xp->action = (pol->sadb_x_policy_type == IPSEC_POLICY_DISCARD ? + XFRM_POLICY_BLOCK : XFRM_POLICY_ALLOW); + + xp->curlft.add_time = (unsigned long)xtime.tv_sec; + xp->curlft.use_time = (unsigned long)xtime.tv_sec; + xp->lft.soft_byte_limit = -1; + xp->lft.hard_byte_limit = -1; + xp->lft.soft_packet_limit = -1; + xp->lft.hard_packet_limit = -1; + xp->lft.soft_add_expires_seconds = -1; + xp->lft.hard_add_expires_seconds = -1; + xp->lft.soft_use_expires_seconds = -1; + xp->lft.hard_use_expires_seconds = -1; + + xp->xfrm_nr = 0; + if (pol->sadb_x_policy_type == IPSEC_POLICY_IPSEC && + (*dir = parse_ipsecrequests(xp, pol)) < 0) + goto out; + + *dir = pol->sadb_x_policy_dir-1; + return xp; + +out: + kfree(xp); + return NULL; +} + static int pfkey_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm) @@ -2246,7 +2270,8 @@ static struct xfrm_mgr pfkeyv2_mgr = { .id = "pfkeyv2", .notify = pfkey_send_notify, - .acquire = pfkey_send_acquire + .acquire = pfkey_send_acquire, + .compile_policy = pfkey_compile_policy, }; static void __exit ipsec_pfkey_exit(void) diff --git a/net/netsyms.c b/net/netsyms.c index 11f5d2ea2886..7a95b134aa7b 100644 --- a/net/netsyms.c +++ b/net/netsyms.c @@ -286,7 +286,6 @@ EXPORT_SYMBOL(dlci_ioctl_hook); EXPORT_SYMBOL(xfrm_policy_alloc); EXPORT_SYMBOL(__xfrm_policy_destroy); EXPORT_SYMBOL(xfrm_policy_lookup); -EXPORT_SYMBOL(xfrm_bundle_create); EXPORT_SYMBOL(xfrm_lookup); EXPORT_SYMBOL(__xfrm_policy_check); EXPORT_SYMBOL(__xfrm_route_forward); |
