Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 56f668df authored by Martin KaFai Lau's avatar Martin KaFai Lau Committed by David S. Miller
Browse files

bpf: Add array of maps support



This patch adds a few helper funcs to enable map-in-map
support (i.e. outer_map->inner_map).  The first outer_map type
BPF_MAP_TYPE_ARRAY_OF_MAPS is also added in this patch.
The next patch will introduce a hash of maps type.

Any bpf map type can be acted as an inner_map.  The exception
is BPF_MAP_TYPE_PROG_ARRAY because the extra level of
indirection makes it harder to verify the owner_prog_type
and owner_jited.

Multi-level map-in-map is not supported (i.e. map->map is ok
but not map->map->map).

When adding an inner_map to an outer_map, it currently checks the
map_type, key_size, value_size, map_flags, max_entries and ops.
The verifier also uses those map's properties to do static analysis.
map_flags is needed because we need to ensure BPF_PROG_TYPE_PERF_EVENT
is using a preallocated hashtab for the inner_hash also.  ops and
max_entries are needed to generate inlined map-lookup instructions.
For simplicity reason, a simple '==' test is used for both map_flags
and max_entries.  The equality of ops is implied by the equality of
map_type.

During outer_map creation time, an inner_map_fd is needed to create an
outer_map.  However, the inner_map_fd's life time does not depend on the
outer_map.  The inner_map_fd is merely used to initialize
the inner_map_meta of the outer_map.

Also, for the outer_map:

* It allows element update and delete from syscall
* It allows element lookup from bpf_prog

The above is similar to the current fd_array pattern.

Signed-off-by: default avatarMartin KaFai Lau <kafai@fb.com>
Acked-by: default avatarAlexei Starovoitov <ast@kernel.org>
Acked-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent fad73a1a
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -50,6 +50,7 @@ struct bpf_map {
	const struct bpf_map_ops *ops;
	const struct bpf_map_ops *ops;
	struct work_struct work;
	struct work_struct work;
	atomic_t usercnt;
	atomic_t usercnt;
	struct bpf_map *inner_map_meta;
};
};


struct bpf_map_type_list {
struct bpf_map_type_list {
+2 −0
Original line number Original line Diff line number Diff line
@@ -96,6 +96,7 @@ enum bpf_map_type {
	BPF_MAP_TYPE_LRU_HASH,
	BPF_MAP_TYPE_LRU_HASH,
	BPF_MAP_TYPE_LRU_PERCPU_HASH,
	BPF_MAP_TYPE_LRU_PERCPU_HASH,
	BPF_MAP_TYPE_LPM_TRIE,
	BPF_MAP_TYPE_LPM_TRIE,
	BPF_MAP_TYPE_ARRAY_OF_MAPS,
};
};


enum bpf_prog_type {
enum bpf_prog_type {
@@ -152,6 +153,7 @@ union bpf_attr {
		__u32	value_size;	/* size of value in bytes */
		__u32	value_size;	/* size of value in bytes */
		__u32	max_entries;	/* max number of entries in a map */
		__u32	max_entries;	/* max number of entries in a map */
		__u32	map_flags;	/* prealloc or not */
		__u32	map_flags;	/* prealloc or not */
		__u32	inner_map_fd;	/* fd pointing to the inner map */
	};
	};


	struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
	struct { /* anonymous struct used by BPF_MAP_*_ELEM commands */
+1 −1
Original line number Original line Diff line number Diff line
obj-y := core.o
obj-y := core.o


obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o
obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o
obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o
obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o
ifeq ($(CONFIG_PERF_EVENTS),y)
ifeq ($(CONFIG_PERF_EVENTS),y)
obj-$(CONFIG_BPF_SYSCALL) += stackmap.o
obj-$(CONFIG_BPF_SYSCALL) += stackmap.o
endif
endif
+63 −0
Original line number Original line Diff line number Diff line
@@ -17,6 +17,8 @@
#include <linux/filter.h>
#include <linux/filter.h>
#include <linux/perf_event.h>
#include <linux/perf_event.h>


#include "map_in_map.h"

static void bpf_array_free_percpu(struct bpf_array *array)
static void bpf_array_free_percpu(struct bpf_array *array)
{
{
	int i;
	int i;
@@ -602,3 +604,64 @@ static int __init register_cgroup_array_map(void)
}
}
late_initcall(register_cgroup_array_map);
late_initcall(register_cgroup_array_map);
#endif
#endif

static struct bpf_map *array_of_map_alloc(union bpf_attr *attr)
{
	struct bpf_map *map, *inner_map_meta;

