877 lines · 24.8 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* Copyright (c) 2019 Facebook  */#include <linux/rculist.h>#include <linux/list.h>#include <linux/hash.h>#include <linux/types.h>#include <linux/spinlock.h>#include <linux/bpf.h>#include <linux/btf_ids.h>#include <linux/bpf_local_storage.h>#include <net/sock.h>#include <uapi/linux/sock_diag.h>#include <uapi/linux/btf.h>#include <linux/rcupdate.h>#include <linux/rcupdate_trace.h>#include <linux/rcupdate_wait.h> #define BPF_LOCAL_STORAGE_CREATE_FLAG_MASK (BPF_F_NO_PREALLOC | BPF_F_CLONE) static struct bpf_local_storage_map_bucket *select_bucket(struct bpf_local_storage_map *smap,	      struct bpf_local_storage *local_storage){	return &smap->buckets[hash_ptr(local_storage, smap->bucket_log)];} static int mem_charge(struct bpf_local_storage_map *smap, void *owner, u32 size){	struct bpf_map *map = &smap->map; 	if (!map->ops->map_local_storage_charge)		return 0; 	return map->ops->map_local_storage_charge(smap, owner, size);} static void mem_uncharge(struct bpf_local_storage_map *smap, void *owner,			 u32 size){	struct bpf_map *map = &smap->map; 	if (map->ops->map_local_storage_uncharge)		map->ops->map_local_storage_uncharge(smap, owner, size);} static struct bpf_local_storage __rcu **owner_storage(struct bpf_local_storage_map *smap, void *owner){	struct bpf_map *map = &smap->map; 	return map->ops->map_owner_storage_ptr(owner);} static bool selem_linked_to_storage_lockless(const struct bpf_local_storage_elem *selem){	return !hlist_unhashed_lockless(&selem->snode);} static bool selem_linked_to_storage(const struct bpf_local_storage_elem *selem){	return !hlist_unhashed(&selem->snode);} static bool selem_linked_to_map(const struct bpf_local_storage_elem *selem){	return !hlist_unhashed(&selem->map_node);} struct bpf_local_storage_elem *bpf_selem_alloc(struct bpf_local_storage_map *smap, void *owner,		void *value, bool swap_uptrs){	struct bpf_local_storage_elem *selem; 	if (mem_charge(smap, owner, smap->elem_size))		return NULL; 	selem = bpf_map_kmalloc_nolock(&smap->map, smap->elem_size,				       __GFP_ZERO, NUMA_NO_NODE); 	if (selem) {		RCU_INIT_POINTER(SDATA(selem)->smap, smap);		atomic_set(&selem->state, 0); 		if (value) {			/* No need to call check_and_init_map_value as memory is zero init */			copy_map_value(&smap->map, SDATA(selem)->data, value);			if (swap_uptrs)				bpf_obj_swap_uptrs(smap->map.record, SDATA(selem)->data, value);		}		return selem;	} 	mem_uncharge(smap, owner, smap->elem_size); 	return NULL;} static void bpf_local_storage_free_trace_rcu(struct rcu_head *rcu){	struct bpf_local_storage *local_storage; 	/*	 * RCU Tasks Trace grace period implies RCU grace period, do	 * kfree() directly.	 */	local_storage = container_of(rcu, struct bpf_local_storage, rcu);	kfree(local_storage);} static void bpf_local_storage_free(struct bpf_local_storage *local_storage,				   bool reuse_now){	if (!local_storage)		return; 	if (reuse_now) {		kfree_rcu(local_storage, rcu);		return;	} 	call_rcu_tasks_trace(&local_storage->rcu,			     bpf_local_storage_free_trace_rcu);} static void bpf_selem_free_trace_rcu(struct rcu_head *rcu){	struct bpf_local_storage_elem *selem;	struct bpf_local_storage_map *smap; 	selem = container_of(rcu, struct bpf_local_storage_elem, rcu);	/* The bpf_local_storage_map_free will wait for rcu_barrier */	smap = rcu_dereference_check(SDATA(selem)->smap, 1); 	if (smap)		bpf_obj_free_fields(smap->map.record, SDATA(selem)->data);	/*	 * RCU Tasks Trace grace period implies RCU grace period, do	 * kfree() directly.	 */	kfree(selem);} void bpf_selem_free(struct bpf_local_storage_elem *selem,		    bool reuse_now){	struct bpf_local_storage_map *smap; 	smap = rcu_dereference_check(SDATA(selem)->smap, 1); 	if (reuse_now) {		if (smap)			bpf_obj_free_fields(smap->map.record, SDATA(selem)->data);		kfree_rcu(selem, rcu);		return;	} 	call_rcu_tasks_trace(&selem->rcu, bpf_selem_free_trace_rcu);} static void bpf_selem_free_list(struct hlist_head *list, bool reuse_now){	struct bpf_local_storage_elem *selem;	struct hlist_node *n; 	/* The "_safe" iteration is needed.	 * The loop is not removing the selem from the list	 * but bpf_selem_free will use the selem->rcu_head	 * which is union-ized with the selem->free_node.	 */	hlist_for_each_entry_safe(selem, n, list, free_node)		bpf_selem_free(selem, reuse_now);} static void bpf_selem_unlink_storage_nolock_misc(struct bpf_local_storage_elem *selem,						 struct bpf_local_storage_map *smap,						 struct bpf_local_storage *local_storage,						 bool free_local_storage, bool pin_owner){	void *owner = local_storage->owner;	u32 uncharge = smap->elem_size; 	if (rcu_access_pointer(local_storage->cache[smap->cache_idx]) ==	    SDATA(selem))		RCU_INIT_POINTER(local_storage->cache[smap->cache_idx], NULL); 	if (pin_owner && !refcount_inc_not_zero(&local_storage->owner_refcnt))		return; 	uncharge += free_local_storage ? sizeof(*local_storage) : 0;	mem_uncharge(smap, local_storage->owner, uncharge);	local_storage->mem_charge -= uncharge; 	if (free_local_storage) {		local_storage->owner = NULL; 		/* After this RCU_INIT, owner may be freed and cannot be used */		RCU_INIT_POINTER(*owner_storage(smap, owner), NULL);	} 	if (pin_owner)		refcount_dec(&local_storage->owner_refcnt);} /* local_storage->lock must be held and selem->local_storage == local_storage. * The caller must ensure selem->smap is still valid to be * dereferenced for its smap->elem_size and smap->cache_idx. */static bool bpf_selem_unlink_storage_nolock(struct bpf_local_storage *local_storage,					    struct bpf_local_storage_elem *selem,					    struct hlist_head *free_selem_list){	struct bpf_local_storage_map *smap;	bool free_local_storage; 	smap = rcu_dereference_check(SDATA(selem)->smap, bpf_rcu_lock_held()); 	free_local_storage = hlist_is_singular_node(&selem->snode,						    &local_storage->list); 	bpf_selem_unlink_storage_nolock_misc(selem, smap, local_storage,					     free_local_storage, false); 	hlist_del_init_rcu(&selem->snode); 	hlist_add_head(&selem->free_node, free_selem_list); 	return free_local_storage;} void bpf_selem_link_storage_nolock(struct bpf_local_storage *local_storage,				   struct bpf_local_storage_elem *selem){	struct bpf_local_storage_map *smap; 	smap = rcu_dereference_check(SDATA(selem)->smap, bpf_rcu_lock_held());	local_storage->mem_charge += smap->elem_size; 	RCU_INIT_POINTER(selem->local_storage, local_storage);	hlist_add_head_rcu(&selem->snode, &local_storage->list);} static int bpf_selem_unlink_map(struct bpf_local_storage_elem *selem){	struct bpf_local_storage *local_storage;	struct bpf_local_storage_map *smap;	struct bpf_local_storage_map_bucket *b;	unsigned long flags;	int err; 	local_storage = rcu_dereference_check(selem->local_storage,					      bpf_rcu_lock_held());	smap = rcu_dereference_check(SDATA(selem)->smap, bpf_rcu_lock_held());	b = select_bucket(smap, local_storage);	err = raw_res_spin_lock_irqsave(&b->lock, flags);	if (err)		return err; 	hlist_del_init_rcu(&selem->map_node);	raw_res_spin_unlock_irqrestore(&b->lock, flags); 	return 0;} static void bpf_selem_unlink_map_nolock(struct bpf_local_storage_elem *selem){	hlist_del_init_rcu(&selem->map_node);} int bpf_selem_link_map(struct bpf_local_storage_map *smap,		       struct bpf_local_storage *local_storage,		       struct bpf_local_storage_elem *selem){	struct bpf_local_storage_map_bucket *b;	unsigned long flags;	int err; 	b = select_bucket(smap, local_storage); 	err = raw_res_spin_lock_irqsave(&b->lock, flags);	if (err)		return err; 	hlist_add_head_rcu(&selem->map_node, &b->list);	raw_res_spin_unlock_irqrestore(&b->lock, flags); 	return 0;} static void bpf_selem_link_map_nolock(struct bpf_local_storage_map_bucket *b,				      struct bpf_local_storage_elem *selem){	hlist_add_head_rcu(&selem->map_node, &b->list);} /* * Unlink an selem from map and local storage with lock held. * This is the common path used by local storages to delete an selem. */int bpf_selem_unlink(struct bpf_local_storage_elem *selem){	struct bpf_local_storage *local_storage;	bool free_local_storage = false;	HLIST_HEAD(selem_free_list);	unsigned long flags;	int err; 	if (in_nmi())		return -EOPNOTSUPP; 	if (unlikely(!selem_linked_to_storage_lockless(selem)))		/* selem has already been unlinked from sk */		return 0; 	local_storage = rcu_dereference_check(selem->local_storage,					      bpf_rcu_lock_held()); 	err = raw_res_spin_lock_irqsave(&local_storage->lock, flags);	if (err)		return err; 	if (likely(selem_linked_to_storage(selem))) {		/* Always unlink from map before unlinking from local_storage		 * because selem will be freed after successfully unlinked from		 * the local_storage.		 */		err = bpf_selem_unlink_map(selem);		if (err)			goto out; 		free_local_storage = bpf_selem_unlink_storage_nolock(			local_storage, selem, &selem_free_list);	}out:	raw_res_spin_unlock_irqrestore(&local_storage->lock, flags); 	bpf_selem_free_list(&selem_free_list, false); 	if (free_local_storage)		bpf_local_storage_free(local_storage, false); 	return err;} /* * Unlink an selem from map and local storage with lockless fallback if callers * are racing or rqspinlock returns error. It should only be called by * bpf_local_storage_destroy() or bpf_local_storage_map_free(). */static void bpf_selem_unlink_nofail(struct bpf_local_storage_elem *selem,				    struct bpf_local_storage_map_bucket *b){	bool in_map_free = !!b, free_storage = false;	struct bpf_local_storage *local_storage;	struct bpf_local_storage_map *smap;	unsigned long flags;	int err, unlink = 0; 	local_storage = rcu_dereference_check(selem->local_storage, bpf_rcu_lock_held());	smap = rcu_dereference_check(SDATA(selem)->smap, bpf_rcu_lock_held()); 	if (smap) {		b = b ? : select_bucket(smap, local_storage);		err = raw_res_spin_lock_irqsave(&b->lock, flags);		if (!err) {			/*			 * Call bpf_obj_free_fields() under b->lock to make sure it is done			 * exactly once for an selem. Safe to free special fields immediately			 * as no BPF program should be referencing the selem.			 */			if (likely(selem_linked_to_map(selem))) {				hlist_del_init_rcu(&selem->map_node);				bpf_obj_free_fields(smap->map.record, SDATA(selem)->data);				unlink++;			}			raw_res_spin_unlock_irqrestore(&b->lock, flags);		}		/*		 * Highly unlikely scenario: resource leak		 *		 * When map_free(selem1), destroy(selem1) and destroy(selem2) are racing		 * and both selem belong to the same bucket, if destroy(selem2) acquired		 * b->lock and block for too long, neither map_free(selem1) and		 * destroy(selem1) will be able to free the special field associated		 * with selem1 as raw_res_spin_lock_irqsave() returns -ETIMEDOUT.		 */		WARN_ON_ONCE(err && in_map_free);		if (!err || in_map_free)			RCU_INIT_POINTER(SDATA(selem)->smap, NULL);	} 	if (local_storage) {		err = raw_res_spin_lock_irqsave(&local_storage->lock, flags);		if (!err) {			if (likely(selem_linked_to_storage(selem))) {				free_storage = hlist_is_singular_node(&selem->snode,								      &local_storage->list);				 /*				  * Okay to skip clearing owner_storage and storage->owner in				  * destroy() since the owner is going away. No user or bpf				  * programs should be able to reference it.				  */				if (smap && in_map_free)					bpf_selem_unlink_storage_nolock_misc(						selem, smap, local_storage,						free_storage, true);				hlist_del_init_rcu(&selem->snode);				unlink++;			}			raw_res_spin_unlock_irqrestore(&local_storage->lock, flags);		}		/*		 * Highly unlikely scenario: memory leak		 *		 * When destroy() fails to acqurire local_storage->lock and initializes		 * selem->local_storage to NULL before any racing map_free() sees the same		 * selem, no one will free the local storage.		 */		WARN_ON_ONCE(err && !in_map_free);		if (!err || !in_map_free)			RCU_INIT_POINTER(selem->local_storage, NULL);	} 	if (unlink != 2)		atomic_or(in_map_free ? SELEM_MAP_UNLINKED : SELEM_STORAGE_UNLINKED, &selem->state); 	/*	 * Normally, an selem can be unlinked under local_storage->lock and b->lock, and	 * then freed after an RCU grace period. However, if destroy() and map_free() are	 * racing or rqspinlock returns errors in unlikely situations (unlink != 2), free	 * the selem only after both map_free() and destroy() see the selem.	 */	if (unlink == 2 ||	    atomic_cmpxchg(&selem->state, SELEM_UNLINKED, SELEM_TOFREE) == SELEM_UNLINKED)		bpf_selem_free(selem, true); 	if (free_storage)		bpf_local_storage_free(local_storage, true);} void __bpf_local_storage_insert_cache(struct bpf_local_storage *local_storage,				      struct bpf_local_storage_map *smap,				      struct bpf_local_storage_elem *selem){	unsigned long flags;	int err; 	/* spinlock is needed to avoid racing with the	 * parallel delete.  Otherwise, publishing an already	 * deleted sdata to the cache will become a use-after-free	 * problem in the next bpf_local_storage_lookup().	 */	err = raw_res_spin_lock_irqsave(&local_storage->lock, flags);	if (err)		return; 	if (selem_linked_to_storage(selem))		rcu_assign_pointer(local_storage->cache[smap->cache_idx], SDATA(selem));	raw_res_spin_unlock_irqrestore(&local_storage->lock, flags);} static int check_flags(const struct bpf_local_storage_data *old_sdata,		       u64 map_flags){	if (old_sdata && (map_flags & ~BPF_F_LOCK) == BPF_NOEXIST)		/* elem already exists */		return -EEXIST; 	if (!old_sdata && (map_flags & ~BPF_F_LOCK) == BPF_EXIST)		/* elem doesn't exist, cannot update it */		return -ENOENT; 	return 0;} int bpf_local_storage_alloc(void *owner,			    struct bpf_local_storage_map *smap,			    struct bpf_local_storage_elem *first_selem){	struct bpf_local_storage *prev_storage, *storage;	struct bpf_local_storage **owner_storage_ptr;	struct bpf_local_storage_map_bucket *b;	unsigned long flags;	int err; 	err = mem_charge(smap, owner, sizeof(*storage));	if (err)		return err; 	storage = bpf_map_kmalloc_nolock(&smap->map, sizeof(*storage),					 __GFP_ZERO, NUMA_NO_NODE);	if (!storage) {		err = -ENOMEM;		goto uncharge;	} 	INIT_HLIST_HEAD(&storage->list);	raw_res_spin_lock_init(&storage->lock);	storage->owner = owner;	storage->mem_charge = sizeof(*storage);

Lines 1–500 of 877.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t