Skip to content

Commit 7e2c7a3

Browse files
author
Martin KaFai Lau
committed
Merge branch 'libbpf: further struct_ops fixes and improvements'
Andrii Nakryiko says: ==================== Fix yet another case of mishandling SEC("struct_ops") programs that were nulled out programmatically through BPF skeleton by the user. While at it, add some improvements around detecting and reporting errors, specifically a common case of declaring SEC("struct_ops") program, but forgetting to actually make use of it by setting it as a callback implementation in SEC(".struct_ops") variable (i.e., map) declaration. A bunch of new selftests are added as well. ==================== Signed-off-by: Martin KaFai Lau <martin.lau@kernel.org>
2 parents 93d1c2d + 7b9959b commit 7e2c7a3

5 files changed

Lines changed: 156 additions & 17 deletions

File tree

tools/lib/bpf/libbpf.c

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,22 +1152,15 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
11521152
return -ENOTSUP;
11531153
}
11541154

1155-
prog = st_ops->progs[i];
1156-
if (prog) {
1155+
if (st_ops->progs[i]) {
11571156
/* If we had declaratively set struct_ops callback, we need to
1158-
* first validate that it's actually a struct_ops program.
1159-
* And then force its autoload to false, because it doesn't have
1157+
* force its autoload to false, because it doesn't have
11601158
* a chance of succeeding from POV of the current struct_ops map.
11611159
* If this program is still referenced somewhere else, though,
11621160
* then bpf_object_adjust_struct_ops_autoload() will update its
11631161
* autoload accordingly.
11641162
*/
1165-
if (!is_valid_st_ops_program(obj, prog)) {
1166-
pr_warn("struct_ops init_kern %s: member %s is declaratively assigned a non-struct_ops program\n",
1167-
map->name, mname);
1168-
return -EINVAL;
1169-
}
1170-
prog->autoload = false;
1163+
st_ops->progs[i]->autoload = false;
11711164
st_ops->progs[i] = NULL;
11721165
}
11731166

@@ -1200,11 +1193,19 @@ static int bpf_map__init_kern_struct_ops(struct bpf_map *map)
12001193
}
12011194

