CaitSith (acronym for "Characteristic action inspection tool. See if
this helps.") is an LSM based access control implementation which uses
action check list (acl) as policy syntax.
Syntax of an acl block is shown below.
acl "Action" "Whether to check Action or not"
"Decision1" "Whether to use Decision1 or not"
"Decision2" "Whether to use Decision2 or not"
"Decision3" "Whether to use Decision3 or not"
Specify Action as a key, and enumerate conditions as needed
using variable=value or variable!=value expression.
Split into two phases: "whether to check or not" and "whether
to grant/deny or not".
"Action" is name of action (e.g. "execve").
"Decision" is either "allow" or "deny".
You can define multiple acl blocks as needed. All acl blocks for
each "Action" will be evaluated. The access request will be granted
when none of deny line in all evaluated acl blocks matches.
An unsigned integer value is prefixed to each line of rules for
controlling priority of evaluation.
Examples shown below will help you understanding.
Example 1: /usr/sbin/httpd is allowed to execute only
/var/www/cgi-bin/counter.cgi and /usr/bin/suexec .
1000 acl execute task.exe="/usr/sbin/httpd"
100 allow path="/var/www/cgi-bin/counter.cgi"
150 allow path="/usr/sbin/suexec"
200 deny
Example 2: /usr/sbin/httpd is not allowed to execute /bin/sh and
/bin/bash .
2000 acl execute task.exe="/usr/sbin/httpd"
100 deny path="/bin/sh"
150 deny path="/bin/bash"
200 allow
Example 3: /usr/sbin/suexec is allowed to be executed by only
/usr/sbin/httpd .
2000 acl execute path="/usr/sbin/suexec"
100 allow task.exe="/usr/sbin/httpd"
200 deny
CaitSith tries to remove many limitations which existing security
enhanced Linux (I mean any LSM based access control implementation) has.
(1) CaitSith can use both string / numeric arguments (like TOMOYO and
AppArmor) and security labels (like SELinux and Smack). There is no
reason that access control implementation must not use both.
(2) CaitSith can specify rules from the point of view of both subjects
(a.k.a. capability list) and objects (a.k.a. access control list).
There is no reason that access control implementation must use
capability list which entails identification of all subjects.
While security people are tempted to protect as much as possible,
users generally have insufficient resource for protecting all. If
users have sufficient resource, they will be already using existing
implementations.
I found users who want to restrict only a few objects without
being bothered by managing all subjects in their systems. This is
impossible for TOMOYO because TOMOYO entails managing all subjects
in their systems. In CaitSith, this limitation is solved by writing
rules using action as a key.
(3) CaitSith can represent complicated string / numeric arguments
compared to TOMOYO, for any condition is represented as zero or
more repetition of variable=value or variable!=value expression.
In TOMOYO, the policy syntax requires positional mandatory parameters
(e.g. pathname when opening a pathname, pathname and DAC permission
mode when creating a pathname) based on type and number of arguments
for that action. But it turned out that using positional parameters
undesirably limits ability to specify complicated conditions. For
example, when specific pattern should be excluded from some pattern
(e.g. "*" but "*.tmp"), administrator has to use \- operator (e.g.
"\*\-\*.tmp") for that purpose. It makes conditions difficult to
understand. \- operator needs to be used with cautions that unwanted
patterns are not included by error. It made complicated conditions
very hard to understand.
In CaitSith, this limitation is solved by writing rules like
path="\*" path!="\*.tmp" instead of path="\*\-\*.tmp" .
(4) CaitSith can specify rules using both whitelisting and blacklisting.
As far as I know, whoever involved in security enhanced Linux in
Japan lost their jobs. In other words, security enhanced Linux made
no business sense in Japan. I think that existing implementations
are asking for too much skill/knowledge compared to users can afford.
CaitSith's syntax acts as whitelisting if an unconditional deny line
comes before an unconditional allow line comes, acts as blacklisting
if an unconditional allow line comes before an unconditional deny
line comes.
CaitSith stayed four years and a half listening for whether it suits
user's needs. In the last year or so, questions regarding how to use
TOMOYO are getting to come. However, it turned out that CaitSith seems
to fit better than TOMOYO for what many of the questioners want to
achieve. You can see slides shown below for full explanation of
the how and why.
http://I-love.SAKURA.ne.jp/tomoyo/CaitSith-en.pdf (English)
http://I-love.SAKURA.ne.jp/tomoyo/CaitSith-ja.pdf (Japanese)
In order to minimize the burden of reviewing, this patchset implements
only functionality of checking program execution requests (i.e. execve()
system call) using pathnames. I'm planning to add other functionalities
after this version got included into mainline. You can find how future
versions of CaitSith will look like at http://caitsith.osdn.jp/ .
Tetsuo Handa (8):
CaitSith: Add header file.
CaitSith: Add pathname calculation functions.
CaitSith: Add policy I/O functions.
CaitSith: Add permission check functions.
CaitSith: Add LSM adapter functions.
CaitSith: Add policy loader functions.
CaitSith: Add garbage collector functions.
CaitSith: Add Kconfig and Makefile
security/Kconfig | 6 +
security/Makefile | 2 +
security/caitsith/Kconfig | 48 ++
security/caitsith/Makefile | 15 +
security/caitsith/caitsith.h | 347 +++++++++
security/caitsith/gc.c | 373 ++++++++++
security/caitsith/load_policy.c | 106 +++
security/caitsith/lsm.c | 60 ++
security/caitsith/permission.c | 691 +++++++++++++++++
security/caitsith/policy_io.c | 1553 +++++++++++++++++++++++++++++++++++++++
security/caitsith/realpath.c | 227 ++++++
11 files changed, 3428 insertions(+)
create mode 100644 security/caitsith/Kconfig
create mode 100644 security/caitsith/Makefile
create mode 100644 security/caitsith/caitsith.h
create mode 100644 security/caitsith/gc.c
create mode 100644 security/caitsith/load_policy.c
create mode 100644 security/caitsith/lsm.c
create mode 100644 security/caitsith/permission.c
create mode 100644 security/caitsith/policy_io.c
create mode 100644 security/caitsith/realpath.c
--
1.8.3.1
This file corresponds to security/tomoyo/gc.c . Since CaitSith uses
a common variable length data structure, garbage collector became
simpler than TOMOYO.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/caitsith/gc.c | 373 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 373 insertions(+)
create mode 100644 security/caitsith/gc.c
diff --git a/security/caitsith/gc.c b/security/caitsith/gc.c
new file mode 100644
index 0000000..d55f02d
--- /dev/null
+++ b/security/caitsith/gc.c
@@ -0,0 +1,373 @@
+/*
+ * security/caitsith/gc.c
+ *
+ * Copyright (C) 2005-2012 NTT DATA CORPORATION
+ */
+
+#include "caitsith.h"
+
+/* The list for "struct cs_io_buffer". */
+static LIST_HEAD(cs_io_buffer_list);
+/* Lock for protecting cs_io_buffer_list. */
+static DEFINE_SPINLOCK(cs_io_buffer_list_lock);
+
+static bool cs_name_used_by_io_buffer(const char *string, const size_t size);
+static bool cs_struct_used_by_io_buffer(const struct list_head *element);
+static int cs_gc_thread(void *unused);
+static void cs_collect_acl(struct list_head *list);
+static void cs_collect_entry(void);
+static void cs_try_to_gc(const enum cs_policy_id type,
+ struct list_head *element);
+
+/*
+ * Lock for syscall users.
+ *
+ * This lock is held for only protecting single SRCU section.
+ */
+struct srcu_struct cs_ss;
+
+/**
+ * cs_struct_used_by_io_buffer - Check whether the list element is used by /sys/kernel/security/caitsith/ users or not.
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns true if @element is used by /sys/kernel/security/caitsith/ users,
+ * false otherwise.
+ */
+static bool cs_struct_used_by_io_buffer(const struct list_head *element)
+{
+ struct cs_io_buffer *head;
+ bool in_use = false;
+
+ spin_lock(&cs_io_buffer_list_lock);
+ list_for_each_entry(head, &cs_io_buffer_list, list) {
+ head->users++;
+ spin_unlock(&cs_io_buffer_list_lock);
+ mutex_lock(&head->io_sem);
+ if (head->r.acl == element || head->r.subacl == element ||
+ &head->w.acl->list == element)
+ in_use = true;
+ mutex_unlock(&head->io_sem);
+ spin_lock(&cs_io_buffer_list_lock);
+ head->users--;
+ if (in_use)
+ break;
+ }
+ spin_unlock(&cs_io_buffer_list_lock);
+ return in_use;
+}
+
+/**
+ * cs_name_used_by_io_buffer - Check whether the string is used by /sys/kernel/security/caitsith/ users or not.
+ *
+ * @string: String to check.
+ * @size: Memory allocated for @string .
+ *
+ * Returns true if @string is used by /sys/kernel/security/caitsith/ users,
+ * false otherwise.
+ */
+static bool cs_name_used_by_io_buffer(const char *string, const size_t size)
+{
+ struct cs_io_buffer *head;
+ bool in_use = false;
+
+ spin_lock(&cs_io_buffer_list_lock);
+ list_for_each_entry(head, &cs_io_buffer_list, list) {
+ int i;
+
+ head->users++;
+ spin_unlock(&cs_io_buffer_list_lock);
+ mutex_lock(&head->io_sem);
+ for (i = 0; i < CS_MAX_IO_READ_QUEUE; i++) {
+ const char *w = head->r.w[i];
+
+ if (w < string || w > string + size)
+ continue;
+ in_use = true;
+ break;
+ }
+ mutex_unlock(&head->io_sem);
+ spin_lock(&cs_io_buffer_list_lock);
+ head->users--;
+ if (in_use)
+ break;
+ }
+ spin_unlock(&cs_io_buffer_list_lock);
+ return in_use;
+}
+
+/**
+ * cs_acl_info_has_sub_acl - Clear "struct cs_acl_info"->acl_info.
+ *
+ * @list: Pointer to "struct list_head".
+ *
+ * Returns true if @list is not empty, false otherwise.
+ */
+static bool cs_acl_info_has_sub_acl(struct list_head *list)
+{
+ struct cs_acl_info *acl;
+ struct cs_acl_info *tmp;
+
+ if (list_empty(list))
+ return false;
+ mutex_lock(&cs_policy_lock);
+ list_for_each_entry_safe(acl, tmp, list, list) {
+ cs_try_to_gc(CS_ID_ACL, &acl->list);
+ }
+ mutex_unlock(&cs_policy_lock);
+ return !list_empty(list);
+}
+
+/**
+ * cs_del_acl - Delete members in "struct cs_acl_info".
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ */
+static inline void cs_del_acl(struct list_head *element)
+{
+ struct cs_acl_info *acl = container_of(element, typeof(*acl), list);
+
+ cs_put_condition(acl->cond);
+}
+
+/**
+ * cs_del_condition - Delete members in "struct cs_condition".
+ *
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ */
+void cs_del_condition(struct list_head *element)
+{
+ struct cs_condition *cond = container_of(element, typeof(*cond),
+ head.list);
+ const union cs_condition_element *condp = (typeof(condp)) (cond + 1);
+
+ while ((void *) condp < (void *) ((u8 *) cond) + cond->size) {
+ const enum cs_conditions_index right = condp->right;
+
+ condp++;
+ if (right == CS_IMM_NAME_ENTRY) {
+ if (condp->path != &cs_null_name)
+ cs_put_name(condp->path);
+ condp++;
+ }
+ }
+}
+
+/**
+ * cs_try_to_gc - Try to kfree() an entry.
+ *
+ * @type: One of values in "enum cs_policy_id".
+ * @element: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static void cs_try_to_gc(const enum cs_policy_id type,
+ struct list_head *element)
+{
+ /*
+ * __list_del_entry() guarantees that the list element became no longer
+ * reachable from the list which the element was originally on (e.g.
+ * cs_domain_list). Also, synchronize_srcu() guarantees that the list
+ * element became no longer referenced by syscall users.
+ */
+ __list_del_entry(element);
+ mutex_unlock(&cs_policy_lock);
+ synchronize_srcu(&cs_ss);
+ /*
+ * However, there are two users which may still be using the list
+ * element. We need to defer until both users forget this element.
+ *
+ * Don't kfree() until "struct cs_io_buffer"->r.{acl,subacl} and
+ * "struct cs_io_buffer"->w.acl forget this element.
+ */
+ if (cs_struct_used_by_io_buffer(element))
+ goto reinject;
+ switch (type) {
+ case CS_ID_CONDITION:
+ cs_del_condition(element);
+ break;
+ case CS_ID_NAME:
+ /*
+ * Don't kfree() until all "struct cs_io_buffer"->r.w[] forget
+ * this element.
+ */
+ if (cs_name_used_by_io_buffer
+ (container_of(element, typeof(struct cs_name),
+ head.list)->entry.name,
+ container_of(element, typeof(struct cs_name),
+ head.list)->size))
+ goto reinject;
+ break;
+ case CS_ID_ACL:
+ /*
+ * Don't kfree() until "struct cs_acl_info"->acl_info_list
+ * becomes empty.
+ */
+ if (cs_acl_info_has_sub_acl
+ (&container_of(element, typeof(struct cs_acl_info),
+ list)->acl_info_list))
+ goto reinject;
+ cs_del_acl(element);
+ break;
+ default:
+ break;
+ }
+ mutex_lock(&cs_policy_lock);
+ cs_memory_used[CS_MEMORY_POLICY] -= ksize(element);
+ kfree(element);
+ return;
+reinject:
+ /*
+ * We can safely reinject this element here bacause
+ * (1) Appending list elements and removing list elements are protected
+ * by cs_policy_lock mutex.
+ * (2) Only this function removes list elements and this function is
+ * exclusively executed by cs_gc_mutex mutex.
+ * are true.
+ */
+ mutex_lock(&cs_policy_lock);
+ list_add_rcu(element, element->prev);
+}
+
+/**
+ * cs_collect_acl - Delete elements in "struct cs_acl_info".
+ *
+ * @list: Pointer to "struct list_head".
+ *
+ * Returns nothing.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static void cs_collect_acl(struct list_head *list)
+{
+ struct cs_acl_info *acl;
+ struct cs_acl_info *tmp;
+
+ list_for_each_entry_safe(acl, tmp, list, list) {
+ if (!acl->is_deleted)
+ continue;
+ cs_try_to_gc(CS_ID_ACL, &acl->list);
+ }
+}
+
+/**
+ * cs_collect_entry - Try to kfree() deleted elements.
+ *
+ * Returns nothing.
+ */
+static void cs_collect_entry(void)
+{
+ int i;
+
+ mutex_lock(&cs_policy_lock);
+ for (i = 0; i < CS_MAX_MAC_INDEX; i++) {
+ struct cs_acl_info *ptr;
+ struct cs_acl_info *tmp;
+ struct list_head * const list = &cs_acl_list[i];
+
+ list_for_each_entry_safe(ptr, tmp, list, list) {
+ cs_collect_acl(&ptr->acl_info_list);
+ if (!ptr->is_deleted)
+ continue;
+ ptr->is_deleted = CS_GC_IN_PROGRESS;
+ cs_try_to_gc(CS_ID_ACL, &ptr->list);
+ }
+ }
+ {
+ struct cs_shared_acl_head *ptr;
+ struct cs_shared_acl_head *tmp;
+
+ list_for_each_entry_safe(ptr, tmp, &cs_condition_list, list) {
+ if (atomic_read(&ptr->users) > 0)
+ continue;
+ atomic_set(&ptr->users, CS_GC_IN_PROGRESS);
+ cs_try_to_gc(CS_ID_CONDITION, &ptr->list);
+ }
+ }
+ for (i = 0; i < CS_MAX_HASH; i++) {
+ struct list_head *list = &cs_name_list[i];
+ struct cs_shared_acl_head *ptr;
+ struct cs_shared_acl_head *tmp;
+
+ list_for_each_entry_safe(ptr, tmp, list, list) {
+ if (atomic_read(&ptr->users) > 0)
+ continue;
+ atomic_set(&ptr->users, CS_GC_IN_PROGRESS);
+ cs_try_to_gc(CS_ID_NAME, &ptr->list);
+ }
+ }
+ mutex_unlock(&cs_policy_lock);
+}
+
+/**
+ * cs_gc_thread - Garbage collector thread function.
+ *
+ * @unused: Unused.
+ *
+ * Returns 0.
+ */
+static int cs_gc_thread(void *unused)
+{
+ /* Garbage collector thread is exclusive. */
+ static DEFINE_MUTEX(cs_gc_mutex);
+
+ if (!mutex_trylock(&cs_gc_mutex))
+ goto out;
+ cs_collect_entry();
+ {
+ struct cs_io_buffer *head;
+ struct cs_io_buffer *tmp;
+
+ spin_lock(&cs_io_buffer_list_lock);
+ list_for_each_entry_safe(head, tmp, &cs_io_buffer_list,
+ list) {
+ if (head->users)
+ continue;
+ list_del(&head->list);
+ kfree(head->read_buf);
+ kfree(head->write_buf);
+ kfree(head);
+ }
+ spin_unlock(&cs_io_buffer_list_lock);
+ }
+ mutex_unlock(&cs_gc_mutex);
+out:
+ /* This acts as do_exit(0). */
+ return 0;
+}
+
+/**
+ * cs_notify_gc - Register/unregister /sys/kernel/security/caitsith/ users.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @is_register: True if register, false if unregister.
+ *
+ * Returns nothing.
+ */
+void cs_notify_gc(struct cs_io_buffer *head, const bool is_register)
+{
+ bool is_write = false;
+
+ spin_lock(&cs_io_buffer_list_lock);
+ if (is_register) {
+ head->users = 1;
+ list_add(&head->list, &cs_io_buffer_list);
+ } else {
+ is_write = head->write_buf != NULL;
+ if (!--head->users) {
+ list_del(&head->list);
+ kfree(head->read_buf);
+ kfree(head->write_buf);
+ kfree(head);
+ }
+ }
+ spin_unlock(&cs_io_buffer_list_lock);
+ if (is_write)
+ kthread_run(cs_gc_thread, NULL, "CaitSith's GC");
+}
--
1.8.3.1
This file corresponds to security/tomoyo/condition.c and
security/tomoyo/file.c .
Compared to TOMOYO, a new wildcard /\(dir\)/ has been introduced for
helping converting from (e.g.) "/tmp/\{\*\}/" to "/tmp/\(\*\)/\*", for
directory's pathname (except the root directory itself) no longer ends
with / character which previously matched /\{\*\}/ wildcard.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/caitsith/permission.c | 691 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 691 insertions(+)
create mode 100644 security/caitsith/permission.c
diff --git a/security/caitsith/permission.c b/security/caitsith/permission.c
new file mode 100644
index 0000000..9b33bea
--- /dev/null
+++ b/security/caitsith/permission.c
@@ -0,0 +1,691 @@
+/*
+ * security/caitsith/permission.c
+ *
+ * Copyright (C) 2005-2012 NTT DATA CORPORATION
+ */
+
+#include "caitsith.h"
+
+/* Type of condition argument. */
+enum cs_arg_type {
+ CS_ARG_TYPE_NONE,
+ CS_ARG_TYPE_NAME,
+} __packed;
+
+/* Structure for holding single condition component. */
+struct cs_cond_arg {
+ enum cs_arg_type type;
+ const struct cs_path_info *name;
+};
+
+static bool cs_alphabet_char(const char c);
+static bool cs_byte_range(const char *str);
+static bool cs_check_entry(struct cs_request_info *r,
+ const struct cs_acl_info *ptr);
+static bool cs_condition(struct cs_request_info *r,
+ const struct cs_condition *cond);
+static bool cs_file_matches_pattern(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end);
+static bool cs_file_matches_pattern2(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end);
+static bool cs_path_matches_pattern(const struct cs_path_info *filename,
+ const struct cs_path_info *pattern);
+static bool cs_path_matches_pattern2(const char *f, const char *p);
+static int cs_execute_path(struct linux_binprm *bprm, struct path *path);
+static int cs_execute(struct cs_request_info *r);
+static void cs_clear_request_info(struct cs_request_info *r);
+
+/* The list for ACL policy. */
+struct list_head cs_acl_list[CS_MAX_MAC_INDEX];
+
+/* NULL value. */
+struct cs_path_info cs_null_name;
+
+/**
+ * cs_check_entry - Do permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @ptr: Pointer to "struct cs_acl_info".
+ *
+ * Returns true on match, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_check_entry(struct cs_request_info *r,
+ const struct cs_acl_info *ptr)
+{
+ return !ptr->is_deleted && cs_condition(r, ptr->cond);
+}
+
+/**
+ * cs_check_acl_list - Do permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_check_acl_list(struct cs_request_info *r)
+{
+ struct cs_acl_info *ptr;
+ int error = 0;
+ struct list_head * const list = &cs_acl_list[r->type];
+
+ r->matched_acl = NULL;
+ list_for_each_entry_rcu(ptr, list, list) {
+ struct cs_acl_info *ptr2;
+
+ if (!cs_check_entry(r, ptr)) {
+ if (unlikely(r->failed_by_oom))
+ goto oom;
+ continue;
+ }
+ r->matched_acl = ptr;
+ r->result = CS_MATCHING_UNMATCHED;
+ list_for_each_entry_rcu(ptr2, &ptr->acl_info_list, list) {
+ if (!cs_check_entry(r, ptr2)) {
+ if (unlikely(r->failed_by_oom))
+ goto oom;
+ continue;
+ }
+ r->result = ptr2->is_deny ?
+ CS_MATCHING_DENIED : CS_MATCHING_ALLOWED;
+ break;
+ }
+ error = cs_audit_log(r);
+ /* Ignore out of memory during audit. */
+ r->failed_by_oom = false;
+ if (error)
+ break;
+ }
+ return error;
+oom:
+ /*
+ * If conditions could not be checked due to out of memory,
+ * reject the request with -ENOMEM, for we don't know whether
+ * there was a possibility of matching "deny" lines or not.
+ */
+ {
+ static unsigned long cs_last_oom;
+ unsigned long oom = get_seconds();
+
+ if (oom != cs_last_oom) {
+ cs_last_oom = oom;
+ printk(KERN_INFO "CaitSith: Rejecting access request due to out of memory.\n");
+ }
+ }
+ return -ENOMEM;
+}
+
+/**
+ * cs_check_acl - Do permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @clear: True to cleanup @r before return, false otherwise.
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_check_acl(struct cs_request_info *r, const bool clear)
+{
+ int error;
+ const int idx = cs_read_lock();
+
+ error = cs_check_acl_list(r);
+ cs_read_unlock(idx);
+ if (clear)
+ cs_clear_request_info(r);
+ return error;
+}
+
+/**
+ * cs_execute - Check permission for "execute".
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_execute(struct cs_request_info *r)
+{
+ int retval;
+
+ /* Get symlink's dentry/vfsmount. */
+ retval = cs_execute_path(r->bprm, &r->obj.path[1]);
+ if (retval < 0)
+ return retval;
+ cs_populate_patharg(r, false);
+ if (!r->param.s[1])
+ return -ENOMEM;
+
+ /* Check execute permission. */
+ r->type = CS_MAC_EXECUTE;
+ return cs_check_acl(r, false);
+}
+
+/**
+ * cs_start_execve - Prepare for execve() operation.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+int cs_start_execve(struct linux_binprm *bprm)
+{
+ int retval;
+ struct cs_request_info r = { };
+ int idx;
+
+ r.tmp = kzalloc(CS_EXEC_TMPSIZE, GFP_NOFS);
+ if (!r.tmp)
+ return -ENOMEM;
+ idx = cs_read_lock();
+ r.bprm = bprm;
+ r.obj.path[0] = bprm->file->f_path;
+ retval = cs_execute(&r);
+ cs_clear_request_info(&r);
+ /* Drop refcount obtained by cs_execute_path(). */
+ if (r.obj.path[1].dentry) {
+ path_put(&r.obj.path[1]);
+ r.obj.path[1].dentry = NULL;
+ }
+ cs_read_unlock(idx);
+ kfree(r.tmp);
+ return retval;
+}
+
+/**
+ * cs_execute_path - Get dentry/vfsmount of a program.
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ * @path: Pointer to "struct path".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_execute_path(struct linux_binprm *bprm, struct path *path)
+{
+ /*
+ * Follow symlinks if the requested pathname is on procfs, for
+ * /proc/\$/exe is meaningless.
+ */
+ const unsigned int follow =
+ (bprm->file->f_path.dentry->d_sb->s_magic == PROC_SUPER_MAGIC)
+ ? LOOKUP_FOLLOW : 0;
+
+ if (kern_path(bprm->filename, follow, path))
+ return -ENOENT;
+ return 0;
+}
+
+/**
+ * cs_manager - Check whether the current process is a policy manager.
+ *
+ * Returns true if the current process is permitted to modify policy
+ * via /sys/kernel/security/caitsith/ interface.
+ *
+ * Caller holds cs_read_lock().
+ */
+bool cs_manager(void)
+{
+ if (!cs_policy_loaded)
+ return true;
+ {
+ struct cs_request_info r = { };
+
+ r.type = CS_MAC_MODIFY_POLICY;
+ if (cs_check_acl(&r, true) == 0)
+ return true;
+ }
+ { /* Reduce error messages. */
+ static pid_t cs_last_pid;
+ const pid_t pid = current->pid;
+
+ if (cs_last_pid != pid) {
+ const char *exe = cs_get_exe();
+
+ printk(KERN_WARNING "'%s' (pid=%u) is not permitted to update policies.\n",
+ exe, pid);
+ cs_last_pid = pid;
+ kfree(exe);
+ }
+ }
+ return false;
+}
+
+/**
+ * cs_populate_patharg - Calculate pathname for permission check and audit logs.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @first: True for first pathname, false for second pathname.
+ *
+ * Returns nothing.
+ */
+void cs_populate_patharg(struct cs_request_info *r, const bool first)
+{
+ struct cs_path_info *buf = &r->obj.pathname[!first];
+ struct path *path = &r->obj.path[!first];
+
+ if (!buf->name && path->dentry) {
+ buf->name = cs_realpath(path);
+ /* Set OOM flag if failed. */
+ if (!buf->name) {
+ r->failed_by_oom = true;
+ return;
+ }
+ cs_fill_path_info(buf);
+ }
+ if (!r->param.s[!first] && buf->name)
+ r->param.s[!first] = buf;
+}
+
+/**
+ * cs_cond2arg - Assign values to condition variables.
+ *
+ * @arg: Pointer to "struct cs_cond_arg".
+ * @cmd: One of values in "enum cs_conditions_index".
+ * @condp: Pointer to "union cs_condition_element *".
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns true on success, false othwerwise.
+ *
+ * This function should not fail. But it can fail if (for example) out of
+ * memory has occurred while calculating cs_populate_patharg() or
+ * cs_get_exename().
+ */
+static bool cs_cond2arg(struct cs_cond_arg *arg,
+ const enum cs_conditions_index cmd,
+ const union cs_condition_element **condp,
+ struct cs_request_info *r)
+{
+ switch (cmd) {
+ case CS_COND_SARG0:
+ if (!r->param.s[0])
+ cs_populate_patharg(r, true);
+ arg->name = r->param.s[0];
+ break;
+ case CS_COND_SARG1:
+ if (!r->param.s[1])
+ cs_populate_patharg(r, false);
+ arg->name = r->param.s[1];
+ break;
+ case CS_IMM_NAME_ENTRY:
+ arg->name = (*condp)->path;
+ (*condp)++;
+ break;
+ case CS_SELF_EXE:
+ if (!r->exename.name) {
+ cs_get_exename(&r->exename);
+ /* Set OOM flag if failed. */
+ if (!r->exename.name)
+ r->failed_by_oom = true;
+ }
+ arg->name = &r->exename;
+ break;
+ default:
+ arg->name = NULL;
+ break;
+ }
+ if (!arg->name)
+ return false;
+ arg->type = CS_ARG_TYPE_NAME;
+ return true;
+}
+
+/**
+ * cs_condition - Check condition part.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ * @cond: Pointer to "struct cs_condition". Maybe NULL.
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_condition(struct cs_request_info *r,
+ const struct cs_condition *cond)
+{
+ const union cs_condition_element *condp;
+
+ if (!cond)
+ return true;
+ condp = (typeof(condp)) (cond + 1);
+ while ((void *) condp < (void *) ((u8 *) cond) + cond->size) {
+ struct cs_cond_arg left;
+ struct cs_cond_arg right;
+ const enum cs_conditions_index left_op = condp->left;
+ const enum cs_conditions_index right_op = condp->right;
+ const bool match = !condp->is_not;
+
+ condp++;
+ if (!cs_cond2arg(&left, left_op, &condp, r) ||
+ !cs_cond2arg(&right, right_op, &condp, r))
+ /*
+ * Something wrong (e.g. out of memory or invalid
+ * argument) occurred. We can't check permission.
+ */
+ return false;
+ if (left.type == CS_ARG_TYPE_NAME) {
+ if (right.type != CS_ARG_TYPE_NAME)
+ return false;
+ if (cs_path_matches_pattern
+ (left.name, right.name) == match)
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+/**
+ * cs_byte_range - Check whether the string is a \ooo style octal value.
+ *
+ * @str: Pointer to the string.
+ *
+ * Returns true if @str is a \ooo style octal value, false otherwise.
+ */
+static bool cs_byte_range(const char *str)
+{
+ return *str >= '0' && *str++ <= '3' &&
+ *str >= '0' && *str++ <= '7' &&
+ *str >= '0' && *str <= '7';
+}
+
+/**
+ * cs_alphabet_char - Check whether the character is an alphabet.
+ *
+ * @c: The character to check.
+ *
+ * Returns true if @c is an alphabet character, false otherwise.
+ */
+static bool cs_alphabet_char(const char c)
+{
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+/**
+ * cs_file_matches_pattern2 - Pattern matching without '/' character and "\-" pattern.
+ *
+ * @filename: The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern: The start of pattern to compare.
+ * @pattern_end: The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool cs_file_matches_pattern2(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ while (filename < filename_end && pattern < pattern_end) {
+ char c;
+
+ if (*pattern != '\\') {
+ if (*filename++ != *pattern++)
+ return false;
+ continue;
+ }
+ c = *filename;
+ pattern++;
+ switch (*pattern) {
+ int i;
+ int j;
+ case '?':
+ if (c == '/') {
+ return false;
+ } else if (c == '\\') {
+ if (cs_byte_range(filename + 1))
+ filename += 3;
+ else
+ return false;
+ }
+ break;
+ case '+':
+ if (!isdigit(c))
+ return false;
+ break;
+ case 'x':
+ if (!isxdigit(c))
+ return false;
+ break;
+ case 'a':
+ if (!cs_alphabet_char(c))
+ return false;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ if (c == '\\' && cs_byte_range(filename + 1)
+ && !strncmp(filename + 1, pattern, 3)) {
+ filename += 3;
+ pattern += 2;
+ break;
+ }
+ return false; /* Not matched. */
+ case '*':
+ case '@':
+ for (i = 0; i <= filename_end - filename; i++) {
+ if (cs_file_matches_pattern2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return true;
+ c = filename[i];
+ if (c == '.' && *pattern == '@')
+ break;
+ if (c != '\\')
+ continue;
+ if (cs_byte_range(filename + i + 1))
+ i += 3;
+ else
+ break; /* Bad pattern. */
+ }
+ return false; /* Not matched. */
+ default:
+ j = 0;
+ c = *pattern;
+ if (c == '$') {
+ while (isdigit(filename[j]))
+ j++;
+ } else if (c == 'X') {
+ while (isxdigit(filename[j]))
+ j++;
+ } else if (c == 'A') {
+ while (cs_alphabet_char(filename[j]))
+ j++;
+ }
+ for (i = 1; i <= j; i++) {
+ if (cs_file_matches_pattern2(filename + i,
+ filename_end,
+ pattern + 1,
+ pattern_end))
+ return true;
+ }
+ return false; /* Not matched or bad pattern. */
+ }
+ filename++;
+ pattern++;
+ }
+ /* Ignore trailing "\*" and "\@" in @pattern. */
+ while (*pattern == '\\' &&
+ (*(pattern + 1) == '*' || *(pattern + 1) == '@'))
+ pattern += 2;
+ return filename == filename_end && pattern == pattern_end;
+}
+
+/**
+ * cs_file_matches_pattern - Pattern matching without '/' character.
+ *
+ * @filename: The start of string to check.
+ * @filename_end: The end of string to check.
+ * @pattern: The start of pattern to compare.
+ * @pattern_end: The end of pattern to compare.
+ *
+ * Returns true if @filename matches @pattern, false otherwise.
+ */
+static bool cs_file_matches_pattern(const char *filename,
+ const char *filename_end,
+ const char *pattern,
+ const char *pattern_end)
+{
+ const char *pattern_start = pattern;
+ bool first = true;
+ bool result;
+
+ while (pattern < pattern_end - 1) {
+ /* Split at "\-" pattern. */
+ if (*pattern++ != '\\' || *pattern++ != '-')
+ continue;
+ result = cs_file_matches_pattern2(filename, filename_end,
+ pattern_start, pattern - 2);
+ if (first)
+ result = !result;
+ if (result)
+ return false;
+ first = false;
+ pattern_start = pattern;
+ }
+ result = cs_file_matches_pattern2(filename, filename_end,
+ pattern_start, pattern_end);
+ return first ? result : !result;
+}
+
+/**
+ * cs_path_matches_pattern2 - Do pathname pattern matching.
+ *
+ * @f: The start of string to check.
+ * @p: The start of pattern to compare.
+ *
+ * Returns true if @f matches @p, false otherwise.
+ */
+static bool cs_path_matches_pattern2(const char *f, const char *p)
+{
+ const char *f_delimiter;
+ const char *p_delimiter;
+
+ while (*f && *p) {
+ f_delimiter = strchr(f + 1, '/');
+ if (!f_delimiter)
+ f_delimiter = f + strlen(f);
+ p_delimiter = strchr(p + 1, '/');
+ if (!p_delimiter)
+ p_delimiter = p + strlen(p);
+ if (*p == '/' && *(p + 1) == '\\') {
+ if (*(p + 2) == '(') {
+ /* Check zero repetition. */
+ if (cs_path_matches_pattern2(f, p_delimiter))
+ return true;
+ /* Check one or more repetition. */
+ goto repetition;
+ }
+ if (*(p + 2) == '{')
+ goto repetition;
+ }
+ if ((*f == '/' || *p == '/') && *f++ != *p++)
+ return false;
+ if (!cs_file_matches_pattern(f, f_delimiter, p, p_delimiter))
+ return false;
+ f = f_delimiter;
+ p = p_delimiter;
+ }
+ /* Ignore trailing "\*" and "\@" in @pattern. */
+ while (*p == '\\' && (*(p + 1) == '*' || *(p + 1) == '@'))
+ p += 2;
+ return !*f && !*p;
+repetition:
+ do {
+ /* Compare current component with pattern. */
+ if (!cs_file_matches_pattern(f + 1, f_delimiter, p + 3,
+ p_delimiter - 2))
+ break;
+ /* Proceed to next component. */
+ f = f_delimiter;
+ if (!*f)
+ break;
+ /* Continue comparison. */
+ if (cs_path_matches_pattern2(f, p_delimiter))
+ return true;
+ f_delimiter = strchr(f + 1, '/');
+ } while (f_delimiter);
+ return false; /* Not matched. */
+}
+
+/**
+ * cs_path_matches_pattern - Check whether the given filename matches the given pattern.
+ *
+ * @filename: The filename to check.
+ * @pattern: The pattern to compare.
+ *
+ * Returns true if matches, false otherwise.
+ *
+ * The following patterns are available.
+ * \ooo Octal representation of a byte.
+ * \* Zero or more repetitions of characters other than '/'.
+ * \@ Zero or more repetitions of characters other than '/' or '.'.
+ * \? 1 byte character other than '/'.
+ * \$ One or more repetitions of decimal digits.
+ * \+ 1 decimal digit.
+ * \X One or more repetitions of hexadecimal digits.
+ * \x 1 hexadecimal digit.
+ * \A One or more repetitions of alphabet characters.
+ * \a 1 alphabet character.
+ *
+ * \- Subtraction operator.
+ *
+ * /\{dir\}/ '/' + 'One or more repetitions of dir/' (e.g. /dir/ /dir/dir/
+ * /dir/dir/dir/ ).
+ *
+ * /\(dir\)/ '/' + 'Zero or more repetitions of dir/' (e.g. / /dir/
+ * /dir/dir/ ).
+ */
+static bool cs_path_matches_pattern(const struct cs_path_info *filename,
+ const struct cs_path_info *pattern)
+{
+ const char *f = filename->name;
+ const char *p = pattern->name;
+ const int len = pattern->const_len;
+
+ /* If @pattern doesn't contain pattern, I can use strcmp(). */
+ if (len == pattern->total_len)
+ return !cs_pathcmp(filename, pattern);
+ /* Compare the initial length without patterns. */
+ if (len) {
+ if (strncmp(f, p, len))
+ return false;
+ f += len - 1;
+ p += len - 1;
+ }
+ return cs_path_matches_pattern2(f, p);
+}
+
+/**
+ * cs_clear_request_info - Release memory allocated during permission check.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns nothing.
+ */
+static void cs_clear_request_info(struct cs_request_info *r)
+{
+ u8 i;
+
+ /*
+ * r->obj.pathname[0] (which is referenced by r->obj.s[0]) and
+ * r->obj.pathname[1] (which is referenced by r->obj.s[1]) may contain
+ * pathnames allocated using cs_populate_patharg().
+ * Their callers do not allocate memory until pathnames becomes needed
+ * for checking condition.
+ */
+ for (i = 0; i < 2; i++) {
+ kfree(r->obj.pathname[i].name);
+ r->obj.pathname[i].name = NULL;
+ }
+ kfree(r->exename.name);
+ r->exename.name = NULL;
+}
--
1.8.3.1
This file corresponds to security/tomoyo/common.c . There are 4 major
policy files ("domain_policy", "exception_policy", "profile" and
"manager") in TOMOYO while there is only 1 major policy file ("policy")
in CaitSith.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/caitsith/policy_io.c | 1553 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 1553 insertions(+)
create mode 100644 security/caitsith/policy_io.c
diff --git a/security/caitsith/policy_io.c b/security/caitsith/policy_io.c
new file mode 100644
index 0000000..060845f
--- /dev/null
+++ b/security/caitsith/policy_io.c
@@ -0,0 +1,1553 @@
+/*
+ * security/caitsith/policy_io.c
+ *
+ * Copyright (C) 2005-2012 NTT DATA CORPORATION
+ */
+
+#include <linux/lsm_hooks.h>
+#include "caitsith.h"
+
+/* Define this to enable debug mode. */
+/* #define DEBUG_CONDITION */
+
+#ifdef DEBUG_CONDITION
+#define dprintk printk
+#else
+#define dprintk(...) do { } while (0)
+#endif
+
+/* String table for operation. */
+static const char * const cs_mac_keywords[CS_MAX_MAC_INDEX] = {
+ [CS_MAC_EXECUTE] = "execute",
+ [CS_MAC_MODIFY_POLICY] = "modify_policy",
+};
+
+/* String table for stat info. */
+static const char * const cs_memory_headers[CS_MAX_MEMORY_STAT] = {
+ [CS_MEMORY_POLICY] = "policy",
+};
+
+#define F(bit) (1U << bit)
+
+enum cs_var_type {
+ CS_TYPE_INVALID,
+ CS_TYPE_STRING,
+} __packed;
+
+/* String table for conditions. */
+static const struct {
+ const char * const keyword;
+ const enum cs_var_type left_type;
+ const enum cs_var_type right_type;
+ const enum cs_conditions_index cmd;
+ const u8 available;
+} cs_conditions[] = {
+ { "exec", CS_TYPE_STRING, CS_TYPE_STRING, CS_COND_SARG1,
+ F(CS_MAC_EXECUTE) },
+ { "path", CS_TYPE_STRING, CS_TYPE_STRING, CS_COND_SARG0,
+ F(CS_MAC_EXECUTE) },
+ { "task.exe", CS_TYPE_STRING, CS_TYPE_STRING, CS_SELF_EXE,
+ F(CS_MAC_EXECUTE) | F(CS_MAC_MODIFY_POLICY) },
+};
+
+/* Structure for holding single condition component. */
+struct cs_cond_tmp {
+ enum cs_conditions_index left;
+ enum cs_conditions_index right;
+ bool is_not;
+ enum cs_var_type type;
+ const struct cs_path_info *path;
+};
+
+static bool cs_correct_word(const char *string);
+static bool cs_flush(struct cs_io_buffer *head);
+static bool cs_print_condition(struct cs_io_buffer *head,
+ const struct cs_condition *cond);
+static bool cs_memory_ok(const void *ptr);
+static bool cs_read_acl(struct cs_io_buffer *head,
+ const struct cs_acl_info *acl);
+static bool cs_set_lf(struct cs_io_buffer *head);
+static char *cs_read_token(struct cs_io_buffer *head);
+static const struct cs_path_info *cs_get_dqword(char *start);
+static const struct cs_path_info *cs_get_name(const char *name);
+static int cs_open(struct inode *inode, struct file *file);
+static int cs_parse_policy(struct cs_io_buffer *head, char *line);
+static int cs_release(struct inode *inode, struct file *file);
+static int cs_write_policy(struct cs_io_buffer *head);
+static ssize_t cs_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos);
+static ssize_t cs_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos);
+static struct cs_condition *cs_get_condition(struct cs_io_buffer *head);
+static void __init cs_create_entry(const char *name, const umode_t mode,
+ struct dentry *parent, const u8 key);
+static void __init cs_load_builtin_policy(void);
+static int __init cs_securityfs_init(void);
+static void cs_convert_time(time_t time, struct cs_time *stamp);
+static void cs_io_printf(struct cs_io_buffer *head, const char *fmt, ...)
+ __printf(2, 3);
+static void cs_normalize_line(unsigned char *buffer);
+static void cs_read_policy(struct cs_io_buffer *head);
+static void *cs_commit_ok(void *data, const unsigned int size);
+static void cs_read_stat(struct cs_io_buffer *head);
+static void cs_read_version(struct cs_io_buffer *head);
+static void cs_set_space(struct cs_io_buffer *head);
+static void cs_set_string(struct cs_io_buffer *head, const char *string);
+static void cs_update_stat(const u8 index);
+
+/**
+ * cs_convert_time - Convert time_t to YYYY/MM/DD hh/mm/ss.
+ *
+ * @time: Seconds since 1970/01/01 00:00:00.
+ * @stamp: Pointer to "struct cs_time".
+ *
+ * Returns nothing.
+ *
+ * This function does not handle Y2038 problem.
+ */
+static void cs_convert_time(time_t time, struct cs_time *stamp)
+{
+ static const u16 cs_eom[2][12] = {
+ { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
+ { 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
+ };
+ u16 y;
+ u8 m;
+ bool r;
+
+ stamp->sec = time % 60;
+ time /= 60;
+ stamp->min = time % 60;
+ time /= 60;
+ stamp->hour = time % 24;
+ time /= 24;
+ for (y = 1970; ; y++) {
+ const unsigned short days = (y & 3) ? 365 : 366;
+
+ if (time < days)
+ break;
+ time -= days;
+ }
+ r = (y & 3) == 0;
+ for (m = 0; m < 11 && time >= cs_eom[r][m]; m++)
+ ;
+ if (m)
+ time -= cs_eom[r][m - 1];
+ stamp->year = y;
+ stamp->month = ++m;
+ stamp->day = ++time;
+}
+
+/* Lock for protecting policy. */
+DEFINE_MUTEX(cs_policy_lock);
+
+/* Has /sbin/init started? */
+bool cs_policy_loaded;
+
+/* Policy version. Currently only 20120401 is defined. */
+static unsigned int cs_policy_version;
+
+/* List of "struct cs_condition". */
+LIST_HEAD(cs_condition_list);
+
+/* Memoy currently used by policy. */
+unsigned int cs_memory_used[CS_MAX_MEMORY_STAT];
+
+/* The list for "struct cs_name". */
+struct list_head cs_name_list[CS_MAX_HASH];
+
+/* Timestamp counter for last updated. */
+static unsigned int cs_stat_updated[CS_MAX_POLICY_STAT];
+
+/* Counter for number of updates. */
+static unsigned int cs_stat_modified[CS_MAX_POLICY_STAT];
+
+/* Operations for /sys/kernel/security/caitsith/ interface. */
+static const struct file_operations cs_operations = {
+ .open = cs_open,
+ .release = cs_release,
+ .read = cs_read,
+ .write = cs_write,
+};
+
+/**
+ * list_for_each_cookie - iterate over a list with cookie.
+ *
+ * @pos: Pointer to "struct list_head".
+ * @head: Pointer to "struct list_head".
+ */
+#define list_for_each_cookie(pos, head) \
+ for (pos = pos ? pos : srcu_dereference((head)->next, &cs_ss); \
+ pos != (head); pos = srcu_dereference(pos->next, &cs_ss))
+
+/**
+ * cs_warn_oom - Print out of memory warning message.
+ *
+ * @function: Function's name.
+ *
+ * Returns nothing.
+ */
+void cs_warn_oom(const char *function)
+{
+ /* Reduce error messages. */
+ static pid_t cs_last_pid;
+ const pid_t pid = current->pid;
+
+ if (cs_last_pid != pid) {
+ printk(KERN_WARNING "ERROR: Out of memory at %s.\n",
+ function);
+ cs_last_pid = pid;
+ }
+ if (!cs_policy_loaded)
+ panic("MAC Initialization failed.\n");
+}
+
+/**
+ * cs_memory_ok - Check memory quota.
+ *
+ * @ptr: Pointer to allocated memory. Maybe NULL.
+ *
+ * Returns true if @ptr is not NULL and quota not exceeded, false otherwise.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static bool cs_memory_ok(const void *ptr)
+{
+ if (ptr) {
+ cs_memory_used[CS_MEMORY_POLICY] += ksize(ptr);
+ return true;
+ }
+ cs_warn_oom(__func__);
+ return false;
+}
+
+/**
+ * cs_get_name - Allocate memory for string data.
+ *
+ * @name: The string to store into the permernent memory. Maybe NULL.
+ *
+ * Returns pointer to "struct cs_path_info" on success, NULL otherwise.
+ */
+static const struct cs_path_info *cs_get_name(const char *name)
+{
+ struct cs_name *ptr;
+ unsigned int hash;
+ int len;
+ int allocated_len;
+ struct list_head *head;
+
+ if (!name)
+ return NULL;
+ len = strlen(name) + 1;
+ hash = full_name_hash(NULL, name, len - 1);
+ head = &cs_name_list[hash_long(hash, CS_HASH_BITS)];
+ if (mutex_lock_interruptible(&cs_policy_lock))
+ return NULL;
+ list_for_each_entry(ptr, head, head.list) {
+ if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name) ||
+ atomic_read(&ptr->head.users) == CS_GC_IN_PROGRESS)
+ continue;
+ atomic_inc(&ptr->head.users);
+ goto out;
+ }
+ allocated_len = sizeof(*ptr) + len;
+ ptr = kzalloc(allocated_len, GFP_NOFS);
+ if (cs_memory_ok(ptr)) {
+ ptr->entry.name = ((char *) ptr) + sizeof(*ptr);
+ memmove((char *) ptr->entry.name, name, len);
+ atomic_set(&ptr->head.users, 1);
+ cs_fill_path_info(&ptr->entry);
+ ptr->size = allocated_len;
+ list_add_tail(&ptr->head.list, head);
+ } else {
+ kfree(ptr);
+ ptr = NULL;
+ }
+out:
+ mutex_unlock(&cs_policy_lock);
+ return ptr ? &ptr->entry : NULL;
+}
+
+/**
+ * cs_read_token - Read a word from a line.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns a word on success, "" otherwise.
+ *
+ * To allow the caller to skip NULL check, this function returns "" rather than
+ * NULL if there is no more words to read.
+ */
+static char *cs_read_token(struct cs_io_buffer *head)
+{
+ char *pos = head->w.data;
+ char *del = strchr(pos, ' ');
+
+ if (del)
+ *del++ = '\0';
+ else
+ del = pos + strlen(pos);
+ head->w.data = del;
+ return pos;
+}
+
+/**
+ * cs_correct_word - Check whether the given string follows the naming rules.
+ *
+ * @string: The string to check.
+ *
+ * Returns true if @string follows the naming rules, false otherwise.
+ */
+static bool cs_correct_word(const char *string)
+{
+ u8 recursion = 20;
+ const char *const start = string;
+ u8 in_repetition = 0;
+
+ if (!*string)
+ goto out;
+ while (*string) {
+ unsigned char c = *string++;
+
+ if (in_repetition && c == '/')
+ goto out;
+ if (c <= ' ' || c >= 127)
+ goto out;
+ if (c != '\\')
+ continue;
+ c = *string++;
+ if (c >= '0' && c <= '3') {
+ unsigned char d;
+ unsigned char e;
+
+ d = *string++;
+ if (d < '0' || d > '7')
+ goto out;
+ e = *string++;
+ if (e < '0' || e > '7')
+ goto out;
+ c = ((c - '0') << 6) + ((d - '0') << 3) + (e - '0');
+ if (c <= ' ' || c >= 127 || c == '\\')
+ continue;
+ goto out;
+ }
+ switch (c) {
+ case '+': /* "\+" */
+ case '?': /* "\?" */
+ case 'x': /* "\x" */
+ case 'a': /* "\a" */
+ case '-': /* "\-" */
+ continue;
+ }
+ /* Reject too deep wildcard that consumes too much stack. */
+ if (!recursion--)
+ goto out;
+ switch (c) {
+ case '*': /* "\*" */
+ case '@': /* "\@" */
+ case '$': /* "\$" */
+ case 'X': /* "\X" */
+ case 'A': /* "\A" */
+ continue;
+ case '{': /* "/\{" */
+ if (string - 3 < start || *(string - 3) != '/')
+ goto out;
+ in_repetition = 1;
+ continue;
+ case '}': /* "\}/" */
+ if (in_repetition != 1 || *string++ != '/')
+ goto out;
+ in_repetition = 0;
+ continue;
+ case '(': /* "/\(" */
+ if (string - 3 < start || *(string - 3) != '/')
+ goto out;
+ in_repetition = 2;
+ continue;
+ case ')': /* "\)/" */
+ if (in_repetition != 2 || *string++ != '/')
+ goto out;
+ in_repetition = 0;
+ continue;
+ }
+ goto out;
+ }
+ if (in_repetition)
+ goto out;
+ return true;
+out:
+ return false;
+}
+
+/**
+ * cs_commit_ok - Allocate memory and check memory quota.
+ *
+ * @data: Data to copy from.
+ * @size: Size in byte.
+ *
+ * Returns pointer to allocated memory on success, NULL otherwise.
+ * @data is zero-cleared on success.
+ *
+ * Caller holds cs_policy_lock mutex.
+ */
+static void *cs_commit_ok(void *data, const unsigned int size)
+{
+ void *ptr = kmalloc(size, GFP_NOFS);
+
+ if (cs_memory_ok(ptr)) {
+ memmove(ptr, data, size);
+ memset(data, 0, size);
+ return ptr;
+ }
+ kfree(ptr);
+ return NULL;
+}
+
+/**
+ * cs_get_dqword - cs_get_name() for a quoted string.
+ *
+ * @start: String to parse.
+ *
+ * Returns pointer to "struct cs_path_info" on success, NULL otherwise.
+ */
+static const struct cs_path_info *cs_get_dqword(char *start)
+{
+ char *cp = start + strlen(start) - 1;
+
+ if (cp == start || *start++ != '"' || *cp != '"')
+ return NULL;
+ *cp = '\0';
+ if (*start && !cs_correct_word(start))
+ return NULL;
+ return cs_get_name(start);
+}
+
+/**
+ * cs_same_condition - Check for duplicated "struct cs_condition" entry.
+ *
+ * @a: Pointer to "struct cs_condition".
+ * @b: Pointer to "struct cs_condition".
+ *
+ * Returns true if @a == @b, false otherwise.
+ */
+static inline bool cs_same_condition(const struct cs_condition *a,
+ const struct cs_condition *b)
+{
+ return a->size == b->size &&
+ !memcmp(a + 1, b + 1, a->size - sizeof(*a));
+}
+
+/**
+ * cs_commit_condition - Commit "struct cs_condition".
+ *
+ * @entry: Pointer to "struct cs_condition".
+ *
+ * Returns pointer to "struct cs_condition" on success, NULL otherwise.
+ *
+ * This function merges duplicated entries. This function returns NULL if
+ * @entry is not duplicated but memory quota for policy has exceeded.
+ */
+static struct cs_condition *cs_commit_condition(struct cs_condition *entry)
+{
+ struct cs_condition *ptr = kmemdup(entry, entry->size, GFP_NOFS);
+ bool found = false;
+
+ if (ptr) {
+ kfree(entry);
+ entry = ptr;
+ }
+ if (mutex_lock_interruptible(&cs_policy_lock)) {
+ dprintk(KERN_WARNING "%u: %s failed\n", __LINE__, __func__);
+ ptr = NULL;
+ found = true;
+ goto out;
+ }
+ list_for_each_entry(ptr, &cs_condition_list, head.list) {
+ if (!cs_same_condition(ptr, entry) ||
+ atomic_read(&ptr->head.users) == CS_GC_IN_PROGRESS)
+ continue;
+ /* Same entry found. Share this entry. */
+ atomic_inc(&ptr->head.users);
+ found = true;
+ break;
+ }
+ if (!found) {
+ if (cs_memory_ok(entry)) {
+ atomic_set(&entry->head.users, 1);
+ list_add(&entry->head.list, &cs_condition_list);
+ } else {
+ found = true;
+ ptr = NULL;
+ }
+ }
+ mutex_unlock(&cs_policy_lock);
+out:
+ if (found) {
+ cs_del_condition(&entry->head.list);
+ kfree(entry);
+ entry = ptr;
+ }
+ return entry;
+}
+
+/**
+ * cs_normalize_line - Format string.
+ *
+ * @buffer: The line to normalize.
+ *
+ * Returns nothing.
+ *
+ * Leading and trailing whitespaces are removed.
+ * Multiple whitespaces are packed into single space.
+ */
+static void cs_normalize_line(unsigned char *buffer)
+{
+ unsigned char *sp = buffer;
+ unsigned char *dp = buffer;
+ bool first = true;
+
+ while (*sp && (*sp <= ' ' || *sp >= 127))
+ sp++;
+ while (*sp) {
+ if (!first)
+ *dp++ = ' ';
+ first = false;
+ while (*sp > ' ' && *sp < 127)
+ *dp++ = *sp++;
+ while (*sp && (*sp <= ' ' || *sp >= 127))
+ sp++;
+ }
+ *dp = '\0';
+}
+
+/**
+ * cs_parse_righthand - Parse special righthand conditions.
+ *
+ * @word: Keyword to search.
+ * @head: Pointer to "struct cs_io_buffer".
+ * @tmp: Pointer to "struct cs_cond_tmp".
+ *
+ * Returns one of values in "enum cs_conditions_index".
+ */
+static enum cs_conditions_index cs_parse_righthand
+(char *word, struct cs_io_buffer *head, struct cs_cond_tmp *tmp)
+{
+ const enum cs_var_type type = tmp->type;
+
+ dprintk(KERN_WARNING "%u: tmp->left=%u type=%u\n",
+ __LINE__, tmp->left, type);
+ if (type == CS_TYPE_STRING) {
+ dprintk(KERN_WARNING "%u: word='%s'\n", __LINE__, word);
+ if (!strcmp(word, "NULL"))
+ goto null_word;
+ tmp->path = cs_get_dqword(word);
+ dprintk(KERN_WARNING "%u: tmp->path=%p\n", __LINE__,
+ tmp->path);
+ if (tmp->path)
+ return CS_IMM_NAME_ENTRY;
+ goto out;
+ }
+out:
+ dprintk(KERN_WARNING "%u: righthand failed\n", __LINE__);
+ return CS_INVALID_CONDITION;
+null_word:
+ tmp->path = &cs_null_name;
+ return CS_IMM_NAME_ENTRY;
+}
+
+/**
+ * cs_condindex - Get condition's index.
+ *
+ * @word: Name of condition.
+ * @mac: One of values in "enum cs_mac_index".
+ * @tmp: Pointer to "struct cs_cond_tmp".
+ * @left: True if lefthand part, false otherwise.
+ *
+ * Returns one of values in "enum cs_condition_index".
+ */
+static enum cs_conditions_index cs_condindex(const char *word,
+ const enum cs_mac_index mac,
+ struct cs_cond_tmp *tmp,
+ const bool lefthand)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cs_conditions); i++) {
+ if (!(cs_conditions[i].available & F(mac)) ||
+ strcmp(cs_conditions[i].keyword, word))
+ continue;
+ tmp->type = lefthand ? cs_conditions[i].left_type :
+ cs_conditions[i].right_type;
+ if (tmp->type != CS_TYPE_INVALID)
+ return cs_conditions[i].cmd;
+ break;
+ }
+ return CS_INVALID_CONDITION;
+}
+
+/**
+ * cs_parse_cond - Parse single condition.
+ *
+ * @tmp: Pointer to "struct cs_cond_tmp".
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_parse_cond(struct cs_cond_tmp *tmp,
+ struct cs_io_buffer *head)
+{
+ char *left = head->w.data;
+ char *right;
+ const enum cs_mac_index mac = head->w.acl_index;
+ enum cs_var_type type = CS_TYPE_STRING;
+
+ dprintk(KERN_WARNING "%u: type=%u word='%s'\n",
+ __LINE__, mac, left);
+ right = strchr(left, '=');
+ if (!right || right == left)
+ return false;
+ *right++ = '\0';
+ tmp->is_not = (*(right - 2) == '!');
+ if (tmp->is_not)
+ *(right - 2) = '\0';
+ if (!*left || !*right)
+ return false;
+ tmp->left = cs_condindex(left, mac, tmp, true);
+ dprintk(KERN_WARNING "%u: tmp->left=%u\n", __LINE__, tmp->left);
+ if (tmp->left == CS_INVALID_CONDITION)
+ return false;
+ type = tmp->type;
+ dprintk(KERN_WARNING "%u: tmp->type=%u\n", __LINE__, tmp->type);
+ tmp->right = cs_condindex(right, mac, tmp, false);
+ dprintk(KERN_WARNING "%u: tmp->right=%u tmp->type=%u\n",
+ __LINE__, tmp->right, tmp->type);
+ if (tmp->right != CS_INVALID_CONDITION && type != tmp->type)
+ return false;
+ if (tmp->right == CS_INVALID_CONDITION)
+ tmp->right = cs_parse_righthand(right, head, tmp);
+ dprintk(KERN_WARNING "%u: tmp->right=%u tmp->type=%u\n",
+ __LINE__, tmp->right, tmp->type);
+ return tmp->right != CS_INVALID_CONDITION;
+}
+
+/**
+ * cs_get_condition - Parse condition part.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns pointer to "struct cs_condition" on success, NULL otherwise.
+ */
+struct cs_condition *cs_get_condition(struct cs_io_buffer *head)
+{
+ struct cs_condition *entry = kzalloc(PAGE_SIZE, GFP_NOFS);
+ union cs_condition_element *condp;
+ struct cs_cond_tmp tmp;
+#ifdef DEBUG_CONDITION
+ const enum cs_mac_index type = head->w.acl_index;
+#endif
+ char *pos = head->w.data;
+
+ if (!entry)
+ return NULL;
+ condp = (union cs_condition_element *) (entry + 1);
+ while (1) {
+ memset(&tmp, 0, sizeof(tmp));
+ while (*pos == ' ')
+ pos++;
+ if (!*pos)
+ break;
+ if ((u8 *) condp >= ((u8 *) entry) + PAGE_SIZE
+ - (sizeof(*condp) * 2))
+ goto out;
+ {
+ char *next = strchr(pos, ' ');
+
+ if (next)
+ *next++ = '\0';
+ else
+ next = "";
+ head->w.data = pos;
+ pos = next;
+ }
+ if (!cs_parse_cond(&tmp, head))
+ goto out;
+ condp->is_not = tmp.is_not;
+ condp->left = tmp.left;
+ condp->right = tmp.right;
+ condp++;
+ if (tmp.right == CS_IMM_NAME_ENTRY) {
+ condp->path = tmp.path;
+ condp++;
+ }
+ }
+ entry->size = (void *) condp - (void *) entry;
+ return cs_commit_condition(entry);
+out:
+ dprintk(KERN_WARNING "%u: bad condition: type=%u path='%s'\n",
+ __LINE__, type, tmp.path ? tmp.path->name : "");
+ if (tmp.path != &cs_null_name)
+ cs_put_name(tmp.path);
+ entry->size = (void *) condp - (void *) entry;
+ cs_del_condition(&entry->head.list);
+ kfree(entry);
+ return NULL;
+}
+
+/**
+ * cs_flush - Flush queued string to userspace's buffer.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true if all data was flushed, false otherwise.
+ */
+static bool cs_flush(struct cs_io_buffer *head)
+{
+ while (head->r.w_pos) {
+ const char *w = head->r.w[0];
+ size_t len = strlen(w);
+
+ if (len) {
+ if (len > head->read_user_buf_avail)
+ len = head->read_user_buf_avail;
+ if (!len)
+ return false;
+ if (copy_to_user(head->read_user_buf, w, len))
+ return false;
+ head->read_user_buf_avail -= len;
+ head->read_user_buf += len;
+ w += len;
+ }
+ head->r.w[0] = w;
+ if (*w)
+ return false;
+ head->r.w_pos--;
+ for (len = 0; len < head->r.w_pos; len++)
+ head->r.w[len] = head->r.w[len + 1];
+ }
+ head->r.avail = 0;
+ return true;
+}
+
+/**
+ * cs_set_string - Queue string to "struct cs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @string: String to print.
+ *
+ * Returns nothing.
+ *
+ * Note that @string has to be kept valid until @head is kfree()d.
+ * This means that char[] allocated on stack memory cannot be passed to
+ * this function. Use cs_io_printf() for char[] allocated on stack memory.
+ */
+static void cs_set_string(struct cs_io_buffer *head, const char *string)
+{
+ if (head->r.w_pos < CS_MAX_IO_READ_QUEUE) {
+ head->r.w[head->r.w_pos++] = string;
+ cs_flush(head);
+ } else
+ printk(KERN_WARNING "Too many words in a line.\n");
+}
+
+/**
+ * cs_io_printf - printf() to "struct cs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @fmt: The printf()'s format string, followed by parameters.
+ *
+ * Returns nothing.
+ */
+static void cs_io_printf(struct cs_io_buffer *head, const char *fmt, ...)
+{
+ va_list args;
+ size_t len;
+ size_t pos = head->r.avail;
+ int size = head->readbuf_size - pos;
+
+ if (size <= 0)
+ return;
+ va_start(args, fmt);
+ len = vsnprintf(head->read_buf + pos, size, fmt, args) + 1;
+ va_end(args);
+ if (pos + len >= head->readbuf_size) {
+ printk(KERN_WARNING "Too many words in a line.\n");
+ return;
+ }
+ head->r.avail += len;
+ cs_set_string(head, head->read_buf + pos);
+}
+
+/**
+ * cs_set_space - Put a space to "struct cs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_set_space(struct cs_io_buffer *head)
+{
+ cs_set_string(head, " ");
+}
+
+/**
+ * cs_set_lf - Put a line feed to "struct cs_io_buffer" structure.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns true if all data was flushed, false otherwise.
+ */
+static bool cs_set_lf(struct cs_io_buffer *head)
+{
+ cs_set_string(head, "\n");
+ return !head->r.w_pos;
+}
+
+/**
+ * cs_check_profile - Check policy is loaded.
+ *
+ * Returns nothing.
+ */
+void cs_check_profile(void)
+{
+ cs_policy_loaded = true;
+ printk(KERN_INFO "CaitSith 2016/09/25\n");
+ if (cs_policy_version == 20120401) {
+ printk(KERN_INFO "CaitSith module activated.\n");
+ return;
+ }
+ printk(KERN_ERR "Policy version %u is not supported.\n",
+ cs_policy_version);
+ printk(KERN_ERR "Userland tools for CaitSith must be installed and policy must be initialized.\n");
+ printk(KERN_ERR "Please see https://caitsith.osdn.jp/ for more information.\n");
+ panic("STOP!");
+}
+
+/**
+ * cs_update_acl - Update "struct cs_acl_info" entry.
+ *
+ * @list: Pointer to "struct list_head".
+ * @head: Pointer to "struct cs_io_buffer".
+ * @update: True to store matching entry, false otherwise.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_update_acl(struct list_head * const list,
+ struct cs_io_buffer *head, const bool update)
+{
+ struct cs_acl_info *ptr;
+ struct cs_acl_info new_entry = { };
+ const bool is_delete = head->w.is_delete;
+ int error = is_delete ? -ENOENT : -ENOMEM;
+
+ new_entry.priority = head->w.priority;
+ new_entry.is_deny = head->w.is_deny;
+ if (head->w.data[0]) {
+ new_entry.cond = cs_get_condition(head);
+ if (!new_entry.cond)
+ return -EINVAL;
+ }
+ if (mutex_lock_interruptible(&cs_policy_lock))
+ goto out;
+ list_for_each_entry_rcu(ptr, list, list) {
+ if (ptr->priority > new_entry.priority)
+ break;
+ /*
+ * We cannot reuse deleted "struct cs_acl_info" entry because
+ * somebody might be referencing children of this deleted entry
+ * from srcu section. We cannot delete children of this deleted
+ * entry until all children are no longer referenced. Thus, let
+ * the garbage collector wait and delete rather than trying to
+ * reuse this deleted entry.
+ */
+ if (ptr->is_deleted || ptr->cond != new_entry.cond ||
+ ptr->priority != new_entry.priority ||
+ ptr->is_deny != new_entry.is_deny)
+ continue;
+ ptr->is_deleted = is_delete;
+ if (!is_delete && update)
+ head->w.acl = ptr;
+ error = 0;
+ break;
+ }
+ if (error && !is_delete) {
+ struct cs_acl_info *entry =
+ cs_commit_ok(&new_entry, sizeof(new_entry));
+
+ if (entry) {
+ INIT_LIST_HEAD(&entry->acl_info_list);
+ list_add_tail_rcu(&entry->list, &ptr->list);
+ if (update)
+ head->w.acl = entry;
+ }
+ }
+ mutex_unlock(&cs_policy_lock);
+out:
+ cs_put_condition(new_entry.cond);
+ return error;
+}
+
+/**
+ * cs_parse_entry - Update ACL entry.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_parse_entry(struct cs_io_buffer *head)
+{
+ enum cs_mac_index type;
+ const char *operation = cs_read_token(head);
+
+ for (type = 0; type < CS_MAX_MAC_INDEX; type++) {
+ if (strcmp(operation, cs_mac_keywords[type]))
+ continue;
+ head->w.acl_index = type;
+ return cs_update_acl(&cs_acl_list[type], head, true);
+ }
+ return -EINVAL;
+}
+
+/**
+ * cs_condword - Get condition's name.
+ *
+ * @type: One of values in "enum cs_mac_index".
+ * @cond: One of values in "enum cs_condition_index".
+ *
+ * Returns condition's name.
+ */
+static const char *cs_condword(const enum cs_mac_index type,
+ const enum cs_conditions_index cond)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cs_conditions); i++) {
+ if (!(cs_conditions[i].available & F(type)) ||
+ cs_conditions[i].cmd != cond)
+ continue;
+ return cs_conditions[i].keyword;
+ }
+ return "unknown"; /* This should not happen. */
+}
+
+/**
+ * cs_print_condition_loop - Print condition part.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @cond: Pointer to "struct cs_condition".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_print_condition_loop(struct cs_io_buffer *head,
+ const struct cs_condition *cond)
+{
+ const enum cs_mac_index type = head->r.acl_index;
+ const union cs_condition_element *condp = head->r.cond;
+
+ while ((void *) condp < (void *) ((u8 *) cond) + cond->size) {
+ const bool is_not = condp->is_not;
+ const enum cs_conditions_index left = condp->left;
+ const enum cs_conditions_index right = condp->right;
+
+ if (!cs_flush(head)) {
+ head->r.cond = condp;
+ return false;
+ }
+ condp++;
+ cs_set_space(head);
+ cs_set_string(head, cs_condword(type, left));
+ cs_set_string(head, is_not ? "!=" : "=");
+ switch (right) {
+ case CS_IMM_NAME_ENTRY:
+ if (condp->path != &cs_null_name) {
+ cs_set_string(head, "\"");
+ cs_set_string(head, condp->path->name);
+ cs_set_string(head, "\"");
+ } else {
+ cs_set_string(head, "NULL");
+ }
+ condp++;
+ break;
+ default:
+ cs_set_string(head, cs_condword(type, right));
+ }
+ }
+ head->r.cond = NULL;
+ return true;
+}
+
+/**
+ * cs_print_condition - Print condition part.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @cond: Pointer to "struct cs_condition".
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_print_condition(struct cs_io_buffer *head,
+ const struct cs_condition *cond)
+{
+ switch (head->r.cond_step) {
+ case 0:
+ head->r.cond = (const union cs_condition_element *)
+ (cond + 1);
+ head->r.cond_step++;
+ /* fall through */
+ case 1:
+ if (!cs_print_condition_loop(head, cond))
+ return false;
+ head->r.cond_step++;
+ /* fall through */
+ case 2:
+ head->r.cond = NULL;
+ return true;
+ }
+ return false;
+}
+
+/**
+ * cs_read_acl - Print an ACL entry.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @acl: Pointer to an ACL entry.
+ *
+ * Returns true on success, false otherwise.
+ */
+static bool cs_read_acl(struct cs_io_buffer *head,
+ const struct cs_acl_info *acl)
+{
+ const enum cs_mac_index type = head->r.acl_index;
+
+ if (head->r.cond)
+ goto print_cond_part;
+ if (acl->is_deleted)
+ return true;
+ if (!cs_flush(head))
+ return false;
+ cs_io_printf(head, "%u ", acl->priority);
+ cs_set_string(head, "acl ");
+ cs_set_string(head, cs_mac_keywords[type]);
+ if (acl->cond) {
+ head->r.cond_step = 0;
+print_cond_part:
+ if (!cs_print_condition(head, acl->cond))
+ return false;
+ }
+ cs_set_lf(head);
+ return true;
+}
+
+/**
+ * cs_write_policy - Write policy.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_write_policy(struct cs_io_buffer *head)
+{
+ unsigned int priority;
+ char *word = cs_read_token(head);
+
+ if (sscanf(word, "%u", &priority) == 1)
+ word = cs_read_token(head);
+ else
+ priority = 1000;
+ if (priority >= 65536 || !*word)
+ return -EINVAL;
+ head->w.priority = priority;
+ if (!head->w.acl)
+ goto no_acl_selected;
+ head->w.is_deny = !strcmp(word, "deny");
+ if (head->w.is_deny || !strcmp(word, "allow"))
+ return cs_update_acl(&head->w.acl->acl_info_list, head,
+ false);
+ head->w.acl = NULL;
+no_acl_selected:
+ if (!strcmp(word, "acl"))
+ return cs_parse_entry(head);
+ if (sscanf(word, "POLICY_VERSION=%u", &cs_policy_version) == 1)
+ return 0;
+ return -EINVAL;
+}
+
+/**
+ * cs_audit_log - Audit permission check log.
+ *
+ * @r: Pointer to "struct cs_request_info".
+ *
+ * Returns 0 to grant the request, CS_RETRY_REQUEST to retry the permission
+ * check, -EPERM otherwise.
+ */
+int cs_audit_log(struct cs_request_info *r)
+{
+ /* Do not reject if not yet activated. */
+ if (!cs_policy_loaded)
+ return 0;
+ /* Nothing more to do unless denied. */
+ if (r->result != CS_MATCHING_DENIED)
+ return 0;
+ /* Update policy violation counter if denied. */
+ cs_update_stat(CS_STAT_REQUEST_DENIED);
+ return -EPERM;
+}
+
+/**
+ * cs_read_version - Get version.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_read_version(struct cs_io_buffer *head)
+{
+ if (head->r.eof)
+ return;
+ cs_set_string(head, "2016/09/25");
+ head->r.eof = true;
+}
+
+/**
+ * cs_update_stat - Update statistic counters.
+ *
+ * @index: Index for policy type.
+ *
+ * Returns nothing.
+ */
+static void cs_update_stat(const u8 index)
+{
+ /*
+ * I don't use atomic operations because race condition is not fatal.
+ */
+ cs_stat_updated[index]++;
+ cs_stat_modified[index] = get_seconds();
+}
+
+/**
+ * cs_read_stat - Read statistic data.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Returns nothing.
+ */
+static void cs_read_stat(struct cs_io_buffer *head)
+{
+ u8 i;
+
+ for (i = 0; i < CS_MAX_POLICY_STAT; i++) {
+ static const char * const k[CS_MAX_POLICY_STAT] = {
+ [CS_STAT_POLICY_UPDATES] = "Policy updated:",
+ [CS_STAT_REQUEST_DENIED] = "Requests denied:",
+ };
+
+ cs_io_printf(head, "stat %s %u", k[i], cs_stat_updated[i]);
+ if (cs_stat_modified[i]) {
+ struct cs_time stamp;
+
+ cs_convert_time(cs_stat_modified[i], &stamp);
+ cs_io_printf(head,
+ " (Last: %04u/%02u/%02u %02u:%02u:%02u)",
+ stamp.year, stamp.month, stamp.day,
+ stamp.hour, stamp.min, stamp.sec);
+ }
+ cs_set_lf(head);
+ }
+ for (i = 0; i < CS_MAX_MEMORY_STAT; i++)
+ cs_io_printf(head, "stat Memory used by %s: %u\n",
+ cs_memory_headers[i], cs_memory_used[i]);
+}
+
+/**
+ * cs_parse_policy - Parse a policy line.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @line: Line to parse.
+ *
+ * Returns 0 on success, negative value otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static int cs_parse_policy(struct cs_io_buffer *head, char *line)
+{
+ /* Set current line's content. */
+ head->w.data = line;
+ head->w.is_deny = false;
+ head->w.priority = 0;
+ /* Delete request? */
+ head->w.is_delete = !strncmp(line, "delete ", 7);
+ if (head->w.is_delete)
+ memmove(line, line + 7, strlen(line + 7) + 1);
+ /* Do the update. */
+ return cs_write_policy(head);
+}
+
+/**
+ * cs_load_builtin_policy - Load built-in policy.
+ *
+ * Returns nothing.
+ */
+static void __init cs_load_builtin_policy(void)
+{
+ /*
+ * This include file is manually created and contains built-in policy.
+ *
+ * static char [] __initdata cs_builtin_policy = { ... };
+ */
+#include "builtin-policy.h"
+ const int idx = cs_read_lock();
+ struct cs_io_buffer head = { };
+ char *start = cs_builtin_policy;
+
+ head.type = CS_POLICY;
+ while (1) {
+ char *end = strchr(start, '\n');
+
+ if (!end)
+ break;
+ *end = '\0';
+ cs_normalize_line(start);
+ head.write_buf = start;
+ cs_parse_policy(&head, start);
+ start = end + 1;
+ }
+ cs_read_unlock(idx);
+#ifdef CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+ cs_check_profile();
+#endif
+}
+
+/**
+ * cs_read_subacl - Read sub ACL in ACL entry.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ * @list: Pointer to "struct list_head".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * Caller holds cs_read_lock().
+ */
+static bool cs_read_subacl(struct cs_io_buffer *head,
+ const struct list_head *list)
+{
+ list_for_each_cookie(head->r.subacl, list) {
+ struct cs_acl_info *acl =
+ list_entry(head->r.subacl, typeof(*acl), list);
+
+ switch (head->r.step) {
+ case 3:
+ if (acl->is_deleted)
+ continue;
+ if (!cs_flush(head))
+ return false;
+ cs_io_printf(head, " %u ", acl->priority);
+ if (acl->is_deny)
+ cs_set_string(head, "deny");
+ else
+ cs_set_string(head, "allow");
+ head->r.cond_step = 0;
+ head->r.step++;
+ /* fall through */
+ case 4:
+ if (!cs_flush(head))
+ return false;
+ if (acl->cond &&
+ !cs_print_condition(head, acl->cond))
+ return false;
+ cs_set_lf(head);
+ head->r.step--;
+ }
+ }
+ head->r.subacl = NULL;
+ return true;
+}
+
+/**
+ * cs_read_policy - Read policy.
+ *
+ * @head: Pointer to "struct cs_io_buffer".
+ *
+ * Caller holds cs_read_lock().
+ */
+static void cs_read_policy(struct cs_io_buffer *head)
+{
+ if (head->r.eof)
+ return;
+ if (!head->r.version_done) {
+ cs_io_printf(head, "POLICY_VERSION=%u\n", cs_policy_version);
+ head->r.version_done = true;
+ }
+ if (!head->r.stat_done) {
+ cs_read_stat(head);
+ head->r.stat_done = true;
+ cs_set_lf(head);
+ }
+ while (head->r.acl_index < CS_MAX_MAC_INDEX) {
+ list_for_each_cookie(head->r.acl,
+ &cs_acl_list[head->r.acl_index]) {
+ struct cs_acl_info *ptr;
+
+ ptr = list_entry(head->r.acl, typeof(*ptr), list);
+ switch (head->r.step) {
+ case 0:
+ if (ptr->is_deleted)
+ continue;
+ head->r.step++;
+ /* fall through */
+ case 1:
+ if (!cs_read_acl(head, ptr))
+ return;
+ head->r.step++;
+ /* fall through */
+ case 2:
+ if (!cs_flush(head))
+ return;
+ head->r.step++;
+ /* fall through */
+ case 3:
+ case 4:
+ if (!cs_read_subacl(head, &ptr->acl_info_list))
+ return;
+ head->r.step = 5;
+ /* fall through */
+ case 5:
+ if (!cs_flush(head))
+ return;
+ cs_set_lf(head);
+ head->r.step = 0;
+ }
+ }
+ head->r.acl = NULL;
+ head->r.acl_index++;
+ }
+ head->r.eof = true;
+}
+
+/**
+ * cs_open - open() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file: Pointer to "struct file".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int cs_open(struct inode *inode, struct file *file)
+{
+ const u8 type = (unsigned long) inode->i_private;
+ struct cs_io_buffer *head = kzalloc(sizeof(*head), GFP_NOFS);
+
+ if (!head)
+ return -ENOMEM;
+ mutex_init(&head->io_sem);
+ head->type = type;
+ if (file->f_mode & FMODE_READ) {
+ head->readbuf_size = 4096;
+ head->read_buf = kzalloc(head->readbuf_size, GFP_NOFS);
+ if (!head->read_buf) {
+ kfree(head);
+ return -ENOMEM;
+ }
+ }
+ if (file->f_mode & FMODE_WRITE) {
+ head->writebuf_size = 4096;
+ head->write_buf = kzalloc(head->writebuf_size, GFP_NOFS);
+ if (!head->write_buf) {
+ kfree(head->read_buf);
+ kfree(head);
+ return -ENOMEM;
+ }
+ }
+ file->private_data = head;
+ cs_notify_gc(head, true);
+ return 0;
+}
+
+/**
+ * cs_release - close() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @inode: Pointer to "struct inode".
+ * @file: Pointer to "struct file".
+ *
+ * Returns 0.
+ */
+static int cs_release(struct inode *inode, struct file *file)
+{
+ struct cs_io_buffer *head = file->private_data;
+
+ cs_notify_gc(head, false);
+ return 0;
+}
+
+/**
+ * cs_read - read() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @file: Pointer to "struct file".
+ * @buf: Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos: Unused.
+ *
+ * Returns bytes read on success, negative value otherwise.
+ */
+static ssize_t cs_read(struct file *file, char __user *buf, size_t count,
+ loff_t *ppos)
+{
+ struct cs_io_buffer *head = file->private_data;
+ int len;
+ int idx;
+
+ if (mutex_lock_interruptible(&head->io_sem))
+ return -EINTR;
+ head->read_user_buf = buf;
+ head->read_user_buf_avail = count;
+ idx = cs_read_lock();
+ if (cs_flush(head)) {
+ /* Call the policy handler. */
+ switch (head->type) {
+ case CS_VERSION:
+ cs_read_version(head);
+ break;
+ case CS_POLICY:
+ cs_read_policy(head);
+ break;
+ }
+ cs_flush(head);
+ }
+ cs_read_unlock(idx);
+ len = head->read_user_buf - buf;
+ mutex_unlock(&head->io_sem);
+ return len;
+}
+
+/**
+ * cs_write - write() for /sys/kernel/security/caitsith/ interface.
+ *
+ * @file: Pointer to "struct file".
+ * @buf: Pointer to buffer.
+ * @count: Size of @buf.
+ * @ppos: Unused.
+ *
+ * Returns @count on success, negative value otherwise.
+ */
+static ssize_t cs_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct cs_io_buffer *head = file->private_data;
+ int error = count;
+ char *cp0 = head->write_buf;
+ int idx;
+
+ if (head->type != CS_POLICY)
+ return -EIO;
+ if (mutex_lock_interruptible(&head->io_sem))
+ return -EINTR;
+ head->read_user_buf_avail = 0;
+ idx = cs_read_lock();
+ /* Read a line and dispatch it to the policy handler. */
+ while (count) {
+ char c;
+
+ if (head->w.avail >= head->writebuf_size - 1) {
+ const int len = head->writebuf_size * 2;
+ char *cp = kzalloc(len, GFP_NOFS);
+
+ if (!cp) {
+ error = -ENOMEM;
+ break;
+ }
+ memmove(cp, cp0, head->w.avail);
+ kfree(cp0);
+ head->write_buf = cp;
+ cp0 = cp;
+ head->writebuf_size = len;
+ }
+ if (get_user(c, buf)) {
+ error = -EFAULT;
+ break;
+ }
+ buf++;
+ count--;
+ cp0[head->w.avail++] = c;
+ if (c != '\n')
+ continue;
+ cp0[head->w.avail - 1] = '\0';
+ head->w.avail = 0;
+ cs_normalize_line(cp0);
+ /* Don't allow updating policies by non manager programs. */
+ if (!cs_manager()) {
+ error = -EPERM;
+ goto out;
+ }
+ if (cs_parse_policy(head, cp0) == 0)
+ /* Update statistics. */
+ cs_update_stat(CS_STAT_POLICY_UPDATES);
+ }
+out:
+ cs_read_unlock(idx);
+ mutex_unlock(&head->io_sem);
+ return error;
+}
+
+/**
+ * cs_create_entry - Create interface files under /sys/kernel/security/caitsith/ directory.
+ *
+ * @name: The name of the interface file.
+ * @mode: The permission of the interface file.
+ * @parent: The parent directory.
+ * @key: Type of interface.
+ *
+ * Returns nothing.
+ */
+static void __init cs_create_entry(const char *name, const umode_t mode,
+ struct dentry *parent, const u8 key)
+{
+ securityfs_create_file(name, S_IFREG | mode, parent,
+ (void *) (unsigned long) key, &cs_operations);
+}
+
+/**
+ * cs_securityfs_init - Initialize /sys/kernel/security/caitsith/ interface.
+ *
+ * Returns 0.
+ */
+static int __init cs_securityfs_init(void)
+{
+ struct dentry *cs_dir;
+
+ if (!security_module_enable("caitsith"))
+ return 0;
+ cs_dir = securityfs_create_dir("caitsith", NULL);
+ cs_create_entry("version", 0400, cs_dir, CS_VERSION);
+ cs_create_entry("policy", 0600, cs_dir, CS_POLICY);
+ cs_load_builtin_policy();
+ return 0;
+}
+
+fs_initcall(cs_securityfs_init);
+
+/**
+ * cs_init_module - Initialize this module.
+ *
+ * Returns nothing.
+ */
+void __init cs_init_module(void)
+{
+ u16 idx;
+
+#ifdef DEBUG_CONDITION
+ for (idx = 0; idx < CS_MAX_MAC_INDEX; idx++) {
+ if (cs_mac_keywords[idx])
+ continue;
+ panic("cs_mac_keywords[%u]==NULL\n", idx);
+ }
+#endif
+ if (init_srcu_struct(&cs_ss))
+ panic("Out of memory.");
+ for (idx = 0; idx < CS_MAX_MAC_INDEX; idx++)
+ INIT_LIST_HEAD(&cs_acl_list[idx]);
+ for (idx = 0; idx < CS_MAX_HASH; idx++)
+ INIT_LIST_HEAD(&cs_name_list[idx]);
+ cs_null_name.name = "NULL";
+ cs_fill_path_info(&cs_null_name);
+}
--
1.8.3.1
This file allows userspace tools to load policy configuration
unless CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER is defined.
If CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER is defined,
only built-in policy configuration which is generated as
security/caitsith/builtin-policy.h at compilation time from
security/caitsith/policy/policy.conf will be loaded.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/caitsith/load_policy.c | 106 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
create mode 100644 security/caitsith/load_policy.c
diff --git a/security/caitsith/load_policy.c b/security/caitsith/load_policy.c
new file mode 100644
index 0000000..e9b9706
--- /dev/null
+++ b/security/caitsith/load_policy.c
@@ -0,0 +1,106 @@
+/*
+ * security/caitsith/load_policy.c
+ *
+ * Copyright (C) 2005-2012 NTT DATA CORPORATION
+ */
+
+#include "caitsith.h"
+
+#ifndef CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+
+/* Path to the policy loader. */
+static const char *cs_loader;
+
+/**
+ * cs_loader_setup - Set policy loader.
+ *
+ * @str: Program to use as a policy loader (e.g. /sbin/caitsith-init ).
+ *
+ * Returns 0.
+ */
+static int __init cs_loader_setup(char *str)
+{
+ cs_loader = str;
+ return 0;
+}
+
+__setup("CS_loader=", cs_loader_setup);
+
+/**
+ * cs_policy_loader_exists - Check whether /sbin/caitsith-init exists.
+ *
+ * Returns true if /sbin/caitsith-init exists, false otherwise.
+ */
+static bool cs_policy_loader_exists(void)
+{
+ struct path path;
+
+ if (!cs_loader)
+ cs_loader = CONFIG_SECURITY_CAITSITH_POLICY_LOADER;
+ if (kern_path(cs_loader, LOOKUP_FOLLOW, &path) == 0) {
+ path_put(&path);
+ return true;
+ }
+ printk(KERN_INFO "Not activating CaitSith as %s does not exist.\n",
+ cs_loader);
+ return false;
+}
+
+/* Path to the trigger. */
+static const char *cs_trigger;
+
+/**
+ * cs_trigger_setup - Set trigger for activation.
+ *
+ * @str: Program to use as an activation trigger (e.g. /sbin/init ).
+ *
+ * Returns 0.
+ */
+static int __init cs_trigger_setup(char *str)
+{
+ cs_trigger = str;
+ return 0;
+}
+
+__setup("CS_trigger=", cs_trigger_setup);
+
+/**
+ * cs_load_policy - Run external policy loader to load policy.
+ *
+ * @filename: The program about to start.
+ *
+ * Returns nothing.
+ *
+ * This function checks whether @filename is /sbin/init, and if so
+ * invoke /sbin/caitsith-init and wait for the termination of
+ * /sbin/caitsith-init and then continues invocation of /sbin/init.
+ * /sbin/caitsith-init reads policy files in /etc/caitsith/ directory and
+ * writes to /sys/kernel/security/caitsith/ interfaces.
+ */
+void cs_load_policy(const char *filename)
+{
+ static _Bool done;
+ char *argv[2];
+ char *envp[3];
+
+ if (done)
+ return;
+ if (!cs_trigger)
+ cs_trigger = CONFIG_SECURITY_CAITSITH_ACTIVATION_TRIGGER;
+ if (strcmp(filename, cs_trigger))
+ return;
+ if (!cs_policy_loader_exists())
+ return;
+ done = 1;
+ printk(KERN_INFO "Calling %s to load policy. Please wait.\n",
+ cs_loader);
+ argv[0] = (char *) cs_loader;
+ argv[1] = NULL;
+ envp[0] = "HOME=/";
+ envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+ envp[2] = NULL;
+ call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
+ cs_check_profile();
+}
+
+#endif
--
1.8.3.1
CaitSith uses LSM hooks and securityfs support. For now, CaitSith is not
using security blobs (i.e. "void *security" field in data structures)
so that you can enable CaitSith using Multiple Concurrent LSMs.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/Kconfig | 6 ++++++
security/Makefile | 2 ++
security/caitsith/Kconfig | 48 ++++++++++++++++++++++++++++++++++++++++++++++
security/caitsith/Makefile | 15 +++++++++++++++
4 files changed, 71 insertions(+)
create mode 100644 security/caitsith/Kconfig
create mode 100644 security/caitsith/Makefile
diff --git a/security/Kconfig b/security/Kconfig
index 176758c..ab5b634 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -124,6 +124,7 @@ source security/tomoyo/Kconfig
source security/apparmor/Kconfig
source security/loadpin/Kconfig
source security/yama/Kconfig
+source security/caitsith/Kconfig
source security/integrity/Kconfig
@@ -133,6 +134,7 @@ choice
default DEFAULT_SECURITY_SMACK if SECURITY_SMACK
default DEFAULT_SECURITY_TOMOYO if SECURITY_TOMOYO
default DEFAULT_SECURITY_APPARMOR if SECURITY_APPARMOR
+ default DEFAULT_SECURITY_CAITSITH if SECURITY_CAITSITH
default DEFAULT_SECURITY_DAC
help
@@ -151,6 +153,9 @@ choice
config DEFAULT_SECURITY_APPARMOR
bool "AppArmor" if SECURITY_APPARMOR=y
+ config DEFAULT_SECURITY_CAITSITH
+ bool "CaitSith" if SECURITY_CAITSITH=y
+
config DEFAULT_SECURITY_DAC
bool "Unix Discretionary Access Controls"
@@ -162,6 +167,7 @@ config DEFAULT_SECURITY
default "smack" if DEFAULT_SECURITY_SMACK
default "tomoyo" if DEFAULT_SECURITY_TOMOYO
default "apparmor" if DEFAULT_SECURITY_APPARMOR
+ default "caitsith" if DEFAULT_SECURITY_CAITSITH
default "" if DEFAULT_SECURITY_DAC
endmenu
diff --git a/security/Makefile b/security/Makefile
index f2d71cd..3745af0 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -9,6 +9,7 @@ subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo
subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin
+subdir-$(CONFIG_SECURITY_CAITSITH) += caitsith
# always enable default capabilities
obj-y += commoncap.o
@@ -25,6 +26,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
+obj-$(CONFIG_SECURITY_CAITSITH) += caitsith/
# Object integrity file lists
subdir-$(CONFIG_INTEGRITY) += integrity
diff --git a/security/caitsith/Kconfig b/security/caitsith/Kconfig
new file mode 100644
index 0000000..005cdb1
--- /dev/null
+++ b/security/caitsith/Kconfig
@@ -0,0 +1,48 @@
+config SECURITY_CAITSITH
+ bool "CaitSith Support"
+ depends on SECURITY
+ select SECURITYFS
+ select SRCU
+ default n
+ help
+ This selects CaitSith, check list based access control.
+ Required userspace tools and further information may be
+ found at <https://caitsith.osdn.jp/>.
+ If you are unsure how to answer this question, answer N.
+
+config SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+ bool "Activate without calling userspace policy loader."
+ default n
+ depends on SECURITY_CAITSITH
+ ---help---
+ Say Y here if you want to activate access control as soon as built-in
+ policy was loaded. This option will be useful for systems where
+ operations which can lead to the hijacking of the boot sequence are
+ needed before loading the policy. For example, you can activate
+ immediately after loading the fixed part of policy which will allow
+ only operations needed for mounting a partition which contains the
+ variant part of policy and verifying (e.g. running GPG check) and
+ loading the variant part of policy. Since you can start using
+ enforcing mode from the beginning, you can reduce the possibility of
+ hijacking the boot sequence.
+
+config SECURITY_CAITSITH_POLICY_LOADER
+ string "Location of userspace policy loader"
+ default "/sbin/caitsith-init"
+ depends on SECURITY_CAITSITH
+ depends on !SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+ ---help---
+ This is the default pathname of policy loader which is called before
+ activation. You can override this setting via CS_loader= kernel
+ command line option.
+
+config SECURITY_CAITSITH_ACTIVATION_TRIGGER
+ string "Trigger for calling userspace policy loader"
+ default "/sbin/init"
+ depends on SECURITY_CAITSITH
+ depends on !SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+ ---help---
+ This is the default pathname of activation trigger. You can override
+ this setting via CS_trigger= kernel command line option.
+ For example, if you pass init=/usr/lib/systemd/systemd option, you
+ may want to also pass CS_trigger=/usr/lib/systemd/systemd option.
diff --git a/security/caitsith/Makefile b/security/caitsith/Makefile
new file mode 100644
index 0000000..1f0b83f
--- /dev/null
+++ b/security/caitsith/Makefile
@@ -0,0 +1,15 @@
+obj-y += permission.o gc.o policy_io.o realpath.o lsm.o load_policy.o
+
+$(obj)/policy/policy.conf:
+ @mkdir -p $(obj)/policy/
+ @echo Creating an empty policy/policy.conf
+ @touch $@
+
+$(obj)/builtin-policy.h: $(obj)/policy/policy.conf
+ @echo Generating built-in policy for CaitSith.
+ @echo "static char cs_builtin_policy[] __initdata =" > [email protected]
+ @sed -e 's/\\/\\134/g' -e 's/"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/policy.conf >> [email protected]
+ @echo "\"\";" >> [email protected]
+ @mv [email protected] $@
+
+$(obj)/policy_io.o: $(obj)/builtin-policy.h
--
1.8.3.1
This file defines data structures, prototypes and inlined functions
used by CaitSith.
In TOMOYO, the policy syntax requires positional mandatory parameters
(e.g. pathname when opening a pathname, pathname and DAC permission mode
when creating a pathname), and is using a dedicated data structure for
type and number of arguments (e.g. "struct tomoyo_path_acl" for operations
which pass one pathname, "struct tomoyo_path_number_acl" for operations
which pass one pathname and one numeric argument). But it turned out that
using a dedicated structure undesirably limits ability to specify
complicated conditions. For example, when specific pattern should be
excluded from some pattern (e.g. "*" but "*.tmp"), administrator has to
use \- operator (e.g. "\*\-\*.tmp") for that purpose. It makes conditions
difficult to understand. Also, since TOMOYO uses only whitelisting
approach, \- operator needs to be used with cautions that unwanted
patterns are not included by error. It made complicated conditions very
hard to understand. Such problems can be avoided if policy syntax (and
underlying data structures) supports referencing same argument for
multiple times using variable=value or variable!=value expression.
Therefore, there is no positional mandatory parameter in CaitSith, and
CaitSith is using a common variable length data structure
("struct cs_acl_info" has a reference to "struct cs_condition" if
there are parameters, and array of "union cs_condition_element" follows
"struct cs_condition" for holding all parameters).
In TOMOYO, a domain ("struct tomoyo_domain_info") is used as a key for
identifying state of "struct task_struct" (via "struct cred"). As a
result, management of the domains is mandatory when using TOMOYO. But it
turned out that management of the domains became a huge burden for users
who is trying to use TOMOYO for usages which TOMOYO did not expect.
Therefore, management of the domains became optional in CaitSith (though
this version does not implement domains). In CaitSith, policy is a
collection of action check lists (list of "struct cs_acl_info" with
optional "struct cs_condition") which uses type of action as a key.
By combination with two changes explained above, the policy syntax in
CaitSith allows both blacklisting approach and whitelisting approach,
as explained below. Since this version implements minimal functionality,
only few conditions are supported by acl (action check list).
acl $action $conditions_to_check_this_acl_block
$decision $conditions_to_use_this_decision
$action is either "execute" (for checking execve() system call) or
"modify_policy" (for updating CaitSith's on-memory policy configuration).
$decision is either "allow" (for granting this acl block) or "deny"
(for rejecting this access request).
$conditions_to_check_this_acl_block and $conditions_to_use_this_decision
are optional which contain conditions to apply current acl block or
decision. Available variables are
task.exe ... The pathname of current thread (i.e. /proc/self/exe in
CaitSith's pathname representation rule).
exec ... Requested program's pathname in CaitSith's pathname
representation rule, but maybe a symbolic link.
Applicable to only $action == "execve" case.
path ... Requested program's pathname in CaitSith's pathname
representation rule.
Applicable to only $action == "execve" case.
Some examples are shown below. An unsigned integer value is prefixed to
each line of rules for controlling priority of evaluation. A line with
smaller priority value is evaluated before a line with larger priority
value.
/usr/sbin/httpd is allowed to execute only /var/www/cgi-bin/counter.cgi
and /usr/bin/suexec using whitelist.
1000 acl execute task.exe="/usr/sbin/httpd"
100 allow path="/var/www/cgi-bin/counter.cgi"
150 allow path="/usr/sbin/suexec"
200 deny
/usr/sbin/httpd is not allowed to execute /bin/sh and /bin/bash using
blacklist.
2000 acl execute task.exe="/usr/sbin/httpd"
100 deny path="/bin/sh"
150 deny path="/bin/bash"
200 allow
/usr/sbin/suexec is allowed to be executed by only /usr/sbin/httpd
using inversed point of view. (This is not possible with TOMOYO.)
2000 acl execute path="/usr/sbin/suexec"
100 allow task.exe="/usr/sbin/httpd"
200 deny
For each $action, all acl blocks with that $action will be checked.
Access request will be denied as soon as one of
$conditions_to_use_this_decision in any "deny" lines in all acl blocks
with that $action and $conditions_to_check_this_acl_block was evaluated
as "true" turned out to be "true" (or out of memory problem occurred
while calculating the value of pathname represented by variables).
Otherwise, access request will be granted.
Note that in order to minimize the burden of reviewing, this version
implements only functionality of checking program execution requests
using pathnames. More $actions and variables will be added in future
version. There is no limitation that CaitSith cannot use security
labels like SELinux or Smack does.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/caitsith/caitsith.h | 347 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 347 insertions(+)
create mode 100644 security/caitsith/caitsith.h
diff --git a/security/caitsith/caitsith.h b/security/caitsith/caitsith.h
new file mode 100644
index 0000000..4292052
--- /dev/null
+++ b/security/caitsith/caitsith.h
@@ -0,0 +1,347 @@
+/*
+ * security/caitsith/caitsith.h
+ *
+ * Copyright (C) 2005-2012 NTT DATA CORPORATION
+ */
+
+#ifndef _SECURITY_CAITSITH_INTERNAL_H
+#define _SECURITY_CAITSITH_INTERNAL_H
+
+#include <linux/security.h>
+#include <linux/binfmts.h>
+#include <linux/namei.h>
+#include <linux/kthread.h>
+#include <linux/uaccess.h>
+#include <linux/file.h>
+#include <linux/ctype.h> /* isdigit()/isxdigit() */
+#include <linux/kmod.h>
+
+/* Enumeration definition for internal use. */
+
+/* Index numbers for "struct cs_condition". */
+enum cs_conditions_index {
+ CS_INVALID_CONDITION,
+ CS_SELF_EXE,
+ CS_COND_SARG0,
+ CS_COND_SARG1,
+ CS_IMM_NAME_ENTRY,
+} __packed;
+
+/* Index numbers for functionality. */
+enum cs_mac_index {
+ CS_MAC_EXECUTE,
+ CS_MAC_MODIFY_POLICY,
+ CS_MAX_MAC_INDEX,
+} __packed;
+
+/* Index numbers for statistic information. */
+enum cs_memory_stat_type {
+ CS_MEMORY_POLICY,
+ CS_MAX_MEMORY_STAT
+} __packed;
+
+enum cs_matching_result {
+ CS_MATCHING_UNMATCHED,
+ CS_MATCHING_ALLOWED,
+ CS_MATCHING_DENIED,
+ CS_MAX_MATCHING
+} __packed;
+
+/* Index numbers for entry type. */
+enum cs_policy_id {
+ CS_ID_CONDITION,
+ CS_ID_NAME,
+ CS_ID_ACL,
+ CS_MAX_POLICY
+} __packed;
+
+/* Index numbers for statistic information. */
+enum cs_policy_stat_type {
+ CS_STAT_POLICY_UPDATES,
+ CS_STAT_REQUEST_DENIED,
+ CS_MAX_POLICY_STAT
+} __packed;
+
+/* Index numbers for /sys/kernel/security/caitsith/ interfaces. */
+enum cs_securityfs_interface_index {
+ CS_POLICY,
+ CS_VERSION,
+} __packed;
+
+/* Constants definition for internal use. */
+
+/*
+ * CaitSith uses this hash only when appending a string into the string table.
+ * Frequency of appending strings is very low. So we don't need large (e.g.
+ * 64k) hash size. 256 will be sufficient.
+ */
+#define CS_HASH_BITS 8
+#define CS_MAX_HASH (1u << CS_HASH_BITS)
+
+/* Size of temporary buffer for execve() operation. */
+#define CS_EXEC_TMPSIZE 4096
+
+/* Garbage collector is trying to kfree() this element. */
+#define CS_GC_IN_PROGRESS -1
+
+/* Size of read buffer for /sys/kernel/security/caitsith/ interface. */
+#define CS_MAX_IO_READ_QUEUE 64
+
+/* Structure definition for internal use. */
+
+/* Common header for holding ACL entries. */
+struct cs_acl_head {
+ struct list_head list;
+ s8 is_deleted; /* true or false or CS_GC_IN_PROGRESS */
+} __packed;
+
+/* Common header for shared entries. */
+struct cs_shared_acl_head {
+ struct list_head list;
+ atomic_t users;
+} __packed;
+
+/* Common header for individual entries. */
+struct cs_acl_info {
+ struct list_head list;
+ struct list_head acl_info_list;
+ struct cs_condition *cond; /* Maybe NULL. */
+ bool is_deleted;
+ bool is_deny;
+ u16 priority;
+};
+
+/* Structure for entries which follows "struct cs_condition". */
+union cs_condition_element {
+ struct {
+ enum cs_conditions_index left;
+ enum cs_conditions_index right;
+ bool is_not;
+ };
+ const struct cs_path_info *path;
+};
+
+/* Structure for optional arguments. */
+struct cs_condition {
+ struct cs_shared_acl_head head;
+ u32 size; /* Memory size allocated for this entry. */
+ /* union cs_condition_element condition[]; */
+};
+
+/* Structure for holding a token. */
+struct cs_path_info {
+ const char *name;
+ u32 hash; /* = full_name_hash(name, strlen(name)) */
+ u32 total_len; /* = strlen(name) */
+ u32 const_len; /* = cs_const_part_length(name) */
+};
+
+/* Structure for request info. */
+struct cs_request_info {
+ /* For holding parameters. */
+ struct cs_request_param {
+ const struct cs_path_info *s[2];
+ } param;
+ /* For holding pathnames and attributes. */
+ struct {
+ /* Pointer to file objects. */
+ struct path path[2];
+ /*
+ * Name of @path[0] and @path[1].
+ * Cleared by cs_clear_request_info().
+ */
+ struct cs_path_info pathname[2];
+ } obj;
+ struct {
+ struct linux_binprm *bprm;
+ /* For temporary use. Size is CS_EXEC_TMPSIZE bytes. */
+ char *tmp;
+ };
+ /*
+ * Name of current thread's executable.
+ * Cleared by cs_clear_request_info().
+ */
+ struct cs_path_info exename;
+ /*
+ * Matching "struct cs_acl_info" is copied. Used for caitsith-queryd.
+ * Valid until cs_read_unlock().
+ */
+ struct cs_acl_info *matched_acl;
+ /*
+ * For holding operation index used for this request.
+ * One of values in "enum cs_mac_index".
+ */
+ enum cs_mac_index type;
+ /* For holding matching result. */
+ enum cs_matching_result result;
+ /*
+ * Set to true if condition could not be checked due to out of memory.
+ * This flag is used for returning out of memory flag back to
+ * cs_check_acl_list(). Thus, this flag will not be set if out of
+ * memory occurred before cs_check_acl_list() is called.
+ */
+ bool failed_by_oom;
+};
+
+/* Structure for holding string data. */
+struct cs_name {
+ struct cs_shared_acl_head head;
+ int size; /* Memory size allocated for this entry. */
+ struct cs_path_info entry;
+};
+
+/*
+ * Structure for reading/writing policy via /sys/kernel/security/caitsith/
+ * interfaces.
+ */
+struct cs_io_buffer {
+ /* Exclusive lock for this structure. */
+ struct mutex io_sem;
+ char __user *read_user_buf;
+ size_t read_user_buf_avail;
+ struct {
+ struct list_head *acl;
+ struct list_head *subacl;
+ const union cs_condition_element *cond;
+ size_t avail;
+ unsigned int step;
+ u16 index;
+ u8 cond_step;
+ u8 w_pos;
+ enum cs_mac_index acl_index;
+ bool eof;
+ bool version_done;
+ bool stat_done;
+ const char *w[CS_MAX_IO_READ_QUEUE];
+ } r;
+ struct {
+ char *data;
+ struct cs_acl_info *acl;
+ size_t avail;
+ enum cs_mac_index acl_index;
+ bool is_delete;
+ bool is_deny;
+ u16 priority;
+ } w;
+ /* Buffer for reading. */
+ char *read_buf;
+ /* Size of read buffer. */
+ size_t readbuf_size;
+ /* Buffer for writing. */
+ char *write_buf;
+ /* Size of write buffer. */
+ size_t writebuf_size;
+ /* Type of interface. */
+ enum cs_securityfs_interface_index type;
+ /* Users counter protected by cs_io_buffer_list_lock. */
+ u8 users;
+ /* List for telling GC not to kfree() elements. */
+ struct list_head list;
+};
+
+/* Structure for representing YYYY/MM/DD hh/mm/ss. */
+struct cs_time {
+ u16 year;
+ u8 month;
+ u8 day;
+ u8 hour;
+ u8 min;
+ u8 sec;
+};
+
+/* Prototype definition for internal use. */
+
+void __init cs_init_module(void);
+void cs_load_policy(const char *filename);
+void cs_check_profile(void);
+bool cs_get_exename(struct cs_path_info *buf);
+bool cs_manager(void);
+char *cs_encode(const char *str);
+char *cs_realpath(const struct path *path);
+char *cs_get_exe(void);
+int cs_audit_log(struct cs_request_info *r);
+int cs_check_acl(struct cs_request_info *r, const bool clear);
+void cs_del_condition(struct list_head *element);
+void cs_fill_path_info(struct cs_path_info *ptr);
+void cs_notify_gc(struct cs_io_buffer *head, const bool is_register);
+void cs_populate_patharg(struct cs_request_info *r, const bool first);
+void cs_warn_oom(const char *function);
+int cs_start_execve(struct linux_binprm *bprm);
+
+/* Variable definition for internal use. */
+
+extern bool cs_policy_loaded;
+extern struct cs_path_info cs_null_name;
+extern struct list_head cs_acl_list[CS_MAX_MAC_INDEX];
+extern struct list_head cs_condition_list;
+extern struct list_head cs_name_list[CS_MAX_HASH];
+extern struct mutex cs_policy_lock;
+extern struct srcu_struct cs_ss;
+extern unsigned int cs_memory_used[CS_MAX_MEMORY_STAT];
+
+/* Inlined functions for internal use. */
+
+/**
+ * cs_pathcmp - strcmp() for "struct cs_path_info" structure.
+ *
+ * @a: Pointer to "struct cs_path_info".
+ * @b: Pointer to "struct cs_path_info".
+ *
+ * Returns true if @a != @b, false otherwise.
+ */
+static inline bool cs_pathcmp(const struct cs_path_info *a,
+ const struct cs_path_info *b)
+{
+ return a->hash != b->hash || strcmp(a->name, b->name);
+}
+
+/**
+ * cs_read_lock - Take lock for protecting policy.
+ *
+ * Returns index number for cs_read_unlock().
+ */
+static inline int cs_read_lock(void)
+{
+ return srcu_read_lock(&cs_ss);
+}
+
+/**
+ * cs_read_unlock - Release lock for protecting policy.
+ *
+ * @idx: Index number returned by cs_read_lock().
+ *
+ * Returns nothing.
+ */
+static inline void cs_read_unlock(const int idx)
+{
+ srcu_read_unlock(&cs_ss, idx);
+}
+
+/**
+ * cs_put_condition - Drop reference on "struct cs_condition".
+ *
+ * @cond: Pointer to "struct cs_condition". Maybe NULL.
+ *
+ * Returns nothing.
+ */
+static inline void cs_put_condition(struct cs_condition *cond)
+{
+ if (cond)
+ atomic_dec(&cond->head.users);
+}
+
+/**
+ * cs_put_name - Drop reference on "struct cs_name".
+ *
+ * @name: Pointer to "struct cs_path_info". Maybe NULL.
+ *
+ * Returns nothing.
+ */
+static inline void cs_put_name(const struct cs_path_info *name)
+{
+ if (name)
+ atomic_dec(&container_of(name, struct cs_name, entry)->
+ head.users);
+}
+
+#endif
--
1.8.3.1
This version implements only execve() related LSM hooks.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/caitsith/lsm.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
create mode 100644 security/caitsith/lsm.c
diff --git a/security/caitsith/lsm.c b/security/caitsith/lsm.c
new file mode 100644
index 0000000..675cee8
--- /dev/null
+++ b/security/caitsith/lsm.c
@@ -0,0 +1,60 @@
+/*
+ * security/caitsith/lsm.c
+ *
+ * Copyright (C) 2010-2013 Tetsuo Handa <[email protected]>
+ */
+
+#include <linux/lsm_hooks.h>
+#include "caitsith.h"
+
+/**
+ * caitsith_bprm_set_creds - Target for security_bprm_set_creds().
+ *
+ * @bprm: Pointer to "struct linux_binprm".
+ *
+ * Returns 0 on success, negative value otherwise.
+ */
+static int caitsith_bprm_set_creds(struct linux_binprm *bprm)
+{
+ /*
+ * Do only if this function is called for the first time of an execve
+ * operation.
+ */
+ if (bprm->cred_prepared)
+ return 0;
+#ifndef CONFIG_SECURITY_CAITSITH_OMIT_USERSPACE_LOADER
+ /*
+ * Load policy if /sbin/caitsith-init exists and /sbin/init is requested
+ * for the first time.
+ */
+ if (!cs_policy_loaded)
+ cs_load_policy(bprm->filename);
+#endif
+ return cs_start_execve(bprm);
+}
+
+/*
+ * caitsith_security_ops is a "struct security_operations" which is used for
+ * registering CaitSith.
+ */
+static struct security_hook_list caitsith_hooks[] = {
+ LSM_HOOK_INIT(bprm_set_creds, caitsith_bprm_set_creds),
+};
+
+/**
+ * caitsith_init - Register CaitSith as a LSM module.
+ *
+ * Returns 0.
+ */
+static int __init caitsith_init(void)
+{
+ if (!security_module_enable("caitsith"))
+ return 0;
+ /* register ourselves with the security framework */
+ security_add_hooks(caitsith_hooks, ARRAY_SIZE(caitsith_hooks));
+ printk(KERN_INFO "CaitSith initialized\n");
+ cs_init_module();
+ return 0;
+}
+
+security_initcall(caitsith_init);
--
1.8.3.1
This is a simplified version of security/tomoyo/realpath.c , except
two changes listed below.
A "\" character (0x5C) in TOMOYO is represented as "\\" while
it in CaitSith is represented as "\134". This allows us to tell
whether a "\" character at any position is a literal "\" character
without scanning from the beginning of the string.
A "/" character (0x2F) is appended in TOMOYO in order to represent
a directory while it is not appended in CaitSith. In CaitSith,
path.type variable which will be implemented after this version is
accepted will allow us to tell whether a pathname represents a
directory without appending a "/" character.
Signed-off-by: Tetsuo Handa <[email protected]>
---
security/caitsith/realpath.c | 227 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 227 insertions(+)
create mode 100644 security/caitsith/realpath.c
diff --git a/security/caitsith/realpath.c b/security/caitsith/realpath.c
new file mode 100644
index 0000000..f62d843
--- /dev/null
+++ b/security/caitsith/realpath.c
@@ -0,0 +1,227 @@
+/*
+ * security/caitsith/realpath.c
+ *
+ * Copyright (C) 2005-2012 NTT DATA CORPORATION
+ */
+
+#include "caitsith.h"
+
+/**
+ * cs_realpath - Returns realpath(3) of the given pathname but ignores chroot'ed root.
+ *
+ * @path: Pointer to "struct path".
+ *
+ * Returns the realpath of the given @path on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+char *cs_realpath(const struct path *path)
+{
+ char *buf = NULL;
+ char *name = NULL;
+ unsigned int buf_len = PAGE_SIZE / 2;
+ struct dentry *dentry = path->dentry;
+ struct super_block *sb;
+
+ if (!dentry)
+ return NULL;
+ sb = dentry->d_sb;
+ while (1) {
+ char *pos;
+
+ buf_len <<= 1;
+ kfree(buf);
+ buf = kmalloc(buf_len, GFP_NOFS);
+ if (!buf)
+ break;
+ /* To make sure that pos is '\0' terminated. */
+ buf[buf_len - 1] = '\0';
+ /* For "pipe:[\$]". */
+ if (dentry->d_op && dentry->d_op->d_dname)
+ pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1);
+ else
+ pos = d_absolute_path(path, buf, buf_len);
+ if (IS_ERR(pos))
+ continue;
+ name = cs_encode(pos);
+ break;
+ }
+ kfree(buf);
+ if (!name)
+ cs_warn_oom(__func__);
+ return name;
+}
+
+/**
+ * cs_encode2 - Encode binary string to ascii string.
+ *
+ * @str: String in binary format. Maybe NULL.
+ * @str_len: Size of @str in byte.
+ *
+ * Returns pointer to @str in ascii format on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+static char *cs_encode2(const char *str, int str_len)
+{
+ int i;
+ int len;
+ const char *p = str;
+ char *cp;
+ char *cp0;
+
+ if (!p)
+ return NULL;
+ len = str_len;
+ for (i = 0; i < str_len; i++) {
+ const unsigned char c = p[i];
+
+ if (!(c > ' ' && c < 127 && c != '\\'))
+ len += 3;
+ }
+ len++;
+ cp = kzalloc(len, GFP_NOFS);
+ if (!cp)
+ return NULL;
+ cp0 = cp;
+ p = str;
+ for (i = 0; i < str_len; i++) {
+ const unsigned char c = p[i];
+
+ if (c > ' ' && c < 127 && c != '\\') {
+ *cp++ = c;
+ } else {
+ *cp++ = '\\';
+ *cp++ = (c >> 6) + '0';
+ *cp++ = ((c >> 3) & 7) + '0';
+ *cp++ = (c & 7) + '0';
+ }
+ }
+ return cp0;
+}
+
+/**
+ * cs_encode - Encode binary string to ascii string.
+ *
+ * @str: String in binary format. Maybe NULL.
+ *
+ * Returns pointer to @str in ascii format on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so caller must kfree() if this function
+ * didn't return NULL.
+ */
+char *cs_encode(const char *str)
+{
+ return str ? cs_encode2(str, strlen(str)) : NULL;
+}
+
+/**
+ * cs_const_part_length - Evaluate the initial length without a pattern in a token.
+ *
+ * @filename: The string to evaluate. Maybe NULL.
+ *
+ * Returns the initial length without a pattern in @filename.
+ */
+static int cs_const_part_length(const char *filename)
+{
+ char c;
+ int len = 0;
+
+ if (!filename)
+ return 0;
+ while (1) {
+ c = *filename++;
+ if (!c)
+ break;
+ if (c != '\\') {
+ len++;
+ continue;
+ }
+ c = *filename++;
+ switch (c) {
+ case '0': /* "\ooo" */
+ case '1':
+ case '2':
+ case '3':
+ c = *filename++;
+ if (c < '0' || c > '7')
+ break;
+ c = *filename++;
+ if (c < '0' || c > '7')
+ break;
+ len += 4;
+ continue;
+ }
+ break;
+ }
+ return len;
+}
+
+/**
+ * cs_fill_path_info - Fill in "struct cs_path_info" members.
+ *
+ * @ptr: Pointer to "struct cs_path_info" to fill in.
+ *
+ * Returns nothing.
+ *
+ * The caller sets "struct cs_path_info"->name.
+ */
+void cs_fill_path_info(struct cs_path_info *ptr)
+{
+ const char *name = ptr->name;
+ const int len = strlen(name);
+
+ ptr->total_len = len;
+ ptr->const_len = cs_const_part_length(name);
+ ptr->hash = full_name_hash(NULL, name, len);
+}
+
+/**
+ * cs_get_exe - Get cs_realpath() of current process.
+ *
+ * Returns the cs_realpath() of current process on success, NULL otherwise.
+ *
+ * This function uses kzalloc(), so the caller must kfree()
+ * if this function didn't return NULL.
+ */
+char *cs_get_exe(void)
+{
+ struct mm_struct *mm = current->mm;
+ char *cp;
+ struct file *exe_file;
+
+ if (current->flags & PF_KTHREAD)
+ return kstrdup("<kernel>", GFP_NOFS);
+ if (!mm)
+ goto task_has_no_mm;
+ exe_file = get_mm_exe_file(mm);
+ if (!exe_file)
+ goto task_has_no_mm;
+ cp = cs_realpath(&exe_file->f_path);
+ fput(exe_file);
+ return cp;
+task_has_no_mm:
+ return kstrdup("<unknown>", GFP_NOFS);
+}
+
+/**
+ * cs_get_exename - Get cs_realpath() of current process.
+ *
+ * @buf: Pointer to "struct cs_path_info".
+ *
+ * Returns true on success, false otherwise.
+ *
+ * This function uses kzalloc(), so the caller must kfree()
+ * if this function returned true.
+ */
+bool cs_get_exename(struct cs_path_info *buf)
+{
+ buf->name = cs_get_exe();
+ if (buf->name) {
+ cs_fill_path_info(buf);
+ return true;
+ }
+ return false;
+}
--
1.8.3.1
On Fri, 21 Oct 2016, Tetsuo Handa wrote:
> (1) CaitSith can use both string / numeric arguments (like TOMOYO and
> AppArmor) and security labels (like SELinux and Smack). There is no
> reason that access control implementation must not use both.
>
I believe that AppArmor will be gaining more support for security labels.
JJ: is that correct?
--
James Morris
<[email protected]>
On 10/23/2016 09:44 PM, James Morris wrote:
> On Fri, 21 Oct 2016, Tetsuo Handa wrote:
>
>> (1) CaitSith can use both string / numeric arguments (like TOMOYO and
>> AppArmor) and security labels (like SELinux and Smack). There is no
>> reason that access control implementation must not use both.
>>
>
> I believe that AppArmor will be gaining more support for security labels.
>
> JJ: is that correct?
>
yes, it will
On 10/21/2016 05:49 AM, Tetsuo Handa wrote:
> CaitSith (acronym for "Characteristic action inspection tool. See if
> this helps.") is an LSM based access control implementation which uses
> action check list (acl) as policy syntax.
>
<< snip >>
> CaitSith tries to remove many limitations which existing security
> enhanced Linux (I mean any LSM based access control implementation) has.
>
> (1) CaitSith can use both string / numeric arguments (like TOMOYO and
> AppArmor) and security labels (like SELinux and Smack). There is no
> reason that access control implementation must not use both.
>
In fact a full DTE implementation requires using both.
> (2) CaitSith can specify rules from the point of view of both subjects
> (a.k.a. capability list) and objects (a.k.a. access control list).
> There is no reason that access control implementation must use
> capability list which entails identification of all subjects.
>
> While security people are tempted to protect as much as possible,
> users generally have insufficient resource for protecting all. If
> users have sufficient resource, they will be already using existing
> implementations.
>
> I found users who want to restrict only a few objects without
> being bothered by managing all subjects in their systems. This is
> impossible for TOMOYO because TOMOYO entails managing all subjects
> in their systems. In CaitSith, this limitation is solved by writing
> rules using action as a key.
>
> (3) CaitSith can represent complicated string / numeric arguments
> compared to TOMOYO, for any condition is represented as zero or
> more repetition of variable=value or variable!=value expression.
>
> In TOMOYO, the policy syntax requires positional mandatory parameters
> (e.g. pathname when opening a pathname, pathname and DAC permission
> mode when creating a pathname) based on type and number of arguments
> for that action. But it turned out that using positional parameters
> undesirably limits ability to specify complicated conditions. For
> example, when specific pattern should be excluded from some pattern
> (e.g. "*" but "*.tmp"), administrator has to use \- operator (e.g.
> "\*\-\*.tmp") for that purpose. It makes conditions difficult to
> understand. \- operator needs to be used with cautions that unwanted
> patterns are not included by error. It made complicated conditions
> very hard to understand.
> In CaitSith, this limitation is solved by writing rules like
> path="\*" path!="\*.tmp" instead of path="\*\-\*.tmp" .
>
> (4) CaitSith can specify rules using both whitelisting and blacklisting.
>
> As far as I know, whoever involved in security enhanced Linux in
> Japan lost their jobs. In other words, security enhanced Linux made
> no business sense in Japan. I think that existing implementations
> are asking for too much skill/knowledge compared to users can afford.
>
> CaitSith's syntax acts as whitelisting if an unconditional deny line
> comes before an unconditional allow line comes, acts as blacklisting
> if an unconditional allow line comes before an unconditional deny
> line comes.
>
> CaitSith stayed four years and a half listening for whether it suits
> user's needs. In the last year or so, questions regarding how to use
> TOMOYO are getting to come. However, it turned out that CaitSith seems
> to fit better than TOMOYO for what many of the questioners want to
> achieve. You can see slides shown below for full explanation of
> the how and why.
>
> http://I-love.SAKURA.ne.jp/tomoyo/CaitSith-en.pdf (English)
> http://I-love.SAKURA.ne.jp/tomoyo/CaitSith-ja.pdf (Japanese)
>
The majority of your points for CaitSith are comparisons to TOMOYO your other
LSM. In the long run do you see CaitSith replacing Tomoyo, as it seems to be
the LSM you are now focused on?
> In order to minimize the burden of reviewing, this patchset implements
> only functionality of checking program execution requests (i.e. execve()
> system call) using pathnames. I'm planning to add other functionalities
> after this version got included into mainline. You can find how future
> versions of CaitSith will look like at http://caitsith.osdn.jp/ .
>
Thanks I've started working my way through this, but it is going to take
me a while.
> Tetsuo Handa (8):
> CaitSith: Add header file.
> CaitSith: Add pathname calculation functions.
> CaitSith: Add policy I/O functions.
> CaitSith: Add permission check functions.
> CaitSith: Add LSM adapter functions.
> CaitSith: Add policy loader functions.
> CaitSith: Add garbage collector functions.
> CaitSith: Add Kconfig and Makefile
>
> security/Kconfig | 6 +
> security/Makefile | 2 +
> security/caitsith/Kconfig | 48 ++
> security/caitsith/Makefile | 15 +
> security/caitsith/caitsith.h | 347 +++++++++
> security/caitsith/gc.c | 373 ++++++++++
> security/caitsith/load_policy.c | 106 +++
> security/caitsith/lsm.c | 60 ++
> security/caitsith/permission.c | 691 +++++++++++++++++
> security/caitsith/policy_io.c | 1553 +++++++++++++++++++++++++++++++++++++++
> security/caitsith/realpath.c | 227 ++++++
> 11 files changed, 3428 insertions(+)
> create mode 100644 security/caitsith/Kconfig
> create mode 100644 security/caitsith/Makefile
> create mode 100644 security/caitsith/caitsith.h
> create mode 100644 security/caitsith/gc.c
> create mode 100644 security/caitsith/load_policy.c
> create mode 100644 security/caitsith/lsm.c
> create mode 100644 security/caitsith/permission.c
> create mode 100644 security/caitsith/policy_io.c
> create mode 100644 security/caitsith/realpath.c
>
John Johansen wrote:
> On 10/21/2016 05:49 AM, Tetsuo Handa wrote:
> > CaitSith (acronym for "Characteristic action inspection tool. See if
> > this helps.") is an LSM based access control implementation which uses
> > action check list (acl) as policy syntax.
> >
>
> << snip >>
>
> > CaitSith tries to remove many limitations which existing security
> > enhanced Linux (I mean any LSM based access control implementation) has.
> >
> > (1) CaitSith can use both string / numeric arguments (like TOMOYO and
> > AppArmor) and security labels (like SELinux and Smack). There is no
> > reason that access control implementation must not use both.
> >
> In fact a full DTE implementation requires using both.
I see.
>
> > (2) CaitSith can specify rules from the point of view of both subjects
> > (a.k.a. capability list) and objects (a.k.a. access control list).
> > There is no reason that access control implementation must use
> > capability list which entails identification of all subjects.
> >
> > While security people are tempted to protect as much as possible,
> > users generally have insufficient resource for protecting all. If
> > users have sufficient resource, they will be already using existing
> > implementations.
> >
> > I found users who want to restrict only a few objects without
> > being bothered by managing all subjects in their systems. This is
> > impossible for TOMOYO because TOMOYO entails managing all subjects
> > in their systems. In CaitSith, this limitation is solved by writing
> > rules using action as a key.
> >
> > (3) CaitSith can represent complicated string / numeric arguments
> > compared to TOMOYO, for any condition is represented as zero or
> > more repetition of variable=value or variable!=value expression.
> >
> > In TOMOYO, the policy syntax requires positional mandatory parameters
> > (e.g. pathname when opening a pathname, pathname and DAC permission
> > mode when creating a pathname) based on type and number of arguments
> > for that action. But it turned out that using positional parameters
> > undesirably limits ability to specify complicated conditions. For
> > example, when specific pattern should be excluded from some pattern
> > (e.g. "*" but "*.tmp"), administrator has to use \- operator (e.g.
> > "\*\-\*.tmp") for that purpose. It makes conditions difficult to
> > understand. \- operator needs to be used with cautions that unwanted
> > patterns are not included by error. It made complicated conditions
> > very hard to understand.
> > In CaitSith, this limitation is solved by writing rules like
> > path="\*" path!="\*.tmp" instead of path="\*\-\*.tmp" .
> >
> > (4) CaitSith can specify rules using both whitelisting and blacklisting.
> >
> > As far as I know, whoever involved in security enhanced Linux in
> > Japan lost their jobs. In other words, security enhanced Linux made
> > no business sense in Japan. I think that existing implementations
> > are asking for too much skill/knowledge compared to users can afford.
> >
> > CaitSith's syntax acts as whitelisting if an unconditional deny line
> > comes before an unconditional allow line comes, acts as blacklisting
> > if an unconditional allow line comes before an unconditional deny
> > line comes.
> >
> > CaitSith stayed four years and a half listening for whether it suits
> > user's needs. In the last year or so, questions regarding how to use
> > TOMOYO are getting to come. However, it turned out that CaitSith seems
> > to fit better than TOMOYO for what many of the questioners want to
> > achieve. You can see slides shown below for full explanation of
> > the how and why.
> >
> > http://I-love.SAKURA.ne.jp/tomoyo/CaitSith-en.pdf (English)
> > http://I-love.SAKURA.ne.jp/tomoyo/CaitSith-ja.pdf (Japanese)
> >
>
> The majority of your points for CaitSith are comparisons to TOMOYO your other
> LSM. In the long run do you see CaitSith replacing Tomoyo, as it seems to be
> the LSM you are now focused on?
I can't predict that. There are users who migrated to CaitSith, but there are
also users who continue using TOMOYO. Users will choose from any implementation
based on what is most suitable for their goals. I'm trying to rescue users
who are trying to use TOMOYO for usages which TOMOYO did not expect.
>
> > In order to minimize the burden of reviewing, this patchset implements
> > only functionality of checking program execution requests (i.e. execve()
> > system call) using pathnames. I'm planning to add other functionalities
> > after this version got included into mainline. You can find how future
> > versions of CaitSith will look like at http://caitsith.osdn.jp/ .
> >
> Thanks I've started working my way through this, but it is going to take
> me a while.
>
Thank you for your time.
Tetsuo Handa wrote:
> John Johansen wrote:
> > > In order to minimize the burden of reviewing, this patchset implements
> > > only functionality of checking program execution requests (i.e. execve()
> > > system call) using pathnames. I'm planning to add other functionalities
> > > after this version got included into mainline. You can find how future
> > > versions of CaitSith will look like at http://caitsith.osdn.jp/ .
> > >
> > Thanks I've started working my way through this, but it is going to take
> > me a while.
> >
>
> Thank you for your time.
May I hear the status? Is there something I can do other than waiting?
I wrote a full manual for this patchset as http://caitsith.osdn.jp/index2.html .
Does anybody have fundamental objection against CaitSith?
On 11/22/2016 10:31 PM, Tetsuo Handa wrote:
> Tetsuo Handa wrote:
>> John Johansen wrote:
>>>> In order to minimize the burden of reviewing, this patchset implements
>>>> only functionality of checking program execution requests (i.e. execve()
>>>> system call) using pathnames. I'm planning to add other functionalities
>>>> after this version got included into mainline. You can find how future
>>>> versions of CaitSith will look like at http://caitsith.osdn.jp/ .
>>>>
>>> Thanks I've started working my way through this, but it is going to take
>>> me a while.
>>>
>>
>> Thank you for your time.
>
> May I hear the status? Is there something I can do other than waiting?
>
progressing very slowly, I have some time over the next few days as its a
long weekend here in the US some hopefully I can finish this up
> I wrote a full manual for this patchset as http://caitsith.osdn.jp/index2.html .
thanks
> Does anybody have fundamental objection against CaitSith?
>
I don't have any objections to CaitSith, but I do worry about the
maintenance of Tomoyo. It feels like there should be a better migration
path for Tomoyo users to CaitSith so that you only have to maintain one
of them.
On 2017/10/22 2:17, Casey Schaufler wrote:
>> As one year elapsed since I proposed CaitSith for upstream, I'd like to
>> hear the status again. I looked at
>> http://schd.ws/hosted_files/lss2017/8b/201709-LinuxSecuritySummit-Stacking.pdf .
>> How is ETA for Security Module Stacking? Is it a half year or so?
>
> Assuming that I can keep working on stacking at my current level,
> and that we can work out a couple issues with audit and networking
> there is a possibility we're looking at mid 2018 for stacking. The
> increased interest in security module namespaces for containers is
> helping make stacking seem important to more people.
>
>> If it is likely take longer, should I resume proposing CaitSith for now
>> as one of "Minor modules" except security_module_enable() check added
>> until Security Module Stacking work completes? Or should I wait for
>> completion of stacking work? I want to know it, for recent proposals are
>> rather staying silent.
>
> I wouldn't wait if it was my project, but I have been known
> to be more aggressive than is good for me from time to time.
>
It seems that stacking needs some more time. Manual with updated patch for
current source code is available at https://caitsith.osdn.jp/index2.html .
John, if you can resume reviewing, I'd like to propose CaitSith as one of
"Minor modules" except security_module_enable() check.
Regards.
On 09/01/2018 06:04 AM, Tetsuo Handa wrote:
> On 2017/10/22 2:17, Casey Schaufler wrote:
>>> As one year elapsed since I proposed CaitSith for upstream, I'd like to
>>> hear the status again. I looked at
>>> http://schd.ws/hosted_files/lss2017/8b/201709-LinuxSecuritySummit-Stacking.pdf .
>>> How is ETA for Security Module Stacking? Is it a half year or so?
>>
>> Assuming that I can keep working on stacking at my current level,
>> and that we can work out a couple issues with audit and networking
>> there is a possibility we're looking at mid 2018 for stacking. The
>> increased interest in security module namespaces for containers is
>> helping make stacking seem important to more people.
>>
>>> If it is likely take longer, should I resume proposing CaitSith for now
>>> as one of "Minor modules" except security_module_enable() check added
>>> until Security Module Stacking work completes? Or should I wait for
>>> completion of stacking work? I want to know it, for recent proposals are
>>> rather staying silent.
>>
>> I wouldn't wait if it was my project, but I have been known
>> to be more aggressive than is good for me from time to time.
>>
>
> It seems that stacking needs some more time. Manual with updated patch for
> current source code is available at https://caitsith.osdn.jp/index2.html .
> John, if you can resume reviewing, I'd like to propose CaitSith as one of
> "Minor modules" except security_module_enable() check.
>
Hey Tetsuo,
yes finishing up the Caitsith review is on my short list