apparmor/module-deprecated/lsm.c

948 lines
21 KiB
C

/*
* Copyright (C) 2002-2005 Novell/SUSE
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* http://forge.novell.com/modules/xfmod/project/?apparmor
*
* Immunix AppArmor LSM interface (previously called "SubDomain")
*/
#include <linux/security.h>
#include <linux/module.h>
#include <linux/mm.h>
/* superblock types */
/* PIPEFS_MAGIC */
#include <linux/pipe_fs_i.h>
/* from net/socket.c */
#define SOCKFS_MAGIC 0x534F434B
/* from inotify.c */
#define INOTIFYFS_MAGIC 0xBAD1DEA
#define VALID_FSTYPE(inode) ((inode)->i_sb->s_magic != PIPEFS_MAGIC && \
(inode)->i_sb->s_magic != SOCKFS_MAGIC && \
(inode)->i_sb->s_magic != INOTIFYFS_MAGIC)
#include <asm/mman.h>
#include "apparmor.h"
#include "inline.h"
/* main SD lock [see get_sdcopy and put_sdcopy] */
rwlock_t sd_lock = RW_LOCK_UNLOCKED;
/* Flag values, also controllable via subdomainfs/control.
* We explicitly do not allow these to be modifiable when exported via
* /sys/modules/parameters, as we want to do additional mediation and
* don't want to add special path code. */
/* Complain mode (used to be 'bitch' mode) */
int subdomain_complain = 0;
module_param_named(complain, subdomain_complain, int, S_IRUSR);
MODULE_PARM_DESC(subdomain_complain, "Toggle AppArmor complain mode");
/* Debug mode */
int subdomain_debug = 0;
module_param_named(debug, subdomain_debug, int, S_IRUSR);
MODULE_PARM_DESC(subdomain_debug, "Toggle AppArmor debug mode");
/* Audit mode */
int subdomain_audit = 0;
module_param_named(audit, subdomain_audit, int, S_IRUSR);
MODULE_PARM_DESC(subdomain_audit, "Toggle AppArmor audit mode");
/* Syscall logging mode */
int subdomain_logsyscall = 0;
module_param_named(logsyscall, subdomain_logsyscall, int, S_IRUSR);
MODULE_PARM_DESC(subdomain_logsyscall, "Toggle AppArmor logsyscall mode");
#ifndef MODULE
static int __init sd_getopt_complain(char *str)
{
get_option(&str, &subdomain_complain);
return 1;
}
__setup("subdomain_complain=", sd_getopt_complain);
static int __init sd_getopt_debug(char *str)
{
get_option(&str, &subdomain_debug);
return 1;
}
__setup("subdomain_debug=", sd_getopt_debug);
static int __init sd_getopt_audit(char *str)
{
get_option(&str, &subdomain_audit);
return 1;
}
__setup("subdomain_audit=", sd_getopt_audit);
static int __init sd_getopt_logsyscall(char *str)
{
get_option(&str, &subdomain_logsyscall);
return 1;
}
__setup("subdomain_logsyscall=", sd_getopt_logsyscall);
#endif
static int subdomain_ptrace(struct task_struct *parent,
struct task_struct *child)
{
int error;
struct subdomain *sd;
unsigned long flags;
error = cap_ptrace(parent, child);
if (error == 0 && parent->security) {
read_lock_irqsave(&sd_lock, flags);
sd = SD_SUBDOMAIN(parent->security);
if (__sd_is_confined(sd))
error = sd_audit_syscallreject(sd, GFP_ATOMIC,
AA_SYSCALL_PTRACE);
read_unlock_irqrestore(&sd_lock, flags);
}
return error;
}
static int subdomain_capget(struct task_struct *target,
kernel_cap_t * effective,
kernel_cap_t * inheritable,
kernel_cap_t * permitted)
{
return cap_capget(target, effective, inheritable, permitted);
}
static int subdomain_capset_check(struct task_struct *target,
kernel_cap_t *effective,
kernel_cap_t *inheritable,
kernel_cap_t *permitted)
{
return cap_capset_check(target, effective, inheritable, permitted);
}
static void subdomain_capset_set(struct task_struct *target,
kernel_cap_t *effective,
kernel_cap_t *inheritable,
kernel_cap_t *permitted)
{
cap_capset_set(target, effective, inheritable, permitted);
return;
}
static int subdomain_capable(struct task_struct *tsk, int cap)
{
int error;
/* cap_capable returns 0 on success, else -EPERM */
error = cap_capable(tsk, cap);
if (error == 0 && current->security) {
struct subdomain *sd, sdcopy;
unsigned long flags;
read_lock_irqsave(&sd_lock, flags);
sd = __get_sdcopy(&sdcopy, tsk);
read_unlock_irqrestore(&sd_lock, flags);
error = sd_capability(sd, cap);
put_sdcopy(sd);
}
return error;
}
static int subdomain_sysctl(struct ctl_table *table, int op)
{
int error = 0;
struct subdomain *sd;
unsigned long flags;
if (!current->security)
return 0;
read_lock_irqsave(&sd_lock, flags);
sd = SD_SUBDOMAIN(current->security);
if ((op & 002) && __sd_is_confined(sd) && !capable(CAP_SYS_ADMIN))
error = sd_audit_syscallreject(sd, GFP_ATOMIC,
AA_SYSCALL_SYSCTL_WRITE);
read_unlock_irqrestore(&sd_lock, flags);
return error;
}
static int subdomain_syslog(int type)
{
return cap_syslog(type);
}
static int subdomain_netlink_send(struct sock *sk, struct sk_buff *skb)
{
return cap_netlink_send(sk, skb);
}
static int subdomain_netlink_recv(struct sk_buff *skb, int cap)
{
return cap_netlink_recv(skb, cap);
}
static void subdomain_bprm_apply_creds(struct linux_binprm *bprm, int unsafe)
{
cap_bprm_apply_creds(bprm, unsafe);
return;
}
static int subdomain_bprm_set_security(struct linux_binprm *bprm)
{
/* handle capability bits with setuid, etc */
cap_bprm_set_security(bprm);
/* already set based on script name */
if (bprm->sh_bang)
return 0;
return sd_register(bprm);
}
static int subdomain_bprm_secureexec(struct linux_binprm *bprm)
{
int ret = cap_bprm_secureexec(bprm);
if (ret == 0 && (unsigned long)bprm->security & SD_SECURE_EXEC_NEEDED) {
SD_DEBUG("%s: secureexec required for %s\n",
__FUNCTION__, bprm->filename);
ret = 1;
}
return ret;
}
static int subdomain_sb_mount(char *dev_name, struct nameidata *nd, char *type,
unsigned long flags, void *data)
{
int error = 0;
struct subdomain *sd;
unsigned long lockflags;
if (!current->security)
return 0;
read_lock_irqsave(&sd_lock, lockflags);
sd = SD_SUBDOMAIN(current->security);
if (__sd_is_confined(sd))
error = sd_audit_syscallreject(sd, GFP_ATOMIC,
AA_SYSCALL_MOUNT);
read_unlock_irqrestore(&sd_lock, lockflags);
return error;
}
static int subdomain_umount(struct vfsmount *mnt, int flags)
{
int error = 0;
struct subdomain *sd;
unsigned long lockflags;
if (!current->security)
return 0;
read_lock_irqsave(&sd_lock, lockflags);
sd = SD_SUBDOMAIN(current->security);
if (__sd_is_confined(sd))
error = sd_audit_syscallreject(sd, GFP_ATOMIC,
AA_SYSCALL_UMOUNT);
read_unlock_irqrestore(&sd_lock, lockflags);
return error;
}
static int subdomain_inode_mkdir(struct inode *inode, struct dentry *dentry,
int mask)
{
struct subdomain sdcopy, *sd;
int error;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
error = sd_perm_dir(sd, dentry, SD_DIR_MKDIR);
put_sdcopy(sd);
return error;
}
static int subdomain_inode_rmdir(struct inode *inode, struct dentry *dentry)
{
struct subdomain sdcopy, *sd;
int error;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
error = sd_perm_dir(sd, dentry, SD_DIR_RMDIR);
put_sdcopy(sd);
return error;
}
static int subdomain_inode_create(struct inode *inode, struct dentry *dentry,
int mask)
{
struct subdomain sdcopy, *sd;
int error;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
/* At a minimum, need write perm to create */
error = sd_perm_dentry(sd, dentry, MAY_WRITE);
put_sdcopy(sd);
return error;
}
static int subdomain_inode_link(struct dentry *old_dentry, struct inode *inode,
struct dentry *new_dentry)
{
int error = 0;
struct subdomain sdcopy, *sd;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
error = sd_link(sd, new_dentry, old_dentry);
put_sdcopy(sd);
return error;
}
static int subdomain_inode_unlink(struct inode *inode, struct dentry *dentry)
{
struct subdomain sdcopy, *sd;
int error;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
error = sd_perm_dentry(sd, dentry, MAY_WRITE);
put_sdcopy(sd);
return error;
}
static int subdomain_inode_mknod(struct inode *inode, struct dentry *dentry,
int mode, dev_t dev)
{
struct subdomain sdcopy, *sd;
int error = 0;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
error = sd_perm_dentry(sd, dentry, MAY_WRITE);
put_sdcopy(sd);
return error;
}
static int subdomain_inode_rename(struct inode *old_inode,
struct dentry *old_dentry,
struct inode *new_inode,
struct dentry *new_dentry)
{
struct subdomain sdcopy, *sd;
int error = 0;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
error = sd_perm_dentry(sd, old_dentry,
MAY_READ | MAY_WRITE);
if (!error)
error = sd_perm_dentry(sd, new_dentry, MAY_WRITE);
put_sdcopy(sd);
return error;
}
static int subdomain_inode_permission(struct inode *inode, int mask,
struct nameidata *nd)
{
int error = 0;
/* Do not perform check on pipes or sockets
* Same as subdomain_file_permission
*/
if (current->security && VALID_FSTYPE(inode)) {
struct subdomain sdcopy, *sd;
sd = get_sdcopy(&sdcopy);
error = sd_perm_nameidata(sd, nd, mask);
put_sdcopy(sd);
}
return error;
}
static int subdomain_inode_setattr(struct dentry *dentry, struct iattr *iattr)
{
struct subdomain sdcopy, *sd;
int error = 0;
if (current->security && VALID_FSTYPE(dentry->d_inode)) {
sd = get_sdcopy(&sdcopy);
/*
* Mediate any attempt to change attributes of a file
* (chmod, chown, chgrp, etc)
*/
error = sd_attr(sd, dentry, iattr);
put_sdcopy(sd);
}
return error;
}
static int subdomain_inode_setxattr(struct dentry *dentry, char *name,
void *value, size_t size, int flags)
{
int error = 0;
if (current->security && VALID_FSTYPE(dentry->d_inode)) {
struct subdomain sdcopy, *sd;
sd = get_sdcopy(&sdcopy);
error = sd_xattr(sd, dentry, name, SD_XATTR_SET);
put_sdcopy(sd);
}
return error;
}
static int subdomain_inode_getxattr(struct dentry *dentry, char *name)
{
int error = 0;
if (current->security && VALID_FSTYPE(dentry->d_inode)) {
struct subdomain sdcopy, *sd;
sd = get_sdcopy(&sdcopy);
error = sd_xattr(sd, dentry, name, SD_XATTR_GET);
put_sdcopy(sd);
}
return error;
}
static int subdomain_inode_listxattr(struct dentry *dentry)
{
int error = 0;
if (current->security && VALID_FSTYPE(dentry->d_inode)) {
struct subdomain sdcopy, *sd;
sd = get_sdcopy(&sdcopy);
error = sd_xattr(sd, dentry, NULL, SD_XATTR_LIST);
put_sdcopy(sd);
}
return error;
}
static int subdomain_inode_removexattr(struct dentry *dentry, char *name)
{
int error = 0;
if (current->security && VALID_FSTYPE(dentry->d_inode)) {
struct subdomain sdcopy, *sd;
sd = get_sdcopy(&sdcopy);
error = sd_xattr(sd, dentry, name, SD_XATTR_REMOVE);
put_sdcopy(sd);
}
return error;
}
static int subdomain_file_permission(struct file *file, int mask)
{
struct subdomain sdcopy, *sd;
struct sdfile *sdf;
int error = 0;
if (!current->security ||
!(sdf = (struct sdfile *)file->f_security) ||
!VALID_FSTYPE(file->f_dentry->d_inode))
return 0;
sd = get_sdcopy(&sdcopy);
if (__sd_is_confined(sd) && sdf->profile != sd->active)
error = sd_perm(sd, file->f_dentry, file->f_vfsmnt,
mask & (MAY_EXEC | MAY_WRITE | MAY_READ));
put_sdcopy(sd);
return error;
}
static int subdomain_file_alloc_security(struct file *file)
{
struct subdomain sdcopy, *sd;
int error = 0;
if (!current->security)
return 0;
sd = get_sdcopy(&sdcopy);
if (__sd_is_confined(sd)) {
struct sdfile *sdf;
sdf = kmalloc(sizeof(struct sdfile), GFP_KERNEL);
if (sdf) {
sdf->type = sd_file_default;
sdf->profile = get_sdprofile(sd->active);
} else {
error = -ENOMEM;
}
file->f_security = sdf;
}
put_sdcopy(sd);
return error;
}
static void subdomain_file_free_security(struct file *file)
{
struct sdfile *sdf = (struct sdfile *)file->f_security;
if (sdf) {
put_sdprofile(sdf->profile);
kfree(sdf);
}
}
static inline int sd_mmap(struct file *file, unsigned long prot,
unsigned long flags)
{
int error = 0, mask = 0;
struct subdomain sdcopy, *sd;
struct sdfile *sdf;
if (!current->security || !file ||
!(sdf = (struct sdfile *)file->f_security) ||
sdf->type == sd_file_shmem)
return 0;
sd = get_sdcopy(&sdcopy);
if (prot & PROT_READ)
mask |= MAY_READ;
/* Private mappings don't require write perms since they don't
* write back to the files */
if (prot & PROT_WRITE && !(flags & MAP_PRIVATE))
mask |= MAY_WRITE;
if (prot & PROT_EXEC)
mask |= SD_EXEC_MMAP;
SD_DEBUG("%s: 0x%x\n", __FUNCTION__, mask);
if (mask)
error = sd_perm(sd, file->f_dentry, file->f_vfsmnt, mask);
put_sdcopy(sd);
return error;
}
static int subdomain_file_mmap(struct file *file, unsigned long reqprot,
unsigned long prot, unsigned long flags)
{
return sd_mmap(file, prot, flags);
}
static int subdomain_file_mprotect(struct vm_area_struct* vma,
unsigned long reqprot, unsigned long prot)
{
return sd_mmap(vma->vm_file, prot,
!(vma->vm_flags & VM_SHARED) ? MAP_PRIVATE : 0);
}
static int subdomain_task_alloc_security(struct task_struct *p)
{
return sd_fork(p);
}
static void subdomain_task_free_security(struct task_struct *p)
{
if (p->security)
sd_release(p);
}
static int subdomain_task_post_setuid(uid_t id0, uid_t id1, uid_t id2,
int flags)
{
return cap_task_post_setuid(id0, id1, id2, flags);
}
static void subdomain_task_reparent_to_init(struct task_struct *p)
{
cap_task_reparent_to_init(p);
return;
}
static int subdomain_shm_shmat(struct shmid_kernel* shp, char __user *shmaddr,
int shmflg)
{
struct sdfile *sdf = (struct sdfile *)shp->shm_file->f_security;
if (sdf)
sdf->type = sd_file_shmem;
return 0;
}
static int subdomain_getprocattr(struct task_struct *p, char *name, void *value,
size_t size)
{
int error;
struct subdomain sdcopy, *sd;
char *str = value;
unsigned long flags;
/* Subdomain only supports the "current" process attribute */
if (strcmp(name, "current") != 0) {
error = -EINVAL;
goto out;
}
/* must be task querying itself or admin */
if (current != p && !capable(CAP_SYS_ADMIN)) {
error = -EPERM;
goto out;
}
read_lock_irqsave(&sd_lock, flags);
sd = __get_sdcopy(&sdcopy, p);
read_unlock_irqrestore(&sd_lock, flags);
error = sd_getprocattr(sd, str, size);
put_sdcopy(sd);
out:
return error;
}
static int subdomain_setprocattr(struct task_struct *p, char *name, void *value,
size_t size)
{
const char *cmd_changehat = "changehat ",
*cmd_setprofile = "setprofile ";
int error = -EACCES; /* default to a perm denied */
char *cmd = (char *)value;
/* only support messages to current */
if (strcmp(name, "current") != 0) {
error = -EINVAL;
goto out;
}
if (!size) {
error = -ERANGE;
goto out;
}
/* CHANGE HAT */
if (size > strlen(cmd_changehat) &&
strncmp(cmd, cmd_changehat, strlen(cmd_changehat)) == 0) {
char *hatinfo = cmd + strlen(cmd_changehat);
size_t infosize = size - strlen(cmd_changehat);
/* Only the current process may change it's hat */
if (current != p) {
SD_WARN("%s: Attempt by foreign task %s(%d) "
"[user %d] to changehat of task %s(%d)\n",
__FUNCTION__,
current->comm,
current->pid,
current->uid,
p->comm,
p->pid);
error = -EACCES;
goto out;
}
error = sd_setprocattr_changehat(hatinfo, infosize);
if (error == 0)
/* success, set return to #bytes in orig request */
error = size;
/* SET NEW PROFILE */
} else if (size > strlen(cmd_setprofile) &&
strncmp(cmd, cmd_setprofile, strlen(cmd_setprofile)) == 0) {
int confined;
unsigned long flags;
/* only an unconfined process with admin capabilities
* may change the profile of another task
*/
if (!capable(CAP_SYS_ADMIN)) {
SD_WARN("%s: Unprivileged attempt by task %s(%d) "
"[user %d] to assign profile to task %s(%d)\n",
__FUNCTION__,
current->comm,
current->pid,
current->uid,
p->comm,
p->pid);
error = -EACCES;
goto out;
}
read_lock_irqsave(&sd_lock, flags);
confined = sd_is_confined();
read_unlock_irqrestore(&sd_lock, flags);
if (!confined) {
char *profile = cmd + strlen(cmd_setprofile);
size_t profilesize = size - strlen(cmd_setprofile);
error = sd_setprocattr_setprofile(p, profile, profilesize);
if (error == 0)
/* success,
* set return to #bytes in orig request
*/
error = size;
} else {
SD_WARN("%s: Attempt by confined task %s(%d) "
"[user %d] to assign profile to task %s(%d)\n",
__FUNCTION__,
current->comm,
current->pid,
current->uid,
p->comm,
p->pid);
error = -EACCES;
}
} else {
/* unknown operation */
SD_WARN("%s: Unknown setprocattr command '%.*s' by task %s(%d) "
"[user %d] for task %s(%d)\n",
__FUNCTION__,
size < 16 ? (int)size : 16,
cmd,
current->comm,
current->pid,
current->uid,
p->comm,
p->pid);
error = -EINVAL;
}
out:
return error;
}
struct security_operations subdomain_ops = {
.ptrace = subdomain_ptrace,
.capget = subdomain_capget,
.capset_check = subdomain_capset_check,
.capset_set = subdomain_capset_set,
.sysctl = subdomain_sysctl,
.capable = subdomain_capable,
.syslog = subdomain_syslog,
.netlink_send = subdomain_netlink_send,
.netlink_recv = subdomain_netlink_recv,
.bprm_apply_creds = subdomain_bprm_apply_creds,
.bprm_set_security = subdomain_bprm_set_security,
.bprm_secureexec = subdomain_bprm_secureexec,
.sb_mount = subdomain_sb_mount,
.sb_umount = subdomain_umount,
.inode_mkdir = subdomain_inode_mkdir,
.inode_rmdir = subdomain_inode_rmdir,
.inode_create = subdomain_inode_create,
.inode_link = subdomain_inode_link,
.inode_unlink = subdomain_inode_unlink,
.inode_mknod = subdomain_inode_mknod,
.inode_rename = subdomain_inode_rename,
.inode_permission = subdomain_inode_permission,
.inode_setattr = subdomain_inode_setattr,
.inode_setxattr = subdomain_inode_setxattr,
.inode_getxattr = subdomain_inode_getxattr,
.inode_listxattr = subdomain_inode_listxattr,
.inode_removexattr = subdomain_inode_removexattr,
.file_permission = subdomain_file_permission,
.file_alloc_security = subdomain_file_alloc_security,
.file_free_security = subdomain_file_free_security,
.file_mmap = subdomain_file_mmap,
.file_mprotect = subdomain_file_mprotect,
.task_alloc_security = subdomain_task_alloc_security,
.task_free_security = subdomain_task_free_security,
.task_post_setuid = subdomain_task_post_setuid,
.task_reparent_to_init = subdomain_task_reparent_to_init,
.shm_shmat = subdomain_shm_shmat,
.getprocattr = subdomain_getprocattr,
.setprocattr = subdomain_setprocattr,
};
static int __init subdomain_init(void)
{
int error = 0;
const char *complainmsg = ": complainmode enabled";
if (!create_subdomainfs()) {
SD_ERROR("Unable to activate AppArmor filesystem\n");
error = -ENOENT;
goto createfs_out;
}
if (!alloc_nullprofiles()){
SD_ERROR("Unable to allocate null profiles\n");
error = -ENOMEM;
goto alloc_out;
}
if ((error = register_security(&subdomain_ops))) {
SD_WARN("Unable to load AppArmor\n");
goto register_security_out;
}
SD_INFO("AppArmor (version %s) initialized%s\n",
apparmor_version(),
subdomain_complain ? complainmsg : "");
sd_audit_message(NULL, GFP_KERNEL, 0,
"AppArmor (version %s) initialized%s\n",
apparmor_version(),
subdomain_complain ? complainmsg : "");
return error;
register_security_out:
free_nullprofiles();
alloc_out:
(void)destroy_subdomainfs();
createfs_out:
return error;
}
static int subdomain_exit_removeall_iter(struct subdomain *sd, void *cookie)
{
/* write_lock(&sd_lock) held here */
if (__sd_is_confined(sd)) {
SD_DEBUG("%s: Dropping profiles %s(%d) "
"profile %s(%p) active %s(%p)\n",
__FUNCTION__,
sd->task->comm, sd->task->pid,
sd->profile->name, sd->profile,
sd->active->name, sd->active);
sd_switch_unconfined(sd);
}
return 0;
}
static void __exit subdomain_exit(void)
{
unsigned long flags;
/* Remove profiles from the global profile list.
* This is just for tidyness as there is no way to reference this
* list once the AppArmor lsm hooks are detached (below)
*/
sd_profilelist_release();
/* Remove profiles from active tasks
* If this is not done, if module is reloaded after being removed,
* old profiles (still refcounted in memory) will become 'magically'
* reattached
*/
write_lock_irqsave(&sd_lock, flags);
sd_subdomainlist_iterate(subdomain_exit_removeall_iter, NULL);
write_unlock_irqrestore(&sd_lock, flags);
/* Free up list of active subdomain */
sd_subdomainlist_release();
free_nullprofiles();
if (!destroy_subdomainfs())
SD_WARN("Unable to properly deactivate AppArmor fs\n");
if (unregister_security(&subdomain_ops))
SD_WARN("Unable to properly unregister AppArmor\n");
SD_INFO("AppArmor protection removed\n");
sd_audit_message(NULL, GFP_KERNEL, 0,
"AppArmor protection removed\n");
}
module_init(subdomain_init);
module_exit(subdomain_exit);
MODULE_DESCRIPTION("AppArmor process confinement");
MODULE_AUTHOR("Tony Jones <tonyj@suse.de>");
MODULE_LICENSE("GPL");