f743324377
DAC's permissions and TOMOYO's permissions are not one-to-one mapping. Regarding DAC, there are "read", "write", "execute" permissions. Regarding TOMOYO, there are "allow_read", "allow_write", "allow_read/write", "allow_execute", "allow_create", "allow_unlink", "allow_mkdir", "allow_rmdir", "allow_mkfifo", "allow_mksock", "allow_mkblock", "allow_mkchar", "allow_truncate", "allow_symlink", "allow_rewrite", "allow_link", "allow_rename" permissions. +----------------------------------+----------------------------------+ | requested operation | required TOMOYO's permission | +----------------------------------+----------------------------------+ | sys_open(O_RDONLY) | allow_read | +----------------------------------+----------------------------------+ | sys_open(O_WRONLY) | allow_write | +----------------------------------+----------------------------------+ | sys_open(O_RDWR) | allow_read/write | +----------------------------------+----------------------------------+ | open_exec() from do_execve() | allow_execute | +----------------------------------+----------------------------------+ | open_exec() from !do_execve() | allow_read | +----------------------------------+----------------------------------+ | sys_read() | (none) | +----------------------------------+----------------------------------+ | sys_write() | (none) | +----------------------------------+----------------------------------+ | sys_mmap() | (none) | +----------------------------------+----------------------------------+ | sys_uselib() | allow_read | +----------------------------------+----------------------------------+ | sys_open(O_CREAT) | allow_create | +----------------------------------+----------------------------------+ | sys_open(O_TRUNC) | allow_truncate | +----------------------------------+----------------------------------+ | sys_truncate() | allow_truncate | +----------------------------------+----------------------------------+ | sys_ftruncate() | allow_truncate | +----------------------------------+----------------------------------+ | sys_open() without O_APPEND | allow_rewrite | +----------------------------------+----------------------------------+ | setfl() without O_APPEND | allow_rewrite | +----------------------------------+----------------------------------+ | sys_sysctl() for writing | allow_write | +----------------------------------+----------------------------------+ | sys_sysctl() for reading | allow_read | +----------------------------------+----------------------------------+ | sys_unlink() | allow_unlink | +----------------------------------+----------------------------------+ | sys_mknod(S_IFREG) | allow_create | +----------------------------------+----------------------------------+ | sys_mknod(0) | allow_create | +----------------------------------+----------------------------------+ | sys_mknod(S_IFIFO) | allow_mkfifo | +----------------------------------+----------------------------------+ | sys_mknod(S_IFSOCK) | allow_mksock | +----------------------------------+----------------------------------+ | sys_bind(AF_UNIX) | allow_mksock | +----------------------------------+----------------------------------+ | sys_mknod(S_IFBLK) | allow_mkblock | +----------------------------------+----------------------------------+ | sys_mknod(S_IFCHR) | allow_mkchar | +----------------------------------+----------------------------------+ | sys_symlink() | allow_symlink | +----------------------------------+----------------------------------+ | sys_mkdir() | allow_mkdir | +----------------------------------+----------------------------------+ | sys_rmdir() | allow_rmdir | +----------------------------------+----------------------------------+ | sys_link() | allow_link | +----------------------------------+----------------------------------+ | sys_rename() | allow_rename | +----------------------------------+----------------------------------+ TOMOYO requires "allow_execute" permission of a pathname passed to do_execve() but does not require "allow_read" permission of that pathname. Let's consider 3 patterns (statically linked, dynamically linked, shell script). This description is to some degree simplified. $ cat hello.c #include <stdio.h> int main() { printf("Hello\n"); return 0; } $ cat hello.sh #! /bin/sh echo "Hello" $ gcc -static -o hello-static hello.c $ gcc -o hello-dynamic hello.c $ chmod 755 hello.sh Case 1 -- Executing hello-static from bash. (1) The bash process calls fork() and the child process requests do_execve("hello-static"). (2) The kernel checks "allow_execute hello-static" from "bash" domain. (3) The kernel calculates "bash hello-static" as the domain to transit to. (4) The kernel overwrites the child process by "hello-static". (5) The child process transits to "bash hello-static" domain. (6) The "hello-static" starts and finishes. Case 2 -- Executing hello-dynamic from bash. (1) The bash process calls fork() and the child process requests do_execve("hello-dynamic"). (2) The kernel checks "allow_execute hello-dynamic" from "bash" domain. (3) The kernel calculates "bash hello-dynamic" as the domain to transit to. (4) The kernel checks "allow_read ld-linux.so" from "bash hello-dynamic" domain. I think permission to access ld-linux.so should be charged hello-dynamic program, for "hello-dynamic needs ld-linux.so" is not a fault of bash program. (5) The kernel overwrites the child process by "hello-dynamic". (6) The child process transits to "bash hello-dynamic" domain. (7) The "hello-dynamic" starts and finishes. Case 3 -- Executing hello.sh from bash. (1) The bash process calls fork() and the child process requests do_execve("hello.sh"). (2) The kernel checks "allow_execute hello.sh" from "bash" domain. (3) The kernel calculates "bash hello.sh" as the domain to transit to. (4) The kernel checks "allow_read /bin/sh" from "bash hello.sh" domain. I think permission to access /bin/sh should be charged hello.sh program, for "hello.sh needs /bin/sh" is not a fault of bash program. (5) The kernel overwrites the child process by "/bin/sh". (6) The child process transits to "bash hello.sh" domain. (7) The "/bin/sh" requests open("hello.sh"). (8) The kernel checks "allow_read hello.sh" from "bash hello.sh" domain. (9) The "/bin/sh" starts and finishes. Whether a file is interpreted as a program or not depends on an application. The kernel cannot know whether the file is interpreted as a program or not. Thus, TOMOYO treats "hello-static" "hello-dynamic" "ld-linux.so" "hello.sh" "/bin/sh" equally as merely files; no distinction between executable and non-executable. Therefore, TOMOYO doesn't check DAC's execute permission. TOMOYO checks "allow_read" permission instead. Calling do_execve() is a bold gesture that an old program's instance (i.e. current process) is ready to be overwritten by a new program and is ready to transfer control to the new program. To split purview of programs, TOMOYO requires "allow_execute" permission of the new program against the old program's instance and performs domain transition. If do_execve() succeeds, the old program is no longer responsible against the consequence of the new program's behavior. Only the new program is responsible for all consequences. But TOMOYO doesn't require "allow_read" permission of the new program. If TOMOYO requires "allow_read" permission of the new program, TOMOYO will allow an attacker (who hijacked the old program's instance) to open the new program and steal data from the new program. Requiring "allow_read" permission will widen purview of the old program. Not requiring "allow_read" permission of the new program against the old program's instance is my design for reducing purview of the old program. To be able to know whether the current process is in do_execve() or not, I want to add in_execve flag to "task_struct". Signed-off-by: Kentaro Takeda <takedakn@nttdata.co.jp> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp> Signed-off-by: Toshiharu Harada <haradats@nttdata.co.jp> Signed-off-by: James Morris <jmorris@namei.org>
293 lines
7.3 KiB
C
293 lines
7.3 KiB
C
/*
|
|
* security/tomoyo/tomoyo.c
|
|
*
|
|
* LSM hooks for TOMOYO Linux.
|
|
*
|
|
* Copyright (C) 2005-2009 NTT DATA CORPORATION
|
|
*
|
|
* Version: 2.2.0-pre 2009/02/01
|
|
*
|
|
*/
|
|
|
|
#include <linux/security.h>
|
|
#include "common.h"
|
|
#include "tomoyo.h"
|
|
#include "realpath.h"
|
|
|
|
static int tomoyo_cred_prepare(struct cred *new, const struct cred *old,
|
|
gfp_t gfp)
|
|
{
|
|
/*
|
|
* Since "struct tomoyo_domain_info *" is a sharable pointer,
|
|
* we don't need to duplicate.
|
|
*/
|
|
new->security = old->security;
|
|
return 0;
|
|
}
|
|
|
|
static int tomoyo_bprm_set_creds(struct linux_binprm *bprm)
|
|
{
|
|
/*
|
|
* Do only if this function is called for the first time of an execve
|
|
* operation.
|
|
*/
|
|
if (bprm->cred_prepared)
|
|
return 0;
|
|
/*
|
|
* Load policy if /sbin/tomoyo-init exists and /sbin/init is requested
|
|
* for the first time.
|
|
*/
|
|
if (!tomoyo_policy_loaded)
|
|
tomoyo_load_policy(bprm->filename);
|
|
/*
|
|
* Tell tomoyo_bprm_check_security() is called for the first time of an
|
|
* execve operation.
|
|
*/
|
|
bprm->cred->security = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int tomoyo_bprm_check_security(struct linux_binprm *bprm)
|
|
{
|
|
struct tomoyo_domain_info *domain = bprm->cred->security;
|
|
|
|
/*
|
|
* Execute permission is checked against pathname passed to do_execve()
|
|
* using current domain.
|
|
*/
|
|
if (!domain) {
|
|
struct tomoyo_domain_info *next_domain = NULL;
|
|
int retval = tomoyo_find_next_domain(bprm, &next_domain);
|
|
|
|
if (!retval)
|
|
bprm->cred->security = next_domain;
|
|
return retval;
|
|
}
|
|
/*
|
|
* Read permission is checked against interpreters using next domain.
|
|
* '1' is the result of open_to_namei_flags(O_RDONLY).
|
|
*/
|
|
return tomoyo_check_open_permission(domain, &bprm->file->f_path, 1);
|
|
}
|
|
|
|
#ifdef CONFIG_SYSCTL
|
|
|
|
static int tomoyo_prepend(char **buffer, int *buflen, const char *str)
|
|
{
|
|
int namelen = strlen(str);
|
|
|
|
if (*buflen < namelen)
|
|
return -ENOMEM;
|
|
*buflen -= namelen;
|
|
*buffer -= namelen;
|
|
memcpy(*buffer, str, namelen);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tomoyo_sysctl_path - return the realpath of a ctl_table.
|
|
* @table: pointer to "struct ctl_table".
|
|
*
|
|
* Returns realpath(3) of the @table on success.
|
|
* Returns NULL on failure.
|
|
*
|
|
* This function uses tomoyo_alloc(), so the caller must call tomoyo_free()
|
|
* if this function didn't return NULL.
|
|
*/
|
|
static char *tomoyo_sysctl_path(struct ctl_table *table)
|
|
{
|
|
int buflen = TOMOYO_MAX_PATHNAME_LEN;
|
|
char *buf = tomoyo_alloc(buflen);
|
|
char *end = buf + buflen;
|
|
int error = -ENOMEM;
|
|
|
|
if (!buf)
|
|
return NULL;
|
|
|
|
*--end = '\0';
|
|
buflen--;
|
|
while (table) {
|
|
char buf[32];
|
|
const char *sp = table->procname;
|
|
|
|
if (!sp) {
|
|
memset(buf, 0, sizeof(buf));
|
|
snprintf(buf, sizeof(buf) - 1, "=%d=", table->ctl_name);
|
|
sp = buf;
|
|
}
|
|
if (tomoyo_prepend(&end, &buflen, sp) ||
|
|
tomoyo_prepend(&end, &buflen, "/"))
|
|
goto out;
|
|
table = table->parent;
|
|
}
|
|
if (tomoyo_prepend(&end, &buflen, "/proc/sys"))
|
|
goto out;
|
|
error = tomoyo_encode(buf, end - buf, end);
|
|
out:
|
|
if (!error)
|
|
return buf;
|
|
tomoyo_free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
static int tomoyo_sysctl(struct ctl_table *table, int op)
|
|
{
|
|
int error;
|
|
char *name;
|
|
|
|
op &= MAY_READ | MAY_WRITE;
|
|
if (!op)
|
|
return 0;
|
|
name = tomoyo_sysctl_path(table);
|
|
if (!name)
|
|
return -ENOMEM;
|
|
error = tomoyo_check_file_perm(tomoyo_domain(), name, op);
|
|
tomoyo_free(name);
|
|
return error;
|
|
}
|
|
#endif
|
|
|
|
static int tomoyo_path_truncate(struct path *path, loff_t length,
|
|
unsigned int time_attrs)
|
|
{
|
|
return tomoyo_check_1path_perm(tomoyo_domain(),
|
|
TOMOYO_TYPE_TRUNCATE_ACL,
|
|
path);
|
|
}
|
|
|
|
static int tomoyo_path_unlink(struct path *parent, struct dentry *dentry)
|
|
{
|
|
struct path path = { parent->mnt, dentry };
|
|
return tomoyo_check_1path_perm(tomoyo_domain(),
|
|
TOMOYO_TYPE_UNLINK_ACL,
|
|
&path);
|
|
}
|
|
|
|
static int tomoyo_path_mkdir(struct path *parent, struct dentry *dentry,
|
|
int mode)
|
|
{
|
|
struct path path = { parent->mnt, dentry };
|
|
return tomoyo_check_1path_perm(tomoyo_domain(),
|
|
TOMOYO_TYPE_MKDIR_ACL,
|
|
&path);
|
|
}
|
|
|
|
static int tomoyo_path_rmdir(struct path *parent, struct dentry *dentry)
|
|
{
|
|
struct path path = { parent->mnt, dentry };
|
|
return tomoyo_check_1path_perm(tomoyo_domain(),
|
|
TOMOYO_TYPE_RMDIR_ACL,
|
|
&path);
|
|
}
|
|
|
|
static int tomoyo_path_symlink(struct path *parent, struct dentry *dentry,
|
|
const char *old_name)
|
|
{
|
|
struct path path = { parent->mnt, dentry };
|
|
return tomoyo_check_1path_perm(tomoyo_domain(),
|
|
TOMOYO_TYPE_SYMLINK_ACL,
|
|
&path);
|
|
}
|
|
|
|
static int tomoyo_path_mknod(struct path *parent, struct dentry *dentry,
|
|
int mode, unsigned int dev)
|
|
{
|
|
struct path path = { parent->mnt, dentry };
|
|
int type = TOMOYO_TYPE_CREATE_ACL;
|
|
|
|
switch (mode & S_IFMT) {
|
|
case S_IFCHR:
|
|
type = TOMOYO_TYPE_MKCHAR_ACL;
|
|
break;
|
|
case S_IFBLK:
|
|
type = TOMOYO_TYPE_MKBLOCK_ACL;
|
|
break;
|
|
case S_IFIFO:
|
|
type = TOMOYO_TYPE_MKFIFO_ACL;
|
|
break;
|
|
case S_IFSOCK:
|
|
type = TOMOYO_TYPE_MKSOCK_ACL;
|
|
break;
|
|
}
|
|
return tomoyo_check_1path_perm(tomoyo_domain(),
|
|
type, &path);
|
|
}
|
|
|
|
static int tomoyo_path_link(struct dentry *old_dentry, struct path *new_dir,
|
|
struct dentry *new_dentry)
|
|
{
|
|
struct path path1 = { new_dir->mnt, old_dentry };
|
|
struct path path2 = { new_dir->mnt, new_dentry };
|
|
return tomoyo_check_2path_perm(tomoyo_domain(),
|
|
TOMOYO_TYPE_LINK_ACL,
|
|
&path1, &path2);
|
|
}
|
|
|
|
static int tomoyo_path_rename(struct path *old_parent,
|
|
struct dentry *old_dentry,
|
|
struct path *new_parent,
|
|
struct dentry *new_dentry)
|
|
{
|
|
struct path path1 = { old_parent->mnt, old_dentry };
|
|
struct path path2 = { new_parent->mnt, new_dentry };
|
|
return tomoyo_check_2path_perm(tomoyo_domain(),
|
|
TOMOYO_TYPE_RENAME_ACL,
|
|
&path1, &path2);
|
|
}
|
|
|
|
static int tomoyo_file_fcntl(struct file *file, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
if (cmd == F_SETFL && ((arg ^ file->f_flags) & O_APPEND))
|
|
return tomoyo_check_rewrite_permission(tomoyo_domain(), file);
|
|
return 0;
|
|
}
|
|
|
|
static int tomoyo_dentry_open(struct file *f, const struct cred *cred)
|
|
{
|
|
int flags = f->f_flags;
|
|
|
|
if ((flags + 1) & O_ACCMODE)
|
|
flags++;
|
|
flags |= f->f_flags & (O_APPEND | O_TRUNC);
|
|
/* Don't check read permission here if called from do_execve(). */
|
|
if (current->in_execve)
|
|
return 0;
|
|
return tomoyo_check_open_permission(tomoyo_domain(), &f->f_path, flags);
|
|
}
|
|
|
|
static struct security_operations tomoyo_security_ops = {
|
|
.name = "tomoyo",
|
|
.cred_prepare = tomoyo_cred_prepare,
|
|
.bprm_set_creds = tomoyo_bprm_set_creds,
|
|
.bprm_check_security = tomoyo_bprm_check_security,
|
|
#ifdef CONFIG_SYSCTL
|
|
.sysctl = tomoyo_sysctl,
|
|
#endif
|
|
.file_fcntl = tomoyo_file_fcntl,
|
|
.dentry_open = tomoyo_dentry_open,
|
|
.path_truncate = tomoyo_path_truncate,
|
|
.path_unlink = tomoyo_path_unlink,
|
|
.path_mkdir = tomoyo_path_mkdir,
|
|
.path_rmdir = tomoyo_path_rmdir,
|
|
.path_symlink = tomoyo_path_symlink,
|
|
.path_mknod = tomoyo_path_mknod,
|
|
.path_link = tomoyo_path_link,
|
|
.path_rename = tomoyo_path_rename,
|
|
};
|
|
|
|
static int __init tomoyo_init(void)
|
|
{
|
|
struct cred *cred = (struct cred *) current_cred();
|
|
|
|
if (!security_module_enable(&tomoyo_security_ops))
|
|
return 0;
|
|
/* register ourselves with the security framework */
|
|
if (register_security(&tomoyo_security_ops))
|
|
panic("Failure registering TOMOYO Linux");
|
|
printk(KERN_INFO "TOMOYO Linux initialized\n");
|
|
cred->security = &tomoyo_kernel_domain;
|
|
return 0;
|
|
}
|
|
|
|
security_initcall(tomoyo_init);
|