	inner_map_meta = bpf_map_meta_alloc(attr->inner_map_fd);
	if (IS_ERR(inner_map_meta))
		return inner_map_meta;

	map = fd_array_map_alloc(attr);
	if (IS_ERR(map)) {
		bpf_map_meta_free(inner_map_meta);
		return map;
	}

	map->inner_map_meta = inner_map_meta;

	return map;
}

static void array_of_map_free(struct bpf_map *map)
{
	/* map->inner_map_meta is only accessed by syscall which
	 * is protected by fdget/fdput.
	 */
	bpf_map_meta_free(map->inner_map_meta);
	bpf_fd_array_map_clear(map);
	fd_array_map_free(map);
}

static void *array_of_map_lookup_elem(struct bpf_map *map, void *key)
{
	struct bpf_map **inner_map = array_map_lookup_elem(map, key);

	if (!inner_map)
		return NULL;

	return READ_ONCE(*inner_map);
}

static const struct bpf_map_ops array_of_map_ops = {
	.map_alloc = array_of_map_alloc,
	.map_free = array_of_map_free,
	.map_get_next_key = array_map_get_next_key,
	.map_lookup_elem = array_of_map_lookup_elem,
	.map_delete_elem = fd_array_map_delete_elem,
	.map_fd_get_ptr = bpf_map_fd_get_ptr,
	.map_fd_put_ptr = bpf_map_fd_put_ptr,
};

static struct bpf_map_type_list array_of_map_type __ro_after_init = {
	.ops = &array_of_map_ops,
	.type = BPF_MAP_TYPE_ARRAY_OF_MAPS,
};

static int __init register_array_of_map(void)
{
	bpf_register_map_type(&array_of_map_type);
	return 0;
}
late_initcall(register_array_of_map);
+97 −0
Original line number Original line Diff line number Diff line
/* Copyright (c) 2017 Facebook
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 */
#include <linux/slab.h>
#include <linux/bpf.h>

#include "map_in_map.h"

struct bpf_map *bpf_map_meta_alloc(int inner_map_ufd)
{
	struct bpf_map *inner_map, *inner_map_meta;
	struct fd f;

	f = fdget(inner_map_ufd);
	inner_map = __bpf_map_get(f);
	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.
	 */
	if (inner_map->map_type == BPF_MAP_TYPE_PROG_ARRAY) {
		fdput(f);
		return ERR_PTR(-ENOTSUPP);
	}

	/* Does not support >1 level map-in-map */
	if (inner_map->inner_map_meta) {
		fdput(f);
		return ERR_PTR(-EINVAL);
	}

	inner_map_meta = kzalloc(sizeof(*inner_map_meta), GFP_USER);
	if (!inner_map_meta) {
		fdput(f);
		return ERR_PTR(-ENOMEM);
	}

	inner_map_meta->map_type = inner_map->map_type;
	inner_map_meta->key_size = inner_map->key_size;
	inner_map_meta->value_size = inner_map->value_size;
	inner_map_meta->map_flags = inner_map->map_flags;
	inner_map_meta->ops = inner_map->ops;
	inner_map_meta->max_entries = inner_map->max_entries;

	fdput(f);
	return inner_map_meta;
}

void bpf_map_meta_free(struct bpf_map *map_meta)
{
	kfree(map_meta);
}

bool bpf_map_meta_equal(const struct bpf_map *meta0,
			const struct bpf_map *meta1)
{
	/* No need to compare ops because it is covered by map_type */
	return meta0->map_type == meta1->map_type &&
		meta0->key_size == meta1->key_size &&
		meta0->value_size == meta1->value_size &&
		meta0->map_flags == meta1->map_flags &&
		meta0->max_entries == meta1->max_entries;
}

void *bpf_map_fd_get_ptr(struct bpf_map *map,
			 struct file *map_file /* not used */,
			 int ufd)
{
	struct bpf_map *inner_map;
	struct fd f;

	f = fdget(ufd);
	inner_map = __bpf_map_get(f);
	if (IS_ERR(inner_map))
		return inner_map;

	if (bpf_map_meta_equal(map->inner_map_meta, inner_map))
		inner_map = bpf_map_inc(inner_map, false);
	else
		inner_map = ERR_PTR(-EINVAL);

	fdput(f);
	return inner_map;
}

void bpf_map_fd_put_ptr(void *ptr)
{
	/* ptr->ops->map_free() has to go through one
	 * rcu grace period by itself.
	 */
	bpf_map_put(ptr);
}
Loading