From: Kentaro Takeda <takedakn@nttdata.co.jp>
To: akpm@linux-foundation.org
Cc: linux-kernel@vger.kernel.org,
linux-security-module@vger.kernel.org,
Kentaro Takeda <takedakn@nttdata.co.jp>,
Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Subject: [TOMOYO #6 retry 11/21] File access control functions.
Date: Wed, 09 Jan 2008 09:53:31 +0900 [thread overview]
Message-ID: <20080109005423.203226400@nttdata.co.jp> (raw)
In-Reply-To: 20080109005320.323184643@nttdata.co.jp
TOMOYO Linux checks permission in
open/creat/unlink/truncate/ftruncate/mknod/mkdir/
rmdir/symlink/link/rename/uselib/sysctl .
Each permission can be automatically accumulated into
the policy of each domain using 'learning mode'.
Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
security/tomoyo/file.c | 1468 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1468 insertions(+)
--- /dev/null
+++ linux-2.6-mm/security/tomoyo/file.c
@@ -0,0 +1,1468 @@
+/*
+ * security/tomoyo/file.c
+ *
+ * File access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
+
+/************************* VARIABLES *************************/
+
+/***** The structure for globally readable files. *****/
+
+struct globally_readable_file_entry {
+ struct list_head list;
+ const struct path_info *filename;
+ bool is_deleted;
+};
+
+/***** The structure for filename patterns. *****/
+
+struct pattern_entry {
+ struct list_head list;
+ const struct path_info *pattern;
+ bool is_deleted;
+};
+
+/***** The structure for non-rewritable-by-default file patterns. *****/
+
+struct no_rewrite_entry {
+ struct list_head list;
+ const struct path_info *pattern;
+ bool is_deleted;
+};
+
+/***** The structure for detailed write operations. *****/
+
+static struct {
+ const char *keyword;
+ const int paths;
+} acl_type_array[] = {
+ { "create", 1 }, /* TMY_TYPE_CREATE_ACL */
+ { "unlink", 1 }, /* TMY_TYPE_UNLINK_ACL */
+ { "mkdir", 1 }, /* TMY_TYPE_MKDIR_ACL */
+ { "rmdir", 1 }, /* TMY_TYPE_RMDIR_ACL */
+ { "mkfifo", 1 }, /* TMY_TYPE_MKFIFO_ACL */
+ { "mksock", 1 }, /* TMY_TYPE_MKSOCK_ACL */
+ { "mkblock", 1 }, /* TMY_TYPE_MKBLOCK_ACL */
+ { "mkchar", 1 }, /* TMY_TYPE_MKCHAR_ACL */
+ { "truncate", 1 }, /* TMY_TYPE_TRUNCATE_ACL */
+ { "symlink", 1 }, /* TMY_TYPE_SYMLINK_ACL */
+ { "link", 2 }, /* TMY_TYPE_LINK_ACL */
+ { "rename", 2 }, /* TMY_TYPE_RENAME_ACL */
+ { "rewrite", 1 }, /* TMY_TYPE_REWRITE_ACL */
+ { NULL, 0 }
+};
+
+/************************* UTILITY FUNCTIONS *************************/
+
+/**
+ * tmy_acltype2keyword - get keyword from access control index.
+ * @acl_type: index number.
+ *
+ * Returns keyword that corresponds with @acl_type .
+ */
+const char *tmy_acltype2keyword(const unsigned int acl_type)
+{
+ return (acl_type < ARRAY_SIZE(acl_type_array))
+ ? acl_type_array[acl_type].keyword : NULL;
+}
+
+/**
+ * tmy_acltype2paths - get number of arguments from access control index.
+ * @acl_type: index number.
+ *
+ * Returns number of arguments that corresponds with @acl_type .
+ */
+int tmy_acltype2paths(const unsigned int acl_type)
+{
+ return (acl_type < ARRAY_SIZE(acl_type_array))
+ ? acl_type_array[acl_type].paths : 0;
+}
+
+static int tmy_strendswith(const char *name, const char *tail)
+{
+ int len;
+
+ if (!name || !tail)
+ return 0;
+
+ len = strlen(name) - strlen(tail);
+ return len >= 0 && strcmp(name + len, tail) == 0;
+}
+
+static struct path_info *tmy_get_path(struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ /* sizeof(struct path_info_with_data) <= PAGE_SIZE */
+ struct path_info_with_data {
+ /* Keep this first, this pointer is passed to tmy_free(). */
+ struct path_info head;
+ char bariier1[16];
+ char body[TMY_MAX_PATHNAME_LEN];
+ char barrier2[16];
+ } *buf = tmy_alloc(sizeof(*buf));
+
+ if (buf) {
+ int error = tmy_realpath_dentry2(dentry,
+ mnt,
+ buf->body,
+ sizeof(buf->body) - 1);
+
+ if (error == 0) {
+ buf->head.name = buf->body;
+ tmy_fill_path_info(&buf->head);
+ return &buf->head;
+ }
+
+ tmy_free(buf);
+ buf = NULL;
+ printk(KERN_INFO "tmy_realpath_dentry = %d\n", error);
+ }
+
+ return NULL;
+}
+
+/************************* PROTOTYPES *************************/
+
+static int tmy_add_double_write_acl(const u8 type,
+ const char *filename1,
+ const char *filename2,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const bool is_delete);
+static int tmy_add_single_write_acl(const u8 type,
+ const char *filename,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const bool is_delete);
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_file_log(const struct path_info *filename,
+ const u8 perm,
+ const bool is_granted,
+ const u8 profile,
+ const unsigned int mode)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = filename->total_len + 8;
+ buf = tmy_init_audit_log(&len, profile, mode);
+
+ if (!buf)
+ return -ENOMEM;
+
+ tmy_sncatprintf(buf, len - 1, "%d %s\n", perm, filename->name);
+
+ return tmy_write_audit_log(buf, is_granted);
+}
+
+static int tmy_audit_write_log(const char *operation,
+ const struct path_info *filename1,
+ const struct path_info *filename2,
+ const bool is_granted,
+ const u8 profile,
+ const unsigned int mode)
+{
+ char *buf;
+ int len;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ len = strlen(operation) +
+ filename1->total_len +
+ (filename2 ? filename2->total_len : 0)
+ + 16;
+
+ buf = tmy_init_audit_log(&len, profile, mode);
+ if (!buf)
+ return -ENOMEM;
+
+ tmy_sncatprintf(buf, len - 1, "allow_%s %s %s\n",
+ operation, filename1->name, filename2 ? filename2->name : "");
+
+ return tmy_write_audit_log(buf, is_granted);
+}
+
+/********************** GLOBALLY READABLE FILE HANDLER **********************/
+
+static LIST_HEAD(globally_readable_list);
+
+static int tmy_add_globally_readable_entry(const char *filename,
+ const bool is_delete)
+{
+ struct globally_readable_file_entry *new_entry;
+ struct globally_readable_file_entry *ptr;
+ static DEFINE_MUTEX(mutex);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(filename, 1, -1, -1, __FUNCTION__))
+ return -EINVAL; /* No patterns allowed. */
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+
+ mutex_lock(&mutex);
+
+ list_for_each_entry(ptr, &globally_readable_list, list) {
+ if (ptr->filename == saved) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->filename = saved;
+ list_add_tail_mb(&new_entry->list, &globally_readable_list);
+ error = 0;
+
+out: ;
+ mutex_unlock(&mutex);
+
+ return error;
+}
+
+static int tmy_globally_readable(const struct path_info *filename)
+{
+ struct globally_readable_file_entry *ptr;
+
+ list_for_each_entry(ptr, &globally_readable_list, list) {
+ if (!ptr->is_deleted &&
+ !tmy_pathcmp(filename, ptr->filename))
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_add_globally_readable_policy - add or delete globally readable policy.
+ * @filename: pointer to filename to add ore remove.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_globally_readable_policy(char *filename, const bool is_delete)
+{
+ return tmy_add_globally_readable_entry(filename, is_delete);
+}
+
+/**
+ * tmy_read_globally_readable_policy - read globally readable policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_globally_readable_policy(struct io_buffer *head)
+{
+ struct list_head *pos;
+ list_for_each_cookie(pos, head->read_var2, &globally_readable_list) {
+ struct globally_readable_file_entry *ptr;
+ ptr = list_entry(pos, struct globally_readable_file_entry,
+ list);
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_io_printf(head, TMY_ALLOW_READ "%s\n",
+ ptr->filename->name))
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/************************* FILE GROUP HANDLER *************************/
+
+static LIST_HEAD(path_group_list);
+
+static int tmy_add_group_entry(const char *group_name,
+ const char *member_name,
+ const bool is_delete)
+{
+ static DEFINE_MUTEX(mutex);
+ struct path_group_entry *new_group;
+ struct path_group_entry *group;
+ struct path_group_member *new_member;
+ struct path_group_member *member;
+ const struct path_info *saved_group;
+ const struct path_info *saved_member;
+ int error = -ENOMEM;
+ bool found = 0;
+
+ if (!tmy_correct_path(group_name, 0, 0, 0, __FUNCTION__) ||
+ !group_name[0] ||
+ !tmy_correct_path(member_name, 0, 0, 0, __FUNCTION__) ||
+ !member_name[0])
+ return -EINVAL;
+
+ saved_group = tmy_save_name(group_name);
+ saved_member = tmy_save_name(member_name);
+
+ if (!saved_group || !saved_member)
+ return -ENOMEM;
+
+ mutex_lock(&mutex);
+ list_for_each_entry(group, &path_group_list, list) {
+ if (saved_group != group->group_name)
+ continue;
+ list_for_each_entry(member, &group->path_group_member_list,
+ list) {
+ if (member->member_name == saved_member) {
+ member->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+ found = 1;
+ break;
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ if (!found) {
+ new_group = tmy_alloc_element(sizeof(*new_group));
+ if (!new_group)
+ goto out;
+ INIT_LIST_HEAD(&new_group->path_group_member_list);
+ new_group->group_name = saved_group;
+ list_add_tail_mb(&new_group->list, &path_group_list);
+ group = new_group;
+ }
+
+ new_member = tmy_alloc_element(sizeof(*new_member));
+ if (!new_member)
+ goto out;
+ new_member->member_name = saved_member;
+ list_add_tail_mb(&new_member->list, &group->path_group_member_list);
+ error = 0;
+out: ;
+ mutex_unlock(&mutex);
+
+ return error;
+}
+
+/**
+ * tmy_add_group_policy - add or delete path group policy.
+ * @data: a line to parse.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_group_policy(char *data, const bool is_delete)
+{
+ char *cp = strchr(data, ' ');
+
+ if (!cp)
+ return -EINVAL;
+ *cp++ = '\0';
+ return tmy_add_group_entry(data, cp, is_delete);
+}
+
+static struct path_group_entry *tmy_new_path_group(const char *group_name)
+{
+ int i;
+ struct path_group_entry *group;
+
+ for (i = 0; i <= 1; i++) {
+ list_for_each_entry(group, &path_group_list, list) {
+ if (strcmp(group_name, group->group_name->name) == 0)
+ return group;
+ }
+
+ if (i == 0) {
+ /*
+ * Add a dummy entry to create new path group
+ * and delete that entry.
+ */
+ tmy_add_group_entry(group_name, "/", 0);
+ tmy_add_group_entry(group_name, "/", 1);
+ }
+ }
+
+ return NULL;
+}
+
+static int tmy_path_match_group(const struct path_info *pathname,
+ const struct path_group_entry *group,
+ const int may_use_pattern)
+{
+ struct path_group_member *member;
+ list_for_each_entry(member, &group->path_group_member_list, list) {
+ if (member->is_deleted)
+ continue;
+ if (!member->member_name->is_patterned) {
+ if (!tmy_pathcmp(pathname, member->member_name))
+ return 1;
+ } else if (may_use_pattern) {
+ if (tmy_path_match(pathname, member->member_name))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_read_path_group_policy - read path group policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_path_group_policy(struct io_buffer *head)
+{
+ struct list_head *gpos;
+ struct list_head *mpos;
+ list_for_each_cookie(gpos, head->read_var1, &path_group_list) {
+ struct path_group_entry *group;
+ group = list_entry(gpos, struct path_group_entry, list);
+ list_for_each_cookie(mpos, head->read_var2,
+ &group->path_group_member_list) {
+ struct path_group_member *member;
+ member = list_entry(mpos, struct path_group_member,
+ list);
+ if (member->is_deleted)
+ continue;
+ if (tmy_io_printf(head,
+ TMY_PATH_GROUP "%s %s\n",
+ group->group_name->name,
+ member->member_name->name))
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+/************************* FILE PATTERN HANDLER *************************/
+
+static LIST_HEAD(pattern_list);
+
+static int tmy_add_pattern_entry(const char *pattern, const bool is_delete)
+{
+ struct pattern_entry *new_entry;
+ struct pattern_entry *ptr;
+ static DEFINE_MUTEX(mutex);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(pattern, 0, 1, 0, __FUNCTION__))
+ return -EINVAL;
+
+ saved = tmy_save_name(pattern);
+ if (!saved)
+ return -ENOMEM;
+
+ mutex_lock(&mutex);
+
+ list_for_each_entry(ptr, &pattern_list, list) {
+ if (saved == ptr->pattern) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+
+ if (!new_entry)
+ goto out;
+ new_entry->pattern = saved;
+ list_add_tail_mb(&new_entry->list, &pattern_list);
+ error = 0;
+out: ;
+ mutex_unlock(&mutex);
+ return error;
+}
+
+static const struct path_info *tmy_get_pattern(const struct path_info *filename)
+{
+ struct pattern_entry *ptr;
+ const struct path_info *pattern = NULL;
+
+ list_for_each_entry(ptr, &pattern_list, list) {
+ if (ptr->is_deleted)
+ continue;
+ if (!tmy_path_match(filename, ptr->pattern))
+ continue;
+ pattern = ptr->pattern;
+ if (!tmy_strendswith(pattern->name, "/\\*"))
+ break;
+ }
+
+ if (pattern)
+ filename = pattern;
+
+ return filename;
+}
+
+/**
+ * tmy_add_pattern_policy - add or delete file pattern policy.
+ * @pattern: pointer to file pattern entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_pattern_policy(char *pattern, const bool is_delete)
+{
+ return tmy_add_pattern_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_pattern_policy - read file pattern policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_pattern_policy(struct io_buffer *head)
+{
+ struct list_head *pos;
+ list_for_each_cookie(pos, head->read_var2, &pattern_list) {
+ struct pattern_entry *ptr;
+ ptr = list_entry(pos, struct pattern_entry, list);
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_io_printf(head, TMY_FILE_PATTERN "%s\n",
+ ptr->pattern->name))
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/*********************** NON REWRITABLE FILE HANDLER ***********************/
+
+static LIST_HEAD(no_rewrite_list);
+
+static int tmy_add_no_rewrite_entry(const char *pattern, const bool is_delete)
+{
+ struct no_rewrite_entry *new_entry;
+ struct no_rewrite_entry *ptr;
+ static DEFINE_MUTEX(mutex);
+ const struct path_info *saved;
+ int error = -ENOMEM;
+
+ if (!tmy_correct_path(pattern, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+ saved = tmy_save_name(pattern);
+ if (!saved)
+ return -ENOMEM;
+
+ mutex_lock(&mutex);
+ list_for_each_entry(ptr, &no_rewrite_list, list) {
+ if (ptr->pattern == saved) {
+ ptr->is_deleted = is_delete;
+ error = 0;
+ goto out;
+ }
+ }
+
+ if (is_delete) {
+ error = -ENOENT;
+ goto out;
+ }
+
+ new_entry = tmy_alloc_element(sizeof(*new_entry));
+ if (!new_entry)
+ goto out;
+
+ new_entry->pattern = saved;
+ list_add_tail_mb(&new_entry->list, &no_rewrite_list);
+ error = 0;
+out: ;
+ mutex_unlock(&mutex);
+
+ return error;
+}
+
+static int tmy_is_no_rewrite_file(const struct path_info *filename)
+{
+ struct no_rewrite_entry *ptr;
+
+ list_for_each_entry(ptr, &no_rewrite_list, list) {
+ if (ptr->is_deleted)
+ continue;
+ if (!tmy_path_match(filename, ptr->pattern))
+ continue;
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * tmy_add_no_rewrite_policy - add or delete no-rewrite policy.
+ * @pattern: pointer to no-rewrite entry.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_no_rewrite_policy(char *pattern, const bool is_delete)
+{
+ return tmy_add_no_rewrite_entry(pattern, is_delete);
+}
+
+/**
+ * tmy_read_no_rewrite_policy - read no-rewrite policy.
+ * @head: pointer to "struct io_buffer".
+ *
+ * Returns nonzero if reading incomplete.
+ * Returns zero otherwise.
+ */
+int tmy_read_no_rewrite_policy(struct io_buffer *head)
+{
+ struct list_head *pos;
+ list_for_each_cookie(pos, head->read_var2, &no_rewrite_list) {
+ struct no_rewrite_entry *ptr;
+ ptr = list_entry(pos, struct no_rewrite_entry, list);
+ if (ptr->is_deleted)
+ continue;
+ if (tmy_io_printf(head, TMY_DENY_REWRITE "%s\n",
+ ptr->pattern->name))
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/************************* FILE ACL HANDLER *************************/
+
+static int tmy_add_file_acl(const char *filename,
+ u8 perm,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const bool is_delete)
+{
+ const struct path_info *saved;
+ struct acl_info *ptr;
+ struct file_acl *acl;
+ int error = -ENOMEM;
+ bool is_group = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (perm > 7 || !perm) {
+ printk(KERN_DEBUG "%s: Invalid permission '%d %s'\n",
+ __FUNCTION__, perm, filename);
+ return -EINVAL;
+ }
+ if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved = (struct path_info *) tmy_new_path_group(filename + 1);
+ if (!saved)
+ return -ENOMEM;
+ is_group = 1;
+ } else {
+
+ if (tmy_strendswith(filename, "/"))
+ /*
+ * Valid permissions for directory are
+ * only 'allow_mkdir' and 'allow_rmdir'.
+ */
+ return 0;
+
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+
+ if (!is_delete && perm == 4 &&
+ tmy_globally_readable(saved))
+ return 0;
+
+ }
+
+ mutex_lock(&domain_acl_lock);
+
+ if (is_delete)
+ goto remove;
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct file_acl, head);
+ if ((ptr->type == TMY_TYPE_FILE_ACL) &&
+ ptr->cond == cond &&
+ (acl->u.filename == saved)) {
+ if (ptr->is_deleted) {
+ acl->perm = 0;
+ mb(); /* Avoid out-of-order execution. */
+ ptr->is_deleted = 0;
+ }
+ /* Found. Just 'OR' the permission bits. */
+ acl->perm |= perm;
+ error = 0;
+ tmy_update_counter(TMY_UPDATE_DOMAINPOLICY);
+ goto ok;
+ }
+ }
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ goto ok;
+
+ acl->head.type = TMY_TYPE_FILE_ACL;
+ acl->head.cond = cond;
+ acl->perm = perm;
+ acl->u_is_group = is_group;
+ acl->u.filename = saved;
+ error = tmy_add_acl(domain, &acl->head);
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct file_acl, head);
+ if (ptr->type != TMY_TYPE_FILE_ACL ||
+ ptr->cond != cond ||
+ ptr->is_deleted ||
+ acl->perm != perm ||
+ acl->u.filename != saved)
+ continue;
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ mutex_unlock(&domain_acl_lock);
+ return error;
+}
+
+static int tmy_file_acl(const struct path_info *filename, const u8 perm,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+ const int may_use_pat = ((perm & 1) == 0);
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ if (!filename->is_dir) {
+ if (perm == 4 && tmy_globally_readable(filename))
+ return 0;
+ }
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ struct file_acl *acl;
+ acl = container_of(ptr, struct file_acl, head);
+
+ if (ptr->type != TMY_TYPE_FILE_ACL ||
+ ptr->is_deleted ||
+ (acl->perm & perm) != perm ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u_is_group) {
+ if (tmy_path_match_group(filename,
+ acl->u.group,
+ may_use_pat))
+ return 0;
+ } else {
+ if ((may_use_pat || !acl->u.filename->is_patterned) &&
+ tmy_path_match(filename, acl->u.filename))
+ return 0;
+ }
+ }
+
+ return -EPERM;
+}
+
+static int tmy_file_perm2(const struct path_info *filename,
+ const u8 perm,
+ struct obj_info *obj,
+ const char *operation)
+{
+ int error = 0;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const u8 profile = domain->profile;
+ const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+ const bool is_enforce = (mode == 3);
+
+ if (!filename)
+ return 0;
+
+ error = tmy_file_acl(filename, perm, obj);
+
+ tmy_audit_file_log(filename, perm, !error, profile, mode);
+
+ if (!error)
+ return error;
+
+ if (tmy_flags(TMY_VERBOSE))
+ tmy_audit("TOMOYO-%s: Access %d(%s) to %s denied for %s\n",
+ tmy_getmsg(is_enforce), perm, operation,
+ filename->name, tmy_lastname(domain));
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\n%d %s\n",
+ domain->domainname->name,
+ perm, filename->name);
+
+ else if (mode == 1 && tmy_quota()) {
+ /* Don't use patterns if execution bit is on. */
+ const struct path_info *patterned =
+ ((perm & 1) == 0) ?
+ tmy_get_pattern(filename) : filename;
+ tmy_add_file_acl(patterned->name, perm, domain, NULL, 0);
+ }
+
+ if (!is_enforce)
+ error = 0;
+
+ return error;
+}
+
+/**
+ * tmy_file_perm - check permission for sysctl(2) operation.
+ * @filename0: pointer to filename returned by sysctlpath_from_table().
+ * @perm: mode (read = 4, write = 2, read-write = 6).
+ * @operation: pointer to error message.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_file_perm(const char *filename0, const u8 perm, const char *operation)
+{
+ struct path_info filename;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ filename.name = filename0;
+ tmy_fill_path_info(&filename);
+
+ return tmy_file_perm2(&filename, perm, NULL, operation);
+}
+
+/**
+ * tmy_add_file_policy - add or delete file policy.
+ * @data: a line to parse.
+ * @domain: pointer to "struct domain_info".
+ * @cond: pointer to "struct condition_list". May be NULL.
+ * @is_delete: is this delete request?
+ *
+ * Returns zero on success.
+ * Returns nonzero on failure.
+ */
+int tmy_add_file_policy(char *data,
+ struct domain_info *domain,
+ const struct condition_list *cond,
+ const bool is_delete)
+{
+ char *filename = strchr(data, ' ');
+ unsigned int perm;
+ u8 type;
+
+ if (!filename)
+ return -EINVAL;
+ *filename++ = '\0';
+
+ if (sscanf(data, "%u", &perm) == 1)
+ return tmy_add_file_acl(filename, (u8) perm, domain, cond,
+ is_delete);
+
+ if (strncmp(data, "allow_", 6))
+ goto out;
+
+ data += 6;
+
+ for (type = 0; acl_type_array[type].keyword; type++) {
+ if (strcmp(data, acl_type_array[type].keyword))
+ continue;
+
+ if (acl_type_array[type].paths == 2) {
+ char *filename2 = strchr(filename, ' ');
+
+ if (!filename2)
+ break;
+ *filename2++ = '\0';
+ return tmy_add_double_write_acl(type, filename,
+ filename2,
+ domain, cond,
+ is_delete);
+ } else
+ return tmy_add_single_write_acl(type, filename,
+ domain, cond,
+ is_delete);
+
+ break;
+ }
+out: ;
+ return -EINVAL;
+}
+
+static int tmy_add_single_write_acl(const u8 type,
+ const char *filename,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const bool is_delete)
+{
+ const struct path_info *saved;
+ struct acl_info *ptr;
+ struct single_acl *acl;
+ int error = -ENOMEM;
+ bool is_group = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (!tmy_correct_path(filename, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved = (struct path_info *) tmy_new_path_group(filename + 1);
+ if (!saved)
+ return -ENOMEM;
+ is_group = 1;
+ } else {
+ saved = tmy_save_name(filename);
+ if (!saved)
+ return -ENOMEM;
+ }
+
+ mutex_lock(&domain_acl_lock);
+ if (is_delete)
+ goto remove;
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct single_acl, head);
+ if (ptr->type == type && ptr->cond == cond) {
+ if (acl->u.filename == saved) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ goto ok;
+ }
+ }
+ }
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ goto ok;
+
+ acl->head.type = type;
+ acl->head.cond = cond;
+ acl->u_is_group = is_group;
+ acl->u.filename = saved;
+ error = tmy_add_acl(domain, &acl->head);
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct single_acl, head);
+
+ if (ptr->type != type || ptr->is_deleted ||
+ ptr->cond != cond || acl->u.filename != saved)
+ continue;
+
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ mutex_unlock(&domain_acl_lock);
+
+ return error;
+}
+
+static int tmy_add_double_write_acl(const u8 type,
+ const char *filename1,
+ const char *filename2,
+ struct domain_info * const domain,
+ const struct condition_list *cond,
+ const bool is_delete)
+{
+ const struct path_info *saved1;
+ const struct path_info *saved2;
+ struct acl_info *ptr;
+ struct double_acl *acl;
+ int error = -ENOMEM;
+ bool is_group1 = 0;
+ bool is_group2 = 0;
+
+ if (!domain)
+ return -EINVAL;
+ if (!tmy_correct_path(filename1, 0, 0, 0, __FUNCTION__) ||
+ !tmy_correct_path(filename2, 0, 0, 0, __FUNCTION__))
+ return -EINVAL;
+
+ if (filename1[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved1 = (struct path_info *) tmy_new_path_group(filename1 + 1);
+ if (!saved1)
+ return -ENOMEM;
+ is_group1 = 1;
+ } else {
+ saved1 = tmy_save_name(filename1);
+ if (!saved1)
+ return -ENOMEM;
+ }
+
+ if (filename2[0] == '@') {
+ /* This cast is OK because I don't dereference. */
+ saved2 = (struct path_info *) tmy_new_path_group(filename2 + 1);
+ if (!saved2)
+ return -ENOMEM;
+ is_group2 = 1;
+ } else {
+ saved2 = tmy_save_name(filename2);
+ if (!saved2)
+ return -ENOMEM;
+ }
+
+ mutex_lock(&domain_acl_lock);
+
+ if (is_delete)
+ goto remove;
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct double_acl, head);
+ if (ptr->type == type && ptr->cond == cond) {
+ if (acl->u1.filename1 == saved1 &&
+ acl->u2.filename2 == saved2) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ error = 0;
+ goto ok;
+ }
+ }
+ }
+ /* Not found. Append it to the tail. */
+ acl = tmy_alloc_element(sizeof(*acl));
+ if (!acl)
+ goto ok;
+
+ acl->head.type = type;
+ acl->head.cond = cond;
+ acl->u1_is_group = is_group1;
+ acl->u2_is_group = is_group2;
+ acl->u1.filename1 = saved1;
+ acl->u2.filename2 = saved2;
+ error = tmy_add_acl(domain, &acl->head);
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = container_of(ptr, struct double_acl, head);
+ if (ptr->type != type || ptr->is_deleted ||
+ ptr->cond != cond ||
+ acl->u1.filename1 != saved1 ||
+ acl->u2.filename2 != saved2)
+ continue;
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ ok: ;
+ mutex_unlock(&domain_acl_lock);
+ return error;
+}
+
+static int tmy_single_write_acl(const u8 type,
+ const struct path_info *filename,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ struct single_acl *acl;
+ acl = container_of(ptr, struct single_acl, head);
+
+ if (ptr->type != type || ptr->is_deleted ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u_is_group) {
+ if (!tmy_path_match_group(filename, acl->u.group, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename, acl->u.filename))
+ continue;
+ }
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int tmy_double_write_acl(const u8 type,
+ const struct path_info *filename1,
+ const struct path_info *filename2,
+ struct obj_info *obj)
+{
+ const struct domain_info *domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ struct double_acl *acl;
+ acl = container_of(ptr, struct double_acl, head);
+
+ if (ptr->type != type || ptr->is_deleted ||
+ tmy_check_condition(ptr->cond, obj))
+ continue;
+
+ if (acl->u1_is_group) {
+ if (!tmy_path_match_group(filename1,
+ acl->u1.group1, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename1, acl->u1.filename1))
+ continue;
+ }
+
+ if (acl->u2_is_group) {
+ if (!tmy_path_match_group(filename2,
+ acl->u2.group2, 1))
+ continue;
+ } else {
+ if (!tmy_path_match(filename2, acl->u2.filename2))
+ continue;
+ }
+
+ return 0;
+ }
+
+ return -EPERM;
+}
+
+static int tmy_single_write_perm2(const unsigned int operation,
+ const struct path_info *filename,
+ struct obj_info *obj)
+{
+ int error;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const u8 profile = domain->profile;
+ const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+ const bool is_enforce = (mode == 3);
+
+ if (!mode)
+ return 0;
+
+ error = tmy_single_write_acl(operation, filename, obj);
+
+ tmy_audit_write_log(tmy_acltype2keyword(operation),
+ filename, NULL, !error, profile, mode);
+
+ if (!error)
+ goto ok;
+
+ if (tmy_flags(TMY_VERBOSE))
+ tmy_audit("TOMOYO-%s: Access '%s %s' denied for %s\n",
+ tmy_getmsg(is_enforce),
+ tmy_acltype2keyword(operation), filename->name,
+ tmy_lastname(domain));
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\nallow_%s %s\n",
+ domain->domainname->name,
+ tmy_acltype2keyword(operation),
+ filename->name);
+
+ else if (mode == 1 && tmy_quota())
+ tmy_add_single_write_acl(operation,
+ tmy_get_pattern(filename)->name,
+ domain, NULL, 0);
+
+ if (!is_enforce)
+ error = 0;
+
+ok: ;
+ if (!error && operation == TMY_TYPE_TRUNCATE_ACL &&
+ tmy_is_no_rewrite_file(filename))
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ filename, obj);
+
+ return error;
+}
+
+/**
+ * tmy_exec_perm - check permission for execve(2) operation.
+ * @filename: pointer to filename to execute.
+ * @filp: pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_exec_perm(const struct path_info *filename, struct file *filp)
+{
+ struct obj_info obj;
+ if (!tmy_flags(TMY_MAC_FOR_FILE))
+ return 0;
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = filp->f_dentry;
+ obj.path1_vfsmnt = filp->f_vfsmnt;
+ return tmy_file_perm2(filename, 1, &obj, "do_execve");
+}
+
+/**
+ * tmy_open_perm - check permission for open(2) operation.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount".
+ * @flag: open flags.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_open_perm(struct dentry *dentry,
+ struct vfsmount *mnt,
+ const int flag)
+{
+ struct obj_info obj;
+ const int acc_mode = ACC_MODE(flag);
+ int error = -ENOMEM;
+ struct path_info *buf;
+ const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+ const bool is_enforce = (mode == 3);
+
+ if (!mode)
+ return 0;
+ if (acc_mode == 0)
+ return 0;
+ if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode))
+ /* I don't check directories here */
+ /* because mkdir() and rmdir() don't call me. */
+ return 0;
+
+ buf = tmy_get_path(dentry, mnt);
+
+ if (!buf)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry;
+ obj.path1_vfsmnt = mnt;
+
+ error = 0;
+ if ((acc_mode & MAY_WRITE) &&
+ ((flag & O_TRUNC) || !(flag & O_APPEND)) &&
+ tmy_is_no_rewrite_file(buf))
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ buf, &obj);
+
+ if (error == 0)
+ error = tmy_file_perm2(buf, acc_mode, &obj, "open");
+
+ if (error == 0 && (flag & O_TRUNC))
+ error = tmy_single_write_perm2(TMY_TYPE_TRUNCATE_ACL,
+ buf, &obj);
+
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_single_write_perm - check permission for create(2) etc. operation.
+ * @operation: operation index number.
+ * @dentry: pointer to "struct dentry".
+ * @mnt: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_single_write_perm(const unsigned int operation,
+ struct dentry *dentry,
+ struct vfsmount *mnt)
+{
+ struct obj_info obj;
+ int error = -ENOMEM;
+ struct path_info *buf;
+ const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+ const bool is_enforce = (mode == 3);
+
+ if (!mode)
+ return 0;
+
+ buf = tmy_get_path(dentry, mnt);
+
+ if (!buf)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry;
+ obj.path1_vfsmnt = mnt;
+
+ switch (operation) {
+ case TMY_TYPE_MKDIR_ACL:
+ case TMY_TYPE_RMDIR_ACL:
+ if (!buf->is_dir) {
+ strcat((char *) buf->name, "/");
+ tmy_fill_path_info(buf);
+ }
+ }
+ error = tmy_single_write_perm2(operation, buf, &obj);
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+
+ return error;
+}
+
+/**
+ * tmy_rewrite_perm - check permission for truncate/overwrite operation.
+ * @filp: pointer to "struct file".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_rewrite_perm(struct file *filp)
+{
+ int error = -ENOMEM;
+ const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+ const bool is_enforce = (mode == 3);
+ struct path_info *buf;
+
+ if (!mode)
+ return 0;
+
+ buf = tmy_get_path(filp->f_dentry, filp->f_vfsmnt);
+ if (!buf)
+ goto out;
+
+ if (tmy_is_no_rewrite_file(buf)) {
+ struct obj_info obj;
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = filp->f_dentry;
+ obj.path1_vfsmnt = filp->f_vfsmnt;
+ error = tmy_single_write_perm2(TMY_TYPE_REWRITE_ACL,
+ buf, &obj);
+ } else
+ error = 0;
+
+ tmy_free(buf);
+
+out: ;
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
+
+/**
+ * tmy_double_write_perm - check permission for link(2)/rename(2) operation.
+ * @operation: operation index number.
+ * @dentry1: pointer to "struct dentry".
+ * @mnt1: pointer to "struct vfsmount".
+ * @dentry2: pointer to "struct dentry".
+ * @mnt2: pointer to "struct vfsmount".
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_double_write_perm(const unsigned int operation,
+ struct dentry *dentry1,
+ struct vfsmount *mnt1,
+ struct dentry *dentry2,
+ struct vfsmount *mnt2)
+{
+ struct obj_info obj;
+ int error = -ENOMEM;
+ struct path_info *buf1;
+ struct path_info *buf2;
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ const u8 profile = domain->profile;
+ const unsigned int mode = tmy_flags(TMY_MAC_FOR_FILE);
+ const bool is_enforce = (mode == 3);
+
+ if (!mode)
+ return 0;
+ buf1 = tmy_get_path(dentry1, mnt1);
+ buf2 = tmy_get_path(dentry2, mnt2);
+
+ if (!buf1 || !buf2)
+ goto out;
+
+ memset(&obj, 0, sizeof(obj));
+ obj.path1_dentry = dentry1;
+ obj.path1_vfsmnt = mnt1;
+ obj.path2_dentry = dentry2;
+ obj.path2_vfsmnt = mnt2;
+ if (operation == TMY_TYPE_RENAME_ACL) {
+ /* TMY_TYPE_LINK_ACL can't reach here for directory. */
+ if (dentry1->d_inode && S_ISDIR(dentry1->d_inode->i_mode)) {
+ if (!buf1->is_dir) {
+ strcat((char *) buf1->name, "/");
+ tmy_fill_path_info(buf1);
+ }
+ if (!buf2->is_dir) {
+ strcat((char *) buf2->name, "/");
+ tmy_fill_path_info(buf2);
+ }
+ }
+ }
+ error = tmy_double_write_acl(operation, buf1, buf2, &obj);
+
+ tmy_audit_write_log(tmy_acltype2keyword(operation),
+ buf1, buf2, !error, profile, mode);
+
+ if (!error)
+ goto out;
+
+ if (tmy_flags(TMY_VERBOSE))
+ tmy_audit("TOMOYO-%s: Access '%s %s %s' denied for %s\n",
+ tmy_getmsg(is_enforce),
+ tmy_acltype2keyword(operation),
+ buf1->name, buf2->name, tmy_lastname(domain));
+
+ if (is_enforce)
+ error = tmy_supervisor("%s\nallow_%s %s %s\n",
+ domain->domainname->name,
+ tmy_acltype2keyword(operation),
+ buf1->name, buf2->name);
+ else if (mode == 1 && tmy_quota())
+ tmy_add_double_write_acl(operation,
+ tmy_get_pattern(buf1)->name,
+ tmy_get_pattern(buf2)->name,
+ domain, NULL, 0);
+
+out: ;
+ tmy_free(buf1);
+ tmy_free(buf2);
+ if (!is_enforce)
+ error = 0;
+ return error;
+}
--
next prev parent reply other threads:[~2008-01-09 0:59 UTC|newest]
Thread overview: 33+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-01-09 0:53 [TOMOYO #6 retry 00/21] TOMOYO Linux - MAC based on process invocation history Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 01/21] TOMOYO Linux documentation Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 02/21] Add struct vfsmount to struct task_struct Kentaro Takeda
2008-01-15 21:16 ` Serge E. Hallyn
2008-01-16 0:22 ` Kentaro Takeda
2008-01-16 14:39 ` Serge E. Hallyn
2008-01-17 4:55 ` Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 03/21] Add wrapper functions for VFS helper functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 04/21] Replace VFS with wrapper functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 05/21] Add packet filtering based on processs security context Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 06/21] Data structures and prototype defitions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 07/21] Memory and pathname management functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 08/21] Utility functions and policy manipulation interface Kentaro Takeda
2008-01-09 4:25 ` James Morris
2008-01-09 4:29 ` James Morris
2008-01-12 2:06 ` [TOMOYO #6 retry 08/21] Utility functions and policy manipulationinterface Tetsuo Handa
2008-01-12 3:06 ` James Morris
2008-01-12 4:45 ` Greg KH
2008-01-12 7:34 ` [TOMOYO #6 retry 08/21] Utility functions and policymanipulationinterface Tetsuo Handa
2008-01-09 4:31 ` [TOMOYO #6 retry 08/21] Utility functions and policy manipulation interface Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 09/21] Domain transition functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 10/21] Auditing interface Kentaro Takeda
2008-01-09 0:53 ` Kentaro Takeda [this message]
2008-01-09 0:53 ` [TOMOYO #6 retry 12/21] argv0 check functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 13/21] environment variable name " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 14/21] Network access control functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 15/21] Namespace manipulation " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 16/21] Signal " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 17/21] Capability access " Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 18/21] LSM adapter functions Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 19/21] Conditional permission support Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 20/21] Kconfig and Makefile Kentaro Takeda
2008-01-09 0:53 ` [TOMOYO #6 retry 21/21] Add signal hooks at sleepable location Kentaro Takeda
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20080109005423.203226400@nttdata.co.jp \
--to=takedakn@nttdata.co.jp \
--cc=akpm@linux-foundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-security-module@vger.kernel.org \
--cc=penguin-kernel@I-love.SAKURA.ne.jp \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.