Skip to content

Commit

Permalink
drbd: fix NULL deref in remember_new_state
Browse files Browse the repository at this point in the history
The recent (not yet released) backport of the extended state broadcasts
to support the "events2" subcommand of drbdsetup had some glitches.

remember_old_state() would first count all connections with a
net_conf != NULL, then allocate a suitable array, then populate that
array with all connections found to have net_conf != NULL.

This races with the state change to C_STANDALONE,
and the NULL assignment there.

remember_new_state() then iterates over said connection array,
assuming that it would be fully populated.

But rcu_lock() just makes sure the thing some pointer points to,
if any, won't go away. It does not make the pointer itself immutable.

In fact there is no need to "filter" connections based on whether or not
they have a currently valid configuration.  Just record them always, if
they don't have a config, that's fine, there will be no change then.

Signed-off-by: Philipp Reisner <philipp.reisner@linbit.com>
Signed-off-by: Lars Ellenberg <lars.ellenberg@linbit.com>
Signed-off-by: Jens Axboe <axboe@fb.com>
  • Loading branch information
Lars Ellenberg authored and Jens Axboe committed Nov 25, 2015
1 parent 84d34f2 commit 2b47976
Showing 1 changed file with 14 additions and 32 deletions.
46 changes: 14 additions & 32 deletions drivers/block/drbd/drbd_state.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,8 @@ static void count_objects(struct drbd_resource *resource,

idr_for_each_entry(&resource->devices, device, vnr)
(*n_devices)++;
for_each_connection(connection, resource) {
if (!has_net_conf(connection))
continue;
for_each_connection(connection, resource)
(*n_connections)++;
}
}

static struct drbd_state_change *alloc_state_change(unsigned int n_devices, unsigned int n_connections, gfp_t gfp)
Expand Down Expand Up @@ -108,23 +105,13 @@ struct drbd_state_change *remember_old_state(struct drbd_resource *resource, gfp
struct drbd_peer_device_state_change *peer_device_state_change;
struct drbd_connection_state_change *connection_state_change;

retry:
rcu_read_lock();
/* Caller holds req_lock spinlock.
* No state, no device IDR, no connections lists can change. */
count_objects(resource, &n_devices, &n_connections);
rcu_read_unlock();
state_change = alloc_state_change(n_devices, n_connections, gfp);
if (!state_change)
return NULL;

rcu_read_lock();
count_objects(resource, &n_devices, &n_connections);
if (n_devices != state_change->n_devices ||
n_connections != state_change->n_connections) {
kfree(state_change);
rcu_read_unlock();
goto retry;
}

kref_get(&resource->kref);
state_change->resource->resource = resource;
state_change->resource->role[OLD] =
Expand All @@ -133,6 +120,17 @@ struct drbd_state_change *remember_old_state(struct drbd_resource *resource, gfp
state_change->resource->susp_nod[OLD] = resource->susp_nod;
state_change->resource->susp_fen[OLD] = resource->susp_fen;

connection_state_change = state_change->connections;
for_each_connection(connection, resource) {
kref_get(&connection->kref);
connection_state_change->connection = connection;
connection_state_change->cstate[OLD] =
connection->cstate;
connection_state_change->peer_role[OLD] =
conn_highest_peer(connection);
connection_state_change++;
}

device_state_change = state_change->devices;
peer_device_state_change = state_change->peer_devices;
idr_for_each_entry(&resource->devices, device, vnr) {
Expand All @@ -145,8 +143,6 @@ struct drbd_state_change *remember_old_state(struct drbd_resource *resource, gfp
for_each_connection(connection, resource) {
struct drbd_peer_device *peer_device;

if (!has_net_conf(connection))
continue;
peer_device = conn_peer_device(connection, device->vnr);
peer_device_state_change->peer_device = peer_device;
peer_device_state_change->disk_state[OLD] =
Expand All @@ -165,20 +161,6 @@ struct drbd_state_change *remember_old_state(struct drbd_resource *resource, gfp
device_state_change++;
}

connection_state_change = state_change->connections;
for_each_connection(connection, resource) {
if (!has_net_conf(connection))
continue;
kref_get(&connection->kref);
connection_state_change->connection = connection;
connection_state_change->cstate[OLD] =
connection->cstate;
connection_state_change->peer_role[OLD] =
conn_highest_peer(connection);
connection_state_change++;
}
rcu_read_unlock();

return state_change;
}

Expand Down

0 comments on commit 2b47976

Please sign in to comment.