2007-02-09 22:25:29 +08:00
|
|
|
/*
|
2005-04-17 06:20:36 +08:00
|
|
|
* xfrm_policy.c
|
|
|
|
*
|
|
|
|
* Changes:
|
|
|
|
* Mitsuru KANDA @USAGI
|
|
|
|
* Kazunori MIYAZAWA @USAGI
|
|
|
|
* Kunihiro Ishiguro <kunihiro@ipinfusion.com>
|
|
|
|
* IPv6 support
|
|
|
|
* Kazunori MIYAZAWA @USAGI
|
|
|
|
* YOSHIFUJI Hideaki
|
|
|
|
* Split up af-specific portion
|
|
|
|
* Derek Atkins <derek@ihtfp.com> Add the post_input processor
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
*
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
|
|
|
|
2007-11-14 13:37:28 +08:00
|
|
|
#include <linux/err.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/kmod.h>
|
|
|
|
#include <linux/list.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/notifier.h>
|
|
|
|
#include <linux/netdevice.h>
|
2006-01-07 15:06:30 +08:00
|
|
|
#include <linux/netfilter.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/module.h>
|
2006-08-24 19:45:07 +08:00
|
|
|
#include <linux/cache.h>
|
2007-12-21 12:49:33 +08:00
|
|
|
#include <linux/audit.h>
|
2007-12-12 01:32:34 +08:00
|
|
|
#include <net/dst.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <net/xfrm.h>
|
|
|
|
#include <net/ip.h>
|
2007-12-21 12:42:57 +08:00
|
|
|
#ifdef CONFIG_XFRM_STATISTICS
|
|
|
|
#include <net/snmp.h>
|
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 19:50:50 +08:00
|
|
|
#include "xfrm_hash.h"
|
|
|
|
|
2008-09-10 07:08:51 +08:00
|
|
|
int sysctl_xfrm_larval_drop __read_mostly = 1;
|
2007-05-25 09:17:54 +08:00
|
|
|
|
2007-12-21 12:42:57 +08:00
|
|
|
#ifdef CONFIG_XFRM_STATISTICS
|
|
|
|
DEFINE_SNMP_STAT(struct linux_xfrm_mib, xfrm_statistics) __read_mostly;
|
|
|
|
EXPORT_SYMBOL(xfrm_statistics);
|
|
|
|
#endif
|
|
|
|
|
2006-03-21 14:33:17 +08:00
|
|
|
DEFINE_MUTEX(xfrm_cfg_mutex);
|
|
|
|
EXPORT_SYMBOL(xfrm_cfg_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
static DEFINE_RWLOCK(xfrm_policy_lock);
|
|
|
|
|
|
|
|
static DEFINE_RWLOCK(xfrm_policy_afinfo_lock);
|
|
|
|
static struct xfrm_policy_afinfo *xfrm_policy_afinfo[NPROTO];
|
|
|
|
|
2006-12-07 12:33:20 +08:00
|
|
|
static struct kmem_cache *xfrm_dst_cache __read_mostly;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
static HLIST_HEAD(xfrm_policy_gc_list);
|
2005-04-17 06:20:36 +08:00
|
|
|
static DEFINE_SPINLOCK(xfrm_policy_gc_lock);
|
|
|
|
|
|
|
|
static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family);
|
|
|
|
static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo);
|
2007-12-12 01:32:34 +08:00
|
|
|
static void xfrm_init_pmtu(struct dst_entry *dst);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-11-09 14:46:26 +08:00
|
|
|
static inline int
|
|
|
|
__xfrm4_selector_match(struct xfrm_selector *sel, struct flowi *fl)
|
|
|
|
{
|
|
|
|
return addr_match(&fl->fl4_dst, &sel->daddr, sel->prefixlen_d) &&
|
|
|
|
addr_match(&fl->fl4_src, &sel->saddr, sel->prefixlen_s) &&
|
|
|
|
!((xfrm_flowi_dport(fl) ^ sel->dport) & sel->dport_mask) &&
|
|
|
|
!((xfrm_flowi_sport(fl) ^ sel->sport) & sel->sport_mask) &&
|
|
|
|
(fl->proto == sel->proto || !sel->proto) &&
|
|
|
|
(fl->oif == sel->ifindex || !sel->ifindex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int
|
|
|
|
__xfrm6_selector_match(struct xfrm_selector *sel, struct flowi *fl)
|
|
|
|
{
|
|
|
|
return addr_match(&fl->fl6_dst, &sel->daddr, sel->prefixlen_d) &&
|
|
|
|
addr_match(&fl->fl6_src, &sel->saddr, sel->prefixlen_s) &&
|
|
|
|
!((xfrm_flowi_dport(fl) ^ sel->dport) & sel->dport_mask) &&
|
|
|
|
!((xfrm_flowi_sport(fl) ^ sel->sport) & sel->sport_mask) &&
|
|
|
|
(fl->proto == sel->proto || !sel->proto) &&
|
|
|
|
(fl->oif == sel->ifindex || !sel->ifindex);
|
|
|
|
}
|
|
|
|
|
|
|
|
int xfrm_selector_match(struct xfrm_selector *sel, struct flowi *fl,
|
|
|
|
unsigned short family)
|
|
|
|
{
|
|
|
|
switch (family) {
|
|
|
|
case AF_INET:
|
|
|
|
return __xfrm4_selector_match(sel, fl);
|
|
|
|
case AF_INET6:
|
|
|
|
return __xfrm6_selector_match(sel, fl);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-11-26 09:51:25 +08:00
|
|
|
static inline struct dst_entry *__xfrm_dst_lookup(struct net *net, int tos,
|
2008-02-22 13:48:22 +08:00
|
|
|
xfrm_address_t *saddr,
|
|
|
|
xfrm_address_t *daddr,
|
|
|
|
int family)
|
|
|
|
{
|
|
|
|
struct xfrm_policy_afinfo *afinfo;
|
|
|
|
struct dst_entry *dst;
|
|
|
|
|
|
|
|
afinfo = xfrm_policy_get_afinfo(family);
|
|
|
|
if (unlikely(afinfo == NULL))
|
|
|
|
return ERR_PTR(-EAFNOSUPPORT);
|
|
|
|
|
2008-11-26 09:51:25 +08:00
|
|
|
dst = afinfo->dst_lookup(net, tos, saddr, daddr);
|
2008-02-22 13:48:22 +08:00
|
|
|
|
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
|
|
|
|
|
|
|
return dst;
|
|
|
|
}
|
|
|
|
|
2007-12-12 01:32:34 +08:00
|
|
|
static inline struct dst_entry *xfrm_dst_lookup(struct xfrm_state *x, int tos,
|
2008-02-22 13:48:22 +08:00
|
|
|
xfrm_address_t *prev_saddr,
|
|
|
|
xfrm_address_t *prev_daddr,
|
2007-12-12 01:32:34 +08:00
|
|
|
int family)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-11-26 09:51:25 +08:00
|
|
|
struct net *net = xs_net(x);
|
2007-11-14 13:37:28 +08:00
|
|
|
xfrm_address_t *saddr = &x->props.saddr;
|
|
|
|
xfrm_address_t *daddr = &x->id.daddr;
|
|
|
|
struct dst_entry *dst;
|
|
|
|
|
2008-02-22 13:48:22 +08:00
|
|
|
if (x->type->flags & XFRM_TYPE_LOCAL_COADDR) {
|
2007-11-14 13:37:28 +08:00
|
|
|
saddr = x->coaddr;
|
2008-02-22 13:48:22 +08:00
|
|
|
daddr = prev_daddr;
|
|
|
|
}
|
|
|
|
if (x->type->flags & XFRM_TYPE_REMOTE_COADDR) {
|
|
|
|
saddr = prev_saddr;
|
2007-11-14 13:37:28 +08:00
|
|
|
daddr = x->coaddr;
|
2008-02-22 13:48:22 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-11-26 09:51:25 +08:00
|
|
|
dst = __xfrm_dst_lookup(net, tos, saddr, daddr, family);
|
2008-02-22 13:48:22 +08:00
|
|
|
|
|
|
|
if (!IS_ERR(dst)) {
|
|
|
|
if (prev_saddr != saddr)
|
|
|
|
memcpy(prev_saddr, saddr, sizeof(*prev_saddr));
|
|
|
|
if (prev_daddr != daddr)
|
|
|
|
memcpy(prev_daddr, daddr, sizeof(*prev_daddr));
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-11-14 13:37:28 +08:00
|
|
|
return dst;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned long make_jiffies(long secs)
|
|
|
|
{
|
|
|
|
if (secs >= (MAX_SCHEDULE_TIMEOUT-1)/HZ)
|
|
|
|
return MAX_SCHEDULE_TIMEOUT-1;
|
|
|
|
else
|
2007-02-09 22:25:29 +08:00
|
|
|
return secs*HZ;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void xfrm_policy_timer(unsigned long data)
|
|
|
|
{
|
|
|
|
struct xfrm_policy *xp = (struct xfrm_policy*)data;
|
2007-03-05 08:12:44 +08:00
|
|
|
unsigned long now = get_seconds();
|
2005-04-17 06:20:36 +08:00
|
|
|
long next = LONG_MAX;
|
|
|
|
int warn = 0;
|
|
|
|
int dir;
|
|
|
|
|
|
|
|
read_lock(&xp->lock);
|
|
|
|
|
2008-10-01 22:03:24 +08:00
|
|
|
if (xp->walk.dead)
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
|
|
|
|
2005-10-06 03:15:12 +08:00
|
|
|
dir = xfrm_policy_id2dir(xp->index);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (xp->lft.hard_add_expires_seconds) {
|
|
|
|
long tmo = xp->lft.hard_add_expires_seconds +
|
|
|
|
xp->curlft.add_time - now;
|
|
|
|
if (tmo <= 0)
|
|
|
|
goto expired;
|
|
|
|
if (tmo < next)
|
|
|
|
next = tmo;
|
|
|
|
}
|
|
|
|
if (xp->lft.hard_use_expires_seconds) {
|
|
|
|
long tmo = xp->lft.hard_use_expires_seconds +
|
|
|
|
(xp->curlft.use_time ? : xp->curlft.add_time) - now;
|
|
|
|
if (tmo <= 0)
|
|
|
|
goto expired;
|
|
|
|
if (tmo < next)
|
|
|
|
next = tmo;
|
|
|
|
}
|
|
|
|
if (xp->lft.soft_add_expires_seconds) {
|
|
|
|
long tmo = xp->lft.soft_add_expires_seconds +
|
|
|
|
xp->curlft.add_time - now;
|
|
|
|
if (tmo <= 0) {
|
|
|
|
warn = 1;
|
|
|
|
tmo = XFRM_KM_TIMEOUT;
|
|
|
|
}
|
|
|
|
if (tmo < next)
|
|
|
|
next = tmo;
|
|
|
|
}
|
|
|
|
if (xp->lft.soft_use_expires_seconds) {
|
|
|
|
long tmo = xp->lft.soft_use_expires_seconds +
|
|
|
|
(xp->curlft.use_time ? : xp->curlft.add_time) - now;
|
|
|
|
if (tmo <= 0) {
|
|
|
|
warn = 1;
|
|
|
|
tmo = XFRM_KM_TIMEOUT;
|
|
|
|
}
|
|
|
|
if (tmo < next)
|
|
|
|
next = tmo;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (warn)
|
2006-03-21 11:17:25 +08:00
|
|
|
km_policy_expired(xp, dir, 0, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (next != LONG_MAX &&
|
|
|
|
!mod_timer(&xp->timer, jiffies + make_jiffies(next)))
|
|
|
|
xfrm_pol_hold(xp);
|
|
|
|
|
|
|
|
out:
|
|
|
|
read_unlock(&xp->lock);
|
|
|
|
xfrm_pol_put(xp);
|
|
|
|
return;
|
|
|
|
|
|
|
|
expired:
|
|
|
|
read_unlock(&xp->lock);
|
2005-06-19 13:43:22 +08:00
|
|
|
if (!xfrm_policy_delete(xp, dir))
|
2006-03-21 11:17:25 +08:00
|
|
|
km_policy_expired(xp, dir, 1, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_pol_put(xp);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Allocate xfrm_policy. Not used here, it is supposed to be used by pfkeyv2
|
|
|
|
* SPD calls.
|
|
|
|
*/
|
|
|
|
|
2008-11-26 09:21:45 +08:00
|
|
|
struct xfrm_policy *xfrm_policy_alloc(struct net *net, gfp_t gfp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct xfrm_policy *policy;
|
|
|
|
|
2006-07-22 05:51:30 +08:00
|
|
|
policy = kzalloc(sizeof(struct xfrm_policy), gfp);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (policy) {
|
2008-11-26 09:21:45 +08:00
|
|
|
write_pnet(&policy->xp_net, net);
|
2008-10-01 22:03:24 +08:00
|
|
|
INIT_LIST_HEAD(&policy->walk.all);
|
2006-08-24 19:45:07 +08:00
|
|
|
INIT_HLIST_NODE(&policy->bydst);
|
|
|
|
INIT_HLIST_NODE(&policy->byidx);
|
2005-04-17 06:20:36 +08:00
|
|
|
rwlock_init(&policy->lock);
|
2006-08-24 19:45:07 +08:00
|
|
|
atomic_set(&policy->refcnt, 1);
|
2008-01-24 13:20:07 +08:00
|
|
|
setup_timer(&policy->timer, xfrm_policy_timer,
|
|
|
|
(unsigned long)policy);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
return policy;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_alloc);
|
|
|
|
|
|
|
|
/* Destroy xfrm_policy: descendant resources must be released to this moment. */
|
|
|
|
|
2008-01-08 14:34:29 +08:00
|
|
|
void xfrm_policy_destroy(struct xfrm_policy *policy)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-10-01 22:03:24 +08:00
|
|
|
BUG_ON(!policy->walk.dead);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-01-09 14:24:28 +08:00
|
|
|
BUG_ON(policy->bundles);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (del_timer(&policy->timer))
|
|
|
|
BUG();
|
|
|
|
|
2008-04-13 10:07:52 +08:00
|
|
|
security_xfrm_policy_free(policy->security);
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(policy);
|
|
|
|
}
|
2008-01-08 14:34:29 +08:00
|
|
|
EXPORT_SYMBOL(xfrm_policy_destroy);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
static void xfrm_policy_gc_kill(struct xfrm_policy *policy)
|
|
|
|
{
|
|
|
|
struct dst_entry *dst;
|
|
|
|
|
|
|
|
while ((dst = policy->bundles) != NULL) {
|
|
|
|
policy->bundles = dst->next;
|
|
|
|
dst_free(dst);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (del_timer(&policy->timer))
|
|
|
|
atomic_dec(&policy->refcnt);
|
|
|
|
|
|
|
|
if (atomic_read(&policy->refcnt) > 1)
|
|
|
|
flow_cache_flush();
|
|
|
|
|
|
|
|
xfrm_pol_put(policy);
|
|
|
|
}
|
|
|
|
|
2006-11-22 22:57:56 +08:00
|
|
|
static void xfrm_policy_gc_task(struct work_struct *work)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct xfrm_policy *policy;
|
2006-08-24 19:45:07 +08:00
|
|
|
struct hlist_node *entry, *tmp;
|
|
|
|
struct hlist_head gc_list;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
spin_lock_bh(&xfrm_policy_gc_lock);
|
2006-08-24 19:45:07 +08:00
|
|
|
gc_list.first = xfrm_policy_gc_list.first;
|
|
|
|
INIT_HLIST_HEAD(&xfrm_policy_gc_list);
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock_bh(&xfrm_policy_gc_lock);
|
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_for_each_entry_safe(policy, entry, tmp, &gc_list, bydst)
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_policy_gc_kill(policy);
|
|
|
|
}
|
2008-11-26 09:13:59 +08:00
|
|
|
static DECLARE_WORK(xfrm_policy_gc_work, xfrm_policy_gc_task);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Rule must be locked. Release descentant resources, announce
|
|
|
|
* entry dead. The rule must be unlinked from lists to the moment.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void xfrm_policy_kill(struct xfrm_policy *policy)
|
|
|
|
{
|
|
|
|
int dead;
|
|
|
|
|
|
|
|
write_lock_bh(&policy->lock);
|
2008-10-01 22:03:24 +08:00
|
|
|
dead = policy->walk.dead;
|
|
|
|
policy->walk.dead = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
write_unlock_bh(&policy->lock);
|
|
|
|
|
|
|
|
if (unlikely(dead)) {
|
|
|
|
WARN_ON(1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-11-04 11:11:29 +08:00
|
|
|
spin_lock_bh(&xfrm_policy_gc_lock);
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_add_head(&policy->bydst, &xfrm_policy_gc_list);
|
2008-11-04 11:11:29 +08:00
|
|
|
spin_unlock_bh(&xfrm_policy_gc_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
schedule_work(&xfrm_policy_gc_work);
|
|
|
|
}
|
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
static unsigned int xfrm_policy_hashmax __read_mostly = 1 * 1024 * 1024;
|
|
|
|
|
2008-11-26 09:32:41 +08:00
|
|
|
static inline unsigned int idx_hash(struct net *net, u32 index)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:32:41 +08:00
|
|
|
return __idx_hash(index, net->xfrm.policy_idx_hmask);
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:33:06 +08:00
|
|
|
static struct hlist_head *policy_hash_bysel(struct net *net, struct xfrm_selector *sel, unsigned short family, int dir)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:33:06 +08:00
|
|
|
unsigned int hmask = net->xfrm.policy_bydst[dir].hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
unsigned int hash = __sel_hash(sel, family, hmask);
|
|
|
|
|
|
|
|
return (hash == hmask + 1 ?
|
2008-11-26 09:33:06 +08:00
|
|
|
&net->xfrm.policy_inexact[dir] :
|
|
|
|
net->xfrm.policy_bydst[dir].table + hash);
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:33:06 +08:00
|
|
|
static struct hlist_head *policy_hash_direct(struct net *net, xfrm_address_t *daddr, xfrm_address_t *saddr, unsigned short family, int dir)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:33:06 +08:00
|
|
|
unsigned int hmask = net->xfrm.policy_bydst[dir].hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
unsigned int hash = __addr_hash(daddr, saddr, family, hmask);
|
|
|
|
|
2008-11-26 09:33:06 +08:00
|
|
|
return net->xfrm.policy_bydst[dir].table + hash;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void xfrm_dst_hash_transfer(struct hlist_head *list,
|
|
|
|
struct hlist_head *ndsttable,
|
|
|
|
unsigned int nhashmask)
|
|
|
|
{
|
2008-02-18 15:29:30 +08:00
|
|
|
struct hlist_node *entry, *tmp, *entry0 = NULL;
|
2006-08-24 19:45:07 +08:00
|
|
|
struct xfrm_policy *pol;
|
2008-02-18 15:29:30 +08:00
|
|
|
unsigned int h0 = 0;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
2008-02-18 15:29:30 +08:00
|
|
|
redo:
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_for_each_entry_safe(pol, entry, tmp, list, bydst) {
|
|
|
|
unsigned int h;
|
|
|
|
|
|
|
|
h = __addr_hash(&pol->selector.daddr, &pol->selector.saddr,
|
|
|
|
pol->family, nhashmask);
|
2008-02-18 15:29:30 +08:00
|
|
|
if (!entry0) {
|
|
|
|
hlist_del(entry);
|
|
|
|
hlist_add_head(&pol->bydst, ndsttable+h);
|
|
|
|
h0 = h;
|
|
|
|
} else {
|
|
|
|
if (h != h0)
|
|
|
|
continue;
|
|
|
|
hlist_del(entry);
|
|
|
|
hlist_add_after(entry0, &pol->bydst);
|
|
|
|
}
|
|
|
|
entry0 = entry;
|
|
|
|
}
|
|
|
|
if (!hlist_empty(list)) {
|
|
|
|
entry0 = NULL;
|
|
|
|
goto redo;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xfrm_idx_hash_transfer(struct hlist_head *list,
|
|
|
|
struct hlist_head *nidxtable,
|
|
|
|
unsigned int nhashmask)
|
|
|
|
{
|
|
|
|
struct hlist_node *entry, *tmp;
|
|
|
|
struct xfrm_policy *pol;
|
|
|
|
|
|
|
|
hlist_for_each_entry_safe(pol, entry, tmp, list, byidx) {
|
|
|
|
unsigned int h;
|
|
|
|
|
|
|
|
h = __idx_hash(pol->index, nhashmask);
|
|
|
|
hlist_add_head(&pol->byidx, nidxtable+h);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long xfrm_new_hash_mask(unsigned int old_hmask)
|
|
|
|
{
|
|
|
|
return ((old_hmask + 1) << 1) - 1;
|
|
|
|
}
|
|
|
|
|
2008-11-26 09:28:57 +08:00
|
|
|
static void xfrm_bydst_resize(struct net *net, int dir)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:28:57 +08:00
|
|
|
unsigned int hmask = net->xfrm.policy_bydst[dir].hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
unsigned int nhashmask = xfrm_new_hash_mask(hmask);
|
|
|
|
unsigned int nsize = (nhashmask + 1) * sizeof(struct hlist_head);
|
2008-11-26 09:28:57 +08:00
|
|
|
struct hlist_head *odst = net->xfrm.policy_bydst[dir].table;
|
2006-08-24 19:50:50 +08:00
|
|
|
struct hlist_head *ndst = xfrm_hash_alloc(nsize);
|
2006-08-24 19:45:07 +08:00
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!ndst)
|
|
|
|
return;
|
|
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
|
|
|
|
for (i = hmask; i >= 0; i--)
|
|
|
|
xfrm_dst_hash_transfer(odst + i, ndst, nhashmask);
|
|
|
|
|
2008-11-26 09:28:57 +08:00
|
|
|
net->xfrm.policy_bydst[dir].table = ndst;
|
|
|
|
net->xfrm.policy_bydst[dir].hmask = nhashmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
2006-08-24 19:50:50 +08:00
|
|
|
xfrm_hash_free(odst, (hmask + 1) * sizeof(struct hlist_head));
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:28:57 +08:00
|
|
|
static void xfrm_byidx_resize(struct net *net, int total)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:28:57 +08:00
|
|
|
unsigned int hmask = net->xfrm.policy_idx_hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
unsigned int nhashmask = xfrm_new_hash_mask(hmask);
|
|
|
|
unsigned int nsize = (nhashmask + 1) * sizeof(struct hlist_head);
|
2008-11-26 09:28:57 +08:00
|
|
|
struct hlist_head *oidx = net->xfrm.policy_byidx;
|
2006-08-24 19:50:50 +08:00
|
|
|
struct hlist_head *nidx = xfrm_hash_alloc(nsize);
|
2006-08-24 19:45:07 +08:00
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!nidx)
|
|
|
|
return;
|
|
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
|
|
|
|
for (i = hmask; i >= 0; i--)
|
|
|
|
xfrm_idx_hash_transfer(oidx + i, nidx, nhashmask);
|
|
|
|
|
2008-11-26 09:28:57 +08:00
|
|
|
net->xfrm.policy_byidx = nidx;
|
|
|
|
net->xfrm.policy_idx_hmask = nhashmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
2006-08-24 19:50:50 +08:00
|
|
|
xfrm_hash_free(oidx, (hmask + 1) * sizeof(struct hlist_head));
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:28:57 +08:00
|
|
|
static inline int xfrm_bydst_should_resize(struct net *net, int dir, int *total)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:28:57 +08:00
|
|
|
unsigned int cnt = net->xfrm.policy_count[dir];
|
|
|
|
unsigned int hmask = net->xfrm.policy_bydst[dir].hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
if (total)
|
|
|
|
*total += cnt;
|
|
|
|
|
|
|
|
if ((hmask + 1) < xfrm_policy_hashmax &&
|
|
|
|
cnt > hmask)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-11-26 09:28:57 +08:00
|
|
|
static inline int xfrm_byidx_should_resize(struct net *net, int total)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:28:57 +08:00
|
|
|
unsigned int hmask = net->xfrm.policy_idx_hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
if ((hmask + 1) < xfrm_policy_hashmax &&
|
|
|
|
total > hmask)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-05-05 03:55:39 +08:00
|
|
|
void xfrm_spd_getinfo(struct xfrmk_spdinfo *si)
|
2007-04-29 12:20:32 +08:00
|
|
|
{
|
|
|
|
read_lock_bh(&xfrm_policy_lock);
|
2008-11-26 09:24:15 +08:00
|
|
|
si->incnt = init_net.xfrm.policy_count[XFRM_POLICY_IN];
|
|
|
|
si->outcnt = init_net.xfrm.policy_count[XFRM_POLICY_OUT];
|
|
|
|
si->fwdcnt = init_net.xfrm.policy_count[XFRM_POLICY_FWD];
|
|
|
|
si->inscnt = init_net.xfrm.policy_count[XFRM_POLICY_IN+XFRM_POLICY_MAX];
|
|
|
|
si->outscnt = init_net.xfrm.policy_count[XFRM_POLICY_OUT+XFRM_POLICY_MAX];
|
|
|
|
si->fwdscnt = init_net.xfrm.policy_count[XFRM_POLICY_FWD+XFRM_POLICY_MAX];
|
2008-11-26 09:22:58 +08:00
|
|
|
si->spdhcnt = init_net.xfrm.policy_idx_hmask;
|
2007-04-29 12:20:32 +08:00
|
|
|
si->spdhmcnt = xfrm_policy_hashmax;
|
|
|
|
read_unlock_bh(&xfrm_policy_lock);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_spd_getinfo);
|
2006-08-24 19:45:07 +08:00
|
|
|
|
2007-04-29 12:20:32 +08:00
|
|
|
static DEFINE_MUTEX(hash_resize_mutex);
|
2008-11-26 09:28:57 +08:00
|
|
|
static void xfrm_hash_resize(struct work_struct *work)
|
2006-08-24 19:45:07 +08:00
|
|
|
{
|
2008-11-26 09:28:57 +08:00
|
|
|
struct net *net = container_of(work, struct net, xfrm.policy_hash_work);
|
2006-08-24 19:45:07 +08:00
|
|
|
int dir, total;
|
|
|
|
|
|
|
|
mutex_lock(&hash_resize_mutex);
|
|
|
|
|
|
|
|
total = 0;
|
|
|
|
for (dir = 0; dir < XFRM_POLICY_MAX * 2; dir++) {
|
2008-11-26 09:28:57 +08:00
|
|
|
if (xfrm_bydst_should_resize(net, dir, &total))
|
|
|
|
xfrm_bydst_resize(net, dir);
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
2008-11-26 09:28:57 +08:00
|
|
|
if (xfrm_byidx_should_resize(net, total))
|
|
|
|
xfrm_byidx_resize(net, total);
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
mutex_unlock(&hash_resize_mutex);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Generate new index... KAME seems to generate them ordered by cost
|
|
|
|
* of an absolute inpredictability of ordering of rules. This will not pass. */
|
2008-11-26 09:33:06 +08:00
|
|
|
static u32 xfrm_gen_index(struct net *net, int dir)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
static u32 idx_generator;
|
|
|
|
|
|
|
|
for (;;) {
|
2006-08-24 19:45:07 +08:00
|
|
|
struct hlist_node *entry;
|
|
|
|
struct hlist_head *list;
|
|
|
|
struct xfrm_policy *p;
|
|
|
|
u32 idx;
|
|
|
|
int found;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
idx = (idx_generator | dir);
|
|
|
|
idx_generator += 8;
|
|
|
|
if (idx == 0)
|
|
|
|
idx = 8;
|
2008-11-26 09:33:06 +08:00
|
|
|
list = net->xfrm.policy_byidx + idx_hash(net, idx);
|
2006-08-24 19:45:07 +08:00
|
|
|
found = 0;
|
|
|
|
hlist_for_each_entry(p, entry, list, byidx) {
|
|
|
|
if (p->index == idx) {
|
|
|
|
found = 1;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-08-24 19:45:07 +08:00
|
|
|
if (!found)
|
2005-04-17 06:20:36 +08:00
|
|
|
return idx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
static inline int selector_cmp(struct xfrm_selector *s1, struct xfrm_selector *s2)
|
|
|
|
{
|
|
|
|
u32 *p1 = (u32 *) s1;
|
|
|
|
u32 *p2 = (u32 *) s2;
|
|
|
|
int len = sizeof(struct xfrm_selector) / sizeof(u32);
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
if (p1[i] != p2[i])
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
int xfrm_policy_insert(int dir, struct xfrm_policy *policy, int excl)
|
|
|
|
{
|
2008-11-26 09:33:06 +08:00
|
|
|
struct net *net = xp_net(policy);
|
2006-08-24 19:45:07 +08:00
|
|
|
struct xfrm_policy *pol;
|
|
|
|
struct xfrm_policy *delpol;
|
|
|
|
struct hlist_head *chain;
|
2007-01-17 08:52:02 +08:00
|
|
|
struct hlist_node *entry, *newpos;
|
2005-12-22 23:39:48 +08:00
|
|
|
struct dst_entry *gc_list;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
2008-11-26 09:33:06 +08:00
|
|
|
chain = policy_hash_bysel(net, &policy->selector, policy->family, dir);
|
2006-08-24 19:45:07 +08:00
|
|
|
delpol = NULL;
|
|
|
|
newpos = NULL;
|
|
|
|
hlist_for_each_entry(pol, entry, chain, bydst) {
|
2007-01-17 08:52:02 +08:00
|
|
|
if (pol->type == policy->type &&
|
2006-08-24 19:45:07 +08:00
|
|
|
!selector_cmp(&pol->selector, &policy->selector) &&
|
2007-01-17 08:52:02 +08:00
|
|
|
xfrm_sec_ctx_match(pol->security, policy->security) &&
|
|
|
|
!WARN_ON(delpol)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (excl) {
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
return -EEXIST;
|
|
|
|
}
|
|
|
|
delpol = pol;
|
|
|
|
if (policy->priority > pol->priority)
|
|
|
|
continue;
|
|
|
|
} else if (policy->priority >= pol->priority) {
|
2007-01-17 08:52:02 +08:00
|
|
|
newpos = &pol->bydst;
|
2005-04-17 06:20:36 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (delpol)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (newpos)
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_add_after(newpos, &policy->bydst);
|
|
|
|
else
|
|
|
|
hlist_add_head(&policy->bydst, chain);
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_pol_hold(policy);
|
2008-11-26 09:33:06 +08:00
|
|
|
net->xfrm.policy_count[dir]++;
|
2005-04-17 06:20:36 +08:00
|
|
|
atomic_inc(&flow_cache_genid);
|
2006-08-24 19:45:07 +08:00
|
|
|
if (delpol) {
|
|
|
|
hlist_del(&delpol->bydst);
|
|
|
|
hlist_del(&delpol->byidx);
|
2008-10-01 22:03:24 +08:00
|
|
|
list_del(&delpol->walk.all);
|
2008-11-26 09:33:06 +08:00
|
|
|
net->xfrm.policy_count[dir]--;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
2008-11-26 09:33:06 +08:00
|
|
|
policy->index = delpol ? delpol->index : xfrm_gen_index(net, dir);
|
|
|
|
hlist_add_head(&policy->byidx, net->xfrm.policy_byidx+idx_hash(net, policy->index));
|
2007-03-05 08:12:44 +08:00
|
|
|
policy->curlft.add_time = get_seconds();
|
2005-04-17 06:20:36 +08:00
|
|
|
policy->curlft.use_time = 0;
|
|
|
|
if (!mod_timer(&policy->timer, jiffies + HZ))
|
|
|
|
xfrm_pol_hold(policy);
|
2008-11-26 09:33:06 +08:00
|
|
|
list_add(&policy->walk.all, &net->xfrm.policy_all);
|
2005-04-17 06:20:36 +08:00
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
2005-12-22 23:39:48 +08:00
|
|
|
if (delpol)
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_policy_kill(delpol);
|
2008-11-26 09:33:06 +08:00
|
|
|
else if (xfrm_bydst_should_resize(net, dir, NULL))
|
|
|
|
schedule_work(&net->xfrm.policy_hash_work);
|
2005-12-22 23:39:48 +08:00
|
|
|
|
|
|
|
read_lock_bh(&xfrm_policy_lock);
|
|
|
|
gc_list = NULL;
|
2006-08-24 19:45:07 +08:00
|
|
|
entry = &policy->bydst;
|
|
|
|
hlist_for_each_entry_continue(policy, entry, bydst) {
|
2005-12-22 23:39:48 +08:00
|
|
|
struct dst_entry *dst;
|
|
|
|
|
|
|
|
write_lock(&policy->lock);
|
|
|
|
dst = policy->bundles;
|
|
|
|
if (dst) {
|
|
|
|
struct dst_entry *tail = dst;
|
|
|
|
while (tail->next)
|
|
|
|
tail = tail->next;
|
|
|
|
tail->next = gc_list;
|
|
|
|
gc_list = dst;
|
|
|
|
|
|
|
|
policy->bundles = NULL;
|
|
|
|
}
|
|
|
|
write_unlock(&policy->lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2005-12-22 23:39:48 +08:00
|
|
|
read_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
|
|
|
while (gc_list) {
|
|
|
|
struct dst_entry *dst = gc_list;
|
|
|
|
|
|
|
|
gc_list = dst->next;
|
|
|
|
dst_free(dst);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_insert);
|
|
|
|
|
2008-11-26 09:34:20 +08:00
|
|
|
struct xfrm_policy *xfrm_policy_bysel_ctx(struct net *net, u8 type, int dir,
|
2006-08-24 13:43:30 +08:00
|
|
|
struct xfrm_selector *sel,
|
2007-03-08 07:37:58 +08:00
|
|
|
struct xfrm_sec_ctx *ctx, int delete,
|
|
|
|
int *err)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-08-24 19:45:07 +08:00
|
|
|
struct xfrm_policy *pol, *ret;
|
|
|
|
struct hlist_head *chain;
|
|
|
|
struct hlist_node *entry;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-08 07:37:58 +08:00
|
|
|
*err = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
2008-11-26 09:34:20 +08:00
|
|
|
chain = policy_hash_bysel(net, sel, sel->family, dir);
|
2006-08-24 19:45:07 +08:00
|
|
|
ret = NULL;
|
|
|
|
hlist_for_each_entry(pol, entry, chain, bydst) {
|
|
|
|
if (pol->type == type &&
|
|
|
|
!selector_cmp(sel, &pol->selector) &&
|
|
|
|
xfrm_sec_ctx_match(ctx, pol->security)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_pol_hold(pol);
|
2006-08-24 19:45:07 +08:00
|
|
|
if (delete) {
|
2008-04-13 10:07:52 +08:00
|
|
|
*err = security_xfrm_policy_delete(
|
|
|
|
pol->security);
|
2007-03-08 07:37:58 +08:00
|
|
|
if (*err) {
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
return pol;
|
|
|
|
}
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_del(&pol->bydst);
|
|
|
|
hlist_del(&pol->byidx);
|
2008-10-01 22:03:24 +08:00
|
|
|
list_del(&pol->walk.all);
|
2008-11-26 09:34:20 +08:00
|
|
|
net->xfrm.policy_count[dir]--;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
ret = pol;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
if (ret && delete) {
|
2005-04-17 06:20:36 +08:00
|
|
|
atomic_inc(&flow_cache_genid);
|
2006-08-24 19:45:07 +08:00
|
|
|
xfrm_policy_kill(ret);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-08-24 19:45:07 +08:00
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
EXPORT_SYMBOL(xfrm_policy_bysel_ctx);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-11-26 09:34:20 +08:00
|
|
|
struct xfrm_policy *xfrm_policy_byid(struct net *net, u8 type, int dir, u32 id,
|
|
|
|
int delete, int *err)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-08-24 19:45:07 +08:00
|
|
|
struct xfrm_policy *pol, *ret;
|
|
|
|
struct hlist_head *chain;
|
|
|
|
struct hlist_node *entry;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-05-14 17:15:47 +08:00
|
|
|
*err = -ENOENT;
|
|
|
|
if (xfrm_policy_id2dir(id) != dir)
|
|
|
|
return NULL;
|
|
|
|
|
2007-03-08 07:37:58 +08:00
|
|
|
*err = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
2008-11-26 09:34:20 +08:00
|
|
|
chain = net->xfrm.policy_byidx + idx_hash(net, id);
|
2006-08-24 19:45:07 +08:00
|
|
|
ret = NULL;
|
|
|
|
hlist_for_each_entry(pol, entry, chain, byidx) {
|
|
|
|
if (pol->type == type && pol->index == id) {
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_pol_hold(pol);
|
2006-08-24 19:45:07 +08:00
|
|
|
if (delete) {
|
2008-04-13 10:07:52 +08:00
|
|
|
*err = security_xfrm_policy_delete(
|
|
|
|
pol->security);
|
2007-03-08 07:37:58 +08:00
|
|
|
if (*err) {
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
return pol;
|
|
|
|
}
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_del(&pol->bydst);
|
|
|
|
hlist_del(&pol->byidx);
|
2008-10-01 22:03:24 +08:00
|
|
|
list_del(&pol->walk.all);
|
2008-11-26 09:34:20 +08:00
|
|
|
net->xfrm.policy_count[dir]--;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
ret = pol;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
if (ret && delete) {
|
2005-04-17 06:20:36 +08:00
|
|
|
atomic_inc(&flow_cache_genid);
|
2006-08-24 19:45:07 +08:00
|
|
|
xfrm_policy_kill(ret);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-08-24 19:45:07 +08:00
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_byid);
|
|
|
|
|
2007-06-05 07:05:57 +08:00
|
|
|
#ifdef CONFIG_SECURITY_NETWORK_XFRM
|
|
|
|
static inline int
|
2008-11-26 09:33:32 +08:00
|
|
|
xfrm_policy_flush_secctx_check(struct net *net, u8 type, struct xfrm_audit *audit_info)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-06-05 07:05:57 +08:00
|
|
|
int dir, err = 0;
|
|
|
|
|
|
|
|
for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
|
|
|
|
struct xfrm_policy *pol;
|
|
|
|
struct hlist_node *entry;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
hlist_for_each_entry(pol, entry,
|
2008-11-26 09:33:32 +08:00
|
|
|
&net->xfrm.policy_inexact[dir], bydst) {
|
2007-06-05 07:05:57 +08:00
|
|
|
if (pol->type != type)
|
|
|
|
continue;
|
2008-04-13 10:07:52 +08:00
|
|
|
err = security_xfrm_policy_delete(pol->security);
|
2007-06-05 07:05:57 +08:00
|
|
|
if (err) {
|
2007-09-18 02:51:22 +08:00
|
|
|
xfrm_audit_policy_delete(pol, 0,
|
|
|
|
audit_info->loginuid,
|
2008-04-18 22:09:25 +08:00
|
|
|
audit_info->sessionid,
|
2007-09-18 02:51:22 +08:00
|
|
|
audit_info->secid);
|
2007-06-05 07:05:57 +08:00
|
|
|
return err;
|
|
|
|
}
|
2007-07-19 09:45:15 +08:00
|
|
|
}
|
2008-11-26 09:33:32 +08:00
|
|
|
for (i = net->xfrm.policy_bydst[dir].hmask; i >= 0; i--) {
|
2007-06-05 07:05:57 +08:00
|
|
|
hlist_for_each_entry(pol, entry,
|
2008-11-26 09:33:32 +08:00
|
|
|
net->xfrm.policy_bydst[dir].table + i,
|
2007-06-05 07:05:57 +08:00
|
|
|
bydst) {
|
|
|
|
if (pol->type != type)
|
|
|
|
continue;
|
2008-04-13 10:07:52 +08:00
|
|
|
err = security_xfrm_policy_delete(
|
|
|
|
pol->security);
|
2007-06-05 07:05:57 +08:00
|
|
|
if (err) {
|
2007-09-18 02:51:22 +08:00
|
|
|
xfrm_audit_policy_delete(pol, 0,
|
|
|
|
audit_info->loginuid,
|
2008-04-18 22:09:25 +08:00
|
|
|
audit_info->sessionid,
|
2007-09-18 02:51:22 +08:00
|
|
|
audit_info->secid);
|
2007-06-05 07:05:57 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline int
|
2008-11-26 09:33:32 +08:00
|
|
|
xfrm_policy_flush_secctx_check(struct net *net, u8 type, struct xfrm_audit *audit_info)
|
2007-06-05 07:05:57 +08:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-11-26 09:33:32 +08:00
|
|
|
int xfrm_policy_flush(struct net *net, u8 type, struct xfrm_audit *audit_info)
|
2007-06-05 07:05:57 +08:00
|
|
|
{
|
|
|
|
int dir, err = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
2007-06-05 07:05:57 +08:00
|
|
|
|
2008-11-26 09:33:32 +08:00
|
|
|
err = xfrm_policy_flush_secctx_check(net, type, audit_info);
|
2007-06-05 07:05:57 +08:00
|
|
|
if (err)
|
|
|
|
goto out;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
for (dir = 0; dir < XFRM_POLICY_MAX; dir++) {
|
2006-08-24 19:45:07 +08:00
|
|
|
struct xfrm_policy *pol;
|
|
|
|
struct hlist_node *entry;
|
2006-10-04 07:00:26 +08:00
|
|
|
int i, killed;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
2006-10-04 07:00:26 +08:00
|
|
|
killed = 0;
|
2006-08-24 19:45:07 +08:00
|
|
|
again1:
|
|
|
|
hlist_for_each_entry(pol, entry,
|
2008-11-26 09:33:32 +08:00
|
|
|
&net->xfrm.policy_inexact[dir], bydst) {
|
2006-08-24 19:45:07 +08:00
|
|
|
if (pol->type != type)
|
|
|
|
continue;
|
|
|
|
hlist_del(&pol->bydst);
|
|
|
|
hlist_del(&pol->byidx);
|
2005-04-17 06:20:36 +08:00
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
2007-09-18 02:51:22 +08:00
|
|
|
xfrm_audit_policy_delete(pol, 1, audit_info->loginuid,
|
2008-04-18 22:09:25 +08:00
|
|
|
audit_info->sessionid,
|
2007-09-18 02:51:22 +08:00
|
|
|
audit_info->secid);
|
2006-11-28 03:11:54 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
xfrm_policy_kill(pol);
|
2006-10-04 07:00:26 +08:00
|
|
|
killed++;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
2006-08-24 19:45:07 +08:00
|
|
|
goto again1;
|
|
|
|
}
|
|
|
|
|
2008-11-26 09:33:32 +08:00
|
|
|
for (i = net->xfrm.policy_bydst[dir].hmask; i >= 0; i--) {
|
2006-08-24 19:45:07 +08:00
|
|
|
again2:
|
|
|
|
hlist_for_each_entry(pol, entry,
|
2008-11-26 09:33:32 +08:00
|
|
|
net->xfrm.policy_bydst[dir].table + i,
|
2006-08-24 19:45:07 +08:00
|
|
|
bydst) {
|
|
|
|
if (pol->type != type)
|
|
|
|
continue;
|
|
|
|
hlist_del(&pol->bydst);
|
|
|
|
hlist_del(&pol->byidx);
|
2008-10-01 22:03:24 +08:00
|
|
|
list_del(&pol->walk.all);
|
2006-08-24 19:45:07 +08:00
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
2007-09-18 02:51:22 +08:00
|
|
|
xfrm_audit_policy_delete(pol, 1,
|
|
|
|
audit_info->loginuid,
|
2008-04-18 22:09:25 +08:00
|
|
|
audit_info->sessionid,
|
2007-09-18 02:51:22 +08:00
|
|
|
audit_info->secid);
|
2006-08-24 19:45:07 +08:00
|
|
|
xfrm_policy_kill(pol);
|
2006-10-04 07:00:26 +08:00
|
|
|
killed++;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
goto again2;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-08-24 19:45:07 +08:00
|
|
|
|
2008-11-26 09:33:32 +08:00
|
|
|
net->xfrm.policy_count[dir] -= killed;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
atomic_inc(&flow_cache_genid);
|
2007-06-05 07:05:57 +08:00
|
|
|
out:
|
2005-04-17 06:20:36 +08:00
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
2007-06-05 07:05:57 +08:00
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_flush);
|
|
|
|
|
2008-11-26 09:34:49 +08:00
|
|
|
int xfrm_policy_walk(struct net *net, struct xfrm_policy_walk *walk,
|
2008-02-29 13:31:08 +08:00
|
|
|
int (*func)(struct xfrm_policy *, int, int, void*),
|
2005-04-17 06:20:36 +08:00
|
|
|
void *data)
|
|
|
|
{
|
2008-10-01 22:03:24 +08:00
|
|
|
struct xfrm_policy *pol;
|
|
|
|
struct xfrm_policy_walk_entry *x;
|
2008-02-29 13:31:08 +08:00
|
|
|
int error = 0;
|
|
|
|
|
|
|
|
if (walk->type >= XFRM_POLICY_TYPE_MAX &&
|
|
|
|
walk->type != XFRM_POLICY_TYPE_ANY)
|
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-10-01 22:03:24 +08:00
|
|
|
if (list_empty(&walk->walk.all) && walk->seq != 0)
|
2008-02-29 13:31:08 +08:00
|
|
|
return 0;
|
|
|
|
|
2008-10-01 22:03:24 +08:00
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
if (list_empty(&walk->walk.all))
|
2008-11-26 09:34:49 +08:00
|
|
|
x = list_first_entry(&net->xfrm.policy_all, struct xfrm_policy_walk_entry, all);
|
2008-10-01 22:03:24 +08:00
|
|
|
else
|
|
|
|
x = list_entry(&walk->walk.all, struct xfrm_policy_walk_entry, all);
|
2008-11-26 09:34:49 +08:00
|
|
|
list_for_each_entry_from(x, &net->xfrm.policy_all, all) {
|
2008-10-01 22:03:24 +08:00
|
|
|
if (x->dead)
|
2008-02-29 13:31:08 +08:00
|
|
|
continue;
|
2008-10-01 22:03:24 +08:00
|
|
|
pol = container_of(x, struct xfrm_policy, walk);
|
|
|
|
if (walk->type != XFRM_POLICY_TYPE_ANY &&
|
|
|
|
walk->type != pol->type)
|
|
|
|
continue;
|
|
|
|
error = func(pol, xfrm_policy_id2dir(pol->index),
|
|
|
|
walk->seq, data);
|
|
|
|
if (error) {
|
|
|
|
list_move_tail(&walk->walk.all, &x->all);
|
|
|
|
goto out;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
2008-10-01 22:03:24 +08:00
|
|
|
walk->seq++;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2008-10-01 22:03:24 +08:00
|
|
|
if (walk->seq == 0) {
|
2006-12-05 12:02:37 +08:00
|
|
|
error = -ENOENT;
|
|
|
|
goto out;
|
|
|
|
}
|
2008-10-01 22:03:24 +08:00
|
|
|
list_del_init(&walk->walk.all);
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
2008-10-01 22:03:24 +08:00
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
return error;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_walk);
|
|
|
|
|
2008-10-01 22:03:24 +08:00
|
|
|
void xfrm_policy_walk_init(struct xfrm_policy_walk *walk, u8 type)
|
|
|
|
{
|
|
|
|
INIT_LIST_HEAD(&walk->walk.all);
|
|
|
|
walk->walk.dead = 1;
|
|
|
|
walk->type = type;
|
|
|
|
walk->seq = 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_walk_init);
|
|
|
|
|
|
|
|
void xfrm_policy_walk_done(struct xfrm_policy_walk *walk)
|
|
|
|
{
|
|
|
|
if (list_empty(&walk->walk.all))
|
|
|
|
return;
|
|
|
|
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
list_del(&walk->walk.all);
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_walk_done);
|
|
|
|
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
/*
|
|
|
|
* Find policy to apply to this flow.
|
|
|
|
*
|
|
|
|
* Returns 0 if policy found, else an -errno.
|
|
|
|
*/
|
2006-08-24 19:45:07 +08:00
|
|
|
static int xfrm_policy_match(struct xfrm_policy *pol, struct flowi *fl,
|
|
|
|
u8 type, u16 family, int dir)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-08-24 19:45:07 +08:00
|
|
|
struct xfrm_selector *sel = &pol->selector;
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
int match, ret = -ESRCH;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
if (pol->family != family ||
|
|
|
|
pol->type != type)
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
match = xfrm_selector_match(sel, fl, family);
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
if (match)
|
2008-04-13 10:07:52 +08:00
|
|
|
ret = security_xfrm_policy_lookup(pol->security, fl->secid,
|
|
|
|
dir);
|
2006-08-24 19:45:07 +08:00
|
|
|
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
return ret;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-11-26 09:35:18 +08:00
|
|
|
static struct xfrm_policy *xfrm_policy_lookup_bytype(struct net *net, u8 type,
|
|
|
|
struct flowi *fl,
|
2006-08-24 19:45:07 +08:00
|
|
|
u16 family, u8 dir)
|
|
|
|
{
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
int err;
|
2006-08-24 19:45:07 +08:00
|
|
|
struct xfrm_policy *pol, *ret;
|
|
|
|
xfrm_address_t *daddr, *saddr;
|
|
|
|
struct hlist_node *entry;
|
|
|
|
struct hlist_head *chain;
|
2006-08-26 06:46:46 +08:00
|
|
|
u32 priority = ~0U;
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
daddr = xfrm_flowi_daddr(fl, family);
|
|
|
|
saddr = xfrm_flowi_saddr(fl, family);
|
|
|
|
if (unlikely(!daddr || !saddr))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
read_lock_bh(&xfrm_policy_lock);
|
2008-11-26 09:35:18 +08:00
|
|
|
chain = policy_hash_direct(net, daddr, saddr, family, dir);
|
2006-08-24 19:45:07 +08:00
|
|
|
ret = NULL;
|
|
|
|
hlist_for_each_entry(pol, entry, chain, bydst) {
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
err = xfrm_policy_match(pol, fl, type, family, dir);
|
|
|
|
if (err) {
|
|
|
|
if (err == -ESRCH)
|
|
|
|
continue;
|
|
|
|
else {
|
|
|
|
ret = ERR_PTR(err);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
} else {
|
2006-08-24 19:45:07 +08:00
|
|
|
ret = pol;
|
2006-08-26 06:46:46 +08:00
|
|
|
priority = ret->priority;
|
2006-08-24 19:45:07 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-11-26 09:35:18 +08:00
|
|
|
chain = &net->xfrm.policy_inexact[dir];
|
2006-08-26 06:46:46 +08:00
|
|
|
hlist_for_each_entry(pol, entry, chain, bydst) {
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
err = xfrm_policy_match(pol, fl, type, family, dir);
|
|
|
|
if (err) {
|
|
|
|
if (err == -ESRCH)
|
|
|
|
continue;
|
|
|
|
else {
|
|
|
|
ret = ERR_PTR(err);
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
} else if (pol->priority < priority) {
|
2006-08-26 06:46:46 +08:00
|
|
|
ret = pol;
|
|
|
|
break;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
2006-08-26 06:46:46 +08:00
|
|
|
if (ret)
|
|
|
|
xfrm_pol_hold(ret);
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
fail:
|
2005-04-17 06:20:36 +08:00
|
|
|
read_unlock_bh(&xfrm_policy_lock);
|
2006-08-24 13:43:30 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
return ret;
|
2006-08-24 13:43:30 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:35:18 +08:00
|
|
|
static int xfrm_policy_lookup(struct net *net, struct flowi *fl, u16 family,
|
|
|
|
u8 dir, void **objp, atomic_t **obj_refp)
|
2006-08-24 13:43:30 +08:00
|
|
|
{
|
|
|
|
struct xfrm_policy *pol;
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
int err = 0;
|
2006-08-24 13:43:30 +08:00
|
|
|
|
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
2008-11-26 09:35:18 +08:00
|
|
|
pol = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_SUB, fl, family, dir);
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
if (IS_ERR(pol)) {
|
|
|
|
err = PTR_ERR(pol);
|
|
|
|
pol = NULL;
|
|
|
|
}
|
|
|
|
if (pol || err)
|
2006-08-24 13:43:30 +08:00
|
|
|
goto end;
|
|
|
|
#endif
|
2008-11-26 09:35:18 +08:00
|
|
|
pol = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_MAIN, fl, family, dir);
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
if (IS_ERR(pol)) {
|
|
|
|
err = PTR_ERR(pol);
|
|
|
|
pol = NULL;
|
|
|
|
}
|
2006-08-24 13:43:30 +08:00
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
2006-08-24 19:45:07 +08:00
|
|
|
end:
|
2006-08-24 13:43:30 +08:00
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
if ((*objp = (void *) pol) != NULL)
|
|
|
|
*obj_refp = &pol->refcnt;
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
static inline int policy_to_flow_dir(int dir)
|
|
|
|
{
|
|
|
|
if (XFRM_POLICY_IN == FLOW_DIR_IN &&
|
2007-02-09 22:25:29 +08:00
|
|
|
XFRM_POLICY_OUT == FLOW_DIR_OUT &&
|
|
|
|
XFRM_POLICY_FWD == FLOW_DIR_FWD)
|
|
|
|
return dir;
|
|
|
|
switch (dir) {
|
|
|
|
default:
|
|
|
|
case XFRM_POLICY_IN:
|
|
|
|
return FLOW_DIR_IN;
|
|
|
|
case XFRM_POLICY_OUT:
|
|
|
|
return FLOW_DIR_OUT;
|
|
|
|
case XFRM_POLICY_FWD:
|
|
|
|
return FLOW_DIR_FWD;
|
2007-04-21 08:09:22 +08:00
|
|
|
}
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
}
|
|
|
|
|
2006-07-25 14:29:07 +08:00
|
|
|
static struct xfrm_policy *xfrm_sk_policy_lookup(struct sock *sk, int dir, struct flowi *fl)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct xfrm_policy *pol;
|
|
|
|
|
|
|
|
read_lock_bh(&xfrm_policy_lock);
|
|
|
|
if ((pol = sk->sk_policy[dir]) != NULL) {
|
2007-02-09 22:25:29 +08:00
|
|
|
int match = xfrm_selector_match(&pol->selector, fl,
|
2005-04-17 06:20:36 +08:00
|
|
|
sk->sk_family);
|
2007-02-09 22:25:29 +08:00
|
|
|
int err = 0;
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
|
2006-10-06 04:42:35 +08:00
|
|
|
if (match) {
|
2008-04-13 10:07:52 +08:00
|
|
|
err = security_xfrm_policy_lookup(pol->security,
|
|
|
|
fl->secid,
|
|
|
|
policy_to_flow_dir(dir));
|
2006-10-06 04:42:35 +08:00
|
|
|
if (!err)
|
|
|
|
xfrm_pol_hold(pol);
|
|
|
|
else if (err == -ESRCH)
|
|
|
|
pol = NULL;
|
|
|
|
else
|
|
|
|
pol = ERR_PTR(err);
|
|
|
|
} else
|
2005-04-17 06:20:36 +08:00
|
|
|
pol = NULL;
|
|
|
|
}
|
|
|
|
read_unlock_bh(&xfrm_policy_lock);
|
|
|
|
return pol;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __xfrm_policy_link(struct xfrm_policy *pol, int dir)
|
|
|
|
{
|
2008-11-26 09:29:47 +08:00
|
|
|
struct net *net = xp_net(pol);
|
2008-11-26 09:33:06 +08:00
|
|
|
struct hlist_head *chain = policy_hash_bysel(net, &pol->selector,
|
2006-08-24 19:45:07 +08:00
|
|
|
pol->family, dir);
|
2006-08-24 13:43:30 +08:00
|
|
|
|
2008-11-26 09:29:47 +08:00
|
|
|
list_add(&pol->walk.all, &net->xfrm.policy_all);
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_add_head(&pol->bydst, chain);
|
2008-11-26 09:32:41 +08:00
|
|
|
hlist_add_head(&pol->byidx, net->xfrm.policy_byidx+idx_hash(net, pol->index));
|
2008-11-26 09:29:47 +08:00
|
|
|
net->xfrm.policy_count[dir]++;
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_pol_hold(pol);
|
2006-08-24 19:45:07 +08:00
|
|
|
|
2008-11-26 09:29:47 +08:00
|
|
|
if (xfrm_bydst_should_resize(net, dir, NULL))
|
|
|
|
schedule_work(&net->xfrm.policy_hash_work);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static struct xfrm_policy *__xfrm_policy_unlink(struct xfrm_policy *pol,
|
|
|
|
int dir)
|
|
|
|
{
|
2008-11-26 09:29:47 +08:00
|
|
|
struct net *net = xp_net(pol);
|
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
if (hlist_unhashed(&pol->bydst))
|
|
|
|
return NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_del(&pol->bydst);
|
|
|
|
hlist_del(&pol->byidx);
|
2008-10-01 22:03:24 +08:00
|
|
|
list_del(&pol->walk.all);
|
2008-11-26 09:29:47 +08:00
|
|
|
net->xfrm.policy_count[dir]--;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
return pol;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-06-19 13:43:22 +08:00
|
|
|
int xfrm_policy_delete(struct xfrm_policy *pol, int dir)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
pol = __xfrm_policy_unlink(pol, dir);
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
if (pol) {
|
|
|
|
if (dir < XFRM_POLICY_MAX)
|
|
|
|
atomic_inc(&flow_cache_genid);
|
|
|
|
xfrm_policy_kill(pol);
|
2005-06-19 13:43:22 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2005-06-19 13:43:22 +08:00
|
|
|
return -ENOENT;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2006-03-21 11:18:52 +08:00
|
|
|
EXPORT_SYMBOL(xfrm_policy_delete);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
int xfrm_sk_policy_insert(struct sock *sk, int dir, struct xfrm_policy *pol)
|
|
|
|
{
|
2008-11-26 09:33:06 +08:00
|
|
|
struct net *net = xp_net(pol);
|
2005-04-17 06:20:36 +08:00
|
|
|
struct xfrm_policy *old_pol;
|
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
|
if (pol && pol->type != XFRM_POLICY_TYPE_MAIN)
|
|
|
|
return -EINVAL;
|
|
|
|
#endif
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
old_pol = sk->sk_policy[dir];
|
|
|
|
sk->sk_policy[dir] = pol;
|
|
|
|
if (pol) {
|
2007-03-05 08:12:44 +08:00
|
|
|
pol->curlft.add_time = get_seconds();
|
2008-11-26 09:33:06 +08:00
|
|
|
pol->index = xfrm_gen_index(net, XFRM_POLICY_MAX+dir);
|
2005-04-17 06:20:36 +08:00
|
|
|
__xfrm_policy_link(pol, XFRM_POLICY_MAX+dir);
|
|
|
|
}
|
|
|
|
if (old_pol)
|
|
|
|
__xfrm_policy_unlink(old_pol, XFRM_POLICY_MAX+dir);
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
|
|
|
if (old_pol) {
|
|
|
|
xfrm_policy_kill(old_pol);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct xfrm_policy *clone_policy(struct xfrm_policy *old, int dir)
|
|
|
|
{
|
2008-11-26 09:21:45 +08:00
|
|
|
struct xfrm_policy *newp = xfrm_policy_alloc(xp_net(old), GFP_ATOMIC);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (newp) {
|
|
|
|
newp->selector = old->selector;
|
2008-04-13 10:07:52 +08:00
|
|
|
if (security_xfrm_policy_clone(old->security,
|
|
|
|
&newp->security)) {
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
kfree(newp);
|
|
|
|
return NULL; /* ENOMEM */
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
newp->lft = old->lft;
|
|
|
|
newp->curlft = old->curlft;
|
|
|
|
newp->action = old->action;
|
|
|
|
newp->flags = old->flags;
|
|
|
|
newp->xfrm_nr = old->xfrm_nr;
|
|
|
|
newp->index = old->index;
|
2006-08-24 13:43:30 +08:00
|
|
|
newp->type = old->type;
|
2005-04-17 06:20:36 +08:00
|
|
|
memcpy(newp->xfrm_vec, old->xfrm_vec,
|
|
|
|
newp->xfrm_nr*sizeof(struct xfrm_tmpl));
|
|
|
|
write_lock_bh(&xfrm_policy_lock);
|
|
|
|
__xfrm_policy_link(newp, XFRM_POLICY_MAX+dir);
|
|
|
|
write_unlock_bh(&xfrm_policy_lock);
|
|
|
|
xfrm_pol_put(newp);
|
|
|
|
}
|
|
|
|
return newp;
|
|
|
|
}
|
|
|
|
|
|
|
|
int __xfrm_sk_clone_policy(struct sock *sk)
|
|
|
|
{
|
|
|
|
struct xfrm_policy *p0 = sk->sk_policy[0],
|
|
|
|
*p1 = sk->sk_policy[1];
|
|
|
|
|
|
|
|
sk->sk_policy[0] = sk->sk_policy[1] = NULL;
|
|
|
|
if (p0 && (sk->sk_policy[0] = clone_policy(p0, 0)) == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
if (p1 && (sk->sk_policy[1] = clone_policy(p1, 1)) == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-09-20 03:57:34 +08:00
|
|
|
static int
|
2008-11-26 09:56:49 +08:00
|
|
|
xfrm_get_saddr(struct net *net, xfrm_address_t *local, xfrm_address_t *remote,
|
2006-09-20 03:57:34 +08:00
|
|
|
unsigned short family)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
|
|
|
|
|
|
|
if (unlikely(afinfo == NULL))
|
|
|
|
return -EINVAL;
|
2008-11-26 09:56:49 +08:00
|
|
|
err = afinfo->get_saddr(net, local, remote);
|
2006-09-20 03:57:34 +08:00
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Resolve list of templates for the flow, given policy. */
|
|
|
|
|
|
|
|
static int
|
2006-08-24 13:43:30 +08:00
|
|
|
xfrm_tmpl_resolve_one(struct xfrm_policy *policy, struct flowi *fl,
|
|
|
|
struct xfrm_state **xfrm,
|
|
|
|
unsigned short family)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-11-26 09:56:49 +08:00
|
|
|
struct net *net = xp_net(policy);
|
2005-04-17 06:20:36 +08:00
|
|
|
int nx;
|
|
|
|
int i, error;
|
|
|
|
xfrm_address_t *daddr = xfrm_flowi_daddr(fl, family);
|
|
|
|
xfrm_address_t *saddr = xfrm_flowi_saddr(fl, family);
|
2006-09-20 03:57:34 +08:00
|
|
|
xfrm_address_t tmp;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
for (nx=0, i = 0; i < policy->xfrm_nr; i++) {
|
|
|
|
struct xfrm_state *x;
|
|
|
|
xfrm_address_t *remote = daddr;
|
|
|
|
xfrm_address_t *local = saddr;
|
|
|
|
struct xfrm_tmpl *tmpl = &policy->xfrm_vec[i];
|
|
|
|
|
2007-07-26 15:08:42 +08:00
|
|
|
if (tmpl->mode == XFRM_MODE_TUNNEL ||
|
|
|
|
tmpl->mode == XFRM_MODE_BEET) {
|
2005-04-17 06:20:36 +08:00
|
|
|
remote = &tmpl->id.daddr;
|
|
|
|
local = &tmpl->saddr;
|
2006-12-01 08:40:43 +08:00
|
|
|
family = tmpl->encap_family;
|
2006-09-20 03:57:34 +08:00
|
|
|
if (xfrm_addr_any(local, family)) {
|
2008-11-26 09:56:49 +08:00
|
|
|
error = xfrm_get_saddr(net, &tmp, remote, family);
|
2006-09-20 03:57:34 +08:00
|
|
|
if (error)
|
|
|
|
goto fail;
|
|
|
|
local = &tmp;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
x = xfrm_state_find(remote, local, fl, tmpl, policy, &error, family);
|
|
|
|
|
|
|
|
if (x && x->km.state == XFRM_STATE_VALID) {
|
|
|
|
xfrm[nx++] = x;
|
|
|
|
daddr = remote;
|
|
|
|
saddr = local;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (x) {
|
|
|
|
error = (x->km.state == XFRM_STATE_ERROR ?
|
|
|
|
-EINVAL : -EAGAIN);
|
|
|
|
xfrm_state_put(x);
|
|
|
|
}
|
2008-10-23 12:27:19 +08:00
|
|
|
else if (error == -ESRCH)
|
|
|
|
error = -EAGAIN;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!tmpl->optional)
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
return nx;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
for (nx--; nx>=0; nx--)
|
|
|
|
xfrm_state_put(xfrm[nx]);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
static int
|
|
|
|
xfrm_tmpl_resolve(struct xfrm_policy **pols, int npols, struct flowi *fl,
|
|
|
|
struct xfrm_state **xfrm,
|
|
|
|
unsigned short family)
|
|
|
|
{
|
2006-08-24 13:48:31 +08:00
|
|
|
struct xfrm_state *tp[XFRM_MAX_DEPTH];
|
|
|
|
struct xfrm_state **tpp = (npols > 1) ? tp : xfrm;
|
2006-08-24 13:43:30 +08:00
|
|
|
int cnx = 0;
|
|
|
|
int error;
|
|
|
|
int ret;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < npols; i++) {
|
|
|
|
if (cnx + pols[i]->xfrm_nr >= XFRM_MAX_DEPTH) {
|
|
|
|
error = -ENOBUFS;
|
|
|
|
goto fail;
|
|
|
|
}
|
2006-08-24 13:48:31 +08:00
|
|
|
|
|
|
|
ret = xfrm_tmpl_resolve_one(pols[i], fl, &tpp[cnx], family);
|
2006-08-24 13:43:30 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
error = ret;
|
|
|
|
goto fail;
|
|
|
|
} else
|
|
|
|
cnx += ret;
|
|
|
|
}
|
|
|
|
|
2006-08-24 13:48:31 +08:00
|
|
|
/* found states are sorted for outbound processing */
|
|
|
|
if (npols > 1)
|
|
|
|
xfrm_state_sort(xfrm, tpp, cnx, family);
|
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
return cnx;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
for (cnx--; cnx>=0; cnx--)
|
2006-08-24 13:48:31 +08:00
|
|
|
xfrm_state_put(tpp[cnx]);
|
2006-08-24 13:43:30 +08:00
|
|
|
return error;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Check that the bundle accepts the flow and its components are
|
|
|
|
* still valid.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct dst_entry *
|
|
|
|
xfrm_find_bundle(struct flowi *fl, struct xfrm_policy *policy, unsigned short family)
|
|
|
|
{
|
|
|
|
struct dst_entry *x;
|
|
|
|
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
|
|
|
if (unlikely(afinfo == NULL))
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
x = afinfo->find_bundle(fl, policy);
|
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
|
|
|
return x;
|
|
|
|
}
|
|
|
|
|
2007-12-12 01:32:34 +08:00
|
|
|
static inline int xfrm_get_tos(struct flowi *fl, int family)
|
|
|
|
{
|
|
|
|
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
|
|
|
int tos;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-12-12 01:32:34 +08:00
|
|
|
if (!afinfo)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
tos = afinfo->get_tos(fl);
|
|
|
|
|
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
|
|
|
|
|
|
|
return tos;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline struct xfrm_dst *xfrm_alloc_dst(int family)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
2007-12-12 01:32:34 +08:00
|
|
|
struct xfrm_dst *xdst;
|
|
|
|
|
|
|
|
if (!afinfo)
|
|
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
|
|
|
|
xdst = dst_alloc(afinfo->dst_ops) ?: ERR_PTR(-ENOBUFS);
|
|
|
|
|
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
|
|
|
|
|
|
|
return xdst;
|
|
|
|
}
|
|
|
|
|
2007-12-21 12:41:12 +08:00
|
|
|
static inline int xfrm_init_path(struct xfrm_dst *path, struct dst_entry *dst,
|
|
|
|
int nfheader_len)
|
|
|
|
{
|
|
|
|
struct xfrm_policy_afinfo *afinfo =
|
|
|
|
xfrm_policy_get_afinfo(dst->ops->family);
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!afinfo)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
err = afinfo->init_path(path, dst, nfheader_len);
|
|
|
|
|
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2007-12-12 01:32:34 +08:00
|
|
|
static inline int xfrm_fill_dst(struct xfrm_dst *xdst, struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct xfrm_policy_afinfo *afinfo =
|
|
|
|
xfrm_policy_get_afinfo(xdst->u.dst.ops->family);
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!afinfo)
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
2007-12-12 01:32:34 +08:00
|
|
|
|
|
|
|
err = afinfo->fill_dst(xdst, dev);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
2007-12-12 01:32:34 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2007-12-12 01:32:34 +08:00
|
|
|
/* Allocate chain of dst_entry's, attach known xfrm's, calculate
|
|
|
|
* all the metrics... Shortly, bundle a bundle.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
|
|
|
|
struct xfrm_state **xfrm, int nx,
|
|
|
|
struct flowi *fl,
|
|
|
|
struct dst_entry *dst)
|
|
|
|
{
|
|
|
|
unsigned long now = jiffies;
|
|
|
|
struct net_device *dev;
|
|
|
|
struct dst_entry *dst_prev = NULL;
|
|
|
|
struct dst_entry *dst0 = NULL;
|
|
|
|
int i = 0;
|
|
|
|
int err;
|
|
|
|
int header_len = 0;
|
2007-12-21 12:41:12 +08:00
|
|
|
int nfheader_len = 0;
|
2007-12-12 01:32:34 +08:00
|
|
|
int trailer_len = 0;
|
|
|
|
int tos;
|
|
|
|
int family = policy->selector.family;
|
2008-02-22 13:48:22 +08:00
|
|
|
xfrm_address_t saddr, daddr;
|
|
|
|
|
|
|
|
xfrm_flowi_addr_get(fl, &saddr, &daddr, family);
|
2007-12-12 01:32:34 +08:00
|
|
|
|
|
|
|
tos = xfrm_get_tos(fl, family);
|
|
|
|
err = tos;
|
|
|
|
if (tos < 0)
|
|
|
|
goto put_states;
|
|
|
|
|
|
|
|
dst_hold(dst);
|
|
|
|
|
|
|
|
for (; i < nx; i++) {
|
|
|
|
struct xfrm_dst *xdst = xfrm_alloc_dst(family);
|
|
|
|
struct dst_entry *dst1 = &xdst->u.dst;
|
|
|
|
|
|
|
|
err = PTR_ERR(xdst);
|
|
|
|
if (IS_ERR(xdst)) {
|
|
|
|
dst_release(dst);
|
|
|
|
goto put_states;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dst_prev)
|
|
|
|
dst0 = dst1;
|
|
|
|
else {
|
|
|
|
dst_prev->child = dst_clone(dst1);
|
|
|
|
dst1->flags |= DST_NOHASH;
|
|
|
|
}
|
|
|
|
|
|
|
|
xdst->route = dst;
|
|
|
|
memcpy(&dst1->metrics, &dst->metrics, sizeof(dst->metrics));
|
|
|
|
|
|
|
|
if (xfrm[i]->props.mode != XFRM_MODE_TRANSPORT) {
|
|
|
|
family = xfrm[i]->props.family;
|
2008-02-22 13:48:22 +08:00
|
|
|
dst = xfrm_dst_lookup(xfrm[i], tos, &saddr, &daddr,
|
|
|
|
family);
|
2007-12-12 01:32:34 +08:00
|
|
|
err = PTR_ERR(dst);
|
|
|
|
if (IS_ERR(dst))
|
|
|
|
goto put_states;
|
|
|
|
} else
|
|
|
|
dst_hold(dst);
|
|
|
|
|
|
|
|
dst1->xfrm = xfrm[i];
|
|
|
|
xdst->genid = xfrm[i]->genid;
|
|
|
|
|
|
|
|
dst1->obsolete = -1;
|
|
|
|
dst1->flags |= DST_HOST;
|
|
|
|
dst1->lastuse = now;
|
|
|
|
|
|
|
|
dst1->input = dst_discard;
|
|
|
|
dst1->output = xfrm[i]->outer_mode->afinfo->output;
|
|
|
|
|
|
|
|
dst1->next = dst_prev;
|
|
|
|
dst_prev = dst1;
|
|
|
|
|
|
|
|
header_len += xfrm[i]->props.header_len;
|
2007-12-21 12:41:12 +08:00
|
|
|
if (xfrm[i]->type->flags & XFRM_TYPE_NON_FRAGMENT)
|
|
|
|
nfheader_len += xfrm[i]->props.header_len;
|
2007-12-12 01:32:34 +08:00
|
|
|
trailer_len += xfrm[i]->props.trailer_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
dst_prev->child = dst;
|
|
|
|
dst0->path = dst;
|
|
|
|
|
|
|
|
err = -ENODEV;
|
|
|
|
dev = dst->dev;
|
|
|
|
if (!dev)
|
|
|
|
goto free_dst;
|
|
|
|
|
|
|
|
/* Copy neighbout for reachability confirmation */
|
|
|
|
dst0->neighbour = neigh_clone(dst->neighbour);
|
|
|
|
|
2007-12-21 12:41:12 +08:00
|
|
|
xfrm_init_path((struct xfrm_dst *)dst0, dst, nfheader_len);
|
2007-12-12 01:32:34 +08:00
|
|
|
xfrm_init_pmtu(dst_prev);
|
|
|
|
|
|
|
|
for (dst_prev = dst0; dst_prev != dst; dst_prev = dst_prev->child) {
|
|
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst_prev;
|
|
|
|
|
|
|
|
err = xfrm_fill_dst(xdst, dev);
|
|
|
|
if (err)
|
|
|
|
goto free_dst;
|
|
|
|
|
|
|
|
dst_prev->header_len = header_len;
|
|
|
|
dst_prev->trailer_len = trailer_len;
|
|
|
|
header_len -= xdst->u.dst.xfrm->props.header_len;
|
|
|
|
trailer_len -= xdst->u.dst.xfrm->props.trailer_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return dst0;
|
|
|
|
|
|
|
|
put_states:
|
|
|
|
for (; i < nx; i++)
|
|
|
|
xfrm_state_put(xfrm[i]);
|
|
|
|
free_dst:
|
|
|
|
if (dst0)
|
|
|
|
dst_free(dst0);
|
|
|
|
dst0 = ERR_PTR(err);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2007-04-30 15:33:35 +08:00
|
|
|
static int inline
|
|
|
|
xfrm_dst_alloc_copy(void **target, void *src, int size)
|
|
|
|
{
|
|
|
|
if (!*target) {
|
|
|
|
*target = kmalloc(size, GFP_ATOMIC);
|
|
|
|
if (!*target)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
memcpy(*target, src, size);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int inline
|
|
|
|
xfrm_dst_update_parent(struct dst_entry *dst, struct xfrm_selector *sel)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
|
|
return xfrm_dst_alloc_copy((void **)&(xdst->partner),
|
|
|
|
sel, sizeof(*sel));
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static int inline
|
|
|
|
xfrm_dst_update_origin(struct dst_entry *dst, struct flowi *fl)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
|
|
return xfrm_dst_alloc_copy((void **)&(xdst->origin), fl, sizeof(*fl));
|
|
|
|
#else
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
static int stale_bundle(struct dst_entry *dst);
|
|
|
|
|
|
|
|
/* Main function: finds/creates a bundle for given flow.
|
|
|
|
*
|
|
|
|
* At the moment we eat a raw IP route. Mostly to speed up lookups
|
|
|
|
* on interfaces with disabled IPsec.
|
|
|
|
*/
|
2008-11-26 09:35:18 +08:00
|
|
|
int __xfrm_lookup(struct net *net, struct dst_entry **dst_p, struct flowi *fl,
|
2007-05-25 09:17:54 +08:00
|
|
|
struct sock *sk, int flags)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct xfrm_policy *policy;
|
2006-08-24 13:43:30 +08:00
|
|
|
struct xfrm_policy *pols[XFRM_POLICY_TYPE_MAX];
|
|
|
|
int npols;
|
|
|
|
int pol_dead;
|
|
|
|
int xfrm_nr;
|
|
|
|
int pi;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct xfrm_state *xfrm[XFRM_MAX_DEPTH];
|
|
|
|
struct dst_entry *dst, *dst_orig = *dst_p;
|
|
|
|
int nx = 0;
|
|
|
|
int err;
|
|
|
|
u32 genid;
|
2006-02-22 05:37:35 +08:00
|
|
|
u16 family;
|
[LSM-IPSec]: Security association restriction.
This patch series implements per packet access control via the
extension of the Linux Security Modules (LSM) interface by hooks in
the XFRM and pfkey subsystems that leverage IPSec security
associations to label packets. Extensions to the SELinux LSM are
included that leverage the patch for this purpose.
This patch implements the changes necessary to the XFRM subsystem,
pfkey interface, ipv4/ipv6, and xfrm_user interface to restrict a
socket to use only authorized security associations (or no security
association) to send/receive network packets.
Patch purpose:
The patch is designed to enable access control per packets based on
the strongly authenticated IPSec security association. Such access
controls augment the existing ones based on network interface and IP
address. The former are very coarse-grained, and the latter can be
spoofed. By using IPSec, the system can control access to remote
hosts based on cryptographic keys generated using the IPSec mechanism.
This enables access control on a per-machine basis or per-application
if the remote machine is running the same mechanism and trusted to
enforce the access control policy.
Patch design approach:
The overall approach is that policy (xfrm_policy) entries set by
user-level programs (e.g., setkey for ipsec-tools) are extended with a
security context that is used at policy selection time in the XFRM
subsystem to restrict the sockets that can send/receive packets via
security associations (xfrm_states) that are built from those
policies.
A presentation available at
www.selinux-symposium.org/2005/presentations/session2/2-3-jaeger.pdf
from the SELinux symposium describes the overall approach.
Patch implementation details:
On output, the policy retrieved (via xfrm_policy_lookup or
xfrm_sk_policy_lookup) must be authorized for the security context of
the socket and the same security context is required for resultant
security association (retrieved or negotiated via racoon in
ipsec-tools). This is enforced in xfrm_state_find.
On input, the policy retrieved must also be authorized for the socket
(at __xfrm_policy_check), and the security context of the policy must
also match the security association being used.
The patch has virtually no impact on packets that do not use IPSec.
The existing Netfilter (outgoing) and LSM rcv_skb hooks are used as
before.
Also, if IPSec is used without security contexts, the impact is
minimal. The LSM must allow such policies to be selected for the
combination of socket and remote machine, but subsequent IPSec
processing proceeds as in the original case.
Testing:
The pfkey interface is tested using the ipsec-tools. ipsec-tools have
been modified (a separate ipsec-tools patch is available for version
0.5) that supports assignment of xfrm_policy entries and security
associations with security contexts via setkey and the negotiation
using the security contexts via racoon.
The xfrm_user interface is tested via ad hoc programs that set
security contexts. These programs are also available from me, and
contain programs for setting, getting, and deleting policy for testing
this interface. Testing of sa functions was done by tracing kernel
behavior.
Signed-off-by: Trent Jaeger <tjaeger@cse.psu.edu>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
2005-12-14 15:12:27 +08:00
|
|
|
u8 dir = policy_to_flow_dir(XFRM_POLICY_OUT);
|
2006-07-25 14:29:07 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
restart:
|
|
|
|
genid = atomic_read(&flow_cache_genid);
|
|
|
|
policy = NULL;
|
2006-08-24 13:43:30 +08:00
|
|
|
for (pi = 0; pi < ARRAY_SIZE(pols); pi++)
|
|
|
|
pols[pi] = NULL;
|
|
|
|
npols = 0;
|
|
|
|
pol_dead = 0;
|
|
|
|
xfrm_nr = 0;
|
|
|
|
|
2007-08-26 04:46:55 +08:00
|
|
|
if (sk && sk->sk_policy[XFRM_POLICY_OUT]) {
|
2006-07-25 14:29:07 +08:00
|
|
|
policy = xfrm_sk_policy_lookup(sk, XFRM_POLICY_OUT, fl);
|
2007-12-11 20:38:08 +08:00
|
|
|
err = PTR_ERR(policy);
|
2007-12-21 12:43:36 +08:00
|
|
|
if (IS_ERR(policy)) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
|
2007-12-11 20:38:08 +08:00
|
|
|
goto dropdst;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2006-10-06 04:42:35 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!policy) {
|
|
|
|
/* To accelerate a bit... */
|
2006-08-24 19:45:07 +08:00
|
|
|
if ((dst_orig->flags & DST_NOXFRM) ||
|
2008-11-26 09:35:18 +08:00
|
|
|
!net->xfrm.policy_count[XFRM_POLICY_OUT])
|
2007-12-13 02:44:43 +08:00
|
|
|
goto nopol;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-11-26 09:35:18 +08:00
|
|
|
policy = flow_cache_lookup(net, fl, dst_orig->ops->family,
|
2006-02-22 05:37:35 +08:00
|
|
|
dir, xfrm_policy_lookup);
|
2007-12-11 20:38:08 +08:00
|
|
|
err = PTR_ERR(policy);
|
2008-01-08 13:46:15 +08:00
|
|
|
if (IS_ERR(policy)) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
|
2007-12-11 20:38:08 +08:00
|
|
|
goto dropdst;
|
2008-01-08 13:46:15 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!policy)
|
2007-12-13 02:44:43 +08:00
|
|
|
goto nopol;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-02-22 05:37:35 +08:00
|
|
|
family = dst_orig->ops->family;
|
2006-08-24 13:43:30 +08:00
|
|
|
pols[0] = policy;
|
|
|
|
npols ++;
|
|
|
|
xfrm_nr += pols[0]->xfrm_nr;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-12-14 01:30:59 +08:00
|
|
|
err = -ENOENT;
|
2007-12-13 02:44:43 +08:00
|
|
|
if ((flags & XFRM_LOOKUP_ICMP) && !(policy->flags & XFRM_POLICY_ICMP))
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
policy->curlft.use_time = get_seconds();
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (policy->action) {
|
2007-11-29 21:50:31 +08:00
|
|
|
default:
|
2005-04-17 06:20:36 +08:00
|
|
|
case XFRM_POLICY_BLOCK:
|
|
|
|
/* Prohibit the flow */
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLBLOCK);
|
2005-09-09 06:11:55 +08:00
|
|
|
err = -EPERM;
|
|
|
|
goto error;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
case XFRM_POLICY_ALLOW:
|
2006-08-24 13:43:30 +08:00
|
|
|
#ifndef CONFIG_XFRM_SUB_POLICY
|
2005-04-17 06:20:36 +08:00
|
|
|
if (policy->xfrm_nr == 0) {
|
|
|
|
/* Flow passes not transformed. */
|
|
|
|
xfrm_pol_put(policy);
|
|
|
|
return 0;
|
|
|
|
}
|
2006-08-24 13:43:30 +08:00
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* Try to find matching bundle.
|
|
|
|
*
|
|
|
|
* LATER: help from flow cache. It is optional, this
|
|
|
|
* is required only for output policy.
|
|
|
|
*/
|
|
|
|
dst = xfrm_find_bundle(fl, policy, family);
|
|
|
|
if (IS_ERR(dst)) {
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
|
2005-09-09 06:11:55 +08:00
|
|
|
err = PTR_ERR(dst);
|
|
|
|
goto error;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (dst)
|
|
|
|
break;
|
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
|
if (pols[0]->type != XFRM_POLICY_TYPE_MAIN) {
|
2008-11-26 09:35:18 +08:00
|
|
|
pols[1] = xfrm_policy_lookup_bytype(net,
|
|
|
|
XFRM_POLICY_TYPE_MAIN,
|
2006-08-24 13:43:30 +08:00
|
|
|
fl, family,
|
|
|
|
XFRM_POLICY_OUT);
|
|
|
|
if (pols[1]) {
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
if (IS_ERR(pols[1])) {
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLERROR);
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
err = PTR_ERR(pols[1]);
|
|
|
|
goto error;
|
|
|
|
}
|
2006-08-24 13:43:30 +08:00
|
|
|
if (pols[1]->action == XFRM_POLICY_BLOCK) {
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLBLOCK);
|
2006-08-24 13:43:30 +08:00
|
|
|
err = -EPERM;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
npols ++;
|
|
|
|
xfrm_nr += pols[1]->xfrm_nr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Because neither flowi nor bundle information knows about
|
|
|
|
* transformation template size. On more than one policy usage
|
|
|
|
* we can realize whether all of them is bypass or not after
|
|
|
|
* they are searched. See above not-transformed bypass
|
|
|
|
* is surrounded by non-sub policy configuration, too.
|
|
|
|
*/
|
|
|
|
if (xfrm_nr == 0) {
|
|
|
|
/* Flow passes not transformed. */
|
|
|
|
xfrm_pols_put(pols, npols);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
nx = xfrm_tmpl_resolve(pols, npols, fl, xfrm, family);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (unlikely(nx<0)) {
|
|
|
|
err = nx;
|
2007-05-25 09:17:54 +08:00
|
|
|
if (err == -EAGAIN && sysctl_xfrm_larval_drop) {
|
|
|
|
/* EREMOTE tells the caller to generate
|
|
|
|
* a one-shot blackhole route.
|
|
|
|
*/
|
2008-01-08 13:46:15 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
|
2007-05-25 09:17:54 +08:00
|
|
|
xfrm_pol_put(policy);
|
|
|
|
return -EREMOTE;
|
|
|
|
}
|
2007-12-13 02:36:59 +08:00
|
|
|
if (err == -EAGAIN && (flags & XFRM_LOOKUP_WAIT)) {
|
2005-04-17 06:20:36 +08:00
|
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
|
2008-11-26 09:35:18 +08:00
|
|
|
add_wait_queue(&net->xfrm.km_waitq, &wait);
|
2005-04-17 06:20:36 +08:00
|
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
schedule();
|
|
|
|
set_current_state(TASK_RUNNING);
|
2008-11-26 09:35:18 +08:00
|
|
|
remove_wait_queue(&net->xfrm.km_waitq, &wait);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
nx = xfrm_tmpl_resolve(pols, npols, fl, xfrm, family);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (nx == -EAGAIN && signal_pending(current)) {
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
|
2005-04-17 06:20:36 +08:00
|
|
|
err = -ERESTART;
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
if (nx == -EAGAIN ||
|
|
|
|
genid != atomic_read(&flow_cache_genid)) {
|
2006-08-24 13:43:30 +08:00
|
|
|
xfrm_pols_put(pols, npols);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto restart;
|
|
|
|
}
|
|
|
|
err = nx;
|
|
|
|
}
|
2007-12-21 12:43:36 +08:00
|
|
|
if (err < 0) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTNOSTATES);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto error;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
if (nx == 0) {
|
|
|
|
/* Flow passes not transformed. */
|
2006-08-24 13:43:30 +08:00
|
|
|
xfrm_pols_put(pols, npols);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-12-12 01:32:34 +08:00
|
|
|
dst = xfrm_bundle_create(policy, xfrm, nx, fl, dst_orig);
|
|
|
|
err = PTR_ERR(dst);
|
2007-12-21 12:43:36 +08:00
|
|
|
if (IS_ERR(dst)) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLEGENERROR);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto error;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
for (pi = 0; pi < npols; pi++) {
|
|
|
|
read_lock_bh(&pols[pi]->lock);
|
2008-10-01 22:03:24 +08:00
|
|
|
pol_dead |= pols[pi]->walk.dead;
|
2006-08-24 13:43:30 +08:00
|
|
|
read_unlock_bh(&pols[pi]->lock);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
write_lock_bh(&policy->lock);
|
2006-08-24 13:43:30 +08:00
|
|
|
if (unlikely(pol_dead || stale_bundle(dst))) {
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Wow! While we worked on resolving, this
|
|
|
|
* policy has gone. Retry. It is not paranoia,
|
|
|
|
* we just cannot enlist new bundle to dead object.
|
|
|
|
* We can't enlist stable bundles either.
|
|
|
|
*/
|
|
|
|
write_unlock_bh(&policy->lock);
|
2008-09-03 08:24:28 +08:00
|
|
|
dst_free(dst);
|
[IPSEC]: Fix strange IPsec freeze.
Problem discovered and initial patch by Olaf Kirch:
there's a problem with IPsec that has been bugging some of our users
for the last couple of kernel revs. Every now and then, IPsec will
freeze the machine completely. This is with openswan user land,
and with kernels up to and including 2.6.16-rc2.
I managed to debug this a little, and what happens is that we end
up looping in xfrm_lookup, and never get out. With a bit of debug
printks added, I can this happening:
ip_route_output_flow calls xfrm_lookup
xfrm_find_bundle returns NULL (apparently we're in the
middle of negotiating a new SA or something)
We therefore call xfrm_tmpl_resolve. This returns EAGAIN
We go to sleep, waiting for a policy update.
Then we loop back to the top
Apparently, the dst_orig that was passed into xfrm_lookup
has been dropped from the routing table (obsolete=2)
This leads to the endless loop, because we now create
a new bundle, check the new bundle and find it's stale
(stale_bundle -> xfrm_bundle_ok -> dst_check() return 0)
People have been testing with the patch below, which seems to fix the
problem partially. They still see connection hangs however (things
only clear up when they start a new ping or new ssh). So the patch
is obvsiouly not sufficient, and something else seems to go wrong.
I'm grateful for any hints you may have...
I suggest that we simply bail out always. If the dst decides to die
on us later on, the packet will be dropped anyway. So there is no
great urgency to retry here. Once we have the proper resolution
queueing, we can then do the retry again.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Acked-by: Olaf Kirch <okir@suse.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
2006-02-14 08:01:27 +08:00
|
|
|
|
2007-12-21 12:43:36 +08:00
|
|
|
if (pol_dead)
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTPOLDEAD);
|
|
|
|
else
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
|
[IPSEC]: Fix strange IPsec freeze.
Problem discovered and initial patch by Olaf Kirch:
there's a problem with IPsec that has been bugging some of our users
for the last couple of kernel revs. Every now and then, IPsec will
freeze the machine completely. This is with openswan user land,
and with kernels up to and including 2.6.16-rc2.
I managed to debug this a little, and what happens is that we end
up looping in xfrm_lookup, and never get out. With a bit of debug
printks added, I can this happening:
ip_route_output_flow calls xfrm_lookup
xfrm_find_bundle returns NULL (apparently we're in the
middle of negotiating a new SA or something)
We therefore call xfrm_tmpl_resolve. This returns EAGAIN
We go to sleep, waiting for a policy update.
Then we loop back to the top
Apparently, the dst_orig that was passed into xfrm_lookup
has been dropped from the routing table (obsolete=2)
This leads to the endless loop, because we now create
a new bundle, check the new bundle and find it's stale
(stale_bundle -> xfrm_bundle_ok -> dst_check() return 0)
People have been testing with the patch below, which seems to fix the
problem partially. They still see connection hangs however (things
only clear up when they start a new ping or new ssh). So the patch
is obvsiouly not sufficient, and something else seems to go wrong.
I'm grateful for any hints you may have...
I suggest that we simply bail out always. If the dst decides to die
on us later on, the packet will be dropped anyway. So there is no
great urgency to retry here. Once we have the proper resolution
queueing, we can then do the retry again.
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Acked-by: Olaf Kirch <okir@suse.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
2006-02-14 08:01:27 +08:00
|
|
|
err = -EHOSTUNREACH;
|
|
|
|
goto error;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-04-30 15:33:35 +08:00
|
|
|
|
|
|
|
if (npols > 1)
|
|
|
|
err = xfrm_dst_update_parent(dst, &pols[1]->selector);
|
|
|
|
else
|
|
|
|
err = xfrm_dst_update_origin(dst, fl);
|
|
|
|
if (unlikely(err)) {
|
|
|
|
write_unlock_bh(&policy->lock);
|
2008-09-03 08:24:28 +08:00
|
|
|
dst_free(dst);
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMOUTBUNDLECHECKERROR);
|
2007-04-30 15:33:35 +08:00
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
dst->next = policy->bundles;
|
|
|
|
policy->bundles = dst;
|
|
|
|
dst_hold(dst);
|
|
|
|
write_unlock_bh(&policy->lock);
|
|
|
|
}
|
|
|
|
*dst_p = dst;
|
|
|
|
dst_release(dst_orig);
|
2007-02-09 22:25:29 +08:00
|
|
|
xfrm_pols_put(pols, npols);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
error:
|
2006-08-24 13:43:30 +08:00
|
|
|
xfrm_pols_put(pols, npols);
|
2007-12-11 20:38:08 +08:00
|
|
|
dropdst:
|
|
|
|
dst_release(dst_orig);
|
2005-04-17 06:20:36 +08:00
|
|
|
*dst_p = NULL;
|
|
|
|
return err;
|
2007-12-13 02:44:43 +08:00
|
|
|
|
|
|
|
nopol:
|
2007-12-14 01:30:59 +08:00
|
|
|
err = -ENOENT;
|
2007-12-13 02:44:43 +08:00
|
|
|
if (flags & XFRM_LOOKUP_ICMP)
|
|
|
|
goto dropdst;
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-05-25 09:17:54 +08:00
|
|
|
EXPORT_SYMBOL(__xfrm_lookup);
|
|
|
|
|
2008-11-26 09:35:18 +08:00
|
|
|
int xfrm_lookup(struct net *net, struct dst_entry **dst_p, struct flowi *fl,
|
2007-05-25 09:17:54 +08:00
|
|
|
struct sock *sk, int flags)
|
|
|
|
{
|
2008-11-26 09:35:18 +08:00
|
|
|
int err = __xfrm_lookup(net, dst_p, fl, sk, flags);
|
2007-05-25 09:17:54 +08:00
|
|
|
|
|
|
|
if (err == -EREMOTE) {
|
|
|
|
dst_release(*dst_p);
|
|
|
|
*dst_p = NULL;
|
|
|
|
err = -EAGAIN;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
EXPORT_SYMBOL(xfrm_lookup);
|
|
|
|
|
2006-08-24 11:41:00 +08:00
|
|
|
static inline int
|
|
|
|
xfrm_secpath_reject(int idx, struct sk_buff *skb, struct flowi *fl)
|
|
|
|
{
|
|
|
|
struct xfrm_state *x;
|
|
|
|
|
|
|
|
if (!skb->sp || idx < 0 || idx >= skb->sp->len)
|
|
|
|
return 0;
|
|
|
|
x = skb->sp->xvec[idx];
|
|
|
|
if (!x->type->reject)
|
|
|
|
return 0;
|
2007-10-10 04:24:07 +08:00
|
|
|
return x->type->reject(x, skb, fl);
|
2006-08-24 11:41:00 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* When skb is transformed back to its "native" form, we have to
|
|
|
|
* check policy restrictions. At the moment we make this in maximally
|
|
|
|
* stupid way. Shame on me. :-) Of course, connected sockets must
|
|
|
|
* have policy cached at them.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static inline int
|
2007-02-09 22:25:29 +08:00
|
|
|
xfrm_state_ok(struct xfrm_tmpl *tmpl, struct xfrm_state *x,
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned short family)
|
|
|
|
{
|
|
|
|
if (xfrm_state_kern(x))
|
2007-02-14 04:57:16 +08:00
|
|
|
return tmpl->optional && !xfrm_state_addr_cmp(tmpl, x, tmpl->encap_family);
|
2005-04-17 06:20:36 +08:00
|
|
|
return x->id.proto == tmpl->id.proto &&
|
|
|
|
(x->id.spi == tmpl->id.spi || !tmpl->id.spi) &&
|
|
|
|
(x->props.reqid == tmpl->reqid || !tmpl->reqid) &&
|
|
|
|
x->props.mode == tmpl->mode &&
|
2008-04-22 15:46:42 +08:00
|
|
|
(tmpl->allalgs || (tmpl->aalgos & (1<<x->props.aalgo)) ||
|
2006-08-24 09:00:48 +08:00
|
|
|
!(xfrm_id_proto_match(tmpl->id.proto, IPSEC_PROTO_ANY))) &&
|
2006-09-23 06:05:15 +08:00
|
|
|
!(x->props.mode != XFRM_MODE_TRANSPORT &&
|
|
|
|
xfrm_state_addr_cmp(tmpl, x, family));
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2006-08-24 11:41:00 +08:00
|
|
|
/*
|
|
|
|
* 0 or more than 0 is returned when validation is succeeded (either bypass
|
|
|
|
* because of optional transport mode, or next index of the mathced secpath
|
|
|
|
* state with the template.
|
|
|
|
* -1 is returned when no matching template is found.
|
|
|
|
* Otherwise "-2 - errored_index" is returned.
|
|
|
|
*/
|
2005-04-17 06:20:36 +08:00
|
|
|
static inline int
|
|
|
|
xfrm_policy_ok(struct xfrm_tmpl *tmpl, struct sec_path *sp, int start,
|
|
|
|
unsigned short family)
|
|
|
|
{
|
|
|
|
int idx = start;
|
|
|
|
|
|
|
|
if (tmpl->optional) {
|
2006-09-23 06:05:15 +08:00
|
|
|
if (tmpl->mode == XFRM_MODE_TRANSPORT)
|
2005-04-17 06:20:36 +08:00
|
|
|
return start;
|
|
|
|
} else
|
|
|
|
start = -1;
|
|
|
|
for (; idx < sp->len; idx++) {
|
2006-04-01 16:54:16 +08:00
|
|
|
if (xfrm_state_ok(tmpl, sp->xvec[idx], family))
|
2005-04-17 06:20:36 +08:00
|
|
|
return ++idx;
|
2006-08-24 11:41:00 +08:00
|
|
|
if (sp->xvec[idx]->props.mode != XFRM_MODE_TRANSPORT) {
|
|
|
|
if (start == -1)
|
|
|
|
start = -2-idx;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
2006-08-24 11:41:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
return start;
|
|
|
|
}
|
|
|
|
|
2007-12-13 02:44:16 +08:00
|
|
|
int __xfrm_decode_session(struct sk_buff *skb, struct flowi *fl,
|
|
|
|
unsigned int family, int reverse)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct xfrm_policy_afinfo *afinfo = xfrm_policy_get_afinfo(family);
|
2006-07-25 14:29:07 +08:00
|
|
|
int err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (unlikely(afinfo == NULL))
|
|
|
|
return -EAFNOSUPPORT;
|
|
|
|
|
2007-12-13 02:44:16 +08:00
|
|
|
afinfo->decode_session(skb, fl, reverse);
|
2006-08-05 14:12:42 +08:00
|
|
|
err = security_xfrm_decode_session(skb, &fl->secid);
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_policy_put_afinfo(afinfo);
|
2006-07-25 14:29:07 +08:00
|
|
|
return err;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2007-12-13 02:44:16 +08:00
|
|
|
EXPORT_SYMBOL(__xfrm_decode_session);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 11:41:00 +08:00
|
|
|
static inline int secpath_has_nontransport(struct sec_path *sp, int k, int *idxp)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
for (; k < sp->len; k++) {
|
2006-08-24 11:41:00 +08:00
|
|
|
if (sp->xvec[k]->props.mode != XFRM_MODE_TRANSPORT) {
|
2006-09-01 15:32:12 +08:00
|
|
|
*idxp = k;
|
2005-04-17 06:20:36 +08:00
|
|
|
return 1;
|
2006-08-24 11:41:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-02-09 22:25:29 +08:00
|
|
|
int __xfrm_policy_check(struct sock *sk, int dir, struct sk_buff *skb,
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned short family)
|
|
|
|
{
|
2008-11-26 09:35:44 +08:00
|
|
|
struct net *net = dev_net(skb->dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
struct xfrm_policy *pol;
|
2006-08-24 13:43:30 +08:00
|
|
|
struct xfrm_policy *pols[XFRM_POLICY_TYPE_MAX];
|
|
|
|
int npols = 0;
|
|
|
|
int xfrm_nr;
|
|
|
|
int pi;
|
2007-12-13 02:44:16 +08:00
|
|
|
int reverse;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct flowi fl;
|
2007-12-13 02:44:16 +08:00
|
|
|
u8 fl_dir;
|
2006-08-24 11:41:00 +08:00
|
|
|
int xerr_idx = -1;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-12-13 02:44:16 +08:00
|
|
|
reverse = dir & ~XFRM_POLICY_MASK;
|
|
|
|
dir &= XFRM_POLICY_MASK;
|
|
|
|
fl_dir = policy_to_flow_dir(dir);
|
|
|
|
|
2007-12-21 12:43:36 +08:00
|
|
|
if (__xfrm_decode_session(skb, &fl, family, reverse) < 0) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
|
|
|
|
2006-01-07 15:06:30 +08:00
|
|
|
nf_nat_decode_session(skb, &fl, family);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* First, check used SA against their selectors. */
|
|
|
|
if (skb->sp) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i=skb->sp->len-1; i>=0; i--) {
|
2006-04-01 16:54:16 +08:00
|
|
|
struct xfrm_state *x = skb->sp->xvec[i];
|
2007-12-21 12:43:36 +08:00
|
|
|
if (!xfrm_selector_match(&x->sel, &fl, family)) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINSTATEMISMATCH);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pol = NULL;
|
2006-10-06 04:42:35 +08:00
|
|
|
if (sk && sk->sk_policy[dir]) {
|
2006-07-25 14:29:07 +08:00
|
|
|
pol = xfrm_sk_policy_lookup(sk, dir, &fl);
|
2007-12-21 12:43:36 +08:00
|
|
|
if (IS_ERR(pol)) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
|
2006-10-06 04:42:35 +08:00
|
|
|
return 0;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2006-10-06 04:42:35 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!pol)
|
2008-11-26 09:35:44 +08:00
|
|
|
pol = flow_cache_lookup(net, &fl, family, fl_dir,
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_policy_lookup);
|
|
|
|
|
2007-12-21 12:43:36 +08:00
|
|
|
if (IS_ERR(pol)) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
return 0;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
|
2006-08-24 11:41:00 +08:00
|
|
|
if (!pol) {
|
2006-09-01 15:32:12 +08:00
|
|
|
if (skb->sp && secpath_has_nontransport(skb->sp, 0, &xerr_idx)) {
|
2006-08-24 11:41:00 +08:00
|
|
|
xfrm_secpath_reject(xerr_idx, skb, &fl);
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINNOPOLS);
|
2006-08-24 11:41:00 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2007-03-05 08:12:44 +08:00
|
|
|
pol->curlft.use_time = get_seconds();
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
pols[0] = pol;
|
|
|
|
npols ++;
|
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
|
if (pols[0]->type != XFRM_POLICY_TYPE_MAIN) {
|
2008-11-26 09:35:44 +08:00
|
|
|
pols[1] = xfrm_policy_lookup_bytype(net, XFRM_POLICY_TYPE_MAIN,
|
2006-08-24 13:43:30 +08:00
|
|
|
&fl, family,
|
|
|
|
XFRM_POLICY_IN);
|
|
|
|
if (pols[1]) {
|
2007-12-21 12:43:36 +08:00
|
|
|
if (IS_ERR(pols[1])) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINPOLERROR);
|
IPsec: propagate security module errors up from flow_cache_lookup
When a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return an access denied permission
(or other error). We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The way I was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
The first SYNACK would be blocked, because of an uncached lookup via
flow_cache_lookup(), which would fail to resolve an xfrm policy because
the SELinux policy is checked at that point via the resolver.
However, retransmitted SYNACKs would then find a cached flow entry when
calling into flow_cache_lookup() with a null xfrm policy, which is
interpreted by xfrm_lookup() as the packet not having any associated
policy and similarly to the first case, allowing it to pass without
transformation.
The solution presented here is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:27 +08:00
|
|
|
return 0;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2007-03-05 08:12:44 +08:00
|
|
|
pols[1]->curlft.use_time = get_seconds();
|
2006-08-24 13:43:30 +08:00
|
|
|
npols ++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (pol->action == XFRM_POLICY_ALLOW) {
|
|
|
|
struct sec_path *sp;
|
|
|
|
static struct sec_path dummy;
|
2006-08-24 13:43:30 +08:00
|
|
|
struct xfrm_tmpl *tp[XFRM_MAX_DEPTH];
|
2006-08-24 13:48:31 +08:00
|
|
|
struct xfrm_tmpl *stp[XFRM_MAX_DEPTH];
|
2006-08-24 13:43:30 +08:00
|
|
|
struct xfrm_tmpl **tpp = tp;
|
|
|
|
int ti = 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
int i, k;
|
|
|
|
|
|
|
|
if ((sp = skb->sp) == NULL)
|
|
|
|
sp = &dummy;
|
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
for (pi = 0; pi < npols; pi++) {
|
|
|
|
if (pols[pi] != pol &&
|
2007-12-21 12:43:36 +08:00
|
|
|
pols[pi]->action != XFRM_POLICY_ALLOW) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINPOLBLOCK);
|
2006-08-24 13:43:30 +08:00
|
|
|
goto reject;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
|
|
|
if (ti + pols[pi]->xfrm_nr >= XFRM_MAX_DEPTH) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINBUFFERERROR);
|
2006-08-24 13:43:30 +08:00
|
|
|
goto reject_error;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2006-08-24 13:43:30 +08:00
|
|
|
for (i = 0; i < pols[pi]->xfrm_nr; i++)
|
|
|
|
tpp[ti++] = &pols[pi]->xfrm_vec[i];
|
|
|
|
}
|
|
|
|
xfrm_nr = ti;
|
2006-08-24 13:48:31 +08:00
|
|
|
if (npols > 1) {
|
|
|
|
xfrm_tmpl_sort(stp, tpp, xfrm_nr, family);
|
|
|
|
tpp = stp;
|
|
|
|
}
|
2006-08-24 13:43:30 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* For each tunnel xfrm, find the first matching tmpl.
|
|
|
|
* For each tmpl before that, find corresponding xfrm.
|
|
|
|
* Order is _important_. Later we will implement
|
|
|
|
* some barriers, but at the moment barriers
|
|
|
|
* are implied between each two transformations.
|
|
|
|
*/
|
2006-08-24 13:43:30 +08:00
|
|
|
for (i = xfrm_nr-1, k = 0; i >= 0; i--) {
|
|
|
|
k = xfrm_policy_ok(tpp[i], sp, k, family);
|
2006-08-24 11:41:00 +08:00
|
|
|
if (k < 0) {
|
2006-09-01 15:32:12 +08:00
|
|
|
if (k < -1)
|
|
|
|
/* "-2 - errored_index" returned */
|
|
|
|
xerr_idx = -(2+k);
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINTMPLMISMATCH);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto reject;
|
2006-08-24 11:41:00 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-12-21 12:43:36 +08:00
|
|
|
if (secpath_has_nontransport(sp, k, &xerr_idx)) {
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINTMPLMISMATCH);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto reject;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 13:43:30 +08:00
|
|
|
xfrm_pols_put(pols, npols);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 1;
|
|
|
|
}
|
2007-12-21 12:43:36 +08:00
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINPOLBLOCK);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
reject:
|
2006-08-24 11:41:00 +08:00
|
|
|
xfrm_secpath_reject(xerr_idx, skb, &fl);
|
2006-08-24 13:43:30 +08:00
|
|
|
reject_error:
|
|
|
|
xfrm_pols_put(pols, npols);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__xfrm_policy_check);
|
|
|
|
|
|
|
|
int __xfrm_route_forward(struct sk_buff *skb, unsigned short family)
|
|
|
|
{
|
2008-11-26 09:36:13 +08:00
|
|
|
struct net *net = dev_net(skb->dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
struct flowi fl;
|
|
|
|
|
2007-12-21 12:43:36 +08:00
|
|
|
if (xfrm_decode_session(skb, &fl, family) < 0) {
|
|
|
|
/* XXX: we should have something like FWDHDRERROR here. */
|
|
|
|
XFRM_INC_STATS(LINUX_MIB_XFRMINHDRERROR);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
2007-12-21 12:43:36 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2008-11-26 09:36:13 +08:00
|
|
|
return xfrm_lookup(net, &skb->dst, &fl, NULL, 0) == 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__xfrm_route_forward);
|
|
|
|
|
2006-08-14 09:55:53 +08:00
|
|
|
/* Optimize later using cookies and generation ids. */
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static struct dst_entry *xfrm_dst_check(struct dst_entry *dst, u32 cookie)
|
|
|
|
{
|
2006-08-14 09:55:53 +08:00
|
|
|
/* Code (such as __xfrm4_bundle_create()) sets dst->obsolete
|
|
|
|
* to "-1" to force all XFRM destinations to get validated by
|
|
|
|
* dst_ops->check on every use. We do this because when a
|
|
|
|
* normal route referenced by an XFRM dst is obsoleted we do
|
|
|
|
* not go looking around for all parent referencing XFRM dsts
|
|
|
|
* so that we can invalidate them. It is just too much work.
|
|
|
|
* Instead we make the checks here on every use. For example:
|
|
|
|
*
|
|
|
|
* XFRM dst A --> IPv4 dst X
|
|
|
|
*
|
|
|
|
* X is the "xdst->route" of A (X is also the "dst->path" of A
|
|
|
|
* in this example). If X is marked obsolete, "A" will not
|
|
|
|
* notice. That's what we are validating here via the
|
|
|
|
* stale_bundle() check.
|
|
|
|
*
|
|
|
|
* When a policy's bundle is pruned, we dst_free() the XFRM
|
|
|
|
* dst which causes it's ->obsolete field to be set to a
|
|
|
|
* positive non-zero integer. If an XFRM dst has been pruned
|
|
|
|
* like this, we want to force a new route lookup.
|
2005-12-20 06:23:23 +08:00
|
|
|
*/
|
2006-08-14 09:55:53 +08:00
|
|
|
if (dst->obsolete < 0 && !stale_bundle(dst))
|
|
|
|
return dst;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int stale_bundle(struct dst_entry *dst)
|
|
|
|
{
|
IPsec: correct semantics for SELinux policy matching
Currently when an IPSec policy rule doesn't specify a security
context, it is assumed to be "unlabeled" by SELinux, and so
the IPSec policy rule fails to match to a flow that it would
otherwise match to, unless one has explicitly added an SELinux
policy rule allowing the flow to "polmatch" to the "unlabeled"
IPSec policy rules. In the absence of such an explicitly added
SELinux policy rule, the IPSec policy rule fails to match and
so the packet(s) flow in clear text without the otherwise applicable
xfrm(s) applied.
The above SELinux behavior violates the SELinux security notion of
"deny by default" which should actually translate to "encrypt by
default" in the above case.
This was first reported by Evgeniy Polyakov and the way James Morris
was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
With this patch applied, SELinux "polmatching" of flows Vs. IPSec
policy rules will only come into play when there's a explicit context
specified for the IPSec policy rule (which also means there's corresponding
SELinux policy allowing appropriate domains/flows to polmatch to this context).
Secondly, when a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return errors other than access denied,
such as -EINVAL. We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The solution for this is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
This patch: Fix the selinux side of things.
This makes sure SELinux polmatching of flow contexts to IPSec policy
rules comes into play only when an explicit context is associated
with the IPSec policy rule.
Also, this no longer defaults the context of a socket policy to
the context of the socket since the "no explicit context" case
is now handled properly.
Signed-off-by: Venkat Yekkirala <vyekkirala@TrustedCS.com>
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:18 +08:00
|
|
|
return !xfrm_bundle_ok(NULL, (struct xfrm_dst *)dst, NULL, AF_UNSPEC, 0);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2005-05-04 07:27:10 +08:00
|
|
|
void xfrm_dst_ifdown(struct dst_entry *dst, struct net_device *dev)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
while ((dst = dst->child) && dst->xfrm && dst->dev == dev) {
|
2008-03-25 20:47:49 +08:00
|
|
|
dst->dev = dev_net(dev)->loopback_dev;
|
2007-09-26 10:16:28 +08:00
|
|
|
dev_hold(dst->dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
dev_put(dev);
|
|
|
|
}
|
|
|
|
}
|
2005-05-04 07:27:10 +08:00
|
|
|
EXPORT_SYMBOL(xfrm_dst_ifdown);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
static void xfrm_link_failure(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
/* Impossible. Such dst must be popped before reaches point of failure. */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct dst_entry *xfrm_negative_advice(struct dst_entry *dst)
|
|
|
|
{
|
|
|
|
if (dst) {
|
|
|
|
if (dst->obsolete) {
|
|
|
|
dst_release(dst);
|
|
|
|
dst = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return dst;
|
|
|
|
}
|
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
static void prune_one_bundle(struct xfrm_policy *pol, int (*func)(struct dst_entry *), struct dst_entry **gc_list_p)
|
|
|
|
{
|
|
|
|
struct dst_entry *dst, **dstp;
|
|
|
|
|
|
|
|
write_lock(&pol->lock);
|
|
|
|
dstp = &pol->bundles;
|
|
|
|
while ((dst=*dstp) != NULL) {
|
|
|
|
if (func(dst)) {
|
|
|
|
*dstp = dst->next;
|
|
|
|
dst->next = *gc_list_p;
|
|
|
|
*gc_list_p = dst;
|
|
|
|
} else {
|
|
|
|
dstp = &dst->next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
write_unlock(&pol->lock);
|
|
|
|
}
|
|
|
|
|
2008-11-26 09:36:51 +08:00
|
|
|
static void xfrm_prune_bundles(struct net *net, int (*func)(struct dst_entry *))
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-08-24 19:45:07 +08:00
|
|
|
struct dst_entry *gc_list = NULL;
|
|
|
|
int dir;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
read_lock_bh(&xfrm_policy_lock);
|
2006-08-24 19:45:07 +08:00
|
|
|
for (dir = 0; dir < XFRM_POLICY_MAX * 2; dir++) {
|
|
|
|
struct xfrm_policy *pol;
|
|
|
|
struct hlist_node *entry;
|
|
|
|
struct hlist_head *table;
|
|
|
|
int i;
|
2006-08-24 13:43:30 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_for_each_entry(pol, entry,
|
2008-11-26 09:36:51 +08:00
|
|
|
&net->xfrm.policy_inexact[dir], bydst)
|
2006-08-24 19:45:07 +08:00
|
|
|
prune_one_bundle(pol, func, &gc_list);
|
|
|
|
|
2008-11-26 09:36:51 +08:00
|
|
|
table = net->xfrm.policy_bydst[dir].table;
|
|
|
|
for (i = net->xfrm.policy_bydst[dir].hmask; i >= 0; i--) {
|
2006-08-24 19:45:07 +08:00
|
|
|
hlist_for_each_entry(pol, entry, table + i, bydst)
|
|
|
|
prune_one_bundle(pol, func, &gc_list);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
read_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
|
|
|
while (gc_list) {
|
2006-08-24 19:45:07 +08:00
|
|
|
struct dst_entry *dst = gc_list;
|
2005-04-17 06:20:36 +08:00
|
|
|
gc_list = dst->next;
|
|
|
|
dst_free(dst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int unused_bundle(struct dst_entry *dst)
|
|
|
|
{
|
|
|
|
return !atomic_read(&dst->__refcnt);
|
|
|
|
}
|
|
|
|
|
2008-11-26 09:37:23 +08:00
|
|
|
static void __xfrm_garbage_collect(struct net *net)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-11-26 09:37:23 +08:00
|
|
|
xfrm_prune_bundles(net, unused_bundle);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:36:51 +08:00
|
|
|
static int xfrm_flush_bundles(struct net *net)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2008-11-26 09:36:51 +08:00
|
|
|
xfrm_prune_bundles(net, stale_bundle);
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-12-12 01:32:34 +08:00
|
|
|
static void xfrm_init_pmtu(struct dst_entry *dst)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
do {
|
|
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
|
|
u32 pmtu, route_mtu_cached;
|
|
|
|
|
|
|
|
pmtu = dst_mtu(dst->child);
|
|
|
|
xdst->child_mtu_cached = pmtu;
|
|
|
|
|
|
|
|
pmtu = xfrm_state_mtu(dst->xfrm, pmtu);
|
|
|
|
|
|
|
|
route_mtu_cached = dst_mtu(xdst->route);
|
|
|
|
xdst->route_mtu_cached = route_mtu_cached;
|
|
|
|
|
|
|
|
if (pmtu > route_mtu_cached)
|
|
|
|
pmtu = route_mtu_cached;
|
|
|
|
|
|
|
|
dst->metrics[RTAX_MTU-1] = pmtu;
|
|
|
|
} while ((dst = dst->next));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check that the bundle accepts the flow and its components are
|
|
|
|
* still valid.
|
|
|
|
*/
|
|
|
|
|
IPsec: correct semantics for SELinux policy matching
Currently when an IPSec policy rule doesn't specify a security
context, it is assumed to be "unlabeled" by SELinux, and so
the IPSec policy rule fails to match to a flow that it would
otherwise match to, unless one has explicitly added an SELinux
policy rule allowing the flow to "polmatch" to the "unlabeled"
IPSec policy rules. In the absence of such an explicitly added
SELinux policy rule, the IPSec policy rule fails to match and
so the packet(s) flow in clear text without the otherwise applicable
xfrm(s) applied.
The above SELinux behavior violates the SELinux security notion of
"deny by default" which should actually translate to "encrypt by
default" in the above case.
This was first reported by Evgeniy Polyakov and the way James Morris
was seeing the problem was when connecting via IPsec to a
confined service on an SELinux box (vsftpd), which did not have the
appropriate SELinux policy permissions to send packets via IPsec.
With this patch applied, SELinux "polmatching" of flows Vs. IPSec
policy rules will only come into play when there's a explicit context
specified for the IPSec policy rule (which also means there's corresponding
SELinux policy allowing appropriate domains/flows to polmatch to this context).
Secondly, when a security module is loaded (in this case, SELinux), the
security_xfrm_policy_lookup() hook can return errors other than access denied,
such as -EINVAL. We were not handling that correctly, and in fact
inverting the return logic and propagating a false "ok" back up to
xfrm_lookup(), which then allowed packets to pass as if they were not
associated with an xfrm policy.
The solution for this is to first ensure that errno values are
correctly propagated all the way back up through the various call chains
from security_xfrm_policy_lookup(), and handled correctly.
Then, flow_cache_lookup() is modified, so that if the policy resolver
fails (typically a permission denied via the security module), the flow
cache entry is killed rather than having a null policy assigned (which
indicates that the packet can pass freely). This also forces any future
lookups for the same flow to consult the security module (e.g. SELinux)
for current security policy (rather than, say, caching the error on the
flow cache entry).
This patch: Fix the selinux side of things.
This makes sure SELinux polmatching of flow contexts to IPSec policy
rules comes into play only when an explicit context is associated
with the IPSec policy rule.
Also, this no longer defaults the context of a socket policy to
the context of the socket since the "no explicit context" case
is now handled properly.
Signed-off-by: Venkat Yekkirala <vyekkirala@TrustedCS.com>
Signed-off-by: James Morris <jmorris@namei.org>
2006-10-06 04:42:18 +08:00
|
|
|
int xfrm_bundle_ok(struct xfrm_policy *pol, struct xfrm_dst *first,
|
|
|
|
struct flowi *fl, int family, int strict)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct dst_entry *dst = &first->u.dst;
|
|
|
|
struct xfrm_dst *last;
|
|
|
|
u32 mtu;
|
|
|
|
|
2005-05-27 03:58:04 +08:00
|
|
|
if (!dst_check(dst->path, ((struct xfrm_dst *)dst)->path_cookie) ||
|
2005-04-17 06:20:36 +08:00
|
|
|
(dst->dev && !netif_running(dst->dev)))
|
|
|
|
return 0;
|
2007-04-30 15:33:35 +08:00
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
|
if (fl) {
|
|
|
|
if (first->origin && !flow_cache_uli_match(first->origin, fl))
|
|
|
|
return 0;
|
|
|
|
if (first->partner &&
|
|
|
|
!xfrm_selector_match(first->partner, fl, family))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
last = NULL;
|
|
|
|
|
|
|
|
do {
|
|
|
|
struct xfrm_dst *xdst = (struct xfrm_dst *)dst;
|
|
|
|
|
|
|
|
if (fl && !xfrm_selector_match(&dst->xfrm->sel, fl, family))
|
|
|
|
return 0;
|
2006-11-09 07:04:26 +08:00
|
|
|
if (fl && pol &&
|
|
|
|
!security_xfrm_state_pol_flow_match(dst->xfrm, pol, fl))
|
2006-07-25 14:29:07 +08:00
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (dst->xfrm->km.state != XFRM_STATE_VALID)
|
|
|
|
return 0;
|
2006-08-24 18:18:09 +08:00
|
|
|
if (xdst->genid != dst->xfrm->genid)
|
|
|
|
return 0;
|
2006-08-24 10:12:01 +08:00
|
|
|
|
2007-10-18 12:31:50 +08:00
|
|
|
if (strict && fl &&
|
2007-10-18 12:35:51 +08:00
|
|
|
!(dst->xfrm->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL) &&
|
2006-08-24 10:12:01 +08:00
|
|
|
!xfrm_state_addr_flow_check(dst->xfrm, fl, family))
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
mtu = dst_mtu(dst->child);
|
|
|
|
if (xdst->child_mtu_cached != mtu) {
|
|
|
|
last = xdst;
|
|
|
|
xdst->child_mtu_cached = mtu;
|
|
|
|
}
|
|
|
|
|
2005-05-27 03:58:04 +08:00
|
|
|
if (!dst_check(xdst->route, xdst->route_cookie))
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
mtu = dst_mtu(xdst->route);
|
|
|
|
if (xdst->route_mtu_cached != mtu) {
|
|
|
|
last = xdst;
|
|
|
|
xdst->route_mtu_cached = mtu;
|
|
|
|
}
|
|
|
|
|
|
|
|
dst = dst->child;
|
|
|
|
} while (dst->xfrm);
|
|
|
|
|
|
|
|
if (likely(!last))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
mtu = last->child_mtu_cached;
|
|
|
|
for (;;) {
|
|
|
|
dst = &last->u.dst;
|
|
|
|
|
|
|
|
mtu = xfrm_state_mtu(dst->xfrm, mtu);
|
|
|
|
if (mtu > last->route_mtu_cached)
|
|
|
|
mtu = last->route_mtu_cached;
|
|
|
|
dst->metrics[RTAX_MTU-1] = mtu;
|
|
|
|
|
|
|
|
if (last == first)
|
|
|
|
break;
|
|
|
|
|
2007-07-18 16:55:52 +08:00
|
|
|
last = (struct xfrm_dst *)last->u.dst.next;
|
2005-04-17 06:20:36 +08:00
|
|
|
last->child_mtu_cached = mtu;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPORT_SYMBOL(xfrm_bundle_ok);
|
|
|
|
|
|
|
|
int xfrm_policy_register_afinfo(struct xfrm_policy_afinfo *afinfo)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
if (unlikely(afinfo == NULL))
|
|
|
|
return -EINVAL;
|
|
|
|
if (unlikely(afinfo->family >= NPROTO))
|
|
|
|
return -EAFNOSUPPORT;
|
2006-04-29 06:32:29 +08:00
|
|
|
write_lock_bh(&xfrm_policy_afinfo_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (unlikely(xfrm_policy_afinfo[afinfo->family] != NULL))
|
|
|
|
err = -ENOBUFS;
|
|
|
|
else {
|
|
|
|
struct dst_ops *dst_ops = afinfo->dst_ops;
|
|
|
|
if (likely(dst_ops->kmem_cachep == NULL))
|
|
|
|
dst_ops->kmem_cachep = xfrm_dst_cache;
|
|
|
|
if (likely(dst_ops->check == NULL))
|
|
|
|
dst_ops->check = xfrm_dst_check;
|
|
|
|
if (likely(dst_ops->negative_advice == NULL))
|
|
|
|
dst_ops->negative_advice = xfrm_negative_advice;
|
|
|
|
if (likely(dst_ops->link_failure == NULL))
|
|
|
|
dst_ops->link_failure = xfrm_link_failure;
|
|
|
|
if (likely(afinfo->garbage_collect == NULL))
|
|
|
|
afinfo->garbage_collect = __xfrm_garbage_collect;
|
|
|
|
xfrm_policy_afinfo[afinfo->family] = afinfo;
|
|
|
|
}
|
2006-04-29 06:32:29 +08:00
|
|
|
write_unlock_bh(&xfrm_policy_afinfo_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_register_afinfo);
|
|
|
|
|
|
|
|
int xfrm_policy_unregister_afinfo(struct xfrm_policy_afinfo *afinfo)
|
|
|
|
{
|
|
|
|
int err = 0;
|
|
|
|
if (unlikely(afinfo == NULL))
|
|
|
|
return -EINVAL;
|
|
|
|
if (unlikely(afinfo->family >= NPROTO))
|
|
|
|
return -EAFNOSUPPORT;
|
2006-04-29 06:32:29 +08:00
|
|
|
write_lock_bh(&xfrm_policy_afinfo_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (likely(xfrm_policy_afinfo[afinfo->family] != NULL)) {
|
|
|
|
if (unlikely(xfrm_policy_afinfo[afinfo->family] != afinfo))
|
|
|
|
err = -EINVAL;
|
|
|
|
else {
|
|
|
|
struct dst_ops *dst_ops = afinfo->dst_ops;
|
|
|
|
xfrm_policy_afinfo[afinfo->family] = NULL;
|
|
|
|
dst_ops->kmem_cachep = NULL;
|
|
|
|
dst_ops->check = NULL;
|
|
|
|
dst_ops->negative_advice = NULL;
|
|
|
|
dst_ops->link_failure = NULL;
|
|
|
|
afinfo->garbage_collect = NULL;
|
|
|
|
}
|
|
|
|
}
|
2006-04-29 06:32:29 +08:00
|
|
|
write_unlock_bh(&xfrm_policy_afinfo_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(xfrm_policy_unregister_afinfo);
|
|
|
|
|
|
|
|
static struct xfrm_policy_afinfo *xfrm_policy_get_afinfo(unsigned short family)
|
|
|
|
{
|
|
|
|
struct xfrm_policy_afinfo *afinfo;
|
|
|
|
if (unlikely(family >= NPROTO))
|
|
|
|
return NULL;
|
|
|
|
read_lock(&xfrm_policy_afinfo_lock);
|
|
|
|
afinfo = xfrm_policy_afinfo[family];
|
2006-05-28 14:03:58 +08:00
|
|
|
if (unlikely(!afinfo))
|
|
|
|
read_unlock(&xfrm_policy_afinfo_lock);
|
2005-04-17 06:20:36 +08:00
|
|
|
return afinfo;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void xfrm_policy_put_afinfo(struct xfrm_policy_afinfo *afinfo)
|
|
|
|
{
|
2006-05-28 14:03:58 +08:00
|
|
|
read_unlock(&xfrm_policy_afinfo_lock);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static int xfrm_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
|
|
|
|
{
|
2007-09-12 19:02:17 +08:00
|
|
|
struct net_device *dev = ptr;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
switch (event) {
|
|
|
|
case NETDEV_DOWN:
|
2008-11-26 09:36:51 +08:00
|
|
|
xfrm_flush_bundles(dev_net(dev));
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
return NOTIFY_DONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct notifier_block xfrm_dev_notifier = {
|
2008-10-31 15:41:59 +08:00
|
|
|
.notifier_call = xfrm_dev_event,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2007-12-21 12:42:57 +08:00
|
|
|
#ifdef CONFIG_XFRM_STATISTICS
|
|
|
|
static int __init xfrm_statistics_init(void)
|
|
|
|
{
|
|
|
|
if (snmp_mib_init((void **)xfrm_statistics,
|
|
|
|
sizeof(struct linux_xfrm_mib)) < 0)
|
|
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2008-11-26 09:14:31 +08:00
|
|
|
static int __net_init xfrm_policy_init(struct net *net)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-08-24 19:45:07 +08:00
|
|
|
unsigned int hmask, sz;
|
|
|
|
int dir;
|
|
|
|
|
2008-11-26 09:14:31 +08:00
|
|
|
if (net_eq(net, &init_net))
|
|
|
|
xfrm_dst_cache = kmem_cache_create("xfrm_dst_cache",
|
2005-04-17 06:20:36 +08:00
|
|
|
sizeof(struct xfrm_dst),
|
2006-08-27 10:25:52 +08:00
|
|
|
0, SLAB_HWCACHE_ALIGN|SLAB_PANIC,
|
2007-07-20 09:11:58 +08:00
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-08-24 19:45:07 +08:00
|
|
|
hmask = 8 - 1;
|
|
|
|
sz = (hmask+1) * sizeof(struct hlist_head);
|
|
|
|
|
2008-11-26 09:22:35 +08:00
|
|
|
net->xfrm.policy_byidx = xfrm_hash_alloc(sz);
|
|
|
|
if (!net->xfrm.policy_byidx)
|
|
|
|
goto out_byidx;
|
2008-11-26 09:22:58 +08:00
|
|
|
net->xfrm.policy_idx_hmask = hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
|
|
|
|
for (dir = 0; dir < XFRM_POLICY_MAX * 2; dir++) {
|
|
|
|
struct xfrm_policy_hash *htab;
|
|
|
|
|
2008-11-26 09:24:15 +08:00
|
|
|
net->xfrm.policy_count[dir] = 0;
|
2008-11-26 09:23:26 +08:00
|
|
|
INIT_HLIST_HEAD(&net->xfrm.policy_inexact[dir]);
|
2006-08-24 19:45:07 +08:00
|
|
|
|
2008-11-26 09:23:48 +08:00
|
|
|
htab = &net->xfrm.policy_bydst[dir];
|
2006-08-24 19:50:50 +08:00
|
|
|
htab->table = xfrm_hash_alloc(sz);
|
2006-08-24 19:45:07 +08:00
|
|
|
if (!htab->table)
|
2008-11-26 09:23:48 +08:00
|
|
|
goto out_bydst;
|
|
|
|
htab->hmask = hmask;
|
2006-08-24 19:45:07 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:22:11 +08:00
|
|
|
INIT_LIST_HEAD(&net->xfrm.policy_all);
|
2008-11-26 09:28:57 +08:00
|
|
|
INIT_WORK(&net->xfrm.policy_hash_work, xfrm_hash_resize);
|
2008-11-26 09:14:31 +08:00
|
|
|
if (net_eq(net, &init_net))
|
|
|
|
register_netdevice_notifier(&xfrm_dev_notifier);
|
|
|
|
return 0;
|
2008-11-26 09:22:35 +08:00
|
|
|
|
2008-11-26 09:23:48 +08:00
|
|
|
out_bydst:
|
|
|
|
for (dir--; dir >= 0; dir--) {
|
|
|
|
struct xfrm_policy_hash *htab;
|
|
|
|
|
|
|
|
htab = &net->xfrm.policy_bydst[dir];
|
|
|
|
xfrm_hash_free(htab->table, sz);
|
|
|
|
}
|
|
|
|
xfrm_hash_free(net->xfrm.policy_byidx, sz);
|
2008-11-26 09:22:35 +08:00
|
|
|
out_byidx:
|
|
|
|
return -ENOMEM;
|
2008-11-26 09:14:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void xfrm_policy_fini(struct net *net)
|
|
|
|
{
|
2008-11-26 09:57:44 +08:00
|
|
|
struct xfrm_audit audit_info;
|
2008-11-26 09:22:35 +08:00
|
|
|
unsigned int sz;
|
2008-11-26 09:23:26 +08:00
|
|
|
int dir;
|
2008-11-26 09:22:35 +08:00
|
|
|
|
2008-11-26 09:57:44 +08:00
|
|
|
flush_work(&net->xfrm.policy_hash_work);
|
|
|
|
#ifdef CONFIG_XFRM_SUB_POLICY
|
|
|
|
audit_info.loginuid = -1;
|
|
|
|
audit_info.sessionid = -1;
|
|
|
|
audit_info.secid = 0;
|
|
|
|
xfrm_policy_flush(net, XFRM_POLICY_TYPE_SUB, &audit_info);
|
|
|
|
#endif
|
|
|
|
audit_info.loginuid = -1;
|
|
|
|
audit_info.sessionid = -1;
|
|
|
|
audit_info.secid = 0;
|
|
|
|
xfrm_policy_flush(net, XFRM_POLICY_TYPE_MAIN, &audit_info);
|
|
|
|
flush_work(&xfrm_policy_gc_work);
|
|
|
|
|
2008-11-26 09:22:11 +08:00
|
|
|
WARN_ON(!list_empty(&net->xfrm.policy_all));
|
2008-11-26 09:22:35 +08:00
|
|
|
|
2008-11-26 09:23:26 +08:00
|
|
|
for (dir = 0; dir < XFRM_POLICY_MAX * 2; dir++) {
|
2008-11-26 09:23:48 +08:00
|
|
|
struct xfrm_policy_hash *htab;
|
|
|
|
|
2008-11-26 09:23:26 +08:00
|
|
|
WARN_ON(!hlist_empty(&net->xfrm.policy_inexact[dir]));
|
2008-11-26 09:23:48 +08:00
|
|
|
|
|
|
|
htab = &net->xfrm.policy_bydst[dir];
|
|
|
|
sz = (htab->hmask + 1);
|
|
|
|
WARN_ON(!hlist_empty(htab->table));
|
|
|
|
xfrm_hash_free(htab->table, sz);
|
2008-11-26 09:23:26 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:22:58 +08:00
|
|
|
sz = (net->xfrm.policy_idx_hmask + 1) * sizeof(struct hlist_head);
|
2008-11-26 09:22:35 +08:00
|
|
|
WARN_ON(!hlist_empty(net->xfrm.policy_byidx));
|
|
|
|
xfrm_hash_free(net->xfrm.policy_byidx, sz);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2008-11-26 09:14:31 +08:00
|
|
|
static int __net_init xfrm_net_init(struct net *net)
|
|
|
|
{
|
|
|
|
int rv;
|
|
|
|
|
|
|
|
rv = xfrm_state_init(net);
|
|
|
|
if (rv < 0)
|
|
|
|
goto out_state;
|
|
|
|
rv = xfrm_policy_init(net);
|
|
|
|
if (rv < 0)
|
|
|
|
goto out_policy;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_policy:
|
|
|
|
xfrm_state_fini(net);
|
|
|
|
out_state:
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __net_exit xfrm_net_exit(struct net *net)
|
|
|
|
{
|
|
|
|
xfrm_policy_fini(net);
|
|
|
|
xfrm_state_fini(net);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct pernet_operations __net_initdata xfrm_net_ops = {
|
|
|
|
.init = xfrm_net_init,
|
|
|
|
.exit = xfrm_net_exit,
|
|
|
|
};
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
void __init xfrm_init(void)
|
|
|
|
{
|
2008-11-26 09:14:31 +08:00
|
|
|
register_pernet_subsys(&xfrm_net_ops);
|
2007-12-21 12:42:57 +08:00
|
|
|
#ifdef CONFIG_XFRM_STATISTICS
|
|
|
|
xfrm_statistics_init();
|
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
xfrm_input_init();
|
2007-12-21 12:42:57 +08:00
|
|
|
#ifdef CONFIG_XFRM_STATISTICS
|
|
|
|
xfrm_proc_init();
|
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2007-09-18 02:51:22 +08:00
|
|
|
#ifdef CONFIG_AUDITSYSCALL
|
2008-01-12 19:20:03 +08:00
|
|
|
static void xfrm_audit_common_policyinfo(struct xfrm_policy *xp,
|
|
|
|
struct audit_buffer *audit_buf)
|
2007-09-18 02:51:22 +08:00
|
|
|
{
|
2007-12-01 20:27:18 +08:00
|
|
|
struct xfrm_sec_ctx *ctx = xp->security;
|
|
|
|
struct xfrm_selector *sel = &xp->selector;
|
|
|
|
|
|
|
|
if (ctx)
|
2007-09-18 02:51:22 +08:00
|
|
|
audit_log_format(audit_buf, " sec_alg=%u sec_doi=%u sec_obj=%s",
|
2007-12-01 20:27:18 +08:00
|
|
|
ctx->ctx_alg, ctx->ctx_doi, ctx->ctx_str);
|
2007-09-18 02:51:22 +08:00
|
|
|
|
2007-12-01 20:27:18 +08:00
|
|
|
switch(sel->family) {
|
2007-09-18 02:51:22 +08:00
|
|
|
case AF_INET:
|
2008-10-31 15:54:56 +08:00
|
|
|
audit_log_format(audit_buf, " src=%pI4", &sel->saddr.a4);
|
2007-12-01 20:27:18 +08:00
|
|
|
if (sel->prefixlen_s != 32)
|
|
|
|
audit_log_format(audit_buf, " src_prefixlen=%d",
|
|
|
|
sel->prefixlen_s);
|
2008-10-31 15:54:56 +08:00
|
|
|
audit_log_format(audit_buf, " dst=%pI4", &sel->daddr.a4);
|
2007-12-01 20:27:18 +08:00
|
|
|
if (sel->prefixlen_d != 32)
|
|
|
|
audit_log_format(audit_buf, " dst_prefixlen=%d",
|
|
|
|
sel->prefixlen_d);
|
2007-09-18 02:51:22 +08:00
|
|
|
break;
|
|
|
|
case AF_INET6:
|
2008-10-30 03:52:50 +08:00
|
|
|
audit_log_format(audit_buf, " src=%pI6", sel->saddr.a6);
|
2007-12-01 20:27:18 +08:00
|
|
|
if (sel->prefixlen_s != 128)
|
|
|
|
audit_log_format(audit_buf, " src_prefixlen=%d",
|
|
|
|
sel->prefixlen_s);
|
2008-10-30 03:52:50 +08:00
|
|
|
audit_log_format(audit_buf, " dst=%pI6", sel->daddr.a6);
|
2007-12-01 20:27:18 +08:00
|
|
|
if (sel->prefixlen_d != 128)
|
|
|
|
audit_log_format(audit_buf, " dst_prefixlen=%d",
|
|
|
|
sel->prefixlen_d);
|
2007-09-18 02:51:22 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-12-21 12:49:33 +08:00
|
|
|
void xfrm_audit_policy_add(struct xfrm_policy *xp, int result,
|
2008-04-18 22:09:25 +08:00
|
|
|
uid_t auid, u32 sessionid, u32 secid)
|
2007-09-18 02:51:22 +08:00
|
|
|
{
|
|
|
|
struct audit_buffer *audit_buf;
|
|
|
|
|
2007-12-22 06:58:11 +08:00
|
|
|
audit_buf = xfrm_audit_start("SPD-add");
|
2007-09-18 02:51:22 +08:00
|
|
|
if (audit_buf == NULL)
|
|
|
|
return;
|
2008-04-18 22:09:25 +08:00
|
|
|
xfrm_audit_helper_usrinfo(auid, sessionid, secid, audit_buf);
|
2007-12-22 06:58:11 +08:00
|
|
|
audit_log_format(audit_buf, " res=%u", result);
|
2007-09-18 02:51:22 +08:00
|
|
|
xfrm_audit_common_policyinfo(xp, audit_buf);
|
|
|
|
audit_log_end(audit_buf);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(xfrm_audit_policy_add);
|
|
|
|
|
2007-12-21 12:49:33 +08:00
|
|
|
void xfrm_audit_policy_delete(struct xfrm_policy *xp, int result,
|
2008-04-18 22:09:25 +08:00
|
|
|
uid_t auid, u32 sessionid, u32 secid)
|
2007-09-18 02:51:22 +08:00
|
|
|
{
|
|
|
|
struct audit_buffer *audit_buf;
|
|
|
|
|
2007-12-22 06:58:11 +08:00
|
|
|
audit_buf = xfrm_audit_start("SPD-delete");
|
2007-09-18 02:51:22 +08:00
|
|
|
if (audit_buf == NULL)
|
|
|
|
return;
|
2008-04-18 22:09:25 +08:00
|
|
|
xfrm_audit_helper_usrinfo(auid, sessionid, secid, audit_buf);
|
2007-12-22 06:58:11 +08:00
|
|
|
audit_log_format(audit_buf, " res=%u", result);
|
2007-09-18 02:51:22 +08:00
|
|
|
xfrm_audit_common_policyinfo(xp, audit_buf);
|
|
|
|
audit_log_end(audit_buf);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(xfrm_audit_policy_delete);
|
|
|
|
#endif
|
|
|
|
|
2007-02-09 05:11:42 +08:00
|
|
|
#ifdef CONFIG_XFRM_MIGRATE
|
|
|
|
static int xfrm_migrate_selector_match(struct xfrm_selector *sel_cmp,
|
|
|
|
struct xfrm_selector *sel_tgt)
|
|
|
|
{
|
|
|
|
if (sel_cmp->proto == IPSEC_ULPROTO_ANY) {
|
|
|
|
if (sel_tgt->family == sel_cmp->family &&
|
|
|
|
xfrm_addr_cmp(&sel_tgt->daddr, &sel_cmp->daddr,
|
2007-02-09 22:25:29 +08:00
|
|
|
sel_cmp->family) == 0 &&
|
2007-02-09 05:11:42 +08:00
|
|
|
xfrm_addr_cmp(&sel_tgt->saddr, &sel_cmp->saddr,
|
|
|
|
sel_cmp->family) == 0 &&
|
|
|
|
sel_tgt->prefixlen_d == sel_cmp->prefixlen_d &&
|
|
|
|
sel_tgt->prefixlen_s == sel_cmp->prefixlen_s) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (memcmp(sel_tgt, sel_cmp, sizeof(*sel_tgt)) == 0) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct xfrm_policy * xfrm_migrate_policy_find(struct xfrm_selector *sel,
|
|
|
|
u8 dir, u8 type)
|
|
|
|
{
|
|
|
|
struct xfrm_policy *pol, *ret = NULL;
|
|
|
|
struct hlist_node *entry;
|
|
|
|
struct hlist_head *chain;
|
|
|
|
u32 priority = ~0U;
|
|
|
|
|
|
|
|
read_lock_bh(&xfrm_policy_lock);
|
2008-11-26 09:33:06 +08:00
|
|
|
chain = policy_hash_direct(&init_net, &sel->daddr, &sel->saddr, sel->family, dir);
|
2007-02-09 05:11:42 +08:00
|
|
|
hlist_for_each_entry(pol, entry, chain, bydst) {
|
|
|
|
if (xfrm_migrate_selector_match(sel, &pol->selector) &&
|
|
|
|
pol->type == type) {
|
|
|
|
ret = pol;
|
|
|
|
priority = ret->priority;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2008-11-26 09:23:26 +08:00
|
|
|
chain = &init_net.xfrm.policy_inexact[dir];
|
2007-02-09 05:11:42 +08:00
|
|
|
hlist_for_each_entry(pol, entry, chain, bydst) {
|
|
|
|
if (xfrm_migrate_selector_match(sel, &pol->selector) &&
|
|
|
|
pol->type == type &&
|
|
|
|
pol->priority < priority) {
|
|
|
|
ret = pol;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ret)
|
|
|
|
xfrm_pol_hold(ret);
|
|
|
|
|
|
|
|
read_unlock_bh(&xfrm_policy_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int migrate_tmpl_match(struct xfrm_migrate *m, struct xfrm_tmpl *t)
|
|
|
|
{
|
|
|
|
int match = 0;
|
|
|
|
|
|
|
|
if (t->mode == m->mode && t->id.proto == m->proto &&
|
|
|
|
(m->reqid == 0 || t->reqid == m->reqid)) {
|
|
|
|
switch (t->mode) {
|
|
|
|
case XFRM_MODE_TUNNEL:
|
|
|
|
case XFRM_MODE_BEET:
|
|
|
|
if (xfrm_addr_cmp(&t->id.daddr, &m->old_daddr,
|
|
|
|
m->old_family) == 0 &&
|
|
|
|
xfrm_addr_cmp(&t->saddr, &m->old_saddr,
|
|
|
|
m->old_family) == 0) {
|
|
|
|
match = 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case XFRM_MODE_TRANSPORT:
|
|
|
|
/* in case of transport mode, template does not store
|
|
|
|
any IP addresses, hence we just compare mode and
|
|
|
|
protocol */
|
|
|
|
match = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return match;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update endpoint address(es) of template(s) */
|
|
|
|
static int xfrm_policy_migrate(struct xfrm_policy *pol,
|
|
|
|
struct xfrm_migrate *m, int num_migrate)
|
|
|
|
{
|
|
|
|
struct xfrm_migrate *mp;
|
|
|
|
struct dst_entry *dst;
|
|
|
|
int i, j, n = 0;
|
|
|
|
|
|
|
|
write_lock_bh(&pol->lock);
|
2008-10-01 22:03:24 +08:00
|
|
|
if (unlikely(pol->walk.dead)) {
|
2007-02-09 05:11:42 +08:00
|
|
|
/* target policy has been deleted */
|
|
|
|
write_unlock_bh(&pol->lock);
|
|
|
|
return -ENOENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < pol->xfrm_nr; i++) {
|
|
|
|
for (j = 0, mp = m; j < num_migrate; j++, mp++) {
|
|
|
|
if (!migrate_tmpl_match(mp, &pol->xfrm_vec[i]))
|
|
|
|
continue;
|
|
|
|
n++;
|
2007-10-18 12:31:50 +08:00
|
|
|
if (pol->xfrm_vec[i].mode != XFRM_MODE_TUNNEL &&
|
|
|
|
pol->xfrm_vec[i].mode != XFRM_MODE_BEET)
|
2007-02-09 05:11:42 +08:00
|
|
|
continue;
|
|
|
|
/* update endpoints */
|
|
|
|
memcpy(&pol->xfrm_vec[i].id.daddr, &mp->new_daddr,
|
|
|
|
sizeof(pol->xfrm_vec[i].id.daddr));
|
|
|
|
memcpy(&pol->xfrm_vec[i].saddr, &mp->new_saddr,
|
|
|
|
sizeof(pol->xfrm_vec[i].saddr));
|
|
|
|
pol->xfrm_vec[i].encap_family = mp->new_family;
|
|
|
|
/* flush bundles */
|
|
|
|
while ((dst = pol->bundles) != NULL) {
|
|
|
|
pol->bundles = dst->next;
|
|
|
|
dst_free(dst);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
write_unlock_bh(&pol->lock);
|
|
|
|
|
|
|
|
if (!n)
|
|
|
|
return -ENODATA;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int xfrm_migrate_check(struct xfrm_migrate *m, int num_migrate)
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
if (num_migrate < 1 || num_migrate > XFRM_MAX_DEPTH)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
for (i = 0; i < num_migrate; i++) {
|
|
|
|
if ((xfrm_addr_cmp(&m[i].old_daddr, &m[i].new_daddr,
|
|
|
|
m[i].old_family) == 0) &&
|
|
|
|
(xfrm_addr_cmp(&m[i].old_saddr, &m[i].new_saddr,
|
|
|
|
m[i].old_family) == 0))
|
|
|
|
return -EINVAL;
|
|
|
|
if (xfrm_addr_any(&m[i].new_daddr, m[i].new_family) ||
|
|
|
|
xfrm_addr_any(&m[i].new_saddr, m[i].new_family))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* check if there is any duplicated entry */
|
|
|
|
for (j = i + 1; j < num_migrate; j++) {
|
|
|
|
if (!memcmp(&m[i].old_daddr, &m[j].old_daddr,
|
|
|
|
sizeof(m[i].old_daddr)) &&
|
|
|
|
!memcmp(&m[i].old_saddr, &m[j].old_saddr,
|
|
|
|
sizeof(m[i].old_saddr)) &&
|
|
|
|
m[i].proto == m[j].proto &&
|
|
|
|
m[i].mode == m[j].mode &&
|
|
|
|
m[i].reqid == m[j].reqid &&
|
|
|
|
m[i].old_family == m[j].old_family)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int xfrm_migrate(struct xfrm_selector *sel, u8 dir, u8 type,
|
2008-10-06 04:33:42 +08:00
|
|
|
struct xfrm_migrate *m, int num_migrate,
|
|
|
|
struct xfrm_kmaddress *k)
|
2007-02-09 05:11:42 +08:00
|
|
|
{
|
|
|
|
int i, err, nx_cur = 0, nx_new = 0;
|
|
|
|
struct xfrm_policy *pol = NULL;
|
|
|
|
struct xfrm_state *x, *xc;
|
|
|
|
struct xfrm_state *x_cur[XFRM_MAX_DEPTH];
|
|
|
|
struct xfrm_state *x_new[XFRM_MAX_DEPTH];
|
|
|
|
struct xfrm_migrate *mp;
|
|
|
|
|
|
|
|
if ((err = xfrm_migrate_check(m, num_migrate)) < 0)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* Stage 1 - find policy */
|
|
|
|
if ((pol = xfrm_migrate_policy_find(sel, dir, type)) == NULL) {
|
|
|
|
err = -ENOENT;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stage 2 - find and update state(s) */
|
|
|
|
for (i = 0, mp = m; i < num_migrate; i++, mp++) {
|
|
|
|
if ((x = xfrm_migrate_state_find(mp))) {
|
|
|
|
x_cur[nx_cur] = x;
|
|
|
|
nx_cur++;
|
|
|
|
if ((xc = xfrm_state_migrate(x, mp))) {
|
|
|
|
x_new[nx_new] = xc;
|
|
|
|
nx_new++;
|
|
|
|
} else {
|
|
|
|
err = -ENODATA;
|
|
|
|
goto restore_state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stage 3 - update policy */
|
|
|
|
if ((err = xfrm_policy_migrate(pol, m, num_migrate)) < 0)
|
|
|
|
goto restore_state;
|
|
|
|
|
|
|
|
/* Stage 4 - delete old state(s) */
|
|
|
|
if (nx_cur) {
|
|
|
|
xfrm_states_put(x_cur, nx_cur);
|
|
|
|
xfrm_states_delete(x_cur, nx_cur);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stage 5 - announce */
|
2008-10-06 04:33:42 +08:00
|
|
|
km_migrate(sel, dir, type, m, num_migrate, k);
|
2007-02-09 05:11:42 +08:00
|
|
|
|
|
|
|
xfrm_pol_put(pol);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
out:
|
|
|
|
return err;
|
|
|
|
|
|
|
|
restore_state:
|
|
|
|
if (pol)
|
|
|
|
xfrm_pol_put(pol);
|
|
|
|
if (nx_cur)
|
|
|
|
xfrm_states_put(x_cur, nx_cur);
|
|
|
|
if (nx_new)
|
|
|
|
xfrm_states_delete(x_new, nx_new);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
2007-02-09 05:29:15 +08:00
|
|
|
EXPORT_SYMBOL(xfrm_migrate);
|
2007-02-09 05:11:42 +08:00
|
|
|
#endif
|