[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1304432663-1575-9-git-send-email-sam@synack.fr>
Date: Tue, 3 May 2011 16:24:21 +0200
From: Samir Bellabes <sam@...ack.fr>
To: linux-security-module@...r.kernel.org
Cc: linux-kernel@...r.kernel.org, netdev@...r.kernel.org,
netfilter-devel@...r.kernel.org, jamal <hadi@...erus.ca>,
Patrick McHardy <kaber@...sh.net>,
Evgeniy Polyakov <zbr@...emap.net>,
Grzegorz Nosek <root@...aldomain.pl>,
Samir Bellabes <sam@...ack.fr>
Subject: [RFC v3 08/10] snet: introduce snet_ticket
this patch adds the snet's subsystem managing granted-access tickets
snet is using the term 'ticket' for refering to a structure which keeps
informations about verdict, coming from userspace.
generic informations:
timeout
syscall
protocol
verdict
protocol-dependant informations : (so some infos may not be used)
address family
socket type
source address
source port
distant address
distant port
ticket are attached to the "void *security" pointer of task_struct
there are 3 modes:
0. no ticket - SNET_TICKET_OFF
every syscalls has to be verified by userspace.
1. timeout fixed - SNET_TICKET_FIX
for each response from the userspace, we are creating a ticket,
attached to the task_struct, with the filled informations, and a
fixed timeout value (10 secs by default).
then before asking userspace, kernel mecanism is checking existing
tickets for the task_struct, if there is a granted-access ticket, we
are using the verdict value attached.
after the timeout value, the ticket is destroyed.
2. timeout with extendable value - SNET_TICKET_EXTEND
this is the same mecanism as 1, but every time a ticket is matched
and used, the timeout value is reset to the default value, so its
life is extended.
Signed-off-by: Samir Bellabes <sam@...ack.fr>
---
security/snet/snet_ticket.c | 195 ++++++++++++++++++++++++++++++++++++
security/snet/snet_ticket.h | 37 +++++++
security/snet/snet_ticket_helper.c | 127 +++++++++++++++++++++++
security/snet/snet_ticket_helper.h | 8 ++
4 files changed, 367 insertions(+), 0 deletions(-)
create mode 100644 security/snet/snet_ticket.c
create mode 100644 security/snet/snet_ticket.h
create mode 100644 security/snet/snet_ticket_helper.c
create mode 100644 security/snet/snet_ticket_helper.h
diff --git a/security/snet/snet_ticket.c b/security/snet/snet_ticket.c
new file mode 100644
index 0000000..a260412
--- /dev/null
+++ b/security/snet/snet_ticket.c
@@ -0,0 +1,195 @@
+#include <linux/slab.h>
+#include <linux/cred.h>
+#include <linux/jhash.h>
+#include <linux/security.h>
+#include <linux/snet.h>
+#include "snet_ticket.h"
+#include "snet_ticket_helper.h"
+
+#define HSIZE 16
+
+static struct kmem_cache *snet_ticket_cachep;
+static struct kmem_cache *snet_task_security_cachep;
+
+enum snet_verdict snet_ticket_check(struct snet_info *info)
+{
+ struct snet_ticket *st = NULL;
+ unsigned int h = 0, verdict = SNET_VERDICT_NONE;
+ struct list_head *l = NULL;
+ struct snet_task_security *tsec = NULL;
+
+ if (snet_ticket_mode == SNET_TICKET_OFF)
+ goto out;
+
+ tsec = (struct snet_task_security*) current_security();
+
+ h = jhash_2words(info->syscall, info->protocol, 0) % HSIZE;
+ l = &tsec->hash[h];
+
+ read_lock_bh(&tsec->lock);
+ list_for_each_entry(st, l, list) {
+ if (__ticket_check(st, info)) {
+ verdict = st->verdict;
+ pr_debug("snet_ticket found: ticket=%p tsec=%p\n",
+ st, st->tsec);
+ if (snet_ticket_mode == SNET_TICKET_EXTEND) {
+ mod_timer(&st->timeout,
+ jiffies + snet_ticket_delay * HZ);
+ }
+ break;
+ }
+ }
+ read_unlock_bh(&tsec->lock);
+out:
+ return verdict;
+}
+
+static void snet_ticket_timeout(unsigned long arg)
+{
+ struct snet_ticket *st = (struct snet_ticket*)arg;
+
+ pr_debug("snet_ticket_timeout: ticket=%p tsec=%p\n", st, st->tsec);
+
+ write_lock_bh(&st->tsec->lock);
+ list_del(&st->list);
+ write_unlock_bh(&st->tsec->lock);
+ kmem_cache_free(snet_ticket_cachep, st);
+ return;
+}
+
+static struct snet_ticket *snet_ticket_alloc(void)
+{
+ struct snet_ticket *st = NULL;
+
+ st = kmem_cache_zalloc(snet_ticket_cachep, GFP_KERNEL);
+ if (st == NULL)
+ goto out;
+
+ INIT_LIST_HEAD(&st->list);
+ init_timer(&st->timeout);
+ st->timeout.expires = snet_ticket_delay * HZ;
+out:
+ return st;
+}
+
+static void snet_ticket_insert(struct snet_ticket *st)
+{
+ unsigned int h;
+ struct list_head *l;
+
+ h = jhash_2words(st->syscall, st->protocol, 0) % HSIZE;
+ l = &(st->tsec->hash[h]);
+
+ st->timeout.expires += jiffies;
+ add_timer(&st->timeout);
+
+ write_lock_bh(&(st->tsec->lock));
+ list_add_tail(&st->list, l);
+ write_unlock_bh(&(st->tsec->lock));
+ return;
+}
+
+void snet_ticket_create(struct snet_info *info, enum snet_verdict verdict)
+{
+ struct snet_ticket *st;
+ struct snet_task_security *tsec = NULL;
+
+ if (snet_ticket_mode == SNET_TICKET_OFF)
+ goto out;
+
+ tsec = (struct snet_task_security*) current_security();
+
+ st = snet_ticket_alloc();
+ if (st == NULL)
+ goto out;
+
+ st->tsec = tsec;
+ snet_ticket_fill(st, info, verdict);
+ setup_timer(&st->timeout, snet_ticket_timeout, (unsigned long)st);
+ snet_ticket_insert(st);
+out:
+ return;
+}
+
+int snet_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp)
+{
+ unsigned int index = 0;
+ struct snet_task_security *tsec = NULL;
+
+ tsec = kmem_cache_zalloc(snet_task_security_cachep, gfp);
+ if (tsec == NULL)
+ return -ENOMEM;
+
+ pr_debug("ticket_prepare_creds: pid=%u tsec=%p\n", current->pid, tsec);
+ rwlock_init(&tsec->lock);
+ for (index = 0; index < HSIZE; index++)
+ INIT_LIST_HEAD(&tsec->hash[index]);
+
+ new->security = tsec;
+ return 0;
+}
+
+void snet_cred_free(struct cred *cred)
+{
+ struct snet_task_security *tsec = cred->security;
+ unsigned int index;
+
+ pr_debug("ticket_free_cred: pid=%u tsec=%p\n", current->pid, tsec);
+
+ write_lock_bh(&tsec->lock);
+ /* destroy all tickets */
+ for (index = 0; index < HSIZE; index++) {
+ struct snet_ticket *st, *tmp;
+ list_for_each_entry_safe(st, tmp, &tsec->hash[index], list) {
+ if (del_timer_sync(&st->timeout)) {
+ pr_debug("ticket_cred_free: [%u] ticket=%p tsec=%p\n",
+ index, st, st->tsec);
+ list_del(&st->list);
+ kmem_cache_free(snet_ticket_cachep, st);
+ }
+ }
+ }
+ cred->security = NULL;
+ write_unlock_bh(&tsec->lock);
+ kmem_cache_free(snet_task_security_cachep, tsec);
+ return;
+}
+
+int snet_ticket_init(void)
+{
+ unsigned int index = 0;
+ struct cred *cred = (struct cred *) current->real_cred;
+ struct snet_task_security *tsec = NULL;
+
+ if (snet_ticket_mode >= SNET_TICKET_INVALID) {
+ printk(KERN_ERR "snet: bad snet_ticket_mode\n");
+ return -EINVAL;
+ }
+
+ if ((snet_ticket_mode == SNET_TICKET_FIX ||
+ snet_ticket_mode == SNET_TICKET_EXTEND) &&
+ (snet_ticket_delay == 0)) {
+ printk(KERN_ERR "snet: bad snet_ticket_delay\n");
+ return -EINVAL;
+ }
+
+ /* snet_ticket_cachep is not destroyed */
+ snet_ticket_cachep = kmem_cache_create("snet_ticket",
+ sizeof(struct snet_ticket),
+ 0, SLAB_PANIC, NULL);
+ /* snet_task_security_cachep is not destroyed */
+ snet_task_security_cachep = kmem_cache_create("snet_task_security",
+ sizeof(struct snet_task_security),
+ 0, SLAB_PANIC, NULL);
+
+ tsec = kmem_cache_zalloc(snet_task_security_cachep, GFP_KERNEL);
+ if (tsec == NULL)
+ return -ENOMEM;
+
+ rwlock_init(&tsec->lock);
+ for (index = 0; index < HSIZE; index++)
+ INIT_LIST_HEAD(&tsec->hash[index]);
+
+ cred->security = tsec;
+ return 0;
+}
diff --git a/security/snet/snet_ticket.h b/security/snet/snet_ticket.h
new file mode 100644
index 0000000..b6d1020
--- /dev/null
+++ b/security/snet/snet_ticket.h
@@ -0,0 +1,37 @@
+#ifndef _SNET_TICKET_H
+#define _SNET_TICKET_H
+
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/cred.h>
+#include <linux/snet.h>
+
+struct snet_task_security {
+ struct list_head hash[16];
+ rwlock_t lock;
+};
+
+struct snet_ticket {
+ struct list_head list;
+ struct snet_task_security *tsec;
+ struct timer_list timeout;
+
+ enum snet_syscall syscall;
+ u8 protocol;
+ u8 family;
+ int type;
+ struct snet_sock_half src;
+ struct snet_sock_half dst;
+ enum snet_verdict verdict;
+};
+
+extern unsigned int snet_ticket_delay;
+extern unsigned int snet_ticket_mode;
+
+void snet_cred_free(struct cred *cred);
+int snet_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp);
+enum snet_verdict snet_ticket_check(struct snet_info *info);
+void snet_ticket_create(struct snet_info *info, enum snet_verdict verdict);
+int snet_ticket_init(void);
+
+#endif /* _SNET_TICKET_H */
diff --git a/security/snet/snet_ticket_helper.c b/security/snet/snet_ticket_helper.c
new file mode 100644
index 0000000..2dc9b94
--- /dev/null
+++ b/security/snet/snet_ticket_helper.c
@@ -0,0 +1,127 @@
+#include <linux/sched.h>
+#include <linux/socket.h>
+#include <linux/snet.h>
+#include "snet_ticket.h"
+#include "snet_utils.h"
+
+static int check_create(struct snet_ticket *st, struct snet_info *info)
+{
+ return (st->type == info->type);
+}
+
+static int check_src(struct snet_ticket *st, struct snet_info *info)
+{
+ switch (info->family) {
+ case AF_INET:
+ if ((st->src.u3.ip == info->src.u3.ip) &&
+ (st->src.u.port == info->src.u.port))
+ return 1;
+ break;
+ case AF_INET6:
+ if ((!memcmp(&st->src.u3.ip6, &info->src.u3.ip6,
+ sizeof(info->src.u3.ip6))) &&
+ (st->src.u.port == info->src.u.port))
+ return 1;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int check_dst(struct snet_ticket *st, struct snet_info *info)
+{
+ switch (info->family) {
+ case AF_INET:
+ if ((st->dst.u3.ip == info->dst.u3.ip) &&
+ (st->dst.u.port == info->dst.u.port))
+ return 1;
+ break;
+ case AF_INET6:
+ if ((!memcmp(&st->dst.u3.ip6, &info->dst.u3.ip6,
+ sizeof(info->dst.u3.ip6))) &&
+ (st->dst.u.port == info->dst.u.port))
+ return 1;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int check_src_and_dst(struct snet_ticket *st, struct snet_info *info)
+{
+ return (check_src(st, info) && check_dst(st, info));
+}
+
+static int check_none(struct snet_ticket *st, struct snet_info *info)
+{
+ return 0;
+}
+
+int __ticket_check(struct snet_ticket *st, struct snet_info *info)
+{
+ static int (*ticket_df[])(struct snet_ticket *, struct snet_info *) = {
+ [SNET_SOCKET_CREATE] = &check_create,
+ [SNET_SOCKET_BIND] = &check_src,
+ [SNET_SOCKET_CONNECT] = &check_dst,
+ [SNET_SOCKET_LISTEN] = &check_src,
+ [SNET_SOCKET_ACCEPT] = &check_src,
+ [SNET_SOCKET_POST_ACCEPT] = &check_none,
+ [SNET_SOCKET_SENDMSG] = &check_src_and_dst,
+ [SNET_SOCKET_RECVMSG] = &check_src_and_dst,
+ [SNET_SOCKET_SOCK_RCV_SKB] = &check_src_and_dst,
+ [SNET_SOCKET_CLOSE] = &check_none,
+ };
+
+ if (info->syscall >= SNET_NR_SOCKET_TYPES)
+ return 0;
+ else {
+ if ((st->syscall == info->syscall) &&
+ (st->protocol == info->protocol) &&
+ (st->family == info->family) &&
+ ticket_df[info->syscall](st, info))
+ return 1;
+ else
+ return 0;
+ }
+}
+
+void snet_ticket_fill(struct snet_ticket *st, struct snet_info *info,
+ enum snet_verdict verdict)
+{
+ st->syscall = info->syscall;
+ st->protocol = info->protocol;
+ st->family = info->family;
+ st->src.u.port = info->src.u.port;
+ st->dst.u.port = info->dst.u.port;
+ st->verdict = verdict;
+
+ switch (info->family) {
+ case AF_INET:
+ st->src.u3.ip = info->src.u3.ip;
+ st->dst.u3.ip = info->dst.u3.ip;
+ pr_debug("ticket=%p [syscall=%s protocol=%u "
+ "family=%u %pI4:%u->%pI4:%u] verdict=%s | tsec=%p pid=%u\n",
+ st, snet_syscall_name(st->syscall), st->protocol,
+ st->family, &st->src.u3.ip, st->src.u.port,
+ &st->dst.u3.ip, st->dst.u.port,
+ snet_verdict_name(st->verdict), st->tsec, current->pid);
+ break;
+ case AF_INET6:
+ memcpy(&st->src.u3.ip6, &info->src.u3.ip6,
+ sizeof(info->src.u3.ip6));
+ memcpy(&st->dst.u3.ip6, &info->dst.u3.ip6,
+ sizeof(info->dst.u3.ip6));
+ pr_debug("ticket=%p [syscall=%s protocol=%u "
+ "family=%u %pI6:%u->%pI6:%u] verdict=%s | tsec=%p pid=%u\n",
+ st, snet_syscall_name(st->syscall), st->protocol,
+ st->family, &st->src.u3.ip6, st->src.u.port,
+ &st->dst.u3.ip6, st->dst.u.port,
+ snet_verdict_name(st->verdict), st->tsec, current->pid);
+ break;
+ default:
+ break;
+ }
+ return;
+}
diff --git a/security/snet/snet_ticket_helper.h b/security/snet/snet_ticket_helper.h
new file mode 100644
index 0000000..177bca5
--- /dev/null
+++ b/security/snet/snet_ticket_helper.h
@@ -0,0 +1,8 @@
+#ifndef _SNET_TICKET_HELPER_H
+#define _SNET_TICKET_HELPER_H
+
+void snet_ticket_fill(struct snet_ticket *st, struct snet_info *info,
+ enum snet_verdict verdict);
+int __ticket_check(struct snet_ticket *st, struct snet_info *info);
+
+#endif /* _SNET_TICKET_HELPER_H */
--
1.7.4.1
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists