By way of introduction, I am at NAI Labs working on SELinux. Attached is a patch against the current LSM patched 2.4.5 kernel, dated May 30, 2001 (selinux-lsm-patch). I have also attached the SELinux implementation for these new hooks (hooks.c), to help explain why they are necessary. I realize that we have not reached a consensus on what a consistent hook interface should look like, but in the meantime I would like to continue moving foward by identifying locations where hooks are necessary. I have added hooks in the following locations: fcntl/fcntl64 (sys_fcntl/sys_fcntl64): Added hooks to authorize these operations. Additionally, a security field was added to the fown_struct so that the attributes of the owning process could be maintained for later use in send_sigio_to_task. fcntl (send_sigio_to_task): Added hook to verify signal permissions. wait (sys_wait4): Added hook to verify that the parent is authorized to receive the exit status of the child. mmap (do_mmap_pgoff): Added hook to check read/write/execute permission for the request. mprotect (sys_mprotect): Added a hook to verify read/write/execute status for requested change. --------------------- Chris Vance, NAI Labs cvanceat_private Index: fs/fcntl.c =================================================================== RCS file: /cvs/lsm/lsm/fs/fcntl.c,v retrieving revision 1.1.1.2 retrieving revision 1.2 diff -u -r1.1.1.2 -r1.2 --- fs/fcntl.c 2001/05/29 17:00:48 1.1.1.2 +++ fs/fcntl.c 2001/06/04 17:19:28 1.2 @@ -10,6 +10,7 @@ #include <linux/dnotify.h> #include <linux/smp_lock.h> #include <linux/slab.h> +#include <linux/security.h> #include <asm/poll.h> #include <asm/siginfo.h> @@ -278,6 +279,9 @@ err = 0; if (S_ISSOCK (filp->f_dentry->d_inode->i_mode)) err = sock_fcntl (filp, F_SETOWN, arg); + + if (!err) + err = security_ops->fown_ops->alloc_security(&filp->f_owner); unlock_kernel(); break; case F_GETSIG: @@ -320,6 +324,12 @@ if (!filp) goto out; + err = security_ops->file_ops->fcntl(filp, cmd, arg); + if (err) { + fput(filp); + return err; + } + err = do_fcntl(fd, cmd, arg, filp); fput(filp); @@ -338,6 +348,13 @@ if (!filp) goto out; + err = security_ops->file_ops->fcntl64(filp, cmd, arg); + if (err) { + fput(filp); + return err; + } + err = -EBADF; + switch (cmd) { case F_GETLK64: err = fcntl_getlk64(fd, (struct flock64 *) arg); @@ -378,6 +395,10 @@ (fown->euid ^ p->suid) && (fown->euid ^ p->uid) && (fown->uid ^ p->suid) && (fown->uid ^ p->uid)) return; + + if (security_ops->fown_ops->send_sigiotask(p, fown, fd, reason)) + return; + switch (fown->signum) { siginfo_t si; default: Index: include/linux/fs.h =================================================================== RCS file: /cvs/lsm/lsm/include/linux/fs.h,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- include/linux/fs.h 2001/05/29 17:29:14 1.4 +++ include/linux/fs.h 2001/06/04 17:17:42 1.5 @@ -486,6 +486,7 @@ int pid; /* pid or -pgrp where SIGIO should be sent */ uid_t uid, euid; /* uid/euid of process setting the owner */ int signum; /* posix.1b rt signal to be delivered on IO */ + void *security; }; struct file { Index: include/linux/security.h =================================================================== RCS file: /cvs/lsm/lsm/include/linux/security.h,v retrieving revision 1.6 retrieving revision 1.9 diff -u -r1.6 -r1.9 --- include/linux/security.h 2001/05/29 17:29:14 1.6 +++ include/linux/security.h 2001/06/05 13:43:46 1.9 @@ -80,12 +80,21 @@ int (* read) (struct file *); int (* write) (struct file *); int (* ioctl) (struct file *); // need more than file* - int (* mmap) (struct file *); + int (* mmap) (struct file *, unsigned long, unsigned long); + int (* mprotect) (struct vm_area_struct *, unsigned long); int (* lock) (struct file *); int (* readv) (struct file *); int (* writev) (struct file *); + int (* fcntl) (struct file *, unsigned int, unsigned long); + int (* fcntl64) (struct file *, unsigned int, unsigned long); }; +struct fown_security_ops { + int (* alloc_security) (struct fown_struct *); + void (* free_security) (struct fown_struct *); + int (* send_sigiotask) (struct task_struct *, struct fown_struct *, int, int); +}; + struct task_security_ops { int (* create) (void); int (* alloc_security) (struct task_struct *p); // create per process security stuff @@ -97,6 +106,7 @@ int (* setrlimit) (unsigned int resource); // CAP_SYS_RESOURCE int (* setscheduler) (struct task_struct *p, int policy); // CAP_SYS_NICE int (* kill) (struct task_struct *p, struct siginfo *info, int sig); // CAP_KILL + int (* wait) (struct task_struct *p); /* set and (in case of exec failure) unset security label */ int (* set_label) (char *filename); @@ -157,6 +167,7 @@ struct super_block_security_ops * sb_ops; struct inode_security_ops * inode_ops; struct file_security_ops * file_ops; + struct fown_security_ops * fown_ops; struct task_security_ops * task_ops; struct socket_security_ops * socket_ops; struct module_security_ops * module_ops; Index: kernel/capability_plug.c =================================================================== RCS file: /cvs/lsm/lsm/kernel/capability_plug.c,v retrieving revision 1.5 retrieving revision 1.8 diff -u -r1.5 -r1.8 --- kernel/capability_plug.c 2001/05/29 17:29:14 1.5 +++ kernel/capability_plug.c 2001/06/05 14:06:38 1.8 @@ -376,10 +376,14 @@ { return 0; } -static int cap_file_mmap(struct file *file) +static int cap_file_mmap(struct file *file, unsigned long prot, unsigned long flags) { return 0; } +static int cap_file_mprotect(struct vm_area_struct *vma, unsigned long prot) +{ + return 0; +} static int cap_file_lock(struct file *file) { return 0; @@ -393,6 +397,36 @@ return 0; } +static int cap_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +static int cap_file_fcntl64(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +/* fown security operations */ +static int cap_fown_alloc_security(struct fown_struct *fown) +{ + return 0; +} + +static void cap_fown_free_security(struct fown_struct *fown) +{ + return; +} + +static int cap_fown_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int fd, + int reason) +{ + return 0; +} + /* task security operations */ static int cap_task_create(void) { @@ -444,6 +478,10 @@ { return is_capable(CAP_SYS_NICE) ? 0 : -EPERM; } +static int cap_task_wait(struct task_struct *p) +{ + return 0; +} static int cap_task_kill(struct task_struct *p, struct siginfo *info, int sig) { return is_capable(CAP_KILL) ? 0 : -EPERM; @@ -568,9 +606,18 @@ write: cap_file_write, ioctl: cap_file_ioctl, mmap: cap_file_mmap, + mprotect: cap_file_mprotect, lock: cap_file_lock, readv: cap_file_readv, writev: cap_file_writev, + fcntl: cap_file_fcntl, + fcntl64: cap_file_fcntl64, +}; + +static struct fown_security_ops cap_fown_ops = { + alloc_security: cap_fown_alloc_security, + free_security: cap_fown_free_security, + send_sigiotask: cap_fown_send_sigiotask, }; static struct task_security_ops cap_task_ops = { @@ -583,6 +630,7 @@ setnice: cap_task_setnice, setrlimit: cap_task_setrlimit, setscheduler: cap_task_setscheduler, + wait: cap_task_wait, kill: cap_task_kill, set_label: cap_task_set_label, reset_label: cap_task_reset_label, @@ -636,6 +684,7 @@ sb_ops: &cap_sb_ops, inode_ops: &cap_inode_ops, file_ops: &cap_file_ops, + fown_ops: &cap_fown_ops, task_ops: &cap_task_ops, socket_ops: &cap_socket_ops, module_ops: &cap_module_ops, Index: kernel/exit.c =================================================================== RCS file: /cvs/lsm/lsm/kernel/exit.c,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- kernel/exit.c 2001/05/29 17:29:14 1.3 +++ kernel/exit.c 2001/06/04 17:20:04 1.4 @@ -524,6 +524,10 @@ if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0)) && !(options & __WALL)) continue; + + if (security_ops->task_ops->wait(p)) + continue; + flag = 1; switch (p->state) { case TASK_STOPPED: Index: kernel/security.c =================================================================== RCS file: /cvs/lsm/lsm/kernel/security.c,v retrieving revision 1.5 retrieving revision 1.8 diff -u -r1.5 -r1.8 --- kernel/security.c 2001/05/29 17:29:14 1.5 +++ kernel/security.c 2001/06/05 14:06:38 1.8 @@ -88,11 +88,18 @@ static int dummy_file_read (struct file *file) {return 0;} static int dummy_file_write (struct file *file) {return 0;} static int dummy_file_ioctl (struct file *file) {return 0;} -static int dummy_file_mmap (struct file *file) {return 0;} +static int dummy_file_mmap (struct file *file, unsigned long prot, unsigned long flags) {return 0;} +static int dummy_file_mprotect (struct vm_area_struct *vma, unsigned long prot) {return 0;} static int dummy_file_lock (struct file *file) {return 0;} static int dummy_file_readv (struct file *file) {return 0;} static int dummy_file_writev (struct file *file) {return 0;} +static int dummy_file_fcntl (struct file *file, unsigned int cmd, unsigned long arg) {return 0;} +static int dummy_file_fcntl64 (struct file *file, unsigned int cmd, unsigned long arg) {return 0;} +static int dummy_fown_alloc_security (struct fown_struct *fown) {return 0;} +static void dummy_fown_free_security (struct fown_struct *fown) {return;} +static int dummy_fown_send_sigiotask (struct task_struct *tsk, struct fown_struct *fown, int fd, int reason) {return 0;} + static int dummy_task_create (void) {return 0;} static int dummy_task_alloc_security (struct task_struct *p) {return 0;} static void dummy_task_free_security (struct task_struct *p) {return;} @@ -102,6 +109,7 @@ static int dummy_task_setnice (struct task_struct *p, int nice) {return 0;} static int dummy_task_setrlimit (unsigned int resource) {return 0;} static int dummy_task_setscheduler (struct task_struct *p, int policy) {return 0;} +static int dummy_task_wait (struct task_struct *p) {return 0;} static int dummy_task_kill (struct task_struct *p, struct siginfo *info, int sig) {return 0;} static int dummy_task_set_label (char *filename) {return 0;} static void dummy_task_reset_label (void) {return;} @@ -172,9 +180,18 @@ write: dummy_file_write, ioctl: dummy_file_ioctl, mmap: dummy_file_mmap, + mprotect: dummy_file_mprotect, lock: dummy_file_lock, readv: dummy_file_readv, writev: dummy_file_writev, + fcntl: dummy_file_fcntl, + fcntl64: dummy_file_fcntl64, +}; + +static struct fown_security_ops dummy_fown_ops = { + alloc_security: dummy_fown_alloc_security, + free_security: dummy_fown_free_security, + send_sigiotask: dummy_fown_send_sigiotask, }; static struct task_security_ops dummy_task_ops = { @@ -187,6 +204,7 @@ setnice: dummy_task_setnice, setrlimit: dummy_task_setrlimit, setscheduler: dummy_task_setscheduler, + wait: dummy_task_wait, kill: dummy_task_kill, set_label: dummy_task_set_label, reset_label: dummy_task_reset_label, Index: mm/mmap.c =================================================================== RCS file: /cvs/lsm/lsm/mm/mmap.c,v retrieving revision 1.1.1.2 retrieving revision 1.2 diff -u -r1.1.1.2 -r1.2 --- mm/mmap.c 2001/05/29 17:00:58 1.1.1.2 +++ mm/mmap.c 2001/06/05 12:47:05 1.2 @@ -13,6 +13,7 @@ #include <linux/init.h> #include <linux/file.h> #include <linux/fs.h> +#include <linux/security.h> #include <asm/uaccess.h> #include <asm/pgalloc.h> @@ -251,6 +252,10 @@ return -EAGAIN; } + error = security_ops->file_ops->mmap(file, flags, prot); + if (error) + return error; + if (file) { switch (flags & MAP_TYPE) { case MAP_SHARED: Index: mm/mprotect.c =================================================================== RCS file: /cvs/lsm/lsm/mm/mprotect.c,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- mm/mprotect.c 2001/05/09 20:09:31 1.1.1.1 +++ mm/mprotect.c 2001/06/05 13:42:37 1.2 @@ -7,6 +7,7 @@ #include <linux/smp_lock.h> #include <linux/shm.h> #include <linux/mman.h> +#include <linux/security.h> #include <asm/uaccess.h> #include <asm/pgalloc.h> @@ -260,6 +261,10 @@ error = -EACCES; break; } + + error = security_ops->file_ops->mprotect(vma, prot); + if (error) + break; if (vma->vm_end >= end) { error = mprotect_fixup(vma, nstart, end, newflags); /* * NSA Security-Enhanced Linux plug * * Authors: Stephen Smalley, NAI Labs, <sdsat_private> * Chris Vance, <cvanceat_private> * * 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; either version 2 of the License, or * (at your option) any later version. */ #include <linux/config.h> #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/security.h> #include <linux/capability.h> #include <linux/flask/avc.h> #include <linux/flask/psid.h> #include <linux/flask/syscalls.h> #include <linux/mm.h> #include <linux/mman.h> #include <linux/slab.h> #include <linux/smp_lock.h> #include <linux/spinlock.h> #include <linux/file.h> #include <asm/uaccess.h> #include "selinux_plug.h" /* Lists of security blobs created by this module. Used to deallocate all security blobs and clear security fields when the module exits. */ static LIST_HEAD(task_security_head); static LIST_HEAD(inode_security_head); static LIST_HEAD(file_security_head); static LIST_HEAD(superblock_security_head); /* Allocate and free functions for each kind of security blob. */ static int task_alloc_security(struct task_struct *task) { struct task_security_struct *tsec = task->security; tsec = kmalloc(sizeof(struct task_security_struct), GFP_KERNEL); if (!tsec) return -ENOMEM; memset(tsec, 0, sizeof(struct task_security_struct)); tsec->magic = SELINUX_MAGIC; tsec->task = task; list_add(&tsec->list, &task_security_head); tsec->osid = tsec->sid = SECINITSID_UNLABELED; task->security = tsec; return 0; } static void task_free_security(struct task_struct *task) { struct task_security_struct *tsec = task->security; if (!tsec || tsec->magic != SELINUX_MAGIC) return; task->security = NULL; list_del(&tsec->list); kfree(tsec); } static int inode_alloc_security(struct inode *inode) { struct task_security_struct *tsec = current->security; struct inode_security_struct *isec = inode->i_security; isec = kmalloc(sizeof(struct inode_security_struct), GFP_KERNEL); if (!isec) return -ENOMEM; memset(isec, 0, sizeof(struct inode_security_struct)); isec->magic = SELINUX_MAGIC; isec->inode = inode; list_add(&isec->list, &inode_security_head); if (tsec && tsec->magic == SELINUX_MAGIC) isec->sid = tsec->sid; else isec->sid = SECINITSID_UNLABELED; inode->i_security = isec; return 0; } static void inode_free_security(struct inode *inode) { struct inode_security_struct *isec = inode->i_security; if (!isec || isec->magic != SELINUX_MAGIC) return; inode->i_security = NULL; list_del(&isec->list); kfree(isec); } static int fown_alloc_security(struct task_struct *task, struct fown_struct *fown) { struct task_security_struct *tsec = task->security; struct fown_security_struct *fsec; if (!tsec || tsec->magic != SELINUX_MAGIC) { printk("file_alloc_security: task pid=%d does not have a security field\n", task->pid); tsec = NULL; } fsec = kmalloc(sizeof(struct fown_security_struct), GFP_KERNEL); if (!fsec) return -ENOMEM; memset(fsec, 0, sizeof(struct fown_security_struct)); fsec->magic = SELINUX_MAGIC; fsec->sid = tsec ? tsec->sid : SECINITSID_UNLABELED; fown->security = fsec; return 0; } static void fown_free_security(struct fown_struct *fown) { struct fown_security_struct *fsec = fown->security; if (!fsec || fsec->magic != SELINUX_MAGIC) return; fown->security = NULL; kfree(fsec); } static int file_alloc_security(struct task_struct *task, struct file *file) { struct task_security_struct *tsec = task->security; struct file_security_struct *fsec = file->f_security; if (!tsec || tsec->magic != SELINUX_MAGIC) { printk("file_alloc_security: task pid=%d does not have a security field\n", task->pid); tsec = NULL; } fsec = kmalloc(sizeof(struct file_security_struct), GFP_KERNEL); if (!fsec) return -ENOMEM; memset(fsec, 0, sizeof(struct file_security_struct)); fsec->magic = SELINUX_MAGIC; fsec->file = file; list_add(&fsec->list, &file_security_head); fsec->sid = tsec ? tsec->sid : SECINITSID_UNLABELED; file->f_security = fsec; return 0; } static void file_free_security(struct file *file) { struct file_security_struct *fsec = file->f_security; if (!fsec || fsec->magic != SELINUX_MAGIC) return; file->f_security = NULL; list_del(&fsec->list); kfree(fsec); fown_free_security(&(file->f_owner)); } static int superblock_alloc_security(struct super_block *sb) { struct superblock_security_struct *sbsec = sb->s_security; sbsec = kmalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); if (!sbsec) return -ENOMEM; memset(sbsec, 0, sizeof(struct superblock_security_struct)); sbsec->magic = SELINUX_MAGIC; sbsec->sb = sb; list_add(&sbsec->list, &superblock_security_head); sbsec->sid = SECINITSID_UNLABELED; sb->s_security = sbsec; return 0; } static void superblock_free_security(struct super_block *sb) { struct superblock_security_struct *sbsec = sb->s_security; if (!sbsec || sbsec->magic != SELINUX_MAGIC) return; if (sbsec->uses_psids && sbsec->psidtab) psid_release(sb); sb->s_security = NULL; list_del(&sbsec->list); kfree(sbsec); } /* The security server must be initialized before any labeling or access decisions can be provided. */ static int ss_precondition(void) { static int ss_initializing = 0, ss_initialized = 0; if (ss_initialized) return 0; /* ready for service */ if (ss_initializing) return -1; /* currently initializing */ if (!current->fs->rootmnt) return -1; /* waiting for root file system */ ss_initializing = 1; /* start initialization */ security_init(); ss_initialized = 1; /* done initialization */ return 0; /* ready for service */ } /* The file system's label must be initialized prior to use. If the file system is persistent, then its persistent label mapping must be initialized before labels for files in it can be obtained. */ int superblock_precondition(struct super_block *sb) { struct superblock_security_struct *sbsec = sb->s_security; int rc; if (ss_precondition()) return -1; if (sbsec && sbsec->magic == SELINUX_MAGIC) { if (sbsec->initialized) return 0; /* ready for service */ if (sbsec->initializing) return -1; /* currently inititalizing */ } if (!sbsec || sbsec->magic != SELINUX_MAGIC) { rc = superblock_alloc_security(sb); if (rc) return rc; sbsec = sb->s_security; } sbsec->initializing = 1; if ((strcmp(sb->s_type->name, "ext2") == 0) || (strcmp(sb->s_type->name, "ufs") == 0) || (strcmp(sb->s_type->name, "reiserfs") == 0)) { /* PSIDs only work for persistent file systems with persistent inode numbers. */ rc = psid_init(sb); if (rc) { printk("superblock_precondition: psid_init returned %d\n", -rc); return rc; } sbsec->uses_psids = 1; } else if (strcmp(sb->s_type->name, "proc") == 0) sbsec->sid = SECINITSID_PROC; else if (strcmp(sb->s_type->name, "devpts") == 0) sbsec->sid = SECINITSID_DEVPTS; else if (strcmp(sb->s_type->name, "nfs") == 0) sbsec->sid = SECINITSID_NFS; else /* Nothing to do here. */ ; sbsec->initialized = 1; return 0; } /* The inode's security attributes must be initialized before first use. */ int inode_precondition(struct inode *inode) { struct superblock_security_struct *sbsec = NULL; struct inode_security_struct *isec = inode->i_security; int rc; if (ss_precondition()) return -1; if (isec && isec->magic == SELINUX_MAGIC) { if (isec->initialized) return 0; if (isec->initializing) return -1; } if (inode->i_sb) { rc = superblock_precondition(inode->i_sb); if (rc) return rc; sbsec = inode->i_sb->s_security; } if (!isec || isec->magic != SELINUX_MAGIC) { rc = inode_alloc_security(inode); if (rc) return rc; isec = inode->i_security; } isec->initializing = 1; isec->sclass = inode_mode_to_security_class(inode->i_mode); if (sbsec) { if (sbsec->uses_psids) { rc = psid_to_sid(inode, &isec->sid); if (rc) { printk("inode_precondition: psid_to_sid returned %d for inode %p\n", -rc, inode); } } else if (sbsec->sid != SECINITSID_UNLABELED) { /* inherit from file system */ isec->sid = sbsec->sid; } /* XXX: Still need to provide equivalents for SELinux handling of special file systems like devpts and procfs. */ } isec->initialized = 1; return 0; } /* The task's security attributes must be initialized before first use. */ int task_precondition(struct task_struct *task) { struct task_security_struct *tsec = task->security, *psec; struct task_struct *parent = task->p_pptr; struct vm_area_struct *vma = NULL; struct inode *inode = NULL; struct inode_security_struct *isec; security_id_t newsid; security_context_t context; char *buffer = NULL, *path = NULL; __u32 len; int rc; if (ss_precondition()) return -1; if (tsec && tsec->magic == SELINUX_MAGIC) return 0; parent = task->p_pptr; if (parent == task) { rc = task_alloc_security(task); if (rc) return rc; tsec = task->security; tsec->osid = tsec->sid = SECINITSID_KERNEL; goto out; } rc = task_precondition(parent); if (rc) return rc; psec = parent->security; rc = task_alloc_security(task); if (rc) return rc; tsec = task->security; /* Default to the attributes of my parent. */ tsec->osid = psec->osid; tsec->sid = psec->sid; /* Try to determine the executable. */ if (!task->mm) goto out; for (vma = task->mm->mmap; vma; vma = vma->vm_next) { if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) { break; } } if (!vma) goto out; /* Try to obtain the executable's security attributes. */ inode = vma->vm_file->f_dentry->d_inode; if (!inode) goto out; rc = inode_precondition(inode); if (rc) { /* May be in the midst of initializing the PSID mapping. */ task_free_security(task); return rc; } isec = inode->i_security; /* Compute my attributes based on the attributes of my parent and my executable. */ rc = security_transition_sid(psec->sid, isec->sid, SECCLASS_PROCESS, &newsid); if (rc) { printk("task_precondition: security_transition_sid returned %d\n", -rc); goto out; } tsec->sid = newsid; out: /* Typically, a task's attributes are initially assigned by task_alloc_security and changed upon program execution by bprm_compute_creds. So task_precondition should only determine a task's attributes if the task was created prior to the initialization of this module. Show all such assignments until we are sure that they occur correctly both in the static case and the dynamically loaded case. */ if (vma) { buffer = (char*)__get_free_page(GFP_KERNEL); if (buffer) { path = d_path(vma->vm_file->f_dentry, vma->vm_file->f_vfsmnt, buffer, PAGE_SIZE); } } rc = security_sid_to_context(tsec->sid, &context, &len); if (rc) { printk("task_precondition: assigning SID %d to pid %d exe=%s\n", tsec->sid, task->pid, path ? path : "none"); } else { printk("task_precondition: assigning context %s to pid %d exe=%s\n", context, task->pid, path ? path : "none"); kfree(context); } if (buffer) free_page((unsigned long)buffer); return 0; } /* The file's security attributes must be initialized before first use. */ int file_precondition(struct file *file) { struct task_security_struct *tsec; struct file_security_struct *fsec = file->f_security; if (ss_precondition()) return -1; if (fsec && fsec->magic == SELINUX_MAGIC) return 0; if (task_precondition(current)) return -1; tsec = current->security; return file_alloc_security(current, file); } /* Check permission betweeen a pair of tasks, e.g. signal checks, fork check, ptrace check, etc. */ int task_has_perm(struct task_struct *tsk1, struct task_struct *tsk2, access_vector_t perms) { struct task_security_struct *tsec1, *tsec2; if (task_precondition(tsk1)) return 0; if (task_precondition(tsk2)) return 0; tsec1 = tsk1->security; tsec2 = tsk2->security; return avc_has_perm_ref(tsec1->sid, tsec2->sid, SECCLASS_PROCESS, perms, &tsec2->avcr); } /* Check whether a task is allowed to use a capability. */ int task_has_capability(struct task_struct *tsk, int cap) { struct task_security_struct *tsec; avc_audit_data_t ad; int rc; /* XXX First, apply the traditional superuser test. XXX This test can be removed if we stack the SELinux XXX module with the capabilities module. */ if (cap_is_fs_cap(cap)) { if (tsk->fsuid != 0) return -EPERM; } else { if (tsk->euid != 0) return -EPERM; } /* If the superuser test passes, then check the SELinux permission. */ if (task_precondition(tsk)) goto grant; tsec = tsk->security; AVC_AUDIT_DATA_INIT(&ad,CAP); ad.u.cap = cap; rc = avc_has_perm_audit(tsec->sid, tsec->sid, SECCLASS_CAPABILITY, CAP_TO_MASK(cap), &ad); if (rc) return rc; grant: current->flags |= PF_SUPERPRIV; return 0; } /* Check whether a task is allowed to use a system operation. */ int task_has_system(struct task_struct *tsk, access_vector_t perms) { struct task_security_struct *tsec; if (task_precondition(tsk)) return 0; tsec = tsk->security; return avc_has_perm(tsec->sid, SECINITSID_KERNEL, SECCLASS_SYSTEM, perms); } /* Check whether a task is allowed to use a security operation. */ int task_has_security(struct task_struct *tsk, access_vector_t perms) { struct task_security_struct *tsec; if (task_precondition(tsk)) return 0; tsec = tsk->security; return avc_has_perm(tsec->sid, SECINITSID_SECURITY, SECCLASS_SECURITY, perms); } /* Check whether a task has a particular permission to an inode. The 'aeref' parameter is optional and allows other AVC entry references to be passed (e.g. the one in the struct file). The 'adp' parameter is optional and allows other audit data to be passed (e.g. the dentry). */ int inode_has_perm(struct task_struct *tsk, struct inode *inode, access_vector_t perms, avc_entry_ref_t *aeref, avc_audit_data_t *adp) { struct task_security_struct *tsec; struct inode_security_struct *isec; avc_audit_data_t ad; if (task_precondition(tsk)) return 0; if (inode_precondition(inode)) return 0; tsec = tsk->security; isec = inode->i_security; if (!adp) { adp = &ad; AVC_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.inode = inode; } return avc_has_perm_ref_audit(tsec->sid, isec->sid, isec->sclass, perms, aeref ? aeref : &isec->avcr, adp); } /* Same as inode_has_perm, but pass explicit audit data containing the dentry to help the auditing code to more easily generate the pathname if needed. */ static inline int dentry_has_perm(struct task_struct *tsk, struct dentry *dentry, access_vector_t av, avc_entry_ref_t *aeref) { struct inode *inode = dentry->d_inode; avc_audit_data_t ad; AVC_AUDIT_DATA_INIT(&ad,FS); ad.u.fs.dentry = dentry; return inode_has_perm(tsk, inode, av, aeref, &ad); } /* Check whether a task can use an open file descriptor to access an inode in a given way. Check access to the descriptor itself, and then use dentry_has_perm to check a particular permission to the file. Access to the descriptor is implicitly granted if it has the same SID as the process. If av is zero, then access to the file is not checked, e.g. for cases where only the descriptor is affected like seek. */ static inline int file_has_perm(struct task_struct *tsk, struct file *file, access_vector_t av) { struct task_security_struct *tsec; struct file_security_struct *fsec; struct dentry *dentry = file->f_dentry; avc_audit_data_t ad; int rc; if (task_precondition(current)) return 0; if (file_precondition(file)) return 0; tsec = current->security; fsec = file->f_security; if (tsec->sid != fsec->sid) { AVC_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.dentry = file->f_dentry; rc = avc_has_perm_ref_audit(tsec->sid, fsec->sid, SECCLASS_FD, FD__USE, &fsec->avcr, &ad); if (rc) return rc; } /* av is zero if only checking access to the descriptor. */ if (av) return dentry_has_perm(tsk, dentry, av, &fsec->inode_avcr); return 0; } /* Check whether a task can create a file. */ static int may_create(struct inode *dir, struct dentry *dentry, security_class_t tclass) { struct task_security_struct *tsec; struct inode_security_struct *dsec; struct superblock_security_struct *sbsec; security_id_t newsid; avc_audit_data_t ad; int rc; if (task_precondition(current)) return 0; if (inode_precondition(dir)) return 0; tsec = current->security; dsec = dir->i_security; AVC_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.dentry = dentry; rc = avc_has_perm_ref_audit(tsec->sid, dsec->sid, SECCLASS_DIR, DIR__ADD_NAME | DIR__SEARCH, &dsec->avcr, &ad); if (rc) return rc; if (tsec->in_sid[0]) { newsid = tsec->in_sid[0]; } else { rc = security_transition_sid(tsec->sid, dsec->sid, tclass, &newsid); if (rc) return rc; } rc = avc_has_perm_audit(tsec->sid, newsid, tclass, FILE__CREATE, &ad); if (rc) return rc; if (dir->i_sb) { sbsec = dir->i_sb->s_security; rc = avc_has_perm_audit(newsid, sbsec->sid, SECCLASS_FILESYSTEM, FILESYSTEM__ASSOCIATE, &ad); if (rc) return rc; } return 0; } #define MAY_LINK 0 #define MAY_UNLINK 1 #define MAY_RMDIR 2 /* Check whether a task can link, unlink, or rmdir a file/directory. */ static int may_link(struct inode *dir, struct dentry *dentry, int kind) { struct task_security_struct *tsec; struct inode_security_struct *dsec, *isec; avc_audit_data_t ad; access_vector_t av; int rc; if (task_precondition(current)) return 0; if (inode_precondition(dir)) return 0; if (inode_precondition(dentry->d_inode)) return 0; tsec = current->security; dsec = dir->i_security; isec = dentry->d_inode->i_security; AVC_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.dentry = dentry; av = DIR__SEARCH; av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); rc = avc_has_perm_ref_audit(tsec->sid, dsec->sid, SECCLASS_DIR, av, &dsec->avcr, &ad); if (rc) return rc; switch (kind) { case MAY_LINK: av = FILE__LINK; break; case MAY_UNLINK: av = FILE__UNLINK; break; case MAY_RMDIR: av = DIR__RMDIR; break; default: printk("may_link: unrecognized kind %d\n", kind); return 0; } rc = avc_has_perm_ref_audit(tsec->sid, isec->sid, isec->sclass, av, &isec->avcr, &ad); return rc; } static inline int may_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { struct task_security_struct *tsec; struct inode_security_struct *old_dsec, *new_dsec, *old_isec, *new_isec; avc_audit_data_t ad; access_vector_t av; int old_is_dir, new_is_dir; int rc; if (task_precondition(current)) return 0; if (inode_precondition(old_dir)) return 0; if (inode_precondition(new_dir)) return 0; if (inode_precondition(old_dentry->d_inode)) return 0; if (new_dentry->d_inode && inode_precondition(new_dentry->d_inode)) return 0; tsec = current->security; old_dsec = old_dir->i_security; old_isec = old_dentry->d_inode->i_security; old_is_dir = S_ISDIR(old_dentry->d_inode->i_mode); new_dsec = new_dir->i_security; AVC_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.dentry = old_dentry; rc = avc_has_perm_ref_audit(tsec->sid, old_dsec->sid, SECCLASS_DIR, DIR__REMOVE_NAME | DIR__SEARCH, &old_dsec->avcr, &ad); if (rc) return rc; rc = avc_has_perm_ref_audit(tsec->sid, old_isec->sid, old_isec->sclass, FILE__RENAME, &old_isec->avcr, &ad); if (rc) return rc; if (old_is_dir && new_dir != old_dir) { rc = avc_has_perm_ref_audit(tsec->sid, old_isec->sid, old_isec->sclass, DIR__REPARENT, &old_isec->avcr, &ad); if (rc) return rc; } ad.u.fs.dentry = new_dentry; av = DIR__ADD_NAME | DIR__SEARCH; if (new_dentry->d_inode) av |= DIR__REMOVE_NAME; rc = avc_has_perm_ref_audit(tsec->sid, new_dsec->sid, SECCLASS_DIR, av,&new_dsec->avcr, &ad); if (rc) return rc; if (new_dentry->d_inode) { new_isec = new_dentry->d_inode->i_security; new_is_dir = S_ISDIR(new_dentry->d_inode->i_mode); rc = avc_has_perm_ref_audit(tsec->sid, new_isec->sid, new_isec->sclass, (new_is_dir ? DIR__RMDIR : FILE__UNLINK), &new_isec->avcr, &ad); if (rc) return rc; } return 0; } /* Check whether a task can perform a filesystem operation. */ int superblock_has_perm(struct task_struct *tsk, struct super_block *sb, access_vector_t perms, avc_audit_data_t *ad) { struct task_security_struct *tsec; struct superblock_security_struct *sbsec; if (task_precondition(tsk)) return 0; if (superblock_precondition(sb)) return 0; tsec = tsk->security; sbsec = sb->s_security; return avc_has_perm_audit(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, perms, ad); } /* Convert a Linux mode and permission mask to an access vector. */ static inline access_vector_t file_mask_to_av(int mode, int mask) { access_vector_t av = 0; if ((mode & S_IFMT) != S_IFDIR) { if (mask & MAY_EXEC) av |= FILE__EXECUTE; if (mask & MAY_WRITE) av |= FILE__WRITE; if (mask & MAY_READ) av |= FILE__READ; } else { if (mask & MAY_EXEC) av |= DIR__SEARCH; if (mask & MAY_WRITE) av |= DIR__WRITE; if (mask & MAY_READ) av |= DIR__READ; } return av; } /* Convert a Linux file mode to an access vector. */ static inline access_vector_t file_mode_to_av(mode_t mode) { access_vector_t av = 0; if (mode & FMODE_READ) av |= FILE__READ; if (mode & FMODE_WRITE) av |= FILE__WRITE; return av; } /* Set an inode's SID, where the inode may or may not already have a security structure. */ int inode_security_set_sid(struct inode *inode, security_id_t sid) { struct inode_security_struct *isec = inode->i_security; int rc; if (!isec) { rc = inode_alloc_security(inode); if (rc) return rc; isec = inode->i_security; } isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = sid; isec->initialized = 1; return 0; } /* Set the security attributes on a newly created file. */ static int post_create(struct inode *dir, struct dentry *dentry) { struct task_security_struct *tsec; struct inode_security_struct *dsec; struct superblock_security_struct *sbsec; security_id_t newsid; int rc; if (task_precondition(current)) return 0; if (inode_precondition(dir)) return 0; tsec = current->security; dsec = dir->i_security; /* XXX: Need a way to propagate SID from may_create to post_create. */ if (tsec->in_sid[0]) { newsid = tsec->in_sid[0]; } else { rc = security_transition_sid(tsec->sid, dsec->sid, inode_mode_to_security_class(dentry->d_inode->i_mode), &newsid); if (rc) { printk("post_create: unable to obtain new SID, rc=%d\n",-rc); return rc; } } rc = inode_security_set_sid(dentry->d_inode, newsid); if (rc) { printk("post_create: unable to set new SID, rc=%d\n",-rc); return rc; } if (dir->i_sb) { sbsec = dir->i_sb->s_security; if (sbsec && sbsec->uses_psids) { rc = sid_to_psid(dentry->d_inode, newsid); if (rc) { printk("post_create: unable to set new PSID, rc=%d\n",-rc); return rc; } } } return 0; } /* Hook functions begin here. */ /* assorted security operations (mostly syscall interposition) */ static int selinux_sethostname(void) { return task_has_capability(current,CAP_SYS_ADMIN); } static int selinux_setdomainname(void) { return task_has_capability(current,CAP_SYS_ADMIN); } static int selinux_reboot(unsigned int cmd) { return task_has_capability(current,CAP_SYS_BOOT); } static int selinux_mount(char * dev_name, struct nameidata *nd, char * type, unsigned long flags, void * data) { return dentry_has_perm(current, nd->dentry, DIR__MOUNTON, NULL); } static int selinux_add_vfsmnt(struct nameidata *nd, struct super_block *sb, char * dev_name) { struct inode_security_struct *isec1, *isec2; struct inode *inode; avc_audit_data_t ad; int rc; AVC_AUDIT_DATA_INIT(&ad,FS); ad.u.fs.dentry = nd->dentry; rc = superblock_has_perm(current, sb, FILESYSTEM__MOUNT, &ad); if (rc) return rc; inode = sb->s_root->d_inode; if (!inode || inode_precondition(inode)) return 0; isec1 = inode->i_security; inode = nd->dentry->d_inode; if (!inode || inode_precondition(inode)) return 0; isec2 = inode->i_security; return avc_has_perm_audit(isec1->sid, isec2->sid, SECCLASS_DIR, DIR__MOUNTASSOCIATE, &ad); } static int selinux_umount(struct vfsmount *mnt, int flags) { return superblock_has_perm(current,mnt->mnt_sb, FILESYSTEM__UNMOUNT,NULL); } static void selinux_umount_close(struct vfsmount *mnt) { struct super_block *sb = mnt->mnt_sb; struct superblock_security_struct *sbsec = sb->s_security; if (sbsec && sbsec->uses_psids) psid_release(sb); return; } static void selinux_umount_busy (struct vfsmount *mnt) { struct super_block *sb = mnt->mnt_sb; struct superblock_security_struct *sbsec = sb->s_security; if (sbsec && sbsec->uses_psids) psid_init(sb); return; } static int selinux_remount(struct vfsmount *mnt, unsigned long flags, void *data) { return superblock_has_perm(current, mnt->mnt_sb, FILESYSTEM__REMOUNT, NULL); } static void selinux_post_remount(struct vfsmount *mnt, unsigned long flags, void *data) { struct super_block *sb = mnt->mnt_sb; struct superblock_security_struct *sbsec = sb->s_security; if (sbsec && sbsec->uses_psids) psid_remount(sb); } static int selinux_ioperm(void) { return task_has_capability(current,CAP_SYS_RAWIO); } static int selinux_iopl(void) { return task_has_capability(current,CAP_SYS_RAWIO); } static int selinux_ptrace(struct task_struct *parent, struct task_struct *child) { return task_has_perm(parent, child, PROCESS__PTRACE); } static int selinux_setcapablity(void) { return task_has_capability(current,CAP_SETPCAP); } static int selinux_acct(void) { return task_has_capability(current,CAP_SYS_PACCT); } static int selinux_capable(int cap) { return task_has_capability(current,cap); } /* binprm security operations */ static int selinux_bprm_alloc_security(struct linux_binprm *bprm) { struct task_security_struct *tsec; struct inode *inode = bprm->file->f_dentry->d_inode; struct inode_security_struct *isec; security_id_t newsid; avc_audit_data_t ad; int rc; if (bprm->sh_bang) /* The security field should already be set properly. */ return 0; /* Clear the field since it may contain garbage. */ bprm->security = NULL; /* Preconditions */ if (task_precondition(current)) return 0; if (inode_precondition(inode)) return 0; tsec = current->security; isec = inode->i_security; /* Default to the current task SID. */ bprm->security = (void *)tsec->sid; if (tsec->in_sid[0]) { newsid = tsec->in_sid[0]; } else { /* Check for a default transition on this program. */ rc = security_transition_sid(tsec->sid, isec->sid, SECCLASS_PROCESS, &newsid); if (rc) return rc; } AVC_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.dentry = bprm->file->f_dentry; if (tsec->sid == newsid) { rc = avc_has_perm_ref_audit(tsec->sid, isec->sid, SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &isec->avcr, &ad); if (rc) return rc; } else { /* Check permissions for the transition. */ rc = avc_has_perm_audit(tsec->sid, newsid, SECCLASS_PROCESS, PROCESS__TRANSITION, &ad); if (rc) return rc; rc = avc_has_perm_ref_audit(newsid, isec->sid, SECCLASS_FILE, FILE__ENTRYPOINT, &isec->avcr, &ad); if (rc) return rc; /* Set the security field to the new SID. */ bprm->security = (void*) newsid; } return 0; } static void selinux_bprm_free_security(struct linux_binprm *bprm) { /* Nothing to do - not dynamically allocated. */ return; } static inline int must_not_trace_exec(struct task_struct * p, struct linux_binprm *bprm) { struct task_security_struct *tsec; security_id_t newsid; if (task_precondition(p->p_pptr)) return 0; tsec = p->p_pptr->security; if (bprm->security) newsid = (security_id_t)bprm->security; else newsid = tsec->sid; return (p->flags & PT_PTRACED) && avc_has_perm(tsec->sid, newsid, SECCLASS_PROCESS, PROCESS__PTRACE); } /* Derived from fs/exec.c:flush_old_files. */ static inline void flush_unauthorized_files(struct files_struct * files) { avc_audit_data_t ad; struct file *file; long j = -1; AVC_AUDIT_DATA_INIT(&ad,FS); read_lock(&files->file_lock); for (;;) { unsigned long set, i; j++; i = j * __NFDBITS; if (i >= files->max_fds || i >= files->max_fdset) break; set = files->open_fds->fds_bits[j]; if (!set) continue; read_unlock(&files->file_lock); for ( ; set ; i++,set >>= 1) { if (set & 1) { file = fget(i); if (!file) continue; if (file_has_perm(current, file, file_mode_to_av(file->f_mode))) sys_close(i); fput(file); } } read_lock(&files->file_lock); } read_unlock(&files->file_lock); } static void selinux_bprm_compute_creds(struct linux_binprm *bprm) { struct task_security_struct *tsec; security_id_t sid; int do_unlock = 0; if (task_precondition(current)) return; if (!bprm->security) return; tsec = current->security; sid = (security_id_t)bprm->security; /* XXX The following code addresses both XXX traditional setuid/setgid programs and XXX SELinux SID transitions. The setuid/setgid XXX logic can be removed if it is reintegrated XXX back into the base LSM kernel. Currently, XXX it is absent from the base LSM kernel. */ if (bprm->e_uid != current->uid || bprm->e_gid != current->gid || tsec->sid != sid) { current->dumpable = 0; lock_kernel(); if (must_not_trace_exec(current, bprm) || atomic_read(¤t->fs->count) > 1 || atomic_read(¤t->files->count) > 1 || atomic_read(¤t->sig->count) > 1) { if(task_has_capability(current,CAP_SETUID)) { bprm->e_uid = current->uid; bprm->e_gid = current->gid; } if (current->pid != 1) sid = tsec->sid; } do_unlock = 1; } /* XXX More code for setuid/setgid programs. */ current->suid = current->euid = current->fsuid = bprm->e_uid; current->sgid = current->egid = current->fsgid = bprm->e_gid; tsec->osid = tsec->sid; if (tsec->sid != sid) { tsec->sid = sid; flush_unauthorized_files(current->files); /* need to force wait permission check if parent is waiting */ wake_up_interruptible(¤t->p_pptr->wait_chldexit); } if(do_unlock) unlock_kernel(); } /* superblock security operations */ static int selinux_sb_alloc_security(struct super_block *sb) { if (ss_precondition()) return 0; if (task_precondition(current)) return 0; return superblock_alloc_security(sb); } static void selinux_sb_free_security(struct super_block *sb) { superblock_free_security(sb); } static int selinux_sb_statfs(struct super_block *sb) { struct task_security_struct *tsec; struct superblock_security_struct *sbsec; /* This hook function would simply call superblock_has_perm, but it also needs to save the sb SID to support statfs_secure, so we just inline superblock_has_perm. */ if (task_precondition(current)) return 0; if (superblock_precondition(sb)) return 0; tsec = current->security; sbsec = sb->s_security; tsec->out_sid[0] = sbsec->sid; return avc_has_perm(tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, FILESYSTEM__GETATTR); } /* inode security operations */ static int selinux_inode_alloc_security(struct inode *inode) { /* Initialize inode security field to some reasonable defaults. The security class (sclass) will be changed later to a more specific value when the mode is available. For some kinds of objects (e.g. files), the security identifier (sid) will be changed later to a different value when the file system and inode number is available. These changes are handled by inode_precondition before the inode is first used. */ if (ss_precondition()) return 0; if (task_precondition(current)) return 0; return inode_alloc_security(inode); } static void selinux_inode_free_security(struct inode *inode) { inode_free_security(inode); } static int selinux_inode_create(struct inode *dir, struct dentry *dentry, int mask) { return may_create(dir, dentry, SECCLASS_FILE); } static void selinux_inode_post_create(struct inode *dir, struct dentry *dentry, int mask) { post_create(dir, dentry); } static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { return may_link(dir, old_dentry, MAY_LINK); } static void selinux_inode_post_link(struct dentry *old_dentry, struct inode *inode, struct dentry *new_dentry) { return; } static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) { return may_link(dir, dentry, MAY_UNLINK); } static int selinux_inode_symlink(struct inode *dir, struct dentry *dentry, const char *name) { return may_create(dir, dentry, SECCLASS_LNK_FILE); } static void selinux_inode_post_symlink(struct inode *dir, struct dentry *dentry, const char *name) { post_create(dir, dentry); } static int selinux_inode_mkdir(struct inode *dir, struct dentry *dentry, int mask) { return may_create(dir, dentry, SECCLASS_DIR); } static void selinux_inode_post_mkdir(struct inode *dir, struct dentry *dentry, int mask) { post_create(dir, dentry); } static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) { return may_link(dir, dentry, MAY_RMDIR); } static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { return may_create(dir, dentry, inode_mode_to_security_class(mode)); } static void selinux_inode_post_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { post_create(dir, dentry); } static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry, struct inode *new_inode, struct dentry *new_dentry) { return may_rename(old_inode, old_dentry, new_inode, new_dentry); } static void selinux_inode_post_rename(struct inode *old_inode, struct dentry *old_dentry, struct inode *new_inode, struct dentry *new_dentry) { return; } static int selinux_inode_readlink(struct dentry *dentry, char *name, int mask) { return dentry_has_perm(current, dentry, FILE__READ, NULL); } static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *nameidata) { return dentry_has_perm(current, dentry, FILE__READ, NULL); } static int selinux_inode_truncate(struct inode *inode) { return inode_has_perm(current, inode, FILE__SETATTR | FILE__WRITE, NULL, NULL); } static int selinux_inode_permission(struct inode *inode, int mask) { return inode_has_perm(current, inode, file_mask_to_av(inode->i_mode, mask), NULL, NULL); } static int selinux_inode_revalidate(struct dentry *inode) { return 0; } static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) { return dentry_has_perm(current, dentry, FILE__SETATTR, NULL); } static void selinux_inode_attach_pathlabel(struct dentry *dentry, struct vfsmount *mnt) { if (!dentry->d_inode) /* May be NULL since attach_pathlabel is always called, even after failed lookup/create operations. */ return; inode_precondition(dentry->d_inode); } static int selinux_inode_stat(struct inode *inode) { struct task_security_struct *tsec; struct inode_security_struct *isec; avc_audit_data_t ad; /* This hook function would simply call inode_has_perm, but it also needs to save the inode SID to support stat_secure, so we just inline inode_has_perm. */ if (task_precondition(current)) return 0; if (inode_precondition(inode)) return 0; tsec = current->security; isec = inode->i_security; tsec->out_sid[0] = isec->sid; AVC_AUDIT_DATA_INIT(&ad, FS); ad.u.fs.inode = inode; return avc_has_perm_ref_audit(tsec->sid, isec->sid, isec->sclass, FILE__GETATTR, &isec->avcr, &ad); } /* file security operations */ static int selinux_file_permission(struct file *file, int mask) { struct inode *inode = file->f_dentry->d_inode; return file_has_perm(current, file, file_mask_to_av(inode->i_mode, mask)); } static int selinux_file_alloc_security(struct file *file) { if (ss_precondition()) return 0; if (task_precondition(current)) return 0; return file_alloc_security(current, file); } static void selinux_file_free_security(struct file *file) { file_free_security(file); } static int selinux_file_llseek(struct file *file) { return file_has_perm(current, file, 0 /* descriptor only */); } static int selinux_file_read(struct file *file) { return file_has_perm(current, file, FILE__READ); } static int selinux_file_write(struct file *file) { return file_has_perm(current, file, FILE__WRITE); } static int selinux_file_ioctl(struct file *file) { return file_has_perm(current, file, FILE__IOCTL); } static int selinux_file_mmap(struct file *file, unsigned long prot, unsigned long flags) { int error; if (file) { /* write access only matters if the mapping is shared */ if ((flags & MAP_TYPE) == MAP_SHARED && (prot & PROT_WRITE)) { error = file_has_perm(current, file, FILE__WRITE); if (error) return error; } /* read access is always possible with a mapping */ error = file_has_perm(current, file, FILE__READ); if (error) return error; if ((prot & PROT_EXEC)) { error = file_has_perm(current, file, FILE__EXECUTE); if (error) return error; } } return 0; } static int selinux_file_mprotect(struct vm_area_struct *vma, unsigned long prot) { return selinux_file_mmap(vma->vm_file, prot, vma->vm_flags); } static int selinux_file_lock(struct file *file) { return file_has_perm(current, file, FILE__LOCK); } static int selinux_file_readv(struct file *file) { return file_has_perm(current, file, FILE__READ); } static int selinux_file_writev(struct file *file) { return file_has_perm(current, file, FILE__WRITE); } static int selinux_file_fcntl(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; switch (cmd) { case F_SETFL: if (!file->f_dentry || !file->f_dentry->d_inode) { err = -EINVAL; break; } if ((file->f_flags & O_APPEND) && !(arg & O_APPEND)) { err = file_has_perm(current, file,FILE__WRITE); if (err) break; } /* fall through */ case F_SETOWN: case F_SETSIG: case F_GETFL: case F_GETOWN: case F_GETSIG: /* Just check FD__USE permission */ err = file_has_perm(current, file, 0); break; case F_GETLK: case F_SETLK: case F_SETLKW: if (!file->f_dentry || !file->f_dentry->d_inode) { err = -EINVAL; break; } err = file_has_perm(current, file, FILE__LOCK); break; } return err; } static int selinux_file_fcntl64(struct file *file, unsigned int cmd, unsigned long arg) { int err = 0; switch (cmd) { case F_GETLK64: case F_SETLK64: case F_SETLKW64: if (!file->f_dentry || !file->f_dentry->d_inode) { err = -EINVAL; break; } err = file_has_perm(current, file, FILE__LOCK); break; default: /* * Otherwise, just use 32bit version */ err = selinux_file_fcntl(file, cmd, arg); break; } return err; } static int selinux_fown_alloc_security(struct fown_struct *fown) { if (task_precondition(current)) return 0; return fown_alloc_security(current, fown); } static void selinux_fown_free_security(struct fown_struct *fown) { fown_free_security(fown); } static int selinux_fown_send_sigiotask(struct task_struct *tsk, struct fown_struct *fown, int fd, int reason) { avc_audit_data_t ad; access_vector_t perm; struct task_security_struct *tsec; struct fown_security_struct *fsec = fown->security; if (task_precondition(tsk)) return 0; tsec = tsk->security; switch (fown->signum) { case SIGCHLD: perm = PROCESS__SIGCHLD; break; case SIGKILL: perm = PROCESS__SIGKILL; break; case SIGSTOP: perm = PROCESS__SIGSTOP; break; default: perm = PROCESS__SIGNAL; break; } return avc_has_perm_audit(fsec->sid, tsec->sid, SECCLASS_PROCESS, perm, &ad); } /* task security operations */ static int selinux_task_create(void) { return task_has_perm(current, current, PROCESS__FORK); } static int selinux_task_alloc_security(struct task_struct *tsk) { struct task_security_struct *tsec1, *tsec2; int rc; if (task_precondition(current)) return 0; tsec1 = current->security; rc = task_alloc_security(tsk); if (rc) return rc; tsec2 = tsk->security; tsec2->osid = tsec1->osid; tsec2->sid = tsec1->sid; return 0; } static void selinux_task_free_security(struct task_struct *tsk) { task_free_security(tsk); } static int selinux_task_setuid(void) { return task_has_capability(current,CAP_SETUID); } static int selinux_task_setgid(void) { return task_has_capability(current,CAP_SETGID); } static int selinux_task_setgroups(void) { return task_has_capability(current,CAP_SETGID); } static int selinux_task_setnice(struct task_struct *p, int nice) { return task_has_perm(current,p, PROCESS__SETSCHED); } static int selinux_task_setrlimit(unsigned int resource) { return task_has_capability(current,CAP_SYS_RESOURCE); } static int selinux_task_setscheduler(struct task_struct *p, int policy) { return task_has_perm(current, p, PROCESS__SETSCHED); } static int selinux_task_kill(struct task_struct *p, struct siginfo *info, int sig) { access_vector_t perm; switch (sig) { case SIGCHLD: perm = PROCESS__SIGCHLD; break; case SIGKILL: perm = PROCESS__SIGKILL; break; case SIGSTOP: perm = PROCESS__SIGSTOP; break; default: perm = PROCESS__SIGNAL; break; } return task_has_perm(current, p, perm); } static int selinux_task_wait(struct task_struct *p) { access_vector_t perm; switch (p->exit_signal) { case SIGCHLD: perm = PROCESS__SIGCHLD; break; case SIGKILL: perm = PROCESS__SIGKILL; break; case SIGSTOP: perm = PROCESS__SIGSTOP; break; default: perm = PROCESS__SIGNAL; break; } return task_has_perm(p, current, perm); } static int selinux_task_set_label(char *filename) { /* This hook is used by DTE for domain transitions. The equivalent in SELinux is implemented using the binprm security operations. */ return 0; } static void selinux_task_reset_label(void) { /* This hook is used by DTE to revert domains on an exec failure. Since SELinux does not change the SID until compute_creds, this hook is not necessary for SELinux. */ return; } /* module security operations */ static int selinux_module_create_module(const char *name_user, size_t size) { return task_has_capability(current,CAP_SYS_MODULE); } static int selinux_module_init_module(const char *name_user, struct module *mod_user) { return task_has_capability(current,CAP_SYS_MODULE); } static int selinux_module_delete_module(const char *name_user) { return task_has_capability(current,CAP_SYS_MODULE); } /* message queue security operations */ static int selinux_msg_queue_create(key_t key) { return 0; } static int selinux_msg_queue_permission(void) { return 0; } static int selinux_msg_queue_setmaxqbytes(void) { return 0; } static int selinux_msg_queue_setattr(void) { return 0; } static int selinux_msg_queue_delete(void) { return 0; } /* shared memory security operations */ static int selinux_shm_create(key_t key) { return 0; } static int selinux_shm_permission(void) { return 0; } static int selinux_shm_setattr(void) { return 0; } static int selinux_shm_delete(void) { return 0; } /* module stacking operations */ int selinux_register_security (struct security_operations *ops) { return -EPERM; } int selinux_unregister_security (struct security_operations *ops) { return -EPERM; } static struct binprm_security_ops selinux_bprm_ops = { alloc_security: selinux_bprm_alloc_security, free_security: selinux_bprm_free_security, compute_creds: selinux_bprm_compute_creds, }; static struct super_block_security_ops selinux_sb_ops = { alloc_security: selinux_sb_alloc_security, free_security: selinux_sb_free_security, statfs: selinux_sb_statfs, }; static struct inode_security_ops selinux_inode_ops = { alloc_security: selinux_inode_alloc_security, free_security: selinux_inode_free_security, create: selinux_inode_create, post_create: selinux_inode_post_create, link: selinux_inode_link, post_link: selinux_inode_post_link, unlink: selinux_inode_unlink, symlink: selinux_inode_symlink, post_symlink: selinux_inode_post_symlink, mkdir: selinux_inode_mkdir, post_mkdir: selinux_inode_post_mkdir, rmdir: selinux_inode_rmdir, mknod: selinux_inode_mknod, post_mknod: selinux_inode_post_mknod, rename: selinux_inode_rename, post_rename: selinux_inode_post_rename, readlink: selinux_inode_readlink, follow_link: selinux_inode_follow_link, truncate: selinux_inode_truncate, permission: selinux_inode_permission, revalidate: selinux_inode_revalidate, setattr: selinux_inode_setattr, attach_pathlabel:selinux_inode_attach_pathlabel, stat: selinux_inode_stat, }; static struct file_security_ops selinux_file_ops = { permission: selinux_file_permission, alloc_security: selinux_file_alloc_security, free_security: selinux_file_free_security, llseek: selinux_file_llseek, read: selinux_file_read, write: selinux_file_write, ioctl: selinux_file_ioctl, mmap: selinux_file_mmap, mprotect: selinux_file_mprotect, lock: selinux_file_lock, readv: selinux_file_readv, writev: selinux_file_writev, fcntl: selinux_file_fcntl, fcntl64: selinux_file_fcntl64, }; static struct fown_security_ops selinux_fown_ops = { alloc_security: selinux_fown_alloc_security, free_security: selinux_fown_free_security, send_sigiotask: selinux_fown_send_sigiotask, }; static struct task_security_ops selinux_task_ops = { create: selinux_task_create, alloc_security: selinux_task_alloc_security, free_security: selinux_task_free_security, setuid: selinux_task_setuid, setgid: selinux_task_setgid, setgroups: selinux_task_setgroups, setnice: selinux_task_setnice, setrlimit: selinux_task_setrlimit, setscheduler: selinux_task_setscheduler, kill: selinux_task_kill, wait: selinux_task_wait, set_label: selinux_task_set_label, reset_label: selinux_task_reset_label, }; static struct socket_security_ops selinux_socket_ops = {}; static struct module_security_ops selinux_module_ops = { create_module: selinux_module_create_module, init_module: selinux_module_init_module, delete_module: selinux_module_delete_module, }; static struct msg_queue_security_ops selinux_msg_queue_ops = { create: selinux_msg_queue_create, permission: selinux_msg_queue_permission, setmaxqbytes: selinux_msg_queue_setmaxqbytes, setattr: selinux_msg_queue_setattr, delete: selinux_msg_queue_delete, }; static struct shm_security_ops selinux_shm_ops = { create: selinux_shm_create, permission: selinux_shm_permission, setattr: selinux_shm_setattr, delete: selinux_shm_delete, }; struct security_operations selinux_ops = { version: SECURITY_INTERFACE_VERSION, sethostname: selinux_sethostname, setdomainname: selinux_setdomainname, reboot: selinux_reboot, mount: selinux_mount, add_vfsmnt: selinux_add_vfsmnt, umount: selinux_umount, umount_close: selinux_umount_close, umount_busy: selinux_umount_busy, remount: selinux_remount, post_remount: selinux_post_remount, ioperm: selinux_ioperm, iopl: selinux_iopl, ptrace: selinux_ptrace, setcapability: selinux_setcapablity, acct: selinux_acct, capable: selinux_capable, bprm_ops: &selinux_bprm_ops, sb_ops: &selinux_sb_ops, inode_ops: &selinux_inode_ops, file_ops: &selinux_file_ops, fown_ops: &selinux_fown_ops, task_ops: &selinux_task_ops, socket_ops: &selinux_socket_ops, module_ops: &selinux_module_ops, msg_queue_ops: &selinux_msg_queue_ops, shm_ops: &selinux_shm_ops, register_security: &selinux_register_security, unregister_security: &selinux_unregister_security, }; extern void *sys_call_table[]; extern long sys_ni_syscall(void); static int __init selinux_plug_init (void) { if (sys_call_table[__NR_lsm] != sys_ni_syscall) { printk (KERN_INFO "Unable to register lsm syscall\n"); return -EINVAL; } sys_call_table[__NR_lsm] = sys_selinux; if (sys_call_table[__NR_execve_secure] != sys_ni_syscall) { printk (KERN_INFO "Unable to register execve_secure syscall\n"); return -EINVAL; } sys_call_table[__NR_execve_secure] = sys_execve_secure; avc_init(); if (register_security (&selinux_ops)) { printk (KERN_INFO "Failure registering SELinux with the kernel\n"); return -EINVAL; } printk (KERN_INFO "SELinux: module inserted\n"); return 0; } static void __exit selinux_plug_exit (void) { struct list_head *p; sys_call_table[__NR_lsm] = NULL; sys_call_table[__NR_execve_secure] = NULL; /* remove ourselves from the security framework */ if (unregister_security (&selinux_ops)) { printk (KERN_INFO "Failure unregistering SELinux with the kernel\n"); } /* Deallocate all of the security blobs created by this module and clear the security fields in the corresponding objects. */ p = task_security_head.next; while (p != &task_security_head) { struct task_security_struct *tsec = list_entry(p, struct task_security_struct, list); p = p->next; task_free_security(tsec->task); } p = inode_security_head.next; while (p != &inode_security_head) { struct inode_security_struct *isec = list_entry(p, struct inode_security_struct, list); p = p->next; inode_free_security(isec->inode); } p = file_security_head.next; while (p != &file_security_head) { struct file_security_struct *fsec = list_entry(p, struct file_security_struct, list); p = p->next; file_free_security(fsec->file); } p = superblock_security_head.next; while (p != &superblock_security_head) { struct superblock_security_struct *sbsec = list_entry(p, struct superblock_security_struct, list); p = p->next; superblock_free_security(sbsec->sb); } /* XXX: Need AVC and security server interfaces for cleaning up. */ printk (KERN_INFO "SELinux: module removed\n"); } module_init (selinux_plug_init); module_exit (selinux_plug_exit); EXPORT_SYMBOL(selinux_ops); _______________________________________________ linux-security-module mailing list linux-security-moduleat_private http://mail.wirex.com/mailman/listinfo/linux-security-module
This archive was generated by hypermail 2b30 : Tue Jun 05 2001 - 08:24:26 PDT