summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2009-09-14 20:07:31 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2009-09-14 20:07:31 -0700
commit18240904960a39e582ced8ba8ececb10b8c22dd3 (patch)
tree90cbad5533c17657969acb97a0371e41923f7f93
parentf86054c24565d09d1997f03192761dabf6b8a9c9 (diff)
parent8a478905adbb2e09a59644e76f7fe7e0ab644204 (diff)
Merge branch 'for-linus3' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/security-testing-2.6
* 'for-linus3' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/security-testing-2.6: SELinux: inline selinux_is_enabled in !CONFIG_SECURITY_SELINUX KEYS: Fix garbage collector KEYS: Unlock tasklist when exiting early from keyctl_session_to_parent CRED: Allow put_cred() to cope with a NULL groups list SELinux: flush the avc before disabling SELinux SELinux: seperate avc_cache flushing Creds: creds->security can be NULL is selinux is disabled
-rw-r--r--include/linux/cred.h13
-rw-r--r--include/linux/selinux.h9
-rw-r--r--kernel/cred.c3
-rw-r--r--security/keys/gc.c78
-rw-r--r--security/keys/key.c4
-rw-r--r--security/keys/keyctl.c3
-rw-r--r--security/keys/keyring.c24
-rw-r--r--security/selinux/avc.c26
-rw-r--r--security/selinux/exports.c6
9 files changed, 118 insertions, 48 deletions
diff --git a/include/linux/cred.h b/include/linux/cred.h
index 24520a539c6..fb371601a3b 100644
--- a/include/linux/cred.h
+++ b/include/linux/cred.h
@@ -15,6 +15,7 @@
#include <linux/capability.h>
#include <linux/init.h>
#include <linux/key.h>
+#include <linux/selinux.h>
#include <asm/atomic.h>
struct user_struct;
@@ -182,11 +183,13 @@ static inline bool creds_are_invalid(const struct cred *cred)
if (atomic_read(&cred->usage) < atomic_read(&cred->subscribers))
return true;
#ifdef CONFIG_SECURITY_SELINUX
- if ((unsigned long) cred->security < PAGE_SIZE)
- return true;
- if ((*(u32*)cred->security & 0xffffff00) ==
- (POISON_FREE << 24 | POISON_FREE << 16 | POISON_FREE << 8))
- return true;
+ if (selinux_is_enabled()) {
+ if ((unsigned long) cred->security < PAGE_SIZE)
+ return true;
+ if ((*(u32 *)cred->security & 0xffffff00) ==
+ (POISON_FREE << 24 | POISON_FREE << 16 | POISON_FREE << 8))
+ return true;
+ }
#endif
return false;
}
diff --git a/include/linux/selinux.h b/include/linux/selinux.h
index 20f965d4b04..82e0f26a129 100644
--- a/include/linux/selinux.h
+++ b/include/linux/selinux.h
@@ -61,6 +61,11 @@ void selinux_secmark_refcount_inc(void);
* existing SECMARK targets has been removed/flushed.
*/
void selinux_secmark_refcount_dec(void);
+
+/**
+ * selinux_is_enabled - is SELinux enabled?
+ */
+bool selinux_is_enabled(void);
#else
static inline int selinux_string_to_sid(const char *str, u32 *sid)
@@ -84,6 +89,10 @@ static inline void selinux_secmark_refcount_dec(void)
return;
}
+static inline bool selinux_is_enabled(void)
+{
+ return false;
+}
#endif /* CONFIG_SECURITY_SELINUX */
#endif /* _LINUX_SELINUX_H */
diff --git a/kernel/cred.c b/kernel/cred.c
index 006fcab009d..d7f7a01082e 100644
--- a/kernel/cred.c
+++ b/kernel/cred.c
@@ -147,7 +147,8 @@ static void put_cred_rcu(struct rcu_head *rcu)
key_put(cred->thread_keyring);
key_put(cred->request_key_auth);
release_tgcred(cred);
- put_group_info(cred->group_info);
+ if (cred->group_info)
+ put_group_info(cred->group_info);
free_uid(cred->user);
kmem_cache_free(cred_jar, cred);
}
diff --git a/security/keys/gc.c b/security/keys/gc.c
index 1e616aef55f..485fc6233c3 100644
--- a/security/keys/gc.c
+++ b/security/keys/gc.c
@@ -26,8 +26,10 @@ static void key_garbage_collector(struct work_struct *);
static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0);
static DECLARE_WORK(key_gc_work, key_garbage_collector);
static key_serial_t key_gc_cursor; /* the last key the gc considered */
+static bool key_gc_again;
static unsigned long key_gc_executing;
static time_t key_gc_next_run = LONG_MAX;
+static time_t key_gc_new_timer;
/*
* Schedule a garbage collection run
@@ -40,9 +42,7 @@ void key_schedule_gc(time_t gc_at)
kenter("%ld", gc_at - now);
- gc_at += key_gc_delay;
-
- if (now >= gc_at) {
+ if (gc_at <= now) {
schedule_work(&key_gc_work);
} else if (gc_at < key_gc_next_run) {
expires = jiffies + (gc_at - now) * HZ;
@@ -112,16 +112,18 @@ static void key_garbage_collector(struct work_struct *work)
struct rb_node *rb;
key_serial_t cursor;
struct key *key, *xkey;
- time_t new_timer = LONG_MAX, limit;
+ time_t new_timer = LONG_MAX, limit, now;
- kenter("");
+ now = current_kernel_time().tv_sec;
+ kenter("[%x,%ld]", key_gc_cursor, key_gc_new_timer - now);
if (test_and_set_bit(0, &key_gc_executing)) {
- key_schedule_gc(current_kernel_time().tv_sec);
+ key_schedule_gc(current_kernel_time().tv_sec + 1);
+ kleave(" [busy; deferring]");
return;
}
- limit = current_kernel_time().tv_sec;
+ limit = now;
if (limit > key_gc_delay)
limit -= key_gc_delay;
else
@@ -129,12 +131,19 @@ static void key_garbage_collector(struct work_struct *work)
spin_lock(&key_serial_lock);
- if (RB_EMPTY_ROOT(&key_serial_tree))
- goto reached_the_end;
+ if (unlikely(RB_EMPTY_ROOT(&key_serial_tree))) {
+ spin_unlock(&key_serial_lock);
+ clear_bit(0, &key_gc_executing);
+ return;
+ }
cursor = key_gc_cursor;
if (cursor < 0)
cursor = 0;
+ if (cursor > 0)
+ new_timer = key_gc_new_timer;
+ else
+ key_gc_again = false;
/* find the first key above the cursor */
key = NULL;
@@ -160,35 +169,50 @@ static void key_garbage_collector(struct work_struct *work)
/* trawl through the keys looking for keyrings */
for (;;) {
- if (key->expiry > 0 && key->expiry < new_timer)
+ if (key->expiry > now && key->expiry < new_timer) {
+ kdebug("will expire %x in %ld",
+ key_serial(key), key->expiry - now);
new_timer = key->expiry;
+ }
if (key->type == &key_type_keyring &&
- key_gc_keyring(key, limit)) {
- /* the gc ate our lock */
- schedule_work(&key_gc_work);
- goto no_unlock;
- }
+ key_gc_keyring(key, limit))
+ /* the gc had to release our lock so that the keyring
+ * could be modified, so we have to get it again */
+ goto gc_released_our_lock;
rb = rb_next(&key->serial_node);
- if (!rb) {
- key_gc_cursor = 0;
- break;
- }
+ if (!rb)
+ goto reached_the_end;
key = rb_entry(rb, struct key, serial_node);
}
-out:
- spin_unlock(&key_serial_lock);
-no_unlock:
+gc_released_our_lock:
+ kdebug("gc_released_our_lock");
+ key_gc_new_timer = new_timer;
+ key_gc_again = true;
clear_bit(0, &key_gc_executing);
- if (new_timer < LONG_MAX)
- key_schedule_gc(new_timer);
-
- kleave("");
+ schedule_work(&key_gc_work);
+ kleave(" [continue]");
return;
+ /* when we reach the end of the run, we set the timer for the next one */
reached_the_end:
+ kdebug("reached_the_end");
+ spin_unlock(&key_serial_lock);
+ key_gc_new_timer = new_timer;
key_gc_cursor = 0;
- goto out;
+ clear_bit(0, &key_gc_executing);
+
+ if (key_gc_again) {
+ /* there may have been a key that expired whilst we were
+ * scanning, so if we discarded any links we should do another
+ * scan */
+ new_timer = now + 1;
+ key_schedule_gc(new_timer);
+ } else if (new_timer < LONG_MAX) {
+ new_timer += key_gc_delay;
+ key_schedule_gc(new_timer);
+ }
+ kleave(" [end]");
}
diff --git a/security/keys/key.c b/security/keys/key.c
index 08531ad0f25..e50d264c9ad 100644
--- a/security/keys/key.c
+++ b/security/keys/key.c
@@ -500,7 +500,7 @@ int key_negate_and_link(struct key *key,
set_bit(KEY_FLAG_INSTANTIATED, &key->flags);
now = current_kernel_time();
key->expiry = now.tv_sec + timeout;
- key_schedule_gc(key->expiry);
+ key_schedule_gc(key->expiry + key_gc_delay);
if (test_and_clear_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags))
awaken = 1;
@@ -909,7 +909,7 @@ void key_revoke(struct key *key)
time = now.tv_sec;
if (key->revoked_at == 0 || key->revoked_at > time) {
key->revoked_at = time;
- key_schedule_gc(key->revoked_at);
+ key_schedule_gc(key->revoked_at + key_gc_delay);
}
up_write(&key->sem);
diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c
index 74c96852459..2fb28efc532 100644
--- a/security/keys/keyctl.c
+++ b/security/keys/keyctl.c
@@ -1115,7 +1115,7 @@ long keyctl_set_timeout(key_serial_t id, unsigned timeout)
}
key->expiry = expiry;
- key_schedule_gc(key->expiry);
+ key_schedule_gc(key->expiry + key_gc_delay);
up_write(&key->sem);
key_put(key);
@@ -1319,6 +1319,7 @@ long keyctl_session_to_parent(void)
already_same:
ret = 0;
not_permitted:
+ write_unlock_irq(&tasklist_lock);
put_cred(cred);
return ret;
diff --git a/security/keys/keyring.c b/security/keys/keyring.c
index ac977f661a7..8ec02746ca9 100644
--- a/security/keys/keyring.c
+++ b/security/keys/keyring.c
@@ -1019,18 +1019,18 @@ void keyring_gc(struct key *keyring, time_t limit)
struct key *key;
int loop, keep, max;
- kenter("%x", key_serial(keyring));
+ kenter("{%x,%s}", key_serial(keyring), keyring->description);
down_write(&keyring->sem);
klist = keyring->payload.subscriptions;
if (!klist)
- goto just_return;
+ goto no_klist;
/* work out how many subscriptions we're keeping */
keep = 0;
for (loop = klist->nkeys - 1; loop >= 0; loop--)
- if (!key_is_dead(klist->keys[loop], limit));
+ if (!key_is_dead(klist->keys[loop], limit))
keep++;
if (keep == klist->nkeys)
@@ -1041,7 +1041,7 @@ void keyring_gc(struct key *keyring, time_t limit)
new = kmalloc(sizeof(struct keyring_list) + max * sizeof(struct key *),
GFP_KERNEL);
if (!new)
- goto just_return;
+ goto nomem;
new->maxkeys = max;
new->nkeys = 0;
new->delkey = 0;
@@ -1081,7 +1081,21 @@ void keyring_gc(struct key *keyring, time_t limit)
discard_new:
new->nkeys = keep;
keyring_clear_rcu_disposal(&new->rcu);
+ up_write(&keyring->sem);
+ kleave(" [discard]");
+ return;
+
just_return:
up_write(&keyring->sem);
- kleave(" [no]");
+ kleave(" [no dead]");
+ return;
+
+no_klist:
+ up_write(&keyring->sem);
+ kleave(" [no_klist]");
+ return;
+
+nomem:
+ up_write(&keyring->sem);
+ kleave(" [oom]");
}
diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index e3d19014259..1ed0f076aad 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -709,18 +709,16 @@ out:
}
/**
- * avc_ss_reset - Flush the cache and revalidate migrated permissions.
- * @seqno: policy sequence number
+ * avc_flush - Flush the cache
*/
-int avc_ss_reset(u32 seqno)
+static void avc_flush(void)
{
- struct avc_callback_node *c;
- int i, rc = 0, tmprc;
- unsigned long flag;
- struct avc_node *node;
struct hlist_head *head;
struct hlist_node *next;
+ struct avc_node *node;
spinlock_t *lock;
+ unsigned long flag;
+ int i;
for (i = 0; i < AVC_CACHE_SLOTS; i++) {
head = &avc_cache.slots[i];
@@ -737,6 +735,18 @@ int avc_ss_reset(u32 seqno)
rcu_read_unlock();
spin_unlock_irqrestore(lock, flag);
}
+}
+
+/**
+ * avc_ss_reset - Flush the cache and revalidate migrated permissions.
+ * @seqno: policy sequence number
+ */
+int avc_ss_reset(u32 seqno)
+{
+ struct avc_callback_node *c;
+ int rc = 0, tmprc;
+
+ avc_flush();
for (c = avc_callbacks; c; c = c->next) {
if (c->events & AVC_CALLBACK_RESET) {
@@ -858,6 +868,8 @@ u32 avc_policy_seqno(void)
void avc_disable(void)
{
+ avc_flush();
+ synchronize_rcu();
if (avc_node_cachep)
kmem_cache_destroy(avc_node_cachep);
}
diff --git a/security/selinux/exports.c b/security/selinux/exports.c
index c73aeaa008e..c0a454aee1e 100644
--- a/security/selinux/exports.c
+++ b/security/selinux/exports.c
@@ -63,3 +63,9 @@ void selinux_secmark_refcount_dec(void)
atomic_dec(&selinux_secmark_refcount);
}
EXPORT_SYMBOL_GPL(selinux_secmark_refcount_dec);
+
+bool selinux_is_enabled(void)
+{
+ return selinux_enabled;
+}
+EXPORT_SYMBOL_GPL(selinux_is_enabled);