If the executed program name and argv[0] is different, TOMOYO Linux checks permission. 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/exec.c | 215 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) --- /dev/null +++ linux-2.6-mm/security/tomoyo/exec.c @@ -0,0 +1,215 @@ +/* + * security/tomoyo/exec.c + * + * Argv0 access control functions for TOMOYO Linux. + */ + +#include "tomoyo.h" +#include "realpath.h" + +/************************* AUDIT FUNCTIONS *************************/ + +static int tmy_audit_argv0_log(const struct path_info *filename, + const char *argv0, + 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 + strlen(argv0) + 8; + buf = tmy_init_audit_log(&len, profile, mode); + + if (!buf) + return -ENOMEM; + + tmy_sncatprintf(buf, len - 1, TMY_ALLOW_ARGV0 "%s %s", + filename->name, argv0); + + return tmy_write_audit_log(buf, is_granted); +} + +/************************* ARGV0 MISMATCH HANDLER *************************/ + +static int tmy_add_argv0_entry(const char *filename, + const char *argv0, + struct domain_info *domain, + const struct condition_list *cond, + const bool is_delete) +{ + struct acl_info *ptr; + struct argv0_acl *acl; + const struct path_info *saved_filename; + const struct path_info *saved_argv0; + int error = -ENOMEM; + + if (!tmy_correct_path(filename, 1, 0, -1, __FUNCTION__) || + !tmy_correct_path(argv0, -1, 0, -1, __FUNCTION__) || + strchr(argv0, '/')) + return -EINVAL; + + saved_filename = tmy_save_name(filename); + saved_argv0 = tmy_save_name(argv0); + if (!saved_filename || !saved_argv0) + 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 argv0_acl, head); + if (ptr->type == TMY_TYPE_ARGV0_ACL && ptr->cond == cond && + acl->filename == saved_filename && + acl->argv0 == saved_argv0) { + 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_ARGV0_ACL; + acl->head.cond = cond; + acl->filename = saved_filename; + acl->argv0 = saved_argv0; + 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 argv0_acl, head); + if (ptr->type != TMY_TYPE_ARGV0_ACL || + ptr->cond != cond || ptr->is_deleted || + acl->filename != saved_filename || + acl->argv0 != saved_argv0) + continue; + + error = tmy_del_acl(ptr); + break; + } +ok: ; + mutex_unlock(&domain_acl_lock); + + return error; +} + +static int tmy_argv0_acl(const struct path_info *filename, + const char *argv0_) +{ + const struct domain_info *domain = TMY_SECURITY->domain; + int error = -EPERM; + struct acl_info *ptr; + struct path_info argv0; + + if (!tmy_flags(TMY_MAC_FOR_ARGV0)) + return 0; + + argv0.name = argv0_; + tmy_fill_path_info(&argv0); + + list_for_each_entry(ptr, &domain->acl_info_list, list) { + struct argv0_acl *acl; + acl = container_of(ptr, struct argv0_acl, head); + + if (ptr->type == TMY_TYPE_ARGV0_ACL && + ptr->is_deleted == 0 && + tmy_check_condition(ptr->cond, NULL) == 0 && + tmy_path_match(filename, acl->filename) && + tmy_path_match(&argv0, acl->argv0)) { + error = 0; + break; + } + } + + return error; +} + +/** + * tmy_argv0_perm - check for argv[0] permission. + * @filename: pointer to filename. + * @argv0: pointer to basename of argv[0]. + * + * Returns zero if permission granted. + * Returns nonzero if permission denied. + */ +int tmy_argv0_perm(const struct path_info *filename, const char *argv0) +{ + int error = 0; + const u8 profile = TMY_SECURITY->domain->profile; + const unsigned int mode = tmy_flags(TMY_MAC_FOR_ARGV0); + const bool is_enforce = (mode == 3); + + if (!mode) + return 0; + if (!filename || !argv0 || !*argv0) + return 0; + + error = tmy_argv0_acl(filename, argv0); + + tmy_audit_argv0_log(filename, argv0, !error, profile, mode); + + if (error) { + struct domain_info * const domain = TMY_SECURITY->domain; + + if (tmy_flags(TMY_VERBOSE)) + tmy_audit("TOMOYO-%s: Run %s as %s denied for %s\n", + tmy_getmsg(is_enforce), filename->name, argv0, + tmy_lastname(domain)); + + if (is_enforce) + error = tmy_supervisor("%s\n" TMY_ALLOW_ARGV0 "%s %s\n", + domain->domainname->name, + filename->name, argv0); + + else if (mode == 1 && tmy_quota()) + tmy_add_argv0_entry(filename->name, argv0, domain, + NULL, 0); + + if (!is_enforce) + error = 0; + } + + return error; +} + +/** + * tmy_add_argv0_policy - add or delete argv[0] 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_argv0_policy(char *data, + struct domain_info *domain, + const struct condition_list *cond, + const bool is_delete) +{ + char *argv0 = strchr(data, ' '); + + if (!argv0) + return -EINVAL; + + *argv0++ = '\0'; + + return tmy_add_argv0_entry(data, argv0, domain, cond, + is_delete); +} --