--- /home/hallyn/bsdjail/bsdjail_saved/bsdjail.c 2004-08-11 15:54:40.000000000 -0500 +++ bsdjail.c 2004-09-01 00:04:55.000000000 -0500 @@ -199,23 +199,18 @@ */ struct gen_sec { unsigned char lsm_id; /* id for a module */ - struct list_head lsm_data; /* list of data per lsm */ + struct hlist_node lsm_next; /* 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; \ + struct hlist_node *tmp; \ + hlist_for_each(tmp, &x->field_name) { \ + struct gen_sec *g = hlist_entry(tmp, struct gen_sec, lsm_next); \ + if (g->lsm_id == BSDJAIL_LSM_ID) \ + return g; \ } \ return NULL; \ } @@ -249,7 +244,7 @@ s = kmalloc(sizeof(struct gen_sec), GFP_ATOMIC); if (!s) return ERR_PTR(-ENOMEM); - INIT_LIST_HEAD(&s->lsm_data); + INIT_HLIST_NODE(&s->lsm_next); s->data = NULL; s->lsm_id = BSDJAIL_LSM_ID; return s; @@ -264,11 +259,7 @@ 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; \ + hlist_add_head(&g->lsm_next, &x->field_name); \ return g; \ } @@ -341,10 +332,7 @@ if (!g) return; - if (list_empty(&g->lsm_data)) - task->security = NULL; - else - list_del(&g->lsm_data); + hlist_del(&g->lsm_next); tsec = g->data; if (!tsec) @@ -374,12 +362,10 @@ if (!g) \ return; \ \ - if (list_empty(&g->lsm_data)) \ - x->field_name = NULL; \ - else \ - list_del(&g->lsm_data); \ + hlist_del(&g->lsm_next); \ tsec = g->data; \ - kref_put(&tsec->kref); \ + if (tsec) \ + kref_put(&tsec->kref); \ kfree(g); \ } @@ -1393,143 +1379,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) @@ -1573,11 +1422,7 @@ .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, @@ -1608,8 +1453,179 @@ .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(); + if (register_security (&bsdjail_security_ops)) { printk (KERN_INFO "Failure registering BSD Jail module with the kernel\n"); @@ -1628,6 +1644,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 "