Skip to content

Commit 9f317bd

Browse files
committed
Merge branch 'bpf-free-special-fields-when-update-hash-and-local-storage-maps'
Leon Hwang says: ==================== bpf: Free special fields when update hash and local storage maps In the discussion thread "[PATCH bpf-next v9 0/7] bpf: Introduce BPF_F_CPU and BPF_F_ALL_CPUS flags for percpu maps"[1], it was pointed out that missing calls to bpf_obj_free_fields() could lead to memory leaks. A selftest was added to confirm that this is indeed a real issue - the refcount of BPF_KPTR_REF field is not decremented when bpf_obj_free_fields() is missing after copy_map_value[,_long](). Further inspection of copy_map_value[,_long]() call sites revealed two locations affected by this issue: 1. pcpu_copy_value() 2. htab_map_update_elem() when used with BPF_F_LOCK Similar cases happen when update local storage maps. This series fixes the issues by properly calling bpf_obj_free_fields() (or check_and_free_fields()) after copy_map_value[,_long]() and adds selftests to verify the fix. Changes: v2 -> v3: * Free special fields when update local storage maps without BPF_F_LOCK. * Add test to verify decrementing refcount when update cgroup local storage maps without BPF_F_LOCK. * Address review from AI bot: * Slow path with BPF_F_LOCK (around line 642-646) in 'bpf_local_storage.c'. * https://lore.kernel.org/bpf/[email protected]/ v1 -> v2: * Add test to verify decrementing refcount when update cgroup local storage maps with BPF_F_LOCK. * Address review from AI bot: * Fast path without bucket lock (around line 610) in 'bpf_local_storage.c'. * https://lore.kernel.org/bpf/[email protected]/ ==================== Link: https://patch.msgid.link/[email protected] Signed-off-by: Andrii Nakryiko <[email protected]>
2 parents 54c134f + d5a7e7a commit 9f317bd

File tree

4 files changed

+343
-1
lines changed

4 files changed

+343
-1
lines changed

