Skip to contentSkip to conversation

libbpf: fix inner map removal in bpf_object__create_map

PATCH In review Martynas Pumputis 2 participants 6 messages Jul 19, 2021 – Jul 20, 2021

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

v2 cover libbpf: fix inner map removal in bpf_object__create_map

This patch set fixes the removal of a BTF-defined inner map if the creation of the outer map has failed. This was identified by Andrii in [1].

[1]: https://lore.kernel.org/bpf/CAEf4BzYaQsD6NaEUij6ttDeKYP7oEB0=c0D9_xdAKw6FYb7h1g@mail.gmail.com/

Martynas Pumputis (2):
libbpf: fix removal of inner map in bpf_object__create_map selftests/bpf: check inner map deletion

 tools/lib/bpf/libbpf.c                        | 11 ++--
 .../bpf/progs/test_map_in_map_invalid.c       | 26 ++++++++
 tools/testing/selftests/bpf/test_maps.c       | 64 ++++++++++++++++++-
 3 files changed, 95 insertions(+), 6 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c
Signature · 1 line
2.32.0
#1 lore raw

PATCH v2 1/2 libbpf: fix removal of inner map in bpf_object__create_map

If creating an outer map of a BTF-defined map-in-map fails (via bpf_object__create_map()), then the previously created its inner map won't be destroyed.

Fix this by ensuring that the destroy routines are not bypassed in the case of a failure.

Fixes 646f02ffdd49c ("libbpf: Add BTF-defined map-in-map support")
---
 tools/lib/bpf/libbpf.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)
