[TOMOYO #5 14/18] Capability access control functions.

penguin-kernel
2007-11-16T17:56:23Z

[-- Attachment #1: tomoyo-capability.diff --]
[-- Type: text/plain, Size: 10749 bytes --]

TOMOYO Linux checks permission for non-POSIX capability
so that the number of capabilities won't be limited to 32 or 64.

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/capability.c | 320 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 320 insertions(+)

--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6-mm/security/tomoyo/capability.c 2007-11-14 15:15:44.000000000 +0900
@@ -0,0 +1,320 @@
+/*
+ * security/tomoyo/capability.c
+ *
+ * Capability access control functions for TOMOYO Linux.
+ */
+
+#include "tomoyo.h"
+#include "realpath.h"
+
+static struct {
+ const char *keyword;
+ unsigned int current_value;
+ const char *capability_name;
+} capability_control_array[TMY_MAX_CAPABILITY_INDEX] = {
+ [TMY_INET_STREAM_SOCKET_CREATE] = /* OK */
+ { "inet_tcp_create", 0, "socket(PF_INET, SOCK_STREAM)" },
+ [TMY_INET_STREAM_SOCKET_LISTEN] = /* OK */
+ { "inet_tcp_listen", 0, "listen(PF_INET, SOCK_STREAM)" },
+ [TMY_INET_STREAM_SOCKET_CONNECT] = /* OK */
+ { "inet_tcp_connect", 0, "connect(PF_INET, SOCK_STREAM)" },
+ [TMY_USE_INET_DGRAM_SOCKET] = /* OK */
+ { "use_inet_udp", 0, "socket(PF_INET, SOCK_DGRAM)" },
+ [TMY_USE_INET_RAW_SOCKET] = /* OK */
+ { "use_inet_ip", 0, "socket(PF_INET, SOCK_RAW)" },
+ [TMY_USE_ROUTE_SOCKET] = /* OK */
+ { "use_route", 0, "socket(PF_ROUTE)" },
+ [TMY_USE_PACKET_SOCKET] = /* OK */
+ { "use_packet", 0, "socket(PF_PACKET)" },
+ [TMY_SYS_MOUNT] = /* OK */
+ { "SYS_MOUNT", 0, "sys_mount()" },
+ [TMY_SYS_UMOUNT] = /* OK */
+ { "SYS_UMOUNT", 0, "sys_umount()" },
+ [TMY_SYS_REBOOT] = /* Too many hooks. */
+ { "SYS_REBOOT", 0, "sys_reboot()" },
+ [TMY_SYS_CHROOT] = /* OK */
+ { "SYS_CHROOT", 0, "sys_chroot()" },
+ [TMY_SYS_KILL] = /* No appropriate hook. */
+ { "SYS_KILL", 0, "sys_kill()" },
+ [TMY_SYS_VHANGUP] = /* Too many hooks. */
+ { "SYS_VHANGUP", 0, "sys_vhangup()" },
+ [TMY_SYS_SETTIME] = /* Too many hooks. */
+ { "SYS_TIME", 0, "sys_settimeofday()" },
+ [TMY_SYS_NICE] = /* No appropriate hook. */
+ { "SYS_NICE", 0, "sys_nice()" },
+ [TMY_SYS_SETHOSTNAME] = /* No appropriate hook. */
+ { "SYS_SETHOSTNAME", 0, "sys_sethostname()" },
+ [TMY_USE_KERNEL_MODULE] = /* Too many hooks. */
+ { "use_kernel_module", 0, "kernel_module" },
+ [TMY_CREATE_FIFO] = /* OK */
+ { "create_fifo", 0, "mknod(FIFO)" },
+ [TMY_CREATE_BLOCK_DEV] = /* OK */
+ { "create_block_dev", 0, "mknod(BDEV)" },
+ [TMY_CREATE_CHAR_DEV] = /* OK */
+ { "create_char_dev", 0, "mknod(CDEV)" },
+ [TMY_CREATE_UNIX_SOCKET] = /* OK */
+ { "create_unix_socket", 0, "mknod(SOCKET)" },
+ [TMY_SYS_LINK] = /* OK */
+ { "SYS_LINK", 0, "sys_link()" },
+ [TMY_SYS_SYMLINK] = /* OK */
+ { "SYS_SYMLINK", 0, "sys_symlink()" },
+ [TMY_SYS_RENAME] = /* OK */
+ { "SYS_RENAME", 0, "sys_rename()" },
+ [TMY_SYS_UNLINK] = /* OK */
+ { "SYS_UNLINK", 0, "sys_unlink()" },
+ [TMY_SYS_CHMOD] = /* OK */
+ { "SYS_CHMOD", 0, "sys_chmod()" },
+ [TMY_SYS_CHOWN] = /* OK */
+ { "SYS_CHOWN", 0, "sys_chown()" },
+ [TMY_SYS_IOCTL] = /* Too many hooks. */
+ { "SYS_IOCTL", 0, "sys_ioctl()" },
+ [TMY_SYS_KEXEC_LOAD] = /* No appropriate hook. */
+ { "SYS_KEXEC_LOAD", 0, "sys_kexec_load()" },
+ [TMY_SYS_PIVOT_ROOT] = /* OK */
+ { "SYS_PIVOT_ROOT", 0, "sys_pivot_root()" },
+};
+
+struct profile {
+ unsigned char value[TMY_MAX_CAPABILITY_INDEX];
+};
+
+static struct profile *profile_ptr[TMY_MAX_PROFILES];
+
+/************************* UTILITY FUNCTIONS *************************/
+
+const char *tmy_capability2keyword(const unsigned int capability)
+{
+ return capability < TMY_MAX_CAPABILITY_INDEX ?
+ capability_control_array[capability].keyword : NULL;
+}
+
+static const char *tmy_capability2name(const unsigned int capability)
+{
+ return capability < TMY_MAX_CAPABILITY_INDEX ?
+ capability_control_array[capability].capability_name : NULL;
+}
+
+/* Check whether the given capability control is enabled. */
+static unsigned int tmy_capability_flags(const unsigned int index)
+{
+ const u8 profile = TMY_SECURITY->domain->profile;
+ /* All operations might sleep. See tmy_supervisor(). */
+ might_sleep();
+ if (in_interrupt())
+ return 0;
+ return sbin_init_started && index < TMY_MAX_CAPABILITY_INDEX
+#if TMY_MAX_PROFILES != 256
+ && profile < TMY_MAX_PROFILES
+#endif
+ && profile_ptr[profile] ?
+ profile_ptr[profile]->value[index] : 0;
+}
+
+static struct profile *tmy_new_capability_profile(const unsigned int profile)
+{
+ static DEFINE_MUTEX(mutex);
+ struct profile *ptr;
+ int i;
+ if (profile >= TMY_MAX_PROFILES)
+ return NULL;
+ mutex_lock(&mutex);
+ ptr = profile_ptr[profile];
+ if (ptr)
+ goto ok;
+ ptr = tmy_alloc_element(sizeof(*ptr));
+ if (!ptr)
+ goto ok;
+ for (i = 0; i < TMY_MAX_CAPABILITY_INDEX; i++)
+ ptr->value[i] = capability_control_array[i].current_value;
+ mb(); /* Avoid out-of-order execution. */
+ profile_ptr[profile] = ptr;
+ok: ;
+ mutex_unlock(&mutex);
+ return ptr;
+}
+
+int tmy_set_capability_profile(const char *data, unsigned int value,
+ const unsigned int profile)
+{
+ int i;
+ struct profile *ptr;
+ ptr = tmy_new_capability_profile(profile);
+ if (!ptr)
+ return -EINVAL;
+ for (i = 0; i < TMY_MAX_CAPABILITY_INDEX; i++) {
+ if (strcmp(data, capability_control_array[i].keyword))
+ continue;
+ if (value > 3)
+ value = 3;
+ ptr->value[i] = value;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+int tmy_read_capability_profile(struct io_buffer *head)
+{
+ int step;
+ for (step = head->read_step;
+ step < TMY_MAX_PROFILES * TMY_MAX_CAPABILITY_INDEX; step++) {
+ const int i = step / TMY_MAX_CAPABILITY_INDEX;
+ const int j = step % TMY_MAX_CAPABILITY_INDEX;
+ const struct profile *profile = profile_ptr[i];
+ head->read_step = step;
+ if (!profile)
+ continue;
+ if (tmy_io_printf(head, "%u-" TMY_MAC_FOR_CAPABILITY "%s=%u\n",
+ i, capability_control_array[j].keyword,
+ profile->value[j]))
+ break;
+ }
+ return step < TMY_MAX_PROFILES * TMY_MAX_CAPABILITY_INDEX ? -ENOMEM : 0;
+}
+
+/************************* AUDIT FUNCTIONS *************************/
+
+static int tmy_audit_capability_log(const unsigned int capability,
+ const bool is_granted,
+ const u8 profile, const unsigned int mode)
+{
+ char *buf;
+ int len = 64 ;
+
+ if (is_granted) {
+ if (!tmy_audit_grant())
+ return 0;
+ } else {
+ if (!tmy_audit_reject())
+ return 0;
+ }
+
+ buf = tmy_init_audit_log(&len, profile, mode);
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf + strlen(buf),
+ len - strlen(buf) - 1,
+ TMY_ALLOW_CAPABILITY "%s\n",
+ tmy_capability2keyword(capability));
+
+ return tmy_write_audit_log(buf, is_granted);
+}
+
+/************************* CAPABILITY ACL HANDLER *************************/
+
+static int tmy_add_capability_acl(const unsigned int capability,
+ struct domain_info *domain,
+ const struct condition_list *condition,
+ const bool is_delete)
+{
+ struct acl_info *ptr;
+ struct capability_acl *acl;
+ int error = -ENOMEM;
+ const u16 hash = capability;
+ if (!domain)
+ return -EINVAL;
+ mutex_lock(&domain_acl_lock);
+ if (is_delete)
+ goto remove;
+
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = (struct capability_acl *) ptr;
+ if (ptr->type == TMY_TYPE_CAPABILITY_ACL
+ && acl->capability == hash
+ && ptr->cond == condition) {
+ ptr->is_deleted = 0;
+ /* Found. Nothing to do. */
+ 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_CAPABILITY_ACL;
+ acl->head.cond = condition;
+ acl->capability = hash;
+ error = tmy_add_acl(domain, (struct acl_info *) acl);
+ goto ok;
+remove: ;
+ error = -ENOENT;
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ acl = (struct capability_acl *) ptr;
+ if (ptr->type != TMY_TYPE_CAPABILITY_ACL || ptr->is_deleted ||
+ acl->capability != hash || ptr->cond != condition) continue;
+ error = tmy_del_acl(ptr);
+ break;
+ }
+ok: ;
+ mutex_unlock(&domain_acl_lock);
+ return error;
+}
+
+/**
+ * tmy_capable - check permission for capability.
+ * @capability: capability index.
+ *
+ * Returns zero if permission granted.
+ * Returns nonzero if permission denied.
+ */
+int tmy_capable(const unsigned int capability)
+{
+ struct domain_info * const domain = TMY_SECURITY->domain;
+ struct acl_info *ptr;
+ const u8 profile = domain->profile;
+ const unsigned int mode = tmy_capability_flags(capability);
+ const bool is_enforce = (mode == 3);
+ const u16 hash = capability;
+ if (!mode)
+ return 0;
+ list_for_each_entry(ptr, &domain->acl_info_list, list) {
+ struct capability_acl *acl = (struct capability_acl *) ptr;
+ if (ptr->type != TMY_TYPE_CAPABILITY_ACL || ptr->is_deleted
+ || acl->capability != hash
+ || tmy_check_condition(ptr->cond, NULL))
+ continue;
+ tmy_audit_capability_log(capability, 1, profile, mode);
+ return 0;
+ }
+ if (tmy_flags(TMY_VERBOSE))
+ tmy_audit("TOMOYO-%s: %s denied for %s\n",
+ tmy_getmsg(is_enforce),
+ tmy_capability2name(capability),
+ tmy_lastname(domain));
+ tmy_audit_capability_log(capability, 0, profile, mode);
+ if (is_enforce)
+ return tmy_supervisor("%s\n" TMY_ALLOW_CAPABILITY "%s\n",
+ domain->domainname->name,
+ tmy_capability2keyword(capability));
+ if (mode == 1 && tmy_quota())
+ tmy_add_capability_acl(capability, domain, NULL, 0);
+ return 0;
+}
+
+/**
+ * tmy_add_capability_policy - add or delete capability 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_capability_policy(char *data, struct domain_info *domain,
+ const struct condition_list *cond,
+ const bool is_delete)
+{
+ unsigned int capability;
+ for (capability = 0; capability < TMY_MAX_CAPABILITY_INDEX;
+ capability++) {
+ if (strcmp(data, capability_control_array[capability].keyword))
+ continue;
+ return tmy_add_capability_acl(capability, domain,
+ cond, is_delete);
+ }
+ return -EINVAL;
+}

--