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 <penguin-ker...@i-love.sakura.ne.jp>
---
 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

Reply via email to