kernel/bpf/bpf_local_storage.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ bpf_local_storage_update(void *owner, struct bpf_local_storage_map *smap,
609609
if (old_sdata && selem_linked_to_storage_lockless(SELEM(old_sdata))) {
610610
copy_map_value_locked(&smap->map, old_sdata->data,
611611
value, false);
612+
bpf_obj_free_fields(smap->map.record, old_sdata->data);
612613
return old_sdata;
613614
}
614615
}
@@ -641,6 +642,7 @@ bpf_local_storage_update(void *owner, struct bpf_local_storage_map *smap,
641642
if (old_sdata && (map_flags & BPF_F_LOCK)) {
642643
copy_map_value_locked(&smap->map, old_sdata->data, value,
643644
false);
645+
bpf_obj_free_fields(smap->map.record, old_sdata->data);
644646
selem = SELEM(old_sdata);
645647
goto unlock;
646648
}

kernel/bpf/hashtab.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,12 +937,14 @@ static void pcpu_copy_value(struct bpf_htab *htab, void __percpu *pptr,
937937
if (!onallcpus) {
938938
/* copy true value_size bytes */
939939
copy_map_value(&htab->map, this_cpu_ptr(pptr), value);
940+
bpf_obj_free_fields(htab->map.record, this_cpu_ptr(pptr));
940941
} else {
941942
u32 size = round_up(htab->map.value_size, 8);
942943
int off = 0, cpu;
943944

944945
for_each_possible_cpu(cpu) {
945946
copy_map_value_long(&htab->map, per_cpu_ptr(pptr, cpu), value + off);
947+
bpf_obj_free_fields(htab->map.record, per_cpu_ptr(pptr, cpu));
946948
off += size;
947949
}
948950
}
@@ -1108,6 +1110,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
11081110
copy_map_value_locked(map,
11091111
htab_elem_value(l_old, key_size),
11101112
value, false);
1113+
check_and_free_fields(htab, l_old);
11111114
return 0;
11121115
}
11131116
/* fall through, grab the bucket lock and lookup again.
@@ -1136,6 +1139,7 @@ static long htab_map_update_elem(struct bpf_map *map, void *key, void *value,
11361139
copy_map_value_locked(map,
11371140
htab_elem_value(l_old, key_size),
11381141
value, false);
1142+
check_and_free_fields(htab, l_old);
11391143
ret = 0;
11401144
goto err;
11411145
}

tools/testing/selftests/bpf/prog_tests/refcounted_kptr.c

Lines changed: 177 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
#include <test_progs.h>
55
#include <network_helpers.h>
6-
6+
#include "cgroup_helpers.h"
77
#include "refcounted_kptr.skel.h"
88
#include "refcounted_kptr_fail.skel.h"
99

@@ -44,3 +44,179 @@ void test_refcounted_kptr_wrong_owner(void)
4444
ASSERT_OK(opts.retval, "rbtree_wrong_owner_remove_fail_a2 retval");
4545
refcounted_kptr__destroy(skel);
4646
}
47+
48+
static void test_refcnt_leak(void *values, size_t values_sz, u64 flags, struct bpf_map *map,
49+
struct bpf_program *prog_leak, struct bpf_program *prog_check)
50+
{
51+
int ret, fd, key = 0;
52+
LIBBPF_OPTS(bpf_test_run_opts, opts,
53+
.data_in = &pkt_v4,
54+
.data_size_in = sizeof(pkt_v4),
55+
.repeat = 1,
56+
);
57+
58+
ret = bpf_map__update_elem(map, &key, sizeof(key), values, values_sz, flags);
59+
if (!ASSERT_OK(ret, "bpf_map__update_elem init"))
60+
return;
61+
62+
fd = bpf_program__fd(prog_leak);
63+
ret = bpf_prog_test_run_opts(fd, &opts);
64+
if (!ASSERT_OK(ret, "test_run_opts"))
65+
return;
66+
if (!ASSERT_EQ(opts.retval, 2, "retval refcount"))
67+
return;
68+
69+
ret = bpf_map__update_elem(map, &key, sizeof(key), values, values_sz, flags);
70+
if (!ASSERT_OK(ret, "bpf_map__update_elem dec refcount"))
71+
return;
72+
73+
fd = bpf_program__fd(prog_check);
74+
ret = bpf_prog_test_run_opts(fd, &opts);
75+
ASSERT_OK(ret, "test_run_opts");
76+
ASSERT_EQ(opts.retval, 1, "retval");
77+
}
78+
79+
static void test_percpu_hash_refcount_leak(void)
80+
{
81+
struct refcounted_kptr *skel;
82+
size_t values_sz;
83+
u64 *values;
84+
int cpu_nr;
85+
86+
cpu_nr = libbpf_num_possible_cpus();
87+
if (!ASSERT_GT(cpu_nr, 0, "libbpf_num_possible_cpus"))
88+
return;
89+
90+
values = calloc(cpu_nr, sizeof(u64));
91+
if (!ASSERT_OK_PTR(values, "calloc values"))
92+
return;
93+
94+
skel = refcounted_kptr__open_and_load();
95+
if (!ASSERT_OK_PTR(skel, "refcounted_kptr__open_and_load")) {
96+
free(values);
97+
return;
98+
}
99+
100+
values_sz = cpu_nr * sizeof(u64);
101+
memset(values, 0, values_sz);
102+
103+
test_refcnt_leak(values, values_sz, 0, skel->maps.pcpu_hash,
104+
skel->progs.pcpu_hash_refcount_leak,
105+
skel->progs.check_pcpu_hash_refcount);
106+
107+
refcounted_kptr__destroy(skel);
108+
free(values);
109+
}
110+
111+
struct lock_map_value {
112+
u64 kptr;
113+
struct bpf_spin_lock lock;
114+
int value;
115+
};
116+
117+
static void test_hash_lock_refcount_leak(void)
118+
{
119+
struct lock_map_value value = {};
120+
struct refcounted_kptr *skel;
121+
122+
skel = refcounted_kptr__open_and_load();
123+
if (!ASSERT_OK_PTR(skel, "refcounted_kptr__open_and_load"))
124+
return;
125+
126+
test_refcnt_leak(&value, sizeof(value), BPF_F_LOCK, skel->maps.lock_hash,
127+
skel->progs.hash_lock_refcount_leak,
128+
skel->progs.check_hash_lock_refcount);
129+
130+
refcounted_kptr__destroy(skel);
131+
}
132+
133+
static void test_cgrp_storage_refcount_leak(u64 flags)
134+
{
135+
int server_fd = -1, client_fd = -1;
136+
struct lock_map_value value = {};
137+
struct refcounted_kptr *skel;
138+
struct bpf_link *link;
139+
struct bpf_map *map;
140+
int cgroup, err;
141+
142+
cgroup = test__join_cgroup("/cg_refcount_leak");
143+
if (!ASSERT_GE(cgroup, 0, "test__join_cgroup"))
144+
return;
145+
146+
skel = refcounted_kptr__open_and_load();
147+
if (!ASSERT_OK_PTR(skel, "refcounted_kptr__open_and_load"))
148+
goto out;
149+
150+
link = bpf_program__attach_cgroup(skel->progs.cgroup_storage_refcount_leak, cgroup);
151+
if (!ASSERT_OK_PTR(link, "bpf_program__attach_cgroup"))
152+
goto out;
153+
skel->links.cgroup_storage_refcount_leak = link;
154+
155+
server_fd = start_server(AF_INET6, SOCK_STREAM, "::1", 0, 0);
156+
if (!ASSERT_GE(server_fd, 0, "start_server"))
157+
goto out;
158+
159+
client_fd = connect_to_fd(server_fd, 0);
160+
if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
161+
goto out;
162+
163+
map = skel->maps.cgrp_strg;
164+
err = bpf_map__lookup_elem(map, &cgroup, sizeof(cgroup), &value, sizeof(value), flags);
165+
if (!ASSERT_OK(err, "bpf_map__lookup_elem"))
166+
goto out;
167+
168+
ASSERT_EQ(value.value, 2, "refcount");
169+
170+
err = bpf_map__update_elem(map, &cgroup, sizeof(cgroup), &value, sizeof(value), flags);
171+
if (!ASSERT_OK(err, "bpf_map__update_elem"))
172+
goto out;
173+
174+
err = bpf_link__detach(skel->links.cgroup_storage_refcount_leak);
175+
if (!ASSERT_OK(err, "bpf_link__detach"))
176+
goto out;
177+
178+
link = bpf_program__attach(skel->progs.check_cgroup_storage_refcount);
179+
if (!ASSERT_OK_PTR(link, "bpf_program__attach"))
180+
goto out;
181+
skel->links.check_cgroup_storage_refcount = link;
182+
183+
close(client_fd);
184+
client_fd = connect_to_fd(server_fd, 0);
185+
if (!ASSERT_GE(client_fd, 0, "connect_to_fd"))
186+
goto out;
187+
188+
err = bpf_map__lookup_elem(map, &cgroup, sizeof(cgroup), &value, sizeof(value), flags);
189+
if (!ASSERT_OK(err, "bpf_map__lookup_elem"))
190+
goto out;
191+
192+
ASSERT_EQ(value.value, 1, "refcount");
193+
out:
194+
close(cgroup);
195+
refcounted_kptr__destroy(skel);
196+
if (client_fd >= 0)
197+
close(client_fd);
198+
if (server_fd >= 0)
199+
close(server_fd);
200+
}
201+
202+
static void test_cgroup_storage_refcount_leak(void)
203+
{
204+
test_cgrp_storage_refcount_leak(0);
205+
}
206+
207+
static void test_cgroup_storage_lock_refcount_leak(void)
208+
{
209+
test_cgrp_storage_refcount_leak(BPF_F_LOCK);
210+
}
211+
212+
void test_kptr_refcount_leak(void)
213+
{
214+
if (test__start_subtest("percpu_hash_refcount_leak"))
215+
test_percpu_hash_refcount_leak();
216+
if (test__start_subtest("hash_lock_refcount_leak"))
217+
test_hash_lock_refcount_leak();
218+
if (test__start_subtest("cgroup_storage_refcount_leak"))
219+
test_cgroup_storage_refcount_leak();
220+
if (test__start_subtest("cgroup_storage_lock_refcount_leak"))
221+
test_cgroup_storage_lock_refcount_leak();
222+
}

0 commit comments

Comments
 (0)