forked from Minki/linux
net: openvswitch: fix possible memleak on destroy flow-table
When we destroy the flow tables which may contain the flow_mask, so release the flow mask struct. Signed-off-by: Tonghao Zhang <xiangxia.m.yue@gmail.com> Tested-by: Greg Rose <gvrose8192@gmail.com> Acked-by: Pravin B Shelar <pshelar@ovn.org> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
0a3e01371d
commit
50b0e61b32
@ -210,6 +210,74 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tbl_mask_array_add_mask(struct flow_table *tbl,
|
||||
struct sw_flow_mask *new)
|
||||
{
|
||||
struct mask_array *ma = ovsl_dereference(tbl->mask_array);
|
||||
int err, ma_count = READ_ONCE(ma->count);
|
||||
|
||||
if (ma_count >= ma->max) {
|
||||
err = tbl_mask_array_realloc(tbl, ma->max +
|
||||
MASK_ARRAY_SIZE_MIN);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
ma = ovsl_dereference(tbl->mask_array);
|
||||
}
|
||||
|
||||
BUG_ON(ovsl_dereference(ma->masks[ma_count]));
|
||||
|
||||
rcu_assign_pointer(ma->masks[ma_count], new);
|
||||
WRITE_ONCE(ma->count, ma_count +1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tbl_mask_array_del_mask(struct flow_table *tbl,
|
||||
struct sw_flow_mask *mask)
|
||||
{
|
||||
struct mask_array *ma = ovsl_dereference(tbl->mask_array);
|
||||
int i, ma_count = READ_ONCE(ma->count);
|
||||
|
||||
/* Remove the deleted mask pointers from the array */
|
||||
for (i = 0; i < ma_count; i++) {
|
||||
if (mask == ovsl_dereference(ma->masks[i]))
|
||||
goto found;
|
||||
}
|
||||
|
||||
BUG();
|
||||
return;
|
||||
|
||||
found:
|
||||
WRITE_ONCE(ma->count, ma_count -1);
|
||||
|
||||
rcu_assign_pointer(ma->masks[i], ma->masks[ma_count -1]);
|
||||
RCU_INIT_POINTER(ma->masks[ma_count -1], NULL);
|
||||
|
||||
kfree_rcu(mask, rcu);
|
||||
|
||||
/* Shrink the mask array if necessary. */
|
||||
if (ma->max >= (MASK_ARRAY_SIZE_MIN * 2) &&
|
||||
ma_count <= (ma->max / 3))
|
||||
tbl_mask_array_realloc(tbl, ma->max / 2);
|
||||
}
|
||||
|
||||
/* Remove 'mask' from the mask list, if it is not needed any more. */
|
||||
static void flow_mask_remove(struct flow_table *tbl, struct sw_flow_mask *mask)
|
||||
{
|
||||
if (mask) {
|
||||
/* ovs-lock is required to protect mask-refcount and
|
||||
* mask list.
|
||||
*/
|
||||
ASSERT_OVSL();
|
||||
BUG_ON(!mask->ref_count);
|
||||
mask->ref_count--;
|
||||
|
||||
if (!mask->ref_count)
|
||||
tbl_mask_array_del_mask(tbl, mask);
|
||||
}
|
||||
}
|
||||
|
||||
int ovs_flow_tbl_init(struct flow_table *table)
|
||||
{
|
||||
struct table_instance *ti, *ufid_ti;
|
||||
@ -257,7 +325,28 @@ static void flow_tbl_destroy_rcu_cb(struct rcu_head *rcu)
|
||||
__table_instance_destroy(ti);
|
||||
}
|
||||
|
||||
static void table_instance_destroy(struct table_instance *ti,
|
||||
static void table_instance_flow_free(struct flow_table *table,
|
||||
struct table_instance *ti,
|
||||
struct table_instance *ufid_ti,
|
||||
struct sw_flow *flow,
|
||||
bool count)
|
||||
{
|
||||
hlist_del_rcu(&flow->flow_table.node[ti->node_ver]);
|
||||
if (count)
|
||||
table->count--;
|
||||
|
||||
if (ovs_identifier_is_ufid(&flow->id)) {
|
||||
hlist_del_rcu(&flow->ufid_table.node[ufid_ti->node_ver]);
|
||||
|
||||
if (count)
|
||||
table->ufid_count--;
|
||||
}
|
||||
|
||||
flow_mask_remove(table, flow->mask);
|
||||
}
|
||||
|
||||
static void table_instance_destroy(struct flow_table *table,
|
||||
struct table_instance *ti,
|
||||
struct table_instance *ufid_ti,
|
||||
bool deferred)
|
||||
{
|
||||
@ -274,13 +363,12 @@ static void table_instance_destroy(struct table_instance *ti,
|
||||
struct sw_flow *flow;
|
||||
struct hlist_head *head = &ti->buckets[i];
|
||||
struct hlist_node *n;
|
||||
int ver = ti->node_ver;
|
||||
int ufid_ver = ufid_ti->node_ver;
|
||||
|
||||
hlist_for_each_entry_safe(flow, n, head, flow_table.node[ver]) {
|
||||
hlist_del_rcu(&flow->flow_table.node[ver]);
|
||||
if (ovs_identifier_is_ufid(&flow->id))
|
||||
hlist_del_rcu(&flow->ufid_table.node[ufid_ver]);
|
||||
hlist_for_each_entry_safe(flow, n, head,
|
||||
flow_table.node[ti->node_ver]) {
|
||||
|
||||
table_instance_flow_free(table, ti, ufid_ti,
|
||||
flow, false);
|
||||
ovs_flow_free(flow, deferred);
|
||||
}
|
||||
}
|
||||
@ -305,7 +393,7 @@ void ovs_flow_tbl_destroy(struct flow_table *table)
|
||||
|
||||
free_percpu(table->mask_cache);
|
||||
kfree_rcu(rcu_dereference_raw(table->mask_array), rcu);
|
||||
table_instance_destroy(ti, ufid_ti, false);
|
||||
table_instance_destroy(table, ti, ufid_ti, false);
|
||||
}
|
||||
|
||||
struct sw_flow *ovs_flow_tbl_dump_next(struct table_instance *ti,
|
||||
@ -421,7 +509,7 @@ int ovs_flow_tbl_flush(struct flow_table *flow_table)
|
||||
flow_table->count = 0;
|
||||
flow_table->ufid_count = 0;
|
||||
|
||||
table_instance_destroy(old_ti, old_ufid_ti, true);
|
||||
table_instance_destroy(flow_table, old_ti, old_ufid_ti, true);
|
||||
return 0;
|
||||
|
||||
err_free_ti:
|
||||
@ -701,51 +789,6 @@ static struct table_instance *table_instance_expand(struct table_instance *ti,
|
||||
return table_instance_rehash(ti, ti->n_buckets * 2, ufid);
|
||||
}
|
||||
|
||||
static void tbl_mask_array_del_mask(struct flow_table *tbl,
|
||||
struct sw_flow_mask *mask)
|
||||
{
|
||||
struct mask_array *ma = ovsl_dereference(tbl->mask_array);
|
||||
int i, ma_count = READ_ONCE(ma->count);
|
||||
|
||||
/* Remove the deleted mask pointers from the array */
|
||||
for (i = 0; i < ma_count; i++) {
|
||||
if (mask == ovsl_dereference(ma->masks[i]))
|
||||
goto found;
|
||||
}
|
||||
|
||||
BUG();
|
||||
return;
|
||||
|
||||
found:
|
||||
WRITE_ONCE(ma->count, ma_count -1);
|
||||
|
||||
rcu_assign_pointer(ma->masks[i], ma->masks[ma_count -1]);
|
||||
RCU_INIT_POINTER(ma->masks[ma_count -1], NULL);
|
||||
|
||||
kfree_rcu(mask, rcu);
|
||||
|
||||
/* Shrink the mask array if necessary. */
|
||||
if (ma->max >= (MASK_ARRAY_SIZE_MIN * 2) &&
|
||||
ma_count <= (ma->max / 3))
|
||||
tbl_mask_array_realloc(tbl, ma->max / 2);
|
||||
}
|
||||
|
||||
/* Remove 'mask' from the mask list, if it is not needed any more. */
|
||||
static void flow_mask_remove(struct flow_table *tbl, struct sw_flow_mask *mask)
|
||||
{
|
||||
if (mask) {
|
||||
/* ovs-lock is required to protect mask-refcount and
|
||||
* mask list.
|
||||
*/
|
||||
ASSERT_OVSL();
|
||||
BUG_ON(!mask->ref_count);
|
||||
mask->ref_count--;
|
||||
|
||||
if (!mask->ref_count)
|
||||
tbl_mask_array_del_mask(tbl, mask);
|
||||
}
|
||||
}
|
||||
|
||||
/* Must be called with OVS mutex held. */
|
||||
void ovs_flow_tbl_remove(struct flow_table *table, struct sw_flow *flow)
|
||||
{
|
||||
@ -753,17 +796,7 @@ void ovs_flow_tbl_remove(struct flow_table *table, struct sw_flow *flow)
|
||||
struct table_instance *ufid_ti = ovsl_dereference(table->ufid_ti);
|
||||
|
||||
BUG_ON(table->count == 0);
|
||||
hlist_del_rcu(&flow->flow_table.node[ti->node_ver]);
|
||||
table->count--;
|
||||
if (ovs_identifier_is_ufid(&flow->id)) {
|
||||
hlist_del_rcu(&flow->ufid_table.node[ufid_ti->node_ver]);
|
||||
table->ufid_count--;
|
||||
}
|
||||
|
||||
/* RCU delete the mask. 'flow->mask' is not NULLed, as it should be
|
||||
* accessible as long as the RCU read lock is held.
|
||||
*/
|
||||
flow_mask_remove(table, flow->mask);
|
||||
table_instance_flow_free(table, ti, ufid_ti, flow, true);
|
||||
}
|
||||
|
||||
static struct sw_flow_mask *mask_alloc(void)
|
||||
@ -806,29 +839,6 @@ static struct sw_flow_mask *flow_mask_find(const struct flow_table *tbl,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int tbl_mask_array_add_mask(struct flow_table *tbl,
|
||||
struct sw_flow_mask *new)
|
||||
{
|
||||
struct mask_array *ma = ovsl_dereference(tbl->mask_array);
|
||||
int err, ma_count = READ_ONCE(ma->count);
|
||||
|
||||
if (ma_count >= ma->max) {
|
||||
err = tbl_mask_array_realloc(tbl, ma->max +
|
||||
MASK_ARRAY_SIZE_MIN);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
ma = ovsl_dereference(tbl->mask_array);
|
||||
}
|
||||
|
||||
BUG_ON(ovsl_dereference(ma->masks[ma_count]));
|
||||
|
||||
rcu_assign_pointer(ma->masks[ma_count], new);
|
||||
WRITE_ONCE(ma->count, ma_count +1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add 'mask' into the mask list, if it is not already there. */
|
||||
static int flow_mask_insert(struct flow_table *tbl, struct sw_flow *flow,
|
||||
const struct sw_flow_mask *new)
|
||||
|
Loading…
Reference in New Issue
Block a user