calipso: fix resource leak on calipso_genopt failure
[cascardo/linux.git] / net / ipv6 / calipso.c
index ea80450..37ac9de 100644 (file)
 static DEFINE_SPINLOCK(calipso_doi_list_lock);
 static LIST_HEAD(calipso_doi_list);
 
+/* Label mapping cache */
+int calipso_cache_enabled = 1;
+int calipso_cache_bucketsize = 10;
+#define CALIPSO_CACHE_BUCKETBITS     7
+#define CALIPSO_CACHE_BUCKETS        BIT(CALIPSO_CACHE_BUCKETBITS)
+#define CALIPSO_CACHE_REORDERLIMIT   10
+struct calipso_map_cache_bkt {
+       spinlock_t lock;
+       u32 size;
+       struct list_head list;
+};
+
+struct calipso_map_cache_entry {
+       u32 hash;
+       unsigned char *key;
+       size_t key_len;
+
+       struct netlbl_lsm_cache *lsm_data;
+
+       u32 activity;
+       struct list_head list;
+};
+
+static struct calipso_map_cache_bkt *calipso_cache;
+
+/* Label Mapping Cache Functions
+ */
+
+/**
+ * calipso_cache_entry_free - Frees a cache entry
+ * @entry: the entry to free
+ *
+ * Description:
+ * This function frees the memory associated with a cache entry including the
+ * LSM cache data if there are no longer any users, i.e. reference count == 0.
+ *
+ */
+static void calipso_cache_entry_free(struct calipso_map_cache_entry *entry)
+{
+       if (entry->lsm_data)
+               netlbl_secattr_cache_free(entry->lsm_data);
+       kfree(entry->key);
+       kfree(entry);
+}
+
+/**
+ * calipso_map_cache_hash - Hashing function for the CALIPSO cache
+ * @key: the hash key
+ * @key_len: the length of the key in bytes
+ *
+ * Description:
+ * The CALIPSO tag hashing function.  Returns a 32-bit hash value.
+ *
+ */
+static u32 calipso_map_cache_hash(const unsigned char *key, u32 key_len)
+{
+       return jhash(key, key_len, 0);
+}
+
+/**
+ * calipso_cache_init - Initialize the CALIPSO cache
+ *
+ * Description:
+ * Initializes the CALIPSO label mapping cache, this function should be called
+ * before any of the other functions defined in this file.  Returns zero on
+ * success, negative values on error.
+ *
+ */
+static int __init calipso_cache_init(void)
+{
+       u32 iter;
+
+       calipso_cache = kcalloc(CALIPSO_CACHE_BUCKETS,
+                               sizeof(struct calipso_map_cache_bkt),
+                               GFP_KERNEL);
+       if (!calipso_cache)
+               return -ENOMEM;
+
+       for (iter = 0; iter < CALIPSO_CACHE_BUCKETS; iter++) {
+               spin_lock_init(&calipso_cache[iter].lock);
+               calipso_cache[iter].size = 0;
+               INIT_LIST_HEAD(&calipso_cache[iter].list);
+       }
+
+       return 0;
+}
+
+/**
+ * calipso_cache_invalidate - Invalidates the current CALIPSO cache
+ *
+ * Description:
+ * Invalidates and frees any entries in the CALIPSO cache.  Returns zero on
+ * success and negative values on failure.
+ *
+ */
+static void calipso_cache_invalidate(void)
+{
+       struct calipso_map_cache_entry *entry, *tmp_entry;
+       u32 iter;
+
+       for (iter = 0; iter < CALIPSO_CACHE_BUCKETS; iter++) {
+               spin_lock_bh(&calipso_cache[iter].lock);
+               list_for_each_entry_safe(entry,
+                                        tmp_entry,
+                                        &calipso_cache[iter].list, list) {
+                       list_del(&entry->list);
+                       calipso_cache_entry_free(entry);
+               }
+               calipso_cache[iter].size = 0;
+               spin_unlock_bh(&calipso_cache[iter].lock);
+       }
+}
+
+/**
+ * calipso_cache_check - Check the CALIPSO cache for a label mapping
+ * @key: the buffer to check
+ * @key_len: buffer length in bytes
+ * @secattr: the security attribute struct to use
+ *
+ * Description:
+ * This function checks the cache to see if a label mapping already exists for
+ * the given key.  If there is a match then the cache is adjusted and the
+ * @secattr struct is populated with the correct LSM security attributes.  The
+ * cache is adjusted in the following manner if the entry is not already the
+ * first in the cache bucket:
+ *
+ *  1. The cache entry's activity counter is incremented
+ *  2. The previous (higher ranking) entry's activity counter is decremented
+ *  3. If the difference between the two activity counters is geater than
+ *     CALIPSO_CACHE_REORDERLIMIT the two entries are swapped
+ *
+ * Returns zero on success, -ENOENT for a cache miss, and other negative values
+ * on error.
+ *
+ */
+static int calipso_cache_check(const unsigned char *key,
+                              u32 key_len,
+                              struct netlbl_lsm_secattr *secattr)
+{
+       u32 bkt;
+       struct calipso_map_cache_entry *entry;
+       struct calipso_map_cache_entry *prev_entry = NULL;
+       u32 hash;
+
+       if (!calipso_cache_enabled)
+               return -ENOENT;
+
+       hash = calipso_map_cache_hash(key, key_len);
+       bkt = hash & (CALIPSO_CACHE_BUCKETS - 1);
+       spin_lock_bh(&calipso_cache[bkt].lock);
+       list_for_each_entry(entry, &calipso_cache[bkt].list, list) {
+               if (entry->hash == hash &&
+                   entry->key_len == key_len &&
+                   memcmp(entry->key, key, key_len) == 0) {
+                       entry->activity += 1;
+                       atomic_inc(&entry->lsm_data->refcount);
+                       secattr->cache = entry->lsm_data;
+                       secattr->flags |= NETLBL_SECATTR_CACHE;
+                       secattr->type = NETLBL_NLTYPE_CALIPSO;
+                       if (!prev_entry) {
+                               spin_unlock_bh(&calipso_cache[bkt].lock);
+                               return 0;
+                       }
+
+                       if (prev_entry->activity > 0)
+                               prev_entry->activity -= 1;
+                       if (entry->activity > prev_entry->activity &&
+                           entry->activity - prev_entry->activity >
+                           CALIPSO_CACHE_REORDERLIMIT) {
+                               __list_del(entry->list.prev, entry->list.next);
+                               __list_add(&entry->list,
+                                          prev_entry->list.prev,
+                                          &prev_entry->list);
+                       }
+
+                       spin_unlock_bh(&calipso_cache[bkt].lock);
+                       return 0;
+               }
+               prev_entry = entry;
+       }
+       spin_unlock_bh(&calipso_cache[bkt].lock);
+
+       return -ENOENT;
+}
+
+/**
+ * calipso_cache_add - Add an entry to the CALIPSO cache
+ * @calipso_ptr: the CALIPSO option
+ * @secattr: the packet's security attributes
+ *
+ * Description:
+ * Add a new entry into the CALIPSO label mapping cache.  Add the new entry to
+ * head of the cache bucket's list, if the cache bucket is out of room remove
+ * the last entry in the list first.  It is important to note that there is
+ * currently no checking for duplicate keys.  Returns zero on success,
+ * negative values on failure.  The key stored starts at calipso_ptr + 2,
+ * i.e. the type and length bytes are not stored, this corresponds to
+ * calipso_ptr[1] bytes of data.
+ *
+ */
+static int calipso_cache_add(const unsigned char *calipso_ptr,
+                            const struct netlbl_lsm_secattr *secattr)
+{
+       int ret_val = -EPERM;
+       u32 bkt;
+       struct calipso_map_cache_entry *entry = NULL;
+       struct calipso_map_cache_entry *old_entry = NULL;
+       u32 calipso_ptr_len;
+
+       if (!calipso_cache_enabled || calipso_cache_bucketsize <= 0)
+               return 0;
+
+       calipso_ptr_len = calipso_ptr[1];
+
+       entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
+       if (!entry)
+               return -ENOMEM;
+       entry->key = kmemdup(calipso_ptr + 2, calipso_ptr_len, GFP_ATOMIC);
+       if (!entry->key) {
+               ret_val = -ENOMEM;
+               goto cache_add_failure;
+       }
+       entry->key_len = calipso_ptr_len;
+       entry->hash = calipso_map_cache_hash(calipso_ptr, calipso_ptr_len);
+       atomic_inc(&secattr->cache->refcount);
+       entry->lsm_data = secattr->cache;
+
+       bkt = entry->hash & (CALIPSO_CACHE_BUCKETS - 1);
+       spin_lock_bh(&calipso_cache[bkt].lock);
+       if (calipso_cache[bkt].size < calipso_cache_bucketsize) {
+               list_add(&entry->list, &calipso_cache[bkt].list);
+               calipso_cache[bkt].size += 1;
+       } else {
+               old_entry = list_entry(calipso_cache[bkt].list.prev,
+                                      struct calipso_map_cache_entry, list);
+               list_del(&old_entry->list);
+               list_add(&entry->list, &calipso_cache[bkt].list);
+               calipso_cache_entry_free(old_entry);
+       }
+       spin_unlock_bh(&calipso_cache[bkt].lock);
+
+       return 0;
+
+cache_add_failure:
+       if (entry)
+               calipso_cache_entry_free(entry);
+       return ret_val;
+}
+
 /* DOI List Functions
  */
 
