Merge remote-tracking branch 'torvalds/master' into perf/core
To pick up BPF changes we'll need. Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
@@ -115,6 +115,8 @@ obj-$(CONFIG_TORTURE_TEST) += torture.o
|
||||
obj-$(CONFIG_HAS_IOMEM) += iomem.o
|
||||
obj-$(CONFIG_RSEQ) += rseq.o
|
||||
|
||||
obj-$(CONFIG_SYSCTL_KUNIT_TEST) += sysctl-test.o
|
||||
|
||||
obj-$(CONFIG_GCC_PLUGIN_STACKLEAK) += stackleak.o
|
||||
KASAN_SANITIZE_stackleak.o := n
|
||||
KCOV_INSTRUMENT_stackleak.o := n
|
||||
|
||||
@@ -6,6 +6,7 @@ obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += disasm.o
|
||||
obj-$(CONFIG_BPF_JIT) += trampoline.o
|
||||
obj-$(CONFIG_BPF_SYSCALL) += btf.o
|
||||
ifeq ($(CONFIG_NET),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += devmap.o
|
||||
|
||||
+251
-12
@@ -14,7 +14,7 @@
|
||||
#include "map_in_map.h"
|
||||
|
||||
#define ARRAY_CREATE_FLAG_MASK \
|
||||
(BPF_F_NUMA_NODE | BPF_F_ACCESS_MASK)
|
||||
(BPF_F_NUMA_NODE | BPF_F_MMAPABLE | BPF_F_ACCESS_MASK)
|
||||
|
||||
static void bpf_array_free_percpu(struct bpf_array *array)
|
||||
{
|
||||
@@ -59,6 +59,10 @@ int array_map_alloc_check(union bpf_attr *attr)
|
||||
(percpu && numa_node != NUMA_NO_NODE))
|
||||
return -EINVAL;
|
||||
|
||||
if (attr->map_type != BPF_MAP_TYPE_ARRAY &&
|
||||
attr->map_flags & BPF_F_MMAPABLE)
|
||||
return -EINVAL;
|
||||
|
||||
if (attr->value_size > KMALLOC_MAX_SIZE)
|
||||
/* if value_size is bigger, the user space won't be able to
|
||||
* access the elements.
|
||||
@@ -102,10 +106,19 @@ static struct bpf_map *array_map_alloc(union bpf_attr *attr)
|
||||
}
|
||||
|
||||
array_size = sizeof(*array);
|
||||
if (percpu)
|
||||
if (percpu) {
|
||||
array_size += (u64) max_entries * sizeof(void *);
|
||||
else
|
||||
array_size += (u64) max_entries * elem_size;
|
||||
} else {
|
||||
/* rely on vmalloc() to return page-aligned memory and
|
||||
* ensure array->value is exactly page-aligned
|
||||
*/
|
||||
if (attr->map_flags & BPF_F_MMAPABLE) {
|
||||
array_size = PAGE_ALIGN(array_size);
|
||||
array_size += PAGE_ALIGN((u64) max_entries * elem_size);
|
||||
} else {
|
||||
array_size += (u64) max_entries * elem_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* make sure there is no u32 overflow later in round_up() */
|
||||
cost = array_size;
|
||||
@@ -117,7 +130,20 @@ static struct bpf_map *array_map_alloc(union bpf_attr *attr)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
/* allocate all map elements and zero-initialize them */
|
||||
array = bpf_map_area_alloc(array_size, numa_node);
|
||||
if (attr->map_flags & BPF_F_MMAPABLE) {
|
||||
void *data;
|
||||
|
||||
/* kmalloc'ed memory can't be mmap'ed, use explicit vmalloc */
|
||||
data = bpf_map_area_mmapable_alloc(array_size, numa_node);
|
||||
if (!data) {
|
||||
bpf_map_charge_finish(&mem);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
array = data + PAGE_ALIGN(sizeof(struct bpf_array))
|
||||
- offsetof(struct bpf_array, value);
|
||||
} else {
|
||||
array = bpf_map_area_alloc(array_size, numa_node);
|
||||
}
|
||||
if (!array) {
|
||||
bpf_map_charge_finish(&mem);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
@@ -350,6 +376,11 @@ static int array_map_delete_elem(struct bpf_map *map, void *key)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void *array_map_vmalloc_addr(struct bpf_array *array)
|
||||
{
|
||||
return (void *)round_down((unsigned long)array, PAGE_SIZE);
|
||||
}
|
||||
|
||||
/* Called when map->refcnt goes to zero, either from workqueue or from syscall */
|
||||
static void array_map_free(struct bpf_map *map)
|
||||
{
|
||||
@@ -365,7 +396,10 @@ static void array_map_free(struct bpf_map *map)
|
||||
if (array->map.map_type == BPF_MAP_TYPE_PERCPU_ARRAY)
|
||||
bpf_array_free_percpu(array);
|
||||
|
||||
bpf_map_area_free(array);
|
||||
if (array->map.map_flags & BPF_F_MMAPABLE)
|
||||
bpf_map_area_free(array_map_vmalloc_addr(array));
|
||||
else
|
||||
bpf_map_area_free(array);
|
||||
}
|
||||
|
||||
static void array_map_seq_show_elem(struct bpf_map *map, void *key,
|
||||
@@ -444,6 +478,17 @@ static int array_map_check_btf(const struct bpf_map *map,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int array_map_mmap(struct bpf_map *map, struct vm_area_struct *vma)
|
||||
{
|
||||
struct bpf_array *array = container_of(map, struct bpf_array, map);
|
||||
pgoff_t pgoff = PAGE_ALIGN(sizeof(*array)) >> PAGE_SHIFT;
|
||||
|
||||
if (!(map->map_flags & BPF_F_MMAPABLE))
|
||||
return -EINVAL;
|
||||
|
||||
return remap_vmalloc_range(vma, array_map_vmalloc_addr(array), pgoff);
|
||||
}
|
||||
|
||||
const struct bpf_map_ops array_map_ops = {
|
||||
.map_alloc_check = array_map_alloc_check,
|
||||
.map_alloc = array_map_alloc,
|
||||
@@ -455,6 +500,7 @@ const struct bpf_map_ops array_map_ops = {
|
||||
.map_gen_lookup = array_map_gen_lookup,
|
||||
.map_direct_value_addr = array_map_direct_value_addr,
|
||||
.map_direct_value_meta = array_map_direct_value_meta,
|
||||
.map_mmap = array_map_mmap,
|
||||
.map_seq_show_elem = array_map_seq_show_elem,
|
||||
.map_check_btf = array_map_check_btf,
|
||||
};
|
||||
@@ -540,10 +586,17 @@ int bpf_fd_array_map_update_elem(struct bpf_map *map, struct file *map_file,
|
||||
if (IS_ERR(new_ptr))
|
||||
return PTR_ERR(new_ptr);
|
||||
|
||||
old_ptr = xchg(array->ptrs + index, new_ptr);
|
||||
if (map->ops->map_poke_run) {
|
||||
mutex_lock(&array->aux->poke_mutex);
|
||||
old_ptr = xchg(array->ptrs + index, new_ptr);
|
||||
map->ops->map_poke_run(map, index, old_ptr, new_ptr);
|
||||
mutex_unlock(&array->aux->poke_mutex);
|
||||
} else {
|
||||
old_ptr = xchg(array->ptrs + index, new_ptr);
|
||||
}
|
||||
|
||||
if (old_ptr)
|
||||
map->ops->map_fd_put_ptr(old_ptr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -556,7 +609,15 @@ static int fd_array_map_delete_elem(struct bpf_map *map, void *key)
|
||||
if (index >= array->map.max_entries)
|
||||
return -E2BIG;
|
||||
|
||||
old_ptr = xchg(array->ptrs + index, NULL);
|
||||
if (map->ops->map_poke_run) {
|
||||
mutex_lock(&array->aux->poke_mutex);
|
||||
old_ptr = xchg(array->ptrs + index, NULL);
|
||||
map->ops->map_poke_run(map, index, old_ptr, NULL);
|
||||
mutex_unlock(&array->aux->poke_mutex);
|
||||
} else {
|
||||
old_ptr = xchg(array->ptrs + index, NULL);
|
||||
}
|
||||
|
||||
if (old_ptr) {
|
||||
map->ops->map_fd_put_ptr(old_ptr);
|
||||
return 0;
|
||||
@@ -625,17 +686,195 @@ static void prog_array_map_seq_show_elem(struct bpf_map *map, void *key,
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
struct prog_poke_elem {
|
||||
struct list_head list;
|
||||
struct bpf_prog_aux *aux;
|
||||
};
|
||||
|
||||
static int prog_array_map_poke_track(struct bpf_map *map,
|
||||
struct bpf_prog_aux *prog_aux)
|
||||
{
|
||||
struct prog_poke_elem *elem;
|
||||
struct bpf_array_aux *aux;
|
||||
int ret = 0;
|
||||
|
||||
aux = container_of(map, struct bpf_array, map)->aux;
|
||||
mutex_lock(&aux->poke_mutex);
|
||||
list_for_each_entry(elem, &aux->poke_progs, list) {
|
||||
if (elem->aux == prog_aux)
|
||||
goto out;
|
||||
}
|
||||
|
||||
elem = kmalloc(sizeof(*elem), GFP_KERNEL);
|
||||
if (!elem) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
INIT_LIST_HEAD(&elem->list);
|
||||
/* We must track the program's aux info at this point in time
|
||||
* since the program pointer itself may not be stable yet, see
|
||||
* also comment in prog_array_map_poke_run().
|
||||
*/
|
||||
elem->aux = prog_aux;
|
||||
|
||||
list_add_tail(&elem->list, &aux->poke_progs);
|
||||
out:
|
||||
mutex_unlock(&aux->poke_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void prog_array_map_poke_untrack(struct bpf_map *map,
|
||||
struct bpf_prog_aux *prog_aux)
|
||||
{
|
||||
struct prog_poke_elem *elem, *tmp;
|
||||
struct bpf_array_aux *aux;
|
||||
|
||||
aux = container_of(map, struct bpf_array, map)->aux;
|
||||
mutex_lock(&aux->poke_mutex);
|
||||
list_for_each_entry_safe(elem, tmp, &aux->poke_progs, list) {
|
||||
if (elem->aux == prog_aux) {
|
||||
list_del_init(&elem->list);
|
||||
kfree(elem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&aux->poke_mutex);
|
||||
}
|
||||
|
||||
static void prog_array_map_poke_run(struct bpf_map *map, u32 key,
|
||||
struct bpf_prog *old,
|
||||
struct bpf_prog *new)
|
||||
{
|
||||
struct prog_poke_elem *elem;
|
||||
struct bpf_array_aux *aux;
|
||||
|
||||
aux = container_of(map, struct bpf_array, map)->aux;
|
||||
WARN_ON_ONCE(!mutex_is_locked(&aux->poke_mutex));
|
||||
|
||||
list_for_each_entry(elem, &aux->poke_progs, list) {
|
||||
struct bpf_jit_poke_descriptor *poke;
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < elem->aux->size_poke_tab; i++) {
|
||||
poke = &elem->aux->poke_tab[i];
|
||||
|
||||
/* Few things to be aware of:
|
||||
*
|
||||
* 1) We can only ever access aux in this context, but
|
||||
* not aux->prog since it might not be stable yet and
|
||||
* there could be danger of use after free otherwise.
|
||||
* 2) Initially when we start tracking aux, the program
|
||||
* is not JITed yet and also does not have a kallsyms
|
||||
* entry. We skip these as poke->ip_stable is not
|
||||
* active yet. The JIT will do the final fixup before
|
||||
* setting it stable. The various poke->ip_stable are
|
||||
* successively activated, so tail call updates can
|
||||
* arrive from here while JIT is still finishing its
|
||||
* final fixup for non-activated poke entries.
|
||||
* 3) On program teardown, the program's kallsym entry gets
|
||||
* removed out of RCU callback, but we can only untrack
|
||||
* from sleepable context, therefore bpf_arch_text_poke()
|
||||
* might not see that this is in BPF text section and
|
||||
* bails out with -EINVAL. As these are unreachable since
|
||||
* RCU grace period already passed, we simply skip them.
|
||||
* 4) Also programs reaching refcount of zero while patching
|
||||
* is in progress is okay since we're protected under
|
||||
* poke_mutex and untrack the programs before the JIT
|
||||
* buffer is freed. When we're still in the middle of
|
||||
* patching and suddenly kallsyms entry of the program
|
||||
* gets evicted, we just skip the rest which is fine due
|
||||
* to point 3).
|
||||
* 5) Any other error happening below from bpf_arch_text_poke()
|
||||
* is a unexpected bug.
|
||||
*/
|
||||
if (!READ_ONCE(poke->ip_stable))
|
||||
continue;
|
||||
if (poke->reason != BPF_POKE_REASON_TAIL_CALL)
|
||||
continue;
|
||||
if (poke->tail_call.map != map ||
|
||||
poke->tail_call.key != key)
|
||||
continue;
|
||||
|
||||
ret = bpf_arch_text_poke(poke->ip, BPF_MOD_JUMP,
|
||||
old ? (u8 *)old->bpf_func +
|
||||
poke->adj_off : NULL,
|
||||
new ? (u8 *)new->bpf_func +
|
||||
poke->adj_off : NULL);
|
||||
BUG_ON(ret < 0 && ret != -EINVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prog_array_map_clear_deferred(struct work_struct *work)
|
||||
{
|
||||
struct bpf_map *map = container_of(work, struct bpf_array_aux,
|
||||
work)->map;
|
||||
bpf_fd_array_map_clear(map);
|
||||
bpf_map_put(map);
|
||||
}
|
||||
|
||||
static void prog_array_map_clear(struct bpf_map *map)
|
||||
{
|
||||
struct bpf_array_aux *aux = container_of(map, struct bpf_array,
|
||||
map)->aux;
|
||||
bpf_map_inc(map);
|
||||
schedule_work(&aux->work);
|
||||
}
|
||||
|
||||
static struct bpf_map *prog_array_map_alloc(union bpf_attr *attr)
|
||||
{
|
||||
struct bpf_array_aux *aux;
|
||||
struct bpf_map *map;
|
||||
|
||||
aux = kzalloc(sizeof(*aux), GFP_KERNEL);
|
||||
if (!aux)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
INIT_WORK(&aux->work, prog_array_map_clear_deferred);
|
||||
INIT_LIST_HEAD(&aux->poke_progs);
|
||||
mutex_init(&aux->poke_mutex);
|
||||
|
||||
map = array_map_alloc(attr);
|
||||
if (IS_ERR(map)) {
|
||||
kfree(aux);
|
||||
return map;
|
||||
}
|
||||
|
||||
container_of(map, struct bpf_array, map)->aux = aux;
|
||||
aux->map = map;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
static void prog_array_map_free(struct bpf_map *map)
|
||||
{
|
||||
struct prog_poke_elem *elem, *tmp;
|
||||
struct bpf_array_aux *aux;
|
||||
|
||||
aux = container_of(map, struct bpf_array, map)->aux;
|
||||
list_for_each_entry_safe(elem, tmp, &aux->poke_progs, list) {
|
||||
list_del_init(&elem->list);
|
||||
kfree(elem);
|
||||
}
|
||||
kfree(aux);
|
||||
fd_array_map_free(map);
|
||||
}
|
||||
|
||||
const struct bpf_map_ops prog_array_map_ops = {
|
||||
.map_alloc_check = fd_array_map_alloc_check,
|
||||
.map_alloc = array_map_alloc,
|
||||
.map_free = fd_array_map_free,
|
||||
.map_alloc = prog_array_map_alloc,
|
||||
.map_free = prog_array_map_free,
|
||||
.map_poke_track = prog_array_map_poke_track,
|
||||
.map_poke_untrack = prog_array_map_poke_untrack,
|
||||
.map_poke_run = prog_array_map_poke_run,
|
||||
.map_get_next_key = array_map_get_next_key,
|
||||
.map_lookup_elem = fd_array_map_lookup_elem,
|
||||
.map_delete_elem = fd_array_map_delete_elem,
|
||||
.map_fd_get_ptr = prog_fd_array_get_ptr,
|
||||
.map_fd_put_ptr = prog_fd_array_put_ptr,
|
||||
.map_fd_sys_lookup_elem = prog_fd_array_sys_lookup_elem,
|
||||
.map_release_uref = bpf_fd_array_map_clear,
|
||||
.map_release_uref = prog_array_map_clear,
|
||||
.map_seq_show_elem = prog_array_map_seq_show_elem,
|
||||
};
|
||||
|
||||
|
||||
+775
-21
@@ -2,6 +2,8 @@
|
||||
/* Copyright (c) 2018 Facebook */
|
||||
|
||||
#include <uapi/linux/btf.h>
|
||||
#include <uapi/linux/bpf.h>
|
||||
#include <uapi/linux/bpf_perf_event.h>
|
||||
#include <uapi/linux/types.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/compiler.h>
|
||||
@@ -16,6 +18,9 @@
|
||||
#include <linux/sort.h>
|
||||
#include <linux/bpf_verifier.h>
|
||||
#include <linux/btf.h>
|
||||
#include <linux/skmsg.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <net/sock.h>
|
||||
|
||||
/* BTF (BPF Type Format) is the meta data format which describes
|
||||
* the data types of BPF program/map. Hence, it basically focus
|
||||
@@ -336,16 +341,6 @@ static bool btf_type_is_fwd(const struct btf_type *t)
|
||||
return BTF_INFO_KIND(t->info) == BTF_KIND_FWD;
|
||||
}
|
||||
|
||||
static bool btf_type_is_func(const struct btf_type *t)
|
||||
{
|
||||
return BTF_INFO_KIND(t->info) == BTF_KIND_FUNC;
|
||||
}
|
||||
|
||||
static bool btf_type_is_func_proto(const struct btf_type *t)
|
||||
{
|
||||
return BTF_INFO_KIND(t->info) == BTF_KIND_FUNC_PROTO;
|
||||
}
|
||||
|
||||
static bool btf_type_nosize(const struct btf_type *t)
|
||||
{
|
||||
return btf_type_is_void(t) || btf_type_is_fwd(t) ||
|
||||
@@ -377,16 +372,6 @@ static bool btf_type_is_array(const struct btf_type *t)
|
||||
return BTF_INFO_KIND(t->info) == BTF_KIND_ARRAY;
|
||||
}
|
||||
|
||||
static bool btf_type_is_ptr(const struct btf_type *t)
|
||||
{
|
||||
return BTF_INFO_KIND(t->info) == BTF_KIND_PTR;
|
||||
}
|
||||
|
||||
static bool btf_type_is_int(const struct btf_type *t)
|
||||
{
|
||||
return BTF_INFO_KIND(t->info) == BTF_KIND_INT;
|
||||
}
|
||||
|
||||
static bool btf_type_is_var(const struct btf_type *t)
|
||||
{
|
||||
return BTF_INFO_KIND(t->info) == BTF_KIND_VAR;
|
||||
@@ -698,6 +683,13 @@ __printf(4, 5) static void __btf_verifier_log_type(struct btf_verifier_env *env,
|
||||
if (!bpf_verifier_log_needed(log))
|
||||
return;
|
||||
|
||||
/* btf verifier prints all types it is processing via
|
||||
* btf_verifier_log_type(..., fmt = NULL).
|
||||
* Skip those prints for in-kernel BTF verification.
|
||||
*/
|
||||
if (log->level == BPF_LOG_KERNEL && !fmt)
|
||||
return;
|
||||
|
||||
__btf_verifier_log(log, "[%u] %s %s%s",
|
||||
env->log_type_id,
|
||||
btf_kind_str[kind],
|
||||
@@ -735,6 +727,8 @@ static void btf_verifier_log_member(struct btf_verifier_env *env,
|
||||
if (!bpf_verifier_log_needed(log))
|
||||
return;
|
||||
|
||||
if (log->level == BPF_LOG_KERNEL && !fmt)
|
||||
return;
|
||||
/* The CHECK_META phase already did a btf dump.
|
||||
*
|
||||
* If member is logged again, it must hit an error in
|
||||
@@ -777,6 +771,8 @@ static void btf_verifier_log_vsi(struct btf_verifier_env *env,
|
||||
|
||||
if (!bpf_verifier_log_needed(log))
|
||||
return;
|
||||
if (log->level == BPF_LOG_KERNEL && !fmt)
|
||||
return;
|
||||
if (env->phase != CHECK_META)
|
||||
btf_verifier_log_type(env, datasec_type, NULL);
|
||||
|
||||
@@ -802,6 +798,8 @@ static void btf_verifier_log_hdr(struct btf_verifier_env *env,
|
||||
if (!bpf_verifier_log_needed(log))
|
||||
return;
|
||||
|
||||
if (log->level == BPF_LOG_KERNEL)
|
||||
return;
|
||||
hdr = &btf->hdr;
|
||||
__btf_verifier_log(log, "magic: 0x%x\n", hdr->magic);
|
||||
__btf_verifier_log(log, "version: %u\n", hdr->version);
|
||||
@@ -1043,6 +1041,82 @@ static const struct resolve_vertex *env_stack_peak(struct btf_verifier_env *env)
|
||||
return env->top_stack ? &env->stack[env->top_stack - 1] : NULL;
|
||||
}
|
||||
|
||||
/* Resolve the size of a passed-in "type"
|
||||
*
|
||||
* type: is an array (e.g. u32 array[x][y])
|
||||
* return type: type "u32[x][y]", i.e. BTF_KIND_ARRAY,
|
||||
* *type_size: (x * y * sizeof(u32)). Hence, *type_size always
|
||||
* corresponds to the return type.
|
||||
* *elem_type: u32
|
||||
* *total_nelems: (x * y). Hence, individual elem size is
|
||||
* (*type_size / *total_nelems)
|
||||
*
|
||||
* type: is not an array (e.g. const struct X)
|
||||
* return type: type "struct X"
|
||||
* *type_size: sizeof(struct X)
|
||||
* *elem_type: same as return type ("struct X")
|
||||
* *total_nelems: 1
|
||||
*/
|
||||
static const struct btf_type *
|
||||
btf_resolve_size(const struct btf *btf, const struct btf_type *type,
|
||||
u32 *type_size, const struct btf_type **elem_type,
|
||||
u32 *total_nelems)
|
||||
{
|
||||
const struct btf_type *array_type = NULL;
|
||||
const struct btf_array *array;
|
||||
u32 i, size, nelems = 1;
|
||||
|
||||
for (i = 0; i < MAX_RESOLVE_DEPTH; i++) {
|
||||
switch (BTF_INFO_KIND(type->info)) {
|
||||
/* type->size can be used */
|
||||
case BTF_KIND_INT:
|
||||
case BTF_KIND_STRUCT:
|
||||
case BTF_KIND_UNION:
|
||||
case BTF_KIND_ENUM:
|
||||
size = type->size;
|
||||
goto resolved;
|
||||
|
||||
case BTF_KIND_PTR:
|
||||
size = sizeof(void *);
|
||||
goto resolved;
|
||||
|
||||
/* Modifiers */
|
||||
case BTF_KIND_TYPEDEF:
|
||||
case BTF_KIND_VOLATILE:
|
||||
case BTF_KIND_CONST:
|
||||
case BTF_KIND_RESTRICT:
|
||||
type = btf_type_by_id(btf, type->type);
|
||||
break;
|
||||
|
||||
case BTF_KIND_ARRAY:
|
||||
if (!array_type)
|
||||
array_type = type;
|
||||
array = btf_type_array(type);
|
||||
if (nelems && array->nelems > U32_MAX / nelems)
|
||||
return ERR_PTR(-EINVAL);
|
||||
nelems *= array->nelems;
|
||||
type = btf_type_by_id(btf, array->type);
|
||||
break;
|
||||
|
||||
/* type without size */
|
||||
default:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
resolved:
|
||||
if (nelems && size > U32_MAX / nelems)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
*type_size = nelems * size;
|
||||
*total_nelems = nelems;
|
||||
*elem_type = type;
|
||||
|
||||
return array_type ? : type;
|
||||
}
|
||||
|
||||
/* The input param "type_id" must point to a needs_resolve type */
|
||||
static const struct btf_type *btf_type_id_resolve(const struct btf *btf,
|
||||
u32 *type_id)
|
||||
@@ -2405,7 +2479,8 @@ static s32 btf_enum_check_meta(struct btf_verifier_env *env,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
||||
if (env->log.level == BPF_LOG_KERNEL)
|
||||
continue;
|
||||
btf_verifier_log(env, "\t%s val=%d\n",
|
||||
__btf_name_by_offset(btf, enums[i].name_off),
|
||||
enums[i].val);
|
||||
@@ -3367,6 +3442,685 @@ errout:
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
extern char __weak _binary__btf_vmlinux_bin_start[];
|
||||
extern char __weak _binary__btf_vmlinux_bin_end[];
|
||||
extern struct btf *btf_vmlinux;
|
||||
|
||||
#define BPF_MAP_TYPE(_id, _ops)
|
||||
static union {
|
||||
struct bpf_ctx_convert {
|
||||
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
|
||||
prog_ctx_type _id##_prog; \
|
||||
kern_ctx_type _id##_kern;
|
||||
#include <linux/bpf_types.h>
|
||||
#undef BPF_PROG_TYPE
|
||||
} *__t;
|
||||
/* 't' is written once under lock. Read many times. */
|
||||
const struct btf_type *t;
|
||||
} bpf_ctx_convert;
|
||||
enum {
|
||||
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
|
||||
__ctx_convert##_id,
|
||||
#include <linux/bpf_types.h>
|
||||
#undef BPF_PROG_TYPE
|
||||
};
|
||||
static u8 bpf_ctx_convert_map[] = {
|
||||
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
|
||||
[_id] = __ctx_convert##_id,
|
||||
#include <linux/bpf_types.h>
|
||||
#undef BPF_PROG_TYPE
|
||||
};
|
||||
#undef BPF_MAP_TYPE
|
||||
|
||||
static const struct btf_member *
|
||||
btf_get_prog_ctx_type(struct bpf_verifier_log *log, struct btf *btf,
|
||||
const struct btf_type *t, enum bpf_prog_type prog_type)
|
||||
{
|
||||
const struct btf_type *conv_struct;
|
||||
const struct btf_type *ctx_struct;
|
||||
const struct btf_member *ctx_type;
|
||||
const char *tname, *ctx_tname;
|
||||
|
||||
conv_struct = bpf_ctx_convert.t;
|
||||
if (!conv_struct) {
|
||||
bpf_log(log, "btf_vmlinux is malformed\n");
|
||||
return NULL;
|
||||
}
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
while (btf_type_is_modifier(t))
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!btf_type_is_struct(t)) {
|
||||
/* Only pointer to struct is supported for now.
|
||||
* That means that BPF_PROG_TYPE_TRACEPOINT with BTF
|
||||
* is not supported yet.
|
||||
* BPF_PROG_TYPE_RAW_TRACEPOINT is fine.
|
||||
*/
|
||||
bpf_log(log, "BPF program ctx type is not a struct\n");
|
||||
return NULL;
|
||||
}
|
||||
tname = btf_name_by_offset(btf, t->name_off);
|
||||
if (!tname) {
|
||||
bpf_log(log, "BPF program ctx struct doesn't have a name\n");
|
||||
return NULL;
|
||||
}
|
||||
/* prog_type is valid bpf program type. No need for bounds check. */
|
||||
ctx_type = btf_type_member(conv_struct) + bpf_ctx_convert_map[prog_type] * 2;
|
||||
/* ctx_struct is a pointer to prog_ctx_type in vmlinux.
|
||||
* Like 'struct __sk_buff'
|
||||
*/
|
||||
ctx_struct = btf_type_by_id(btf_vmlinux, ctx_type->type);
|
||||
if (!ctx_struct)
|
||||
/* should not happen */
|
||||
return NULL;
|
||||
ctx_tname = btf_name_by_offset(btf_vmlinux, ctx_struct->name_off);
|
||||
if (!ctx_tname) {
|
||||
/* should not happen */
|
||||
bpf_log(log, "Please fix kernel include/linux/bpf_types.h\n");
|
||||
return NULL;
|
||||
}
|
||||
/* only compare that prog's ctx type name is the same as
|
||||
* kernel expects. No need to compare field by field.
|
||||
* It's ok for bpf prog to do:
|
||||
* struct __sk_buff {};
|
||||
* int socket_filter_bpf_prog(struct __sk_buff *skb)
|
||||
* { // no fields of skb are ever used }
|
||||
*/
|
||||
if (strcmp(ctx_tname, tname))
|
||||
return NULL;
|
||||
return ctx_type;
|
||||
}
|
||||
|
||||
static int btf_translate_to_vmlinux(struct bpf_verifier_log *log,
|
||||
struct btf *btf,
|
||||
const struct btf_type *t,
|
||||
enum bpf_prog_type prog_type)
|
||||
{
|
||||
const struct btf_member *prog_ctx_type, *kern_ctx_type;
|
||||
|
||||
prog_ctx_type = btf_get_prog_ctx_type(log, btf, t, prog_type);
|
||||
if (!prog_ctx_type)
|
||||
return -ENOENT;
|
||||
kern_ctx_type = prog_ctx_type + 1;
|
||||
return kern_ctx_type->type;
|
||||
}
|
||||
|
||||
struct btf *btf_parse_vmlinux(void)
|
||||
{
|
||||
struct btf_verifier_env *env = NULL;
|
||||
struct bpf_verifier_log *log;
|
||||
struct btf *btf = NULL;
|
||||
int err, i;
|
||||
|
||||
env = kzalloc(sizeof(*env), GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!env)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
log = &env->log;
|
||||
log->level = BPF_LOG_KERNEL;
|
||||
|
||||
btf = kzalloc(sizeof(*btf), GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!btf) {
|
||||
err = -ENOMEM;
|
||||
goto errout;
|
||||
}
|
||||
env->btf = btf;
|
||||
|
||||
btf->data = _binary__btf_vmlinux_bin_start;
|
||||
btf->data_size = _binary__btf_vmlinux_bin_end -
|
||||
_binary__btf_vmlinux_bin_start;
|
||||
|
||||
err = btf_parse_hdr(env);
|
||||
if (err)
|
||||
goto errout;
|
||||
|
||||
btf->nohdr_data = btf->data + btf->hdr.hdr_len;
|
||||
|
||||
err = btf_parse_str_sec(env);
|
||||
if (err)
|
||||
goto errout;
|
||||
|
||||
err = btf_check_all_metas(env);
|
||||
if (err)
|
||||
goto errout;
|
||||
|
||||
/* find struct bpf_ctx_convert for type checking later */
|
||||
for (i = 1; i <= btf->nr_types; i++) {
|
||||
const struct btf_type *t;
|
||||
const char *tname;
|
||||
|
||||
t = btf_type_by_id(btf, i);
|
||||
if (!__btf_type_is_struct(t))
|
||||
continue;
|
||||
tname = __btf_name_by_offset(btf, t->name_off);
|
||||
if (!strcmp(tname, "bpf_ctx_convert")) {
|
||||
/* btf_parse_vmlinux() runs under bpf_verifier_lock */
|
||||
bpf_ctx_convert.t = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i > btf->nr_types) {
|
||||
err = -ENOENT;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
btf_verifier_env_free(env);
|
||||
refcount_set(&btf->refcnt, 1);
|
||||
return btf;
|
||||
|
||||
errout:
|
||||
btf_verifier_env_free(env);
|
||||
if (btf) {
|
||||
kvfree(btf->types);
|
||||
kfree(btf);
|
||||
}
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
struct btf *bpf_prog_get_target_btf(const struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_prog *tgt_prog = prog->aux->linked_prog;
|
||||
|
||||
if (tgt_prog) {
|
||||
return tgt_prog->aux->btf;
|
||||
} else {
|
||||
return btf_vmlinux;
|
||||
}
|
||||
}
|
||||
|
||||
bool btf_ctx_access(int off, int size, enum bpf_access_type type,
|
||||
const struct bpf_prog *prog,
|
||||
struct bpf_insn_access_aux *info)
|
||||
{
|
||||
const struct btf_type *t = prog->aux->attach_func_proto;
|
||||
struct bpf_prog *tgt_prog = prog->aux->linked_prog;
|
||||
struct btf *btf = bpf_prog_get_target_btf(prog);
|
||||
const char *tname = prog->aux->attach_func_name;
|
||||
struct bpf_verifier_log *log = info->log;
|
||||
const struct btf_param *args;
|
||||
u32 nr_args, arg;
|
||||
int ret;
|
||||
|
||||
if (off % 8) {
|
||||
bpf_log(log, "func '%s' offset %d is not multiple of 8\n",
|
||||
tname, off);
|
||||
return false;
|
||||
}
|
||||
arg = off / 8;
|
||||
args = (const struct btf_param *)(t + 1);
|
||||
/* if (t == NULL) Fall back to default BPF prog with 5 u64 arguments */
|
||||
nr_args = t ? btf_type_vlen(t) : 5;
|
||||
if (prog->aux->attach_btf_trace) {
|
||||
/* skip first 'void *__data' argument in btf_trace_##name typedef */
|
||||
args++;
|
||||
nr_args--;
|
||||
}
|
||||
|
||||
if (prog->expected_attach_type == BPF_TRACE_FEXIT &&
|
||||
arg == nr_args) {
|
||||
if (!t)
|
||||
/* Default prog with 5 args. 6th arg is retval. */
|
||||
return true;
|
||||
/* function return type */
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
} else if (arg >= nr_args) {
|
||||
bpf_log(log, "func '%s' doesn't have %d-th argument\n",
|
||||
tname, arg + 1);
|
||||
return false;
|
||||
} else {
|
||||
if (!t)
|
||||
/* Default prog with 5 args */
|
||||
return true;
|
||||
t = btf_type_by_id(btf, args[arg].type);
|
||||
}
|
||||
/* skip modifiers */
|
||||
while (btf_type_is_modifier(t))
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (btf_type_is_int(t))
|
||||
/* accessing a scalar */
|
||||
return true;
|
||||
if (!btf_type_is_ptr(t)) {
|
||||
bpf_log(log,
|
||||
"func '%s' arg%d '%s' has type %s. Only pointer access is allowed\n",
|
||||
tname, arg,
|
||||
__btf_name_by_offset(btf, t->name_off),
|
||||
btf_kind_str[BTF_INFO_KIND(t->info)]);
|
||||
return false;
|
||||
}
|
||||
if (t->type == 0)
|
||||
/* This is a pointer to void.
|
||||
* It is the same as scalar from the verifier safety pov.
|
||||
* No further pointer walking is allowed.
|
||||
*/
|
||||
return true;
|
||||
|
||||
/* this is a pointer to another type */
|
||||
info->reg_type = PTR_TO_BTF_ID;
|
||||
info->btf_id = t->type;
|
||||
|
||||
if (tgt_prog) {
|
||||
ret = btf_translate_to_vmlinux(log, btf, t, tgt_prog->type);
|
||||
if (ret > 0) {
|
||||
info->btf_id = ret;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
/* skip modifiers */
|
||||
while (btf_type_is_modifier(t))
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!btf_type_is_struct(t)) {
|
||||
bpf_log(log,
|
||||
"func '%s' arg%d type %s is not a struct\n",
|
||||
tname, arg, btf_kind_str[BTF_INFO_KIND(t->info)]);
|
||||
return false;
|
||||
}
|
||||
bpf_log(log, "func '%s' arg%d has btf_id %d type %s '%s'\n",
|
||||
tname, arg, info->btf_id, btf_kind_str[BTF_INFO_KIND(t->info)],
|
||||
__btf_name_by_offset(btf, t->name_off));
|
||||
return true;
|
||||
}
|
||||
|
||||
int btf_struct_access(struct bpf_verifier_log *log,
|
||||
const struct btf_type *t, int off, int size,
|
||||
enum bpf_access_type atype,
|
||||
u32 *next_btf_id)
|
||||
{
|
||||
u32 i, moff, mtrue_end, msize = 0, total_nelems = 0;
|
||||
const struct btf_type *mtype, *elem_type = NULL;
|
||||
const struct btf_member *member;
|
||||
const char *tname, *mname;
|
||||
|
||||
again:
|
||||
tname = __btf_name_by_offset(btf_vmlinux, t->name_off);
|
||||
if (!btf_type_is_struct(t)) {
|
||||
bpf_log(log, "Type '%s' is not a struct", tname);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for_each_member(i, t, member) {
|
||||
if (btf_member_bitfield_size(t, member))
|
||||
/* bitfields are not supported yet */
|
||||
continue;
|
||||
|
||||
/* offset of the field in bytes */
|
||||
moff = btf_member_bit_offset(t, member) / 8;
|
||||
if (off + size <= moff)
|
||||
/* won't find anything, field is already too far */
|
||||
break;
|
||||
/* In case of "off" is pointing to holes of a struct */
|
||||
if (off < moff)
|
||||
continue;
|
||||
|
||||
/* type of the field */
|
||||
mtype = btf_type_by_id(btf_vmlinux, member->type);
|
||||
mname = __btf_name_by_offset(btf_vmlinux, member->name_off);
|
||||
|
||||
mtype = btf_resolve_size(btf_vmlinux, mtype, &msize,
|
||||
&elem_type, &total_nelems);
|
||||
if (IS_ERR(mtype)) {
|
||||
bpf_log(log, "field %s doesn't have size\n", mname);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
mtrue_end = moff + msize;
|
||||
if (off >= mtrue_end)
|
||||
/* no overlap with member, keep iterating */
|
||||
continue;
|
||||
|
||||
if (btf_type_is_array(mtype)) {
|
||||
u32 elem_idx;
|
||||
|
||||
/* btf_resolve_size() above helps to
|
||||
* linearize a multi-dimensional array.
|
||||
*
|
||||
* The logic here is treating an array
|
||||
* in a struct as the following way:
|
||||
*
|
||||
* struct outer {
|
||||
* struct inner array[2][2];
|
||||
* };
|
||||
*
|
||||
* looks like:
|
||||
*
|
||||
* struct outer {
|
||||
* struct inner array_elem0;
|
||||
* struct inner array_elem1;
|
||||
* struct inner array_elem2;
|
||||
* struct inner array_elem3;
|
||||
* };
|
||||
*
|
||||
* When accessing outer->array[1][0], it moves
|
||||
* moff to "array_elem2", set mtype to
|
||||
* "struct inner", and msize also becomes
|
||||
* sizeof(struct inner). Then most of the
|
||||
* remaining logic will fall through without
|
||||
* caring the current member is an array or
|
||||
* not.
|
||||
*
|
||||
* Unlike mtype/msize/moff, mtrue_end does not
|
||||
* change. The naming difference ("_true") tells
|
||||
* that it is not always corresponding to
|
||||
* the current mtype/msize/moff.
|
||||
* It is the true end of the current
|
||||
* member (i.e. array in this case). That
|
||||
* will allow an int array to be accessed like
|
||||
* a scratch space,
|
||||
* i.e. allow access beyond the size of
|
||||
* the array's element as long as it is
|
||||
* within the mtrue_end boundary.
|
||||
*/
|
||||
|
||||
/* skip empty array */
|
||||
if (moff == mtrue_end)
|
||||
continue;
|
||||
|
||||
msize /= total_nelems;
|
||||
elem_idx = (off - moff) / msize;
|
||||
moff += elem_idx * msize;
|
||||
mtype = elem_type;
|
||||
}
|
||||
|
||||
/* the 'off' we're looking for is either equal to start
|
||||
* of this field or inside of this struct
|
||||
*/
|
||||
if (btf_type_is_struct(mtype)) {
|
||||
/* our field must be inside that union or struct */
|
||||
t = mtype;
|
||||
|
||||
/* adjust offset we're looking for */
|
||||
off -= moff;
|
||||
goto again;
|
||||
}
|
||||
|
||||
if (btf_type_is_ptr(mtype)) {
|
||||
const struct btf_type *stype;
|
||||
|
||||
if (msize != size || off != moff) {
|
||||
bpf_log(log,
|
||||
"cannot access ptr member %s with moff %u in struct %s with off %u size %u\n",
|
||||
mname, moff, tname, off, size);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
stype = btf_type_by_id(btf_vmlinux, mtype->type);
|
||||
/* skip modifiers */
|
||||
while (btf_type_is_modifier(stype))
|
||||
stype = btf_type_by_id(btf_vmlinux, stype->type);
|
||||
if (btf_type_is_struct(stype)) {
|
||||
*next_btf_id = mtype->type;
|
||||
return PTR_TO_BTF_ID;
|
||||
}
|
||||
}
|
||||
|
||||
/* Allow more flexible access within an int as long as
|
||||
* it is within mtrue_end.
|
||||
* Since mtrue_end could be the end of an array,
|
||||
* that also allows using an array of int as a scratch
|
||||
* space. e.g. skb->cb[].
|
||||
*/
|
||||
if (off + size > mtrue_end) {
|
||||
bpf_log(log,
|
||||
"access beyond the end of member %s (mend:%u) in struct %s with off %u size %u\n",
|
||||
mname, mtrue_end, tname, off, size);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
return SCALAR_VALUE;
|
||||
}
|
||||
bpf_log(log, "struct %s doesn't have field at offset %d\n", tname, off);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int __btf_resolve_helper_id(struct bpf_verifier_log *log, void *fn,
|
||||
int arg)
|
||||
{
|
||||
char fnname[KSYM_SYMBOL_LEN + 4] = "btf_";
|
||||
const struct btf_param *args;
|
||||
const struct btf_type *t;
|
||||
const char *tname, *sym;
|
||||
u32 btf_id, i;
|
||||
|
||||
if (IS_ERR(btf_vmlinux)) {
|
||||
bpf_log(log, "btf_vmlinux is malformed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
sym = kallsyms_lookup((long)fn, NULL, NULL, NULL, fnname + 4);
|
||||
if (!sym) {
|
||||
bpf_log(log, "kernel doesn't have kallsyms\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
for (i = 1; i <= btf_vmlinux->nr_types; i++) {
|
||||
t = btf_type_by_id(btf_vmlinux, i);
|
||||
if (BTF_INFO_KIND(t->info) != BTF_KIND_TYPEDEF)
|
||||
continue;
|
||||
tname = __btf_name_by_offset(btf_vmlinux, t->name_off);
|
||||
if (!strcmp(tname, fnname))
|
||||
break;
|
||||
}
|
||||
if (i > btf_vmlinux->nr_types) {
|
||||
bpf_log(log, "helper %s type is not found\n", fnname);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
t = btf_type_by_id(btf_vmlinux, t->type);
|
||||
if (!btf_type_is_ptr(t))
|
||||
return -EFAULT;
|
||||
t = btf_type_by_id(btf_vmlinux, t->type);
|
||||
if (!btf_type_is_func_proto(t))
|
||||
return -EFAULT;
|
||||
|
||||
args = (const struct btf_param *)(t + 1);
|
||||
if (arg >= btf_type_vlen(t)) {
|
||||
bpf_log(log, "bpf helper %s doesn't have %d-th argument\n",
|
||||
fnname, arg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
t = btf_type_by_id(btf_vmlinux, args[arg].type);
|
||||
if (!btf_type_is_ptr(t) || !t->type) {
|
||||
/* anything but the pointer to struct is a helper config bug */
|
||||
bpf_log(log, "ARG_PTR_TO_BTF is misconfigured\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
btf_id = t->type;
|
||||
t = btf_type_by_id(btf_vmlinux, t->type);
|
||||
/* skip modifiers */
|
||||
while (btf_type_is_modifier(t)) {
|
||||
btf_id = t->type;
|
||||
t = btf_type_by_id(btf_vmlinux, t->type);
|
||||
}
|
||||
if (!btf_type_is_struct(t)) {
|
||||
bpf_log(log, "ARG_PTR_TO_BTF is not a struct\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
bpf_log(log, "helper %s arg%d has btf_id %d struct %s\n", fnname + 4,
|
||||
arg, btf_id, __btf_name_by_offset(btf_vmlinux, t->name_off));
|
||||
return btf_id;
|
||||
}
|
||||
|
||||
int btf_resolve_helper_id(struct bpf_verifier_log *log,
|
||||
const struct bpf_func_proto *fn, int arg)
|
||||
{
|
||||
int *btf_id = &fn->btf_id[arg];
|
||||
int ret;
|
||||
|
||||
if (fn->arg_type[arg] != ARG_PTR_TO_BTF_ID)
|
||||
return -EINVAL;
|
||||
|
||||
ret = READ_ONCE(*btf_id);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* ok to race the search. The result is the same */
|
||||
ret = __btf_resolve_helper_id(log, fn->func, arg);
|
||||
if (!ret) {
|
||||
/* Function argument cannot be type 'void' */
|
||||
bpf_log(log, "BTF resolution bug\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
WRITE_ONCE(*btf_id, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __get_type_size(struct btf *btf, u32 btf_id,
|
||||
const struct btf_type **bad_type)
|
||||
{
|
||||
const struct btf_type *t;
|
||||
|
||||
if (!btf_id)
|
||||
/* void */
|
||||
return 0;
|
||||
t = btf_type_by_id(btf, btf_id);
|
||||
while (t && btf_type_is_modifier(t))
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!t)
|
||||
return -EINVAL;
|
||||
if (btf_type_is_ptr(t))
|
||||
/* kernel size of pointer. Not BPF's size of pointer*/
|
||||
return sizeof(void *);
|
||||
if (btf_type_is_int(t) || btf_type_is_enum(t))
|
||||
return t->size;
|
||||
*bad_type = t;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
int btf_distill_func_proto(struct bpf_verifier_log *log,
|
||||
struct btf *btf,
|
||||
const struct btf_type *func,
|
||||
const char *tname,
|
||||
struct btf_func_model *m)
|
||||
{
|
||||
const struct btf_param *args;
|
||||
const struct btf_type *t;
|
||||
u32 i, nargs;
|
||||
int ret;
|
||||
|
||||
if (!func) {
|
||||
/* BTF function prototype doesn't match the verifier types.
|
||||
* Fall back to 5 u64 args.
|
||||
*/
|
||||
for (i = 0; i < 5; i++)
|
||||
m->arg_size[i] = 8;
|
||||
m->ret_size = 8;
|
||||
m->nr_args = 5;
|
||||
return 0;
|
||||
}
|
||||
args = (const struct btf_param *)(func + 1);
|
||||
nargs = btf_type_vlen(func);
|
||||
if (nargs >= MAX_BPF_FUNC_ARGS) {
|
||||
bpf_log(log,
|
||||
"The function %s has %d arguments. Too many.\n",
|
||||
tname, nargs);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = __get_type_size(btf, func->type, &t);
|
||||
if (ret < 0) {
|
||||
bpf_log(log,
|
||||
"The function %s return type %s is unsupported.\n",
|
||||
tname, btf_kind_str[BTF_INFO_KIND(t->info)]);
|
||||
return -EINVAL;
|
||||
}
|
||||
m->ret_size = ret;
|
||||
|
||||
for (i = 0; i < nargs; i++) {
|
||||
ret = __get_type_size(btf, args[i].type, &t);
|
||||
if (ret < 0) {
|
||||
bpf_log(log,
|
||||
"The function %s arg%d type %s is unsupported.\n",
|
||||
tname, i, btf_kind_str[BTF_INFO_KIND(t->info)]);
|
||||
return -EINVAL;
|
||||
}
|
||||
m->arg_size[i] = ret;
|
||||
}
|
||||
m->nr_args = nargs;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int btf_check_func_arg_match(struct bpf_verifier_env *env, int subprog)
|
||||
{
|
||||
struct bpf_verifier_state *st = env->cur_state;
|
||||
struct bpf_func_state *func = st->frame[st->curframe];
|
||||
struct bpf_reg_state *reg = func->regs;
|
||||
struct bpf_verifier_log *log = &env->log;
|
||||
struct bpf_prog *prog = env->prog;
|
||||
struct btf *btf = prog->aux->btf;
|
||||
const struct btf_param *args;
|
||||
const struct btf_type *t;
|
||||
u32 i, nargs, btf_id;
|
||||
const char *tname;
|
||||
|
||||
if (!prog->aux->func_info)
|
||||
return 0;
|
||||
|
||||
btf_id = prog->aux->func_info[subprog].type_id;
|
||||
if (!btf_id)
|
||||
return 0;
|
||||
|
||||
if (prog->aux->func_info_aux[subprog].unreliable)
|
||||
return 0;
|
||||
|
||||
t = btf_type_by_id(btf, btf_id);
|
||||
if (!t || !btf_type_is_func(t)) {
|
||||
bpf_log(log, "BTF of subprog %d doesn't point to KIND_FUNC\n",
|
||||
subprog);
|
||||
return -EINVAL;
|
||||
}
|
||||
tname = btf_name_by_offset(btf, t->name_off);
|
||||
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!t || !btf_type_is_func_proto(t)) {
|
||||
bpf_log(log, "Invalid type of func %s\n", tname);
|
||||
return -EINVAL;
|
||||
}
|
||||
args = (const struct btf_param *)(t + 1);
|
||||
nargs = btf_type_vlen(t);
|
||||
if (nargs > 5) {
|
||||
bpf_log(log, "Function %s has %d > 5 args\n", tname, nargs);
|
||||
goto out;
|
||||
}
|
||||
/* check that BTF function arguments match actual types that the
|
||||
* verifier sees.
|
||||
*/
|
||||
for (i = 0; i < nargs; i++) {
|
||||
t = btf_type_by_id(btf, args[i].type);
|
||||
while (btf_type_is_modifier(t))
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (btf_type_is_int(t) || btf_type_is_enum(t)) {
|
||||
if (reg[i + 1].type == SCALAR_VALUE)
|
||||
continue;
|
||||
bpf_log(log, "R%d is not a scalar\n", i + 1);
|
||||
goto out;
|
||||
}
|
||||
if (btf_type_is_ptr(t)) {
|
||||
if (reg[i + 1].type == SCALAR_VALUE) {
|
||||
bpf_log(log, "R%d is not a pointer\n", i + 1);
|
||||
goto out;
|
||||
}
|
||||
/* If program is passing PTR_TO_CTX into subprogram
|
||||
* check that BTF type matches.
|
||||
*/
|
||||
if (reg[i + 1].type == PTR_TO_CTX &&
|
||||
!btf_get_prog_ctx_type(log, btf, t, prog->type))
|
||||
goto out;
|
||||
/* All other pointers are ok */
|
||||
continue;
|
||||
}
|
||||
bpf_log(log, "Unrecognized argument type %s\n",
|
||||
btf_kind_str[BTF_INFO_KIND(t->info)]);
|
||||
goto out;
|
||||
}
|
||||
return 0;
|
||||
out:
|
||||
/* LLVM optimizations can remove arguments from static functions. */
|
||||
bpf_log(log,
|
||||
"Type info disagrees with actual arguments due to compiler optimizations\n");
|
||||
prog->aux->func_info_aux[subprog].unreliable = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void btf_type_seq_show(const struct btf *btf, u32 type_id, void *obj,
|
||||
struct seq_file *m)
|
||||
{
|
||||
|
||||
+119
-10
@@ -30,7 +30,8 @@
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/perf_event.h>
|
||||
|
||||
#include <linux/extable.h>
|
||||
#include <linux/log2.h>
|
||||
#include <asm/unaligned.h>
|
||||
|
||||
/* Registers */
|
||||
@@ -255,6 +256,7 @@ void __bpf_prog_free(struct bpf_prog *fp)
|
||||
{
|
||||
if (fp->aux) {
|
||||
free_percpu(fp->aux->stats);
|
||||
kfree(fp->aux->poke_tab);
|
||||
kfree(fp->aux);
|
||||
}
|
||||
vfree(fp);
|
||||
@@ -668,9 +670,6 @@ static struct bpf_prog *bpf_prog_kallsyms_find(unsigned long addr)
|
||||
{
|
||||
struct latch_tree_node *n;
|
||||
|
||||
if (!bpf_jit_kallsyms_enabled())
|
||||
return NULL;
|
||||
|
||||
n = latch_tree_find((void *)addr, &bpf_tree, &bpf_tree_ops);
|
||||
return n ?
|
||||
container_of(n, struct bpf_prog_aux, ksym_tnode)->prog :
|
||||
@@ -712,6 +711,24 @@ bool is_bpf_text_address(unsigned long addr)
|
||||
return ret;
|
||||
}
|
||||
|
||||
const struct exception_table_entry *search_bpf_extables(unsigned long addr)
|
||||
{
|
||||
const struct exception_table_entry *e = NULL;
|
||||
struct bpf_prog *prog;
|
||||
|
||||
rcu_read_lock();
|
||||
prog = bpf_prog_kallsyms_find(addr);
|
||||
if (!prog)
|
||||
goto out;
|
||||
if (!prog->aux->num_exentries)
|
||||
goto out;
|
||||
|
||||
e = search_extable(prog->aux->extable, prog->aux->num_exentries, addr);
|
||||
out:
|
||||
rcu_read_unlock();
|
||||
return e;
|
||||
}
|
||||
|
||||
int bpf_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
|
||||
char *sym)
|
||||
{
|
||||
@@ -740,6 +757,39 @@ int bpf_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
|
||||
return ret;
|
||||
}
|
||||
|
||||
int bpf_jit_add_poke_descriptor(struct bpf_prog *prog,
|
||||
struct bpf_jit_poke_descriptor *poke)
|
||||
{
|
||||
struct bpf_jit_poke_descriptor *tab = prog->aux->poke_tab;
|
||||
static const u32 poke_tab_max = 1024;
|
||||
u32 slot = prog->aux->size_poke_tab;
|
||||
u32 size = slot + 1;
|
||||
|
||||
if (size > poke_tab_max)
|
||||
return -ENOSPC;
|
||||
if (poke->ip || poke->ip_stable || poke->adj_off)
|
||||
return -EINVAL;
|
||||
|
||||
switch (poke->reason) {
|
||||
case BPF_POKE_REASON_TAIL_CALL:
|
||||
if (!poke->tail_call.map)
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
tab = krealloc(tab, size * sizeof(*poke), GFP_KERNEL);
|
||||
if (!tab)
|
||||
return -ENOMEM;
|
||||
|
||||
memcpy(&tab[slot], poke, sizeof(*poke));
|
||||
prog->aux->size_poke_tab = size;
|
||||
prog->aux->poke_tab = tab;
|
||||
|
||||
return slot;
|
||||
}
|
||||
|
||||
static atomic_long_t bpf_jit_current;
|
||||
|
||||
/* Can be overridden by an arch's JIT compiler if it has a custom,
|
||||
@@ -800,6 +850,9 @@ bpf_jit_binary_alloc(unsigned int proglen, u8 **image_ptr,
|
||||
struct bpf_binary_header *hdr;
|
||||
u32 size, hole, start, pages;
|
||||
|
||||
WARN_ON_ONCE(!is_power_of_2(alignment) ||
|
||||
alignment > BPF_IMAGE_ALIGNMENT);
|
||||
|
||||
/* Most of BPF filters are really small, but if some of them
|
||||
* fill a page, allow at least 128 extra bytes to insert a
|
||||
* random section of illegal instructions.
|
||||
@@ -1291,6 +1344,12 @@ bool bpf_opcode_in_insntable(u8 code)
|
||||
}
|
||||
|
||||
#ifndef CONFIG_BPF_JIT_ALWAYS_ON
|
||||
u64 __weak bpf_probe_read_kernel(void *dst, u32 size, const void *unsafe_ptr)
|
||||
{
|
||||
memset(dst, 0, size);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* __bpf_prog_run - run eBPF program on a given context
|
||||
* @regs: is the array of MAX_BPF_EXT_REG eBPF pseudo-registers
|
||||
@@ -1310,6 +1369,10 @@ static u64 __no_fgcse ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn, u6
|
||||
/* Non-UAPI available opcodes. */
|
||||
[BPF_JMP | BPF_CALL_ARGS] = &&JMP_CALL_ARGS,
|
||||
[BPF_JMP | BPF_TAIL_CALL] = &&JMP_TAIL_CALL,
|
||||
[BPF_LDX | BPF_PROBE_MEM | BPF_B] = &&LDX_PROBE_MEM_B,
|
||||
[BPF_LDX | BPF_PROBE_MEM | BPF_H] = &&LDX_PROBE_MEM_H,
|
||||
[BPF_LDX | BPF_PROBE_MEM | BPF_W] = &&LDX_PROBE_MEM_W,
|
||||
[BPF_LDX | BPF_PROBE_MEM | BPF_DW] = &&LDX_PROBE_MEM_DW,
|
||||
};
|
||||
#undef BPF_INSN_3_LBL
|
||||
#undef BPF_INSN_2_LBL
|
||||
@@ -1542,6 +1605,16 @@ out:
|
||||
LDST(W, u32)
|
||||
LDST(DW, u64)
|
||||
#undef LDST
|
||||
#define LDX_PROBE(SIZEOP, SIZE) \
|
||||
LDX_PROBE_MEM_##SIZEOP: \
|
||||
bpf_probe_read_kernel(&DST, SIZE, (const void *)(long) (SRC + insn->off)); \
|
||||
CONT;
|
||||
LDX_PROBE(B, 1)
|
||||
LDX_PROBE(H, 2)
|
||||
LDX_PROBE(W, 4)
|
||||
LDX_PROBE(DW, 8)
|
||||
#undef LDX_PROBE
|
||||
|
||||
STX_XADD_W: /* lock xadd *(u32 *)(dst_reg + off16) += src_reg */
|
||||
atomic_add((u32) SRC, (atomic_t *)(unsigned long)
|
||||
(DST + insn->off));
|
||||
@@ -1652,18 +1725,17 @@ bool bpf_prog_array_compatible(struct bpf_array *array,
|
||||
if (fp->kprobe_override)
|
||||
return false;
|
||||
|
||||
if (!array->owner_prog_type) {
|
||||
if (!array->aux->type) {
|
||||
/* There's no owner yet where we could check for
|
||||
* compatibility.
|
||||
*/
|
||||
array->owner_prog_type = fp->type;
|
||||
array->owner_jited = fp->jited;
|
||||
|
||||
array->aux->type = fp->type;
|
||||
array->aux->jited = fp->jited;
|
||||
return true;
|
||||
}
|
||||
|
||||
return array->owner_prog_type == fp->type &&
|
||||
array->owner_jited == fp->jited;
|
||||
return array->aux->type == fp->type &&
|
||||
array->aux->jited == fp->jited;
|
||||
}
|
||||
|
||||
static int bpf_check_tail_call(const struct bpf_prog *fp)
|
||||
@@ -1964,18 +2036,47 @@ int bpf_prog_array_copy_info(struct bpf_prog_array *array,
|
||||
: 0;
|
||||
}
|
||||
|
||||
static void bpf_free_cgroup_storage(struct bpf_prog_aux *aux)
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
if (!aux->cgroup_storage[stype])
|
||||
continue;
|
||||
bpf_cgroup_storage_release(aux->prog,
|
||||
aux->cgroup_storage[stype]);
|
||||
}
|
||||
}
|
||||
|
||||
static void bpf_free_used_maps(struct bpf_prog_aux *aux)
|
||||
{
|
||||
struct bpf_map *map;
|
||||
int i;
|
||||
|
||||
bpf_free_cgroup_storage(aux);
|
||||
for (i = 0; i < aux->used_map_cnt; i++) {
|
||||
map = aux->used_maps[i];
|
||||
if (map->ops->map_poke_untrack)
|
||||
map->ops->map_poke_untrack(map, aux);
|
||||
bpf_map_put(map);
|
||||
}
|
||||
kfree(aux->used_maps);
|
||||
}
|
||||
|
||||
static void bpf_prog_free_deferred(struct work_struct *work)
|
||||
{
|
||||
struct bpf_prog_aux *aux;
|
||||
int i;
|
||||
|
||||
aux = container_of(work, struct bpf_prog_aux, work);
|
||||
bpf_free_used_maps(aux);
|
||||
if (bpf_prog_is_dev_bound(aux))
|
||||
bpf_prog_offload_destroy(aux->prog);
|
||||
#ifdef CONFIG_PERF_EVENTS
|
||||
if (aux->prog->has_callchain_buf)
|
||||
put_callchain_buffers();
|
||||
#endif
|
||||
bpf_trampoline_put(aux->trampoline);
|
||||
for (i = 0; i < aux->func_cnt; i++)
|
||||
bpf_jit_free(aux->func[i]);
|
||||
if (aux->func_cnt) {
|
||||
@@ -1991,6 +2092,8 @@ void bpf_prog_free(struct bpf_prog *fp)
|
||||
{
|
||||
struct bpf_prog_aux *aux = fp->aux;
|
||||
|
||||
if (aux->linked_prog)
|
||||
bpf_prog_put(aux->linked_prog);
|
||||
INIT_WORK(&aux->work, bpf_prog_free_deferred);
|
||||
schedule_work(&aux->work);
|
||||
}
|
||||
@@ -2105,6 +2208,12 @@ int __weak skb_copy_bits(const struct sk_buff *skb, int offset, void *to,
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
int __weak bpf_arch_text_poke(void *ip, enum bpf_text_poke_type t,
|
||||
void *addr1, void *addr2)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
DEFINE_STATIC_KEY_FALSE(bpf_stats_enabled_key);
|
||||
EXPORT_SYMBOL(bpf_stats_enabled_key);
|
||||
|
||||
|
||||
+46
-28
@@ -74,7 +74,7 @@ struct bpf_dtab_netdev {
|
||||
|
||||
struct bpf_dtab {
|
||||
struct bpf_map map;
|
||||
struct bpf_dtab_netdev **netdev_map;
|
||||
struct bpf_dtab_netdev **netdev_map; /* DEVMAP type only */
|
||||
struct list_head __percpu *flush_list;
|
||||
struct list_head list;
|
||||
|
||||
@@ -101,6 +101,12 @@ static struct hlist_head *dev_map_create_hash(unsigned int entries)
|
||||
return hash;
|
||||
}
|
||||
|
||||
static inline struct hlist_head *dev_map_index_hash(struct bpf_dtab *dtab,
|
||||
int idx)
|
||||
{
|
||||
return &dtab->dev_index_head[idx & (dtab->n_buckets - 1)];
|
||||
}
|
||||
|
||||
static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr)
|
||||
{
|
||||
int err, cpu;
|
||||
@@ -120,8 +126,7 @@ static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr)
|
||||
bpf_map_init_from_attr(&dtab->map, attr);
|
||||
|
||||
/* make sure page count doesn't overflow */
|
||||
cost = (u64) dtab->map.max_entries * sizeof(struct bpf_dtab_netdev *);
|
||||
cost += sizeof(struct list_head) * num_possible_cpus();
|
||||
cost = (u64) sizeof(struct list_head) * num_possible_cpus();
|
||||
|
||||
if (attr->map_type == BPF_MAP_TYPE_DEVMAP_HASH) {
|
||||
dtab->n_buckets = roundup_pow_of_two(dtab->map.max_entries);
|
||||
@@ -129,6 +134,8 @@ static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr)
|
||||
if (!dtab->n_buckets) /* Overflow check */
|
||||
return -EINVAL;
|
||||
cost += (u64) sizeof(struct hlist_head) * dtab->n_buckets;
|
||||
} else {
|
||||
cost += (u64) dtab->map.max_entries * sizeof(struct bpf_dtab_netdev *);
|
||||
}
|
||||
|
||||
/* if map size is larger than memlock limit, reject it */
|
||||
@@ -143,24 +150,22 @@ static int dev_map_init_map(struct bpf_dtab *dtab, union bpf_attr *attr)
|
||||
for_each_possible_cpu(cpu)
|
||||
INIT_LIST_HEAD(per_cpu_ptr(dtab->flush_list, cpu));
|
||||
|
||||
dtab->netdev_map = bpf_map_area_alloc(dtab->map.max_entries *
|
||||
sizeof(struct bpf_dtab_netdev *),
|
||||
dtab->map.numa_node);
|
||||
if (!dtab->netdev_map)
|
||||
goto free_percpu;
|
||||
|
||||
if (attr->map_type == BPF_MAP_TYPE_DEVMAP_HASH) {
|
||||
dtab->dev_index_head = dev_map_create_hash(dtab->n_buckets);
|
||||
if (!dtab->dev_index_head)
|
||||
goto free_map_area;
|
||||
goto free_percpu;
|
||||
|
||||
spin_lock_init(&dtab->index_lock);
|
||||
} else {
|
||||
dtab->netdev_map = bpf_map_area_alloc(dtab->map.max_entries *
|
||||
sizeof(struct bpf_dtab_netdev *),
|
||||
dtab->map.numa_node);
|
||||
if (!dtab->netdev_map)
|
||||
goto free_percpu;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_map_area:
|
||||
bpf_map_area_free(dtab->netdev_map);
|
||||
free_percpu:
|
||||
free_percpu(dtab->flush_list);
|
||||
free_charge:
|
||||
@@ -228,21 +233,40 @@ static void dev_map_free(struct bpf_map *map)
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
for (i = 0; i < dtab->map.max_entries; i++) {
|
||||
struct bpf_dtab_netdev *dev;
|
||||
if (dtab->map.map_type == BPF_MAP_TYPE_DEVMAP_HASH) {
|
||||
for (i = 0; i < dtab->n_buckets; i++) {
|
||||
struct bpf_dtab_netdev *dev;
|
||||
struct hlist_head *head;
|
||||
struct hlist_node *next;
|
||||
|
||||
dev = dtab->netdev_map[i];
|
||||
if (!dev)
|
||||
continue;
|
||||
head = dev_map_index_hash(dtab, i);
|
||||
|
||||
free_percpu(dev->bulkq);
|
||||
dev_put(dev->dev);
|
||||
kfree(dev);
|
||||
hlist_for_each_entry_safe(dev, next, head, index_hlist) {
|
||||
hlist_del_rcu(&dev->index_hlist);
|
||||
free_percpu(dev->bulkq);
|
||||
dev_put(dev->dev);
|
||||
kfree(dev);
|
||||
}
|
||||
}
|
||||
|
||||
kfree(dtab->dev_index_head);
|
||||
} else {
|
||||
for (i = 0; i < dtab->map.max_entries; i++) {
|
||||
struct bpf_dtab_netdev *dev;
|
||||
|
||||
dev = dtab->netdev_map[i];
|
||||
if (!dev)
|
||||
continue;
|
||||
|
||||
free_percpu(dev->bulkq);
|
||||
dev_put(dev->dev);
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
bpf_map_area_free(dtab->netdev_map);
|
||||
}
|
||||
|
||||
free_percpu(dtab->flush_list);
|
||||
bpf_map_area_free(dtab->netdev_map);
|
||||
kfree(dtab->dev_index_head);
|
||||
kfree(dtab);
|
||||
}
|
||||
|
||||
@@ -263,12 +287,6 @@ static int dev_map_get_next_key(struct bpf_map *map, void *key, void *next_key)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline struct hlist_head *dev_map_index_hash(struct bpf_dtab *dtab,
|
||||
int idx)
|
||||
{
|
||||
return &dtab->dev_index_head[idx & (dtab->n_buckets - 1)];
|
||||
}
|
||||
|
||||
struct bpf_dtab_netdev *__dev_map_hash_lookup_elem(struct bpf_map *map, u32 key)
|
||||
{
|
||||
struct bpf_dtab *dtab = container_of(map, struct bpf_dtab, map);
|
||||
|
||||
@@ -317,7 +317,7 @@ BPF_CALL_0(bpf_get_current_cgroup_id)
|
||||
{
|
||||
struct cgroup *cgrp = task_dfl_cgroup(current);
|
||||
|
||||
return cgrp->kn->id.id;
|
||||
return cgroup_id(cgrp);
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_get_current_cgroup_id_proto = {
|
||||
|
||||
+4
-3
@@ -31,10 +31,10 @@ static void *bpf_any_get(void *raw, enum bpf_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case BPF_TYPE_PROG:
|
||||
raw = bpf_prog_inc(raw);
|
||||
bpf_prog_inc(raw);
|
||||
break;
|
||||
case BPF_TYPE_MAP:
|
||||
raw = bpf_map_inc(raw, true);
|
||||
bpf_map_inc_with_uref(raw);
|
||||
break;
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
@@ -534,7 +534,8 @@ static struct bpf_prog *__get_prog_inode(struct inode *inode, enum bpf_prog_type
|
||||
if (!bpf_prog_get_ok(prog, &type, false))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
return bpf_prog_inc(prog);
|
||||
bpf_prog_inc(prog);
|
||||
return prog;
|
||||
}
|
||||
|
||||
struct bpf_prog *bpf_prog_get_type_path(const char *name, enum bpf_prog_type type)
|
||||
|
||||
@@ -569,7 +569,7 @@ void bpf_cgroup_storage_link(struct bpf_cgroup_storage *storage,
|
||||
return;
|
||||
|
||||
storage->key.attach_type = type;
|
||||
storage->key.cgroup_inode_id = cgroup->kn->id.id;
|
||||
storage->key.cgroup_inode_id = cgroup_id(cgroup);
|
||||
|
||||
map = storage->map;
|
||||
|
||||
|
||||
@@ -17,9 +17,8 @@ struct bpf_map *bpf_map_meta_alloc(int inner_map_ufd)
|
||||
if (IS_ERR(inner_map))
|
||||
return inner_map;
|
||||
|
||||
/* prog_array->owner_prog_type and owner_jited
|
||||
* is a runtime binding. Doing static check alone
|
||||
* in the verifier is not enough.
|
||||
/* prog_array->aux->{type,jited} is a runtime binding.
|
||||
* Doing static check alone in the verifier is not enough.
|
||||
*/
|
||||
if (inner_map->map_type == BPF_MAP_TYPE_PROG_ARRAY ||
|
||||
inner_map->map_type == BPF_MAP_TYPE_CGROUP_STORAGE ||
|
||||
@@ -98,7 +97,7 @@ void *bpf_map_fd_get_ptr(struct bpf_map *map,
|
||||
return inner_map;
|
||||
|
||||
if (bpf_map_meta_equal(map->inner_map_meta, inner_map))
|
||||
inner_map = bpf_map_inc(inner_map, false);
|
||||
bpf_map_inc(inner_map);
|
||||
else
|
||||
inner_map = ERR_PTR(-EINVAL);
|
||||
|
||||
|
||||
@@ -678,8 +678,10 @@ bpf_offload_dev_create(const struct bpf_prog_offload_ops *ops, void *priv)
|
||||
down_write(&bpf_devs_lock);
|
||||
if (!offdevs_inited) {
|
||||
err = rhashtable_init(&offdevs, &offdevs_params);
|
||||
if (err)
|
||||
if (err) {
|
||||
up_write(&bpf_devs_lock);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
offdevs_inited = true;
|
||||
}
|
||||
up_write(&bpf_devs_lock);
|
||||
|
||||
@@ -287,7 +287,7 @@ static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
|
||||
bool irq_work_busy = false;
|
||||
struct stack_map_irq_work *work = NULL;
|
||||
|
||||
if (in_nmi()) {
|
||||
if (irqs_disabled()) {
|
||||
work = this_cpu_ptr(&up_read_work);
|
||||
if (work->irq_work.flags & IRQ_WORK_BUSY)
|
||||
/* cannot queue more up_read, fallback */
|
||||
@@ -295,8 +295,9 @@ static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
|
||||
}
|
||||
|
||||
/*
|
||||
* We cannot do up_read() in nmi context. To do build_id lookup
|
||||
* in nmi context, we need to run up_read() in irq_work. We use
|
||||
* We cannot do up_read() when the irq is disabled, because of
|
||||
* risk to deadlock with rq_lock. To do build_id lookup when the
|
||||
* irqs are disabled, we need to run up_read() in irq_work. We use
|
||||
* a percpu variable to do the irq_work. If the irq_work is
|
||||
* already used by another lookup, we fall back to report ips.
|
||||
*
|
||||
|
||||
+267
-115
@@ -23,13 +23,15 @@
|
||||
#include <linux/timekeeping.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/nospec.h>
|
||||
#include <uapi/linux/btf.h>
|
||||
|
||||
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PROG_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
|
||||
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
|
||||
#define IS_FD_PROG_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PROG_ARRAY)
|
||||
#define IS_FD_HASH(map) ((map)->map_type == BPF_MAP_TYPE_HASH_OF_MAPS)
|
||||
#define IS_FD_MAP(map) (IS_FD_ARRAY(map) || IS_FD_HASH(map))
|
||||
#define IS_FD_MAP(map) (IS_FD_ARRAY(map) || IS_FD_PROG_ARRAY(map) || \
|
||||
IS_FD_HASH(map))
|
||||
|
||||
#define BPF_OBJ_FLAG_MASK (BPF_F_RDONLY | BPF_F_WRONLY)
|
||||
|
||||
@@ -42,7 +44,7 @@ static DEFINE_SPINLOCK(map_idr_lock);
|
||||
int sysctl_unprivileged_bpf_disabled __read_mostly;
|
||||
|
||||
static const struct bpf_map_ops * const bpf_map_types[] = {
|
||||
#define BPF_PROG_TYPE(_id, _ops)
|
||||
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type)
|
||||
#define BPF_MAP_TYPE(_id, _ops) \
|
||||
[_id] = &_ops,
|
||||
#include <linux/bpf_types.h>
|
||||
@@ -126,7 +128,7 @@ static struct bpf_map *find_and_alloc_map(union bpf_attr *attr)
|
||||
return map;
|
||||
}
|
||||
|
||||
void *bpf_map_area_alloc(u64 size, int numa_node)
|
||||
static void *__bpf_map_area_alloc(u64 size, int numa_node, bool mmapable)
|
||||
{
|
||||
/* We really just want to fail instead of triggering OOM killer
|
||||
* under memory pressure, therefore we set __GFP_NORETRY to kmalloc,
|
||||
@@ -144,18 +146,33 @@ void *bpf_map_area_alloc(u64 size, int numa_node)
|
||||
if (size >= SIZE_MAX)
|
||||
return NULL;
|
||||
|
||||
if (size <= (PAGE_SIZE << PAGE_ALLOC_COSTLY_ORDER)) {
|
||||
/* kmalloc()'ed memory can't be mmap()'ed */
|
||||
if (!mmapable && size <= (PAGE_SIZE << PAGE_ALLOC_COSTLY_ORDER)) {
|
||||
area = kmalloc_node(size, GFP_USER | __GFP_NORETRY | flags,
|
||||
numa_node);
|
||||
if (area != NULL)
|
||||
return area;
|
||||
}
|
||||
|
||||
if (mmapable) {
|
||||
BUG_ON(!PAGE_ALIGNED(size));
|
||||
return vmalloc_user_node_flags(size, numa_node, GFP_KERNEL |
|
||||
__GFP_RETRY_MAYFAIL | flags);
|
||||
}
|
||||
return __vmalloc_node_flags_caller(size, numa_node,
|
||||
GFP_KERNEL | __GFP_RETRY_MAYFAIL |
|
||||
flags, __builtin_return_address(0));
|
||||
}
|
||||
|
||||
void *bpf_map_area_alloc(u64 size, int numa_node)
|
||||
{
|
||||
return __bpf_map_area_alloc(size, numa_node, false);
|
||||
}
|
||||
|
||||
void *bpf_map_area_mmapable_alloc(u64 size, int numa_node)
|
||||
{
|
||||
return __bpf_map_area_alloc(size, numa_node, true);
|
||||
}
|
||||
|
||||
void bpf_map_area_free(void *area)
|
||||
{
|
||||
kvfree(area);
|
||||
@@ -313,7 +330,7 @@ static void bpf_map_free_deferred(struct work_struct *work)
|
||||
|
||||
static void bpf_map_put_uref(struct bpf_map *map)
|
||||
{
|
||||
if (atomic_dec_and_test(&map->usercnt)) {
|
||||
if (atomic64_dec_and_test(&map->usercnt)) {
|
||||
if (map->ops->map_release_uref)
|
||||
map->ops->map_release_uref(map);
|
||||
}
|
||||
@@ -324,7 +341,7 @@ static void bpf_map_put_uref(struct bpf_map *map)
|
||||
*/
|
||||
static void __bpf_map_put(struct bpf_map *map, bool do_idr_lock)
|
||||
{
|
||||
if (atomic_dec_and_test(&map->refcnt)) {
|
||||
if (atomic64_dec_and_test(&map->refcnt)) {
|
||||
/* bpf_map_free_id() must be called first */
|
||||
bpf_map_free_id(map, do_idr_lock);
|
||||
btf_put(map->btf);
|
||||
@@ -373,13 +390,12 @@ static void bpf_map_show_fdinfo(struct seq_file *m, struct file *filp)
|
||||
{
|
||||
const struct bpf_map *map = filp->private_data;
|
||||
const struct bpf_array *array;
|
||||
u32 owner_prog_type = 0;
|
||||
u32 owner_jited = 0;
|
||||
u32 type = 0, jited = 0;
|
||||
|
||||
if (map->map_type == BPF_MAP_TYPE_PROG_ARRAY) {
|
||||
array = container_of(map, struct bpf_array, map);
|
||||
owner_prog_type = array->owner_prog_type;
|
||||
owner_jited = array->owner_jited;
|
||||
type = array->aux->type;
|
||||
jited = array->aux->jited;
|
||||
}
|
||||
|
||||
seq_printf(m,
|
||||
@@ -399,12 +415,9 @@ static void bpf_map_show_fdinfo(struct seq_file *m, struct file *filp)
|
||||
map->memory.pages * 1ULL << PAGE_SHIFT,
|
||||
map->id,
|
||||
READ_ONCE(map->frozen));
|
||||
|
||||
if (owner_prog_type) {
|
||||
seq_printf(m, "owner_prog_type:\t%u\n",
|
||||
owner_prog_type);
|
||||
seq_printf(m, "owner_jited:\t%u\n",
|
||||
owner_jited);
|
||||
if (type) {
|
||||
seq_printf(m, "owner_prog_type:\t%u\n", type);
|
||||
seq_printf(m, "owner_jited:\t%u\n", jited);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -427,6 +440,74 @@ static ssize_t bpf_dummy_write(struct file *filp, const char __user *buf,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* called for any extra memory-mapped regions (except initial) */
|
||||
static void bpf_map_mmap_open(struct vm_area_struct *vma)
|
||||
{
|
||||
struct bpf_map *map = vma->vm_file->private_data;
|
||||
|
||||
bpf_map_inc_with_uref(map);
|
||||
|
||||
if (vma->vm_flags & VM_WRITE) {
|
||||
mutex_lock(&map->freeze_mutex);
|
||||
map->writecnt++;
|
||||
mutex_unlock(&map->freeze_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
/* called for all unmapped memory region (including initial) */
|
||||
static void bpf_map_mmap_close(struct vm_area_struct *vma)
|
||||
{
|
||||
struct bpf_map *map = vma->vm_file->private_data;
|
||||
|
||||
if (vma->vm_flags & VM_WRITE) {
|
||||
mutex_lock(&map->freeze_mutex);
|
||||
map->writecnt--;
|
||||
mutex_unlock(&map->freeze_mutex);
|
||||
}
|
||||
|
||||
bpf_map_put_with_uref(map);
|
||||
}
|
||||
|
||||
static const struct vm_operations_struct bpf_map_default_vmops = {
|
||||
.open = bpf_map_mmap_open,
|
||||
.close = bpf_map_mmap_close,
|
||||
};
|
||||
|
||||
static int bpf_map_mmap(struct file *filp, struct vm_area_struct *vma)
|
||||
{
|
||||
struct bpf_map *map = filp->private_data;
|
||||
int err;
|
||||
|
||||
if (!map->ops->map_mmap || map_value_has_spin_lock(map))
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (!(vma->vm_flags & VM_SHARED))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&map->freeze_mutex);
|
||||
|
||||
if ((vma->vm_flags & VM_WRITE) && map->frozen) {
|
||||
err = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* set default open/close callbacks */
|
||||
vma->vm_ops = &bpf_map_default_vmops;
|
||||
vma->vm_private_data = map;
|
||||
|
||||
err = map->ops->map_mmap(map, vma);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
bpf_map_inc_with_uref(map);
|
||||
|
||||
if (vma->vm_flags & VM_WRITE)
|
||||
map->writecnt++;
|
||||
out:
|
||||
mutex_unlock(&map->freeze_mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
const struct file_operations bpf_map_fops = {
|
||||
#ifdef CONFIG_PROC_FS
|
||||
.show_fdinfo = bpf_map_show_fdinfo,
|
||||
@@ -434,6 +515,7 @@ const struct file_operations bpf_map_fops = {
|
||||
.release = bpf_map_release,
|
||||
.read = bpf_dummy_read,
|
||||
.write = bpf_dummy_write,
|
||||
.mmap = bpf_map_mmap,
|
||||
};
|
||||
|
||||
int bpf_map_new_fd(struct bpf_map *map, int flags)
|
||||
@@ -577,8 +659,9 @@ static int map_create(union bpf_attr *attr)
|
||||
if (err)
|
||||
goto free_map;
|
||||
|
||||
atomic_set(&map->refcnt, 1);
|
||||
atomic_set(&map->usercnt, 1);
|
||||
atomic64_set(&map->refcnt, 1);
|
||||
atomic64_set(&map->usercnt, 1);
|
||||
mutex_init(&map->freeze_mutex);
|
||||
|
||||
if (attr->btf_key_type_id || attr->btf_value_type_id) {
|
||||
struct btf *btf;
|
||||
@@ -655,21 +738,19 @@ struct bpf_map *__bpf_map_get(struct fd f)
|
||||
return f.file->private_data;
|
||||
}
|
||||
|
||||
/* prog's and map's refcnt limit */
|
||||
#define BPF_MAX_REFCNT 32768
|
||||
|
||||
struct bpf_map *bpf_map_inc(struct bpf_map *map, bool uref)
|
||||
void bpf_map_inc(struct bpf_map *map)
|
||||
{
|
||||
if (atomic_inc_return(&map->refcnt) > BPF_MAX_REFCNT) {
|
||||
atomic_dec(&map->refcnt);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
if (uref)
|
||||
atomic_inc(&map->usercnt);
|
||||
return map;
|
||||
atomic64_inc(&map->refcnt);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_map_inc);
|
||||
|
||||
void bpf_map_inc_with_uref(struct bpf_map *map)
|
||||
{
|
||||
atomic64_inc(&map->refcnt);
|
||||
atomic64_inc(&map->usercnt);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_map_inc_with_uref);
|
||||
|
||||
struct bpf_map *bpf_map_get_with_uref(u32 ufd)
|
||||
{
|
||||
struct fd f = fdget(ufd);
|
||||
@@ -679,38 +760,30 @@ struct bpf_map *bpf_map_get_with_uref(u32 ufd)
|
||||
if (IS_ERR(map))
|
||||
return map;
|
||||
|
||||
map = bpf_map_inc(map, true);
|
||||
bpf_map_inc_with_uref(map);
|
||||
fdput(f);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/* map_idr_lock should have been held */
|
||||
static struct bpf_map *__bpf_map_inc_not_zero(struct bpf_map *map,
|
||||
bool uref)
|
||||
static struct bpf_map *__bpf_map_inc_not_zero(struct bpf_map *map, bool uref)
|
||||
{
|
||||
int refold;
|
||||
|
||||
refold = atomic_fetch_add_unless(&map->refcnt, 1, 0);
|
||||
|
||||
if (refold >= BPF_MAX_REFCNT) {
|
||||
__bpf_map_put(map, false);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
|
||||
refold = atomic64_fetch_add_unless(&map->refcnt, 1, 0);
|
||||
if (!refold)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if (uref)
|
||||
atomic_inc(&map->usercnt);
|
||||
atomic64_inc(&map->usercnt);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
struct bpf_map *bpf_map_inc_not_zero(struct bpf_map *map, bool uref)
|
||||
struct bpf_map *bpf_map_inc_not_zero(struct bpf_map *map)
|
||||
{
|
||||
spin_lock_bh(&map_idr_lock);
|
||||
map = __bpf_map_inc_not_zero(map, uref);
|
||||
map = __bpf_map_inc_not_zero(map, false);
|
||||
spin_unlock_bh(&map_idr_lock);
|
||||
|
||||
return map;
|
||||
@@ -805,7 +878,7 @@ static int map_lookup_elem(union bpf_attr *attr)
|
||||
err = bpf_percpu_cgroup_storage_copy(map, key, value);
|
||||
} else if (map->map_type == BPF_MAP_TYPE_STACK_TRACE) {
|
||||
err = bpf_stackmap_copy(map, key, value);
|
||||
} else if (IS_FD_ARRAY(map)) {
|
||||
} else if (IS_FD_ARRAY(map) || IS_FD_PROG_ARRAY(map)) {
|
||||
err = bpf_fd_array_map_lookup_elem(map, key, value);
|
||||
} else if (IS_FD_HASH(map)) {
|
||||
err = bpf_fd_htab_map_lookup_elem(map, key, value);
|
||||
@@ -932,6 +1005,10 @@ static int map_update_elem(union bpf_attr *attr)
|
||||
map->map_type == BPF_MAP_TYPE_SOCKMAP) {
|
||||
err = map->ops->map_update_elem(map, key, value, attr->flags);
|
||||
goto out;
|
||||
} else if (IS_FD_PROG_ARRAY(map)) {
|
||||
err = bpf_fd_array_map_update_elem(map, f.file, key, value,
|
||||
attr->flags);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* must increment bpf_prog_active to avoid kprobe+bpf triggering from
|
||||
@@ -1014,6 +1091,9 @@ static int map_delete_elem(union bpf_attr *attr)
|
||||
if (bpf_map_is_dev_bound(map)) {
|
||||
err = bpf_map_offload_delete_elem(map, key);
|
||||
goto out;
|
||||
} else if (IS_FD_PROG_ARRAY(map)) {
|
||||
err = map->ops->map_delete_elem(map, key);
|
||||
goto out;
|
||||
}
|
||||
|
||||
preempt_disable();
|
||||
@@ -1175,6 +1255,13 @@ static int map_freeze(const union bpf_attr *attr)
|
||||
map = __bpf_map_get(f);
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
mutex_lock(&map->freeze_mutex);
|
||||
|
||||
if (map->writecnt) {
|
||||
err = -EBUSY;
|
||||
goto err_put;
|
||||
}
|
||||
if (READ_ONCE(map->frozen)) {
|
||||
err = -EBUSY;
|
||||
goto err_put;
|
||||
@@ -1186,12 +1273,13 @@ static int map_freeze(const union bpf_attr *attr)
|
||||
|
||||
WRITE_ONCE(map->frozen, true);
|
||||
err_put:
|
||||
mutex_unlock(&map->freeze_mutex);
|
||||
fdput(f);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct bpf_prog_ops * const bpf_prog_types[] = {
|
||||
#define BPF_PROG_TYPE(_id, _name) \
|
||||
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
|
||||
[_id] = & _name ## _prog_ops,
|
||||
#define BPF_MAP_TYPE(_id, _ops)
|
||||
#include <linux/bpf_types.h>
|
||||
@@ -1218,25 +1306,6 @@ static int find_prog_type(enum bpf_prog_type type, struct bpf_prog *prog)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* drop refcnt on maps used by eBPF program and free auxilary data */
|
||||
static void free_used_maps(struct bpf_prog_aux *aux)
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
int i;
|
||||
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
if (!aux->cgroup_storage[stype])
|
||||
continue;
|
||||
bpf_cgroup_storage_release(aux->prog,
|
||||
aux->cgroup_storage[stype]);
|
||||
}
|
||||
|
||||
for (i = 0; i < aux->used_map_cnt; i++)
|
||||
bpf_map_put(aux->used_maps[i]);
|
||||
|
||||
kfree(aux->used_maps);
|
||||
}
|
||||
|
||||
int __bpf_prog_charge(struct user_struct *user, u32 pages)
|
||||
{
|
||||
unsigned long memlock_limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
|
||||
@@ -1330,7 +1399,7 @@ static void __bpf_prog_put_rcu(struct rcu_head *rcu)
|
||||
struct bpf_prog_aux *aux = container_of(rcu, struct bpf_prog_aux, rcu);
|
||||
|
||||
kvfree(aux->func_info);
|
||||
free_used_maps(aux);
|
||||
kfree(aux->func_info_aux);
|
||||
bpf_prog_uncharge_memlock(aux->prog);
|
||||
security_bpf_prog_free(aux);
|
||||
bpf_prog_free(aux->prog);
|
||||
@@ -1350,7 +1419,7 @@ static void __bpf_prog_put_noref(struct bpf_prog *prog, bool deferred)
|
||||
|
||||
static void __bpf_prog_put(struct bpf_prog *prog, bool do_idr_lock)
|
||||
{
|
||||
if (atomic_dec_and_test(&prog->aux->refcnt)) {
|
||||
if (atomic64_dec_and_test(&prog->aux->refcnt)) {
|
||||
perf_event_bpf_event(prog, PERF_BPF_EVENT_PROG_UNLOAD, 0);
|
||||
/* bpf_prog_free_id() must be called first */
|
||||
bpf_prog_free_id(prog, do_idr_lock);
|
||||
@@ -1456,13 +1525,9 @@ static struct bpf_prog *____bpf_prog_get(struct fd f)
|
||||
return f.file->private_data;
|
||||
}
|
||||
|
||||
struct bpf_prog *bpf_prog_add(struct bpf_prog *prog, int i)
|
||||
void bpf_prog_add(struct bpf_prog *prog, int i)
|
||||
{
|
||||
if (atomic_add_return(i, &prog->aux->refcnt) > BPF_MAX_REFCNT) {
|
||||
atomic_sub(i, &prog->aux->refcnt);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
return prog;
|
||||
atomic64_add(i, &prog->aux->refcnt);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_prog_add);
|
||||
|
||||
@@ -1473,13 +1538,13 @@ void bpf_prog_sub(struct bpf_prog *prog, int i)
|
||||
* path holds a reference to the program, thus atomic_sub() can
|
||||
* be safely used in such cases!
|
||||
*/
|
||||
WARN_ON(atomic_sub_return(i, &prog->aux->refcnt) == 0);
|
||||
WARN_ON(atomic64_sub_return(i, &prog->aux->refcnt) == 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_prog_sub);
|
||||
|
||||
struct bpf_prog *bpf_prog_inc(struct bpf_prog *prog)
|
||||
void bpf_prog_inc(struct bpf_prog *prog)
|
||||
{
|
||||
return bpf_prog_add(prog, 1);
|
||||
atomic64_inc(&prog->aux->refcnt);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_prog_inc);
|
||||
|
||||
@@ -1488,12 +1553,7 @@ struct bpf_prog *bpf_prog_inc_not_zero(struct bpf_prog *prog)
|
||||
{
|
||||
int refold;
|
||||
|
||||
refold = atomic_fetch_add_unless(&prog->aux->refcnt, 1, 0);
|
||||
|
||||
if (refold >= BPF_MAX_REFCNT) {
|
||||
__bpf_prog_put(prog, false);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
refold = atomic64_fetch_add_unless(&prog->aux->refcnt, 1, 0);
|
||||
|
||||
if (!refold)
|
||||
return ERR_PTR(-ENOENT);
|
||||
@@ -1531,7 +1591,7 @@ static struct bpf_prog *__bpf_prog_get(u32 ufd, enum bpf_prog_type *attach_type,
|
||||
goto out;
|
||||
}
|
||||
|
||||
prog = bpf_prog_inc(prog);
|
||||
bpf_prog_inc(prog);
|
||||
out:
|
||||
fdput(f);
|
||||
return prog;
|
||||
@@ -1576,9 +1636,21 @@ static void bpf_prog_load_fixup_attach_type(union bpf_attr *attr)
|
||||
}
|
||||
|
||||
static int
|
||||
bpf_prog_load_check_attach_type(enum bpf_prog_type prog_type,
|
||||
enum bpf_attach_type expected_attach_type)
|
||||
bpf_prog_load_check_attach(enum bpf_prog_type prog_type,
|
||||
enum bpf_attach_type expected_attach_type,
|
||||
u32 btf_id, u32 prog_fd)
|
||||
{
|
||||
switch (prog_type) {
|
||||
case BPF_PROG_TYPE_TRACING:
|
||||
if (btf_id > BTF_MAX_TYPE)
|
||||
return -EINVAL;
|
||||
break;
|
||||
default:
|
||||
if (btf_id || prog_fd)
|
||||
return -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (prog_type) {
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK:
|
||||
switch (expected_attach_type) {
|
||||
@@ -1625,7 +1697,7 @@ bpf_prog_load_check_attach_type(enum bpf_prog_type prog_type,
|
||||
}
|
||||
|
||||
/* last field in 'union bpf_attr' used by this command */
|
||||
#define BPF_PROG_LOAD_LAST_FIELD line_info_cnt
|
||||
#define BPF_PROG_LOAD_LAST_FIELD attach_prog_fd
|
||||
|
||||
static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)
|
||||
{
|
||||
@@ -1667,7 +1739,9 @@ static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)
|
||||
return -EPERM;
|
||||
|
||||
bpf_prog_load_fixup_attach_type(attr);
|
||||
if (bpf_prog_load_check_attach_type(type, attr->expected_attach_type))
|
||||
if (bpf_prog_load_check_attach(type, attr->expected_attach_type,
|
||||
attr->attach_btf_id,
|
||||
attr->attach_prog_fd))
|
||||
return -EINVAL;
|
||||
|
||||
/* plain bpf_prog allocation */
|
||||
@@ -1676,6 +1750,17 @@ static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)
|
||||
return -ENOMEM;
|
||||
|
||||
prog->expected_attach_type = attr->expected_attach_type;
|
||||
prog->aux->attach_btf_id = attr->attach_btf_id;
|
||||
if (attr->attach_prog_fd) {
|
||||
struct bpf_prog *tgt_prog;
|
||||
|
||||
tgt_prog = bpf_prog_get(attr->attach_prog_fd);
|
||||
if (IS_ERR(tgt_prog)) {
|
||||
err = PTR_ERR(tgt_prog);
|
||||
goto free_prog_nouncharge;
|
||||
}
|
||||
prog->aux->linked_prog = tgt_prog;
|
||||
}
|
||||
|
||||
prog->aux->offload_requested = !!attr->prog_ifindex;
|
||||
|
||||
@@ -1697,7 +1782,7 @@ static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)
|
||||
prog->orig_prog = NULL;
|
||||
prog->jited = 0;
|
||||
|
||||
atomic_set(&prog->aux->refcnt, 1);
|
||||
atomic64_set(&prog->aux->refcnt, 1);
|
||||
prog->gpl_compatible = is_gpl ? 1 : 0;
|
||||
|
||||
if (bpf_prog_is_dev_bound(prog->aux)) {
|
||||
@@ -1787,6 +1872,49 @@ static int bpf_obj_get(const union bpf_attr *attr)
|
||||
attr->file_flags);
|
||||
}
|
||||
|
||||
static int bpf_tracing_prog_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct bpf_prog *prog = filp->private_data;
|
||||
|
||||
WARN_ON_ONCE(bpf_trampoline_unlink_prog(prog));
|
||||
bpf_prog_put(prog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations bpf_tracing_prog_fops = {
|
||||
.release = bpf_tracing_prog_release,
|
||||
.read = bpf_dummy_read,
|
||||
.write = bpf_dummy_write,
|
||||
};
|
||||
|
||||
static int bpf_tracing_prog_attach(struct bpf_prog *prog)
|
||||
{
|
||||
int tr_fd, err;
|
||||
|
||||
if (prog->expected_attach_type != BPF_TRACE_FENTRY &&
|
||||
prog->expected_attach_type != BPF_TRACE_FEXIT) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
err = bpf_trampoline_link_prog(prog);
|
||||
if (err)
|
||||
goto out_put_prog;
|
||||
|
||||
tr_fd = anon_inode_getfd("bpf-tracing-prog", &bpf_tracing_prog_fops,
|
||||
prog, O_CLOEXEC);
|
||||
if (tr_fd < 0) {
|
||||
WARN_ON_ONCE(bpf_trampoline_unlink_prog(prog));
|
||||
err = tr_fd;
|
||||
goto out_put_prog;
|
||||
}
|
||||
return tr_fd;
|
||||
|
||||
out_put_prog:
|
||||
bpf_prog_put(prog);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct bpf_raw_tracepoint {
|
||||
struct bpf_raw_event_map *btp;
|
||||
struct bpf_prog *prog;
|
||||
@@ -1818,17 +1946,52 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
|
||||
struct bpf_raw_tracepoint *raw_tp;
|
||||
struct bpf_raw_event_map *btp;
|
||||
struct bpf_prog *prog;
|
||||
char tp_name[128];
|
||||
const char *tp_name;
|
||||
char buf[128];
|
||||
int tp_fd, err;
|
||||
|
||||
if (strncpy_from_user(tp_name, u64_to_user_ptr(attr->raw_tracepoint.name),
|
||||
sizeof(tp_name) - 1) < 0)
|
||||
return -EFAULT;
|
||||
tp_name[sizeof(tp_name) - 1] = 0;
|
||||
if (CHECK_ATTR(BPF_RAW_TRACEPOINT_OPEN))
|
||||
return -EINVAL;
|
||||
|
||||
prog = bpf_prog_get(attr->raw_tracepoint.prog_fd);
|
||||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
|
||||
if (prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT &&
|
||||
prog->type != BPF_PROG_TYPE_TRACING &&
|
||||
prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
if (prog->type == BPF_PROG_TYPE_TRACING) {
|
||||
if (attr->raw_tracepoint.name) {
|
||||
/* The attach point for this category of programs
|
||||
* should be specified via btf_id during program load.
|
||||
*/
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
if (prog->expected_attach_type == BPF_TRACE_RAW_TP)
|
||||
tp_name = prog->aux->attach_func_name;
|
||||
else
|
||||
return bpf_tracing_prog_attach(prog);
|
||||
} else {
|
||||
if (strncpy_from_user(buf,
|
||||
u64_to_user_ptr(attr->raw_tracepoint.name),
|
||||
sizeof(buf) - 1) < 0) {
|
||||
err = -EFAULT;
|
||||
goto out_put_prog;
|
||||
}
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
tp_name = buf;
|
||||
}
|
||||
|
||||
btp = bpf_get_raw_tracepoint(tp_name);
|
||||
if (!btp)
|
||||
return -ENOENT;
|
||||
if (!btp) {
|
||||
err = -ENOENT;
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
raw_tp = kzalloc(sizeof(*raw_tp), GFP_USER);
|
||||
if (!raw_tp) {
|
||||
@@ -1836,38 +1999,27 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
|
||||
goto out_put_btp;
|
||||
}
|
||||
raw_tp->btp = btp;
|
||||
|
||||
prog = bpf_prog_get(attr->raw_tracepoint.prog_fd);
|
||||
if (IS_ERR(prog)) {
|
||||
err = PTR_ERR(prog);
|
||||
goto out_free_tp;
|
||||
}
|
||||
if (prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT &&
|
||||
prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
raw_tp->prog = prog;
|
||||
|
||||
err = bpf_probe_register(raw_tp->btp, prog);
|
||||
if (err)
|
||||
goto out_put_prog;
|
||||
goto out_free_tp;
|
||||
|
||||
raw_tp->prog = prog;
|
||||
tp_fd = anon_inode_getfd("bpf-raw-tracepoint", &bpf_raw_tp_fops, raw_tp,
|
||||
O_CLOEXEC);
|
||||
if (tp_fd < 0) {
|
||||
bpf_probe_unregister(raw_tp->btp, prog);
|
||||
err = tp_fd;
|
||||
goto out_put_prog;
|
||||
goto out_free_tp;
|
||||
}
|
||||
return tp_fd;
|
||||
|
||||
out_put_prog:
|
||||
bpf_prog_put(prog);
|
||||
out_free_tp:
|
||||
kfree(raw_tp);
|
||||
out_put_btp:
|
||||
bpf_put_raw_tracepoint(btp);
|
||||
out_put_prog:
|
||||
bpf_prog_put(prog);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,253 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/* Copyright (c) 2019 Facebook */
|
||||
#include <linux/hash.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/filter.h>
|
||||
|
||||
/* btf_vmlinux has ~22k attachable functions. 1k htab is enough. */
|
||||
#define TRAMPOLINE_HASH_BITS 10
|
||||
#define TRAMPOLINE_TABLE_SIZE (1 << TRAMPOLINE_HASH_BITS)
|
||||
|
||||
static struct hlist_head trampoline_table[TRAMPOLINE_TABLE_SIZE];
|
||||
|
||||
/* serializes access to trampoline_table */
|
||||
static DEFINE_MUTEX(trampoline_mutex);
|
||||
|
||||
struct bpf_trampoline *bpf_trampoline_lookup(u64 key)
|
||||
{
|
||||
struct bpf_trampoline *tr;
|
||||
struct hlist_head *head;
|
||||
void *image;
|
||||
int i;
|
||||
|
||||
mutex_lock(&trampoline_mutex);
|
||||
head = &trampoline_table[hash_64(key, TRAMPOLINE_HASH_BITS)];
|
||||
hlist_for_each_entry(tr, head, hlist) {
|
||||
if (tr->key == key) {
|
||||
refcount_inc(&tr->refcnt);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
tr = kzalloc(sizeof(*tr), GFP_KERNEL);
|
||||
if (!tr)
|
||||
goto out;
|
||||
|
||||
/* is_root was checked earlier. No need for bpf_jit_charge_modmem() */
|
||||
image = bpf_jit_alloc_exec(PAGE_SIZE);
|
||||
if (!image) {
|
||||
kfree(tr);
|
||||
tr = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
tr->key = key;
|
||||
INIT_HLIST_NODE(&tr->hlist);
|
||||
hlist_add_head(&tr->hlist, head);
|
||||
refcount_set(&tr->refcnt, 1);
|
||||
mutex_init(&tr->mutex);
|
||||
for (i = 0; i < BPF_TRAMP_MAX; i++)
|
||||
INIT_HLIST_HEAD(&tr->progs_hlist[i]);
|
||||
|
||||
set_vm_flush_reset_perms(image);
|
||||
/* Keep image as writeable. The alternative is to keep flipping ro/rw
|
||||
* everytime new program is attached or detached.
|
||||
*/
|
||||
set_memory_x((long)image, 1);
|
||||
tr->image = image;
|
||||
out:
|
||||
mutex_unlock(&trampoline_mutex);
|
||||
return tr;
|
||||
}
|
||||
|
||||
/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50
|
||||
* bytes on x86. Pick a number to fit into PAGE_SIZE / 2
|
||||
*/
|
||||
#define BPF_MAX_TRAMP_PROGS 40
|
||||
|
||||
static int bpf_trampoline_update(struct bpf_trampoline *tr)
|
||||
{
|
||||
void *old_image = tr->image + ((tr->selector + 1) & 1) * PAGE_SIZE/2;
|
||||
void *new_image = tr->image + (tr->selector & 1) * PAGE_SIZE/2;
|
||||
struct bpf_prog *progs_to_run[BPF_MAX_TRAMP_PROGS];
|
||||
int fentry_cnt = tr->progs_cnt[BPF_TRAMP_FENTRY];
|
||||
int fexit_cnt = tr->progs_cnt[BPF_TRAMP_FEXIT];
|
||||
struct bpf_prog **progs, **fentry, **fexit;
|
||||
u32 flags = BPF_TRAMP_F_RESTORE_REGS;
|
||||
struct bpf_prog_aux *aux;
|
||||
int err;
|
||||
|
||||
if (fentry_cnt + fexit_cnt == 0) {
|
||||
err = bpf_arch_text_poke(tr->func.addr, BPF_MOD_CALL,
|
||||
old_image, NULL);
|
||||
tr->selector = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* populate fentry progs */
|
||||
fentry = progs = progs_to_run;
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[BPF_TRAMP_FENTRY], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
|
||||
/* populate fexit progs */
|
||||
fexit = progs;
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[BPF_TRAMP_FEXIT], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
|
||||
if (fexit_cnt)
|
||||
flags = BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_SKIP_FRAME;
|
||||
|
||||
err = arch_prepare_bpf_trampoline(new_image, &tr->func.model, flags,
|
||||
fentry, fentry_cnt,
|
||||
fexit, fexit_cnt,
|
||||
tr->func.addr);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (tr->selector)
|
||||
/* progs already running at this address */
|
||||
err = bpf_arch_text_poke(tr->func.addr, BPF_MOD_CALL,
|
||||
old_image, new_image);
|
||||
else
|
||||
/* first time registering */
|
||||
err = bpf_arch_text_poke(tr->func.addr, BPF_MOD_CALL, NULL,
|
||||
new_image);
|
||||
if (err)
|
||||
goto out;
|
||||
tr->selector++;
|
||||
out:
|
||||
return err;
|
||||
}
|
||||
|
||||
static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(enum bpf_attach_type t)
|
||||
{
|
||||
switch (t) {
|
||||
case BPF_TRACE_FENTRY:
|
||||
return BPF_TRAMP_FENTRY;
|
||||
default:
|
||||
return BPF_TRAMP_FEXIT;
|
||||
}
|
||||
}
|
||||
|
||||
int bpf_trampoline_link_prog(struct bpf_prog *prog)
|
||||
{
|
||||
enum bpf_tramp_prog_type kind;
|
||||
struct bpf_trampoline *tr;
|
||||
int err = 0;
|
||||
|
||||
tr = prog->aux->trampoline;
|
||||
kind = bpf_attach_type_to_tramp(prog->expected_attach_type);
|
||||
mutex_lock(&tr->mutex);
|
||||
if (tr->progs_cnt[BPF_TRAMP_FENTRY] + tr->progs_cnt[BPF_TRAMP_FEXIT]
|
||||
>= BPF_MAX_TRAMP_PROGS) {
|
||||
err = -E2BIG;
|
||||
goto out;
|
||||
}
|
||||
if (!hlist_unhashed(&prog->aux->tramp_hlist)) {
|
||||
/* prog already linked */
|
||||
err = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
hlist_add_head(&prog->aux->tramp_hlist, &tr->progs_hlist[kind]);
|
||||
tr->progs_cnt[kind]++;
|
||||
err = bpf_trampoline_update(prog->aux->trampoline);
|
||||
if (err) {
|
||||
hlist_del(&prog->aux->tramp_hlist);
|
||||
tr->progs_cnt[kind]--;
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&tr->mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* bpf_trampoline_unlink_prog() should never fail. */
|
||||
int bpf_trampoline_unlink_prog(struct bpf_prog *prog)
|
||||
{
|
||||
enum bpf_tramp_prog_type kind;
|
||||
struct bpf_trampoline *tr;
|
||||
int err;
|
||||
|
||||
tr = prog->aux->trampoline;
|
||||
kind = bpf_attach_type_to_tramp(prog->expected_attach_type);
|
||||
mutex_lock(&tr->mutex);
|
||||
hlist_del(&prog->aux->tramp_hlist);
|
||||
tr->progs_cnt[kind]--;
|
||||
err = bpf_trampoline_update(prog->aux->trampoline);
|
||||
mutex_unlock(&tr->mutex);
|
||||
return err;
|
||||
}
|
||||
|
||||
void bpf_trampoline_put(struct bpf_trampoline *tr)
|
||||
{
|
||||
if (!tr)
|
||||
return;
|
||||
mutex_lock(&trampoline_mutex);
|
||||
if (!refcount_dec_and_test(&tr->refcnt))
|
||||
goto out;
|
||||
WARN_ON_ONCE(mutex_is_locked(&tr->mutex));
|
||||
if (WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[BPF_TRAMP_FENTRY])))
|
||||
goto out;
|
||||
if (WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[BPF_TRAMP_FEXIT])))
|
||||
goto out;
|
||||
bpf_jit_free_exec(tr->image);
|
||||
hlist_del(&tr->hlist);
|
||||
kfree(tr);
|
||||
out:
|
||||
mutex_unlock(&trampoline_mutex);
|
||||
}
|
||||
|
||||
/* The logic is similar to BPF_PROG_RUN, but with explicit rcu and preempt that
|
||||
* are needed for trampoline. The macro is split into
|
||||
* call _bpf_prog_enter
|
||||
* call prog->bpf_func
|
||||
* call __bpf_prog_exit
|
||||
*/
|
||||
u64 notrace __bpf_prog_enter(void)
|
||||
{
|
||||
u64 start = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
preempt_disable();
|
||||
if (static_branch_unlikely(&bpf_stats_enabled_key))
|
||||
start = sched_clock();
|
||||
return start;
|
||||
}
|
||||
|
||||
void notrace __bpf_prog_exit(struct bpf_prog *prog, u64 start)
|
||||
{
|
||||
struct bpf_prog_stats *stats;
|
||||
|
||||
if (static_branch_unlikely(&bpf_stats_enabled_key) &&
|
||||
/* static_key could be enabled in __bpf_prog_enter
|
||||
* and disabled in __bpf_prog_exit.
|
||||
* And vice versa.
|
||||
* Hence check that 'start' is not zero.
|
||||
*/
|
||||
start) {
|
||||
stats = this_cpu_ptr(prog->aux->stats);
|
||||
u64_stats_update_begin(&stats->syncp);
|
||||
stats->cnt++;
|
||||
stats->nsecs += sched_clock() - start;
|
||||
u64_stats_update_end(&stats->syncp);
|
||||
}
|
||||
preempt_enable();
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
int __weak
|
||||
arch_prepare_bpf_trampoline(void *image, struct btf_func_model *m, u32 flags,
|
||||
struct bpf_prog **fentry_progs, int fentry_cnt,
|
||||
struct bpf_prog **fexit_progs, int fexit_cnt,
|
||||
void *orig_call)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static int __init init_trampolines(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < TRAMPOLINE_TABLE_SIZE; i++)
|
||||
INIT_HLIST_HEAD(&trampoline_table[i]);
|
||||
return 0;
|
||||
}
|
||||
late_initcall(init_trampolines);
|
||||
+506
-37
@@ -23,7 +23,7 @@
|
||||
#include "disasm.h"
|
||||
|
||||
static const struct bpf_verifier_ops * const bpf_verifier_ops[] = {
|
||||
#define BPF_PROG_TYPE(_id, _name) \
|
||||
#define BPF_PROG_TYPE(_id, _name, prog_ctx_type, kern_ctx_type) \
|
||||
[_id] = & _name ## _verifier_ops,
|
||||
#define BPF_MAP_TYPE(_id, _ops)
|
||||
#include <linux/bpf_types.h>
|
||||
@@ -171,6 +171,9 @@ struct bpf_verifier_stack_elem {
|
||||
#define BPF_COMPLEXITY_LIMIT_JMP_SEQ 8192
|
||||
#define BPF_COMPLEXITY_LIMIT_STATES 64
|
||||
|
||||
#define BPF_MAP_KEY_POISON (1ULL << 63)
|
||||
#define BPF_MAP_KEY_SEEN (1ULL << 62)
|
||||
|
||||
#define BPF_MAP_PTR_UNPRIV 1UL
|
||||
#define BPF_MAP_PTR_POISON ((void *)((0xeB9FUL << 1) + \
|
||||
POISON_POINTER_DELTA))
|
||||
@@ -178,12 +181,12 @@ struct bpf_verifier_stack_elem {
|
||||
|
||||
static bool bpf_map_ptr_poisoned(const struct bpf_insn_aux_data *aux)
|
||||
{
|
||||
return BPF_MAP_PTR(aux->map_state) == BPF_MAP_PTR_POISON;
|
||||
return BPF_MAP_PTR(aux->map_ptr_state) == BPF_MAP_PTR_POISON;
|
||||
}
|
||||
|
||||
static bool bpf_map_ptr_unpriv(const struct bpf_insn_aux_data *aux)
|
||||
{
|
||||
return aux->map_state & BPF_MAP_PTR_UNPRIV;
|
||||
return aux->map_ptr_state & BPF_MAP_PTR_UNPRIV;
|
||||
}
|
||||
|
||||
static void bpf_map_ptr_store(struct bpf_insn_aux_data *aux,
|
||||
@@ -191,8 +194,31 @@ static void bpf_map_ptr_store(struct bpf_insn_aux_data *aux,
|
||||
{
|
||||
BUILD_BUG_ON((unsigned long)BPF_MAP_PTR_POISON & BPF_MAP_PTR_UNPRIV);
|
||||
unpriv |= bpf_map_ptr_unpriv(aux);
|
||||
aux->map_state = (unsigned long)map |
|
||||
(unpriv ? BPF_MAP_PTR_UNPRIV : 0UL);
|
||||
aux->map_ptr_state = (unsigned long)map |
|
||||
(unpriv ? BPF_MAP_PTR_UNPRIV : 0UL);
|
||||
}
|
||||
|
||||
static bool bpf_map_key_poisoned(const struct bpf_insn_aux_data *aux)
|
||||
{
|
||||
return aux->map_key_state & BPF_MAP_KEY_POISON;
|
||||
}
|
||||
|
||||
static bool bpf_map_key_unseen(const struct bpf_insn_aux_data *aux)
|
||||
{
|
||||
return !(aux->map_key_state & BPF_MAP_KEY_SEEN);
|
||||
}
|
||||
|
||||
static u64 bpf_map_key_immediate(const struct bpf_insn_aux_data *aux)
|
||||
{
|
||||
return aux->map_key_state & ~(BPF_MAP_KEY_SEEN | BPF_MAP_KEY_POISON);
|
||||
}
|
||||
|
||||
static void bpf_map_key_store(struct bpf_insn_aux_data *aux, u64 state)
|
||||
{
|
||||
bool poisoned = bpf_map_key_poisoned(aux);
|
||||
|
||||
aux->map_key_state = state | BPF_MAP_KEY_SEEN |
|
||||
(poisoned ? BPF_MAP_KEY_POISON : 0ULL);
|
||||
}
|
||||
|
||||
struct bpf_call_arg_meta {
|
||||
@@ -205,8 +231,11 @@ struct bpf_call_arg_meta {
|
||||
u64 msize_umax_value;
|
||||
int ref_obj_id;
|
||||
int func_id;
|
||||
u32 btf_id;
|
||||
};
|
||||
|
||||
struct btf *btf_vmlinux;
|
||||
|
||||
static DEFINE_MUTEX(bpf_verifier_lock);
|
||||
|
||||
static const struct bpf_line_info *
|
||||
@@ -243,6 +272,10 @@ void bpf_verifier_vlog(struct bpf_verifier_log *log, const char *fmt,
|
||||
n = min(log->len_total - log->len_used - 1, n);
|
||||
log->kbuf[n] = '\0';
|
||||
|
||||
if (log->level == BPF_LOG_KERNEL) {
|
||||
pr_err("BPF:%s\n", log->kbuf);
|
||||
return;
|
||||
}
|
||||
if (!copy_to_user(log->ubuf + log->len_used, log->kbuf, n + 1))
|
||||
log->len_used += n;
|
||||
else
|
||||
@@ -280,6 +313,19 @@ __printf(2, 3) static void verbose(void *private_data, const char *fmt, ...)
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
__printf(2, 3) void bpf_log(struct bpf_verifier_log *log,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
if (!bpf_verifier_log_needed(log))
|
||||
return;
|
||||
|
||||
va_start(args, fmt);
|
||||
bpf_verifier_vlog(log, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static const char *ltrim(const char *s)
|
||||
{
|
||||
while (isspace(*s))
|
||||
@@ -400,6 +446,7 @@ static const char * const reg_type_str[] = {
|
||||
[PTR_TO_TCP_SOCK_OR_NULL] = "tcp_sock_or_null",
|
||||
[PTR_TO_TP_BUFFER] = "tp_buffer",
|
||||
[PTR_TO_XDP_SOCK] = "xdp_sock",
|
||||
[PTR_TO_BTF_ID] = "ptr_",
|
||||
};
|
||||
|
||||
static char slot_type_char[] = {
|
||||
@@ -430,6 +477,12 @@ static struct bpf_func_state *func(struct bpf_verifier_env *env,
|
||||
return cur->frame[reg->frameno];
|
||||
}
|
||||
|
||||
const char *kernel_type_name(u32 id)
|
||||
{
|
||||
return btf_name_by_offset(btf_vmlinux,
|
||||
btf_type_by_id(btf_vmlinux, id)->name_off);
|
||||
}
|
||||
|
||||
static void print_verifier_state(struct bpf_verifier_env *env,
|
||||
const struct bpf_func_state *state)
|
||||
{
|
||||
@@ -454,6 +507,8 @@ static void print_verifier_state(struct bpf_verifier_env *env,
|
||||
/* reg->off should be 0 for SCALAR_VALUE */
|
||||
verbose(env, "%lld", reg->var_off.value + reg->off);
|
||||
} else {
|
||||
if (t == PTR_TO_BTF_ID)
|
||||
verbose(env, "%s", kernel_type_name(reg->btf_id));
|
||||
verbose(env, "(id=%d", reg->id);
|
||||
if (reg_type_may_be_refcounted_or_null(t))
|
||||
verbose(env, ",ref_obj_id=%d", reg->ref_obj_id);
|
||||
@@ -978,6 +1033,17 @@ static void __reg_bound_offset(struct bpf_reg_state *reg)
|
||||
reg->umax_value));
|
||||
}
|
||||
|
||||
static void __reg_bound_offset32(struct bpf_reg_state *reg)
|
||||
{
|
||||
u64 mask = 0xffffFFFF;
|
||||
struct tnum range = tnum_range(reg->umin_value & mask,
|
||||
reg->umax_value & mask);
|
||||
struct tnum lo32 = tnum_cast(reg->var_off, 4);
|
||||
struct tnum hi32 = tnum_lshift(tnum_rshift(reg->var_off, 32), 32);
|
||||
|
||||
reg->var_off = tnum_or(hi32, tnum_intersect(lo32, range));
|
||||
}
|
||||
|
||||
/* Reset the min/max bounds of a register */
|
||||
static void __mark_reg_unbounded(struct bpf_reg_state *reg)
|
||||
{
|
||||
@@ -2331,10 +2397,12 @@ static int check_packet_access(struct bpf_verifier_env *env, u32 regno, int off,
|
||||
|
||||
/* check access to 'struct bpf_context' fields. Supports fixed offsets only */
|
||||
static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off, int size,
|
||||
enum bpf_access_type t, enum bpf_reg_type *reg_type)
|
||||
enum bpf_access_type t, enum bpf_reg_type *reg_type,
|
||||
u32 *btf_id)
|
||||
{
|
||||
struct bpf_insn_access_aux info = {
|
||||
.reg_type = *reg_type,
|
||||
.log = &env->log,
|
||||
};
|
||||
|
||||
if (env->ops->is_valid_access &&
|
||||
@@ -2348,7 +2416,10 @@ static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off,
|
||||
*/
|
||||
*reg_type = info.reg_type;
|
||||
|
||||
env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
|
||||
if (*reg_type == PTR_TO_BTF_ID)
|
||||
*btf_id = info.btf_id;
|
||||
else
|
||||
env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
|
||||
/* remember the offset of last byte accessed in ctx */
|
||||
if (env->prog->aux->max_ctx_offset < off + size)
|
||||
env->prog->aux->max_ctx_offset = off + size;
|
||||
@@ -2739,6 +2810,88 @@ static void coerce_reg_to_size(struct bpf_reg_state *reg, int size)
|
||||
reg->smax_value = reg->umax_value;
|
||||
}
|
||||
|
||||
static bool bpf_map_is_rdonly(const struct bpf_map *map)
|
||||
{
|
||||
return (map->map_flags & BPF_F_RDONLY_PROG) && map->frozen;
|
||||
}
|
||||
|
||||
static int bpf_map_direct_read(struct bpf_map *map, int off, int size, u64 *val)
|
||||
{
|
||||
void *ptr;
|
||||
u64 addr;
|
||||
int err;
|
||||
|
||||
err = map->ops->map_direct_value_addr(map, &addr, off);
|
||||
if (err)
|
||||
return err;
|
||||
ptr = (void *)(long)addr + off;
|
||||
|
||||
switch (size) {
|
||||
case sizeof(u8):
|
||||
*val = (u64)*(u8 *)ptr;
|
||||
break;
|
||||
case sizeof(u16):
|
||||
*val = (u64)*(u16 *)ptr;
|
||||
break;
|
||||
case sizeof(u32):
|
||||
*val = (u64)*(u32 *)ptr;
|
||||
break;
|
||||
case sizeof(u64):
|
||||
*val = *(u64 *)ptr;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
|
||||
struct bpf_reg_state *regs,
|
||||
int regno, int off, int size,
|
||||
enum bpf_access_type atype,
|
||||
int value_regno)
|
||||
{
|
||||
struct bpf_reg_state *reg = regs + regno;
|
||||
const struct btf_type *t = btf_type_by_id(btf_vmlinux, reg->btf_id);
|
||||
const char *tname = btf_name_by_offset(btf_vmlinux, t->name_off);
|
||||
u32 btf_id;
|
||||
int ret;
|
||||
|
||||
if (atype != BPF_READ) {
|
||||
verbose(env, "only read is supported\n");
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (off < 0) {
|
||||
verbose(env,
|
||||
"R%d is ptr_%s invalid negative access: off=%d\n",
|
||||
regno, tname, off);
|
||||
return -EACCES;
|
||||
}
|
||||
if (!tnum_is_const(reg->var_off) || reg->var_off.value) {
|
||||
char tn_buf[48];
|
||||
|
||||
tnum_strn(tn_buf, sizeof(tn_buf), reg->var_off);
|
||||
verbose(env,
|
||||
"R%d is ptr_%s invalid variable offset: off=%d, var_off=%s\n",
|
||||
regno, tname, off, tn_buf);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
ret = btf_struct_access(&env->log, t, off, size, atype, &btf_id);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (ret == SCALAR_VALUE) {
|
||||
mark_reg_unknown(env, regs, value_regno);
|
||||
return 0;
|
||||
}
|
||||
mark_reg_known_zero(env, regs, value_regno);
|
||||
regs[value_regno].type = PTR_TO_BTF_ID;
|
||||
regs[value_regno].btf_id = btf_id;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* check whether memory at (regno + off) is accessible for t = (read | write)
|
||||
* if t==write, value_regno is a register which value is stored into memory
|
||||
* if t==read, value_regno is a register which will receive the value from memory
|
||||
@@ -2776,11 +2929,30 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
|
||||
if (err)
|
||||
return err;
|
||||
err = check_map_access(env, regno, off, size, false);
|
||||
if (!err && t == BPF_READ && value_regno >= 0)
|
||||
mark_reg_unknown(env, regs, value_regno);
|
||||
if (!err && t == BPF_READ && value_regno >= 0) {
|
||||
struct bpf_map *map = reg->map_ptr;
|
||||
|
||||
/* if map is read-only, track its contents as scalars */
|
||||
if (tnum_is_const(reg->var_off) &&
|
||||
bpf_map_is_rdonly(map) &&
|
||||
map->ops->map_direct_value_addr) {
|
||||
int map_off = off + reg->var_off.value;
|
||||
u64 val = 0;
|
||||
|
||||
err = bpf_map_direct_read(map, map_off, size,
|
||||
&val);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
regs[value_regno].type = SCALAR_VALUE;
|
||||
__mark_reg_known(®s[value_regno], val);
|
||||
} else {
|
||||
mark_reg_unknown(env, regs, value_regno);
|
||||
}
|
||||
}
|
||||
} else if (reg->type == PTR_TO_CTX) {
|
||||
enum bpf_reg_type reg_type = SCALAR_VALUE;
|
||||
u32 btf_id = 0;
|
||||
|
||||
if (t == BPF_WRITE && value_regno >= 0 &&
|
||||
is_pointer_value(env, value_regno)) {
|
||||
@@ -2792,7 +2964,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
err = check_ctx_access(env, insn_idx, off, size, t, ®_type);
|
||||
err = check_ctx_access(env, insn_idx, off, size, t, ®_type, &btf_id);
|
||||
if (err)
|
||||
verbose_linfo(env, insn_idx, "; ");
|
||||
if (!err && t == BPF_READ && value_regno >= 0) {
|
||||
/* ctx access returns either a scalar, or a
|
||||
* PTR_TO_PACKET[_META,_END]. In the latter
|
||||
@@ -2811,6 +2985,8 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
|
||||
* a sub-register.
|
||||
*/
|
||||
regs[value_regno].subreg_def = DEF_NOT_SUBREG;
|
||||
if (reg_type == PTR_TO_BTF_ID)
|
||||
regs[value_regno].btf_id = btf_id;
|
||||
}
|
||||
regs[value_regno].type = reg_type;
|
||||
}
|
||||
@@ -2870,6 +3046,9 @@ static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regn
|
||||
err = check_tp_buffer_access(env, reg, regno, off, size);
|
||||
if (!err && t == BPF_READ && value_regno >= 0)
|
||||
mark_reg_unknown(env, regs, value_regno);
|
||||
} else if (reg->type == PTR_TO_BTF_ID) {
|
||||
err = check_ptr_to_btf_access(env, regs, regno, off, size, t,
|
||||
value_regno);
|
||||
} else {
|
||||
verbose(env, "R%d invalid mem access '%s'\n", regno,
|
||||
reg_type_str[reg->type]);
|
||||
@@ -3298,6 +3477,22 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 regno,
|
||||
expected_type = PTR_TO_SOCKET;
|
||||
if (type != expected_type)
|
||||
goto err_type;
|
||||
} else if (arg_type == ARG_PTR_TO_BTF_ID) {
|
||||
expected_type = PTR_TO_BTF_ID;
|
||||
if (type != expected_type)
|
||||
goto err_type;
|
||||
if (reg->btf_id != meta->btf_id) {
|
||||
verbose(env, "Helper has type %s got %s in R%d\n",
|
||||
kernel_type_name(meta->btf_id),
|
||||
kernel_type_name(reg->btf_id), regno);
|
||||
|
||||
return -EACCES;
|
||||
}
|
||||
if (!tnum_is_const(reg->var_off) || reg->var_off.value || reg->off) {
|
||||
verbose(env, "R%d is a pointer to in-kernel struct with non-zero offset\n",
|
||||
regno);
|
||||
return -EACCES;
|
||||
}
|
||||
} else if (arg_type == ARG_PTR_TO_SPIN_LOCK) {
|
||||
if (meta->func_id == BPF_FUNC_spin_lock) {
|
||||
if (process_spin_lock(env, regno, true))
|
||||
@@ -3445,6 +3640,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
|
||||
case BPF_MAP_TYPE_PERF_EVENT_ARRAY:
|
||||
if (func_id != BPF_FUNC_perf_event_read &&
|
||||
func_id != BPF_FUNC_perf_event_output &&
|
||||
func_id != BPF_FUNC_skb_output &&
|
||||
func_id != BPF_FUNC_perf_event_read_value)
|
||||
goto error;
|
||||
break;
|
||||
@@ -3532,6 +3728,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env,
|
||||
case BPF_FUNC_perf_event_read:
|
||||
case BPF_FUNC_perf_event_output:
|
||||
case BPF_FUNC_perf_event_read_value:
|
||||
case BPF_FUNC_skb_output:
|
||||
if (map->map_type != BPF_MAP_TYPE_PERF_EVENT_ARRAY)
|
||||
goto error;
|
||||
break;
|
||||
@@ -3810,6 +4007,9 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
||||
/* only increment it after check_reg_arg() finished */
|
||||
state->curframe++;
|
||||
|
||||
if (btf_check_func_arg_match(env, subprog))
|
||||
return -EINVAL;
|
||||
|
||||
/* and go analyze first insn of the callee */
|
||||
*insn_idx = target_insn;
|
||||
|
||||
@@ -3916,15 +4116,49 @@ record_func_map(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (!BPF_MAP_PTR(aux->map_state))
|
||||
if (!BPF_MAP_PTR(aux->map_ptr_state))
|
||||
bpf_map_ptr_store(aux, meta->map_ptr,
|
||||
meta->map_ptr->unpriv_array);
|
||||
else if (BPF_MAP_PTR(aux->map_state) != meta->map_ptr)
|
||||
else if (BPF_MAP_PTR(aux->map_ptr_state) != meta->map_ptr)
|
||||
bpf_map_ptr_store(aux, BPF_MAP_PTR_POISON,
|
||||
meta->map_ptr->unpriv_array);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
record_func_key(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
|
||||
int func_id, int insn_idx)
|
||||
{
|
||||
struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx];
|
||||
struct bpf_reg_state *regs = cur_regs(env), *reg;
|
||||
struct bpf_map *map = meta->map_ptr;
|
||||
struct tnum range;
|
||||
u64 val;
|
||||
|
||||
if (func_id != BPF_FUNC_tail_call)
|
||||
return 0;
|
||||
if (!map || map->map_type != BPF_MAP_TYPE_PROG_ARRAY) {
|
||||
verbose(env, "kernel subsystem misconfigured verifier\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
range = tnum_range(0, map->max_entries - 1);
|
||||
reg = ®s[BPF_REG_3];
|
||||
|
||||
if (!register_is_const(reg) || !tnum_in(range, reg->var_off)) {
|
||||
bpf_map_key_store(aux, BPF_MAP_KEY_POISON);
|
||||
return 0;
|
||||
}
|
||||
|
||||
val = reg->var_off.value;
|
||||
if (bpf_map_key_unseen(aux))
|
||||
bpf_map_key_store(aux, val);
|
||||
else if (!bpf_map_key_poisoned(aux) &&
|
||||
bpf_map_key_immediate(aux) != val)
|
||||
bpf_map_key_store(aux, BPF_MAP_KEY_POISON);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_reference_leak(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_func_state *state = cur_func(env);
|
||||
@@ -3986,23 +4220,20 @@ static int check_helper_call(struct bpf_verifier_env *env, int func_id, int insn
|
||||
|
||||
meta.func_id = func_id;
|
||||
/* check args */
|
||||
err = check_func_arg(env, BPF_REG_1, fn->arg1_type, &meta);
|
||||
if (err)
|
||||
return err;
|
||||
err = check_func_arg(env, BPF_REG_2, fn->arg2_type, &meta);
|
||||
if (err)
|
||||
return err;
|
||||
err = check_func_arg(env, BPF_REG_3, fn->arg3_type, &meta);
|
||||
if (err)
|
||||
return err;
|
||||
err = check_func_arg(env, BPF_REG_4, fn->arg4_type, &meta);
|
||||
if (err)
|
||||
return err;
|
||||
err = check_func_arg(env, BPF_REG_5, fn->arg5_type, &meta);
|
||||
for (i = 0; i < 5; i++) {
|
||||
err = btf_resolve_helper_id(&env->log, fn, i);
|
||||
if (err > 0)
|
||||
meta.btf_id = err;
|
||||
err = check_func_arg(env, BPF_REG_1 + i, fn->arg_type[i], &meta);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
err = record_func_map(env, &meta, func_id, insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = record_func_map(env, &meta, func_id, insn_idx);
|
||||
err = record_func_key(env, &meta, func_id, insn_idx);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
@@ -5433,6 +5664,10 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg,
|
||||
/* We might have learned some bits from the bounds. */
|
||||
__reg_bound_offset(false_reg);
|
||||
__reg_bound_offset(true_reg);
|
||||
if (is_jmp32) {
|
||||
__reg_bound_offset32(false_reg);
|
||||
__reg_bound_offset32(true_reg);
|
||||
}
|
||||
/* Intersecting with the old var_off might have improved our bounds
|
||||
* slightly. e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc),
|
||||
* then new var_off is (0; 0x7f...fc) which improves our umax.
|
||||
@@ -5542,6 +5777,10 @@ static void reg_set_min_max_inv(struct bpf_reg_state *true_reg,
|
||||
/* We might have learned some bits from the bounds. */
|
||||
__reg_bound_offset(false_reg);
|
||||
__reg_bound_offset(true_reg);
|
||||
if (is_jmp32) {
|
||||
__reg_bound_offset32(false_reg);
|
||||
__reg_bound_offset32(true_reg);
|
||||
}
|
||||
/* Intersecting with the old var_off might have improved our bounds
|
||||
* slightly. e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc),
|
||||
* then new var_off is (0; 0x7f...fc) which improves our umax.
|
||||
@@ -6124,6 +6363,11 @@ static int check_return_code(struct bpf_verifier_env *env)
|
||||
case BPF_PROG_TYPE_CGROUP_SYSCTL:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
|
||||
break;
|
||||
case BPF_PROG_TYPE_RAW_TRACEPOINT:
|
||||
if (!env->prog->aux->attach_btf_id)
|
||||
return 0;
|
||||
range = tnum_const(0);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -6406,6 +6650,7 @@ static int check_btf_func(struct bpf_verifier_env *env,
|
||||
u32 i, nfuncs, urec_size, min_size;
|
||||
u32 krec_size = sizeof(struct bpf_func_info);
|
||||
struct bpf_func_info *krecord;
|
||||
struct bpf_func_info_aux *info_aux = NULL;
|
||||
const struct btf_type *type;
|
||||
struct bpf_prog *prog;
|
||||
const struct btf *btf;
|
||||
@@ -6439,6 +6684,9 @@ static int check_btf_func(struct bpf_verifier_env *env,
|
||||
krecord = kvcalloc(nfuncs, krec_size, GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!krecord)
|
||||
return -ENOMEM;
|
||||
info_aux = kcalloc(nfuncs, sizeof(*info_aux), GFP_KERNEL | __GFP_NOWARN);
|
||||
if (!info_aux)
|
||||
goto err_free;
|
||||
|
||||
for (i = 0; i < nfuncs; i++) {
|
||||
ret = bpf_check_uarg_tail_zero(urecord, krec_size, urec_size);
|
||||
@@ -6490,29 +6738,31 @@ static int check_btf_func(struct bpf_verifier_env *env,
|
||||
ret = -EINVAL;
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
prev_offset = krecord[i].insn_off;
|
||||
urecord += urec_size;
|
||||
}
|
||||
|
||||
prog->aux->func_info = krecord;
|
||||
prog->aux->func_info_cnt = nfuncs;
|
||||
prog->aux->func_info_aux = info_aux;
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
kvfree(krecord);
|
||||
kfree(info_aux);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void adjust_btf_func(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_prog_aux *aux = env->prog->aux;
|
||||
int i;
|
||||
|
||||
if (!env->prog->aux->func_info)
|
||||
if (!aux->func_info)
|
||||
return;
|
||||
|
||||
for (i = 0; i < env->subprog_cnt; i++)
|
||||
env->prog->aux->func_info[i].insn_off = env->subprog_info[i].start;
|
||||
aux->func_info[i].insn_off = env->subprog_info[i].start;
|
||||
}
|
||||
|
||||
#define MIN_BPF_LINEINFO_SIZE (offsetof(struct bpf_line_info, line_col) + \
|
||||
@@ -7440,6 +7690,7 @@ static bool reg_type_mismatch_ok(enum bpf_reg_type type)
|
||||
case PTR_TO_TCP_SOCK:
|
||||
case PTR_TO_TCP_SOCK_OR_NULL:
|
||||
case PTR_TO_XDP_SOCK:
|
||||
case PTR_TO_BTF_ID:
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
@@ -7492,6 +7743,9 @@ static int do_check(struct bpf_verifier_env *env)
|
||||
0 /* frameno */,
|
||||
0 /* subprogno, zero == main subprog */);
|
||||
|
||||
if (btf_check_func_arg_match(env, 0))
|
||||
return -EINVAL;
|
||||
|
||||
for (;;) {
|
||||
struct bpf_insn *insn;
|
||||
u8 class;
|
||||
@@ -8008,11 +8262,7 @@ static int replace_map_fd_with_map_ptr(struct bpf_verifier_env *env)
|
||||
* will be used by the valid program until it's unloaded
|
||||
* and all maps are released in free_used_maps()
|
||||
*/
|
||||
map = bpf_map_inc(map, false);
|
||||
if (IS_ERR(map)) {
|
||||
fdput(f);
|
||||
return PTR_ERR(map);
|
||||
}
|
||||
bpf_map_inc(map);
|
||||
|
||||
aux->map_index = env->used_map_cnt;
|
||||
env->used_maps[env->used_map_cnt++] = map;
|
||||
@@ -8581,6 +8831,14 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
|
||||
case PTR_TO_XDP_SOCK:
|
||||
convert_ctx_access = bpf_xdp_sock_convert_ctx_access;
|
||||
break;
|
||||
case PTR_TO_BTF_ID:
|
||||
if (type == BPF_WRITE) {
|
||||
verbose(env, "Writes through BTF pointers are not allowed\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
insn->code = BPF_LDX | BPF_PROBE_MEM | BPF_SIZE((insn)->code);
|
||||
env->prog->aux->num_exentries++;
|
||||
continue;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
@@ -8871,6 +9129,7 @@ static int fixup_call_args(struct bpf_verifier_env *env)
|
||||
static int fixup_bpf_calls(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_prog *prog = env->prog;
|
||||
bool expect_blinding = bpf_jit_blinding_enabled(prog);
|
||||
struct bpf_insn *insn = prog->insnsi;
|
||||
const struct bpf_func_proto *fn;
|
||||
const int insn_cnt = prog->len;
|
||||
@@ -8879,7 +9138,7 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env)
|
||||
struct bpf_insn insn_buf[16];
|
||||
struct bpf_prog *new_prog;
|
||||
struct bpf_map *map_ptr;
|
||||
int i, cnt, delta = 0;
|
||||
int i, ret, cnt, delta = 0;
|
||||
|
||||
for (i = 0; i < insn_cnt; i++, insn++) {
|
||||
if (insn->code == (BPF_ALU64 | BPF_MOD | BPF_X) ||
|
||||
@@ -9023,6 +9282,26 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env)
|
||||
insn->code = BPF_JMP | BPF_TAIL_CALL;
|
||||
|
||||
aux = &env->insn_aux_data[i + delta];
|
||||
if (prog->jit_requested && !expect_blinding &&
|
||||
!bpf_map_key_poisoned(aux) &&
|
||||
!bpf_map_ptr_poisoned(aux) &&
|
||||
!bpf_map_ptr_unpriv(aux)) {
|
||||
struct bpf_jit_poke_descriptor desc = {
|
||||
.reason = BPF_POKE_REASON_TAIL_CALL,
|
||||
.tail_call.map = BPF_MAP_PTR(aux->map_ptr_state),
|
||||
.tail_call.key = bpf_map_key_immediate(aux),
|
||||
};
|
||||
|
||||
ret = bpf_jit_add_poke_descriptor(prog, &desc);
|
||||
if (ret < 0) {
|
||||
verbose(env, "adding tail call poke descriptor failed\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
insn->imm = ret + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!bpf_map_ptr_unpriv(aux))
|
||||
continue;
|
||||
|
||||
@@ -9037,7 +9316,7 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
map_ptr = BPF_MAP_PTR(aux->map_state);
|
||||
map_ptr = BPF_MAP_PTR(aux->map_ptr_state);
|
||||
insn_buf[0] = BPF_JMP_IMM(BPF_JGE, BPF_REG_3,
|
||||
map_ptr->max_entries, 2);
|
||||
insn_buf[1] = BPF_ALU32_IMM(BPF_AND, BPF_REG_3,
|
||||
@@ -9071,7 +9350,7 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env)
|
||||
if (bpf_map_ptr_poisoned(aux))
|
||||
goto patch_call_imm;
|
||||
|
||||
map_ptr = BPF_MAP_PTR(aux->map_state);
|
||||
map_ptr = BPF_MAP_PTR(aux->map_ptr_state);
|
||||
ops = map_ptr->ops;
|
||||
if (insn->imm == BPF_FUNC_map_lookup_elem &&
|
||||
ops->map_gen_lookup) {
|
||||
@@ -9151,6 +9430,23 @@ patch_call_imm:
|
||||
insn->imm = fn->func - __bpf_call_base;
|
||||
}
|
||||
|
||||
/* Since poke tab is now finalized, publish aux to tracker. */
|
||||
for (i = 0; i < prog->aux->size_poke_tab; i++) {
|
||||
map_ptr = prog->aux->poke_tab[i].tail_call.map;
|
||||
if (!map_ptr->ops->map_poke_track ||
|
||||
!map_ptr->ops->map_poke_untrack ||
|
||||
!map_ptr->ops->map_poke_run) {
|
||||
verbose(env, "bpf verifier is misconfigured\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = map_ptr->ops->map_poke_track(map_ptr, prog->aux);
|
||||
if (ret < 0) {
|
||||
verbose(env, "tracking tail call prog failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -9208,6 +9504,161 @@ static void print_verification_stats(struct bpf_verifier_env *env)
|
||||
env->peak_states, env->longest_mark_read_walk);
|
||||
}
|
||||
|
||||
static int check_attach_btf_id(struct bpf_verifier_env *env)
|
||||
{
|
||||
struct bpf_prog *prog = env->prog;
|
||||
struct bpf_prog *tgt_prog = prog->aux->linked_prog;
|
||||
u32 btf_id = prog->aux->attach_btf_id;
|
||||
const char prefix[] = "btf_trace_";
|
||||
int ret = 0, subprog = -1, i;
|
||||
struct bpf_trampoline *tr;
|
||||
const struct btf_type *t;
|
||||
bool conservative = true;
|
||||
const char *tname;
|
||||
struct btf *btf;
|
||||
long addr;
|
||||
u64 key;
|
||||
|
||||
if (prog->type != BPF_PROG_TYPE_TRACING)
|
||||
return 0;
|
||||
|
||||
if (!btf_id) {
|
||||
verbose(env, "Tracing programs must provide btf_id\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
btf = bpf_prog_get_target_btf(prog);
|
||||
if (!btf) {
|
||||
verbose(env,
|
||||
"FENTRY/FEXIT program can only be attached to another program annotated with BTF\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
t = btf_type_by_id(btf, btf_id);
|
||||
if (!t) {
|
||||
verbose(env, "attach_btf_id %u is invalid\n", btf_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
tname = btf_name_by_offset(btf, t->name_off);
|
||||
if (!tname) {
|
||||
verbose(env, "attach_btf_id %u doesn't have a name\n", btf_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (tgt_prog) {
|
||||
struct bpf_prog_aux *aux = tgt_prog->aux;
|
||||
|
||||
for (i = 0; i < aux->func_info_cnt; i++)
|
||||
if (aux->func_info[i].type_id == btf_id) {
|
||||
subprog = i;
|
||||
break;
|
||||
}
|
||||
if (subprog == -1) {
|
||||
verbose(env, "Subprog %s doesn't exist\n", tname);
|
||||
return -EINVAL;
|
||||
}
|
||||
conservative = aux->func_info_aux[subprog].unreliable;
|
||||
key = ((u64)aux->id) << 32 | btf_id;
|
||||
} else {
|
||||
key = btf_id;
|
||||
}
|
||||
|
||||
switch (prog->expected_attach_type) {
|
||||
case BPF_TRACE_RAW_TP:
|
||||
if (tgt_prog) {
|
||||
verbose(env,
|
||||
"Only FENTRY/FEXIT progs are attachable to another BPF prog\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if (!btf_type_is_typedef(t)) {
|
||||
verbose(env, "attach_btf_id %u is not a typedef\n",
|
||||
btf_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (strncmp(prefix, tname, sizeof(prefix) - 1)) {
|
||||
verbose(env, "attach_btf_id %u points to wrong type name %s\n",
|
||||
btf_id, tname);
|
||||
return -EINVAL;
|
||||
}
|
||||
tname += sizeof(prefix) - 1;
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!btf_type_is_ptr(t))
|
||||
/* should never happen in valid vmlinux build */
|
||||
return -EINVAL;
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!btf_type_is_func_proto(t))
|
||||
/* should never happen in valid vmlinux build */
|
||||
return -EINVAL;
|
||||
|
||||
/* remember two read only pointers that are valid for
|
||||
* the life time of the kernel
|
||||
*/
|
||||
prog->aux->attach_func_name = tname;
|
||||
prog->aux->attach_func_proto = t;
|
||||
prog->aux->attach_btf_trace = true;
|
||||
return 0;
|
||||
case BPF_TRACE_FENTRY:
|
||||
case BPF_TRACE_FEXIT:
|
||||
if (!btf_type_is_func(t)) {
|
||||
verbose(env, "attach_btf_id %u is not a function\n",
|
||||
btf_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
if (!btf_type_is_func_proto(t))
|
||||
return -EINVAL;
|
||||
tr = bpf_trampoline_lookup(key);
|
||||
if (!tr)
|
||||
return -ENOMEM;
|
||||
prog->aux->attach_func_name = tname;
|
||||
/* t is either vmlinux type or another program's type */
|
||||
prog->aux->attach_func_proto = t;
|
||||
mutex_lock(&tr->mutex);
|
||||
if (tr->func.addr) {
|
||||
prog->aux->trampoline = tr;
|
||||
goto out;
|
||||
}
|
||||
if (tgt_prog && conservative) {
|
||||
prog->aux->attach_func_proto = NULL;
|
||||
t = NULL;
|
||||
}
|
||||
ret = btf_distill_func_proto(&env->log, btf, t,
|
||||
tname, &tr->func.model);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
if (tgt_prog) {
|
||||
if (!tgt_prog->jited) {
|
||||
/* for now */
|
||||
verbose(env, "Can trace only JITed BPF progs\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
if (tgt_prog->type == BPF_PROG_TYPE_TRACING) {
|
||||
/* prevent cycles */
|
||||
verbose(env, "Cannot recursively attach\n");
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
addr = (long) tgt_prog->aux->func[subprog]->bpf_func;
|
||||
} else {
|
||||
addr = kallsyms_lookup_name(tname);
|
||||
if (!addr) {
|
||||
verbose(env,
|
||||
"The address of function %s cannot be found\n",
|
||||
tname);
|
||||
ret = -ENOENT;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
tr->func.addr = (void *)addr;
|
||||
prog->aux->trampoline = tr;
|
||||
out:
|
||||
mutex_unlock(&tr->mutex);
|
||||
if (ret)
|
||||
bpf_trampoline_put(tr);
|
||||
return ret;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
int bpf_check(struct bpf_prog **prog, union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
@@ -9241,6 +9692,13 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr,
|
||||
env->ops = bpf_verifier_ops[env->prog->type];
|
||||
is_priv = capable(CAP_SYS_ADMIN);
|
||||
|
||||
if (!btf_vmlinux && IS_ENABLED(CONFIG_DEBUG_INFO_BTF)) {
|
||||
mutex_lock(&bpf_verifier_lock);
|
||||
if (!btf_vmlinux)
|
||||
btf_vmlinux = btf_parse_vmlinux();
|
||||
mutex_unlock(&bpf_verifier_lock);
|
||||
}
|
||||
|
||||
/* grab the mutex to protect few globals used by verifier */
|
||||
if (!is_priv)
|
||||
mutex_lock(&bpf_verifier_lock);
|
||||
@@ -9260,6 +9718,17 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr,
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
if (IS_ERR(btf_vmlinux)) {
|
||||
/* Either gcc or pahole or kernel are broken. */
|
||||
verbose(env, "in-kernel BTF is malformed\n");
|
||||
ret = PTR_ERR(btf_vmlinux);
|
||||
goto skip_full_check;
|
||||
}
|
||||
|
||||
ret = check_attach_btf_id(env);
|
||||
if (ret)
|
||||
goto skip_full_check;
|
||||
|
||||
env->strict_alignment = !!(attr->prog_flags & BPF_F_STRICT_ALIGNMENT);
|
||||
if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS))
|
||||
env->strict_alignment = true;
|
||||
|
||||
+37
-79
@@ -9,19 +9,10 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched.h>
|
||||
|
||||
struct xsk_map {
|
||||
struct bpf_map map;
|
||||
struct xdp_sock **xsk_map;
|
||||
struct list_head __percpu *flush_list;
|
||||
spinlock_t lock; /* Synchronize map updates */
|
||||
};
|
||||
|
||||
int xsk_map_inc(struct xsk_map *map)
|
||||
{
|
||||
struct bpf_map *m = &map->map;
|
||||
|
||||
m = bpf_map_inc(m, false);
|
||||
return PTR_ERR_OR_ZERO(m);
|
||||
bpf_map_inc(&map->map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void xsk_map_put(struct xsk_map *map)
|
||||
@@ -80,9 +71,10 @@ static void xsk_map_sock_delete(struct xdp_sock *xs,
|
||||
|
||||
static struct bpf_map *xsk_map_alloc(union bpf_attr *attr)
|
||||
{
|
||||
struct bpf_map_memory mem;
|
||||
int cpu, err, numa_node;
|
||||
struct xsk_map *m;
|
||||
int cpu, err;
|
||||
u64 cost;
|
||||
u64 cost, size;
|
||||
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return ERR_PTR(-EPERM);
|
||||
@@ -92,44 +84,35 @@ static struct bpf_map *xsk_map_alloc(union bpf_attr *attr)
|
||||
attr->map_flags & ~(BPF_F_NUMA_NODE | BPF_F_RDONLY | BPF_F_WRONLY))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
m = kzalloc(sizeof(*m), GFP_USER);
|
||||
if (!m)
|
||||
numa_node = bpf_map_attr_numa_node(attr);
|
||||
size = struct_size(m, xsk_map, attr->max_entries);
|
||||
cost = size + array_size(sizeof(*m->flush_list), num_possible_cpus());
|
||||
|
||||
err = bpf_map_charge_init(&mem, cost);
|
||||
if (err < 0)
|
||||
return ERR_PTR(err);
|
||||
|
||||
m = bpf_map_area_alloc(size, numa_node);
|
||||
if (!m) {
|
||||
bpf_map_charge_finish(&mem);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
bpf_map_init_from_attr(&m->map, attr);
|
||||
bpf_map_charge_move(&m->map.memory, &mem);
|
||||
spin_lock_init(&m->lock);
|
||||
|
||||
cost = (u64)m->map.max_entries * sizeof(struct xdp_sock *);
|
||||
cost += sizeof(struct list_head) * num_possible_cpus();
|
||||
|
||||
/* Notice returns -EPERM on if map size is larger than memlock limit */
|
||||
err = bpf_map_charge_init(&m->map.memory, cost);
|
||||
if (err)
|
||||
goto free_m;
|
||||
|
||||
err = -ENOMEM;
|
||||
|
||||
m->flush_list = alloc_percpu(struct list_head);
|
||||
if (!m->flush_list)
|
||||
goto free_charge;
|
||||
if (!m->flush_list) {
|
||||
bpf_map_charge_finish(&m->map.memory);
|
||||
bpf_map_area_free(m);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
for_each_possible_cpu(cpu)
|
||||
INIT_LIST_HEAD(per_cpu_ptr(m->flush_list, cpu));
|
||||
|
||||
m->xsk_map = bpf_map_area_alloc(m->map.max_entries *
|
||||
sizeof(struct xdp_sock *),
|
||||
m->map.numa_node);
|
||||
if (!m->xsk_map)
|
||||
goto free_percpu;
|
||||
return &m->map;
|
||||
|
||||
free_percpu:
|
||||
free_percpu(m->flush_list);
|
||||
free_charge:
|
||||
bpf_map_charge_finish(&m->map.memory);
|
||||
free_m:
|
||||
kfree(m);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
static void xsk_map_free(struct bpf_map *map)
|
||||
@@ -139,8 +122,7 @@ static void xsk_map_free(struct bpf_map *map)
|
||||
bpf_clear_redirect_map(map);
|
||||
synchronize_net();
|
||||
free_percpu(m->flush_list);
|
||||
bpf_map_area_free(m->xsk_map);
|
||||
kfree(m);
|
||||
bpf_map_area_free(m);
|
||||
}
|
||||
|
||||
static int xsk_map_get_next_key(struct bpf_map *map, void *key, void *next_key)
|
||||
@@ -160,45 +142,20 @@ static int xsk_map_get_next_key(struct bpf_map *map, void *key, void *next_key)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct xdp_sock *__xsk_map_lookup_elem(struct bpf_map *map, u32 key)
|
||||
static u32 xsk_map_gen_lookup(struct bpf_map *map, struct bpf_insn *insn_buf)
|
||||
{
|
||||
struct xsk_map *m = container_of(map, struct xsk_map, map);
|
||||
struct xdp_sock *xs;
|
||||
const int ret = BPF_REG_0, mp = BPF_REG_1, index = BPF_REG_2;
|
||||
struct bpf_insn *insn = insn_buf;
|
||||
|
||||
if (key >= map->max_entries)
|
||||
return NULL;
|
||||
|
||||
xs = READ_ONCE(m->xsk_map[key]);
|
||||
return xs;
|
||||
}
|
||||
|
||||
int __xsk_map_redirect(struct bpf_map *map, struct xdp_buff *xdp,
|
||||
struct xdp_sock *xs)
|
||||
{
|
||||
struct xsk_map *m = container_of(map, struct xsk_map, map);
|
||||
struct list_head *flush_list = this_cpu_ptr(m->flush_list);
|
||||
int err;
|
||||
|
||||
err = xsk_rcv(xs, xdp);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (!xs->flush_node.prev)
|
||||
list_add(&xs->flush_node, flush_list);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __xsk_map_flush(struct bpf_map *map)
|
||||
{
|
||||
struct xsk_map *m = container_of(map, struct xsk_map, map);
|
||||
struct list_head *flush_list = this_cpu_ptr(m->flush_list);
|
||||
struct xdp_sock *xs, *tmp;
|
||||
|
||||
list_for_each_entry_safe(xs, tmp, flush_list, flush_node) {
|
||||
xsk_flush(xs);
|
||||
__list_del_clearprev(&xs->flush_node);
|
||||
}
|
||||
*insn++ = BPF_LDX_MEM(BPF_W, ret, index, 0);
|
||||
*insn++ = BPF_JMP_IMM(BPF_JGE, ret, map->max_entries, 5);
|
||||
*insn++ = BPF_ALU64_IMM(BPF_LSH, ret, ilog2(sizeof(struct xsk_sock *)));
|
||||
*insn++ = BPF_ALU64_IMM(BPF_ADD, mp, offsetof(struct xsk_map, xsk_map));
|
||||
*insn++ = BPF_ALU64_REG(BPF_ADD, ret, mp);
|
||||
*insn++ = BPF_LDX_MEM(BPF_SIZEOF(struct xsk_sock *), ret, ret, 0);
|
||||
*insn++ = BPF_JMP_IMM(BPF_JA, 0, 0, 1);
|
||||
*insn++ = BPF_MOV64_IMM(ret, 0);
|
||||
return insn - insn_buf;
|
||||
}
|
||||
|
||||
static void *xsk_map_lookup_elem(struct bpf_map *map, void *key)
|
||||
@@ -312,6 +269,7 @@ const struct bpf_map_ops xsk_map_ops = {
|
||||
.map_free = xsk_map_free,
|
||||
.map_get_next_key = xsk_map_get_next_key,
|
||||
.map_lookup_elem = xsk_map_lookup_elem,
|
||||
.map_gen_lookup = xsk_map_gen_lookup,
|
||||
.map_lookup_elem_sys_only = xsk_map_lookup_elem_sys_only,
|
||||
.map_update_elem = xsk_map_update_elem,
|
||||
.map_delete_elem = xsk_map_delete_elem,
|
||||
|
||||
@@ -231,9 +231,10 @@ int cgroup_migrate(struct task_struct *leader, bool threadgroup,
|
||||
|
||||
int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,
|
||||
bool threadgroup);
|
||||
struct task_struct *cgroup_procs_write_start(char *buf, bool threadgroup)
|
||||
struct task_struct *cgroup_procs_write_start(char *buf, bool threadgroup,
|
||||
bool *locked)
|
||||
__acquires(&cgroup_threadgroup_rwsem);
|
||||
void cgroup_procs_write_finish(struct task_struct *task)
|
||||
void cgroup_procs_write_finish(struct task_struct *task, bool locked)
|
||||
__releases(&cgroup_threadgroup_rwsem);
|
||||
|
||||
void cgroup_lock_and_drain_offline(struct cgroup *cgrp);
|
||||
|
||||
@@ -495,12 +495,13 @@ static ssize_t __cgroup1_procs_write(struct kernfs_open_file *of,
|
||||
struct task_struct *task;
|
||||
const struct cred *cred, *tcred;
|
||||
ssize_t ret;
|
||||
bool locked;
|
||||
|
||||
cgrp = cgroup_kn_lock_live(of->kn, false);
|
||||
if (!cgrp)
|
||||
return -ENODEV;
|
||||
|
||||
task = cgroup_procs_write_start(buf, threadgroup);
|
||||
task = cgroup_procs_write_start(buf, threadgroup, &locked);
|
||||
ret = PTR_ERR_OR_ZERO(task);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
@@ -522,7 +523,7 @@ static ssize_t __cgroup1_procs_write(struct kernfs_open_file *of,
|
||||
ret = cgroup_attach_task(cgrp, task, threadgroup);
|
||||
|
||||
out_finish:
|
||||
cgroup_procs_write_finish(task);
|
||||
cgroup_procs_write_finish(task, locked);
|
||||
out_unlock:
|
||||
cgroup_kn_unlock(of->kn);
|
||||
|
||||
|
||||
+103
-224
@@ -899,8 +899,7 @@ static void css_set_move_task(struct task_struct *task,
|
||||
/*
|
||||
* We are synchronized through cgroup_threadgroup_rwsem
|
||||
* against PF_EXITING setting such that we can't race
|
||||
* against cgroup_exit() changing the css_set to
|
||||
* init_css_set and dropping the old one.
|
||||
* against cgroup_exit()/cgroup_free() dropping the css_set.
|
||||
*/
|
||||
WARN_ON_ONCE(task->flags & PF_EXITING);
|
||||
|
||||
@@ -1309,10 +1308,7 @@ static void cgroup_exit_root_id(struct cgroup_root *root)
|
||||
|
||||
void cgroup_free_root(struct cgroup_root *root)
|
||||
{
|
||||
if (root) {
|
||||
idr_destroy(&root->cgroup_idr);
|
||||
kfree(root);
|
||||
}
|
||||
kfree(root);
|
||||
}
|
||||
|
||||
static void cgroup_destroy_root(struct cgroup_root *root)
|
||||
@@ -1374,6 +1370,8 @@ current_cgns_cgroup_from_root(struct cgroup_root *root)
|
||||
cset = current->nsproxy->cgroup_ns->root_cset;
|
||||
if (cset == &init_css_set) {
|
||||
res = &root->cgrp;
|
||||
} else if (root == &cgrp_dfl_root) {
|
||||
res = cset->dfl_cgrp;
|
||||
} else {
|
||||
struct cgrp_cset_link *link;
|
||||
|
||||
@@ -1430,9 +1428,8 @@ struct cgroup *task_cgroup_from_root(struct task_struct *task,
|
||||
struct cgroup_root *root)
|
||||
{
|
||||
/*
|
||||
* No need to lock the task - since we hold cgroup_mutex the
|
||||
* task can't change groups, so the only thing that can happen
|
||||
* is that it exits and its css is set back to init_css_set.
|
||||
* No need to lock the task - since we hold css_set_lock the
|
||||
* task can't change groups.
|
||||
*/
|
||||
return cset_cgroup_from_root(task_css_set(task), root);
|
||||
}
|
||||
@@ -1883,65 +1880,6 @@ static int cgroup_reconfigure(struct fs_context *fc)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* To reduce the fork() overhead for systems that are not actually using
|
||||
* their cgroups capability, we don't maintain the lists running through
|
||||
* each css_set to its tasks until we see the list actually used - in other
|
||||
* words after the first mount.
|
||||
*/
|
||||
static bool use_task_css_set_links __read_mostly;
|
||||
|
||||
void cgroup_enable_task_cg_lists(void)
|
||||
{
|
||||
struct task_struct *p, *g;
|
||||
|
||||
/*
|
||||
* We need tasklist_lock because RCU is not safe against
|
||||
* while_each_thread(). Besides, a forking task that has passed
|
||||
* cgroup_post_fork() without seeing use_task_css_set_links = 1
|
||||
* is not guaranteed to have its child immediately visible in the
|
||||
* tasklist if we walk through it with RCU.
|
||||
*/
|
||||
read_lock(&tasklist_lock);
|
||||
spin_lock_irq(&css_set_lock);
|
||||
|
||||
if (use_task_css_set_links)
|
||||
goto out_unlock;
|
||||
|
||||
use_task_css_set_links = true;
|
||||
|
||||
do_each_thread(g, p) {
|
||||
WARN_ON_ONCE(!list_empty(&p->cg_list) ||
|
||||
task_css_set(p) != &init_css_set);
|
||||
|
||||
/*
|
||||
* We should check if the process is exiting, otherwise
|
||||
* it will race with cgroup_exit() in that the list
|
||||
* entry won't be deleted though the process has exited.
|
||||
* Do it while holding siglock so that we don't end up
|
||||
* racing against cgroup_exit().
|
||||
*
|
||||
* Interrupts were already disabled while acquiring
|
||||
* the css_set_lock, so we do not need to disable it
|
||||
* again when acquiring the sighand->siglock here.
|
||||
*/
|
||||
spin_lock(&p->sighand->siglock);
|
||||
if (!(p->flags & PF_EXITING)) {
|
||||
struct css_set *cset = task_css_set(p);
|
||||
|
||||
if (!css_set_populated(cset))
|
||||
css_set_update_populated(cset, true);
|
||||
list_add_tail(&p->cg_list, &cset->tasks);
|
||||
get_css_set(cset);
|
||||
cset->nr_tasks++;
|
||||
}
|
||||
spin_unlock(&p->sighand->siglock);
|
||||
} while_each_thread(g, p);
|
||||
out_unlock:
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
static void init_cgroup_housekeeping(struct cgroup *cgrp)
|
||||
{
|
||||
struct cgroup_subsys *ss;
|
||||
@@ -1976,7 +1914,6 @@ void init_cgroup_root(struct cgroup_fs_context *ctx)
|
||||
atomic_set(&root->nr_cgrps, 1);
|
||||
cgrp->root = root;
|
||||
init_cgroup_housekeeping(cgrp);
|
||||
idr_init(&root->cgroup_idr);
|
||||
|
||||
root->flags = ctx->flags;
|
||||
if (ctx->release_agent)
|
||||
@@ -1997,12 +1934,6 @@ int cgroup_setup_root(struct cgroup_root *root, u16 ss_mask)
|
||||
|
||||
lockdep_assert_held(&cgroup_mutex);
|
||||
|
||||
ret = cgroup_idr_alloc(&root->cgroup_idr, root_cgrp, 1, 2, GFP_KERNEL);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
root_cgrp->id = ret;
|
||||
root_cgrp->ancestor_ids[0] = ret;
|
||||
|
||||
ret = percpu_ref_init(&root_cgrp->self.refcnt, css_release,
|
||||
0, GFP_KERNEL);
|
||||
if (ret)
|
||||
@@ -2035,6 +1966,8 @@ int cgroup_setup_root(struct cgroup_root *root, u16 ss_mask)
|
||||
goto exit_root_id;
|
||||
}
|
||||
root_cgrp->kn = root->kf_root->kn;
|
||||
WARN_ON_ONCE(cgroup_ino(root_cgrp) != 1);
|
||||
root_cgrp->ancestor_ids[0] = cgroup_id(root_cgrp);
|
||||
|
||||
ret = css_populate_dir(&root_cgrp->self);
|
||||
if (ret)
|
||||
@@ -2188,13 +2121,6 @@ static int cgroup_init_fs_context(struct fs_context *fc)
|
||||
if (!ctx)
|
||||
return -ENOMEM;
|
||||
|
||||
/*
|
||||
* The first time anyone tries to mount a cgroup, enable the list
|
||||
* linking each css_set to its tasks and fix up all existing tasks.
|
||||
*/
|
||||
if (!use_task_css_set_links)
|
||||
cgroup_enable_task_cg_lists();
|
||||
|
||||
ctx->ns = current->nsproxy->cgroup_ns;
|
||||
get_cgroup_ns(ctx->ns);
|
||||
fc->fs_private = &ctx->kfc;
|
||||
@@ -2372,9 +2298,8 @@ static void cgroup_migrate_add_task(struct task_struct *task,
|
||||
if (task->flags & PF_EXITING)
|
||||
return;
|
||||
|
||||
/* leave @task alone if post_fork() hasn't linked it yet */
|
||||
if (list_empty(&task->cg_list))
|
||||
return;
|
||||
/* cgroup_threadgroup_rwsem protects racing against forks */
|
||||
WARN_ON_ONCE(list_empty(&task->cg_list));
|
||||
|
||||
cset = task_css_set(task);
|
||||
if (!cset->mg_src_cgrp)
|
||||
@@ -2825,7 +2750,8 @@ int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct task_struct *cgroup_procs_write_start(char *buf, bool threadgroup)
|
||||
struct task_struct *cgroup_procs_write_start(char *buf, bool threadgroup,
|
||||
bool *locked)
|
||||
__acquires(&cgroup_threadgroup_rwsem)
|
||||
{
|
||||
struct task_struct *tsk;
|
||||
@@ -2834,7 +2760,21 @@ struct task_struct *cgroup_procs_write_start(char *buf, bool threadgroup)
|
||||
if (kstrtoint(strstrip(buf), 0, &pid) || pid < 0)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
percpu_down_write(&cgroup_threadgroup_rwsem);
|
||||
/*
|
||||
* If we migrate a single thread, we don't care about threadgroup
|
||||
* stability. If the thread is `current`, it won't exit(2) under our
|
||||
* hands or change PID through exec(2). We exclude
|
||||
* cgroup_update_dfl_csses and other cgroup_{proc,thread}s_write
|
||||
* callers by cgroup_mutex.
|
||||
* Therefore, we can skip the global lock.
|
||||
*/
|
||||
lockdep_assert_held(&cgroup_mutex);
|
||||
if (pid || threadgroup) {
|
||||
percpu_down_write(&cgroup_threadgroup_rwsem);
|
||||
*locked = true;
|
||||
} else {
|
||||
*locked = false;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
if (pid) {
|
||||
@@ -2865,13 +2805,16 @@ struct task_struct *cgroup_procs_write_start(char *buf, bool threadgroup)
|
||||
goto out_unlock_rcu;
|
||||
|
||||
out_unlock_threadgroup:
|
||||
percpu_up_write(&cgroup_threadgroup_rwsem);
|
||||
if (*locked) {
|
||||
percpu_up_write(&cgroup_threadgroup_rwsem);
|
||||
*locked = false;
|
||||
}
|
||||
out_unlock_rcu:
|
||||
rcu_read_unlock();
|
||||
return tsk;
|
||||
}
|
||||
|
||||
void cgroup_procs_write_finish(struct task_struct *task)
|
||||
void cgroup_procs_write_finish(struct task_struct *task, bool locked)
|
||||
__releases(&cgroup_threadgroup_rwsem)
|
||||
{
|
||||
struct cgroup_subsys *ss;
|
||||
@@ -2880,7 +2823,8 @@ void cgroup_procs_write_finish(struct task_struct *task)
|
||||
/* release reference from cgroup_procs_write_start() */
|
||||
put_task_struct(task);
|
||||
|
||||
percpu_up_write(&cgroup_threadgroup_rwsem);
|
||||
if (locked)
|
||||
percpu_up_write(&cgroup_threadgroup_rwsem);
|
||||
for_each_subsys(ss, ssid)
|
||||
if (ss->post_attach)
|
||||
ss->post_attach();
|
||||
@@ -3601,22 +3545,22 @@ static int cpu_stat_show(struct seq_file *seq, void *v)
|
||||
#ifdef CONFIG_PSI
|
||||
static int cgroup_io_pressure_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup *cgroup = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup->id == 1 ? &psi_system : &cgroup->psi;
|
||||
struct cgroup *cgrp = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup_id(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
|
||||
return psi_show(seq, psi, PSI_IO);
|
||||
}
|
||||
static int cgroup_memory_pressure_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup *cgroup = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup->id == 1 ? &psi_system : &cgroup->psi;
|
||||
struct cgroup *cgrp = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup_id(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
|
||||
return psi_show(seq, psi, PSI_MEM);
|
||||
}
|
||||
static int cgroup_cpu_pressure_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup *cgroup = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup->id == 1 ? &psi_system : &cgroup->psi;
|
||||
struct cgroup *cgrp = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup_id(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
|
||||
return psi_show(seq, psi, PSI_CPU);
|
||||
}
|
||||
@@ -4568,9 +4512,6 @@ repeat:
|
||||
void css_task_iter_start(struct cgroup_subsys_state *css, unsigned int flags,
|
||||
struct css_task_iter *it)
|
||||
{
|
||||
/* no one should try to iterate before mounting cgroups */
|
||||
WARN_ON_ONCE(!use_task_css_set_links);
|
||||
|
||||
memset(it, 0, sizeof(*it));
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
@@ -4755,12 +4696,13 @@ static ssize_t cgroup_procs_write(struct kernfs_open_file *of,
|
||||
struct cgroup *src_cgrp, *dst_cgrp;
|
||||
struct task_struct *task;
|
||||
ssize_t ret;
|
||||
bool locked;
|
||||
|
||||
dst_cgrp = cgroup_kn_lock_live(of->kn, false);
|
||||
if (!dst_cgrp)
|
||||
return -ENODEV;
|
||||
|
||||
task = cgroup_procs_write_start(buf, true);
|
||||
task = cgroup_procs_write_start(buf, true, &locked);
|
||||
ret = PTR_ERR_OR_ZERO(task);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
@@ -4778,7 +4720,7 @@ static ssize_t cgroup_procs_write(struct kernfs_open_file *of,
|
||||
ret = cgroup_attach_task(dst_cgrp, task, true);
|
||||
|
||||
out_finish:
|
||||
cgroup_procs_write_finish(task);
|
||||
cgroup_procs_write_finish(task, locked);
|
||||
out_unlock:
|
||||
cgroup_kn_unlock(of->kn);
|
||||
|
||||
@@ -4796,6 +4738,7 @@ static ssize_t cgroup_threads_write(struct kernfs_open_file *of,
|
||||
struct cgroup *src_cgrp, *dst_cgrp;
|
||||
struct task_struct *task;
|
||||
ssize_t ret;
|
||||
bool locked;
|
||||
|
||||
buf = strstrip(buf);
|
||||
|
||||
@@ -4803,7 +4746,7 @@ static ssize_t cgroup_threads_write(struct kernfs_open_file *of,
|
||||
if (!dst_cgrp)
|
||||
return -ENODEV;
|
||||
|
||||
task = cgroup_procs_write_start(buf, false);
|
||||
task = cgroup_procs_write_start(buf, false, &locked);
|
||||
ret = PTR_ERR_OR_ZERO(task);
|
||||
if (ret)
|
||||
goto out_unlock;
|
||||
@@ -4827,7 +4770,7 @@ static ssize_t cgroup_threads_write(struct kernfs_open_file *of,
|
||||
ret = cgroup_attach_task(dst_cgrp, task, false);
|
||||
|
||||
out_finish:
|
||||
cgroup_procs_write_finish(task);
|
||||
cgroup_procs_write_finish(task, locked);
|
||||
out_unlock:
|
||||
cgroup_kn_unlock(of->kn);
|
||||
|
||||
@@ -5037,9 +4980,6 @@ static void css_release_work_fn(struct work_struct *work)
|
||||
tcgrp->nr_dying_descendants--;
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
cgroup_idr_remove(&cgrp->root->cgroup_idr, cgrp->id);
|
||||
cgrp->id = -1;
|
||||
|
||||
/*
|
||||
* There are two control paths which try to determine
|
||||
* cgroup from dentry without going through kernfs -
|
||||
@@ -5204,10 +5144,12 @@ err_free_css:
|
||||
* it isn't associated with its kernfs_node and doesn't have the control
|
||||
* mask applied.
|
||||
*/
|
||||
static struct cgroup *cgroup_create(struct cgroup *parent)
|
||||
static struct cgroup *cgroup_create(struct cgroup *parent, const char *name,
|
||||
umode_t mode)
|
||||
{
|
||||
struct cgroup_root *root = parent->root;
|
||||
struct cgroup *cgrp, *tcgrp;
|
||||
struct kernfs_node *kn;
|
||||
int level = parent->level + 1;
|
||||
int ret;
|
||||
|
||||
@@ -5227,15 +5169,13 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
|
||||
goto out_cancel_ref;
|
||||
}
|
||||
|
||||
/*
|
||||
* Temporarily set the pointer to NULL, so idr_find() won't return
|
||||
* a half-baked cgroup.
|
||||
*/
|
||||
cgrp->id = cgroup_idr_alloc(&root->cgroup_idr, NULL, 2, 0, GFP_KERNEL);
|
||||
if (cgrp->id < 0) {
|
||||
ret = -ENOMEM;
|
||||
/* create the directory */
|
||||
kn = kernfs_create_dir(parent->kn, name, mode, cgrp);
|
||||
if (IS_ERR(kn)) {
|
||||
ret = PTR_ERR(kn);
|
||||
goto out_stat_exit;
|
||||
}
|
||||
cgrp->kn = kn;
|
||||
|
||||
init_cgroup_housekeeping(cgrp);
|
||||
|
||||
@@ -5245,7 +5185,7 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
|
||||
|
||||
ret = psi_cgroup_alloc(cgrp);
|
||||
if (ret)
|
||||
goto out_idr_free;
|
||||
goto out_kernfs_remove;
|
||||
|
||||
ret = cgroup_bpf_inherit(cgrp);
|
||||
if (ret)
|
||||
@@ -5269,7 +5209,7 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
for (tcgrp = cgrp; tcgrp; tcgrp = cgroup_parent(tcgrp)) {
|
||||
cgrp->ancestor_ids[tcgrp->level] = tcgrp->id;
|
||||
cgrp->ancestor_ids[tcgrp->level] = cgroup_id(tcgrp);
|
||||
|
||||
if (tcgrp != cgrp) {
|
||||
tcgrp->nr_descendants++;
|
||||
@@ -5298,12 +5238,6 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
|
||||
atomic_inc(&root->nr_cgrps);
|
||||
cgroup_get_live(parent);
|
||||
|
||||
/*
|
||||
* @cgrp is now fully operational. If something fails after this
|
||||
* point, it'll be released via the normal destruction path.
|
||||
*/
|
||||
cgroup_idr_replace(&root->cgroup_idr, cgrp, cgrp->id);
|
||||
|
||||
/*
|
||||
* On the default hierarchy, a child doesn't automatically inherit
|
||||
* subtree_control from the parent. Each is configured manually.
|
||||
@@ -5317,8 +5251,8 @@ static struct cgroup *cgroup_create(struct cgroup *parent)
|
||||
|
||||
out_psi_free:
|
||||
psi_cgroup_free(cgrp);
|
||||
out_idr_free:
|
||||
cgroup_idr_remove(&root->cgroup_idr, cgrp->id);
|
||||
out_kernfs_remove:
|
||||
kernfs_remove(cgrp->kn);
|
||||
out_stat_exit:
|
||||
if (cgroup_on_dfl(parent))
|
||||
cgroup_rstat_exit(cgrp);
|
||||
@@ -5355,7 +5289,6 @@ fail:
|
||||
int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode)
|
||||
{
|
||||
struct cgroup *parent, *cgrp;
|
||||
struct kernfs_node *kn;
|
||||
int ret;
|
||||
|
||||
/* do not accept '\n' to prevent making /proc/<pid>/cgroup unparsable */
|
||||
@@ -5371,27 +5304,19 @@ int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode)
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
cgrp = cgroup_create(parent);
|
||||
cgrp = cgroup_create(parent, name, mode);
|
||||
if (IS_ERR(cgrp)) {
|
||||
ret = PTR_ERR(cgrp);
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/* create the directory */
|
||||
kn = kernfs_create_dir(parent->kn, name, mode, cgrp);
|
||||
if (IS_ERR(kn)) {
|
||||
ret = PTR_ERR(kn);
|
||||
goto out_destroy;
|
||||
}
|
||||
cgrp->kn = kn;
|
||||
|
||||
/*
|
||||
* This extra ref will be put in cgroup_free_fn() and guarantees
|
||||
* that @cgrp->kn is always accessible.
|
||||
*/
|
||||
kernfs_get(kn);
|
||||
kernfs_get(cgrp->kn);
|
||||
|
||||
ret = cgroup_kn_set_ugid(kn);
|
||||
ret = cgroup_kn_set_ugid(cgrp->kn);
|
||||
if (ret)
|
||||
goto out_destroy;
|
||||
|
||||
@@ -5406,7 +5331,7 @@ int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode)
|
||||
TRACE_CGROUP_PATH(mkdir, cgrp);
|
||||
|
||||
/* let's create and online css's */
|
||||
kernfs_activate(kn);
|
||||
kernfs_activate(cgrp->kn);
|
||||
|
||||
ret = 0;
|
||||
goto out_unlock;
|
||||
@@ -5836,12 +5761,11 @@ static int __init cgroup_wq_init(void)
|
||||
}
|
||||
core_initcall(cgroup_wq_init);
|
||||
|
||||
void cgroup_path_from_kernfs_id(const union kernfs_node_id *id,
|
||||
char *buf, size_t buflen)
|
||||
void cgroup_path_from_kernfs_id(u64 id, char *buf, size_t buflen)
|
||||
{
|
||||
struct kernfs_node *kn;
|
||||
|
||||
kn = kernfs_get_node_by_id(cgrp_dfl_root.kf_root, id);
|
||||
kn = kernfs_find_and_get_node_by_id(cgrp_dfl_root.kf_root, id);
|
||||
if (!kn)
|
||||
return;
|
||||
kernfs_path(kn, buf, buflen);
|
||||
@@ -6002,62 +5926,38 @@ void cgroup_cancel_fork(struct task_struct *child)
|
||||
void cgroup_post_fork(struct task_struct *child)
|
||||
{
|
||||
struct cgroup_subsys *ss;
|
||||
struct css_set *cset;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* This may race against cgroup_enable_task_cg_lists(). As that
|
||||
* function sets use_task_css_set_links before grabbing
|
||||
* tasklist_lock and we just went through tasklist_lock to add
|
||||
* @child, it's guaranteed that either we see the set
|
||||
* use_task_css_set_links or cgroup_enable_task_cg_lists() sees
|
||||
* @child during its iteration.
|
||||
*
|
||||
* If we won the race, @child is associated with %current's
|
||||
* css_set. Grabbing css_set_lock guarantees both that the
|
||||
* association is stable, and, on completion of the parent's
|
||||
* migration, @child is visible in the source of migration or
|
||||
* already in the destination cgroup. This guarantee is necessary
|
||||
* when implementing operations which need to migrate all tasks of
|
||||
* a cgroup to another.
|
||||
*
|
||||
* Note that if we lose to cgroup_enable_task_cg_lists(), @child
|
||||
* will remain in init_css_set. This is safe because all tasks are
|
||||
* in the init_css_set before cg_links is enabled and there's no
|
||||
* operation which transfers all tasks out of init_css_set.
|
||||
*/
|
||||
if (use_task_css_set_links) {
|
||||
struct css_set *cset;
|
||||
spin_lock_irq(&css_set_lock);
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
cset = task_css_set(current);
|
||||
if (list_empty(&child->cg_list)) {
|
||||
get_css_set(cset);
|
||||
cset->nr_tasks++;
|
||||
css_set_move_task(child, NULL, cset, false);
|
||||
}
|
||||
WARN_ON_ONCE(!list_empty(&child->cg_list));
|
||||
cset = task_css_set(current); /* current is @child's parent */
|
||||
get_css_set(cset);
|
||||
cset->nr_tasks++;
|
||||
css_set_move_task(child, NULL, cset, false);
|
||||
|
||||
/*
|
||||
* If the cgroup has to be frozen, the new task has too. Let's set
|
||||
* the JOBCTL_TRAP_FREEZE jobctl bit to get the task into the
|
||||
* frozen state.
|
||||
*/
|
||||
if (unlikely(cgroup_task_freeze(child))) {
|
||||
spin_lock(&child->sighand->siglock);
|
||||
WARN_ON_ONCE(child->frozen);
|
||||
child->jobctl |= JOBCTL_TRAP_FREEZE;
|
||||
spin_unlock(&child->sighand->siglock);
|
||||
|
||||
/*
|
||||
* If the cgroup has to be frozen, the new task has too.
|
||||
* Let's set the JOBCTL_TRAP_FREEZE jobctl bit to get
|
||||
* the task into the frozen state.
|
||||
* Calling cgroup_update_frozen() isn't required here,
|
||||
* because it will be called anyway a bit later from
|
||||
* do_freezer_trap(). So we avoid cgroup's transient switch
|
||||
* from the frozen state and back.
|
||||
*/
|
||||
if (unlikely(cgroup_task_freeze(child))) {
|
||||
spin_lock(&child->sighand->siglock);
|
||||
WARN_ON_ONCE(child->frozen);
|
||||
child->jobctl |= JOBCTL_TRAP_FREEZE;
|
||||
spin_unlock(&child->sighand->siglock);
|
||||
|
||||
/*
|
||||
* Calling cgroup_update_frozen() isn't required here,
|
||||
* because it will be called anyway a bit later
|
||||
* from do_freezer_trap(). So we avoid cgroup's
|
||||
* transient switch from the frozen state and back.
|
||||
*/
|
||||
}
|
||||
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
}
|
||||
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
/*
|
||||
* Call ss->fork(). This must happen after @child is linked on
|
||||
* css_set; otherwise, @child might change state between ->fork()
|
||||
@@ -6072,20 +5972,8 @@ void cgroup_post_fork(struct task_struct *child)
|
||||
* cgroup_exit - detach cgroup from exiting task
|
||||
* @tsk: pointer to task_struct of exiting process
|
||||
*
|
||||
* Description: Detach cgroup from @tsk and release it.
|
||||
* Description: Detach cgroup from @tsk.
|
||||
*
|
||||
* Note that cgroups marked notify_on_release force every task in
|
||||
* them to take the global cgroup_mutex mutex when exiting.
|
||||
* This could impact scaling on very large systems. Be reluctant to
|
||||
* use notify_on_release cgroups where very high task exit scaling
|
||||
* is required on large systems.
|
||||
*
|
||||
* We set the exiting tasks cgroup to the root cgroup (top_cgroup). We
|
||||
* call cgroup_exit() while the task is still competent to handle
|
||||
* notify_on_release(), then leave the task attached to the root cgroup in
|
||||
* each hierarchy for the remainder of its exit. No need to bother with
|
||||
* init_css_set refcnting. init_css_set never goes away and we can't race
|
||||
* with migration path - PF_EXITING is visible to migration path.
|
||||
*/
|
||||
void cgroup_exit(struct task_struct *tsk)
|
||||
{
|
||||
@@ -6093,26 +5981,19 @@ void cgroup_exit(struct task_struct *tsk)
|
||||
struct css_set *cset;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Unlink from @tsk from its css_set. As migration path can't race
|
||||
* with us, we can check css_set and cg_list without synchronization.
|
||||
*/
|
||||
spin_lock_irq(&css_set_lock);
|
||||
|
||||
WARN_ON_ONCE(list_empty(&tsk->cg_list));
|
||||
cset = task_css_set(tsk);
|
||||
css_set_move_task(tsk, cset, NULL, false);
|
||||
list_add_tail(&tsk->cg_list, &cset->dying_tasks);
|
||||
cset->nr_tasks--;
|
||||
|
||||
if (!list_empty(&tsk->cg_list)) {
|
||||
spin_lock_irq(&css_set_lock);
|
||||
css_set_move_task(tsk, cset, NULL, false);
|
||||
list_add_tail(&tsk->cg_list, &cset->dying_tasks);
|
||||
cset->nr_tasks--;
|
||||
WARN_ON_ONCE(cgroup_task_frozen(tsk));
|
||||
if (unlikely(cgroup_task_freeze(tsk)))
|
||||
cgroup_update_frozen(task_dfl_cgroup(tsk));
|
||||
|
||||
WARN_ON_ONCE(cgroup_task_frozen(tsk));
|
||||
if (unlikely(cgroup_task_freeze(tsk)))
|
||||
cgroup_update_frozen(task_dfl_cgroup(tsk));
|
||||
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
} else {
|
||||
get_css_set(cset);
|
||||
}
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
/* see cgroup_post_fork() for details */
|
||||
do_each_subsys_mask(ss, i, have_exit_callback) {
|
||||
@@ -6129,12 +6010,10 @@ void cgroup_release(struct task_struct *task)
|
||||
ss->release(task);
|
||||
} while_each_subsys_mask();
|
||||
|
||||
if (use_task_css_set_links) {
|
||||
spin_lock_irq(&css_set_lock);
|
||||
css_set_skip_task_iters(task_css_set(task), task);
|
||||
list_del_init(&task->cg_list);
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
}
|
||||
spin_lock_irq(&css_set_lock);
|
||||
css_set_skip_task_iters(task_css_set(task), task);
|
||||
list_del_init(&task->cg_list);
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
}
|
||||
|
||||
void cgroup_free(struct task_struct *task)
|
||||
|
||||
@@ -929,8 +929,6 @@ static void rebuild_root_domains(void)
|
||||
lockdep_assert_cpus_held();
|
||||
lockdep_assert_held(&sched_domains_mutex);
|
||||
|
||||
cgroup_enable_task_cg_lists();
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
/*
|
||||
|
||||
@@ -230,6 +230,15 @@ void cgroup_freezer_migrate_task(struct task_struct *task,
|
||||
if (task->flags & PF_KTHREAD)
|
||||
return;
|
||||
|
||||
/*
|
||||
* It's not necessary to do changes if both of the src and dst cgroups
|
||||
* are not freezing and task is not frozen.
|
||||
*/
|
||||
if (!test_bit(CGRP_FREEZE, &src->flags) &&
|
||||
!test_bit(CGRP_FREEZE, &dst->flags) &&
|
||||
!task->frozen)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Adjust counters of freezing and frozen tasks.
|
||||
* Note, that if the task is frozen, but the destination cgroup is not
|
||||
|
||||
@@ -45,7 +45,7 @@ struct pids_cgroup {
|
||||
* %PIDS_MAX = (%PID_MAX_LIMIT + 1).
|
||||
*/
|
||||
atomic64_t counter;
|
||||
int64_t limit;
|
||||
atomic64_t limit;
|
||||
|
||||
/* Handle for "pids.events" */
|
||||
struct cgroup_file events_file;
|
||||
@@ -73,8 +73,8 @@ pids_css_alloc(struct cgroup_subsys_state *parent)
|
||||
if (!pids)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
pids->limit = PIDS_MAX;
|
||||
atomic64_set(&pids->counter, 0);
|
||||
atomic64_set(&pids->limit, PIDS_MAX);
|
||||
atomic64_set(&pids->events_limit, 0);
|
||||
return &pids->css;
|
||||
}
|
||||
@@ -146,13 +146,14 @@ static int pids_try_charge(struct pids_cgroup *pids, int num)
|
||||
|
||||
for (p = pids; parent_pids(p); p = parent_pids(p)) {
|
||||
int64_t new = atomic64_add_return(num, &p->counter);
|
||||
int64_t limit = atomic64_read(&p->limit);
|
||||
|
||||
/*
|
||||
* Since new is capped to the maximum number of pid_t, if
|
||||
* p->limit is %PIDS_MAX then we know that this test will never
|
||||
* fail.
|
||||
*/
|
||||
if (new > p->limit)
|
||||
if (new > limit)
|
||||
goto revert;
|
||||
}
|
||||
|
||||
@@ -277,7 +278,7 @@ set_limit:
|
||||
* Limit updates don't need to be mutex'd, since it isn't
|
||||
* critical that any racing fork()s follow the new limit.
|
||||
*/
|
||||
pids->limit = limit;
|
||||
atomic64_set(&pids->limit, limit);
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
@@ -285,7 +286,7 @@ static int pids_max_show(struct seq_file *sf, void *v)
|
||||
{
|
||||
struct cgroup_subsys_state *css = seq_css(sf);
|
||||
struct pids_cgroup *pids = css_pids(css);
|
||||
int64_t limit = pids->limit;
|
||||
int64_t limit = atomic64_read(&pids->limit);
|
||||
|
||||
if (limit >= PIDS_MAX)
|
||||
seq_printf(sf, "%s\n", PIDS_MAX_STR);
|
||||
|
||||
+24
-20
@@ -304,44 +304,48 @@ void __init cgroup_rstat_boot(void)
|
||||
* Functions for cgroup basic resource statistics implemented on top of
|
||||
* rstat.
|
||||
*/
|
||||
static void cgroup_base_stat_accumulate(struct cgroup_base_stat *dst_bstat,
|
||||
struct cgroup_base_stat *src_bstat)
|
||||
static void cgroup_base_stat_add(struct cgroup_base_stat *dst_bstat,
|
||||
struct cgroup_base_stat *src_bstat)
|
||||
{
|
||||
dst_bstat->cputime.utime += src_bstat->cputime.utime;
|
||||
dst_bstat->cputime.stime += src_bstat->cputime.stime;
|
||||
dst_bstat->cputime.sum_exec_runtime += src_bstat->cputime.sum_exec_runtime;
|
||||
}
|
||||
|
||||
static void cgroup_base_stat_sub(struct cgroup_base_stat *dst_bstat,
|
||||
struct cgroup_base_stat *src_bstat)
|
||||
{
|
||||
dst_bstat->cputime.utime -= src_bstat->cputime.utime;
|
||||
dst_bstat->cputime.stime -= src_bstat->cputime.stime;
|
||||
dst_bstat->cputime.sum_exec_runtime -= src_bstat->cputime.sum_exec_runtime;
|
||||
}
|
||||
|
||||
static void cgroup_base_stat_flush(struct cgroup *cgrp, int cpu)
|
||||
{
|
||||
struct cgroup *parent = cgroup_parent(cgrp);
|
||||
struct cgroup_rstat_cpu *rstatc = cgroup_rstat_cpu(cgrp, cpu);
|
||||
struct task_cputime *last_cputime = &rstatc->last_bstat.cputime;
|
||||
struct task_cputime cputime;
|
||||
struct cgroup_base_stat delta;
|
||||
struct cgroup_base_stat cur, delta;
|
||||
unsigned seq;
|
||||
|
||||
/* fetch the current per-cpu values */
|
||||
do {
|
||||
seq = __u64_stats_fetch_begin(&rstatc->bsync);
|
||||
cputime = rstatc->bstat.cputime;
|
||||
cur.cputime = rstatc->bstat.cputime;
|
||||
} while (__u64_stats_fetch_retry(&rstatc->bsync, seq));
|
||||
|
||||
/* calculate the delta to propgate */
|
||||
delta.cputime.utime = cputime.utime - last_cputime->utime;
|
||||
delta.cputime.stime = cputime.stime - last_cputime->stime;
|
||||
delta.cputime.sum_exec_runtime = cputime.sum_exec_runtime -
|
||||
last_cputime->sum_exec_runtime;
|
||||
*last_cputime = cputime;
|
||||
/* propagate percpu delta to global */
|
||||
delta = cur;
|
||||
cgroup_base_stat_sub(&delta, &rstatc->last_bstat);
|
||||
cgroup_base_stat_add(&cgrp->bstat, &delta);
|
||||
cgroup_base_stat_add(&rstatc->last_bstat, &delta);
|
||||
|
||||
/* transfer the pending stat into delta */
|
||||
cgroup_base_stat_accumulate(&delta, &cgrp->pending_bstat);
|
||||
memset(&cgrp->pending_bstat, 0, sizeof(cgrp->pending_bstat));
|
||||
|
||||
/* propagate delta into the global stat and the parent's pending */
|
||||
cgroup_base_stat_accumulate(&cgrp->bstat, &delta);
|
||||
if (parent)
|
||||
cgroup_base_stat_accumulate(&parent->pending_bstat, &delta);
|
||||
/* propagate global delta to parent */
|
||||
if (parent) {
|
||||
delta = cgrp->bstat;
|
||||
cgroup_base_stat_sub(&delta, &cgrp->last_bstat);
|
||||
cgroup_base_stat_add(&parent->bstat, &delta);
|
||||
cgroup_base_stat_add(&cgrp->last_bstat, &delta);
|
||||
}
|
||||
}
|
||||
|
||||
static struct cgroup_rstat_cpu *
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@ static inline void dump_entry_trace(struct dma_debug_entry *entry)
|
||||
{
|
||||
#ifdef CONFIG_STACKTRACE
|
||||
if (entry) {
|
||||
pr_warning("Mapped at:\n");
|
||||
pr_warn("Mapped at:\n");
|
||||
stack_trace_print(entry->stack_entries, entry->stack_len, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
+6
-7
@@ -16,12 +16,11 @@
|
||||
#include <linux/swiotlb.h>
|
||||
|
||||
/*
|
||||
* Most architectures use ZONE_DMA for the first 16 Megabytes, but
|
||||
* some use it for entirely different regions:
|
||||
* Most architectures use ZONE_DMA for the first 16 Megabytes, but some use it
|
||||
* it for entirely different regions. In that case the arch code needs to
|
||||
* override the variable below for dma-direct to work properly.
|
||||
*/
|
||||
#ifndef ARCH_ZONE_DMA_BITS
|
||||
#define ARCH_ZONE_DMA_BITS 24
|
||||
#endif
|
||||
unsigned int zone_dma_bits __ro_after_init = 24;
|
||||
|
||||
static void report_addr(struct device *dev, dma_addr_t dma_addr, size_t size)
|
||||
{
|
||||
@@ -69,7 +68,7 @@ static gfp_t __dma_direct_optimal_gfp_mask(struct device *dev, u64 dma_mask,
|
||||
* Note that GFP_DMA32 and GFP_DMA are no ops without the corresponding
|
||||
* zones.
|
||||
*/
|
||||
if (*phys_mask <= DMA_BIT_MASK(ARCH_ZONE_DMA_BITS))
|
||||
if (*phys_mask <= DMA_BIT_MASK(zone_dma_bits))
|
||||
return GFP_DMA;
|
||||
if (*phys_mask <= DMA_BIT_MASK(32))
|
||||
return GFP_DMA32;
|
||||
@@ -395,7 +394,7 @@ int dma_direct_supported(struct device *dev, u64 mask)
|
||||
u64 min_mask;
|
||||
|
||||
if (IS_ENABLED(CONFIG_ZONE_DMA))
|
||||
min_mask = DMA_BIT_MASK(ARCH_ZONE_DMA_BITS);
|
||||
min_mask = DMA_BIT_MASK(zone_dma_bits);
|
||||
else
|
||||
min_mask = DMA_BIT_MASK(32);
|
||||
|
||||
|
||||
+41
-12
@@ -5081,6 +5081,24 @@ static void _perf_event_reset(struct perf_event *event)
|
||||
perf_event_update_userpage(event);
|
||||
}
|
||||
|
||||
/* Assume it's not an event with inherit set. */
|
||||
u64 perf_event_pause(struct perf_event *event, bool reset)
|
||||
{
|
||||
struct perf_event_context *ctx;
|
||||
u64 count;
|
||||
|
||||
ctx = perf_event_ctx_lock(event);
|
||||
WARN_ON_ONCE(event->attr.inherit);
|
||||
_perf_event_disable(event);
|
||||
count = local64_read(&event->count);
|
||||
if (reset)
|
||||
local64_set(&event->count, 0);
|
||||
perf_event_ctx_unlock(event, ctx);
|
||||
|
||||
return count;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_event_pause);
|
||||
|
||||
/*
|
||||
* Holding the top-level event's child_mutex means that any
|
||||
* descendant process that has inherited this event will block
|
||||
@@ -5158,16 +5176,11 @@ static int perf_event_check_period(struct perf_event *event, u64 value)
|
||||
return event->pmu->check_period(event, value);
|
||||
}
|
||||
|
||||
static int perf_event_period(struct perf_event *event, u64 __user *arg)
|
||||
static int _perf_event_period(struct perf_event *event, u64 value)
|
||||
{
|
||||
u64 value;
|
||||
|
||||
if (!is_sampling_event(event))
|
||||
return -EINVAL;
|
||||
|
||||
if (copy_from_user(&value, arg, sizeof(value)))
|
||||
return -EFAULT;
|
||||
|
||||
if (!value)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -5185,6 +5198,19 @@ static int perf_event_period(struct perf_event *event, u64 __user *arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int perf_event_period(struct perf_event *event, u64 value)
|
||||
{
|
||||
struct perf_event_context *ctx;
|
||||
int ret;
|
||||
|
||||
ctx = perf_event_ctx_lock(event);
|
||||
ret = _perf_event_period(event, value);
|
||||
perf_event_ctx_unlock(event, ctx);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_event_period);
|
||||
|
||||
static const struct file_operations perf_fops;
|
||||
|
||||
static inline int perf_fget_light(int fd, struct fd *p)
|
||||
@@ -5228,8 +5254,14 @@ static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned lon
|
||||
return _perf_event_refresh(event, arg);
|
||||
|
||||
case PERF_EVENT_IOC_PERIOD:
|
||||
return perf_event_period(event, (u64 __user *)arg);
|
||||
{
|
||||
u64 value;
|
||||
|
||||
if (copy_from_user(&value, (u64 __user *)arg, sizeof(value)))
|
||||
return -EFAULT;
|
||||
|
||||
return _perf_event_period(event, value);
|
||||
}
|
||||
case PERF_EVENT_IOC_ID:
|
||||
{
|
||||
u64 id = primary_event_id(event);
|
||||
@@ -10710,12 +10742,9 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
|
||||
context = parent_event->overflow_handler_context;
|
||||
#if defined(CONFIG_BPF_SYSCALL) && defined(CONFIG_EVENT_TRACING)
|
||||
if (overflow_handler == bpf_overflow_handler) {
|
||||
struct bpf_prog *prog = bpf_prog_inc(parent_event->prog);
|
||||
struct bpf_prog *prog = parent_event->prog;
|
||||
|
||||
if (IS_ERR(prog)) {
|
||||
err = PTR_ERR(prog);
|
||||
goto err_ns;
|
||||
}
|
||||
bpf_prog_inc(prog);
|
||||
event->prog = prog;
|
||||
event->orig_overflow_handler =
|
||||
parent_event->orig_overflow_handler;
|
||||
|
||||
+1
-1
@@ -1457,7 +1457,7 @@ repeat:
|
||||
*/
|
||||
wo->notask_error = -ECHILD;
|
||||
if ((wo->wo_type < PIDTYPE_MAX) &&
|
||||
(!wo->wo_pid || hlist_empty(&wo->wo_pid->tasks[wo->wo_type])))
|
||||
(!wo->wo_pid || !pid_has_task(wo->wo_pid, wo->wo_type)))
|
||||
goto notask;
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
@@ -56,6 +56,8 @@ const struct exception_table_entry *search_exception_tables(unsigned long addr)
|
||||
e = search_kernel_exception_table(addr);
|
||||
if (!e)
|
||||
e = search_module_extables(addr);
|
||||
if (!e)
|
||||
e = search_bpf_extables(addr);
|
||||
return e;
|
||||
}
|
||||
|
||||
|
||||
+95
-11
@@ -1517,6 +1517,11 @@ static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)
|
||||
spin_lock_irq(¤t->sighand->siglock);
|
||||
memcpy(sig->action, current->sighand->action, sizeof(sig->action));
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
|
||||
/* Reset all signal handler not set to SIG_IGN to SIG_DFL. */
|
||||
if (clone_flags & CLONE_CLEAR_SIGHAND)
|
||||
flush_signal_handlers(tsk, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1695,12 +1700,68 @@ static int pidfd_release(struct inode *inode, struct file *file)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PROC_FS
|
||||
/**
|
||||
* pidfd_show_fdinfo - print information about a pidfd
|
||||
* @m: proc fdinfo file
|
||||
* @f: file referencing a pidfd
|
||||
*
|
||||
* Pid:
|
||||
* This function will print the pid that a given pidfd refers to in the
|
||||
* pid namespace of the procfs instance.
|
||||
* If the pid namespace of the process is not a descendant of the pid
|
||||
* namespace of the procfs instance 0 will be shown as its pid. This is
|
||||
* similar to calling getppid() on a process whose parent is outside of
|
||||
* its pid namespace.
|
||||
*
|
||||
* NSpid:
|
||||
* If pid namespaces are supported then this function will also print
|
||||
* the pid of a given pidfd refers to for all descendant pid namespaces
|
||||
* starting from the current pid namespace of the instance, i.e. the
|
||||
* Pid field and the first entry in the NSpid field will be identical.
|
||||
* If the pid namespace of the process is not a descendant of the pid
|
||||
* namespace of the procfs instance 0 will be shown as its first NSpid
|
||||
* entry and no others will be shown.
|
||||
* Note that this differs from the Pid and NSpid fields in
|
||||
* /proc/<pid>/status where Pid and NSpid are always shown relative to
|
||||
* the pid namespace of the procfs instance. The difference becomes
|
||||
* obvious when sending around a pidfd between pid namespaces from a
|
||||
* different branch of the tree, i.e. where no ancestoral relation is
|
||||
* present between the pid namespaces:
|
||||
* - create two new pid namespaces ns1 and ns2 in the initial pid
|
||||
* namespace (also take care to create new mount namespaces in the
|
||||
* new pid namespace and mount procfs)
|
||||
* - create a process with a pidfd in ns1
|
||||
* - send pidfd from ns1 to ns2
|
||||
* - read /proc/self/fdinfo/<pidfd> and observe that both Pid and NSpid
|
||||
* have exactly one entry, which is 0
|
||||
*/
|
||||
static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
|
||||
{
|
||||
struct pid_namespace *ns = proc_pid_ns(file_inode(m->file));
|
||||
struct pid *pid = f->private_data;
|
||||
struct pid_namespace *ns;
|
||||
pid_t nr = -1;
|
||||
|
||||
seq_put_decimal_ull(m, "Pid:\t", pid_nr_ns(pid, ns));
|
||||
if (likely(pid_has_task(pid, PIDTYPE_PID))) {
|
||||
ns = proc_pid_ns(file_inode(m->file));
|
||||
nr = pid_nr_ns(pid, ns);
|
||||
}
|
||||
|
||||
seq_put_decimal_ll(m, "Pid:\t", nr);
|
||||
|
||||
#ifdef CONFIG_PID_NS
|
||||
seq_put_decimal_ll(m, "\nNSpid:\t", nr);
|
||||
if (nr > 0) {
|
||||
int i;
|
||||
|
||||
/* If nr is non-zero it means that 'pid' is valid and that
|
||||
* ns, i.e. the pid namespace associated with the procfs
|
||||
* instance, is in the pid namespace hierarchy of pid.
|
||||
* Start at one below the already printed level.
|
||||
*/
|
||||
for (i = ns->level + 1; i <= pid->level; i++)
|
||||
seq_put_decimal_ll(m, "\t", pid->numbers[i].nr);
|
||||
}
|
||||
#endif
|
||||
seq_putc(m, '\n');
|
||||
}
|
||||
#endif
|
||||
@@ -1708,11 +1769,11 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
|
||||
/*
|
||||
* Poll support for process exit notification.
|
||||
*/
|
||||
static unsigned int pidfd_poll(struct file *file, struct poll_table_struct *pts)
|
||||
static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
|
||||
{
|
||||
struct task_struct *task;
|
||||
struct pid *pid = file->private_data;
|
||||
int poll_flags = 0;
|
||||
__poll_t poll_flags = 0;
|
||||
|
||||
poll_wait(file, &pid->wait_pidfd, pts);
|
||||
|
||||
@@ -1724,7 +1785,7 @@ static unsigned int pidfd_poll(struct file *file, struct poll_table_struct *pts)
|
||||
* group, then poll(2) should block, similar to the wait(2) family.
|
||||
*/
|
||||
if (!task || (task->exit_state && thread_group_empty(task)))
|
||||
poll_flags = POLLIN | POLLRDNORM;
|
||||
poll_flags = EPOLLIN | EPOLLRDNORM;
|
||||
rcu_read_unlock();
|
||||
|
||||
return poll_flags;
|
||||
@@ -2026,7 +2087,8 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
stackleak_task_init(p);
|
||||
|
||||
if (pid != &init_struct_pid) {
|
||||
pid = alloc_pid(p->nsproxy->pid_ns_for_children);
|
||||
pid = alloc_pid(p->nsproxy->pid_ns_for_children, args->set_tid,
|
||||
args->set_tid_size);
|
||||
if (IS_ERR(pid)) {
|
||||
retval = PTR_ERR(pid);
|
||||
goto bad_fork_cleanup_thread;
|
||||
@@ -2529,6 +2591,7 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
|
||||
{
|
||||
int err;
|
||||
struct clone_args args;
|
||||
pid_t *kset_tid = kargs->set_tid;
|
||||
|
||||
if (unlikely(usize > PAGE_SIZE))
|
||||
return -E2BIG;
|
||||
@@ -2539,6 +2602,15 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (unlikely(args.set_tid_size > MAX_PID_NS_LEVEL))
|
||||
return -EINVAL;
|
||||
|
||||
if (unlikely(!args.set_tid && args.set_tid_size > 0))
|
||||
return -EINVAL;
|
||||
|
||||
if (unlikely(args.set_tid && args.set_tid_size == 0))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Verify that higher 32bits of exit_signal are unset and that
|
||||
* it is a valid signal
|
||||
@@ -2556,8 +2628,16 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
|
||||
.stack = args.stack,
|
||||
.stack_size = args.stack_size,
|
||||
.tls = args.tls,
|
||||
.set_tid_size = args.set_tid_size,
|
||||
};
|
||||
|
||||
if (args.set_tid &&
|
||||
copy_from_user(kset_tid, u64_to_user_ptr(args.set_tid),
|
||||
(kargs->set_tid_size * sizeof(pid_t))))
|
||||
return -EFAULT;
|
||||
|
||||
kargs->set_tid = kset_tid;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2591,11 +2671,8 @@ static inline bool clone3_stack_valid(struct kernel_clone_args *kargs)
|
||||
|
||||
static bool clone3_args_valid(struct kernel_clone_args *kargs)
|
||||
{
|
||||
/*
|
||||
* All lower bits of the flag word are taken.
|
||||
* Verify that no other unknown flags are passed along.
|
||||
*/
|
||||
if (kargs->flags & ~CLONE_LEGACY_FLAGS)
|
||||
/* Verify that no unknown flags are passed along. */
|
||||
if (kargs->flags & ~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND))
|
||||
return false;
|
||||
|
||||
/*
|
||||
@@ -2605,6 +2682,10 @@ static bool clone3_args_valid(struct kernel_clone_args *kargs)
|
||||
if (kargs->flags & (CLONE_DETACHED | CSIGNAL))
|
||||
return false;
|
||||
|
||||
if ((kargs->flags & (CLONE_SIGHAND | CLONE_CLEAR_SIGHAND)) ==
|
||||
(CLONE_SIGHAND | CLONE_CLEAR_SIGHAND))
|
||||
return false;
|
||||
|
||||
if ((kargs->flags & (CLONE_THREAD | CLONE_PARENT)) &&
|
||||
kargs->exit_signal)
|
||||
return false;
|
||||
@@ -2631,6 +2712,9 @@ SYSCALL_DEFINE2(clone3, struct clone_args __user *, uargs, size_t, size)
|
||||
int err;
|
||||
|
||||
struct kernel_clone_args kargs;
|
||||
pid_t set_tid[MAX_PID_NS_LEVEL];
|
||||
|
||||
kargs.set_tid = set_tid;
|
||||
|
||||
err = copy_clone_args_from_user(&kargs, uargs, size);
|
||||
if (err)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
obj-$(CONFIG_LIVEPATCH) += livepatch.o
|
||||
|
||||
livepatch-objs := core.o patch.o shadow.o transition.o
|
||||
livepatch-objs := core.o patch.o shadow.o state.o transition.o
|
||||
|
||||
+34
-10
@@ -22,6 +22,7 @@
|
||||
#include <asm/cacheflush.h>
|
||||
#include "core.h"
|
||||
#include "patch.h"
|
||||
#include "state.h"
|
||||
#include "transition.h"
|
||||
|
||||
/*
|
||||
@@ -632,7 +633,7 @@ static void klp_free_objects_dynamic(struct klp_patch *patch)
|
||||
* The operation must be completed by calling klp_free_patch_finish()
|
||||
* outside klp_mutex.
|
||||
*/
|
||||
void klp_free_patch_start(struct klp_patch *patch)
|
||||
static void klp_free_patch_start(struct klp_patch *patch)
|
||||
{
|
||||
if (!list_empty(&patch->list))
|
||||
list_del(&patch->list);
|
||||
@@ -677,6 +678,23 @@ static void klp_free_patch_work_fn(struct work_struct *work)
|
||||
klp_free_patch_finish(patch);
|
||||
}
|
||||
|
||||
void klp_free_patch_async(struct klp_patch *patch)
|
||||
{
|
||||
klp_free_patch_start(patch);
|
||||
schedule_work(&patch->free_work);
|
||||
}
|
||||
|
||||
void klp_free_replaced_patches_async(struct klp_patch *new_patch)
|
||||
{
|
||||
struct klp_patch *old_patch, *tmp_patch;
|
||||
|
||||
klp_for_each_patch_safe(old_patch, tmp_patch) {
|
||||
if (old_patch == new_patch)
|
||||
return;
|
||||
klp_free_patch_async(old_patch);
|
||||
}
|
||||
}
|
||||
|
||||
static int klp_init_func(struct klp_object *obj, struct klp_func *func)
|
||||
{
|
||||
if (!func->old_name)
|
||||
@@ -992,6 +1010,13 @@ int klp_enable_patch(struct klp_patch *patch)
|
||||
|
||||
mutex_lock(&klp_mutex);
|
||||
|
||||
if (!klp_is_patch_compatible(patch)) {
|
||||
pr_err("Livepatch patch (%s) is not compatible with the already installed livepatches.\n",
|
||||
patch->mod->name);
|
||||
mutex_unlock(&klp_mutex);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = klp_init_patch_early(patch);
|
||||
if (ret) {
|
||||
mutex_unlock(&klp_mutex);
|
||||
@@ -1022,12 +1047,13 @@ err:
|
||||
EXPORT_SYMBOL_GPL(klp_enable_patch);
|
||||
|
||||
/*
|
||||
* This function removes replaced patches.
|
||||
* This function unpatches objects from the replaced livepatches.
|
||||
*
|
||||
* We could be pretty aggressive here. It is called in the situation where
|
||||
* these structures are no longer accessible. All functions are redirected
|
||||
* by the klp_transition_patch. They use either a new code or they are in
|
||||
* the original code because of the special nop function patches.
|
||||
* these structures are no longer accessed from the ftrace handler.
|
||||
* All functions are redirected by the klp_transition_patch. They
|
||||
* use either a new code or they are in the original code because
|
||||
* of the special nop function patches.
|
||||
*
|
||||
* The only exception is when the transition was forced. In this case,
|
||||
* klp_ftrace_handler() might still see the replaced patch on the stack.
|
||||
@@ -1035,18 +1061,16 @@ EXPORT_SYMBOL_GPL(klp_enable_patch);
|
||||
* thanks to RCU. We only have to keep the patches on the system. Also
|
||||
* this is handled transparently by patch->module_put.
|
||||
*/
|
||||
void klp_discard_replaced_patches(struct klp_patch *new_patch)
|
||||
void klp_unpatch_replaced_patches(struct klp_patch *new_patch)
|
||||
{
|
||||
struct klp_patch *old_patch, *tmp_patch;
|
||||
struct klp_patch *old_patch;
|
||||
|
||||
klp_for_each_patch_safe(old_patch, tmp_patch) {
|
||||
klp_for_each_patch(old_patch) {
|
||||
if (old_patch == new_patch)
|
||||
return;
|
||||
|
||||
old_patch->enabled = false;
|
||||
klp_unpatch_objects(old_patch);
|
||||
klp_free_patch_start(old_patch);
|
||||
schedule_work(&old_patch->free_work);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ extern struct list_head klp_patches;
|
||||
#define klp_for_each_patch(patch) \
|
||||
list_for_each_entry(patch, &klp_patches, list)
|
||||
|
||||
void klp_free_patch_start(struct klp_patch *patch);
|
||||
void klp_discard_replaced_patches(struct klp_patch *new_patch);
|
||||
void klp_free_patch_async(struct klp_patch *patch);
|
||||
void klp_free_replaced_patches_async(struct klp_patch *new_patch);
|
||||
void klp_unpatch_replaced_patches(struct klp_patch *new_patch);
|
||||
void klp_discard_nops(struct klp_patch *new_patch);
|
||||
|
||||
static inline bool klp_is_object_loaded(struct klp_object *obj)
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* system_state.c - State of the system modified by livepatches
|
||||
*
|
||||
* Copyright (C) 2019 SUSE
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/livepatch.h>
|
||||
#include "core.h"
|
||||
#include "state.h"
|
||||
#include "transition.h"
|
||||
|
||||
#define klp_for_each_state(patch, state) \
|
||||
for (state = patch->states; state && state->id; state++)
|
||||
|
||||
/**
|
||||
* klp_get_state() - get information about system state modified by
|
||||
* the given patch
|
||||
* @patch: livepatch that modifies the given system state
|
||||
* @id: custom identifier of the modified system state
|
||||
*
|
||||
* Checks whether the given patch modifies the given system state.
|
||||
*
|
||||
* The function can be called either from pre/post (un)patch
|
||||
* callbacks or from the kernel code added by the livepatch.
|
||||
*
|
||||
* Return: pointer to struct klp_state when found, otherwise NULL.
|
||||
*/
|
||||
struct klp_state *klp_get_state(struct klp_patch *patch, unsigned long id)
|
||||
{
|
||||
struct klp_state *state;
|
||||
|
||||
klp_for_each_state(patch, state) {
|
||||
if (state->id == id)
|
||||
return state;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(klp_get_state);
|
||||
|
||||
/**
|
||||
* klp_get_prev_state() - get information about system state modified by
|
||||
* the already installed livepatches
|
||||
* @id: custom identifier of the modified system state
|
||||
*
|
||||
* Checks whether already installed livepatches modify the given
|
||||
* system state.
|
||||
*
|
||||
* The same system state can be modified by more non-cumulative
|
||||
* livepatches. It is expected that the latest livepatch has
|
||||
* the most up-to-date information.
|
||||
*
|
||||
* The function can be called only during transition when a new
|
||||
* livepatch is being enabled or when such a transition is reverted.
|
||||
* It is typically called only from from pre/post (un)patch
|
||||
* callbacks.
|
||||
*
|
||||
* Return: pointer to the latest struct klp_state from already
|
||||
* installed livepatches, NULL when not found.
|
||||
*/
|
||||
struct klp_state *klp_get_prev_state(unsigned long id)
|
||||
{
|
||||
struct klp_patch *patch;
|
||||
struct klp_state *state, *last_state = NULL;
|
||||
|
||||
if (WARN_ON_ONCE(!klp_transition_patch))
|
||||
return NULL;
|
||||
|
||||
klp_for_each_patch(patch) {
|
||||
if (patch == klp_transition_patch)
|
||||
goto out;
|
||||
|
||||
state = klp_get_state(patch, id);
|
||||
if (state)
|
||||
last_state = state;
|
||||
}
|
||||
|
||||
out:
|
||||
return last_state;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(klp_get_prev_state);
|
||||
|
||||
/* Check if the patch is able to deal with the existing system state. */
|
||||
static bool klp_is_state_compatible(struct klp_patch *patch,
|
||||
struct klp_state *old_state)
|
||||
{
|
||||
struct klp_state *state;
|
||||
|
||||
state = klp_get_state(patch, old_state->id);
|
||||
|
||||
/* A cumulative livepatch must handle all already modified states. */
|
||||
if (!state)
|
||||
return !patch->replace;
|
||||
|
||||
return state->version >= old_state->version;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that the new livepatch will not break the existing system states.
|
||||
* Cumulative patches must handle all already modified states.
|
||||
* Non-cumulative patches can touch already modified states.
|
||||
*/
|
||||
bool klp_is_patch_compatible(struct klp_patch *patch)
|
||||
{
|
||||
struct klp_patch *old_patch;
|
||||
struct klp_state *old_state;
|
||||
|
||||
klp_for_each_patch(old_patch) {
|
||||
klp_for_each_state(old_patch, old_state) {
|
||||
if (!klp_is_state_compatible(patch, old_state))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _LIVEPATCH_STATE_H
|
||||
#define _LIVEPATCH_STATE_H
|
||||
|
||||
#include <linux/livepatch.h>
|
||||
|
||||
bool klp_is_patch_compatible(struct klp_patch *patch);
|
||||
|
||||
#endif /* _LIVEPATCH_STATE_H */
|
||||
@@ -78,7 +78,7 @@ static void klp_complete_transition(void)
|
||||
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");
|
||||
|
||||
if (klp_transition_patch->replace && klp_target_state == KLP_PATCHED) {
|
||||
klp_discard_replaced_patches(klp_transition_patch);
|
||||
klp_unpatch_replaced_patches(klp_transition_patch);
|
||||
klp_discard_nops(klp_transition_patch);
|
||||
}
|
||||
|
||||
@@ -446,14 +446,14 @@ void klp_try_complete_transition(void)
|
||||
klp_complete_transition();
|
||||
|
||||
/*
|
||||
* It would make more sense to free the patch in
|
||||
* It would make more sense to free the unused patches in
|
||||
* klp_complete_transition() but it is called also
|
||||
* from klp_cancel_transition().
|
||||
*/
|
||||
if (!patch->enabled) {
|
||||
klp_free_patch_start(patch);
|
||||
schedule_work(&patch->free_work);
|
||||
}
|
||||
if (!patch->enabled)
|
||||
klp_free_patch_async(patch);
|
||||
else if (patch->replace)
|
||||
klp_free_replaced_patches_async(patch);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+1
-1
@@ -3222,7 +3222,7 @@ static int find_module_sections(struct module *mod, struct load_info *info)
|
||||
#endif
|
||||
#ifdef CONFIG_FTRACE_MCOUNT_RECORD
|
||||
/* sechdrs[0].sh_size is always zero */
|
||||
mod->ftrace_callsites = section_objs(info, "__mcount_loc",
|
||||
mod->ftrace_callsites = section_objs(info, FTRACE_CALLSITE_SECTION,
|
||||
sizeof(*mod->ftrace_callsites),
|
||||
&mod->num_ftrace_callsites);
|
||||
#endif
|
||||
|
||||
+62
-22
@@ -157,7 +157,8 @@ void free_pid(struct pid *pid)
|
||||
call_rcu(&pid->rcu, delayed_put_pid);
|
||||
}
|
||||
|
||||
struct pid *alloc_pid(struct pid_namespace *ns)
|
||||
struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
|
||||
size_t set_tid_size)
|
||||
{
|
||||
struct pid *pid;
|
||||
enum pid_type type;
|
||||
@@ -166,6 +167,17 @@ struct pid *alloc_pid(struct pid_namespace *ns)
|
||||
struct upid *upid;
|
||||
int retval = -ENOMEM;
|
||||
|
||||
/*
|
||||
* set_tid_size contains the size of the set_tid array. Starting at
|
||||
* the most nested currently active PID namespace it tells alloc_pid()
|
||||
* which PID to set for a process in that most nested PID namespace
|
||||
* up to set_tid_size PID namespaces. It does not have to set the PID
|
||||
* for a process in all nested PID namespaces but set_tid_size must
|
||||
* never be greater than the current ns->level + 1.
|
||||
*/
|
||||
if (set_tid_size > ns->level + 1)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
|
||||
if (!pid)
|
||||
return ERR_PTR(retval);
|
||||
@@ -174,24 +186,54 @@ struct pid *alloc_pid(struct pid_namespace *ns)
|
||||
pid->level = ns->level;
|
||||
|
||||
for (i = ns->level; i >= 0; i--) {
|
||||
int pid_min = 1;
|
||||
int tid = 0;
|
||||
|
||||
if (set_tid_size) {
|
||||
tid = set_tid[ns->level - i];
|
||||
|
||||
retval = -EINVAL;
|
||||
if (tid < 1 || tid >= pid_max)
|
||||
goto out_free;
|
||||
/*
|
||||
* Also fail if a PID != 1 is requested and
|
||||
* no PID 1 exists.
|
||||
*/
|
||||
if (tid != 1 && !tmp->child_reaper)
|
||||
goto out_free;
|
||||
retval = -EPERM;
|
||||
if (!ns_capable(tmp->user_ns, CAP_SYS_ADMIN))
|
||||
goto out_free;
|
||||
set_tid_size--;
|
||||
}
|
||||
|
||||
idr_preload(GFP_KERNEL);
|
||||
spin_lock_irq(&pidmap_lock);
|
||||
|
||||
/*
|
||||
* init really needs pid 1, but after reaching the maximum
|
||||
* wrap back to RESERVED_PIDS
|
||||
*/
|
||||
if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
|
||||
pid_min = RESERVED_PIDS;
|
||||
if (tid) {
|
||||
nr = idr_alloc(&tmp->idr, NULL, tid,
|
||||
tid + 1, GFP_ATOMIC);
|
||||
/*
|
||||
* If ENOSPC is returned it means that the PID is
|
||||
* alreay in use. Return EEXIST in that case.
|
||||
*/
|
||||
if (nr == -ENOSPC)
|
||||
nr = -EEXIST;
|
||||
} else {
|
||||
int pid_min = 1;
|
||||
/*
|
||||
* init really needs pid 1, but after reaching the
|
||||
* maximum wrap back to RESERVED_PIDS
|
||||
*/
|
||||
if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS)
|
||||
pid_min = RESERVED_PIDS;
|
||||
|
||||
/*
|
||||
* Store a null pointer so find_pid_ns does not find
|
||||
* a partially initialized PID (see below).
|
||||
*/
|
||||
nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min,
|
||||
pid_max, GFP_ATOMIC);
|
||||
/*
|
||||
* Store a null pointer so find_pid_ns does not find
|
||||
* a partially initialized PID (see below).
|
||||
*/
|
||||
nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min,
|
||||
pid_max, GFP_ATOMIC);
|
||||
}
|
||||
spin_unlock_irq(&pidmap_lock);
|
||||
idr_preload_end();
|
||||
|
||||
@@ -299,7 +341,7 @@ static void __change_pid(struct task_struct *task, enum pid_type type,
|
||||
*pid_ptr = new;
|
||||
|
||||
for (tmp = PIDTYPE_MAX; --tmp >= 0; )
|
||||
if (!hlist_empty(&pid->tasks[tmp]))
|
||||
if (pid_has_task(pid, tmp))
|
||||
return;
|
||||
|
||||
free_pid(pid);
|
||||
@@ -497,7 +539,7 @@ static int pidfd_create(struct pid *pid)
|
||||
*/
|
||||
SYSCALL_DEFINE2(pidfd_open, pid_t, pid, unsigned int, flags)
|
||||
{
|
||||
int fd, ret;
|
||||
int fd;
|
||||
struct pid *p;
|
||||
|
||||
if (flags)
|
||||
@@ -510,13 +552,11 @@ SYSCALL_DEFINE2(pidfd_open, pid_t, pid, unsigned int, flags)
|
||||
if (!p)
|
||||
return -ESRCH;
|
||||
|
||||
ret = 0;
|
||||
rcu_read_lock();
|
||||
if (!pid_task(p, PIDTYPE_TGID))
|
||||
ret = -EINVAL;
|
||||
rcu_read_unlock();
|
||||
if (pid_has_task(p, PIDTYPE_TGID))
|
||||
fd = pidfd_create(p);
|
||||
else
|
||||
fd = -EINVAL;
|
||||
|
||||
fd = ret ?: pidfd_create(p);
|
||||
put_pid(p);
|
||||
return fd;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
static DEFINE_MUTEX(pid_caches_mutex);
|
||||
static struct kmem_cache *pid_ns_cachep;
|
||||
/* MAX_PID_NS_LEVEL is needed for limiting size of 'struct pid' */
|
||||
#define MAX_PID_NS_LEVEL 32
|
||||
/* Write once array, filled from the beginning. */
|
||||
static struct kmem_cache *pid_cache[MAX_PID_NS_LEVEL];
|
||||
|
||||
|
||||
+7
-1
@@ -814,6 +814,8 @@ EXPORT_SYMBOL_GPL(freq_qos_update_request);
|
||||
*/
|
||||
int freq_qos_remove_request(struct freq_qos_request *req)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!req)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -821,7 +823,11 @@ int freq_qos_remove_request(struct freq_qos_request *req)
|
||||
"%s() called for unknown object\n", __func__))
|
||||
return -EINVAL;
|
||||
|
||||
return freq_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
|
||||
ret = freq_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
|
||||
req->qos = NULL;
|
||||
req->type = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(freq_qos_remove_request);
|
||||
|
||||
|
||||
+12
-4
@@ -16,6 +16,7 @@
|
||||
#include <asm/tlb.h>
|
||||
|
||||
#include "../workqueue_internal.h"
|
||||
#include "../../fs/io-wq.h"
|
||||
#include "../smpboot.h"
|
||||
|
||||
#include "pelt.h"
|
||||
@@ -4112,9 +4113,12 @@ static inline void sched_submit_work(struct task_struct *tsk)
|
||||
* we disable preemption to avoid it calling schedule() again
|
||||
* in the possible wakeup of a kworker.
|
||||
*/
|
||||
if (tsk->flags & PF_WQ_WORKER) {
|
||||
if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER)) {
|
||||
preempt_disable();
|
||||
wq_worker_sleeping(tsk);
|
||||
if (tsk->flags & PF_WQ_WORKER)
|
||||
wq_worker_sleeping(tsk);
|
||||
else
|
||||
io_wq_worker_sleeping(tsk);
|
||||
preempt_enable_no_resched();
|
||||
}
|
||||
|
||||
@@ -4131,8 +4135,12 @@ static inline void sched_submit_work(struct task_struct *tsk)
|
||||
|
||||
static void sched_update_worker(struct task_struct *tsk)
|
||||
{
|
||||
if (tsk->flags & PF_WQ_WORKER)
|
||||
wq_worker_running(tsk);
|
||||
if (tsk->flags & (PF_WQ_WORKER | PF_IO_WORKER)) {
|
||||
if (tsk->flags & PF_WQ_WORKER)
|
||||
wq_worker_running(tsk);
|
||||
else
|
||||
io_wq_worker_running(tsk);
|
||||
}
|
||||
}
|
||||
|
||||
asmlinkage __visible void __sched schedule(void)
|
||||
|
||||
@@ -0,0 +1,392 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* KUnit test of proc sysctl.
|
||||
*/
|
||||
|
||||
#include <kunit/test.h>
|
||||
#include <linux/sysctl.h>
|
||||
|
||||
#define KUNIT_PROC_READ 0
|
||||
#define KUNIT_PROC_WRITE 1
|
||||
|
||||
static int i_zero;
|
||||
static int i_one_hundred = 100;
|
||||
|
||||
/*
|
||||
* Test that proc_dointvec will not try to use a NULL .data field even when the
|
||||
* length is non-zero.
|
||||
*/
|
||||
static void sysctl_test_api_dointvec_null_tbl_data(struct kunit *test)
|
||||
{
|
||||
struct ctl_table null_data_table = {
|
||||
.procname = "foo",
|
||||
/*
|
||||
* Here we are testing that proc_dointvec behaves correctly when
|
||||
* we give it a NULL .data field. Normally this would point to a
|
||||
* piece of memory where the value would be stored.
|
||||
*/
|
||||
.data = NULL,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
/*
|
||||
* proc_dointvec expects a buffer in user space, so we allocate one. We
|
||||
* also need to cast it to __user so sparse doesn't get mad.
|
||||
*/
|
||||
void __user *buffer = (void __user *)kunit_kzalloc(test, sizeof(int),
|
||||
GFP_USER);
|
||||
size_t len;
|
||||
loff_t pos;
|
||||
|
||||
/*
|
||||
* We don't care what the starting length is since proc_dointvec should
|
||||
* not try to read because .data is NULL.
|
||||
*/
|
||||
len = 1234;
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&null_data_table,
|
||||
KUNIT_PROC_READ, buffer, &len,
|
||||
&pos));
|
||||
KUNIT_EXPECT_EQ(test, (size_t)0, len);
|
||||
|
||||
/*
|
||||
* See above.
|
||||
*/
|
||||
len = 1234;
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&null_data_table,
|
||||
KUNIT_PROC_WRITE, buffer, &len,
|
||||
&pos));
|
||||
KUNIT_EXPECT_EQ(test, (size_t)0, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Similar to the previous test, we create a struct ctrl_table that has a .data
|
||||
* field that proc_dointvec cannot do anything with; however, this time it is
|
||||
* because we tell proc_dointvec that the size is 0.
|
||||
*/
|
||||
static void sysctl_test_api_dointvec_table_maxlen_unset(struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
struct ctl_table data_maxlen_unset_table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
/*
|
||||
* So .data is no longer NULL, but we tell proc_dointvec its
|
||||
* length is 0, so it still shouldn't try to use it.
|
||||
*/
|
||||
.maxlen = 0,
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
void __user *buffer = (void __user *)kunit_kzalloc(test, sizeof(int),
|
||||
GFP_USER);
|
||||
size_t len;
|
||||
loff_t pos;
|
||||
|
||||
/*
|
||||
* As before, we don't care what buffer length is because proc_dointvec
|
||||
* cannot do anything because its internal .data buffer has zero length.
|
||||
*/
|
||||
len = 1234;
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&data_maxlen_unset_table,
|
||||
KUNIT_PROC_READ, buffer, &len,
|
||||
&pos));
|
||||
KUNIT_EXPECT_EQ(test, (size_t)0, len);
|
||||
|
||||
/*
|
||||
* See previous comment.
|
||||
*/
|
||||
len = 1234;
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&data_maxlen_unset_table,
|
||||
KUNIT_PROC_WRITE, buffer, &len,
|
||||
&pos));
|
||||
KUNIT_EXPECT_EQ(test, (size_t)0, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Here we provide a valid struct ctl_table, but we try to read and write from
|
||||
* it using a buffer of zero length, so it should still fail in a similar way as
|
||||
* before.
|
||||
*/
|
||||
static void sysctl_test_api_dointvec_table_len_is_zero(struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
/* Good table. */
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
void __user *buffer = (void __user *)kunit_kzalloc(test, sizeof(int),
|
||||
GFP_USER);
|
||||
/*
|
||||
* However, now our read/write buffer has zero length.
|
||||
*/
|
||||
size_t len = 0;
|
||||
loff_t pos;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ, buffer,
|
||||
&len, &pos));
|
||||
KUNIT_EXPECT_EQ(test, (size_t)0, len);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_WRITE, buffer,
|
||||
&len, &pos));
|
||||
KUNIT_EXPECT_EQ(test, (size_t)0, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that proc_dointvec refuses to read when the file position is non-zero.
|
||||
*/
|
||||
static void sysctl_test_api_dointvec_table_read_but_position_set(
|
||||
struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
/* Good table. */
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
void __user *buffer = (void __user *)kunit_kzalloc(test, sizeof(int),
|
||||
GFP_USER);
|
||||
/*
|
||||
* We don't care about our buffer length because we start off with a
|
||||
* non-zero file position.
|
||||
*/
|
||||
size_t len = 1234;
|
||||
/*
|
||||
* proc_dointvec should refuse to read into the buffer since the file
|
||||
* pos is non-zero.
|
||||
*/
|
||||
loff_t pos = 1;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ, buffer,
|
||||
&len, &pos));
|
||||
KUNIT_EXPECT_EQ(test, (size_t)0, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that we can read a two digit number in a sufficiently size buffer.
|
||||
* Nothing fancy.
|
||||
*/
|
||||
static void sysctl_test_dointvec_read_happy_single_positive(struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
/* Good table. */
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
size_t len = 4;
|
||||
loff_t pos = 0;
|
||||
char *buffer = kunit_kzalloc(test, len, GFP_USER);
|
||||
char __user *user_buffer = (char __user *)buffer;
|
||||
/* Store 13 in the data field. */
|
||||
*((int *)table.data) = 13;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ,
|
||||
user_buffer, &len, &pos));
|
||||
KUNIT_ASSERT_EQ(test, (size_t)3, len);
|
||||
buffer[len] = '\0';
|
||||
/* And we read 13 back out. */
|
||||
KUNIT_EXPECT_STREQ(test, "13\n", buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Same as previous test, just now with negative numbers.
|
||||
*/
|
||||
static void sysctl_test_dointvec_read_happy_single_negative(struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
/* Good table. */
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
size_t len = 5;
|
||||
loff_t pos = 0;
|
||||
char *buffer = kunit_kzalloc(test, len, GFP_USER);
|
||||
char __user *user_buffer = (char __user *)buffer;
|
||||
*((int *)table.data) = -16;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_READ,
|
||||
user_buffer, &len, &pos));
|
||||
KUNIT_ASSERT_EQ(test, (size_t)4, len);
|
||||
buffer[len] = '\0';
|
||||
KUNIT_EXPECT_STREQ(test, "-16\n", (char *)buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that a simple positive write works.
|
||||
*/
|
||||
static void sysctl_test_dointvec_write_happy_single_positive(struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
/* Good table. */
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
char input[] = "9";
|
||||
size_t len = sizeof(input) - 1;
|
||||
loff_t pos = 0;
|
||||
char *buffer = kunit_kzalloc(test, len, GFP_USER);
|
||||
char __user *user_buffer = (char __user *)buffer;
|
||||
|
||||
memcpy(buffer, input, len);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_WRITE,
|
||||
user_buffer, &len, &pos));
|
||||
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
|
||||
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
|
||||
KUNIT_EXPECT_EQ(test, 9, *((int *)table.data));
|
||||
}
|
||||
|
||||
/*
|
||||
* Same as previous test, but now with negative numbers.
|
||||
*/
|
||||
static void sysctl_test_dointvec_write_happy_single_negative(struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
char input[] = "-9";
|
||||
size_t len = sizeof(input) - 1;
|
||||
loff_t pos = 0;
|
||||
char *buffer = kunit_kzalloc(test, len, GFP_USER);
|
||||
char __user *user_buffer = (char __user *)buffer;
|
||||
|
||||
memcpy(buffer, input, len);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, 0, proc_dointvec(&table, KUNIT_PROC_WRITE,
|
||||
user_buffer, &len, &pos));
|
||||
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, len);
|
||||
KUNIT_EXPECT_EQ(test, sizeof(input) - 1, (size_t)pos);
|
||||
KUNIT_EXPECT_EQ(test, -9, *((int *)table.data));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that writing a value smaller than the minimum possible value is not
|
||||
* allowed.
|
||||
*/
|
||||
static void sysctl_test_api_dointvec_write_single_less_int_min(
|
||||
struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
size_t max_len = 32, len = max_len;
|
||||
loff_t pos = 0;
|
||||
char *buffer = kunit_kzalloc(test, max_len, GFP_USER);
|
||||
char __user *user_buffer = (char __user *)buffer;
|
||||
unsigned long abs_of_less_than_min = (unsigned long)INT_MAX
|
||||
- (INT_MAX + INT_MIN) + 1;
|
||||
|
||||
/*
|
||||
* We use this rigmarole to create a string that contains a value one
|
||||
* less than the minimum accepted value.
|
||||
*/
|
||||
KUNIT_ASSERT_LT(test,
|
||||
(size_t)snprintf(buffer, max_len, "-%lu",
|
||||
abs_of_less_than_min),
|
||||
max_len);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, -EINVAL, proc_dointvec(&table, KUNIT_PROC_WRITE,
|
||||
user_buffer, &len, &pos));
|
||||
KUNIT_EXPECT_EQ(test, max_len, len);
|
||||
KUNIT_EXPECT_EQ(test, 0, *((int *)table.data));
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that writing the maximum possible value works.
|
||||
*/
|
||||
static void sysctl_test_api_dointvec_write_single_greater_int_max(
|
||||
struct kunit *test)
|
||||
{
|
||||
int data = 0;
|
||||
struct ctl_table table = {
|
||||
.procname = "foo",
|
||||
.data = &data,
|
||||
.maxlen = sizeof(int),
|
||||
.mode = 0644,
|
||||
.proc_handler = proc_dointvec,
|
||||
.extra1 = &i_zero,
|
||||
.extra2 = &i_one_hundred,
|
||||
};
|
||||
size_t max_len = 32, len = max_len;
|
||||
loff_t pos = 0;
|
||||
char *buffer = kunit_kzalloc(test, max_len, GFP_USER);
|
||||
char __user *user_buffer = (char __user *)buffer;
|
||||
unsigned long greater_than_max = (unsigned long)INT_MAX + 1;
|
||||
|
||||
KUNIT_ASSERT_GT(test, greater_than_max, (unsigned long)INT_MAX);
|
||||
KUNIT_ASSERT_LT(test, (size_t)snprintf(buffer, max_len, "%lu",
|
||||
greater_than_max),
|
||||
max_len);
|
||||
KUNIT_EXPECT_EQ(test, -EINVAL, proc_dointvec(&table, KUNIT_PROC_WRITE,
|
||||
user_buffer, &len, &pos));
|
||||
KUNIT_ASSERT_EQ(test, max_len, len);
|
||||
KUNIT_EXPECT_EQ(test, 0, *((int *)table.data));
|
||||
}
|
||||
|
||||
static struct kunit_case sysctl_test_cases[] = {
|
||||
KUNIT_CASE(sysctl_test_api_dointvec_null_tbl_data),
|
||||
KUNIT_CASE(sysctl_test_api_dointvec_table_maxlen_unset),
|
||||
KUNIT_CASE(sysctl_test_api_dointvec_table_len_is_zero),
|
||||
KUNIT_CASE(sysctl_test_api_dointvec_table_read_but_position_set),
|
||||
KUNIT_CASE(sysctl_test_dointvec_read_happy_single_positive),
|
||||
KUNIT_CASE(sysctl_test_dointvec_read_happy_single_negative),
|
||||
KUNIT_CASE(sysctl_test_dointvec_write_happy_single_positive),
|
||||
KUNIT_CASE(sysctl_test_dointvec_write_happy_single_negative),
|
||||
KUNIT_CASE(sysctl_test_api_dointvec_write_single_less_int_min),
|
||||
KUNIT_CASE(sysctl_test_api_dointvec_write_single_greater_int_max),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct kunit_suite sysctl_test_suite = {
|
||||
.name = "sysctl_test",
|
||||
.test_cases = sysctl_test_cases,
|
||||
};
|
||||
|
||||
kunit_test_suite(sysctl_test_suite);
|
||||
+46
-38
@@ -64,8 +64,7 @@ static void blk_unregister_tracepoints(void);
|
||||
* Send out a notify message.
|
||||
*/
|
||||
static void trace_note(struct blk_trace *bt, pid_t pid, int action,
|
||||
const void *data, size_t len,
|
||||
union kernfs_node_id *cgid)
|
||||
const void *data, size_t len, u64 cgid)
|
||||
{
|
||||
struct blk_io_trace *t;
|
||||
struct ring_buffer_event *event = NULL;
|
||||
@@ -73,7 +72,7 @@ static void trace_note(struct blk_trace *bt, pid_t pid, int action,
|
||||
int pc = 0;
|
||||
int cpu = smp_processor_id();
|
||||
bool blk_tracer = blk_tracer_enabled;
|
||||
ssize_t cgid_len = cgid ? sizeof(*cgid) : 0;
|
||||
ssize_t cgid_len = cgid ? sizeof(cgid) : 0;
|
||||
|
||||
if (blk_tracer) {
|
||||
buffer = blk_tr->trace_buffer.buffer;
|
||||
@@ -100,8 +99,8 @@ record_it:
|
||||
t->pid = pid;
|
||||
t->cpu = cpu;
|
||||
t->pdu_len = len + cgid_len;
|
||||
if (cgid)
|
||||
memcpy((void *)t + sizeof(*t), cgid, cgid_len);
|
||||
if (cgid_len)
|
||||
memcpy((void *)t + sizeof(*t), &cgid, cgid_len);
|
||||
memcpy((void *) t + sizeof(*t) + cgid_len, data, len);
|
||||
|
||||
if (blk_tracer)
|
||||
@@ -122,7 +121,7 @@ static void trace_note_tsk(struct task_struct *tsk)
|
||||
spin_lock_irqsave(&running_trace_lock, flags);
|
||||
list_for_each_entry(bt, &running_trace_list, running_list) {
|
||||
trace_note(bt, tsk->pid, BLK_TN_PROCESS, tsk->comm,
|
||||
sizeof(tsk->comm), NULL);
|
||||
sizeof(tsk->comm), 0);
|
||||
}
|
||||
spin_unlock_irqrestore(&running_trace_lock, flags);
|
||||
}
|
||||
@@ -139,7 +138,7 @@ static void trace_note_time(struct blk_trace *bt)
|
||||
words[1] = now.tv_nsec;
|
||||
|
||||
local_irq_save(flags);
|
||||
trace_note(bt, 0, BLK_TN_TIMESTAMP, words, sizeof(words), NULL);
|
||||
trace_note(bt, 0, BLK_TN_TIMESTAMP, words, sizeof(words), 0);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
@@ -172,9 +171,9 @@ void __trace_note_message(struct blk_trace *bt, struct blkcg *blkcg,
|
||||
blkcg = NULL;
|
||||
#ifdef CONFIG_BLK_CGROUP
|
||||
trace_note(bt, 0, BLK_TN_MESSAGE, buf, n,
|
||||
blkcg ? cgroup_get_kernfs_id(blkcg->css.cgroup) : NULL);
|
||||
blkcg ? cgroup_id(blkcg->css.cgroup) : 1);
|
||||
#else
|
||||
trace_note(bt, 0, BLK_TN_MESSAGE, buf, n, NULL);
|
||||
trace_note(bt, 0, BLK_TN_MESSAGE, buf, n, 0);
|
||||
#endif
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
@@ -212,7 +211,7 @@ static const u32 ddir_act[2] = { BLK_TC_ACT(BLK_TC_READ),
|
||||
*/
|
||||
static void __blk_add_trace(struct blk_trace *bt, sector_t sector, int bytes,
|
||||
int op, int op_flags, u32 what, int error, int pdu_len,
|
||||
void *pdu_data, union kernfs_node_id *cgid)
|
||||
void *pdu_data, u64 cgid)
|
||||
{
|
||||
struct task_struct *tsk = current;
|
||||
struct ring_buffer_event *event = NULL;
|
||||
@@ -223,7 +222,7 @@ static void __blk_add_trace(struct blk_trace *bt, sector_t sector, int bytes,
|
||||
pid_t pid;
|
||||
int cpu, pc = 0;
|
||||
bool blk_tracer = blk_tracer_enabled;
|
||||
ssize_t cgid_len = cgid ? sizeof(*cgid) : 0;
|
||||
ssize_t cgid_len = cgid ? sizeof(cgid) : 0;
|
||||
|
||||
if (unlikely(bt->trace_state != Blktrace_running && !blk_tracer))
|
||||
return;
|
||||
@@ -294,7 +293,7 @@ record_it:
|
||||
t->pdu_len = pdu_len + cgid_len;
|
||||
|
||||
if (cgid_len)
|
||||
memcpy((void *)t + sizeof(*t), cgid, cgid_len);
|
||||
memcpy((void *)t + sizeof(*t), &cgid, cgid_len);
|
||||
if (pdu_len)
|
||||
memcpy((void *)t + sizeof(*t) + cgid_len, pdu_data, pdu_len);
|
||||
|
||||
@@ -751,31 +750,29 @@ void blk_trace_shutdown(struct request_queue *q)
|
||||
}
|
||||
|
||||
#ifdef CONFIG_BLK_CGROUP
|
||||
static union kernfs_node_id *
|
||||
blk_trace_bio_get_cgid(struct request_queue *q, struct bio *bio)
|
||||
static u64 blk_trace_bio_get_cgid(struct request_queue *q, struct bio *bio)
|
||||
{
|
||||
struct blk_trace *bt = q->blk_trace;
|
||||
|
||||
if (!bt || !(blk_tracer_flags.val & TRACE_BLK_OPT_CGROUP))
|
||||
return NULL;
|
||||
return 0;
|
||||
|
||||
if (!bio->bi_blkg)
|
||||
return NULL;
|
||||
return cgroup_get_kernfs_id(bio_blkcg(bio)->css.cgroup);
|
||||
return 0;
|
||||
return cgroup_id(bio_blkcg(bio)->css.cgroup);
|
||||
}
|
||||
#else
|
||||
static union kernfs_node_id *
|
||||
blk_trace_bio_get_cgid(struct request_queue *q, struct bio *bio)
|
||||
u64 blk_trace_bio_get_cgid(struct request_queue *q, struct bio *bio)
|
||||
{
|
||||
return NULL;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static union kernfs_node_id *
|
||||
static u64
|
||||
blk_trace_request_get_cgid(struct request_queue *q, struct request *rq)
|
||||
{
|
||||
if (!rq->bio)
|
||||
return NULL;
|
||||
return 0;
|
||||
/* Use the first bio */
|
||||
return blk_trace_bio_get_cgid(q, rq->bio);
|
||||
}
|
||||
@@ -797,8 +794,7 @@ blk_trace_request_get_cgid(struct request_queue *q, struct request *rq)
|
||||
*
|
||||
**/
|
||||
static void blk_add_trace_rq(struct request *rq, int error,
|
||||
unsigned int nr_bytes, u32 what,
|
||||
union kernfs_node_id *cgid)
|
||||
unsigned int nr_bytes, u32 what, u64 cgid)
|
||||
{
|
||||
struct blk_trace *bt = rq->q->blk_trace;
|
||||
|
||||
@@ -913,7 +909,7 @@ static void blk_add_trace_getrq(void *ignore,
|
||||
|
||||
if (bt)
|
||||
__blk_add_trace(bt, 0, 0, rw, 0, BLK_TA_GETRQ, 0, 0,
|
||||
NULL, NULL);
|
||||
NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -929,7 +925,7 @@ static void blk_add_trace_sleeprq(void *ignore,
|
||||
|
||||
if (bt)
|
||||
__blk_add_trace(bt, 0, 0, rw, 0, BLK_TA_SLEEPRQ,
|
||||
0, 0, NULL, NULL);
|
||||
0, 0, NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -938,7 +934,7 @@ static void blk_add_trace_plug(void *ignore, struct request_queue *q)
|
||||
struct blk_trace *bt = q->blk_trace;
|
||||
|
||||
if (bt)
|
||||
__blk_add_trace(bt, 0, 0, 0, 0, BLK_TA_PLUG, 0, 0, NULL, NULL);
|
||||
__blk_add_trace(bt, 0, 0, 0, 0, BLK_TA_PLUG, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
static void blk_add_trace_unplug(void *ignore, struct request_queue *q,
|
||||
@@ -955,7 +951,7 @@ static void blk_add_trace_unplug(void *ignore, struct request_queue *q,
|
||||
else
|
||||
what = BLK_TA_UNPLUG_TIMER;
|
||||
|
||||
__blk_add_trace(bt, 0, 0, 0, 0, what, 0, sizeof(rpdu), &rpdu, NULL);
|
||||
__blk_add_trace(bt, 0, 0, 0, 0, what, 0, sizeof(rpdu), &rpdu, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1172,19 +1168,17 @@ const struct blk_io_trace *te_blk_io_trace(const struct trace_entry *ent)
|
||||
|
||||
static inline const void *pdu_start(const struct trace_entry *ent, bool has_cg)
|
||||
{
|
||||
return (void *)(te_blk_io_trace(ent) + 1) +
|
||||
(has_cg ? sizeof(union kernfs_node_id) : 0);
|
||||
return (void *)(te_blk_io_trace(ent) + 1) + (has_cg ? sizeof(u64) : 0);
|
||||
}
|
||||
|
||||
static inline const void *cgid_start(const struct trace_entry *ent)
|
||||
static inline u64 t_cgid(const struct trace_entry *ent)
|
||||
{
|
||||
return (void *)(te_blk_io_trace(ent) + 1);
|
||||
return *(u64 *)(te_blk_io_trace(ent) + 1);
|
||||
}
|
||||
|
||||
static inline int pdu_real_len(const struct trace_entry *ent, bool has_cg)
|
||||
{
|
||||
return te_blk_io_trace(ent)->pdu_len -
|
||||
(has_cg ? sizeof(union kernfs_node_id) : 0);
|
||||
return te_blk_io_trace(ent)->pdu_len - (has_cg ? sizeof(u64) : 0);
|
||||
}
|
||||
|
||||
static inline u32 t_action(const struct trace_entry *ent)
|
||||
@@ -1257,7 +1251,7 @@ static void blk_log_action(struct trace_iterator *iter, const char *act,
|
||||
|
||||
fill_rwbs(rwbs, t);
|
||||
if (has_cg) {
|
||||
const union kernfs_node_id *id = cgid_start(iter->ent);
|
||||
u64 id = t_cgid(iter->ent);
|
||||
|
||||
if (blk_tracer_flags.val & TRACE_BLK_OPT_CGNAME) {
|
||||
char blkcg_name_buf[NAME_MAX + 1] = "<...>";
|
||||
@@ -1267,11 +1261,25 @@ static void blk_log_action(struct trace_iterator *iter, const char *act,
|
||||
trace_seq_printf(&iter->seq, "%3d,%-3d %s %2s %3s ",
|
||||
MAJOR(t->device), MINOR(t->device),
|
||||
blkcg_name_buf, act, rwbs);
|
||||
} else
|
||||
} else {
|
||||
/*
|
||||
* The cgid portion used to be "INO,GEN". Userland
|
||||
* builds a FILEID_INO32_GEN fid out of them and
|
||||
* opens the cgroup using open_by_handle_at(2).
|
||||
* While 32bit ino setups are still the same, 64bit
|
||||
* ones now use the 64bit ino as the whole ID and
|
||||
* no longer use generation.
|
||||
*
|
||||
* Regarldess of the content, always output
|
||||
* "LOW32,HIGH32" so that FILEID_INO32_GEN fid can
|
||||
* be mapped back to @id on both 64 and 32bit ino
|
||||
* setups. See __kernfs_fh_to_dentry().
|
||||
*/
|
||||
trace_seq_printf(&iter->seq,
|
||||
"%3d,%-3d %x,%-x %2s %3s ",
|
||||
"%3d,%-3d %llx,%-llx %2s %3s ",
|
||||
MAJOR(t->device), MINOR(t->device),
|
||||
id->ino, id->generation, act, rwbs);
|
||||
id & U32_MAX, id >> 32, act, rwbs);
|
||||
}
|
||||
} else
|
||||
trace_seq_printf(&iter->seq, "%3d,%-3d %2s %3s ",
|
||||
MAJOR(t->device), MINOR(t->device), act, rwbs);
|
||||
|
||||
+177
-54
@@ -138,24 +138,19 @@ static const struct bpf_func_proto bpf_override_return_proto = {
|
||||
};
|
||||
#endif
|
||||
|
||||
BPF_CALL_3(bpf_probe_read, void *, dst, u32, size, const void *, unsafe_ptr)
|
||||
BPF_CALL_3(bpf_probe_read_user, void *, dst, u32, size,
|
||||
const void __user *, unsafe_ptr)
|
||||
{
|
||||
int ret;
|
||||
int ret = probe_user_read(dst, unsafe_ptr, size);
|
||||
|
||||
ret = security_locked_down(LOCKDOWN_BPF_READ);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = probe_kernel_read(dst, unsafe_ptr, size);
|
||||
if (unlikely(ret < 0))
|
||||
out:
|
||||
memset(dst, 0, size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_read_proto = {
|
||||
.func = bpf_probe_read,
|
||||
static const struct bpf_func_proto bpf_probe_read_user_proto = {
|
||||
.func = bpf_probe_read_user,
|
||||
.gpl_only = true,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
@@ -163,7 +158,128 @@ static const struct bpf_func_proto bpf_probe_read_proto = {
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_3(bpf_probe_write_user, void *, unsafe_ptr, const void *, src,
|
||||
BPF_CALL_3(bpf_probe_read_user_str, void *, dst, u32, size,
|
||||
const void __user *, unsafe_ptr)
|
||||
{
|
||||
int ret = strncpy_from_unsafe_user(dst, unsafe_ptr, size);
|
||||
|
||||
if (unlikely(ret < 0))
|
||||
memset(dst, 0, size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_read_user_str_proto = {
|
||||
.func = bpf_probe_read_user_str,
|
||||
.gpl_only = true,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
static __always_inline int
|
||||
bpf_probe_read_kernel_common(void *dst, u32 size, const void *unsafe_ptr,
|
||||
const bool compat)
|
||||
{
|
||||
int ret = security_locked_down(LOCKDOWN_BPF_READ);
|
||||
|
||||
if (unlikely(ret < 0))
|
||||
goto out;
|
||||
ret = compat ? probe_kernel_read(dst, unsafe_ptr, size) :
|
||||
probe_kernel_read_strict(dst, unsafe_ptr, size);
|
||||
if (unlikely(ret < 0))
|
||||
out:
|
||||
memset(dst, 0, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
BPF_CALL_3(bpf_probe_read_kernel, void *, dst, u32, size,
|
||||
const void *, unsafe_ptr)
|
||||
{
|
||||
return bpf_probe_read_kernel_common(dst, size, unsafe_ptr, false);
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_read_kernel_proto = {
|
||||
.func = bpf_probe_read_kernel,
|
||||
.gpl_only = true,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_3(bpf_probe_read_compat, void *, dst, u32, size,
|
||||
const void *, unsafe_ptr)
|
||||
{
|
||||
return bpf_probe_read_kernel_common(dst, size, unsafe_ptr, true);
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_read_compat_proto = {
|
||||
.func = bpf_probe_read_compat,
|
||||
.gpl_only = true,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
static __always_inline int
|
||||
bpf_probe_read_kernel_str_common(void *dst, u32 size, const void *unsafe_ptr,
|
||||
const bool compat)
|
||||
{
|
||||
int ret = security_locked_down(LOCKDOWN_BPF_READ);
|
||||
|
||||
if (unlikely(ret < 0))
|
||||
goto out;
|
||||
/*
|
||||
* The strncpy_from_unsafe_*() call will likely not fill the entire
|
||||
* buffer, but that's okay in this circumstance as we're probing
|
||||
* arbitrary memory anyway similar to bpf_probe_read_*() and might
|
||||
* as well probe the stack. Thus, memory is explicitly cleared
|
||||
* only in error case, so that improper users ignoring return
|
||||
* code altogether don't copy garbage; otherwise length of string
|
||||
* is returned that can be used for bpf_perf_event_output() et al.
|
||||
*/
|
||||
ret = compat ? strncpy_from_unsafe(dst, unsafe_ptr, size) :
|
||||
strncpy_from_unsafe_strict(dst, unsafe_ptr, size);
|
||||
if (unlikely(ret < 0))
|
||||
out:
|
||||
memset(dst, 0, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
BPF_CALL_3(bpf_probe_read_kernel_str, void *, dst, u32, size,
|
||||
const void *, unsafe_ptr)
|
||||
{
|
||||
return bpf_probe_read_kernel_str_common(dst, size, unsafe_ptr, false);
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_read_kernel_str_proto = {
|
||||
.func = bpf_probe_read_kernel_str,
|
||||
.gpl_only = true,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_3(bpf_probe_read_compat_str, void *, dst, u32, size,
|
||||
const void *, unsafe_ptr)
|
||||
{
|
||||
return bpf_probe_read_kernel_str_common(dst, size, unsafe_ptr, true);
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_read_compat_str_proto = {
|
||||
.func = bpf_probe_read_compat_str,
|
||||
.gpl_only = true,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_3(bpf_probe_write_user, void __user *, unsafe_ptr, const void *, src,
|
||||
u32, size)
|
||||
{
|
||||
/*
|
||||
@@ -186,10 +302,8 @@ BPF_CALL_3(bpf_probe_write_user, void *, unsafe_ptr, const void *, src,
|
||||
return -EPERM;
|
||||
if (unlikely(!nmi_uaccess_okay()))
|
||||
return -EPERM;
|
||||
if (!access_ok(unsafe_ptr, size))
|
||||
return -EPERM;
|
||||
|
||||
return probe_kernel_write(unsafe_ptr, src, size);
|
||||
return probe_user_write(unsafe_ptr, src, size);
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_write_user_proto = {
|
||||
@@ -585,41 +699,6 @@ static const struct bpf_func_proto bpf_current_task_under_cgroup_proto = {
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
BPF_CALL_3(bpf_probe_read_str, void *, dst, u32, size,
|
||||
const void *, unsafe_ptr)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = security_locked_down(LOCKDOWN_BPF_READ);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* The strncpy_from_unsafe() call will likely not fill the entire
|
||||
* buffer, but that's okay in this circumstance as we're probing
|
||||
* arbitrary memory anyway similar to bpf_probe_read() and might
|
||||
* as well probe the stack. Thus, memory is explicitly cleared
|
||||
* only in error case, so that improper users ignoring return
|
||||
* code altogether don't copy garbage; otherwise length of string
|
||||
* is returned that can be used for bpf_perf_event_output() et al.
|
||||
*/
|
||||
ret = strncpy_from_unsafe(dst, unsafe_ptr, size);
|
||||
if (unlikely(ret < 0))
|
||||
out:
|
||||
memset(dst, 0, size);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto bpf_probe_read_str_proto = {
|
||||
.func = bpf_probe_read_str,
|
||||
.gpl_only = true,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg2_type = ARG_CONST_SIZE_OR_ZERO,
|
||||
.arg3_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
struct send_signal_irq_work {
|
||||
struct irq_work irq_work;
|
||||
struct task_struct *task;
|
||||
@@ -699,8 +778,6 @@ tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
return &bpf_map_pop_elem_proto;
|
||||
case BPF_FUNC_map_peek_elem:
|
||||
return &bpf_map_peek_elem_proto;
|
||||
case BPF_FUNC_probe_read:
|
||||
return &bpf_probe_read_proto;
|
||||
case BPF_FUNC_ktime_get_ns:
|
||||
return &bpf_ktime_get_ns_proto;
|
||||
case BPF_FUNC_tail_call:
|
||||
@@ -727,8 +804,18 @@ tracing_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
return &bpf_current_task_under_cgroup_proto;
|
||||
case BPF_FUNC_get_prandom_u32:
|
||||
return &bpf_get_prandom_u32_proto;
|
||||
case BPF_FUNC_probe_read_user:
|
||||
return &bpf_probe_read_user_proto;
|
||||
case BPF_FUNC_probe_read_kernel:
|
||||
return &bpf_probe_read_kernel_proto;
|
||||
case BPF_FUNC_probe_read:
|
||||
return &bpf_probe_read_compat_proto;
|
||||
case BPF_FUNC_probe_read_user_str:
|
||||
return &bpf_probe_read_user_str_proto;
|
||||
case BPF_FUNC_probe_read_kernel_str:
|
||||
return &bpf_probe_read_kernel_str_proto;
|
||||
case BPF_FUNC_probe_read_str:
|
||||
return &bpf_probe_read_str_proto;
|
||||
return &bpf_probe_read_compat_str_proto;
|
||||
#ifdef CONFIG_CGROUPS
|
||||
case BPF_FUNC_get_current_cgroup_id:
|
||||
return &bpf_get_current_cgroup_id_proto;
|
||||
@@ -995,6 +1082,8 @@ static const struct bpf_func_proto bpf_perf_event_output_proto_raw_tp = {
|
||||
.arg5_type = ARG_CONST_SIZE_OR_ZERO,
|
||||
};
|
||||
|
||||
extern const struct bpf_func_proto bpf_skb_output_proto;
|
||||
|
||||
BPF_CALL_3(bpf_get_stackid_raw_tp, struct bpf_raw_tracepoint_args *, args,
|
||||
struct bpf_map *, map, u64, flags)
|
||||
{
|
||||
@@ -1062,13 +1151,25 @@ raw_tp_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
}
|
||||
}
|
||||
|
||||
static const struct bpf_func_proto *
|
||||
tracing_prog_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
|
||||
{
|
||||
switch (func_id) {
|
||||
#ifdef CONFIG_NET
|
||||
case BPF_FUNC_skb_output:
|
||||
return &bpf_skb_output_proto;
|
||||
#endif
|
||||
default:
|
||||
return raw_tp_prog_func_proto(func_id, prog);
|
||||
}
|
||||
}
|
||||
|
||||
static bool raw_tp_prog_is_valid_access(int off, int size,
|
||||
enum bpf_access_type type,
|
||||
const struct bpf_prog *prog,
|
||||
struct bpf_insn_access_aux *info)
|
||||
{
|
||||
/* largest tracepoint in the kernel has 12 args */
|
||||
if (off < 0 || off >= sizeof(__u64) * 12)
|
||||
if (off < 0 || off >= sizeof(__u64) * MAX_BPF_FUNC_ARGS)
|
||||
return false;
|
||||
if (type != BPF_READ)
|
||||
return false;
|
||||
@@ -1077,6 +1178,20 @@ static bool raw_tp_prog_is_valid_access(int off, int size,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool tracing_prog_is_valid_access(int off, int size,
|
||||
enum bpf_access_type type,
|
||||
const struct bpf_prog *prog,
|
||||
struct bpf_insn_access_aux *info)
|
||||
{
|
||||
if (off < 0 || off >= sizeof(__u64) * MAX_BPF_FUNC_ARGS)
|
||||
return false;
|
||||
if (type != BPF_READ)
|
||||
return false;
|
||||
if (off % size != 0)
|
||||
return false;
|
||||
return btf_ctx_access(off, size, type, prog, info);
|
||||
}
|
||||
|
||||
const struct bpf_verifier_ops raw_tracepoint_verifier_ops = {
|
||||
.get_func_proto = raw_tp_prog_func_proto,
|
||||
.is_valid_access = raw_tp_prog_is_valid_access,
|
||||
@@ -1085,6 +1200,14 @@ const struct bpf_verifier_ops raw_tracepoint_verifier_ops = {
|
||||
const struct bpf_prog_ops raw_tracepoint_prog_ops = {
|
||||
};
|
||||
|
||||
const struct bpf_verifier_ops tracing_verifier_ops = {
|
||||
.get_func_proto = tracing_prog_func_proto,
|
||||
.is_valid_access = tracing_prog_is_valid_access,
|
||||
};
|
||||
|
||||
const struct bpf_prog_ops tracing_prog_ops = {
|
||||
};
|
||||
|
||||
static bool raw_tp_writable_prog_is_valid_access(int off, int size,
|
||||
enum bpf_access_type type,
|
||||
const struct bpf_prog *prog,
|
||||
|
||||
@@ -2494,14 +2494,14 @@ struct dyn_ftrace *ftrace_rec_iter_record(struct ftrace_rec_iter *iter)
|
||||
}
|
||||
|
||||
static int
|
||||
ftrace_code_disable(struct module *mod, struct dyn_ftrace *rec)
|
||||
ftrace_nop_initialize(struct module *mod, struct dyn_ftrace *rec)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (unlikely(ftrace_disabled))
|
||||
return 0;
|
||||
|
||||
ret = ftrace_make_nop(mod, rec, MCOUNT_ADDR);
|
||||
ret = ftrace_init_nop(mod, rec);
|
||||
if (ret) {
|
||||
ftrace_bug_type = FTRACE_BUG_INIT;
|
||||
ftrace_bug(ret, rec);
|
||||
@@ -2943,7 +2943,7 @@ static int ftrace_update_code(struct module *mod, struct ftrace_page *new_pgs)
|
||||
* to the NOP instructions.
|
||||
*/
|
||||
if (!__is_defined(CC_USING_NOP_MCOUNT) &&
|
||||
!ftrace_code_disable(mod, p))
|
||||
!ftrace_nop_initialize(mod, p))
|
||||
break;
|
||||
|
||||
update_cnt++;
|
||||
|
||||
@@ -178,14 +178,14 @@ static int benchmark_event_kthread(void *arg)
|
||||
int trace_benchmark_reg(void)
|
||||
{
|
||||
if (!ok_to_run) {
|
||||
pr_warning("trace benchmark cannot be started via kernel command line\n");
|
||||
pr_warn("trace benchmark cannot be started via kernel command line\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
bm_event_thread = kthread_run(benchmark_event_kthread,
|
||||
NULL, "event_benchmark");
|
||||
if (IS_ERR(bm_event_thread)) {
|
||||
pr_warning("trace benchmark failed to create kernel thread\n");
|
||||
pr_warn("trace benchmark failed to create kernel thread\n");
|
||||
return PTR_ERR(bm_event_thread);
|
||||
}
|
||||
|
||||
|
||||
+65
-25
@@ -248,7 +248,7 @@ struct workqueue_struct {
|
||||
struct list_head flusher_overflow; /* WQ: flush overflow list */
|
||||
|
||||
struct list_head maydays; /* MD: pwqs requesting rescue */
|
||||
struct worker *rescuer; /* I: rescue worker */
|
||||
struct worker *rescuer; /* MD: rescue worker */
|
||||
|
||||
int nr_drainers; /* WQ: drain in progress */
|
||||
int saved_max_active; /* WQ: saved pwq max_active */
|
||||
@@ -355,6 +355,7 @@ EXPORT_SYMBOL_GPL(system_freezable_power_efficient_wq);
|
||||
|
||||
static int worker_thread(void *__worker);
|
||||
static void workqueue_sysfs_unregister(struct workqueue_struct *wq);
|
||||
static void show_pwq(struct pool_workqueue *pwq);
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/workqueue.h>
|
||||
@@ -425,7 +426,8 @@ static void workqueue_sysfs_unregister(struct workqueue_struct *wq);
|
||||
* ignored.
|
||||
*/
|
||||
#define for_each_pwq(pwq, wq) \
|
||||
list_for_each_entry_rcu((pwq), &(wq)->pwqs, pwqs_node) \
|
||||
list_for_each_entry_rcu((pwq), &(wq)->pwqs, pwqs_node, \
|
||||
lockdep_is_held(&wq->mutex)) \
|
||||
if (({ assert_rcu_or_wq_mutex(wq); false; })) { } \
|
||||
else
|
||||
|
||||
@@ -2532,8 +2534,14 @@ repeat:
|
||||
*/
|
||||
if (need_to_create_worker(pool)) {
|
||||
spin_lock(&wq_mayday_lock);
|
||||
get_pwq(pwq);
|
||||
list_move_tail(&pwq->mayday_node, &wq->maydays);
|
||||
/*
|
||||
* Queue iff we aren't racing destruction
|
||||
* and somebody else hasn't queued it already.
|
||||
*/
|
||||
if (wq->rescuer && list_empty(&pwq->mayday_node)) {
|
||||
get_pwq(pwq);
|
||||
list_add_tail(&pwq->mayday_node, &wq->maydays);
|
||||
}
|
||||
spin_unlock(&wq_mayday_lock);
|
||||
}
|
||||
}
|
||||
@@ -4314,6 +4322,22 @@ err_destroy:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(alloc_workqueue);
|
||||
|
||||
static bool pwq_busy(struct pool_workqueue *pwq)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WORK_NR_COLORS; i++)
|
||||
if (pwq->nr_in_flight[i])
|
||||
return true;
|
||||
|
||||
if ((pwq != pwq->wq->dfl_pwq) && (pwq->refcnt > 1))
|
||||
return true;
|
||||
if (pwq->nr_active || !list_empty(&pwq->delayed_works))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* destroy_workqueue - safely terminate a workqueue
|
||||
* @wq: target workqueue
|
||||
@@ -4325,31 +4349,51 @@ void destroy_workqueue(struct workqueue_struct *wq)
|
||||
struct pool_workqueue *pwq;
|
||||
int node;
|
||||
|
||||
/*
|
||||
* Remove it from sysfs first so that sanity check failure doesn't
|
||||
* lead to sysfs name conflicts.
|
||||
*/
|
||||
workqueue_sysfs_unregister(wq);
|
||||
|
||||
/* drain it before proceeding with destruction */
|
||||
drain_workqueue(wq);
|
||||
|
||||
/* sanity checks */
|
||||
/* kill rescuer, if sanity checks fail, leave it w/o rescuer */
|
||||
if (wq->rescuer) {
|
||||
struct worker *rescuer = wq->rescuer;
|
||||
|
||||
/* this prevents new queueing */
|
||||
spin_lock_irq(&wq_mayday_lock);
|
||||
wq->rescuer = NULL;
|
||||
spin_unlock_irq(&wq_mayday_lock);
|
||||
|
||||
/* rescuer will empty maydays list before exiting */
|
||||
kthread_stop(rescuer->task);
|
||||
kfree(rescuer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sanity checks - grab all the locks so that we wait for all
|
||||
* in-flight operations which may do put_pwq().
|
||||
*/
|
||||
mutex_lock(&wq_pool_mutex);
|
||||
mutex_lock(&wq->mutex);
|
||||
for_each_pwq(pwq, wq) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < WORK_NR_COLORS; i++) {
|
||||
if (WARN_ON(pwq->nr_in_flight[i])) {
|
||||
mutex_unlock(&wq->mutex);
|
||||
show_workqueue_state();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (WARN_ON((pwq != wq->dfl_pwq) && (pwq->refcnt > 1)) ||
|
||||
WARN_ON(pwq->nr_active) ||
|
||||
WARN_ON(!list_empty(&pwq->delayed_works))) {
|
||||
spin_lock_irq(&pwq->pool->lock);
|
||||
if (WARN_ON(pwq_busy(pwq))) {
|
||||
pr_warning("%s: %s has the following busy pwq\n",
|
||||
__func__, wq->name);
|
||||
show_pwq(pwq);
|
||||
spin_unlock_irq(&pwq->pool->lock);
|
||||
mutex_unlock(&wq->mutex);
|
||||
mutex_unlock(&wq_pool_mutex);
|
||||
show_workqueue_state();
|
||||
return;
|
||||
}
|
||||
spin_unlock_irq(&pwq->pool->lock);
|
||||
}
|
||||
mutex_unlock(&wq->mutex);
|
||||
mutex_unlock(&wq_pool_mutex);
|
||||
|
||||
/*
|
||||
* wq list is used to freeze wq, remove from list after
|
||||
@@ -4359,11 +4403,6 @@ void destroy_workqueue(struct workqueue_struct *wq)
|
||||
list_del_rcu(&wq->list);
|
||||
mutex_unlock(&wq_pool_mutex);
|
||||
|
||||
workqueue_sysfs_unregister(wq);
|
||||
|
||||
if (wq->rescuer)
|
||||
kthread_stop(wq->rescuer->task);
|
||||
|
||||
if (!(wq->flags & WQ_UNBOUND)) {
|
||||
wq_unregister_lockdep(wq);
|
||||
/*
|
||||
@@ -4638,7 +4677,8 @@ static void show_pwq(struct pool_workqueue *pwq)
|
||||
pr_info(" pwq %d:", pool->id);
|
||||
pr_cont_pool_info(pool);
|
||||
|
||||
pr_cont(" active=%d/%d%s\n", pwq->nr_active, pwq->max_active,
|
||||
pr_cont(" active=%d/%d refcnt=%d%s\n",
|
||||
pwq->nr_active, pwq->max_active, pwq->refcnt,
|
||||
!list_empty(&pwq->mayday_node) ? " MAYDAY" : "");
|
||||
|
||||
hash_for_each(pool->busy_hash, bkt, worker, hentry) {
|
||||
@@ -4657,7 +4697,7 @@ static void show_pwq(struct pool_workqueue *pwq)
|
||||
|
||||
pr_cont("%s %d%s:%ps", comma ? "," : "",
|
||||
task_pid_nr(worker->task),
|
||||
worker == pwq->wq->rescuer ? "(RESCUER)" : "",
|
||||
worker->rescue_wq ? "(RESCUER)" : "",
|
||||
worker->current_func);
|
||||
list_for_each_entry(work, &worker->scheduled, entry)
|
||||
pr_cont_work(false, work);
|
||||
|
||||
Reference in New Issue
Block a user