tools/lib/bpf/libbpf.c +4 -6
index 6f5e2757bb3c..dde521366579 100644
@@ -4479,6 +4479,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 {
 	struct bpf_create_map_attr create_attr;
 	struct bpf_map_def *def = &map->def;
+	int err = 0;
 
 	memset(&create_attr, 0, sizeof(create_attr));
 
@@ -4521,8 +4522,6 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 
 	if (bpf_map_type__is_map_in_map(def->type)) {
 		if (map->inner_map) {
-			int err;
-
 			err = bpf_object__create_map(obj, map->inner_map, true);
 			if (err) {
 				pr_warn("map '%s': failed to create inner map: %d\n",
@@ -4547,7 +4546,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 	if (map->fd < 0 && (create_attr.btf_key_type_id ||
 			    create_attr.btf_value_type_id)) {
 		char *cp, errmsg[STRERR_BUFSIZE];
-		int err = -errno;
+		err = -errno;
 
 		cp = libbpf_strerror_r(err, errmsg, sizeof(errmsg));
 		pr_warn("Error in bpf_create_map_xattr(%s):%s(%d). Retrying without BTF.\n",
@@ -4560,8 +4559,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 		map->fd = bpf_create_map_xattr(&create_attr);
 	}
 
-	if (map->fd < 0)
-		return -errno;
+	err = map->fd < 0 ? -errno : 0;
 
 	if (bpf_map_type__is_map_in_map(def->type) && map->inner_map) {
 		if (obj->gen_loader)
@@ -4570,7 +4568,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 		zfree(&map->inner_map);
 	}
 
-	return 0;
+	return err;
 }
 
 static int init_map_slots(struct bpf_object *obj, struct bpf_map *map)
Signature · 1 line
2.32.0
Acked-by John Fastabend on v2 1/2
#2 lore raw

Re: [PATCH bpf 1/2] libbpf: fix removal of inner map in bpf_object__create_map

Quoted 10 lines · Martynas Pumputis

If creating an outer map of a BTF-defined map-in-map fails (via bpf_object__create_map()), then the previously created its inner map won't be destroyed.

Fix this by ensuring that the destroy routines are not bypassed in the case of a failure.

Fixes 646f02ffdd49c ("libbpf: Add BTF-defined map-in-map support")

Please preserve received acks between versions (unless some significant changes happen between versions invalidating the original ack):

Thanks, applied (with few adjustments) to bpf-next, given we lived with this bug for a long while and no one ever noticed/reported it (and BPF map will be cleaned up when the process exits anyway). Keeping it in bpf-next will make it easier for follow-up patches changing/enhancing bpf_map__create_map() and will avoid merge conflicts down the line.

Quoted 27 lines
---
 tools/lib/bpf/libbpf.c | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)
tools/lib/bpf/libbpf.c +1 -2
index 6f5e2757bb3c..dde521366579 100644
@@ -4479,6 +4479,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 {
        struct bpf_create_map_attr create_attr;
        struct bpf_map_def *def = &map->def;
+       int err = 0;
 
        memset(&create_attr, 0, sizeof(create_attr));
 
@@ -4521,8 +4522,6 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
 
        if (bpf_map_type__is_map_in_map(def->type)) {
                if (map->inner_map) {
-                       int err;
-
                        err = bpf_object__create_map(obj, map->inner_map, true);
                        if (err) {
                                pr_warn("map '%s': failed to create inner map: %d\n",

tools/lib/bpf/libbpf.c static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b · line 4550

Quoted 4 lines
tools/lib/bpf/libbpf.c +0 -1
@@ -4547,7 +4546,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
        if (map->fd < 0 && (create_attr.btf_key_type_id ||
                            create_attr.btf_value_type_id)) {
                char *cp, errmsg[STRERR_BUFSIZE];
-               int err = -errno;

needed empty line here

Quoted 26 lines
tools/lib/bpf/libbpf.c +3 -4
+               err = -errno;
 
                cp = libbpf_strerror_r(err, errmsg, sizeof(errmsg));
                pr_warn("Error in bpf_create_map_xattr(%s):%s(%d). Retrying without BTF.\n",
@@ -4560,8 +4559,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
                map->fd = bpf_create_map_xattr(&create_attr);
        }
 
-       if (map->fd < 0)
-               return -errno;
+       err = map->fd < 0 ? -errno : 0;
 
        if (bpf_map_type__is_map_in_map(def->type) && map->inner_map) {
                if (obj->gen_loader)
@@ -4570,7 +4568,7 @@ static int bpf_object__create_map(struct bpf_object *obj, struct bpf_map *map, b
                zfree(&map->inner_map);
        }
 
-       return 0;
+       return err;
 }
 
 static int init_map_slots(struct bpf_object *obj, struct bpf_map *map)
--

2.32.0

#3 lore raw

PATCH v2 2/2 selftests/bpf: check inner map deletion

In reply to Martynas Pumputis

Add a test case to check whether an unsuccessful creation of an outer map of a BTF-defined map-in-map destroys the inner map.

As bpf_object__create_map() is a static function, we cannot just call it from the test case and then check whether a map accessible via map->inner_map_fd has been closed. Instead, we iterate over all maps and check whether the map "$MAP_NAME.inner" does not exist.

---
 .../bpf/progs/test_map_in_map_invalid.c       | 26 ++++++++
 tools/testing/selftests/bpf/test_maps.c       | 64 ++++++++++++++++++-
 2 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c
tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c new file +26 -0
new file mode 100644
index 000000000000..2918caea1e3d
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2021 Isovalent, Inc. */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+struct inner {
+	__uint(type, BPF_MAP_TYPE_ARRAY);
+	__type(key, __u32);
+	__type(value, int);
+	__uint(max_entries, 4);
+};
+
+struct {
+	__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
+	__uint(max_entries, 0); /* This will make map creation to fail */
+	__uint(key_size, sizeof(__u32));
+	__array(values, struct inner);
+} mim SEC(".maps");
+
+SEC("xdp_noop")
+int xdp_noop0(struct xdp_md *ctx)
+{
+	return XDP_PASS;
+}
+
+char _license[] SEC("license") = "GPL";
tools/testing/selftests/bpf/test_maps.c +63 -1

85 lines not on this page · raw message

Signature · 1 line
2.32.0
#4 lore raw

Re: [PATCH bpf 2/2] selftests/bpf: check inner map deletion

Quoted 32 lines · Martynas Pumputis

Add a test case to check whether an unsuccessful creation of an outer map of a BTF-defined map-in-map destroys the inner map.

As bpf_object__create_map() is a static function, we cannot just call it from the test case and then check whether a map accessible via map->inner_map_fd has been closed. Instead, we iterate over all maps and check whether the map "$MAP_NAME.inner" does not exist.

---
 .../bpf/progs/test_map_in_map_invalid.c       | 26 ++++++++
 tools/testing/selftests/bpf/test_maps.c       | 64 ++++++++++++++++++-
 2 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c
tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c new file +10 -0
new file mode 100644
index 000000000000..2918caea1e3d
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2021 Isovalent, Inc. */
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+struct inner {
+       __uint(type, BPF_MAP_TYPE_ARRAY);
+       __type(key, __u32);
+       __type(value, int);
+       __uint(max_entries, 4);

tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c line 20

Quoted 10 lines
tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c new file +10 -0
+};
+
+struct {
+       __uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
+       __uint(max_entries, 0); /* This will make map creation to fail */
+       __uint(key_size, sizeof(__u32));
+       __array(values, struct inner);
+} mim SEC(".maps");
+
+SEC("xdp_noop")

canonical section name for XDP programs is strictly "xdp", so I updated it to avoid fixing that later when libbpf will start enforcing this

Quoted 28 lines
tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c new file +6 -0
+int xdp_noop0(struct xdp_md *ctx)
+{
+       return XDP_PASS;
+}
+
+char _license[] SEC("license") = "GPL";
tools/testing/selftests/bpf/test_maps.c +4 -0
index 30cbf5d98f7d..d4184dde04df 100644
@@ -1153,12 +1153,16 @@ static void test_sockmap(unsigned int tasks, void *data)
 }
 
 #define MAPINMAP_PROG "./test_map_in_map.o"
+#define MAPINMAP_INVALID_PROG "./test_map_in_map_invalid.o"
 static void test_map_in_map(void)
 {
        struct bpf_object *obj;
        struct bpf_map *map;
        int mim_fd, fd, err;
        int pos = 0;
+       struct bpf_map_info info = {};
+       __u32 len = sizeof(info);
+       __u32 id = 0;
 
        obj = bpf_object__open(MAPINMAP_PROG);
 

tools/testing/selftests/bpf/test_maps.c static void test_map_in_map(void) · line 1234

Quoted 2 lines
tools/testing/selftests/bpf/test_maps.c +0 -0
@@ -1229,10 +1233,68 @@ static void test_map_in_map(void)
 
        close(fd);

added fd = -1 here

Quoted 13 lines
tools/testing/selftests/bpf/test_maps.c +12 -0
        bpf_object__close(obj);
+
+
+       /* Test that failing bpf_object__create_map() destroys the inner map */
+
+       obj = bpf_object__open(MAPINMAP_INVALID_PROG);
+       err = libbpf_get_error(obj);
+       if (err) {
+               printf("Failed to load %s program: %d %d",
+                      MAPINMAP_INVALID_PROG, err, errno);
+               goto out_map_in_map;
+       }
+

[...]

#5 lore raw

Re: [PATCH bpf 2/2] selftests/bpf: check inner map deletion

In reply to Martynas Pumputis
Quoted 16 lines · Martynas Pumputis

Add a test case to check whether an unsuccessful creation of an outer map of a BTF-defined map-in-map destroys the inner map.

As bpf_object__create_map() is a static function, we cannot just call it from the test case and then check whether a map accessible via map->inner_map_fd has been closed. Instead, we iterate over all maps and check whether the map "$MAP_NAME.inner" does not exist.

---
 .../bpf/progs/test_map_in_map_invalid.c       | 26 ++++++++
 tools/testing/selftests/bpf/test_maps.c       | 64 ++++++++++++++++++-
 2 files changed, 89 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/bpf/progs/test_map_in_map_invalid.c

[...]

tools/testing/selftests/bpf/test_maps.c static void test_map_in_map(void) · line 1254

Quoted 7 lines
tools/testing/selftests/bpf/test_maps.c +7 -0
+       map = bpf_object__find_map_by_name(obj, "mim");
+       if (!map) {
+               printf("Failed to load array of maps from test prog\n");
+               goto out_map_in_map;
+       }
+
+       err = bpf_object__load(obj);

Hi Martynas,

This now is producing this warning, when running test_maps:

libbpf: map 'mim': failed to create: Invalid argument(-22)
libbpf: failed to load object './test_map_in_map_invalid.o'

It's quite confusing, I think it's better to mute this. You can do that by temporarily swapping libbpf's logger function to a no-op function, ignoring all the warnings. We do this in few other places, see libbpf_set_print().

Quoted 5 lines
tools/testing/selftests/bpf/test_maps.c +5 -0
+       if (!err) {
+               printf("Loading obj supposed to fail\n");
+               goto out_map_in_map;
+       }
+

[...]