12021195
if (btf_is_ptr(mtype)) {
1203-
/* Update the value from the shadow type */
12041196
prog = *(void **)mdata;
1197+
/* just like for !kern_member case above, reset declaratively
1198+
* set (at compile time) program's autload to false,
1199+
* if user replaced it with another program or NULL
1200+
*/
1201+
if (st_ops->progs[i] && st_ops->progs[i] != prog)
1202+
st_ops->progs[i]->autoload = false;
1203+
1204+
/* Update the value from the shadow type */
12051205
st_ops->progs[i] = prog;
12061206
if (!prog)
12071207
continue;
1208+
12081209
if (!is_valid_st_ops_program(obj, prog)) {
12091210
pr_warn("struct_ops init_kern %s: member %s is not a struct_ops program\n",
12101211
map->name, mname);
@@ -7371,14 +7372,27 @@ static int bpf_object_load_prog(struct bpf_object *obj, struct bpf_program *prog
73717372
__u32 log_level = prog->log_level;
73727373
int ret, err;
73737374

7374-
if (prog->type == BPF_PROG_TYPE_UNSPEC) {
7375+
/* Be more helpful by rejecting programs that can't be validated early
7376+
* with more meaningful and actionable error message.
7377+
*/
7378+
switch (prog->type) {
7379+
case BPF_PROG_TYPE_UNSPEC:
73757380
/*
73767381
* The program type must be set. Most likely we couldn't find a proper
73777382
* section definition at load time, and thus we didn't infer the type.
73787383
*/
73797384
pr_warn("prog '%s': missing BPF prog type, check ELF section name '%s'\n",
73807385
prog->name, prog->sec_name);
73817386
return -EINVAL;
7387+
case BPF_PROG_TYPE_STRUCT_OPS:
7388+
if (prog->attach_btf_id == 0) {
7389+
pr_warn("prog '%s': SEC(\"struct_ops\") program isn't referenced anywhere, did you forget to use it?\n",
7390+
prog->name);
7391+
return -EINVAL;
7392+
}
7393+
break;
7394+
default:
7395+
break;
73827396
}
73837397

73847398
if (!insns || !insns_cnt)

tools/lib/bpf/str_error.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#undef _GNU_SOURCE
33
#include <string.h>
44
#include <stdio.h>
5+
#include <errno.h>
56
#include "str_error.h"
67

78
/* make sure libbpf doesn't use kernel-only integer typedefs */
@@ -15,7 +16,18 @@
1516
char *libbpf_strerror_r(int err, char *dst, int len)
1617
{
1718
int ret = strerror_r(err < 0 ? -err : err, dst, len);
18-
if (ret)
19-
snprintf(dst, len, "ERROR: strerror_r(%d)=%d", err, ret);
19+
/* on glibc <2.13, ret == -1 and errno is set, if strerror_r() can't
20+
* handle the error, on glibc >=2.13 *positive* (errno-like) error
21+
* code is returned directly
22+
*/
23+
if (ret == -1)
24+
ret = errno;
25+
if (ret) {
26+
if (ret == EINVAL)
27+
/* strerror_r() doesn't recognize this specific error */
28+
snprintf(dst, len, "unknown error (%d)", err < 0 ? err : -err);
29+
else
30+
snprintf(dst, len, "ERROR: strerror_r(%d)=%d", err, ret);
31+
}
2032
return dst;
2133
}

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

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include <time.h>
55

66
#include "struct_ops_module.skel.h"
7+
#include "struct_ops_nulled_out_cb.skel.h"
8+
#include "struct_ops_forgotten_cb.skel.h"
79

810
static void check_map_info(struct bpf_map_info *info)
911
{
@@ -174,13 +176,83 @@ static void test_struct_ops_incompatible(void)
174176
struct_ops_module__destroy(skel);
175177
}
176178

179+
/* validate that it's ok to "turn off" callback that kernel supports */
180+
static void test_struct_ops_nulled_out_cb(void)
181+
{
182+
struct struct_ops_nulled_out_cb *skel;
183+
int err;
184+
185+
skel = struct_ops_nulled_out_cb__open();
186+
if (!ASSERT_OK_PTR(skel, "skel_open"))
187+
return;
188+
189+
/* kernel knows about test_1, but we still null it out */
190+
skel->struct_ops.ops->test_1 = NULL;
191+
192+
err = struct_ops_nulled_out_cb__load(skel);
193+
if (!ASSERT_OK(err, "skel_load"))
194+
goto cleanup;
195+
196+
ASSERT_FALSE(bpf_program__autoload(skel->progs.test_1_turn_off), "prog_autoload");
197+
ASSERT_LT(bpf_program__fd(skel->progs.test_1_turn_off), 0, "prog_fd");
198+
199+
cleanup:
200+
struct_ops_nulled_out_cb__destroy(skel);
201+
}
202+
203+
/* validate that libbpf generates reasonable error message if struct_ops is
204+
* not referenced in any struct_ops map
205+
*/
206+
static void test_struct_ops_forgotten_cb(void)
207+
{
208+
struct struct_ops_forgotten_cb *skel;
209+
char *log;
210+
int err;
211+
212+
skel = struct_ops_forgotten_cb__open();
213+
if (!ASSERT_OK_PTR(skel, "skel_open"))
214+
return;
215+
216+
start_libbpf_log_capture();
217+
218+
err = struct_ops_forgotten_cb__load(skel);
219+
if (!ASSERT_ERR(err, "skel_load"))
220+
goto cleanup;
221+
222+
log = stop_libbpf_log_capture();
223+
ASSERT_HAS_SUBSTR(log,
224+
"prog 'test_1_forgotten': SEC(\"struct_ops\") program isn't referenced anywhere, did you forget to use it?",
225+
"libbpf_log");
226+
free(log);
227+
228+
struct_ops_forgotten_cb__destroy(skel);
229+
230+
/* now let's programmatically use it, we should be fine now */
231+
skel = struct_ops_forgotten_cb__open();
232+
if (!ASSERT_OK_PTR(skel, "skel_open"))
233+
return;
234+
235+
skel->struct_ops.ops->test_1 = skel->progs.test_1_forgotten; /* not anymore */
236+
237+
err = struct_ops_forgotten_cb__load(skel);
238+
if (!ASSERT_OK(err, "skel_load"))
239+
goto cleanup;
240+
241+
cleanup:
242+
struct_ops_forgotten_cb__destroy(skel);
243+
}
244+
177245
void serial_test_struct_ops_module(void)
178246
{
179-
if (test__start_subtest("test_struct_ops_load"))
247+
if (test__start_subtest("struct_ops_load"))
180248
test_struct_ops_load();
181-
if (test__start_subtest("test_struct_ops_not_zeroed"))
249+
if (test__start_subtest("struct_ops_not_zeroed"))
182250
test_struct_ops_not_zeroed();
183-
if (test__start_subtest("test_struct_ops_incompatible"))
251+
if (test__start_subtest("struct_ops_incompatible"))
184252
test_struct_ops_incompatible();
253+
if (test__start_subtest("struct_ops_null_out_cb"))
254+
test_struct_ops_nulled_out_cb();
255+
if (test__start_subtest("struct_ops_forgotten_cb"))
256+
test_struct_ops_forgotten_cb();
185257
}
186258

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
3+
#include <vmlinux.h>
4+
#include <bpf/bpf_tracing.h>
5+
#include "../bpf_testmod/bpf_testmod.h"
6+
7+
char _license[] SEC("license") = "GPL";
8+
9+
SEC("struct_ops/test_1")
10+
int BPF_PROG(test_1_forgotten)
11+
{
12+
return 0;
13+
}
14+
15+
SEC(".struct_ops.link")
16+
struct bpf_testmod_ops ops = {
17+
/* we forgot to reference test_1_forgotten above, oops */
18+
};
19+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
3+
#include <vmlinux.h>
4+
#include <bpf/bpf_tracing.h>
5+
#include "../bpf_testmod/bpf_testmod.h"
6+
7+
char _license[] SEC("license") = "GPL";
8+
9+
int rand;
10+
int arr[1];
11+
12+
SEC("struct_ops/test_1")
13+
int BPF_PROG(test_1_turn_off)
14+
{
15+
return arr[rand]; /* potentially way out of range access */
16+
}
17+
18+
SEC(".struct_ops.link")
19+
struct bpf_testmod_ops ops = {
20+
.test_1 = (void *)test_1_turn_off,
21+
};
22+

0 commit comments

Comments
 (0)