@@ -703,8 +952,10 @@ calipso_opt_insert(struct ipv6_opt_hdr *hop,
                memcpy(new, hop, start);
        ret_val = calipso_genopt((unsigned char *)new, start, buf_len, doi_def,
                                 secattr);
-       if (ret_val < 0)
+       if (ret_val < 0) {
+               kfree(new);
                return ERR_PTR(ret_val);
+       }
 
        buf_len = start + ret_val;
        /* At this point buf_len aligns to 4n, so (buf_len & 4) pads to 8n */
@@ -789,6 +1040,9 @@ static int calipso_opt_getattr(const unsigned char *calipso,
        if (cat_len + 8 > len)
                return -EINVAL;
 
+       if (calipso_cache_check(calipso + 2, calipso[1], secattr) == 0)
+               return 0;
+
        doi = get_unaligned_be32(calipso + 2);
        rcu_read_lock();
        doi_def = calipso_doi_search(doi);
@@ -1191,6 +1445,8 @@ static const struct netlbl_calipso_ops ops = {
        .skbuff_optptr    = calipso_skbuff_optptr,
        .skbuff_setattr   = calipso_skbuff_setattr,
        .skbuff_delattr   = calipso_skbuff_delattr,
+       .cache_invalidate = calipso_cache_invalidate,
+       .cache_add        = calipso_cache_add
 };
 
 /**
@@ -1203,11 +1459,17 @@ static const struct netlbl_calipso_ops ops = {
  */
 int __init calipso_init(void)
 {
-       netlbl_calipso_ops_register(&ops);
-       return 0;
+       int ret_val;
+
+       ret_val = calipso_cache_init();
+       if (!ret_val)
+               netlbl_calipso_ops_register(&ops);
+       return ret_val;
 }
 
 void calipso_exit(void)
 {
        netlbl_calipso_ops_register(NULL);
+       calipso_cache_invalidate();
+       kfree(calipso_cache);
 }