// 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. Load more
// 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);