--- /home/hallyn/bsdjail/bsdjail_saved/bsdjail.c 2004-08-11 15:54:40.000000000 -0500 +++ bsdjail.c 2004-08-30 19:35:25.000000000 -0500 @@ -187,97 +187,16 @@ * structs, defines, and functions to cope with stacking */ -#define BSDJAIL_LSM_ID 0x89 -#define DIGSIG_LSM_ID 0x5A -#define DIRJAIL_LSM_ID 0xD5 -#define DTE_LSM_ID 0x75 +unsigned int my_lsm_id; -/* - * A "general security" structure which hopefully all LSM's will - * use. data points to the data they actually want to store. - * inode->i_security would point to one of these. - */ -struct gen_sec { - unsigned char lsm_id; /* id for a module */ - struct list_head lsm_data; /* list of data per lsm */ - void *data; /* actual data for this module */ -}; - -#define DECLARE_GET_GEN_SECURITY(name,obj_type,field_name) \ -static inline struct gen_sec * \ -get_gen_##name##_security(struct obj_type *x) { \ - struct list_head *tmp; \ - struct gen_sec *g = x->field_name; \ - if (!g) \ - return NULL; \ - if (g->lsm_id == BSDJAIL_LSM_ID) \ - return g; \ - list_for_each(tmp, &g->lsm_data) { \ - struct gen_sec *g2 = list_entry(tmp, struct gen_sec, lsm_data); \ - if (g2->lsm_id == BSDJAIL_LSM_ID) \ - return g2; \ - } \ - return NULL; \ -} - -DECLARE_GET_GEN_SECURITY(task,task_struct,security); /* get_gen_task_security */ -DECLARE_GET_GEN_SECURITY(sock,sock,sk_security); /* get_gen_sock_security */ -DECLARE_GET_GEN_SECURITY(file,file,f_security); /* get_gen_file_security */ -DECLARE_GET_GEN_SECURITY(ipc,kern_ipc_perm,security); /* get_gen_ipc_security */ -DECLARE_GET_GEN_SECURITY(inode,inode,i_security); /* get_gen_inode_security */ - -/* - * return the actual data for an LSM - ie gen_sec->data - */ -#define DECLARE_GET_SECURITY(name,obj_type,field_name) \ -static inline void *get_##name##_security(struct obj_type *x) { \ - struct gen_sec *g = get_gen_##name##_security(x); \ - return g ? g->data : NULL; \ -} - -DECLARE_GET_SECURITY(task,task_struct,security); /* get_task_security */ -DECLARE_GET_SECURITY(sock,sock,sk_security); /* get_sock_security */ -DECLARE_GET_SECURITY(file,file,f_security); /* get_file_security */ -DECLARE_GET_SECURITY(ipc,kern_ipc_perm,security); /* get_ipc_security */ -DECLARE_GET_SECURITY(inode,inode,i_security); /* get_inode_security */ +#define get_task_security(task) (task->security[my_lsm_id]) +#define get_inode_security(inode) (inode->i_security[my_lsm_id]) +#define get_sock_security(sock) (sock->sk_security[my_lsm_id]) +#define get_file_security(file) (file->f_security[my_lsm_id]) +#define get_ipc_security(ipc) (ipc->security[my_lsm_id]) #define jail_of(proc) (get_task_security(proc)) -static inline struct gen_sec *reserve_gen_security(void) -{ - struct gen_sec *s; - s = kmalloc(sizeof(struct gen_sec), GFP_ATOMIC); - if (!s) - return ERR_PTR(-ENOMEM); - INIT_LIST_HEAD(&s->lsm_data); - s->data = NULL; - s->lsm_id = BSDJAIL_LSM_ID; - return s; -} - -/* - * create and link in a gen_sec struct to use for pointing to - * the LSM's actual data - */ -#define DECLARE_RESERVE_SECURITY(name,obj_type,field_name) \ -static struct gen_sec *reserve_##name##_security(struct obj_type *x) { \ - struct gen_sec *g = reserve_gen_security(); \ - if (IS_ERR(g)) \ - return g; \ - if (x->field_name) { \ - struct gen_sec *g2 = x->field_name; \ - list_add(&g->lsm_data, &g2->lsm_data); \ - } else \ - x->field_name = g; \ - return g; \ -} - -DECLARE_RESERVE_SECURITY(task,task_struct,security); /* reserve_task_security */ -DECLARE_RESERVE_SECURITY(sock,sock,sk_security); /* reserve_sock_security */ -DECLARE_RESERVE_SECURITY(file,file,f_security); /* reserve_file_security */ -DECLARE_RESERVE_SECURITY(ipc,kern_ipc_perm,security); /* reserve_ipc_security */ -DECLARE_RESERVE_SECURITY(inode,inode,i_security); /* reserve_inode_security */ - /* * disable_jail: A jail which was in use, but has no references * left, is disabled - we free up the mountpoint and dentry, and @@ -308,22 +227,11 @@ kfree(tsec); } -#define DECLARE_SET_SECURITY(name,obj_type,field_name) \ - static void set_##name##_security(struct obj_type *x, void *data) { \ - struct gen_sec *g; \ - g = get_gen_##name##_security(x); \ - if (!g) \ - g = reserve_##name##_security(x); \ - if (IS_ERR(g)) \ - panic("out of memory\n"); \ - g->data = data; \ - } - -DECLARE_SET_SECURITY(task,task_struct,security); /* set_task_security */ -DECLARE_SET_SECURITY(sock,sock,sk_security); /* set_sock_security */ -DECLARE_SET_SECURITY(file,file,f_security); /* set_file_security */ -DECLARE_SET_SECURITY(ipc,kern_ipc_perm,security); /* set_ipc_security */ -DECLARE_SET_SECURITY(inode,inode,i_security); /* set_inode_security */ +#define set_task_security(task,data) task->security[my_lsm_id] = data +#define set_inode_security(inode,data) inode->i_security[my_lsm_id] = data +#define set_sock_security(sock,data) sock->sk_security[my_lsm_id] = data +#define set_file_security(file,data) file->f_security[my_lsm_id] = data +#define set_ipc_security(ipc,data) ipc.security[my_lsm_id] = data /* * jail_task_free_security: this is the callback hooked into LSM. @@ -334,74 +242,36 @@ */ static void jail_task_free_security(struct task_struct *task) { - struct gen_sec *g; struct jail_struct *tsec; - g = get_gen_task_security(task); - if (!g) - return; - - if (list_empty(&g->lsm_data)) - task->security = NULL; - else - list_del(&g->lsm_data); + tsec = get_task_security(task); - tsec = g->data; if (!tsec) - goto free_g; + return; + if (!in_use(tsec)) { - /* jail allocated but never enabled */ + /* + * someone did 'echo -n x > /proc//attr/exec' but + * then forked before execing. Nuke the old info. + */ free_jail(tsec); - goto free_g; + set_task_security(task,NULL); + return; } tsec->cur_nrtask--; /* If this was the last process in the jail, delete the jail */ kref_put(&tsec->kref); -free_g: - kfree(g); } -/* - * free the gen_sec structs pointing to jail structures, and decrement - * the refcount on the jail structure. If 0, free the jail. - */ -#define DECLARE_FREE_SECURITY(name,obj_type,field_name) \ -static void free_##name##_security(struct obj_type *x) { \ - struct jail_struct *tsec; \ - struct gen_sec *g; \ -\ - g = get_gen_##name##_security(x); \ - if (!g) \ - return; \ -\ - if (list_empty(&g->lsm_data)) \ - x->field_name = NULL; \ - else \ - list_del(&g->lsm_data); \ - tsec = g->data; \ - kref_put(&tsec->kref); \ - kfree(g); \ -} - -DECLARE_FREE_SECURITY(file,file,f_security); /* free_file_security */ -DECLARE_FREE_SECURITY(ipc,kern_ipc_perm,security); /* free_ipc_security */ -DECLARE_FREE_SECURITY(sock,sock,sk_security); /* free_sock_security */ -DECLARE_FREE_SECURITY(inode,inode,i_security); /* free_inode_security */ - static struct jail_struct * alloc_task_security(struct task_struct *tsk) { struct jail_struct *tsec; - struct gen_sec *g; - - g = reserve_task_security(tsk); - if (!g) - return ERR_PTR(-ENOMEM); tsec = kmalloc(sizeof(struct jail_struct), GFP_KERNEL); if (!tsec) return ERR_PTR(-ENOMEM); memset(tsec, 0, sizeof(struct jail_struct)); - g->data = tsec; + set_task_security(tsk, tsec); return tsec; } @@ -440,8 +310,7 @@ bsdj_debug(DBG, "Network set up (%s)\n", tsec->ip_addr_name); } -/* - * release_jail: +/* release_jail: * Callback for kref_put to use for releasing a jail when its * last user exits. */ @@ -748,6 +617,50 @@ return 0; } +static void free_ipc_security(struct kern_ipc_perm *ipc) +{ + struct jail_struct *tsec; + + tsec = get_ipc_security(ipc); + if (!tsec) + return; + kref_put(&tsec->kref); + set_ipc_security((*ipc), NULL); +} + +static void free_file_security(struct file *file) +{ + struct jail_struct *tsec; + + tsec = get_file_security(file); + if (!tsec) + return; + kref_put(&tsec->kref); + set_file_security(file, NULL); +} + +static void free_sock_security(struct sock *sk) +{ + struct jail_struct *tsec; + + tsec = get_sock_security(sk); + if (!tsec) + return; + kref_put(&tsec->kref); + set_sock_security(sk, NULL); +} + +static void free_inode_security(struct inode *inode) +{ + struct jail_struct *tsec; + + tsec = get_inode_security(inode); + if (!tsec) + return; + kref_put(&tsec->kref); + set_inode_security(inode, NULL); +} + /* * LSM ptrace hook: * process in jail may not ptrace process not in the same jail @@ -1042,7 +955,7 @@ unsigned pid; int err = 0; #if 0 - struct task_struct *tsk; + struct task_struct *tsk; #endif pid = name_to_int(nd->dentry); @@ -1233,7 +1146,7 @@ if (!tsec || !in_use(tsec)) return 0; - set_ipc_security(&shp->shm_perm, tsec); + set_ipc_security(shp->shm_perm, tsec); kref_get(&tsec->kref); return 0; } @@ -1272,7 +1185,7 @@ if (!tsec || !in_use(tsec)) return 0; - set_ipc_security(&msq->q_perm, tsec); + set_ipc_security(msq->q_perm, tsec); kref_get(&tsec->kref); return 0; } @@ -1318,7 +1231,7 @@ if (!tsec || !in_use(tsec)) return 0; - set_ipc_security(&sma->sem_perm, tsec); + set_ipc_security(sma->sem_perm, tsec); kref_get(&tsec->kref); return 0; } @@ -1393,143 +1306,6 @@ return 0; } -/* - * Permission to ioctl on an interface. - * Note this is first subject to CAP_NET_ADMIN. - * Allowed if unjailed or unjailed network. - * Allowed if looking at loopback. - * Allowed if lookign at jail's network interface. - */ -static int jail_inet_ioctl (struct in_ifaddr *ifa, unsigned int cmd) -{ - struct jail_struct *tsec = jail_of(current); - - if (!tsec || !in_use(tsec) || !got_network(tsec)) - return 0; - - if (!ifa) - return 0; - - if (ifa->ifa_address == loopbackaddr) - return 0; - - if (ifa->ifa_address == tsec->realaddr) - return 0; - - return -ENODEV; -} - -/* - * Permission to dump an interface's data. - * Called on a specific internet interface (that is, alias). - * Allowed if not in jail or unjailed network. - * Allowed if this is the loopback interface. - * Allowed if this is the specific network inteface for our jail. - */ -static int jail_inet_dumpaddr (struct in_ifaddr *ifa) -{ - struct jail_struct *tsec = jail_of(current); - - if (!tsec || !in_use(tsec) || !got_network(tsec)) - return 0; - - if (!ifa) - return 0; - - if (ifa->ifa_address == loopbackaddr) - return 0; - - if (ifa->ifa_address == tsec->realaddr) - return 0; - - return -ENODEV; -} - -/* - * gifconf: do we permit showing this device in a list of network - * devices? This can't be done on a per-network-alias basis. So - * we can only hide, eg, all of eth1, not hide eth1 and show eth1:0. - * - * For /proc/net/dev that's ok. - * - * For ifconfig -a, we will deny on a per-alias basis out of the - * jail_inet_dumpaddr function. - */ -static int -jail_netdev_viewdev(struct net_device *dev) -{ - struct jail_struct *tsec = jail_of(current); - struct in_device *in_dev; - struct in_ifaddr *ifa; - - if (!tsec || !in_use(tsec) || !got_network(tsec)) - return 0; - - if (!dev) - return 0; - - in_dev = __in_dev_get(dev); - if (!in_dev) - return 0; - - ifa = in_dev->ifa_list; - for (; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_address == loopbackaddr) - return 0; - if (ifa->ifa_address == tsec->realaddr) - return 0; - } - - return -ENODEV; -} - -/* - * netdev_ioctl: perform an ioctl on this device? - * We allow this if the process is unjailed or has no network jail. - * We allow this if it is the loopback device. - * We allow this if it is the device alias which it is authorized to - * use - * We allow it for SIOCGIFFLAGS if the name queried is the device's - * true name - this is only becuase we have to to let ifconfig -a - * work. - */ -static int -jail_netdev_ioctl(struct net_device *dev, char *orig_name, unsigned int cmd) -{ - struct jail_struct *tsec = jail_of(current); - struct in_device *in_dev; - struct in_ifaddr *ifa; - int dev_ok = 0; - - if (!tsec || !in_use(tsec) || !got_network(tsec)) - return 0; - - if (!dev) - return 0; - - in_dev = __in_dev_get(dev); - if (!in_dev) - return 0; - - ifa = in_dev->ifa_list; - for (; ifa; ifa = ifa->ifa_next) { - if (ifa->ifa_address == loopbackaddr) - return 0; - if (ifa->ifa_address == tsec->realaddr) { - if (strcmp(ifa->ifa_label, orig_name)==0) - return 0; - dev_ok = 1; - } - } - - if (dev_ok && (strcmp(dev->name, orig_name)==0) && - (cmd == SIOCGIFFLAGS)) { - return 0; - } - - return -ENODEV; -} - static int jail_socket_unix_stream_connect(struct socket *sock, struct socket *other, struct sock *newsk) @@ -1554,6 +1330,7 @@ .task_free_security = jail_task_free_security, .bprm_alloc_security = jail_bprm_alloc_security, .task_create = jail_security_task_create, + .task_to_inode = jail_task_to_inode, .task_lookup = jail_task_lookup, .task_setrlimit = jail_task_setrlimit, @@ -1570,20 +1347,14 @@ .socket_bind = jail_socket_bind, .socket_listen = jail_socket_listen, .socket_post_create = jail_socket_post_create, - .sk_free_security = free_sock_security, .unix_stream_connect = jail_socket_unix_stream_connect, .unix_may_send = jail_socket_unix_may_send, - .inet_ioctl = jail_inet_ioctl, - .inet_dumpaddr = jail_inet_dumpaddr, #endif - .netdev_viewdev = jail_netdev_viewdev, - .netdev_ioctl = jail_netdev_ioctl, .inode_mknod = jail_inode_mknod, .inode_permission = jail_inode_permission, - .task_to_inode = jail_task_to_inode, .inode_free_security = free_inode_security, - + .sk_free_security = free_sock_security, .sb_mount = jail_mount, .sb_umount = jail_umount, @@ -1608,17 +1379,191 @@ .sem_semop = jail_sem_semop, }; +/* + * networking ioctl ops: + * we insert our own wrapper around the dgram and stream ioctl + * functions, which calls the original ioctl function, then + * butchers the output so as to show only a jail's own network + * address. + */ +extern struct proto_ops inet_stream_ops; +extern struct proto_ops inet_dgram_ops; + +int (*saved_stream_ioctl)(struct socket *sock, unsigned int cmd, + unsigned long arg); +int (*saved_dgram_ioctl)(struct socket *sock, unsigned int cmd, + unsigned long arg); + +int jail_stream_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct ifreq ifr; + struct sockaddr_in *sin; + struct jail_struct *tsec = jail_of(current); + struct ifconf ifc; + char *lastgood, *cur; + int oldlen; + + err = saved_stream_ioctl(sock, cmd, arg); + + if (!tsec || !in_use(tsec) || !got_network(tsec)) + return err; + + switch (cmd) { + case SIOCGIFADDR: + if (copy_from_user(&ifr, (void *)arg, sizeof(struct ifreq))) + return -EFAULT; + sin = (struct sockaddr_in *)&ifr.ifr_addr; + if (sin->sin_family != AF_INET) + return err; + if (sin->sin_addr.s_addr != tsec->realaddr) { + bsdj_debug(WARN, "jail_stream_ioctl DENIED %lu\n", + (unsigned long)sin->sin_addr.s_addr); + memset(&ifr, 0, sizeof(struct ifreq)); + copy_to_user((void *)arg, &ifr, sizeof(struct ifreq)); + return -EFAULT; + } + break; + case SIOCGIFCONF: + + bsdj_debug(DBG, "%s called\n", __FUNCTION__); + if (copy_from_user(&ifc, (void *)arg, sizeof(struct ifconf))) + return -EFAULT; + /* first we figure out how much space we really need */ + lastgood = cur = ifc.ifc_buf; + oldlen = ifc.ifc_len; + ifc.ifc_len = 0; + while (cur < ifc.ifc_buf + oldlen) { + copy_from_user(&ifr, cur, sizeof(struct ifreq)); + sin = (struct sockaddr_in *)&ifr.ifr_addr; + if (sin->sin_family != AF_INET || + sin->sin_addr.s_addr == tsec->realaddr) { + if (lastgood < cur) { + copy_to_user(lastgood, &ifr, + sizeof(struct ifreq)); + } + ifc.ifc_len += sizeof(struct ifreq); + lastgood += sizeof(struct ifreq); + bsdj_debug(DBG, "adding %s\n\n", + ifr.ifr_name); + } else { + bsdj_debug(DBG, "skipping %s\n\n", + ifr.ifr_name); + } + cur += sizeof(struct ifreq); + } + memset(&ifr, 0, sizeof(struct ifreq)); + while (lastgood < ifc.ifc_buf + oldlen) { + copy_to_user(lastgood, &ifr, sizeof(struct ifreq)); + lastgood += sizeof(struct ifreq); + } + copy_to_user((void *)arg, &ifc, sizeof(struct ifconf)); + } + return err; +} + +int jail_dgram_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + int err = 0; + struct ifreq ifr; + struct sockaddr_in *sin; + struct jail_struct *tsec = jail_of(current); + struct ifconf ifc; + char *lastgood, *cur; + int oldlen; + + err = saved_dgram_ioctl(sock, cmd, arg); + + if (!tsec || !in_use(tsec) || !got_network(tsec)) + return err; + + switch (cmd) { + case SIOCGIFADDR: + if (copy_from_user(&ifr, (void *)arg, sizeof(struct ifreq))) + return -EFAULT; + sin = (struct sockaddr_in *)&ifr.ifr_addr; + if (sin->sin_family != AF_INET) + return err; + if (sin->sin_addr.s_addr != tsec->realaddr) { + bsdj_debug(WARN, "jail_dgram_ioctl DENIED %lu\n", + (unsigned long)sin->sin_addr.s_addr); + memset(&ifr, 0, sizeof(struct ifreq)); + copy_to_user((void *)arg, &ifr, sizeof(struct ifreq)); + return -EFAULT; + } + break; + + case SIOCGIFCONF: + bsdj_debug(DBG, "%s called\n", __FUNCTION__); + if (copy_from_user(&ifc, (void *)arg, sizeof(struct ifconf))) + return -EFAULT; + /* first we figure out how much space we really need */ + lastgood = cur = ifc.ifc_buf; + oldlen = ifc.ifc_len; + ifc.ifc_len = 0; + while (cur < ifc.ifc_buf + oldlen) { + copy_from_user(&ifr, cur, sizeof(struct ifreq)); + sin = (struct sockaddr_in *)&ifr.ifr_addr; + if (sin->sin_family != AF_INET || + sin->sin_addr.s_addr == tsec->realaddr) { + if (lastgood < cur) { + copy_to_user(lastgood, &ifr, + sizeof(struct ifreq)); + } + ifc.ifc_len += sizeof(struct ifreq); + lastgood += sizeof(struct ifreq); + bsdj_debug(DBG, "adding %s\n\n", + ifr.ifr_name); + } else { + bsdj_debug(DBG, "skipping %s\n\n", + ifr.ifr_name); + } + cur += sizeof(struct ifreq); + } + memset(&ifr, 0, sizeof(struct ifreq)); + while (lastgood < ifc.ifc_buf + oldlen) { + copy_to_user(lastgood, &ifr, sizeof(struct ifreq)); + lastgood += sizeof(struct ifreq); + } + copy_to_user((void *)arg, &ifc, sizeof(struct ifconf)); + } + return err; +} + +void butcher_inet_ops(void) +{ + lock_kernel(); + saved_stream_ioctl = inet_stream_ops.ioctl; + saved_dgram_ioctl = inet_dgram_ops.ioctl; + inet_stream_ops.ioctl = jail_stream_ioctl; + inet_dgram_ops.ioctl = jail_dgram_ioctl; + unlock_kernel(); +} + +void unbutcher_inet_ops(void) +{ + lock_kernel(); + inet_stream_ops.ioctl = saved_stream_ioctl; + inet_dgram_ops.ioctl = saved_dgram_ioctl; + unlock_kernel(); +} + static int __init bsdjail_init (void) { + butcher_inet_ops(); + my_lsm_id = 0; + if (register_security (&bsdjail_security_ops)) { printk (KERN_INFO "Failure registering BSD Jail module with the kernel\n"); - if (mod_reg_security (MY_NAME, &bsdjail_security_ops)) { + my_lsm_id = mod_reg_security(MY_NAME, &bsdjail_security_ops); + if (my_lsm_id < 0) { printk (KERN_INFO "Failure registering BSD Jail " " module with primary security module.\n"); return -EINVAL; } + printk (KERN_INFO "bsdjail: registered as id %d\n", my_lsm_id); secondary = 1; } printk (KERN_INFO "BSD Jail module initialized.\n"); @@ -1628,6 +1573,8 @@ static void __exit bsdjail_exit (void) { + unbutcher_inet_ops(); + if (secondary) { if (mod_unreg_security (MY_NAME, &bsdjail_security_ops)) printk (KERN_INFO "Failure unregistering BSD Jail "