forked from mirrors/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
					
				
					 1 changed files with 98 additions and 88 deletions
				
			
		|  | @ -210,6 +210,74 @@ static int tbl_mask_array_realloc(struct flow_table *tbl, int size) | ||||||
| 	return 0; | 	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) | int ovs_flow_tbl_init(struct flow_table *table) | ||||||
| { | { | ||||||
| 	struct table_instance *ti, *ufid_ti; | 	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); | 	__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, | 				   struct table_instance *ufid_ti, | ||||||
| 				   bool deferred) | 				   bool deferred) | ||||||
| { | { | ||||||
|  | @ -274,13 +363,12 @@ static void table_instance_destroy(struct table_instance *ti, | ||||||
| 		struct sw_flow *flow; | 		struct sw_flow *flow; | ||||||
| 		struct hlist_head *head = &ti->buckets[i]; | 		struct hlist_head *head = &ti->buckets[i]; | ||||||
| 		struct hlist_node *n; | 		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_for_each_entry_safe(flow, n, head, | ||||||
| 			hlist_del_rcu(&flow->flow_table.node[ver]); | 					  flow_table.node[ti->node_ver]) { | ||||||
| 			if (ovs_identifier_is_ufid(&flow->id)) | 
 | ||||||
| 				hlist_del_rcu(&flow->ufid_table.node[ufid_ver]); | 			table_instance_flow_free(table, ti, ufid_ti, | ||||||
|  | 						 flow, false); | ||||||
| 			ovs_flow_free(flow, deferred); | 			ovs_flow_free(flow, deferred); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -305,7 +393,7 @@ void ovs_flow_tbl_destroy(struct flow_table *table) | ||||||
| 
 | 
 | ||||||
| 	free_percpu(table->mask_cache); | 	free_percpu(table->mask_cache); | ||||||
| 	kfree_rcu(rcu_dereference_raw(table->mask_array), rcu); | 	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, | 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->count = 0; | ||||||
| 	flow_table->ufid_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; | 	return 0; | ||||||
| 
 | 
 | ||||||
| err_free_ti: | 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); | 	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. */ | /* Must be called with OVS mutex held. */ | ||||||
| void ovs_flow_tbl_remove(struct flow_table *table, struct sw_flow *flow) | 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); | 	struct table_instance *ufid_ti = ovsl_dereference(table->ufid_ti); | ||||||
| 
 | 
 | ||||||
| 	BUG_ON(table->count == 0); | 	BUG_ON(table->count == 0); | ||||||
| 	hlist_del_rcu(&flow->flow_table.node[ti->node_ver]); | 	table_instance_flow_free(table, ti, ufid_ti, flow, true); | ||||||
| 	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); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static struct sw_flow_mask *mask_alloc(void) | 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; | 	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. */ | /* 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, | static int flow_mask_insert(struct flow_table *tbl, struct sw_flow *flow, | ||||||
| 			    const struct sw_flow_mask *new) | 			    const struct sw_flow_mask *new) | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue
	
	 Tonghao Zhang
						Tonghao Zhang