TOMOYO Linux checks sending signal by signal number and the domain of target process. In order to check signal permission, modification against kernel/signal.c is needed. Each permission can be automatically accumulated into the policy of each domain using 'learning mode'. Signed-off-by: Kentaro Takeda Signed-off-by: Tetsuo Handa --- security/tomoyo/signal.c | 226 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) --- /dev/null +++ linux-2.6-mm/security/tomoyo/signal.c @@ -0,0 +1,226 @@ +/* + * security/tomoyo/signal.c + * + * Signal access contol functions for TOMOYO Linux. + */ + +#include "tomoyo.h" +#include "realpath.h" + +/************************* AUDIT FUNCTIONS *************************/ + +static int tmy_audit_signal_log(const int signal, + const struct path_info *dest_domain, + 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 = dest_domain->total_len; + buf = tmy_init_audit_log(&len, profile, mode); + + if (!buf) + return -ENOMEM; + + tmy_sncatprintf(buf, len - 1, "%s%d %s\n", + TMY_ALLOW_SIGNAL, signal, dest_domain->name); + + return tmy_write_audit_log(buf, is_granted); +} + +/************************* SIGNAL ACL HANDLER *************************/ + +static int tmy_add_signal_entry(const u16 sig, const char *dest_pattern, + struct domain_info *domain, + const struct condition_list *cond, + const bool is_delete) +{ + struct acl_info *ptr; + struct signal_acl *acl; + const struct path_info *saved_dest_pattern; + int error = -ENOMEM; + + if (!domain) + return -EINVAL; + if (!dest_pattern || + !tmy_is_correct_domain(dest_pattern, __FUNCTION__)) + return -EINVAL; + + saved_dest_pattern = tmy_save_name(dest_pattern); + if (!saved_dest_pattern) + 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 signal_acl, head); + + if (ptr->type == TMY_TYPE_SIGNAL_ACL && acl->sig == sig + && ptr->cond == cond + && !tmy_pathcmp(acl->domainname, saved_dest_pattern)) { + 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 = TMY_TYPE_SIGNAL_ACL; + acl->head.cond = cond; + acl->sig = sig; + acl->domainname = saved_dest_pattern; + 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 signal_acl, head); + if (ptr->type != TMY_TYPE_SIGNAL_ACL || ptr->cond != cond || + ptr->is_deleted || acl->sig != sig || + tmy_pathcmp(acl->domainname, saved_dest_pattern)) + continue; + error = tmy_del_acl(ptr); + break; + } + +ok: ; + mutex_unlock(&domain_acl_lock); + + return error; +} + +/** + * tmy_signal_acl - check permission for kill(2)/tkill(2)/tgkill(2). + * @sig: signal number. + * @pid: pid of destination process. + * + * Returns zero if permission granted. + * Returns nonzero if permission denied. + */ +int tmy_signal_acl(const int sig, const int pid) +{ + struct domain_info *domain = TMY_SECURITY->domain; + struct domain_info *dest = NULL; + const char *dest_pattern; + struct acl_info *ptr; + const u16 hash = sig; + const u8 profile = domain->profile; + const unsigned int mode = tmy_flags(TMY_MAC_FOR_SIGNAL); + const bool is_enforce = (mode == 3); + bool found = 0; + + if (!mode) + return 0; + if (!sig) + return 0; /* No check for NULL signal. */ + if (current->pid == pid) { + tmy_audit_signal_log(sig, domain->domainname, 1, profile, mode); + return 0; /* No check for self. */ + } + + { /* Simplified checking. */ + struct task_struct *p = NULL; + read_lock(&tasklist_lock); + if (pid > 0) + p = find_task_by_pid((pid_t) pid); + else if (pid == 0) + p = current; + else if (pid == -1) + dest = &KERNEL_DOMAIN; + else + p = find_task_by_pid((pid_t) -pid); + if (p) + /* "struct task_struct"->security is not NULL. */ + dest = ((struct tmy_security *) p->security)->domain; + read_unlock(&tasklist_lock); + if (!dest) + return 0; /* I can't find destinatioin. */ + } + + if (domain == dest) { + tmy_audit_signal_log(sig, dest->domainname, 1, profile, mode); + return 0; + } + + dest_pattern = dest->domainname->name; + list_for_each_entry(ptr, &domain->acl_info_list, list) { + struct signal_acl *acl; + acl = container_of(ptr, struct signal_acl, head); + + if (ptr->type == TMY_TYPE_SIGNAL_ACL && ptr->is_deleted == 0 + && acl->sig == hash && + tmy_check_condition(ptr->cond, NULL) == 0) { + const int len = acl->domainname->total_len; + + if (strncmp(acl->domainname->name, + dest_pattern, len) == 0 + && (dest_pattern[len] == ' ' || + dest_pattern[len] == '\0')) { + found = 1; + break; + } + } + } + + tmy_audit_signal_log(sig, dest->domainname, found, profile, mode); + + if (found) + return 0; + + if (tmy_flags(TMY_VERBOSE)) + tmy_audit("TOMOYO-%s: Signal %d to %s denied for %s\n", + tmy_getmsg(is_enforce), sig, + tmy_lastname(dest), tmy_lastname(domain)); + + if (is_enforce) + return tmy_supervisor("%s\n" TMY_ALLOW_SIGNAL "%d %s\n", + domain->domainname->name, + sig, dest_pattern); + if (mode == 1 && tmy_quota()) + tmy_add_signal_entry(sig, dest_pattern, domain, NULL, 0); + + return 0; +} + +/** + * tmy_add_signal_policy - add or delete signal 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_signal_policy(char *data, + struct domain_info *domain, + const struct condition_list *cond, + const bool is_delete) +{ + int sig; + char *domainname = strchr(data, ' '); + + if (sscanf(data, "%d", &sig) == 1 && domainname && + tmy_is_domain_def(domainname + 1)) + return tmy_add_signal_entry(sig, domainname + 1, domain, + cond, is_delete); + + return -EINVAL; +} --