bonding: fix lock ordering for rtnl and bonding_rwsem
Fix the handling of rtnl and the bonding_rwsem to always be acquired in a consistent order (rtnl, then bonding_rwsem). The existing code sometimes acquired them in this order, and sometimes in the opposite order, which opens a window for deadlock between ifenslave and sysfs. Signed-off-by: Jay Vosburgh <fubar@us.ibm.com> Signed-off-by: Jeff Garzik <jeff@garzik.org>
This commit is contained in:
committed by
Jeff Garzik
parent
ece95f7fef
commit
027ea0416c
@@ -4874,9 +4874,22 @@ static struct lock_class_key bonding_netdev_xmit_lock_key;
|
|||||||
int bond_create(char *name, struct bond_params *params, struct bonding **newbond)
|
int bond_create(char *name, struct bond_params *params, struct bonding **newbond)
|
||||||
{
|
{
|
||||||
struct net_device *bond_dev;
|
struct net_device *bond_dev;
|
||||||
|
struct bonding *bond, *nxt;
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
rtnl_lock();
|
rtnl_lock();
|
||||||
|
down_write(&bonding_rwsem);
|
||||||
|
|
||||||
|
/* Check to see if the bond already exists. */
|
||||||
|
list_for_each_entry_safe(bond, nxt, &bond_dev_list, bond_list)
|
||||||
|
if (strnicmp(bond->dev->name, name, IFNAMSIZ) == 0) {
|
||||||
|
printk(KERN_ERR DRV_NAME
|
||||||
|
": cannot add bond %s; it already exists\n",
|
||||||
|
name);
|
||||||
|
res = -EPERM;
|
||||||
|
goto out_rtnl;
|
||||||
|
}
|
||||||
|
|
||||||
bond_dev = alloc_netdev(sizeof(struct bonding), name ? name : "",
|
bond_dev = alloc_netdev(sizeof(struct bonding), name ? name : "",
|
||||||
ether_setup);
|
ether_setup);
|
||||||
if (!bond_dev) {
|
if (!bond_dev) {
|
||||||
@@ -4915,10 +4928,12 @@ int bond_create(char *name, struct bond_params *params, struct bonding **newbond
|
|||||||
|
|
||||||
netif_carrier_off(bond_dev);
|
netif_carrier_off(bond_dev);
|
||||||
|
|
||||||
|
up_write(&bonding_rwsem);
|
||||||
rtnl_unlock(); /* allows sysfs registration of net device */
|
rtnl_unlock(); /* allows sysfs registration of net device */
|
||||||
res = bond_create_sysfs_entry(bond_dev->priv);
|
res = bond_create_sysfs_entry(bond_dev->priv);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
rtnl_lock();
|
rtnl_lock();
|
||||||
|
down_write(&bonding_rwsem);
|
||||||
goto out_bond;
|
goto out_bond;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4929,6 +4944,7 @@ out_bond:
|
|||||||
out_netdev:
|
out_netdev:
|
||||||
free_netdev(bond_dev);
|
free_netdev(bond_dev);
|
||||||
out_rtnl:
|
out_rtnl:
|
||||||
|
up_write(&bonding_rwsem);
|
||||||
rtnl_unlock();
|
rtnl_unlock();
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -4949,6 +4965,9 @@ static int __init bonding_init(void)
|
|||||||
#ifdef CONFIG_PROC_FS
|
#ifdef CONFIG_PROC_FS
|
||||||
bond_create_proc_dir();
|
bond_create_proc_dir();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
init_rwsem(&bonding_rwsem);
|
||||||
|
|
||||||
for (i = 0; i < max_bonds; i++) {
|
for (i = 0; i < max_bonds; i++) {
|
||||||
res = bond_create(NULL, &bonding_defaults, NULL);
|
res = bond_create(NULL, &bonding_defaults, NULL);
|
||||||
if (res)
|
if (res)
|
||||||
|
@@ -109,11 +109,10 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_t
|
|||||||
{
|
{
|
||||||
char command[IFNAMSIZ + 1] = {0, };
|
char command[IFNAMSIZ + 1] = {0, };
|
||||||
char *ifname;
|
char *ifname;
|
||||||
int res = count;
|
int rv, res = count;
|
||||||
struct bonding *bond;
|
struct bonding *bond;
|
||||||
struct bonding *nxt;
|
struct bonding *nxt;
|
||||||
|
|
||||||
down_write(&(bonding_rwsem));
|
|
||||||
sscanf(buffer, "%16s", command); /* IFNAMSIZ*/
|
sscanf(buffer, "%16s", command); /* IFNAMSIZ*/
|
||||||
ifname = command + 1;
|
ifname = command + 1;
|
||||||
if ((strlen(command) <= 1) ||
|
if ((strlen(command) <= 1) ||
|
||||||
@@ -121,39 +120,28 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_t
|
|||||||
goto err_no_cmd;
|
goto err_no_cmd;
|
||||||
|
|
||||||
if (command[0] == '+') {
|
if (command[0] == '+') {
|
||||||
|
|
||||||
/* Check to see if the bond already exists. */
|
|
||||||
list_for_each_entry_safe(bond, nxt, &bond_dev_list, bond_list)
|
|
||||||
if (strnicmp(bond->dev->name, ifname, IFNAMSIZ) == 0) {
|
|
||||||
printk(KERN_ERR DRV_NAME
|
|
||||||
": cannot add bond %s; it already exists\n",
|
|
||||||
ifname);
|
|
||||||
res = -EPERM;
|
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
|
|
||||||
printk(KERN_INFO DRV_NAME
|
printk(KERN_INFO DRV_NAME
|
||||||
": %s is being created...\n", ifname);
|
": %s is being created...\n", ifname);
|
||||||
if (bond_create(ifname, &bonding_defaults, &bond)) {
|
rv = bond_create(ifname, &bonding_defaults, &bond);
|
||||||
printk(KERN_INFO DRV_NAME
|
if (rv) {
|
||||||
": %s interface already exists. Bond creation failed.\n",
|
printk(KERN_INFO DRV_NAME ": Bond creation failed.\n");
|
||||||
ifname);
|
res = rv;
|
||||||
res = -EPERM;
|
|
||||||
}
|
}
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command[0] == '-') {
|
if (command[0] == '-') {
|
||||||
|
rtnl_lock();
|
||||||
|
down_write(&bonding_rwsem);
|
||||||
|
|
||||||
list_for_each_entry_safe(bond, nxt, &bond_dev_list, bond_list)
|
list_for_each_entry_safe(bond, nxt, &bond_dev_list, bond_list)
|
||||||
if (strnicmp(bond->dev->name, ifname, IFNAMSIZ) == 0) {
|
if (strnicmp(bond->dev->name, ifname, IFNAMSIZ) == 0) {
|
||||||
rtnl_lock();
|
|
||||||
/* check the ref count on the bond's kobject.
|
/* check the ref count on the bond's kobject.
|
||||||
* If it's > expected, then there's a file open,
|
* If it's > expected, then there's a file open,
|
||||||
* and we have to fail.
|
* and we have to fail.
|
||||||
*/
|
*/
|
||||||
if (atomic_read(&bond->dev->dev.kobj.kref.refcount)
|
if (atomic_read(&bond->dev->dev.kobj.kref.refcount)
|
||||||
> expected_refcount){
|
> expected_refcount){
|
||||||
rtnl_unlock();
|
|
||||||
printk(KERN_INFO DRV_NAME
|
printk(KERN_INFO DRV_NAME
|
||||||
": Unable remove bond %s due to open references.\n",
|
": Unable remove bond %s due to open references.\n",
|
||||||
ifname);
|
ifname);
|
||||||
@@ -164,6 +152,7 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_t
|
|||||||
": %s is being deleted...\n",
|
": %s is being deleted...\n",
|
||||||
bond->dev->name);
|
bond->dev->name);
|
||||||
bond_destroy(bond);
|
bond_destroy(bond);
|
||||||
|
up_write(&bonding_rwsem);
|
||||||
rtnl_unlock();
|
rtnl_unlock();
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
@@ -171,6 +160,8 @@ static ssize_t bonding_store_bonds(struct class *cls, const char *buffer, size_t
|
|||||||
printk(KERN_ERR DRV_NAME
|
printk(KERN_ERR DRV_NAME
|
||||||
": unable to delete non-existent bond %s\n", ifname);
|
": unable to delete non-existent bond %s\n", ifname);
|
||||||
res = -ENODEV;
|
res = -ENODEV;
|
||||||
|
up_write(&bonding_rwsem);
|
||||||
|
rtnl_unlock();
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +174,6 @@ err_no_cmd:
|
|||||||
* get called forever, which is bad.
|
* get called forever, which is bad.
|
||||||
*/
|
*/
|
||||||
out:
|
out:
|
||||||
up_write(&(bonding_rwsem));
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
/* class attribute for bond_masters file. This ends up in /sys/class/net */
|
/* class attribute for bond_masters file. This ends up in /sys/class/net */
|
||||||
@@ -271,6 +261,9 @@ static ssize_t bonding_store_slaves(struct device *d,
|
|||||||
|
|
||||||
/* Note: We can't hold bond->lock here, as bond_create grabs it. */
|
/* Note: We can't hold bond->lock here, as bond_create grabs it. */
|
||||||
|
|
||||||
|
rtnl_lock();
|
||||||
|
down_write(&(bonding_rwsem));
|
||||||
|
|
||||||
sscanf(buffer, "%16s", command); /* IFNAMSIZ*/
|
sscanf(buffer, "%16s", command); /* IFNAMSIZ*/
|
||||||
ifname = command + 1;
|
ifname = command + 1;
|
||||||
if ((strlen(command) <= 1) ||
|
if ((strlen(command) <= 1) ||
|
||||||
@@ -336,12 +329,10 @@ static ssize_t bonding_store_slaves(struct device *d,
|
|||||||
dev->mtu = bond->dev->mtu;
|
dev->mtu = bond->dev->mtu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rtnl_lock();
|
|
||||||
res = bond_enslave(bond->dev, dev);
|
res = bond_enslave(bond->dev, dev);
|
||||||
bond_for_each_slave(bond, slave, i)
|
bond_for_each_slave(bond, slave, i)
|
||||||
if (strnicmp(slave->dev->name, ifname, IFNAMSIZ) == 0)
|
if (strnicmp(slave->dev->name, ifname, IFNAMSIZ) == 0)
|
||||||
slave->original_mtu = original_mtu;
|
slave->original_mtu = original_mtu;
|
||||||
rtnl_unlock();
|
|
||||||
if (res) {
|
if (res) {
|
||||||
ret = res;
|
ret = res;
|
||||||
}
|
}
|
||||||
@@ -359,12 +350,10 @@ static ssize_t bonding_store_slaves(struct device *d,
|
|||||||
if (dev) {
|
if (dev) {
|
||||||
printk(KERN_INFO DRV_NAME ": %s: Removing slave %s\n",
|
printk(KERN_INFO DRV_NAME ": %s: Removing slave %s\n",
|
||||||
bond->dev->name, dev->name);
|
bond->dev->name, dev->name);
|
||||||
rtnl_lock();
|
|
||||||
if (bond->setup_by_slave)
|
if (bond->setup_by_slave)
|
||||||
res = bond_release_and_destroy(bond->dev, dev);
|
res = bond_release_and_destroy(bond->dev, dev);
|
||||||
else
|
else
|
||||||
res = bond_release(bond->dev, dev);
|
res = bond_release(bond->dev, dev);
|
||||||
rtnl_unlock();
|
|
||||||
if (res) {
|
if (res) {
|
||||||
ret = res;
|
ret = res;
|
||||||
goto out;
|
goto out;
|
||||||
@@ -389,6 +378,8 @@ err_no_cmd:
|
|||||||
ret = -EPERM;
|
ret = -EPERM;
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
up_write(&(bonding_rwsem));
|
||||||
|
rtnl_unlock();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1423,8 +1414,6 @@ int bond_create_sysfs(void)
|
|||||||
int ret = 0;
|
int ret = 0;
|
||||||
struct bonding *firstbond;
|
struct bonding *firstbond;
|
||||||
|
|
||||||
init_rwsem(&bonding_rwsem);
|
|
||||||
|
|
||||||
/* get the netdev class pointer */
|
/* get the netdev class pointer */
|
||||||
firstbond = container_of(bond_dev_list.next, struct bonding, bond_list);
|
firstbond = container_of(bond_dev_list.next, struct bonding, bond_list);
|
||||||
if (!firstbond)
|
if (!firstbond)
|
||||||
|
Reference in New Issue
Block a user