[TOMOYO #6 retry 19/21] Conditional permission support.

Kentaro Takeda
2008-01-09T01:02:42Z

This patch allows administrators use conditional permission.
TOMOYO Linux supports conditional permission based on
process's UID,GID etc. and/or requested pathname's UID/GID.

Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
security/tomoyo/condition.c | 684 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 684 insertions(+)

--- /dev/null
+++ linux-2.6-mm/security/tomoyo/condition.c
@@ -0,0 +1,684 @@
+/*
+ * security/tomoyo/condition.c
+ *
+ * Functions to support conditional access control for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+/**
+ * tmy_find_condition_part - check whether a line contains condition part.
+ * @data: a line to check.
+ *
+ * Returns pointer to condition part if found.
+ * Returns NULL if not found.
+ *
+ * Since the trailing spaces are removed by tmy_normalize_line(),
+ * the last "\040if\040" sequence corresponds to condition part.
+ */
+char *tmy_find_condition_part(char *data)
+{
+ char *cp = strstr(data, " if ");
+ if (cp) {
+ char *cp2;
+ while ((cp2 = strstr(cp + 3, " if ")) != NULL)
+ cp = cp2;
+ *cp++ = '\0';
+ }
+ return cp;
+}
+
+#define VALUE_TYPE_DECIMAL 1 /* 01 */
+#define VALUE_TYPE_OCTAL 2 /* 10 */
+#define VALUE_TYPE_HEXADECIMAL 3 /* 11 */
+
+static int tmy_parse_ulong(unsigned long *result, const char **str)
+{
+ const char *cp = *str;
+ char *ep;
+ int base = 10;
+ if (*cp == '0') {
+ char c = *(cp + 1);
+ if (c == 'x' || c == 'X') {
+ base = 16;
+ cp += 2;
+ } else if (c >= '0' && c <= '7') {
+ base = 8;
+ cp++;
+ }
+ }
+ *result = simple_strtoul(cp, &ep, base);
+ if (cp == ep) /* 00 */
+ return 0;
+ *str = ep;
+ return (base == 16 ? VALUE_TYPE_HEXADECIMAL :
+ (base == 8 ? VALUE_TYPE_OCTAL : VALUE_TYPE_DECIMAL));
+}
+
+static void tmy_print_ulong(char *buffer, const int buffer_len,
+ const unsigned long value, const int type)
+{
+ if (type == VALUE_TYPE_DECIMAL)
+ snprintf(buffer, buffer_len, "%lu", value);
+ else if (type == VALUE_TYPE_OCTAL)
+ snprintf(buffer, buffer_len, "0%lo", value);
+ else
+ snprintf(buffer, buffer_len, "0x%lX", value);
+}
+
+/* List of conditins. */
+static struct condition_list {
+ struct condition_list *next;
+ int length;
+ /* "unsigned long condition[length]" comes here.*/
+} head;
+
+#define TASK_UID 0
+#define TASK_EUID 1
+#define TASK_SUID 2
+#define TASK_FSUID 3
+#define TASK_GID 4
+#define TASK_EGID 5
+#define TASK_SGID 6
+#define TASK_FSGID 7
+#define TASK_PID 8
+#define TASK_PPID 9
+#define PATH1_UID 10
+#define PATH1_GID 11
+#define PATH1_INO 12
+#define PATH1_PARENT_UID 13
+#define PATH1_PARENT_GID 14
+#define PATH1_PARENT_INO 15
+#define PATH2_PARENT_UID 16
+#define PATH2_PARENT_GID 17
+#define PATH2_PARENT_INO 18
+#define MAX_KEYWORD 19
+
+static struct {
+ const char *keyword;
+ const int keyword_len; /* strlen(keyword) */
+} cc_keyword[MAX_KEYWORD] = {
+ [TASK_UID] = { "task.uid", 8 },
+ [TASK_EUID] = { "task.euid", 9 },
+ [TASK_SUID] = { "task.suid", 9 },
+ [TASK_FSUID] = { "task.fsuid", 10 },
+ [TASK_GID] = { "task.gid", 8 },
+ [TASK_EGID] = { "task.egid", 9 },
+ [TASK_SGID] = { "task.sgid", 9 },
+ [TASK_FSGID] = { "task.fsgid", 10 },
+ [TASK_PID] = { "task.pid", 8 },
+ [TASK_PPID] = { "task.ppid", 9 },
+ [PATH1_UID] = { "path1.uid", 9 },
+ [PATH1_GID] = { "path1.gid", 9 },
+ [PATH1_INO] = { "path1.ino", 9 },
+ [PATH1_PARENT_UID] = { "path1.parent.uid", 16 },
+ [PATH1_PARENT_GID] = { "path1.parent.gid", 16 },
+ [PATH1_PARENT_INO] = { "path1.parent.ino", 16 },
+ [PATH2_PARENT_UID] = { "path2.parent.uid", 16 },
+ [PATH2_PARENT_GID] = { "path2.parent.gid", 16 },
+ [PATH2_PARENT_INO] = { "path2.parent.ino", 16 }
+};
+
+/**
+ * tmy_assign_condition - create condition part.
+ * @condition: pointer to condition part.
+ *
+ * Returns pointer to "struct condition_list" on success.
+ * Returns NULL on failure.
+ */
+const struct condition_list *tmy_assign_condition(const char *condition)
+{
+ const char *start;
+ struct condition_list *ptr;
+ struct condition_list *new_ptr;
+ unsigned long *ptr2;
+ int counter = 0;
+ int size;
+ int left;
+ int right;
+ unsigned long left_min = 0;
+ unsigned long left_max = 0;
+ unsigned long right_min = 0;
+ unsigned long right_max = 0;
+ if (strncmp(condition, "if ", 3))
+ return NULL;
+ condition += 3;
+ start = condition;
+ while (*condition) {
+ if (*condition == ' ')
+ condition++;
+ for (left = 0; left < MAX_KEYWORD; left++) {
+ if (strncmp(condition, cc_keyword[left].keyword,
+ cc_keyword[left].keyword_len))
+ continue;
+ condition += cc_keyword[left].keyword_len;
+ break;
+ }
+ if (left == MAX_KEYWORD) {
+ if (!tmy_parse_ulong(&left_min, &condition))
+ goto out;
+ counter++; /* body */
+ if (*condition != '-')
+ goto not_range1;
+ condition++;
+ if (!tmy_parse_ulong(&left_max, &condition)
+ || left_min > left_max)
+ goto out;
+ counter++; /* body */
+not_range1: ;
+ }
+ if (strncmp(condition, "!=", 2) == 0)
+ condition += 2;
+ else if (*condition == '=')
+ condition++;
+ else
+ goto out;
+ counter++; /* header */
+ for (right = 0; right < MAX_KEYWORD; right++) {
+ if (strncmp(condition, cc_keyword[right].keyword,
+ cc_keyword[right].keyword_len))
+ continue;
+ condition += cc_keyword[right].keyword_len;
+ break;
+ }
+ if (right == MAX_KEYWORD) {
+ if (!tmy_parse_ulong(&right_min, &condition))
+ goto out;
+ counter++; /* body */
+ if (*condition != '-')
+ goto not_range2;
+ condition++;
+ if (!tmy_parse_ulong(&right_max, &condition)
+ || right_min > right_max)
+ goto out;
+ counter++; /* body */
+not_range2: ;
+ }
+ }
+ size = sizeof(*new_ptr) + counter * sizeof(unsigned long);
+ new_ptr = tmy_alloc(size);
+ if (!new_ptr)
+ return NULL;
+ new_ptr->length = counter;
+ ptr2 = (unsigned long *) (((u8 *) new_ptr) + sizeof(*new_ptr));
+ condition = start;
+ while (*condition) {
+ unsigned int match = 0;
+ if (*condition == ' ')
+ condition++;
+ for (left = 0; left < MAX_KEYWORD; left++) {
+ if (strncmp(condition, cc_keyword[left].keyword,
+ cc_keyword[left].keyword_len))
+ continue;
+ condition += cc_keyword[left].keyword_len;
+ break;
+ }
+ if (left == MAX_KEYWORD) {
+ match |= tmy_parse_ulong(&left_min, &condition) << 2;
+ counter--; /* body */
+ if (*condition != '-')
+ goto not_range3;
+ condition++;
+ match |= tmy_parse_ulong(&left_max, &condition) << 4;
+ counter--; /* body */
+ left++;
+not_range3: ;
+ }
+ if (strncmp(condition, "!=", 2) == 0)
+ condition += 2;
+ else if (*condition == '=') {
+ match |= 1;
+ condition++;
+ } else
+ goto out2;
+ counter--; /* header */
+ for (right = 0; right < MAX_KEYWORD; right++) {
+ if (strncmp(condition, cc_keyword[right].keyword,
+ cc_keyword[right].keyword_len))
+ continue;
+ condition += cc_keyword[right].keyword_len;
+ break;
+ }
+ if (right == MAX_KEYWORD) {
+ match |= tmy_parse_ulong(&right_min, &condition) << 6;
+ counter--; /* body */
+ if (*condition != '-')
+ goto not_range4;
+ condition++;
+ match |= tmy_parse_ulong(&right_max, &condition) << 8;
+ counter--; /* body */
+ right++;
+not_range4: ;
+ }
+ if (counter < 0)
+ goto out2;
+ *ptr2++ = (match << 16) | (left << 8) | right;
+ if (left >= MAX_KEYWORD)
+ *ptr2++ = left_min;
+ if (left == MAX_KEYWORD + 1)
+ *ptr2++ = left_max;
+ if (right >= MAX_KEYWORD)
+ *ptr2++ = right_min;
+ if (right == MAX_KEYWORD + 1)
+ *ptr2++ = right_max;
+ }
+ {
+ static DEFINE_MUTEX(mutex);
+ struct condition_list *prev = NULL;
+ mutex_lock(&mutex);
+ for (ptr = &head; ptr; prev = ptr, ptr = ptr->next) {
+ /* Don't compare if size differs. */
+ if (ptr->length != new_ptr->length)
+ continue;
+ /*
+ * Compare ptr and new_ptr
+ * except ptr->next and new_ptr->next .
+ */
+ if (memcmp(((u8 *) ptr) + sizeof(ptr->next),
+ ((u8 *) new_ptr) + sizeof(new_ptr->next),
+ size - sizeof(ptr->next)))
+ continue;
+ /* Same entry found. Share this entry. */
+ tmy_free(new_ptr);
+ new_ptr = ptr;
+ goto ok;
+ }
+ /* Same entry not found. Save this entry. */
+ ptr = tmy_alloc_element(size);
+ if (ptr)
+ memmove(ptr, new_ptr, size);
+ tmy_free(new_ptr);
+ new_ptr = ptr;
+ /* Append to chain. */
+ prev->next = new_ptr;
+ok: ;
+ mutex_unlock(&mutex);
+ }
+ return new_ptr;
+out2: ;
+ tmy_free(new_ptr);
+out: ;
+ return NULL;
+}
+
+/* Get inode's attribute information. */
+static void tmy_get_attributes(struct obj_info *obj)
+{
+ struct vfsmount *mnt;
+ struct dentry *dentry;
+ struct inode *inode;
+ struct kstat stat;
+
+ mnt = obj->path1_vfsmnt;
+ dentry = obj->path1_dentry;
+ inode = dentry->d_inode;
+ if (inode) {
+ if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+ /* Nothing to do. */
+ } else {
+ obj->path1_stat.uid = stat.uid;
+ obj->path1_stat.gid = stat.gid;
+ obj->path1_stat.ino = stat.ino;
+ obj->path1_valid = 1;
+ }
+ }
+
+ dentry = dget_parent(obj->path1_dentry);
+ inode = dentry->d_inode;
+ if (inode) {
+ if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+ /* Nothing to do. */
+ } else {
+ obj->path1_parent_stat.uid = stat.uid;
+ obj->path1_parent_stat.gid = stat.gid;
+ obj->path1_parent_stat.ino = stat.ino;
+ obj->path1_parent_valid = 1;
+ }
+ }
+ dput(dentry);
+
+ mnt = obj->path2_vfsmnt;
+ if (mnt) {
+ dentry = dget_parent(obj->path2_dentry);
+ inode = dentry->d_inode;
+ if (inode) {
+ if (!inode->i_op || vfs_getattr(mnt, dentry, &stat)) {
+ /* Nothing to do. */
+ } else {
+ obj->path2_parent_stat.uid = stat.uid;
+ obj->path2_parent_stat.gid = stat.gid;
+ obj->path2_parent_stat.ino = stat.ino;
+ obj->path2_parent_valid = 1;
+ }
+ }
+ dput(dentry);
+ }
+}
+
+/**
+ * tmy_check_condition - check condition part.
+ * @ptr: pointer to "struct condition_list".
+ * @obj: pointer to "struct obj_info". May be NULL.
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_check_condition(const struct condition_list *ptr, struct obj_info *obj)
+{
+ struct task_struct *task = current;
+ int i;
+ unsigned long left_min = 0;
+ unsigned long left_max = 0;
+ unsigned long right_min = 0;
+ unsigned long right_max = 0;
+ const unsigned long *ptr2;
+ if (!ptr)
+ return 0;
+ ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+ for (i = 0; i < ptr->length; i++) {
+ const bool match = ((*ptr2) >> 16) & 1;
+ const u8 left = (*ptr2) >> 8;
+ const u8 right = *ptr2;
+ ptr2++;
+ if ((left >= PATH1_UID && left < MAX_KEYWORD)
+ || (right >= PATH1_UID && right < MAX_KEYWORD)) {
+ if (!obj)
+ goto out;
+ if (!obj->validate_done) {
+ tmy_get_attributes(obj);
+ obj->validate_done = 1;
+ }
+ }
+ switch (left) {
+ case TASK_UID:
+ left_min = task->uid;
+ left_max = left_min;
+ break;
+ case TASK_EUID:
+ left_min = task->euid;
+ left_max = left_min;
+ break;
+ case TASK_SUID:
+ left_min = task->suid;
+ left_max = left_min;
+ break;
+ case TASK_FSUID:
+ left_min = task->fsuid;
+ left_max = left_min;
+ break;
+ case TASK_GID:
+ left_min = task->gid;
+ left_max = left_min;
+ break;
+ case TASK_EGID:
+ left_min = task->egid;
+ left_max = left_min;
+ break;
+ case TASK_SGID:
+ left_min = task->sgid;
+ left_max = left_min;
+ break;
+ case TASK_FSGID:
+ left_min = task->fsgid;
+ left_max = left_min;
+ break;
+ case TASK_PID:
+ left_min = task->pid;
+ left_max = left_min;
+ break;
+ case TASK_PPID:
+ left_min = sys_getppid();
+ left_max = left_min;
+ break;
+ case PATH1_UID:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH1_GID:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH1_INO:
+ if (!obj->path1_valid)
+ goto out;
+ left_min = obj->path1_stat.ino;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_UID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_GID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH1_PARENT_INO:
+ if (!obj->path1_parent_valid)
+ goto out;
+ left_min = obj->path1_parent_stat.ino;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_UID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.uid;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_GID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.gid;
+ left_max = left_min;
+ break;
+ case PATH2_PARENT_INO:
+ if (!obj->path2_parent_valid)
+ goto out;
+ left_min = obj->path2_parent_stat.ino;
+ left_max = left_min;
+ break;
+ case MAX_KEYWORD:
+ left_min = *ptr2++;
+ left_max = left_min;
+ i++;
+ break;
+ case MAX_KEYWORD + 1:
+ left_min = *ptr2++;
+ left_max = *ptr2++;
+ i += 2;
+ break;
+ }
+ switch (right) {
+ case TASK_UID:
+ right_min = task->uid;
+ right_max = right_min;
+ break;
+ case TASK_EUID:
+ right_min = task->euid;
+ right_max = right_min;
+ break;
+ case TASK_SUID:
+ right_min = task->suid;
+ right_max = right_min;
+ break;
+ case TASK_FSUID:
+ right_min = task->fsuid;
+ right_max = right_min;
+ break;
+ case TASK_GID:
+ right_min = task->gid;
+ right_max = right_min;
+ break;
+ case TASK_EGID:
+ right_min = task->egid;
+ right_max = right_min;
+ break;
+ case TASK_SGID:
+ right_min = task->sgid;
+ right_max = right_min;
+ break;
+ case TASK_FSGID:
+ right_min = task->fsgid;
+ right_max = right_min;
+ break;
+ case TASK_PID:
+ right_min = task->pid;
+ right_max = right_min;
+ break;
+ case TASK_PPID:
+ right_min = sys_getppid();
+ right_max = right_min;
+ break;
+ case PATH1_UID:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH1_GID:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH1_INO:
+ if (!obj->path1_valid)
+ goto out;
+ right_min = obj->path1_stat.ino;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_UID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_GID:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH1_PARENT_INO:
+ if (!obj->path1_parent_valid)
+ goto out;
+ right_min = obj->path1_parent_stat.ino;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_UID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.uid;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_GID:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.gid;
+ right_max = right_min;
+ break;
+ case PATH2_PARENT_INO:
+ if (!obj->path2_parent_valid)
+ goto out;
+ right_min = obj->path2_parent_stat.ino;
+ right_max = right_min;
+ break;
+ case MAX_KEYWORD:
+ right_min = *ptr2++;
+ right_max = right_min;
+ i++;
+ break;
+ case MAX_KEYWORD + 1:
+ right_min = *ptr2++;
+ right_max = *ptr2++;
+ i += 2;
+ break;
+ }
+ if (match) {
+ if (left_min <= right_max && left_max >= right_min)
+ continue;
+ } else {
+ if (left_min > right_max || left_max < right_min)
+ continue;
+ }
+out: ;
+ return -EPERM;
+ }
+ return 0;
+}
+
+/**
+ * tmy_dump_condition - dump condition part.
+ * @head: pointer to "struct io_buffer".
+ * @ptr: pointer to "struct condition_list". May be NULL.
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_dump_condition(struct io_buffer *head, const struct condition_list *ptr)
+{
+ int i;
+ const unsigned long *ptr2;
+ char buffer[32];
+ if (!ptr)
+ goto last;
+ ptr2 = (unsigned long *) (((u8 *) ptr) + sizeof(*ptr));
+ memset(buffer, 0, sizeof(buffer));
+ for (i = 0; i < ptr->length; i++) {
+ const u16 match = (*ptr2) >> 16;
+ const u8 left = (*ptr2) >> 8;
+ const u8 right = *ptr2;
+ ptr2++;
+ if (tmy_io_printf(head, "%s", i ? " " : " if "))
+ break;
+ if (left < MAX_KEYWORD) {
+ if (tmy_io_printf(head, "%s", cc_keyword[left].keyword))
+ break;
+ } else {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+ (match >> 2) & 3);
+ if (tmy_io_printf(head, "%s", buffer))
+ break;
+ i++;
+ if (left == MAX_KEYWORD + 1) {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1,
+ *ptr2++, (match >> 4) & 3);
+ if (tmy_io_printf(head, "-%s", buffer))
+ break;
+ i++;
+ }
+ }
+ if (tmy_io_printf(head, "%s", (match & 1) ? "=" : "!="))
+ break;
+ if (right < MAX_KEYWORD) {
+ if (tmy_io_printf(head, "%s",
+ cc_keyword[right].keyword))
+ break;
+ } else {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1, *ptr2++,
+ (match >> 6) & 3);
+ if (tmy_io_printf(head, "%s", buffer))
+ break;
+ i++;
+ if (right == MAX_KEYWORD + 1) {
+ tmy_print_ulong(buffer, sizeof(buffer) - 1,
+ *ptr2++, (match >> 8) & 3);
+ if (tmy_io_printf(head, "-%s", buffer))
+ break;
+ i++;
+ }
+ }
+ }
+ if (i < ptr->length)
+ return -ENOMEM;
+last: ;
+ return tmy_io_printf(head, "\n") ? -ENOMEM : 0;
+}

--