Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

+156 -17
+26 -12
tools/lib/bpf/libbpf.c
··· 1152 1152 return -ENOTSUP; 1153 1153 } 1154 1154 1155 - prog = st_ops->progs[i]; 1156 - if (prog) { 1155 + if (st_ops->progs[i]) { 1157 1156 /* 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 1160 1158 * a chance of succeeding from POV of the current struct_ops map. 1161 1159 * If this program is still referenced somewhere else, though, 1162 1160 * then bpf_object_adjust_struct_ops_autoload() will update its 1163 1161 * autoload accordingly. 1164 1162 */ 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; 1171 1164 st_ops->progs[i] = NULL; 1172 1165 } 1173 1166 ··· 1193 1200 } 1194 1201 1195 1202 if (btf_is_ptr(mtype)) { 1196 - /* Update the value from the shadow type */ 1197 1203 prog = *(void **)mdata; 1204 + /* just like for !kern_member case above, reset declaratively 1205 + * set (at compile time) program's autload to false, 1206 + * if user replaced it with another program or NULL 1207 + */ 1208 + if (st_ops->progs[i] && st_ops->progs[i] != prog) 1209 + st_ops->progs[i]->autoload = false; 1210 + 1211 + /* Update the value from the shadow type */ 1198 1212 st_ops->progs[i] = prog; 1199 1213 if (!prog) 1200 1214 continue; 1215 + 1201 1216 if (!is_valid_st_ops_program(obj, prog)) { 1202 1217 pr_warn("struct_ops init_kern %s: member %s is not a struct_ops program\n", 1203 1218 map->name, mname); ··· 7372 7371 __u32 log_level = prog->log_level; 7373 7372 int ret, err; 7374 7373 7375 - if (prog->type == BPF_PROG_TYPE_UNSPEC) { 7374 + /* Be more helpful by rejecting programs that can't be validated early 7375 + * with more meaningful and actionable error message. 7376 + */ 7377 + switch (prog->type) { 7378 + case BPF_PROG_TYPE_UNSPEC: 7376 7379 /* 7377 7380 * The program type must be set. Most likely we couldn't find a proper 7378 7381 * section definition at load time, and thus we didn't infer the type. ··· 7384 7379 pr_warn("prog '%s': missing BPF prog type, check ELF section name '%s'\n", 7385 7380 prog->name, prog->sec_name); 7386 7381 return -EINVAL; 7382 + case BPF_PROG_TYPE_STRUCT_OPS: 7383 + if (prog->attach_btf_id == 0) { 7384 + pr_warn("prog '%s': SEC(\"struct_ops\") program isn't referenced anywhere, did you forget to use it?\n", 7385 + prog->name); 7386 + return -EINVAL; 7387 + } 7388 + break; 7389 + default: 7390 + break; 7387 7391 } 7388 7392 7389 7393 if (!insns || !insns_cnt)
+14 -2
tools/lib/bpf/str_error.c
··· 2 2 #undef _GNU_SOURCE 3 3 #include <string.h> 4 4 #include <stdio.h> 5 + #include <errno.h> 5 6 #include "str_error.h" 6 7 7 8 /* make sure libbpf doesn't use kernel-only integer typedefs */ ··· 16 15 char *libbpf_strerror_r(int err, char *dst, int len) 17 16 { 18 17 int ret = strerror_r(err < 0 ? -err : err, dst, len); 19 - if (ret) 20 - snprintf(dst, len, "ERROR: strerror_r(%d)=%d", err, ret); 18 + /* on glibc <2.13, ret == -1 and errno is set, if strerror_r() can't 19 + * handle the error, on glibc >=2.13 *positive* (errno-like) error 20 + * code is returned directly 21 + */ 22 + if (ret == -1) 23 + ret = errno; 24 + if (ret) { 25 + if (ret == EINVAL) 26 + /* strerror_r() doesn't recognize this specific error */ 27 + snprintf(dst, len, "unknown error (%d)", err < 0 ? err : -err); 28 + else 29 + snprintf(dst, len, "ERROR: strerror_r(%d)=%d", err, ret); 30 + } 21 31 return dst; 22 32 }
+75 -3
tools/testing/selftests/bpf/prog_tests/test_struct_ops_module.c
··· 4 4 #include <time.h> 5 5 6 6 #include "struct_ops_module.skel.h" 7 + #include "struct_ops_nulled_out_cb.skel.h" 8 + #include "struct_ops_forgotten_cb.skel.h" 7 9 8 10 static void check_map_info(struct bpf_map_info *info) 9 11 { ··· 176 174 struct_ops_module__destroy(skel); 177 175 } 178 176 177 + /* validate that it's ok to "turn off" callback that kernel supports */ 178 + static void test_struct_ops_nulled_out_cb(void) 179 + { 180 + struct struct_ops_nulled_out_cb *skel; 181 + int err; 182 + 183 + skel = struct_ops_nulled_out_cb__open(); 184 + if (!ASSERT_OK_PTR(skel, "skel_open")) 185 + return; 186 + 187 + /* kernel knows about test_1, but we still null it out */ 188 + skel->struct_ops.ops->test_1 = NULL; 189 + 190 + err = struct_ops_nulled_out_cb__load(skel); 191 + if (!ASSERT_OK(err, "skel_load")) 192 + goto cleanup; 193 + 194 + ASSERT_FALSE(bpf_program__autoload(skel->progs.test_1_turn_off), "prog_autoload"); 195 + ASSERT_LT(bpf_program__fd(skel->progs.test_1_turn_off), 0, "prog_fd"); 196 + 197 + cleanup: 198 + struct_ops_nulled_out_cb__destroy(skel); 199 + } 200 + 201 + /* validate that libbpf generates reasonable error message if struct_ops is 202 + * not referenced in any struct_ops map 203 + */ 204 + static void test_struct_ops_forgotten_cb(void) 205 + { 206 + struct struct_ops_forgotten_cb *skel; 207 + char *log; 208 + int err; 209 + 210 + skel = struct_ops_forgotten_cb__open(); 211 + if (!ASSERT_OK_PTR(skel, "skel_open")) 212 + return; 213 + 214 + start_libbpf_log_capture(); 215 + 216 + err = struct_ops_forgotten_cb__load(skel); 217 + if (!ASSERT_ERR(err, "skel_load")) 218 + goto cleanup; 219 + 220 + log = stop_libbpf_log_capture(); 221 + ASSERT_HAS_SUBSTR(log, 222 + "prog 'test_1_forgotten': SEC(\"struct_ops\") program isn't referenced anywhere, did you forget to use it?", 223 + "libbpf_log"); 224 + free(log); 225 + 226 + struct_ops_forgotten_cb__destroy(skel); 227 + 228 + /* now let's programmatically use it, we should be fine now */ 229 + skel = struct_ops_forgotten_cb__open(); 230 + if (!ASSERT_OK_PTR(skel, "skel_open")) 231 + return; 232 + 233 + skel->struct_ops.ops->test_1 = skel->progs.test_1_forgotten; /* not anymore */ 234 + 235 + err = struct_ops_forgotten_cb__load(skel); 236 + if (!ASSERT_OK(err, "skel_load")) 237 + goto cleanup; 238 + 239 + cleanup: 240 + struct_ops_forgotten_cb__destroy(skel); 241 + } 242 + 179 243 void serial_test_struct_ops_module(void) 180 244 { 181 - if (test__start_subtest("test_struct_ops_load")) 245 + if (test__start_subtest("struct_ops_load")) 182 246 test_struct_ops_load(); 183 - if (test__start_subtest("test_struct_ops_not_zeroed")) 247 + if (test__start_subtest("struct_ops_not_zeroed")) 184 248 test_struct_ops_not_zeroed(); 185 - if (test__start_subtest("test_struct_ops_incompatible")) 249 + if (test__start_subtest("struct_ops_incompatible")) 186 250 test_struct_ops_incompatible(); 251 + if (test__start_subtest("struct_ops_null_out_cb")) 252 + test_struct_ops_nulled_out_cb(); 253 + if (test__start_subtest("struct_ops_forgotten_cb")) 254 + test_struct_ops_forgotten_cb(); 187 255 } 188 256
+19
tools/testing/selftests/bpf/progs/struct_ops_forgotten_cb.c
··· 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 +
+22
tools/testing/selftests/bpf/progs/struct_ops_nulled_out_cb.c
··· 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 +