/* "Stacker" Linux security module (LSM). * * Allows you to load (stack) multiple (additional) security modules. * * Copyright (C) 2002,2003,2004,2005 Serge E. Hallyn * Copyright (C) 2002 David A. Wheeler . * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* A module entry keeps track of one of the stacked modules * Note that module_operations is aggregated instead of being pointed to - * it's one less allocation and one less pointer to follow. */ struct module_entry { struct list_head lsm_list; /* list of active lsms */ struct list_head all_lsms; /* list of all lsms */ char *module_name; int namelen; struct security_operations module_operations; }; static struct list_head stacked_modules; /* list of stacked modules */ static struct list_head all_modules; /* list of all modules, including freed */ static short sysfsfiles_registered; static spinlock_t stacker_lock; static int forbid_stacker_register; /* = 0; if 1, can't register */ struct stacker_hook { struct list_head list; void *f; }; static LIST_HEAD(hooks_ptrace); static LIST_HEAD(hooks_capget); static LIST_HEAD(hooks_capset_check); static LIST_HEAD(hooks_capset_set); static LIST_HEAD(hooks_acct); static LIST_HEAD(hooks_capable); static LIST_HEAD(hooks_quotactl); static LIST_HEAD(hooks_quota_on); static LIST_HEAD(hooks_sysctl); static LIST_HEAD(hooks_syslog); static LIST_HEAD(hooks_settime); static LIST_HEAD(hooks_vm_enough_memory); static LIST_HEAD(hooks_bprm_alloc_security); static LIST_HEAD(hooks_bprm_free_security); static LIST_HEAD(hooks_bprm_apply_creds); static LIST_HEAD(hooks_bprm_post_apply_creds); static LIST_HEAD(hooks_bprm_set_security); static LIST_HEAD(hooks_bprm_check_security); static LIST_HEAD(hooks_bprm_secureexec); static LIST_HEAD(hooks_sb_alloc_security); static LIST_HEAD(hooks_sb_free_security); static LIST_HEAD(hooks_sb_copy_data); static LIST_HEAD(hooks_sb_kern_mount); static LIST_HEAD(hooks_sb_statfs); static LIST_HEAD(hooks_sb_mount); static LIST_HEAD(hooks_sb_check_sb); static LIST_HEAD(hooks_sb_umount); static LIST_HEAD(hooks_sb_umount_close); static LIST_HEAD(hooks_sb_umount_busy); static LIST_HEAD(hooks_sb_post_remount); static LIST_HEAD(hooks_sb_post_mountroot); static LIST_HEAD(hooks_sb_post_addmount); static LIST_HEAD(hooks_sb_pivotroot); static LIST_HEAD(hooks_sb_post_pivotroot); static LIST_HEAD(hooks_inode_alloc_security); static LIST_HEAD(hooks_inode_free_security); static LIST_HEAD(hooks_inode_create); static LIST_HEAD(hooks_inode_post_create); static LIST_HEAD(hooks_inode_link); static LIST_HEAD(hooks_inode_post_link); static LIST_HEAD(hooks_inode_unlink); static LIST_HEAD(hooks_inode_symlink); static LIST_HEAD(hooks_inode_post_symlink); static LIST_HEAD(hooks_inode_mkdir); static LIST_HEAD(hooks_inode_post_mkdir); static LIST_HEAD(hooks_inode_rmdir); static LIST_HEAD(hooks_inode_mknod); static LIST_HEAD(hooks_inode_post_mknod); static LIST_HEAD(hooks_inode_rename); static LIST_HEAD(hooks_inode_post_rename); static LIST_HEAD(hooks_inode_readlink); static LIST_HEAD(hooks_inode_follow_link); static LIST_HEAD(hooks_inode_permission); static LIST_HEAD(hooks_inode_setattr); static LIST_HEAD(hooks_inode_getattr); static LIST_HEAD(hooks_inode_delete); static LIST_HEAD(hooks_inode_setxattr); static LIST_HEAD(hooks_inode_post_setxattr); static LIST_HEAD(hooks_inode_getxattr); static LIST_HEAD(hooks_inode_listxattr); static LIST_HEAD(hooks_inode_removexattr); static LIST_HEAD(hooks_inode_getsecurity); static LIST_HEAD(hooks_inode_setsecurity); static LIST_HEAD(hooks_inode_listsecurity); static LIST_HEAD(hooks_file_permission); static LIST_HEAD(hooks_file_alloc_security); static LIST_HEAD(hooks_file_free_security); static LIST_HEAD(hooks_file_ioctl); static LIST_HEAD(hooks_file_mmap); static LIST_HEAD(hooks_file_mprotect); static LIST_HEAD(hooks_file_lock); static LIST_HEAD(hooks_file_fcntl); static LIST_HEAD(hooks_file_set_fowner); static LIST_HEAD(hooks_file_send_sigiotask); static LIST_HEAD(hooks_file_receive); static LIST_HEAD(hooks_task_create); static LIST_HEAD(hooks_task_alloc_security); static LIST_HEAD(hooks_task_free_security); static LIST_HEAD(hooks_task_setuid); static LIST_HEAD(hooks_task_post_setuid); static LIST_HEAD(hooks_task_setgid); static LIST_HEAD(hooks_task_setpgid); static LIST_HEAD(hooks_task_getpgid); static LIST_HEAD(hooks_task_getsid); static LIST_HEAD(hooks_task_setgroups); static LIST_HEAD(hooks_task_setnice); static LIST_HEAD(hooks_task_setrlimit); static LIST_HEAD(hooks_task_setscheduler); static LIST_HEAD(hooks_task_getscheduler); static LIST_HEAD(hooks_task_wait); static LIST_HEAD(hooks_task_kill); static LIST_HEAD(hooks_task_prctl); static LIST_HEAD(hooks_task_reparent_to_init); static LIST_HEAD(hooks_task_to_inode); static LIST_HEAD(hooks_ipc_permission); static LIST_HEAD(hooks_msg_msg_alloc_security); static LIST_HEAD(hooks_msg_msg_free_security); static LIST_HEAD(hooks_msg_queue_alloc_security); static LIST_HEAD(hooks_msg_queue_free_security); static LIST_HEAD(hooks_msg_queue_associate); static LIST_HEAD(hooks_msg_queue_msgctl); static LIST_HEAD(hooks_msg_queue_msgsnd); static LIST_HEAD(hooks_msg_queue_msgrcv); static LIST_HEAD(hooks_shm_alloc_security); static LIST_HEAD(hooks_shm_free_security); static LIST_HEAD(hooks_shm_associate); static LIST_HEAD(hooks_shm_shmctl); static LIST_HEAD(hooks_shm_shmat); static LIST_HEAD(hooks_sem_alloc_security); static LIST_HEAD(hooks_sem_free_security); static LIST_HEAD(hooks_sem_associate); static LIST_HEAD(hooks_sem_semctl); static LIST_HEAD(hooks_sem_semop); static LIST_HEAD(hooks_netlink_send); static LIST_HEAD(hooks_netlink_recv); static LIST_HEAD(hooks_register_security); static LIST_HEAD(hooks_unregister_security); static LIST_HEAD(hooks_d_instantiate); static LIST_HEAD(hooks_getprocattr); static LIST_HEAD(hooks_setprocattr); #ifdef CONFIG_SECURITY_NETWORK static LIST_HEAD(hooks_unix_stream_connect); static LIST_HEAD(hooks_unix_may_send); static LIST_HEAD(hooks_socket_create); static LIST_HEAD(hooks_socket_post_create); static LIST_HEAD(hooks_socket_bind); static LIST_HEAD(hooks_socket_connect); static LIST_HEAD(hooks_socket_listen); static LIST_HEAD(hooks_socket_accept); static LIST_HEAD(hooks_socket_post_accept); static LIST_HEAD(hooks_socket_sendmsg); static LIST_HEAD(hooks_socket_recvmsg); static LIST_HEAD(hooks_socket_getsockname); static LIST_HEAD(hooks_socket_getpeername); static LIST_HEAD(hooks_socket_setsockopt); static LIST_HEAD(hooks_socket_getsockopt); static LIST_HEAD(hooks_socket_shutdown); static LIST_HEAD(hooks_socket_sock_rcv_skb); static LIST_HEAD(hooks_socket_getpeersec); static LIST_HEAD(hooks_sk_alloc_security); static LIST_HEAD(hooks_sk_free_security); #endif /* CONFIG_SECURITY_NETWORK */ /* * Workarounds for the fact that get and setprocattr are used only by * selinux. (Maybe) */ static struct module_entry *selinux_module; /* Maximum number of characters in a stacked LSM module name */ #define MAX_MODULE_NAME_LEN 128 static int debug = 0; module_param(debug, bool, 0600); MODULE_PARM_DESC(debug, "Debug enabled or not"); #define MY_NAME "stacker" #define stacker_dbg(fmt, arg...) \ do { \ if (debug) \ printk(KERN_DEBUG "%s: %s: " fmt , \ MY_NAME , __FUNCTION__ , \ ## arg); \ } while (0) /* * The list of functions for stacker_ops */ static int stacker_ptrace (struct task_struct *parent, struct task_struct *child) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *, struct task_struct *); if (list_empty(&hooks_ptrace)) return 0; list_for_each_entry_safe(h, next, &hooks_ptrace, list) { f = h->f; result = f(parent, child); if (result) break; } return result; } static int stacker_capget (struct task_struct *target, kernel_cap_t * effective, kernel_cap_t * inheritable, kernel_cap_t * permitted) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *target, kernel_cap_t * effective, kernel_cap_t * inheritable, kernel_cap_t * permitted); if (list_empty(&hooks_capget)) return 0; list_for_each_entry_safe(h, next, &hooks_capget, list) { f = h->f; result = f(target, effective, inheritable, permitted); if (result) break; } return result; } static int stacker_capset_check (struct task_struct *target, kernel_cap_t * effective, kernel_cap_t * inheritable, kernel_cap_t * permitted) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *target, kernel_cap_t * effective, kernel_cap_t * inheritable, kernel_cap_t * permitted); if (list_empty(&hooks_capset_check)) return 0; list_for_each_entry_safe(h, next, &hooks_capset_check, list) { f = h->f; result = f(target, effective, inheritable, permitted); if (result) break; } return result; } static void stacker_capset_set (struct task_struct *target, kernel_cap_t * effective, kernel_cap_t * inheritable, kernel_cap_t * permitted) { struct stacker_hook *h, *next; int (*f) (struct task_struct *target, kernel_cap_t * effective, kernel_cap_t * inheritable, kernel_cap_t * permitted); if (list_empty(&hooks_capset_set)) return; list_for_each_entry_safe(h, next, &hooks_capset_set, list) { f = h->f; f(target, effective, inheritable, permitted); } } static int stacker_acct (struct file *file) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file); if (list_empty(&hooks_acct)) return 0; list_for_each_entry_safe(h, next, &hooks_acct, list) { f = h->f; result = f(file); if (result) break; } return result; } static int stacker_capable (struct task_struct *tsk, int cap) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *tsk, int cap); if (list_empty(&hooks_capable)) return 0; list_for_each_entry_safe(h, next, &hooks_capable, list) { f = h->f; result = f(tsk,cap); if (result) break; } return result; } static int stacker_sysctl (struct ctl_table * table, int op) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct ctl_table * table, int op); if (list_empty(&hooks_sysctl)) return 0; list_for_each_entry_safe(h, next, &hooks_sysctl, list) { f = h->f; result = f(table, op); if (result) break; } return result; } static int stacker_quotactl (int cmds, int type, int id, struct super_block *sb) { int result = 0; struct stacker_hook *h, *next; int (*f) (int cmds, int type, int id, struct super_block *sb); if (list_empty(&hooks_quotactl)) return 0; list_for_each_entry_safe(h, next, &hooks_quotactl, list) { f = h->f; result = f(cmds,type,id,sb); if (result) break; } return result; } static int stacker_quota_on (struct dentry *dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry); if (list_empty(&hooks_quota_on)) return 0; list_for_each_entry_safe(h, next, &hooks_quota_on, list) { f = h->f; result = f(dentry); if (result) break; } return result; } static int stacker_syslog (int type) { int result = 0; struct stacker_hook *h, *next; int (*f) (int type); if (list_empty(&hooks_syslog)) return 0; list_for_each_entry_safe(h, next, &hooks_syslog, list) { f = h->f; result = f(type); if (result) break; } return result; } /* * vm_enough_memory performs actual updates of vm state. Most * modules, including dummy, use the __vm_enough_memory helper * to do this for them. * This means we can't call more than one module's vm_enough. * So we call the first module's, or if no modules are loaded, * we call dummy_vm_enough_memory. */ static int stacker_vm_enough_memory(long pages) { int result; struct stacker_hook *h, *next; int (*f) (long pages); if (list_empty(&hooks_vm_enough_memory)) return 0; list_for_each_entry_safe(h, next, &hooks_vm_enough_memory, list) { f = h->f; result = f(pages); return result; } result = __vm_enough_memory(pages, capable(CAP_SYS_ADMIN)); return result; } /* * Each module may have it's own idea of how to set netlink perms. * We use the intersection of all as the final alloted perms. */ static int stacker_netlink_send (struct sock *sk, struct sk_buff *skb) { kernel_cap_t tmpcap = ~0; int result = 0; struct stacker_hook *h, *next; int (*f) (struct sock *sk, struct sk_buff *skb); if (list_empty(&hooks_netlink_send)) return 0; list_for_each_entry_safe(h, next, &hooks_netlink_send, list) { f = h->f; NETLINK_CB(skb).eff_cap = ~0; result = f(sk, skb); tmpcap &= NETLINK_CB(skb).eff_cap; if (result) break; } NETLINK_CB(skb).eff_cap = tmpcap; return result; } static int stacker_netlink_recv (struct sk_buff *skb) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct sk_buff *skb); if (list_empty(&hooks_netlink_recv)) return 0; list_for_each_entry_safe(h, next, &hooks_netlink_recv, list) { f = h->f; result = f(skb); if (result) break; } return result; } static int stacker_bprm_alloc_security (struct linux_binprm *bprm) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct linux_binprm *bprm); if (list_empty(&hooks_bprm_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_bprm_alloc_security, list) { f = h->f; result = f(bprm); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_bprm_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_bprm_free_security, list) { f = h->f; f(bprm); } } return result; } static void stacker_bprm_free_security (struct linux_binprm *bprm) { struct stacker_hook *h, *next; int (*f) (struct linux_binprm *bprm); if (list_empty(&hooks_bprm_free_security)) return; list_for_each_entry_safe(h, next, &hooks_bprm_free_security, list) { f = h->f; f(bprm); } } static void stacker_bprm_apply_creds (struct linux_binprm *bprm, int unsafe) { struct stacker_hook *h, *next; int (*f) (struct linux_binprm *bprm, int unsafe); if (list_empty(&hooks_bprm_apply_creds)) return; list_for_each_entry_safe(h, next, &hooks_bprm_apply_creds, list) { f = h->f; f(bprm, unsafe); } } static void stacker_bprm_post_apply_creds (struct linux_binprm * bprm) { struct stacker_hook *h, *next; int (*f) (struct linux_binprm * bprm); if (list_empty(&hooks_bprm_post_apply_creds)) return; list_for_each_entry_safe(h, next, &hooks_bprm_post_apply_creds, list) { f = h->f; f(bprm); } } static int stacker_bprm_set_security (struct linux_binprm *bprm) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct linux_binprm *bprm); if (list_empty(&hooks_bprm_set_security)) return 0; list_for_each_entry_safe(h, next, &hooks_bprm_set_security, list) { f = h->f; result = f(bprm); if (result) break; } return result; } static int stacker_bprm_check_security (struct linux_binprm *bprm) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct linux_binprm *bprm); if (list_empty(&hooks_bprm_check_security)) return 0; list_for_each_entry_safe(h, next, &hooks_bprm_check_security, list) { f = h->f; result = f(bprm); if (result) break; } return result; } static int stacker_bprm_secureexec (struct linux_binprm *bprm) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct linux_binprm *bprm); if (list_empty(&hooks_bprm_secureexec)) return 0; list_for_each_entry_safe(h, next, &hooks_bprm_secureexec, list) { f = h->f; result = f(bprm); if (result) break; } return result; } static int stacker_sb_alloc_security (struct super_block *sb) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct super_block *sb); if (list_empty(&hooks_sb_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_alloc_security, list) { f = h->f; result = f(sb); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_sb_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_sb_free_security, list) { f = h->f; f(sb); } } return result; } static void stacker_sb_free_security (struct super_block *sb) { struct stacker_hook *h, *next; int (*f) (struct super_block *sb); if (list_empty(&hooks_sb_free_security)) return; list_for_each_entry_safe(h, next, &hooks_sb_free_security, list) { f = h->f; f(sb); } } static int stacker_sb_copy_data (struct file_system_type *type, void *orig, void *copy) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file_system_type *type, void *orig, void *copy); if (list_empty(&hooks_sb_copy_data)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_copy_data, list) { f = h->f; result = f(type,orig,copy); if (result) break; } return result; } static int stacker_sb_kern_mount (struct super_block *sb, void *data) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct super_block *sb, void *data); if (list_empty(&hooks_sb_kern_mount)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_kern_mount, list) { f = h->f; result = f(sb, data); if (result) break; } return result; } static int stacker_sb_statfs (struct super_block *sb) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct super_block *sb); if (list_empty(&hooks_sb_statfs)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_statfs, list) { f = h->f; result = f(sb); if (result) break; } return result; } static int stacker_sb_mount (char *dev_name, struct nameidata *nd, char *type, unsigned long flags, void *data) { int result = 0; struct stacker_hook *h, *next; int (*f) (char *dev_name, struct nameidata *nd, char *type, unsigned long flags, void *data); if (list_empty(&hooks_sb_mount)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_mount, list) { f = h->f; result = f(dev_name, nd, type, flags, data); if (result) break; } return result; } static int stacker_sb_check_sb (struct vfsmount *mnt, struct nameidata *nd) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct vfsmount *mnt, struct nameidata *nd); if (list_empty(&hooks_sb_check_sb)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_check_sb, list) { f = h->f; result = f(mnt, nd); if (result) break; } return result; } static int stacker_sb_umount (struct vfsmount *mnt, int flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct vfsmount *mnt, int flags); if (list_empty(&hooks_sb_umount)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_umount, list) { f = h->f; result = f(mnt, flags); if (result) break; } return result; } static void stacker_sb_umount_close (struct vfsmount *mnt) { struct stacker_hook *h, *next; int (*f) (struct vfsmount *mnt); if (list_empty(&hooks_sb_umount_close)) return; list_for_each_entry_safe(h, next, &hooks_sb_umount_close, list) { f = h->f; f(mnt); } } static void stacker_sb_umount_busy (struct vfsmount *mnt) { struct stacker_hook *h, *next; int (*f) (struct vfsmount *mnt); if (list_empty(&hooks_sb_umount_busy)) return; list_for_each_entry_safe(h, next, &hooks_sb_umount_busy, list) { f = h->f; f(mnt); } } static void stacker_sb_post_remount (struct vfsmount *mnt, unsigned long flags, void *data) { struct stacker_hook *h, *next; int (*f) (struct vfsmount *mnt, unsigned long flags, void *data); if (list_empty(&hooks_sb_post_remount)) return; list_for_each_entry_safe(h, next, &hooks_sb_post_remount, list) { f = h->f; f(mnt, flags, data); } } static void stacker_sb_post_mountroot (void) { struct stacker_hook *h, *next; int (*f) (void); if (list_empty(&hooks_sb_post_mountroot)) return; list_for_each_entry_safe(h, next, &hooks_sb_post_mountroot, list) { f = h->f; f(); } } static void stacker_sb_post_addmount (struct vfsmount *mnt, struct nameidata *nd) { struct stacker_hook *h, *next; int (*f) (struct vfsmount *mnt, struct nameidata *nd); if (list_empty(&hooks_sb_post_addmount)) return; list_for_each_entry_safe(h, next, &hooks_sb_post_addmount, list) { f = h->f; f(mnt, nd); } } static int stacker_sb_pivotroot (struct nameidata *old_nd, struct nameidata *new_nd) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct nameidata *old_nd, struct nameidata *new_nd); if (list_empty(&hooks_sb_pivotroot)) return 0; list_for_each_entry_safe(h, next, &hooks_sb_pivotroot, list) { f = h->f; result = f(old_nd, new_nd); if (result) break; } return result; } static void stacker_sb_post_pivotroot (struct nameidata *old_nd, struct nameidata *new_nd) { struct stacker_hook *h, *next; int (*f) (struct nameidata *old_nd, struct nameidata *new_nd); if (list_empty(&hooks_sb_post_pivotroot)) return; list_for_each_entry_safe(h, next, &hooks_sb_post_pivotroot, list) { f = h->f; f(old_nd, new_nd); } } static int stacker_inode_alloc_security (struct inode *inode) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode); if (list_empty(&hooks_inode_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_alloc_security, list) { f = h->f; result = f(inode); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_inode_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_inode_free_security, list) { f = h->f; f(inode); } } return result; } static void stacker_inode_free_security (struct inode *inode) { struct stacker_hook *h, *next; int (*f) (struct inode *inode); if (list_empty(&hooks_inode_free_security)) return; list_for_each_entry_safe(h, next, &hooks_inode_free_security, list) { f = h->f; f(inode); } } static int stacker_inode_create (struct inode *inode, struct dentry *dentry, int mask) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, int mask); if (list_empty(&hooks_inode_create)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_create, list) { f = h->f; result = f(inode, dentry, mask); if (result) break; } return result; } static void stacker_inode_post_create (struct inode *inode, struct dentry *dentry, int mask) { struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, int mask); if (list_empty(&hooks_inode_post_create)) return; list_for_each_entry_safe(h, next, &hooks_inode_post_create, list) { f = h->f; f(inode, dentry, mask); } } static int stacker_inode_link (struct dentry *old_dentry, struct inode *inode, struct dentry *new_dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *old_dentry, struct inode *inode, struct dentry *new_dentry); if (list_empty(&hooks_inode_link)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_link, list) { f = h->f; result = f(old_dentry, inode, new_dentry); if (result) break; } return result; } static void stacker_inode_post_link (struct dentry *old_dentry, struct inode *inode, struct dentry *new_dentry) { struct stacker_hook *h, *next; int (*f) (struct dentry *old_dentry, struct inode *inode, struct dentry *new_dentry); if (list_empty(&hooks_inode_post_link)) return; list_for_each_entry_safe(h, next, &hooks_inode_post_link, list) { f = h->f; f(old_dentry, inode, new_dentry); } } static int stacker_inode_unlink (struct inode *inode, struct dentry *dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry); if (list_empty(&hooks_inode_unlink)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_unlink, list) { f = h->f; result = f(inode, dentry); if (result) break; } return result; } static int stacker_inode_symlink (struct inode *inode, struct dentry *dentry, const char *name) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, const char *name); if (list_empty(&hooks_inode_symlink)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_symlink, list) { f = h->f; result = f(inode, dentry, name); if (result) break; } return result; } static void stacker_inode_post_symlink (struct inode *inode, struct dentry *dentry, const char *name) { struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, const char *name); if (list_empty(&hooks_inode_post_symlink)) return; list_for_each_entry_safe(h, next, &hooks_inode_post_symlink, list) { f = h->f; f(inode, dentry, name); } } static int stacker_inode_mkdir (struct inode *inode, struct dentry *dentry, int mask) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, int mask); if (list_empty(&hooks_inode_mkdir)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_mkdir, list) { f = h->f; result = f(inode, dentry, mask); if (result) break; } return result; } static void stacker_inode_post_mkdir (struct inode *inode, struct dentry *dentry, int mask) { struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, int mask); if (list_empty(&hooks_inode_post_mkdir)) return; list_for_each_entry_safe(h, next, &hooks_inode_post_mkdir, list) { f = h->f; f(inode, dentry, mask); } } static int stacker_inode_rmdir (struct inode *inode, struct dentry *dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry); if (list_empty(&hooks_inode_rmdir)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_rmdir, list) { f = h->f; result = f(inode, dentry); if (result) break; } return result; } static int stacker_inode_mknod (struct inode *inode, struct dentry *dentry, int major, dev_t minor) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, int major, dev_t minor); if (list_empty(&hooks_inode_mknod)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_mknod, list) { f = h->f; result = f(inode, dentry, major, minor); if (result) break; } return result; } static void stacker_inode_post_mknod (struct inode *inode, struct dentry *dentry, int major, dev_t minor) { struct stacker_hook *h, *next; int (*f) (struct inode *inode, struct dentry *dentry, int major, dev_t minor); if (list_empty(&hooks_inode_post_mknod)) return; list_for_each_entry_safe(h, next, &hooks_inode_post_mknod, list) { f = h->f; f(inode, dentry, major, minor); } } static int stacker_inode_rename (struct inode *old_inode, struct dentry *old_dentry, struct inode *new_inode, struct dentry *new_dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *old_inode, struct dentry *old_dentry, struct inode *new_inode, struct dentry *new_dentry); if (list_empty(&hooks_inode_rename)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_rename, list) { f = h->f; result = f(old_inode, old_dentry, new_inode, new_dentry); if (result) break; } return result; } static void stacker_inode_post_rename (struct inode *old_inode, struct dentry *old_dentry, struct inode *new_inode, struct dentry *new_dentry) { struct stacker_hook *h, *next; int (*f) (struct inode *old_inode, struct dentry *old_dentry, struct inode *new_inode, struct dentry *new_dentry); if (list_empty(&hooks_inode_post_rename)) return; list_for_each_entry_safe(h, next, &hooks_inode_post_rename, list) { f = h->f; f(old_inode, old_dentry, new_inode, new_dentry); } } static int stacker_inode_readlink (struct dentry *dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry); if (list_empty(&hooks_inode_readlink)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_readlink, list) { f = h->f; result = f(dentry); if (result) break; } return result; } static int stacker_inode_follow_link (struct dentry *dentry, struct nameidata *nameidata) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry, struct nameidata *nameidata); if (list_empty(&hooks_inode_follow_link)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_follow_link, list) { f = h->f; result = f(dentry, nameidata); if (result) break; } return result; } static int stacker_inode_permission (struct inode *inode, int mask, struct nameidata *nd) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, int mask, struct nameidata *nd); if (list_empty(&hooks_inode_permission)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_permission, list) { f = h->f; result = f(inode, mask, nd); if (result) break; } return result; } static int stacker_inode_setattr (struct dentry *dentry, struct iattr *iattr) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry, struct iattr *iattr); if (list_empty(&hooks_inode_setattr)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_setattr, list) { f = h->f; result = f(dentry, iattr); if (result) break; } return result; } static int stacker_inode_getattr (struct vfsmount *mnt, struct dentry *dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct vfsmount *mnt, struct dentry *dentry); if (list_empty(&hooks_inode_getattr)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_getattr, list) { f = h->f; result = f(mnt,dentry); if (result) break; } return result; } static void stacker_inode_delete (struct inode *ino) { struct stacker_hook *h, *next; int (*f) (struct inode *ino); if (list_empty(&hooks_inode_delete)) return; list_for_each_entry_safe(h, next, &hooks_inode_delete, list) { f = h->f; f(ino); } } static int stacker_inode_setxattr (struct dentry *dentry, char *name, void *value, size_t size, int flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry, char *name, void *value, size_t size, int flags); if (list_empty(&hooks_inode_setxattr)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_setxattr, list) { f = h->f; result = f(dentry,name,value,size,flags); if (result) break; } return result; } static void stacker_inode_post_setxattr (struct dentry *dentry, char *name, void *value, size_t size, int flags) { struct stacker_hook *h, *next; int (*f) (struct dentry *dentry, char *name, void *value, size_t size, int flags); if (list_empty(&hooks_inode_post_setxattr)) return; list_for_each_entry_safe(h, next, &hooks_inode_post_setxattr, list) { f = h->f; f(dentry,name,value,size,flags); } } static int stacker_inode_getxattr (struct dentry *dentry, char *name) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry, char *name); if (list_empty(&hooks_inode_getxattr)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_getxattr, list) { f = h->f; result = f(dentry,name); if (result) break; } return result; } static int stacker_inode_listxattr (struct dentry *dentry) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry); if (list_empty(&hooks_inode_listxattr)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_listxattr, list) { f = h->f; result = f(dentry); if (result) break; } return result; } static int stacker_inode_removexattr (struct dentry *dentry, char *name) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct dentry *dentry, char *name); if (list_empty(&hooks_inode_removexattr)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_removexattr, list) { f = h->f; result = f(dentry,name); if (result) break; } return result; } static int stacker_inode_getsecurity(struct inode *inode, const char *name, void *buffer, size_t size) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, const char *name, void *buffer, size_t size); if (list_empty(&hooks_inode_getsecurity)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_getsecurity, list) { f = h->f; result = f(inode,name,buffer,size); if (result) break; } return result; } static int stacker_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, const char *name, const void *value, size_t size, int flags); if (list_empty(&hooks_inode_setsecurity)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_setsecurity, list) { f = h->f; result = f(inode,name,value,size,flags); if (result) break; } return result; } static int stacker_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct inode *inode, char *buffer, size_t buffer_size); if (list_empty(&hooks_inode_listsecurity)) return 0; list_for_each_entry_safe(h, next, &hooks_inode_listsecurity, list) { f = h->f; result = f(inode,buffer, buffer_size); if (result) break; } return result; } static int stacker_file_permission (struct file *file, int mask) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file, int mask); if (list_empty(&hooks_file_permission)) return 0; list_for_each_entry_safe(h, next, &hooks_file_permission, list) { f = h->f; result = f(file,mask); if (result) break; } return result; } static int stacker_file_alloc_security (struct file *file) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file); if (list_empty(&hooks_file_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_file_alloc_security, list) { f = h->f; result = f(file); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_file_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_file_free_security, list) { f = h->f; f(file); } } return result; } static void stacker_file_free_security (struct file *file) { struct stacker_hook *h, *next; int (*f) (struct file *file); if (list_empty(&hooks_file_free_security)) return; list_for_each_entry_safe(h, next, &hooks_file_free_security, list) { f = h->f; f(file); } } static int stacker_file_ioctl (struct file *file, unsigned int command, unsigned long arg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file, unsigned int command, unsigned long arg); if (list_empty(&hooks_file_ioctl)) return 0; list_for_each_entry_safe(h, next, &hooks_file_ioctl, list) { f = h->f; result = f(file,command,arg); if (result) break; } return result; } static int stacker_file_mmap (struct file *file, unsigned long prot, unsigned long flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file, unsigned long prot, unsigned long flags); if (list_empty(&hooks_file_mmap)) return 0; list_for_each_entry_safe(h, next, &hooks_file_mmap, list) { f = h->f; result = f(file, prot, flags); if (result) break; } return result; } static int stacker_file_mprotect (struct vm_area_struct *vma, unsigned long prot) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct vm_area_struct *vma, unsigned long prot); if (list_empty(&hooks_file_mprotect)) return 0; list_for_each_entry_safe(h, next, &hooks_file_mprotect, list) { f = h->f; result = f(vma,prot); if (result) break; } return result; } static int stacker_file_lock (struct file *file, unsigned int cmd) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file, unsigned int cmd); if (list_empty(&hooks_file_lock)) return 0; list_for_each_entry_safe(h, next, &hooks_file_lock, list) { f = h->f; result = f(file,cmd); if (result) break; } return result; } static int stacker_file_fcntl (struct file *file, unsigned int cmd, unsigned long arg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file, unsigned int cmd, unsigned long arg); if (list_empty(&hooks_file_fcntl)) return 0; list_for_each_entry_safe(h, next, &hooks_file_fcntl, list) { f = h->f; result = f(file,cmd,arg); if (result) break; } return result; } static int stacker_file_set_fowner (struct file *file) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file); if (list_empty(&hooks_file_set_fowner)) return 0; list_for_each_entry_safe(h, next, &hooks_file_set_fowner, list) { f = h->f; result = f(file); if (result) break; } return result; } static int stacker_file_send_sigiotask (struct task_struct *tsk, struct fown_struct *fown, int sig) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *tsk, struct fown_struct *fown, int sig); if (list_empty(&hooks_file_send_sigiotask)) return 0; list_for_each_entry_safe(h, next, &hooks_file_send_sigiotask, list) { f = h->f; result = f(tsk,fown,sig); if (result) break; } return result; } static int stacker_file_receive (struct file *file) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct file *file); if (list_empty(&hooks_file_receive)) return 0; list_for_each_entry_safe(h, next, &hooks_file_receive, list) { f = h->f; result = f(file); if (result) break; } return result; } static int stacker_task_create (unsigned long clone_flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (unsigned long clone_flags); if (list_empty(&hooks_task_create)) return 0; list_for_each_entry_safe(h, next, &hooks_task_create, list) { f = h->f; result = f(clone_flags); if (result) break; } return result; } static int stacker_task_alloc_security (struct task_struct *p) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p); if (list_empty(&hooks_task_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_task_alloc_security, list) { f = h->f; result = f(p); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_task_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_task_free_security, list) { f = h->f; f(p); } } return result; } static void stacker_task_free_security (struct task_struct *p) { struct stacker_hook *h, *next; int (*f) (struct task_struct *p); if (list_empty(&hooks_task_free_security)) return; list_for_each_entry_safe(h, next, &hooks_task_free_security, list) { f = h->f; f(p); } } static int stacker_task_setuid (uid_t id0, uid_t id1, uid_t id2, int flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (uid_t id0, uid_t id1, uid_t id2, int flags); if (list_empty(&hooks_task_setuid)) return 0; list_for_each_entry_safe(h, next, &hooks_task_setuid, list) { f = h->f; result = f(id0,id1,id2,flags); if (result) break; } return result; } static int stacker_task_post_setuid (uid_t id0, uid_t id1, uid_t id2, int flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (uid_t id0, uid_t id1, uid_t id2, int flags); if (list_empty(&hooks_task_post_setuid)) return 0; list_for_each_entry_safe(h, next, &hooks_task_post_setuid, list) { f = h->f; result = f(id0,id1,id2,flags); if (result) break; } return result; } static int stacker_task_setgid (gid_t id0, gid_t id1, gid_t id2, int flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (gid_t id0, gid_t id1, gid_t id2, int flags); if (list_empty(&hooks_task_setgid)) return 0; list_for_each_entry_safe(h, next, &hooks_task_setgid, list) { f = h->f; result = f(id0,id1,id2,flags); if (result) break; } return result; } static int stacker_task_setpgid (struct task_struct *p, pid_t pgid) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p, pid_t pgid); if (list_empty(&hooks_task_setpgid)) return 0; list_for_each_entry_safe(h, next, &hooks_task_setpgid, list) { f = h->f; result = f(p,pgid); if (result) break; } return result; } static int stacker_task_getpgid (struct task_struct *p) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p); if (list_empty(&hooks_task_getpgid)) return 0; list_for_each_entry_safe(h, next, &hooks_task_getpgid, list) { f = h->f; result = f(p); if (result) break; } return result; } static int stacker_task_getsid (struct task_struct *p) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p); if (list_empty(&hooks_task_getsid)) return 0; list_for_each_entry_safe(h, next, &hooks_task_getsid, list) { f = h->f; result = f(p); if (result) break; } return result; } static int stacker_task_setgroups (struct group_info *group_info) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct group_info *group_info); if (list_empty(&hooks_task_setgroups)) return 0; list_for_each_entry_safe(h, next, &hooks_task_setgroups, list) { f = h->f; result = f(group_info); if (result) break; } return result; } static int stacker_task_setnice (struct task_struct *p, int nice) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p, int nice); if (list_empty(&hooks_task_setnice)) return 0; list_for_each_entry_safe(h, next, &hooks_task_setnice, list) { f = h->f; result = f(p,nice); if (result) break; } return result; } static int stacker_task_setrlimit (unsigned int resource, struct rlimit *new_rlim) { int result = 0; struct stacker_hook *h, *next; int (*f) (unsigned int resource, struct rlimit *new_rlim); if (list_empty(&hooks_task_setrlimit)) return 0; list_for_each_entry_safe(h, next, &hooks_task_setrlimit, list) { f = h->f; result = f(resource,new_rlim); if (result) break; } return result; } static int stacker_task_setscheduler (struct task_struct *p, int policy, struct sched_param *lp) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p, int policy, struct sched_param *lp); if (list_empty(&hooks_task_setscheduler)) return 0; list_for_each_entry_safe(h, next, &hooks_task_setscheduler, list) { f = h->f; result = f(p,policy,lp); if (result) break; } return result; } static int stacker_task_getscheduler (struct task_struct *p) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p); if (list_empty(&hooks_task_getscheduler)) return 0; list_for_each_entry_safe(h, next, &hooks_task_getscheduler, list) { f = h->f; result = f(p); if (result) break; } return result; } static int stacker_task_wait (struct task_struct *p) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p); if (list_empty(&hooks_task_wait)) return 0; list_for_each_entry_safe(h, next, &hooks_task_wait, list) { f = h->f; result = f(p); if (result) break; } return result; } static int stacker_task_kill (struct task_struct *p, struct siginfo *info, int sig) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct task_struct *p, struct siginfo *info, int sig); if (list_empty(&hooks_task_kill)) return 0; list_for_each_entry_safe(h, next, &hooks_task_kill, list) { f = h->f; result = f(p,info,sig); if (result) break; } return result; } static int stacker_task_prctl (int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { int result = 0; struct stacker_hook *h, *next; int (*f) (int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); if (list_empty(&hooks_task_prctl)) return 0; list_for_each_entry_safe(h, next, &hooks_task_prctl, list) { f = h->f; result = f(option,arg2,arg3,arg4,arg5); if (result) break; } return result; } /* Note that the dummy version of this hook would call: * p->euid = p->fsuid = 0; */ static void stacker_task_reparent_to_init (struct task_struct *p) { struct stacker_hook *h, *next; int (*f) (struct task_struct *p); if (list_empty(&hooks_task_reparent_to_init)) return; list_for_each_entry_safe(h, next, &hooks_task_reparent_to_init, list) { f = h->f; f(p); } } static void stacker_task_to_inode(struct task_struct *p, struct inode *inode) { struct stacker_hook *h, *next; int (*f) (struct task_struct *p, struct inode *inode); if (list_empty(&hooks_task_to_inode)) return; list_for_each_entry_safe(h, next, &hooks_task_to_inode, list) { f = h->f; f(p, inode); } } #ifdef CONFIG_SECURITY_NETWORK static int stacker_socket_create (int family, int type, int protocol, int kern) { int result = 0; struct stacker_hook *h, *next; int (*f) (int family, int type, int protocol, int kern); if (list_empty(&hooks_socket_create)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_create, list) { f = h->f; result = f(family,type,protocol,kern); if (result) break; } return result; } static void stacker_socket_post_create (struct socket *sock, int family, int type, int protocol, int kern) { struct stacker_hook *h, *next; int (*f) (struct socket *sock, int family, int type, int protocol, int kern); if (list_empty(&hooks_socket_post_create)) return; list_for_each_entry_safe(h, next, &hooks_socket_post_create, list) { f = h->f; f(sock,family,type,protocol,kern); } } static int stacker_socket_bind (struct socket *sock, struct sockaddr *address, int addrlen) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct sockaddr *address, int addrlen); if (list_empty(&hooks_socket_bind)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_bind, list) { f = h->f; result = f(sock,address,addrlen); if (result) break; } return result; } static int stacker_socket_connect (struct socket *sock, struct sockaddr *address, int addrlen) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct sockaddr *address, int addrlen); if (list_empty(&hooks_socket_connect)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_connect, list) { f = h->f; result = f(sock,address,addrlen); if (result) break; } return result; } static int stacker_socket_listen (struct socket *sock, int backlog) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, int backlog); if (list_empty(&hooks_socket_listen)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_listen, list) { f = h->f; result = f(sock,backlog); if (result) break; } return result; } static int stacker_socket_accept (struct socket *sock, struct socket *newsock) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct socket *newsock); if (list_empty(&hooks_socket_accept)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_accept, list) { f = h->f; result = f(sock,newsock); if (result) break; } return result; } static void stacker_socket_post_accept (struct socket *sock, struct socket *newsock) { struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct socket *newsock); if (list_empty(&hooks_socket_post_accept)) return; list_for_each_entry_safe(h, next, &hooks_socket_post_accept, list) { f = h->f; f(sock,newsock); } } static int stacker_socket_sendmsg (struct socket *sock, struct msghdr *msg, int size) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct msghdr *msg, int size); if (list_empty(&hooks_socket_sendmsg)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_sendmsg, list) { f = h->f; result = f(sock,msg,size); if (result) break; } return result; } static int stacker_socket_recvmsg (struct socket *sock, struct msghdr *msg, int size, int flags) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct msghdr *msg, int size, int flags); if (list_empty(&hooks_socket_recvmsg)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_recvmsg, list) { f = h->f; result = f(sock,msg,size,flags); if (result) break; } return result; } static int stacker_socket_getsockname (struct socket *sock) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock); if (list_empty(&hooks_socket_getsockname)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_getsockname, list) { f = h->f; result = f(sock); if (result) break; } return result; } static int stacker_socket_getpeername (struct socket *sock) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock); if (list_empty(&hooks_socket_getpeername)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_getpeername, list) { f = h->f; result = f(sock); if (result) break; } return result; } static int stacker_socket_setsockopt (struct socket *sock, int level, int optname) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, int level, int optname); if (list_empty(&hooks_socket_setsockopt)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_setsockopt, list) { f = h->f; result = f(sock,level,optname); if (result) break; } return result; } static int stacker_socket_getsockopt (struct socket *sock, int level, int optname) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, int level, int optname); if (list_empty(&hooks_socket_getsockopt)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_getsockopt, list) { f = h->f; result = f(sock,level,optname); if (result) break; } return result; } static int stacker_socket_shutdown (struct socket *sock, int how) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, int how); if (list_empty(&hooks_socket_shutdown)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_shutdown, list) { f = h->f; result = f(sock,how); if (result) break; } return result; } static int stacker_socket_sock_rcv_skb (struct sock *sk, struct sk_buff *skb) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct sock *sk, struct sk_buff *skb); if (list_empty(&hooks_socket_sock_rcv_skb)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_sock_rcv_skb, list) { f = h->f; result = f(sk,skb); if (result) break; } return result; } static int stacker_unix_stream_connect (struct socket *sock, struct socket *other, struct sock *newsk) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct socket *other, struct sock *newsk); if (list_empty(&hooks_unix_stream_connect)) return 0; list_for_each_entry_safe(h, next, &hooks_unix_stream_connect, list) { f = h->f; result = f(sock,other,newsk); if (result) break; } return result; } static int stacker_unix_may_send (struct socket *sock, struct socket *other) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, struct socket *other); if (list_empty(&hooks_unix_may_send)) return 0; list_for_each_entry_safe(h, next, &hooks_unix_may_send, list) { f = h->f; result = f(sock,other); if (result) break; } return result; } static int stacker_socket_getpeersec(struct socket *sock, char __user *optval, int __user *optlen, unsigned len) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct socket *sock, char __user *optval, int __user *optlen, unsigned len); if (list_empty(&hooks_socket_getpeersec)) return 0; list_for_each_entry_safe(h, next, &hooks_socket_getpeersec, list) { f = h->f; result = f(sock,optval,optlen,len); if (result) break; } return result; } static int stacker_sk_alloc_security(struct sock *sk, int family, int priority) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct sock *sk, int family, int priority); int (*f2) (struct sock *sk); if (list_empty(&hooks_sk_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_sk_alloc_security, list) { f = h->f; result = f(sk, family, priority); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_sk_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_sk_free_security, list) { f2 = h->f; f2(sk); } } return result; } static void stacker_sk_free_security (struct sock *sk) { struct stacker_hook *h, *next; int (*f) (struct sock *sk); if (list_empty(&hooks_sk_free_security)) return; list_for_each_entry_safe(h, next, &hooks_sk_free_security, list) { f = h->f; f(sk); } } #endif static int stacker_ipc_permission (struct kern_ipc_perm *ipcp, short flag) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct kern_ipc_perm *ipcp, short flag); if (list_empty(&hooks_ipc_permission)) return 0; list_for_each_entry_safe(h, next, &hooks_ipc_permission, list) { f = h->f; result = f(ipcp,flag); if (result) break; } return result; } static int stacker_msg_msg_alloc_security (struct msg_msg *msg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct msg_msg *msg); if (list_empty(&hooks_msg_msg_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_msg_msg_alloc_security, list) { f = h->f; result = f(msg); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_msg_msg_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_msg_msg_free_security, list) { f = h->f; f(msg); } } return result; } static void stacker_msg_msg_free_security (struct msg_msg *msg) { struct stacker_hook *h, *next; int (*f) (struct msg_msg *msg); if (list_empty(&hooks_msg_msg_free_security)) return; list_for_each_entry_safe(h, next, &hooks_msg_msg_free_security, list) { f = h->f; f(msg); } } static int stacker_msg_queue_alloc_security (struct msg_queue *msq) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct msg_queue *msq); if (list_empty(&hooks_msg_queue_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_msg_queue_alloc_security, list) { f = h->f; result = f(msq); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_msg_queue_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_msg_queue_free_security, list) { f = h->f; f(msq); } } return result; } static void stacker_msg_queue_free_security (struct msg_queue *msq) { struct stacker_hook *h, *next; int (*f) (struct msg_queue *msq); if (list_empty(&hooks_msg_queue_free_security)) return; list_for_each_entry_safe(h, next, &hooks_msg_queue_free_security, list) { f = h->f; f(msq); } } static int stacker_msg_queue_associate (struct msg_queue *msq, int msqflg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct msg_queue *msq, int msqflg); if (list_empty(&hooks_msg_queue_associate)) return 0; list_for_each_entry_safe(h, next, &hooks_msg_queue_associate, list) { f = h->f; result = f(msq,msqflg); if (result) break; } return result; } static int stacker_msg_queue_msgctl (struct msg_queue *msq, int cmd) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct msg_queue *msq, int cmd); if (list_empty(&hooks_msg_queue_msgctl)) return 0; list_for_each_entry_safe(h, next, &hooks_msg_queue_msgctl, list) { f = h->f; result = f(msq,cmd); if (result) break; } return result; } static int stacker_msg_queue_msgsnd (struct msg_queue *msq, struct msg_msg *msg, int msgflg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct msg_queue *msq, struct msg_msg *msg, int msgflg); if (list_empty(&hooks_msg_queue_msgsnd)) return 0; list_for_each_entry_safe(h, next, &hooks_msg_queue_msgsnd, list) { f = h->f; result = f(msq,msg,msgflg); if (result) break; } return result; } static int stacker_msg_queue_msgrcv (struct msg_queue *msq, struct msg_msg *msg, struct task_struct *target, long type, int mode) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct msg_queue *msq, struct msg_msg *msg, struct task_struct *target, long type, int mode); if (list_empty(&hooks_msg_queue_msgrcv)) return 0; list_for_each_entry_safe(h, next, &hooks_msg_queue_msgrcv, list) { f = h->f; result = f(msq,msg,target,type,mode); if (result) break; } return result; } static int stacker_shm_alloc_security (struct shmid_kernel *shp) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct shmid_kernel *shp); if (list_empty(&hooks_shm_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_shm_alloc_security, list) { f = h->f; result = f(shp); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_shm_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_shm_free_security, list) { f = h->f; f(shp); } } return result; } static void stacker_shm_free_security (struct shmid_kernel *shp) { struct stacker_hook *h, *next; int (*f) (struct shmid_kernel *shp); if (list_empty(&hooks_shm_free_security)) return; list_for_each_entry_safe(h, next, &hooks_shm_free_security, list) { f = h->f; f(shp); } } static int stacker_shm_associate (struct shmid_kernel *shp, int shmflg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct shmid_kernel *shp, int shmflg); if (list_empty(&hooks_shm_associate)) return 0; list_for_each_entry_safe(h, next, &hooks_shm_associate, list) { f = h->f; result = f(shp,shmflg); if (result) break; } return result; } static int stacker_shm_shmctl (struct shmid_kernel *shp, int cmd) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct shmid_kernel *shp, int cmd); if (list_empty(&hooks_shm_shmctl)) return 0; list_for_each_entry_safe(h, next, &hooks_shm_shmctl, list) { f = h->f; result = f(shp,cmd); if (result) break; } return result; } static int stacker_shm_shmat (struct shmid_kernel *shp, char *shmaddr, int shmflg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct shmid_kernel *shp, char *shmaddr, int shmflg); if (list_empty(&hooks_shm_shmat)) return 0; list_for_each_entry_safe(h, next, &hooks_shm_shmat, list) { f = h->f; result = f(shp,shmaddr,shmflg); if (result) break; } return result; } static int stacker_sem_alloc_security (struct sem_array *sma) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct sem_array *sma); if (list_empty(&hooks_sem_alloc_security)) return 0; list_for_each_entry_safe(h, next, &hooks_sem_alloc_security, list) { f = h->f; result = f(sma); if (result) goto bad; } return 0; bad: if (result) { if (list_empty(&hooks_sem_free_security)) return result; list_for_each_entry_safe(h, next, &hooks_sem_free_security, list) { f = h->f; f(sma); } } return result; } static void stacker_sem_free_security (struct sem_array *sma) { struct stacker_hook *h, *next; int (*f) (struct sem_array *sma); if (list_empty(&hooks_sem_free_security)) return; list_for_each_entry_safe(h, next, &hooks_sem_free_security, list) { f = h->f; f(sma); } } static int stacker_sem_associate (struct sem_array *sma, int semflg) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct sem_array *sma, int semflg); if (list_empty(&hooks_sem_associate)) return 0; list_for_each_entry_safe(h, next, &hooks_sem_associate, list) { f = h->f; result = f(sma,semflg); if (result) break; } return result; } static int stacker_sem_semctl (struct sem_array *sma, int cmd) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct sem_array *sma, int cmd); if (list_empty(&hooks_sem_semctl)) return 0; list_for_each_entry_safe(h, next, &hooks_sem_semctl, list) { f = h->f; result = f(sma,cmd); if (result) break; } return result; } static int stacker_sem_semop (struct sem_array *sma, struct sembuf *sops, unsigned nsops, int alter) { int result = 0; struct stacker_hook *h, *next; int (*f) (struct sem_array *sma, struct sembuf *sops, unsigned nsops, int alter); if (list_empty(&hooks_sem_semop)) return 0; list_for_each_entry_safe(h, next, &hooks_sem_semop, list) { f = h->f; result = f(sma,sops,nsops,alter); if (result) break; } return result; } static void stacker_d_instantiate (struct dentry *dentry, struct inode *inode) { struct stacker_hook *h, *next; int (*f) (struct dentry *dentry, struct inode *inode); if (list_empty(&hooks_d_instantiate)) return; list_for_each_entry_safe(h, next, &hooks_d_instantiate, list) { f = h->f; f(dentry,inode); } } static int stacker_getprocattr(struct task_struct *p, char *name, void *value, size_t size) { if (!selinux_module) return -EINVAL; if (!selinux_module->module_operations.getprocattr) return -EINVAL; return selinux_module->module_operations.getprocattr(p, name, value, size); } static int stacker_setprocattr(struct task_struct *p, char *name, void *value, size_t size) { if (!selinux_module) return -EINVAL; if (!selinux_module->module_operations.setprocattr) return -EINVAL; return selinux_module->module_operations.setprocattr(p, name, value, size); } static struct stacker_hook *stacker_new_hook(void) { struct stacker_hook *h; h = kmalloc(sizeof(struct stacker_hook), GFP_KERNEL); return h; } #define add_to_listhead_if_set(ops, function) \ do { \ if (ops->function) { \ struct stacker_hook *h; \ h = stacker_new_hook(); \ BUG_ON(!h); \ INIT_LIST_HEAD(&h->list); \ h->f = (void *) ops->function; \ list_add_tail(&h->list, &hooks_##function); \ } \ } while (0) /* * XXX Note that now especially each _free_ function should not fail if * it has nothing to free */ void stacker_fixup_ops (struct security_operations *ops) { add_to_listhead_if_set(ops, ptrace); add_to_listhead_if_set(ops, capget); add_to_listhead_if_set(ops, capset_check); add_to_listhead_if_set(ops, capset_set); add_to_listhead_if_set(ops, acct); add_to_listhead_if_set(ops, capable); add_to_listhead_if_set(ops, quotactl); add_to_listhead_if_set(ops, quota_on); add_to_listhead_if_set(ops, sysctl); add_to_listhead_if_set(ops, syslog); add_to_listhead_if_set(ops, settime); add_to_listhead_if_set(ops, vm_enough_memory); add_to_listhead_if_set(ops, bprm_alloc_security); add_to_listhead_if_set(ops, bprm_free_security); add_to_listhead_if_set(ops, bprm_apply_creds); add_to_listhead_if_set(ops, bprm_post_apply_creds); add_to_listhead_if_set(ops, bprm_set_security); add_to_listhead_if_set(ops, bprm_check_security); add_to_listhead_if_set(ops, bprm_secureexec); add_to_listhead_if_set(ops, sb_alloc_security); add_to_listhead_if_set(ops, sb_free_security); add_to_listhead_if_set(ops, sb_copy_data); add_to_listhead_if_set(ops, sb_kern_mount); add_to_listhead_if_set(ops, sb_statfs); add_to_listhead_if_set(ops, sb_mount); add_to_listhead_if_set(ops, sb_check_sb); add_to_listhead_if_set(ops, sb_umount); add_to_listhead_if_set(ops, sb_umount_close); add_to_listhead_if_set(ops, sb_umount_busy); add_to_listhead_if_set(ops, sb_post_remount); add_to_listhead_if_set(ops, sb_post_mountroot); add_to_listhead_if_set(ops, sb_post_addmount); add_to_listhead_if_set(ops, sb_pivotroot); add_to_listhead_if_set(ops, sb_post_pivotroot); add_to_listhead_if_set(ops, inode_alloc_security); add_to_listhead_if_set(ops, inode_free_security); add_to_listhead_if_set(ops, inode_create); add_to_listhead_if_set(ops, inode_post_create); add_to_listhead_if_set(ops, inode_link); add_to_listhead_if_set(ops, inode_post_link); add_to_listhead_if_set(ops, inode_unlink); add_to_listhead_if_set(ops, inode_symlink); add_to_listhead_if_set(ops, inode_post_symlink); add_to_listhead_if_set(ops, inode_mkdir); add_to_listhead_if_set(ops, inode_post_mkdir); add_to_listhead_if_set(ops, inode_rmdir); add_to_listhead_if_set(ops, inode_mknod); add_to_listhead_if_set(ops, inode_post_mknod); add_to_listhead_if_set(ops, inode_rename); add_to_listhead_if_set(ops, inode_post_rename); add_to_listhead_if_set(ops, inode_readlink); add_to_listhead_if_set(ops, inode_follow_link); add_to_listhead_if_set(ops, inode_permission); add_to_listhead_if_set(ops, inode_setattr); add_to_listhead_if_set(ops, inode_getattr); add_to_listhead_if_set(ops, inode_delete); add_to_listhead_if_set(ops, inode_setxattr); add_to_listhead_if_set(ops, inode_post_setxattr); add_to_listhead_if_set(ops, inode_getxattr); add_to_listhead_if_set(ops, inode_listxattr); add_to_listhead_if_set(ops, inode_removexattr); add_to_listhead_if_set(ops, inode_getsecurity); add_to_listhead_if_set(ops, inode_setsecurity); add_to_listhead_if_set(ops, inode_listsecurity); add_to_listhead_if_set(ops, file_permission); add_to_listhead_if_set(ops, file_alloc_security); add_to_listhead_if_set(ops, file_free_security); add_to_listhead_if_set(ops, file_ioctl); add_to_listhead_if_set(ops, file_mmap); add_to_listhead_if_set(ops, file_mprotect); add_to_listhead_if_set(ops, file_lock); add_to_listhead_if_set(ops, file_fcntl); add_to_listhead_if_set(ops, file_set_fowner); add_to_listhead_if_set(ops, file_send_sigiotask); add_to_listhead_if_set(ops, file_receive); add_to_listhead_if_set(ops, task_create); add_to_listhead_if_set(ops, task_alloc_security); add_to_listhead_if_set(ops, task_free_security); add_to_listhead_if_set(ops, task_setuid); add_to_listhead_if_set(ops, task_post_setuid); add_to_listhead_if_set(ops, task_setgid); add_to_listhead_if_set(ops, task_setpgid); add_to_listhead_if_set(ops, task_getpgid); add_to_listhead_if_set(ops, task_getsid); add_to_listhead_if_set(ops, task_setgroups); add_to_listhead_if_set(ops, task_setnice); add_to_listhead_if_set(ops, task_setrlimit); add_to_listhead_if_set(ops, task_setscheduler); add_to_listhead_if_set(ops, task_getscheduler); add_to_listhead_if_set(ops, task_wait); add_to_listhead_if_set(ops, task_kill); add_to_listhead_if_set(ops, task_prctl); add_to_listhead_if_set(ops, task_reparent_to_init); add_to_listhead_if_set(ops, task_to_inode); add_to_listhead_if_set(ops, ipc_permission); add_to_listhead_if_set(ops, msg_msg_alloc_security); add_to_listhead_if_set(ops, msg_msg_free_security); add_to_listhead_if_set(ops, msg_queue_alloc_security); add_to_listhead_if_set(ops, msg_queue_free_security); add_to_listhead_if_set(ops, msg_queue_associate); add_to_listhead_if_set(ops, msg_queue_msgctl); add_to_listhead_if_set(ops, msg_queue_msgsnd); add_to_listhead_if_set(ops, msg_queue_msgrcv); add_to_listhead_if_set(ops, shm_alloc_security); add_to_listhead_if_set(ops, shm_free_security); add_to_listhead_if_set(ops, shm_associate); add_to_listhead_if_set(ops, shm_shmctl); add_to_listhead_if_set(ops, shm_shmat); add_to_listhead_if_set(ops, sem_alloc_security); add_to_listhead_if_set(ops, sem_free_security); add_to_listhead_if_set(ops, sem_associate); add_to_listhead_if_set(ops, sem_semctl); add_to_listhead_if_set(ops, sem_semop); add_to_listhead_if_set(ops, netlink_send); add_to_listhead_if_set(ops, netlink_recv); add_to_listhead_if_set(ops, register_security); add_to_listhead_if_set(ops, unregister_security); add_to_listhead_if_set(ops, d_instantiate); add_to_listhead_if_set(ops, getprocattr); add_to_listhead_if_set(ops, setprocattr); #ifdef CONFIG_SECURITY_NETWORK add_to_listhead_if_set(ops, unix_stream_connect); add_to_listhead_if_set(ops, unix_may_send); add_to_listhead_if_set(ops, socket_create); add_to_listhead_if_set(ops, socket_post_create); add_to_listhead_if_set(ops, socket_bind); add_to_listhead_if_set(ops, socket_connect); add_to_listhead_if_set(ops, socket_listen); add_to_listhead_if_set(ops, socket_accept); add_to_listhead_if_set(ops, socket_post_accept); add_to_listhead_if_set(ops, socket_sendmsg); add_to_listhead_if_set(ops, socket_recvmsg); add_to_listhead_if_set(ops, socket_getsockname); add_to_listhead_if_set(ops, socket_getpeername); add_to_listhead_if_set(ops, socket_setsockopt); add_to_listhead_if_set(ops, socket_getsockopt); add_to_listhead_if_set(ops, socket_shutdown); add_to_listhead_if_set(ops, socket_sock_rcv_skb); add_to_listhead_if_set(ops, socket_getpeersec); add_to_listhead_if_set(ops, sk_alloc_security); add_to_listhead_if_set(ops, sk_free_security); #endif /* CONFIG_SECURITY_NETWORK */ } /* * Add the stacked module (as specified by name and ops) * according to the current ordering policy. */ static int stacker_register (const char *name, struct security_operations *ops) { char *new_module_name; struct module_entry *new_module_entry; int namelen = strnlen(name, MAX_MODULE_NAME_LEN); int ret = 0; spin_lock(&stacker_lock); stacker_fixup_ops(ops); if (forbid_stacker_register) { ret = -EINVAL; goto out; } new_module_name = kmalloc(namelen+1, GFP_KERNEL); new_module_entry = kmalloc(sizeof(struct module_entry), GFP_KERNEL); if (!new_module_name || !new_module_entry) { printk(KERN_WARNING "%s: Failure registering module - out of memory\n", __FUNCTION__); ret = -ENOMEM; goto out; } memset(new_module_entry, 0, sizeof(struct module_entry)); strncpy(new_module_name, name, namelen); new_module_name[namelen] = '\0'; memcpy(&new_module_entry->module_operations, ops, sizeof(struct security_operations)); new_module_entry->module_name = new_module_name; new_module_entry->namelen = namelen; INIT_LIST_HEAD(&new_module_entry->lsm_list); INIT_LIST_HEAD(&new_module_entry->all_lsms); list_add_tail(&new_module_entry->all_lsms, &all_modules); list_add_tail(&new_module_entry->lsm_list, &stacked_modules); if (strcmp(name, "selinux") == 0) selinux_module = new_module_entry; printk(KERN_INFO "%s: registered %s module\n", __FUNCTION__, new_module_entry->module_name); out: spin_unlock(&stacker_lock); return ret; } /* * find_lsm: * Find a module by name. Called with stacker spinlock held. */ static struct module_entry * find_lsm(const char *name, int len) { struct module_entry *m, *next; list_for_each_entry_safe(m, next, &all_modules, all_lsms) { if (m->namelen == len && !strncmp(m->module_name, name, len)) return m; } return NULL; } static struct module_entry * find_active_lsm(const char *name, int len) { struct module_entry *m, *next; list_for_each_entry_safe(m, next, &stacked_modules, lsm_list) { if (m->namelen == len && !strncmp(m->module_name, name, len)) return m; } return NULL; } /* * Unregister a registered LSM. * It would be nice if we could veto this, but we can't... * So we assume that if a module didn't stop itself being * unloaded (using try_module_get), it doesn't need to free * any more kernel object annotated data. */ static int stacker_unregister (const char *name, struct security_operations *ops) { struct module_entry *m; int len = strnlen(name, MAX_MODULE_NAME_LEN); int ret = 0; spin_lock(&stacker_lock); m = find_lsm(name, len); if (!m) { printk(KERN_INFO "%s: could not find module %s.\n", __FUNCTION__, name); ret = -ENOENT; goto out; } /* It may have been unloaded through sysfs first, in which case we can't remove m->lsm_list again. */ if (m->lsm_list.prev != LIST_POISON2) { list_del_rcu(&m->lsm_list); if (strcmp(m->module_name, "selinux") == 0) selinux_module = NULL; } list_del_rcu(&m->all_lsms); /* And yes, we are leaking the memory for the struct module_entry, as someone might be calling one of it's callbacks right now. */ out: spin_unlock(&stacker_lock); return ret; } static struct security_operations stacker_ops = { .ptrace = stacker_ptrace, .capget = stacker_capget, .capset_check = stacker_capset_check, .capset_set = stacker_capset_set, .acct = stacker_acct, .sysctl = stacker_sysctl, .capable = stacker_capable, .quotactl = stacker_quotactl, .quota_on = stacker_quota_on, .syslog = stacker_syslog, .vm_enough_memory = stacker_vm_enough_memory, .bprm_alloc_security = stacker_bprm_alloc_security, .bprm_free_security = stacker_bprm_free_security, .bprm_apply_creds = stacker_bprm_apply_creds, .bprm_post_apply_creds = stacker_bprm_post_apply_creds, .bprm_set_security = stacker_bprm_set_security, .bprm_check_security = stacker_bprm_check_security, .bprm_secureexec = stacker_bprm_secureexec, .sb_alloc_security = stacker_sb_alloc_security, .sb_free_security = stacker_sb_free_security, .sb_copy_data = stacker_sb_copy_data, .sb_kern_mount = stacker_sb_kern_mount, .sb_statfs = stacker_sb_statfs, .sb_mount = stacker_sb_mount, .sb_check_sb = stacker_sb_check_sb, .sb_umount = stacker_sb_umount, .sb_umount_close = stacker_sb_umount_close, .sb_umount_busy = stacker_sb_umount_busy, .sb_post_remount = stacker_sb_post_remount, .sb_post_mountroot = stacker_sb_post_mountroot, .sb_post_addmount = stacker_sb_post_addmount, .sb_pivotroot = stacker_sb_pivotroot, .sb_post_pivotroot = stacker_sb_post_pivotroot, .inode_alloc_security = stacker_inode_alloc_security, .inode_free_security = stacker_inode_free_security, .inode_create = stacker_inode_create, .inode_post_create = stacker_inode_post_create, .inode_link = stacker_inode_link, .inode_post_link = stacker_inode_post_link, .inode_unlink = stacker_inode_unlink, .inode_symlink = stacker_inode_symlink, .inode_post_symlink = stacker_inode_post_symlink, .inode_mkdir = stacker_inode_mkdir, .inode_post_mkdir = stacker_inode_post_mkdir, .inode_rmdir = stacker_inode_rmdir, .inode_mknod = stacker_inode_mknod, .inode_post_mknod = stacker_inode_post_mknod, .inode_rename = stacker_inode_rename, .inode_post_rename = stacker_inode_post_rename, .inode_readlink = stacker_inode_readlink, .inode_follow_link = stacker_inode_follow_link, .inode_permission = stacker_inode_permission, .inode_setattr = stacker_inode_setattr, .inode_getattr = stacker_inode_getattr, .inode_delete = stacker_inode_delete, .inode_setxattr = stacker_inode_setxattr, .inode_post_setxattr = stacker_inode_post_setxattr, .inode_getxattr = stacker_inode_getxattr, .inode_listxattr = stacker_inode_listxattr, .inode_removexattr = stacker_inode_removexattr, .inode_getsecurity = stacker_inode_getsecurity, .inode_setsecurity = stacker_inode_setsecurity, .inode_listsecurity = stacker_inode_listsecurity, .file_permission = stacker_file_permission, .file_alloc_security = stacker_file_alloc_security, .file_free_security = stacker_file_free_security, .file_ioctl = stacker_file_ioctl, .file_mmap = stacker_file_mmap, .file_mprotect = stacker_file_mprotect, .file_lock = stacker_file_lock, .file_fcntl = stacker_file_fcntl, .file_set_fowner = stacker_file_set_fowner, .file_send_sigiotask = stacker_file_send_sigiotask, .file_receive = stacker_file_receive, .task_create = stacker_task_create, .task_alloc_security = stacker_task_alloc_security, .task_free_security = stacker_task_free_security, .task_setuid = stacker_task_setuid, .task_post_setuid = stacker_task_post_setuid, .task_setgid = stacker_task_setgid, .task_setpgid = stacker_task_setpgid, .task_getpgid = stacker_task_getpgid, .task_getsid = stacker_task_getsid, .task_setgroups = stacker_task_setgroups, .task_setnice = stacker_task_setnice, .task_setrlimit = stacker_task_setrlimit, .task_setscheduler = stacker_task_setscheduler, .task_getscheduler = stacker_task_getscheduler, .task_kill = stacker_task_kill, .task_wait = stacker_task_wait, .task_prctl = stacker_task_prctl, .task_reparent_to_init = stacker_task_reparent_to_init, .task_to_inode = stacker_task_to_inode, .ipc_permission = stacker_ipc_permission, .msg_msg_alloc_security = stacker_msg_msg_alloc_security, .msg_msg_free_security = stacker_msg_msg_free_security, .msg_queue_alloc_security = stacker_msg_queue_alloc_security, .msg_queue_free_security = stacker_msg_queue_free_security, .msg_queue_associate = stacker_msg_queue_associate, .msg_queue_msgctl = stacker_msg_queue_msgctl, .msg_queue_msgsnd = stacker_msg_queue_msgsnd, .msg_queue_msgrcv = stacker_msg_queue_msgrcv, .shm_alloc_security = stacker_shm_alloc_security, .shm_free_security = stacker_shm_free_security, .shm_associate = stacker_shm_associate, .shm_shmctl = stacker_shm_shmctl, .shm_shmat = stacker_shm_shmat, .sem_alloc_security = stacker_sem_alloc_security, .sem_free_security = stacker_sem_free_security, .sem_associate = stacker_sem_associate, .sem_semctl = stacker_sem_semctl, .sem_semop = stacker_sem_semop, .netlink_send = stacker_netlink_send, .netlink_recv = stacker_netlink_recv, .register_security = stacker_register, .unregister_security = stacker_unregister, .d_instantiate = stacker_d_instantiate, .getprocattr = stacker_getprocattr, .setprocattr = stacker_setprocattr, #ifdef CONFIG_SECURITY_NETWORK .unix_stream_connect = stacker_unix_stream_connect, .unix_may_send = stacker_unix_may_send, .socket_create = stacker_socket_create, .socket_post_create = stacker_socket_post_create, .socket_bind = stacker_socket_bind, .socket_connect = stacker_socket_connect, .socket_listen = stacker_socket_listen, .socket_accept = stacker_socket_accept, .socket_post_accept = stacker_socket_post_accept, .socket_sendmsg = stacker_socket_sendmsg, .socket_recvmsg = stacker_socket_recvmsg, .socket_getsockname = stacker_socket_getsockname, .socket_getpeername = stacker_socket_getpeername, .socket_getsockopt = stacker_socket_getsockopt, .socket_setsockopt = stacker_socket_setsockopt, .socket_shutdown = stacker_socket_shutdown, .socket_sock_rcv_skb = stacker_socket_sock_rcv_skb, .socket_getpeersec = stacker_socket_getpeersec, .sk_alloc_security = stacker_sk_alloc_security, .sk_free_security = stacker_sk_free_security, #endif }; /* * Functions to provide the sysfs interface */ /* A structure to pass into sysfs through kobjects */ struct stacker_kobj { struct list_head slot_list; struct kobject kobj; }; struct stacker_attribute { struct attribute attr; ssize_t (*show)(struct stacker_kobj *, char *); ssize_t (*store)(struct stacker_kobj *, const char *, size_t); }; /* variables to hold kobject/sysfs data */ static struct subsystem stacker_subsys; static void unregister_sysfs_files(void); static ssize_t stacker_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len) { struct stacker_kobj *obj = container_of(kobj, struct stacker_kobj, kobj); struct stacker_attribute *attribute = container_of(attr, struct stacker_attribute, attr); return attribute->store ? attribute->store(obj, buf, len) : 0; } static ssize_t stacker_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct stacker_kobj *obj = container_of(kobj, struct stacker_kobj, kobj); struct stacker_attribute *attribute = container_of(attr, struct stacker_attribute, attr); return attribute->show ? attribute->show(obj, buf) : 0; } static struct sysfs_ops stacker_sysfs_ops = { .show = stacker_attr_show, .store = stacker_attr_store, }; static struct kobj_type stacker_ktype = { .sysfs_ops = &stacker_sysfs_ops }; static decl_subsys(stacker, &stacker_ktype, NULL); /* Set lockdown */ static ssize_t lockdown_read (struct stacker_kobj *obj, char *buff) { return sprintf(buff, "%d", forbid_stacker_register); } static ssize_t lockdown_write (struct stacker_kobj *obj, const char *buff, size_t count) { if (count>0) forbid_stacker_register = 1; return count; } static struct stacker_attribute stacker_attr_lockdown = { .attr = {.name = "lockdown", .mode = S_IFREG | S_IRUGO | S_IWUSR}, .show = lockdown_read, .store = lockdown_write }; /* list modules */ static ssize_t listmodules_read (struct stacker_kobj *obj, char *buff) { ssize_t len = 0; struct module_entry *m, *next; list_for_each_entry_safe(m, next, &stacked_modules, lsm_list) { len += snprintf(buff+len, PAGE_SIZE - len, "%s\n", m->module_name); } return len; } static struct stacker_attribute stacker_attr_listmodules = { .attr = {.name = "list_modules", .mode = S_IFREG | S_IRUGO | S_IWUSR}, .show = listmodules_read, }; /* respond to a request to unload a module */ static ssize_t stacker_unload_write (struct stacker_kobj *obj, const char *name, size_t count) { struct module_entry *m; int len = strnlen(name, MAX_MODULE_NAME_LEN); int ret = count; if (!capable(CAP_SYS_ADMIN)) return -EPERM; if (count <= 0) return -EINVAL; spin_lock(&stacker_lock); m = find_active_lsm(name, len); if (!m) { printk(KERN_INFO "%s: could not find module %s.\n", __FUNCTION__, name); ret = -ENOENT; goto out; } if (strcmp(m->module_name, "selinux") == 0) selinux_module = NULL; list_del_rcu(&m->lsm_list); out: spin_unlock(&stacker_lock); return ret; } static struct stacker_attribute stacker_attr_unload = { .attr = {.name = "unload", .mode = S_IFREG | S_IRUGO | S_IWUSR}, .store = stacker_unload_write, }; /* stop responding to sysfs */ static ssize_t stop_responding_read (struct stacker_kobj *obj, char *buff) { ssize_t len; len = sprintf(buff, "Obviously not\n"); return len; } static ssize_t stop_responding_write (struct stacker_kobj *obj, const char *buff, size_t count) { if (count>0) unregister_sysfs_files(); return count; } static struct stacker_attribute stacker_attr_stop_responding = { .attr = {.name = "stop_responding", .mode = S_IFREG | S_IRUGO | S_IWUSR}, .show = stop_responding_read, .store = stop_responding_write }; static void unregister_sysfs_files(void) { struct kobject *kobj; if (!sysfsfiles_registered) return; kobj = &stacker_subsys.kset.kobj; sysfs_remove_file(kobj, &stacker_attr_lockdown.attr); sysfs_remove_file(kobj, &stacker_attr_listmodules.attr); sysfs_remove_file(kobj, &stacker_attr_stop_responding.attr); sysfs_remove_file(kobj, &stacker_attr_unload.attr); sysfsfiles_registered = 0; } static int register_sysfs_files(void) { int result; result = subsystem_register(&stacker_subsys); if (result) { printk(KERN_WARNING "Error (%d) registering stacker sysfs subsystem\n", result); return result; } sysfs_create_file(&stacker_subsys.kset.kobj, &stacker_attr_lockdown.attr); sysfs_create_file(&stacker_subsys.kset.kobj, &stacker_attr_listmodules.attr); sysfs_create_file(&stacker_subsys.kset.kobj, &stacker_attr_unload.attr); sysfsfiles_registered = 1; stacker_dbg("sysfs files registered\n"); return 0; } module_init(register_sysfs_files); static int __init stacker_init (void) { forbid_stacker_register = 0; INIT_LIST_HEAD(&stacked_modules); INIT_LIST_HEAD(&all_modules); sysfsfiles_registered = 0; spin_lock_init(&stacker_lock); if (register_security (&stacker_ops)) { /* * stacking stacker is just a stupid idea, so don't ask * the current module to load us. */ printk (KERN_WARNING "Failure registering stacker module " "as primary security module.\n"); return -EINVAL; } stacker_dbg("LSM stacker initialized\n"); return 0; } static void __exit stacker_exit (void) { /* * Since we have no return value, we can't just say no. * Should probably force all child modules to exit somehow... */ unregister_sysfs_files(); if (unregister_security (&stacker_ops)) printk (KERN_WARNING "Error unregistering LSM stacker.\n"); else stacker_dbg("LSM stacker removed.\n"); } security_initcall (stacker_init); module_exit (stacker_exit); MODULE_DESCRIPTION("LSM stacker - supports multiple simultaneous LSM modules"); MODULE_AUTHOR("David A. Wheeler, Serge Hallyn"); MODULE_LICENSE("GPL");