| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/10 11:25 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"CGROUP_BPF",
"INET",
"IPV6"
],
"FocusSymbols": [
"bpf_icmp_send"
],
"Reasoning": "The patch introduces a new BPF kfunc `bpf_icmp_send` that allows BPF programs (specifically CGROUP_SKB programs) to send ICMP control messages. This is a functional change that adds new reachable kernel logic, making it a good candidate for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/10 11:25 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit d74f6b888c1a6e967121d981dc3b130f978a43b3\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Fri Jul 10 11:25:44 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/net/core/filter.c b/net/core/filter.c\nindex 4f5cbcac3e7819..e4697036c67b53 100644\n--- a/net/core/filter.c\n+++ b/net/core/filter.c\n@@ -84,6 +84,9 @@\n #include \u003clinux/un.h\u003e\n #include \u003cnet/xdp_sock_drv.h\u003e\n #include \u003cnet/inet_dscp.h\u003e\n+#include \u003clinux/icmpv6.h\u003e\n+#include \u003cnet/icmp.h\u003e\n+#include \u003cnet/ip6_route.h\u003e\n \n #include \"dev.h\"\n \n@@ -12548,6 +12551,83 @@ __bpf_kfunc int bpf_xdp_pull_data(struct xdp_md *x, u32 len)\n \treturn 0;\n }\n \n+/**\n+ * bpf_icmp_send - Send an ICMP control message\n+ * @skb_ctx: Packet that triggered the control message\n+ * @type: ICMP type (only ICMP_DEST_UNREACH/ICMPV6_DEST_UNREACH supported)\n+ * @code: ICMP code (0-15 except ICMP_FRAG_NEEDED for IPv4, 0-6 for IPv6)\n+ *\n+ * Sends an ICMP control message in response to the packet. The original packet\n+ * is cloned before sending the ICMP message, so the BPF program can still let\n+ * the packet pass if desired.\n+ *\n+ * Currently only ICMP_DEST_UNREACH (IPv4) and ICMPV6_DEST_UNREACH (IPv6) are\n+ * supported.\n+ *\n+ * Return: 0 on success (send attempt), negative error code on failure:\n+ * -EBUSY: Recursion detected\n+ * -EPROTONOSUPPORT: Non-IP protocol\n+ * -EOPNOTSUPP: Unsupported ICMP type\n+ * -EINVAL: Invalid code parameter\n+ * -ENETUNREACH: No usable route/dst for the ICMP reply\n+ * -ENOMEM: Memory allocation failed\n+ */\n+__bpf_kfunc int bpf_icmp_send(struct __sk_buff *skb_ctx, int type, int code)\n+{\n+\tstruct sk_buff *skb = (struct sk_buff *)skb_ctx;\n+\tstruct sk_buff *nskb;\n+\tstruct sock *sk;\n+\n+\tsk = skb_to_full_sk(skb);\n+\tif (sk \u0026\u0026 sk-\u003esk_kern_sock \u0026\u0026\n+\t (sk-\u003esk_protocol == IPPROTO_ICMP || sk-\u003esk_protocol == IPPROTO_ICMPV6))\n+\t\treturn -EBUSY;\n+\n+\tif (!skb_valid_dst(skb))\n+\t\treturn -ENETUNREACH;\n+\n+\tswitch (skb-\u003eprotocol) {\n+#if IS_ENABLED(CONFIG_INET)\n+\tcase htons(ETH_P_IP): {\n+\t\tif (type != ICMP_DEST_UNREACH)\n+\t\t\treturn -EOPNOTSUPP;\n+\t\tif (code \u003c 0 || code \u003e NR_ICMP_UNREACH ||\n+\t\t code == ICMP_FRAG_NEEDED) /* needs a valid next-hop MTU */\n+\t\t\treturn -EINVAL;\n+\n+\t\tnskb = skb_clone(skb, GFP_ATOMIC);\n+\t\tif (!nskb)\n+\t\t\treturn -ENOMEM;\n+\n+\t\tmemset(IPCB(nskb), 0, sizeof(*IPCB(nskb)));\n+\t\ticmp_send(nskb, type, code, 0);\n+\t\tconsume_skb(nskb);\n+\t\tbreak;\n+\t}\n+#endif\n+#if IS_ENABLED(CONFIG_IPV6)\n+\tcase htons(ETH_P_IPV6):\n+\t\tif (type != ICMPV6_DEST_UNREACH)\n+\t\t\treturn -EOPNOTSUPP;\n+\t\tif (code \u003c 0 || code \u003e ICMPV6_REJECT_ROUTE)\n+\t\t\treturn -EINVAL;\n+\n+\t\tnskb = skb_clone(skb, GFP_ATOMIC);\n+\t\tif (!nskb)\n+\t\t\treturn -ENOMEM;\n+\n+\t\tmemset(IP6CB(nskb), 0, sizeof(*IP6CB(nskb)));\n+\t\ticmpv6_send(nskb, type, code, 0);\n+\t\tconsume_skb(nskb);\n+\t\tbreak;\n+#endif\n+\tdefault:\n+\t\treturn -EPROTONOSUPPORT;\n+\t}\n+\n+\treturn 0;\n+}\n+\n __bpf_kfunc_end_defs();\n \n int bpf_dynptr_from_skb_rdonly(struct __sk_buff *skb, u64 flags,\n@@ -12590,6 +12670,10 @@ BTF_KFUNCS_START(bpf_kfunc_check_set_sock_ops)\n BTF_ID_FLAGS(func, bpf_sock_ops_enable_tx_tstamp)\n BTF_KFUNCS_END(bpf_kfunc_check_set_sock_ops)\n \n+BTF_KFUNCS_START(bpf_kfunc_check_set_icmp_send)\n+BTF_ID_FLAGS(func, bpf_icmp_send)\n+BTF_KFUNCS_END(bpf_kfunc_check_set_icmp_send)\n+\n static const struct btf_kfunc_id_set bpf_kfunc_set_skb = {\n \t.owner = THIS_MODULE,\n \t.set = \u0026bpf_kfunc_check_set_skb,\n@@ -12620,6 +12704,11 @@ static const struct btf_kfunc_id_set bpf_kfunc_set_sock_ops = {\n \t.set = \u0026bpf_kfunc_check_set_sock_ops,\n };\n \n+static const struct btf_kfunc_id_set bpf_kfunc_set_icmp_send = {\n+\t.owner = THIS_MODULE,\n+\t.set = \u0026bpf_kfunc_check_set_icmp_send,\n+};\n+\n static int __init bpf_kfunc_init(void)\n {\n \tint ret;\n@@ -12641,6 +12730,7 @@ static int __init bpf_kfunc_init(void)\n \tret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SOCK_ADDR,\n \t\t\t\t\t \u0026bpf_kfunc_set_sock_addr);\n \tret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, \u0026bpf_kfunc_set_tcp_reqsk);\n+\tret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SKB, \u0026bpf_kfunc_set_icmp_send);\n \treturn ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SOCK_OPS, \u0026bpf_kfunc_set_sock_ops);\n }\n late_initcall(bpf_kfunc_init);\ndiff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c\nnew file mode 100644\nindex 00000000000000..9318d4bc7ce8be\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c\n@@ -0,0 +1,292 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \u003ctest_progs.h\u003e\n+#include \u003cnetwork_helpers.h\u003e\n+#include \u003ccgroup_helpers.h\u003e\n+#include \u003clinux/errqueue.h\u003e\n+#include \u003cpoll.h\u003e\n+#include \u003cunistd.h\u003e\n+#include \"icmp_send.skel.h\"\n+\n+#define TIMEOUT_MS 1000\n+\n+#define ICMP_DEST_UNREACH 3\n+#define ICMPV6_DEST_UNREACH 1\n+\n+#define ICMP_HOST_UNREACH 1\n+#define ICMP_FRAG_NEEDED 4\n+#define NR_ICMP_UNREACH 15\n+#define ICMPV6_REJECT_ROUTE 6\n+\n+#define KFUNC_RET_UNSET -1\n+\n+static int connect_to_fd_nonblock(int server_fd)\n+{\n+\tstruct sockaddr_storage addr;\n+\tsocklen_t len = sizeof(addr);\n+\tint fd, err, on = 1;\n+\n+\tif (getsockname(server_fd, (struct sockaddr *)\u0026addr, \u0026len))\n+\t\treturn -1;\n+\n+\tfd = socket(addr.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);\n+\tif (fd \u003c 0)\n+\t\treturn -1;\n+\n+\tif (addr.ss_family == AF_INET6 \u0026\u0026\n+\t setsockopt(fd, IPPROTO_IPV6, IPV6_RECVERR, \u0026on, sizeof(on)) \u003c 0) {\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\terr = connect(fd, (struct sockaddr *)\u0026addr, len);\n+\tif (err \u003c 0 \u0026\u0026 errno != EINPROGRESS) {\n+\t\tclose(fd);\n+\t\treturn -1;\n+\t}\n+\n+\treturn fd;\n+}\n+\n+static void read_icmp_errqueue(int sockfd, int expected_code, int af)\n+{\n+\tint expected_ee_type = (af == AF_INET) ? ICMP_DEST_UNREACH :\n+\t\t\t\t\t\t ICMPV6_DEST_UNREACH;\n+\tint expected_origin = (af == AF_INET) ? SO_EE_ORIGIN_ICMP :\n+\t\t\t\t\t\tSO_EE_ORIGIN_ICMP6;\n+\tint expected_level = (af == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;\n+\tint expected_type = (af == AF_INET) ? IP_RECVERR : IPV6_RECVERR;\n+\tstruct sock_extended_err *sock_err;\n+\tchar ctrl_buf[512];\n+\tstruct msghdr msg = {\n+\t\t.msg_control = ctrl_buf,\n+\t\t.msg_controllen = sizeof(ctrl_buf),\n+\t};\n+\tstruct pollfd pfd = {\n+\t\t.fd = sockfd,\n+\t\t.events = POLLERR,\n+\t};\n+\tstruct cmsghdr *cm;\n+\tssize_t n;\n+\n+\tif (!ASSERT_GE(poll(\u0026pfd, 1, TIMEOUT_MS), 1, \"poll_errqueue\"))\n+\t\treturn;\n+\n+\tn = recvmsg(sockfd, \u0026msg, MSG_ERRQUEUE);\n+\tif (!ASSERT_GE(n, 0, \"recvmsg_errqueue\"))\n+\t\treturn;\n+\n+\tcm = CMSG_FIRSTHDR(\u0026msg);\n+\tif (!ASSERT_NEQ(cm, NULL, \"cm_firsthdr_null\"))\n+\t\treturn;\n+\n+\tfor (; cm; cm = CMSG_NXTHDR(\u0026msg, cm)) {\n+\t\tif (cm-\u003ecmsg_level != expected_level ||\n+\t\t cm-\u003ecmsg_type != expected_type)\n+\t\t\tcontinue;\n+\n+\t\tsock_err = (struct sock_extended_err *)CMSG_DATA(cm);\n+\n+\t\tif (!ASSERT_EQ(sock_err-\u003eee_origin, expected_origin,\n+\t\t\t \"sock_err_origin\"))\n+\t\t\treturn;\n+\t\tif (!ASSERT_EQ(sock_err-\u003eee_type, expected_ee_type,\n+\t\t\t \"sock_err_type_dest_unreach\"))\n+\t\t\treturn;\n+\t\tASSERT_EQ(sock_err-\u003eee_code, expected_code, \"sock_err_code\");\n+\t\treturn;\n+\t}\n+\n+\tASSERT_FAIL(\"no IP_RECVERR/IPV6_RECVERR control message found\");\n+}\n+\n+static bool valid_unreach_code(int code, int af)\n+{\n+\tif (code \u003c 0)\n+\t\treturn false;\n+\n+\tif (af == AF_INET)\n+\t\treturn code \u003c= NR_ICMP_UNREACH \u0026\u0026 code != ICMP_FRAG_NEEDED;\n+\n+\treturn code \u003c= ICMPV6_REJECT_ROUTE;\n+}\n+\n+static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code,\n+\t\t\t\t\t int af, const char *ip)\n+{\n+\tint srv_fd = -1, client_fd = -1;\n+\tint port;\n+\n+\tsrv_fd = start_server(af, SOCK_STREAM, ip, 0, TIMEOUT_MS);\n+\tif (!ASSERT_OK_FD(srv_fd, \"start_server\"))\n+\t\treturn;\n+\n+\tport = get_socket_local_port(srv_fd);\n+\tif (!ASSERT_GE(port, 0, \"get_socket_local_port\")) {\n+\t\tclose(srv_fd);\n+\t\treturn;\n+\t}\n+\n+\tskel-\u003ebss-\u003eserver_port = ntohs(port);\n+\tskel-\u003ebss-\u003eunreach_type = (af == AF_INET) ? ICMP_DEST_UNREACH :\n+\t\t\t\t\t\t ICMPV6_DEST_UNREACH;\n+\tskel-\u003ebss-\u003eunreach_code = code;\n+\tskel-\u003edata-\u003ekfunc_ret = KFUNC_RET_UNSET;\n+\n+\tclient_fd = connect_to_fd_nonblock(srv_fd);\n+\tif (!ASSERT_OK_FD(client_fd, \"client_connect_nonblock\")) {\n+\t\tclose(srv_fd);\n+\t\treturn;\n+\t}\n+\n+\tif (valid_unreach_code(code, af))\n+\t\tread_icmp_errqueue(client_fd, code, af);\n+\n+\tclose(client_fd);\n+\tclose(srv_fd);\n+}\n+\n+static void run_icmp_test(struct icmp_send *skel, int af, const char *ip,\n+\t\t\t int max_code)\n+{\n+\tfor (int code = 0; code \u003c= max_code; code++) {\n+\t\tif (af == AF_INET \u0026\u0026 code == ICMP_FRAG_NEEDED)\n+\t\t\tcontinue;\n+\n+\t\ttrigger_prog_read_icmp_errqueue(skel, code, af, ip);\n+\t\tASSERT_EQ(skel-\u003edata-\u003ekfunc_ret, 0, \"kfunc_ret\");\n+\t}\n+\n+\t/* Test invalid codes */\n+\ttrigger_prog_read_icmp_errqueue(skel, -1, af, ip);\n+\tASSERT_EQ(skel-\u003edata-\u003ekfunc_ret, -EINVAL, \"kfunc_ret\");\n+\n+\ttrigger_prog_read_icmp_errqueue(skel, max_code + 1, af, ip);\n+\tASSERT_EQ(skel-\u003edata-\u003ekfunc_ret, -EINVAL, \"kfunc_ret\");\n+\n+\tif (af == AF_INET) {\n+\t\ttrigger_prog_read_icmp_errqueue(skel, ICMP_FRAG_NEEDED, af, ip);\n+\t\tASSERT_EQ(skel-\u003edata-\u003ekfunc_ret, -EINVAL, \"kfunc_ret\");\n+\t}\n+}\n+\n+static void run_icmp_no_route_test(struct icmp_send *skel, int af)\n+{\n+\tunion {\n+\t\tstruct ipv4_packet v4;\n+\t\tstruct ipv6_packet v6;\n+\t} pkt;\n+\tDECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts,\n+\t\t.data_in = \u0026pkt,\n+\t);\n+\tint err;\n+\n+\tswitch (af) {\n+\tcase AF_INET:\n+\t\tpkt.v4 = pkt_v4;\n+\t\tpkt.v4.iph.version = 4;\n+\t\tpkt.v4.iph.daddr = htonl(INADDR_LOOPBACK);\n+\t\tpkt.v4.tcp.dest = htons(80);\n+\t\topts.data_size_in = sizeof(pkt.v4);\n+\t\tskel-\u003ebss-\u003eunreach_type = ICMP_DEST_UNREACH;\n+\t\tbreak;\n+\tcase AF_INET6:\n+\t\tpkt.v6 = pkt_v6;\n+\t\tpkt.v6.iph.version = 6;\n+\t\tpkt.v6.iph.daddr = in6addr_loopback;\n+\t\tpkt.v6.tcp.dest = htons(80);\n+\t\topts.data_size_in = sizeof(pkt.v6);\n+\t\tskel-\u003ebss-\u003eunreach_type = ICMPV6_DEST_UNREACH;\n+\t\tbreak;\n+\tdefault:\n+\t\tASSERT_FAIL(\"af_not_supported\");\n+\t\treturn;\n+\t}\n+\n+\tskel-\u003ebss-\u003eserver_port = 80;\n+\tskel-\u003edata-\u003ekfunc_ret = KFUNC_RET_UNSET;\n+\n+\terr = bpf_prog_test_run_opts(bpf_program__fd(skel-\u003eprogs.egress), \u0026opts);\n+\tif (!ASSERT_OK(err, \"test_run\"))\n+\t\treturn;\n+\n+\tASSERT_EQ(skel-\u003edata-\u003ekfunc_ret, -ENETUNREACH, \"kfunc_ret_no_route\");\n+}\n+\n+void test_icmp_send_unreach_cgroup(void)\n+{\n+\tstruct icmp_send *skel;\n+\tint cgroup_fd = -1;\n+\n+\tskel = icmp_send__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\tgoto cleanup;\n+\n+\tcgroup_fd = test__join_cgroup(\"/icmp_send_unreach_cgroup\");\n+\tif (!ASSERT_OK_FD(cgroup_fd, \"join_cgroup\"))\n+\t\tgoto cleanup;\n+\n+\tskel-\u003elinks.egress =\n+\t\tbpf_program__attach_cgroup(skel-\u003eprogs.egress, cgroup_fd);\n+\tif (!ASSERT_OK_PTR(skel-\u003elinks.egress, \"prog_attach_cgroup\"))\n+\t\tgoto cleanup;\n+\n+\tif (test__start_subtest(\"ipv4\"))\n+\t\trun_icmp_test(skel, AF_INET, \"127.0.0.1\", NR_ICMP_UNREACH);\n+\n+\tif (test__start_subtest(\"ipv6\"))\n+\t\trun_icmp_test(skel, AF_INET6, \"::1\", ICMPV6_REJECT_ROUTE);\n+\n+\tif (test__start_subtest(\"no_route_ipv4\"))\n+\t\trun_icmp_no_route_test(skel, AF_INET);\n+\n+\tif (test__start_subtest(\"no_route_ipv6\"))\n+\t\trun_icmp_no_route_test(skel, AF_INET6);\n+\n+cleanup:\n+\ticmp_send__destroy(skel);\n+\tif (cgroup_fd \u003e= 0)\n+\t\tclose(cgroup_fd);\n+}\n+\n+void test_icmp_send_unreach_recursion(void)\n+{\n+\tstruct icmp_send *skel;\n+\tint cgroup_fd = -1;\n+\tint err;\n+\n+\terr = setup_cgroup_environment();\n+\tif (!ASSERT_OK(err, \"setup_cgroup_environment\"))\n+\t\treturn;\n+\n+\tskel = icmp_send__open_and_load();\n+\tif (!ASSERT_OK_PTR(skel, \"skel_open\"))\n+\t\tgoto cleanup;\n+\n+\tcgroup_fd = get_root_cgroup();\n+\tif (!ASSERT_OK_FD(cgroup_fd, \"get_root_cgroup\"))\n+\t\tgoto cleanup;\n+\n+\tskel-\u003edata-\u003etarget_pid = getpid();\n+\tskel-\u003elinks.recursion =\n+\t\tbpf_program__attach_cgroup(skel-\u003eprogs.recursion, cgroup_fd);\n+\tif (!ASSERT_OK_PTR(skel-\u003elinks.recursion, \"prog_attach_cgroup\"))\n+\t\tgoto cleanup;\n+\n+\ttrigger_prog_read_icmp_errqueue(skel, ICMP_HOST_UNREACH, AF_INET,\n+\t\t\t\t\t\"127.0.0.1\");\n+\n+\t/*\n+\t * Because there's recursion involved, the first call will return at\n+\t * index 1 since it will return the second, and the second call will\n+\t * return at index 0 since it will return the first.\n+\t */\n+\tASSERT_EQ(skel-\u003ebss-\u003erec_count, 2, \"rec_count\");\n+\tASSERT_EQ(skel-\u003edata-\u003erec_kfunc_rets[0], -EBUSY, \"kfunc_rets[0]\");\n+\tASSERT_EQ(skel-\u003edata-\u003erec_kfunc_rets[1], 0, \"kfunc_rets[1]\");\n+\n+cleanup:\n+\ticmp_send__destroy(skel);\n+\tif (cgroup_fd \u003e= 0)\n+\t\tclose(cgroup_fd);\n+\tcleanup_cgroup_environment();\n+}\ndiff --git a/tools/testing/selftests/bpf/progs/icmp_send.c b/tools/testing/selftests/bpf/progs/icmp_send.c\nnew file mode 100644\nindex 00000000000000..c642ccdf9fd544\n--- /dev/null\n+++ b/tools/testing/selftests/bpf/progs/icmp_send.c\n@@ -0,0 +1,123 @@\n+// SPDX-License-Identifier: GPL-2.0\n+#include \"vmlinux.h\"\n+#include \u003cbpf/bpf_helpers.h\u003e\n+#include \u003cbpf/bpf_endian.h\u003e\n+\n+/* 127.0.0.1 in host byte order */\n+#define SERVER_IP 0x7F000001\n+/* ::1 in host byte order (last 32-bit word) */\n+#define SERVER_IP6_LO 0x00000001\n+\n+__u16 server_port = 0;\n+int unreach_type = 0;\n+int unreach_code = 0;\n+int kfunc_ret = -1;\n+int target_pid = -1;\n+\n+unsigned int rec_count = 0;\n+int rec_kfunc_rets[] = { -1, -1 };\n+\n+SEC(\"cgroup_skb/egress\")\n+int egress(struct __sk_buff *skb)\n+{\n+\tvoid *data = (void *)(long)skb-\u003edata;\n+\tvoid *data_end = (void *)(long)skb-\u003edata_end;\n+\tstruct iphdr *iph;\n+\tstruct ipv6hdr *ip6h;\n+\tstruct tcphdr *tcph;\n+\t__u8 version;\n+\n+\tif (data + 1 \u003e data_end)\n+\t\treturn SK_PASS;\n+\n+\tversion = (*((__u8 *)data)) \u003e\u003e 4;\n+\n+\tif (version == 4) {\n+\t\tiph = data;\n+\t\tif ((void *)(iph + 1) \u003e data_end ||\n+\t\t iph-\u003eprotocol != IPPROTO_TCP ||\n+\t\t iph-\u003edaddr != bpf_htonl(SERVER_IP))\n+\t\t\treturn SK_PASS;\n+\n+\t\ttcph = (void *)iph + iph-\u003eihl * 4;\n+\t\tif ((void *)(tcph + 1) \u003e data_end ||\n+\t\t tcph-\u003edest != bpf_htons(server_port))\n+\t\t\treturn SK_PASS;\n+\n+\t} else if (version == 6) {\n+\t\tip6h = data;\n+\t\tif ((void *)(ip6h + 1) \u003e data_end ||\n+\t\t ip6h-\u003enexthdr != IPPROTO_TCP)\n+\t\t\treturn SK_PASS;\n+\n+\t\tif (ip6h-\u003edaddr.in6_u.u6_addr32[0] != 0 ||\n+\t\t ip6h-\u003edaddr.in6_u.u6_addr32[1] != 0 ||\n+\t\t ip6h-\u003edaddr.in6_u.u6_addr32[2] != 0 ||\n+\t\t ip6h-\u003edaddr.in6_u.u6_addr32[3] != bpf_htonl(SERVER_IP6_LO))\n+\t\t\treturn SK_PASS;\n+\n+\t\ttcph = (void *)(ip6h + 1);\n+\t\tif ((void *)(tcph + 1) \u003e data_end ||\n+\t\t tcph-\u003edest != bpf_htons(server_port))\n+\t\t\treturn SK_PASS;\n+\t} else {\n+\t\treturn SK_PASS;\n+\t}\n+\n+\tkfunc_ret = bpf_icmp_send(skb, unreach_type, unreach_code);\n+\n+\treturn SK_DROP;\n+}\n+\n+SEC(\"cgroup_skb/egress\")\n+int recursion(struct __sk_buff *skb)\n+{\n+\tvoid *data = (void *)(long)skb-\u003edata;\n+\tvoid *data_end = (void *)(long)skb-\u003edata_end;\n+\tstruct icmphdr *icmph;\n+\tstruct tcphdr *tcph;\n+\tstruct iphdr *iph;\n+\tint ret;\n+\n+\tif ((bpf_get_current_pid_tgid() \u003e\u003e 32) != target_pid)\n+\t\treturn SK_PASS;\n+\n+\tiph = data;\n+\tif ((void *)(iph + 1) \u003e data_end || iph-\u003eversion != 4)\n+\t\treturn SK_PASS;\n+\n+\tif (iph-\u003edaddr != bpf_htonl(SERVER_IP))\n+\t\treturn SK_PASS;\n+\n+\tif (iph-\u003eprotocol == IPPROTO_TCP) {\n+\t\ttcph = (void *)iph + iph-\u003eihl * 4;\n+\t\tif ((void *)(tcph + 1) \u003e data_end ||\n+\t\t tcph-\u003edest != bpf_htons(server_port))\n+\t\t\treturn SK_PASS;\n+\t} else if (iph-\u003eprotocol == IPPROTO_ICMP) {\n+\t\ticmph = (void *)iph + iph-\u003eihl * 4;\n+\t\tif ((void *)(icmph + 1) \u003e data_end ||\n+\t\t icmph-\u003etype != unreach_type || icmph-\u003ecode != unreach_code)\n+\t\t\treturn SK_PASS;\n+\t} else {\n+\t\treturn SK_PASS;\n+\t}\n+\n+\t/*\n+\t * This call will provoke a recursion: the ICMP packet generated by the\n+\t * kfunc will re-trigger this program since we are in the root cgroup in\n+\t * which the kernel ICMP socket belongs. However when re-entering the\n+\t * kfunc, it should return EBUSY.\n+\t */\n+\tret = bpf_icmp_send(skb, unreach_type, unreach_code);\n+\trec_kfunc_rets[rec_count \u0026 1] = ret;\n+\t__sync_fetch_and_add(\u0026rec_count, 1);\n+\n+\t/* Let the first ICMP error message pass */\n+\tif (iph-\u003eprotocol == IPPROTO_ICMP)\n+\t\treturn SK_PASS;\n+\n+\treturn SK_DROP;\n+}\n+\n+char LICENSE[] SEC(\"license\") = \"Dual BSD/GPL\";\n"
} |
| 2/1 | 2026/07/10 11:25 | agent | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"CGROUP_BPF",
"INET",
"IPV6"
],
"FocusSymbols": [
"bpf_icmp_send"
],
"Reasoning": "The patch introduces a new BPF kfunc `bpf_icmp_send` that allows BPF programs (specifically CGROUP_SKB programs) to send ICMP control messages. This is a functional change that adds new reachable kernel logic, making it a good candidate for fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit d74f6b888c1a6e967121d981dc3b130f978a43b3
Author: syz-cluster <triage@syzkaller.com>
Date: Fri Jul 10 11:25:44 2026 +0000
syz-cluster: applied patch under review
diff --git a/net/core/filter.c b/net/core/filter.c
index 4f5cbcac3e7819..e4697036c67b53 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -84,6 +84,9 @@
#include <linux/un.h>
#include <net/xdp_sock_drv.h>
#include <net/inet_dscp.h>
+#include <linux/icmpv6.h>
+#include <net/icmp.h>
+#include <net/ip6_route.h>
#include "dev.h"
@@ -12548,6 +12551,83 @@ __bpf_kfunc int bpf_xdp_pull_data(struct xdp_md *x, u32 len)
return 0;
}
+/**
+ * bpf_icmp_send - Send an ICMP control message
+ * @skb_ctx: Packet that triggered the control message
+ * @type: ICMP type (only ICMP_DEST_UNREACH/ICMPV6_DEST_UNREACH supported)
+ * @code: ICMP code (0-15 except ICMP_FRAG_NEEDED for IPv4, 0-6 for IPv6)
+ *
+ * Sends an ICMP control message in response to the packet. The original packet
+ * is cloned before sending the ICMP message, so the BPF program can still let
+ * the packet pass if desired.
+ *
+ * Currently only ICMP_DEST_UNREACH (IPv4) and ICMPV6_DEST_UNREACH (IPv6) are
+ * supported.
+ *
+ * Return: 0 on success (send attempt), negative error code on failure:
+ * -EBUSY: Recursion detected
+ * -EPROTONOSUPPORT: Non-IP protocol
+ * -EOPNOTSUPP: Unsupported ICMP type
+ * -EINVAL: Invalid code parameter
+ * -ENETUNREACH: No usable route/dst for the ICMP reply
+ * -ENOMEM: Memory allocation failed
+ */
+__bpf_kfunc int bpf_icmp_send(struct __sk_buff *skb_ctx, int type, int code)
+{
+ struct sk_buff *skb = (struct sk_buff *)skb_ctx;
+ struct sk_buff *nskb;
+ struct sock *sk;
+
+ sk = skb_to_full_sk(skb);
+ if (sk && sk->sk_kern_sock &&
+ (sk->sk_protocol == IPPROTO_ICMP || sk->sk_protocol == IPPROTO_ICMPV6))
+ return -EBUSY;
+
+ if (!skb_valid_dst(skb))
+ return -ENETUNREACH;
+
+ switch (skb->protocol) {
+#if IS_ENABLED(CONFIG_INET)
+ case htons(ETH_P_IP): {
+ if (type != ICMP_DEST_UNREACH)
+ return -EOPNOTSUPP;
+ if (code < 0 || code > NR_ICMP_UNREACH ||
+ code == ICMP_FRAG_NEEDED) /* needs a valid next-hop MTU */
+ return -EINVAL;
+
+ nskb = skb_clone(skb, GFP_ATOMIC);
+ if (!nskb)
+ return -ENOMEM;
+
+ memset(IPCB(nskb), 0, sizeof(*IPCB(nskb)));
+ icmp_send(nskb, type, code, 0);
+ consume_skb(nskb);
+ break;
+ }
+#endif
+#if IS_ENABLED(CONFIG_IPV6)
+ case htons(ETH_P_IPV6):
+ if (type != ICMPV6_DEST_UNREACH)
+ return -EOPNOTSUPP;
+ if (code < 0 || code > ICMPV6_REJECT_ROUTE)
+ return -EINVAL;
+
+ nskb = skb_clone(skb, GFP_ATOMIC);
+ if (!nskb)
+ return -ENOMEM;
+
+ memset(IP6CB(nskb), 0, sizeof(*IP6CB(nskb)));
+ icmpv6_send(nskb, type, code, 0);
+ consume_skb(nskb);
+ break;
+#endif
+ default:
+ return -EPROTONOSUPPORT;
+ }
+
+ return 0;
+}
+
__bpf_kfunc_end_defs();
int bpf_dynptr_from_skb_rdonly(struct __sk_buff *skb, u64 flags,
@@ -12590,6 +12670,10 @@ BTF_KFUNCS_START(bpf_kfunc_check_set_sock_ops)
BTF_ID_FLAGS(func, bpf_sock_ops_enable_tx_tstamp)
BTF_KFUNCS_END(bpf_kfunc_check_set_sock_ops)
+BTF_KFUNCS_START(bpf_kfunc_check_set_icmp_send)
+BTF_ID_FLAGS(func, bpf_icmp_send)
+BTF_KFUNCS_END(bpf_kfunc_check_set_icmp_send)
+
static const struct btf_kfunc_id_set bpf_kfunc_set_skb = {
.owner = THIS_MODULE,
.set = &bpf_kfunc_check_set_skb,
@@ -12620,6 +12704,11 @@ static const struct btf_kfunc_id_set bpf_kfunc_set_sock_ops = {
.set = &bpf_kfunc_check_set_sock_ops,
};
+static const struct btf_kfunc_id_set bpf_kfunc_set_icmp_send = {
+ .owner = THIS_MODULE,
+ .set = &bpf_kfunc_check_set_icmp_send,
+};
+
static int __init bpf_kfunc_init(void)
{
int ret;
@@ -12641,6 +12730,7 @@ static int __init bpf_kfunc_init(void)
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
&bpf_kfunc_set_sock_addr);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_kfunc_set_tcp_reqsk);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SKB, &bpf_kfunc_set_icmp_send);
return ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SOCK_OPS, &bpf_kfunc_set_sock_ops);
}
late_initcall(bpf_kfunc_init);
diff --git a/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
new file mode 100644
index 00000000000000..9318d4bc7ce8be
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/icmp_send_kfunc.c
@@ -0,0 +1,292 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include <network_helpers.h>
+#include <cgroup_helpers.h>
+#include <linux/errqueue.h>
+#include <poll.h>
+#include <unistd.h>
+#include "icmp_send.skel.h"
+
+#define TIMEOUT_MS 1000
+
+#define ICMP_DEST_UNREACH 3
+#define ICMPV6_DEST_UNREACH 1
+
+#define ICMP_HOST_UNREACH 1
+#define ICMP_FRAG_NEEDED 4
+#define NR_ICMP_UNREACH 15
+#define ICMPV6_REJECT_ROUTE 6
+
+#define KFUNC_RET_UNSET -1
+
+static int connect_to_fd_nonblock(int server_fd)
+{
+ struct sockaddr_storage addr;
+ socklen_t len = sizeof(addr);
+ int fd, err, on = 1;
+
+ if (getsockname(server_fd, (struct sockaddr *)&addr, &len))
+ return -1;
+
+ fd = socket(addr.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
+ if (fd < 0)
+ return -1;
+
+ if (addr.ss_family == AF_INET6 &&
+ setsockopt(fd, IPPROTO_IPV6, IPV6_RECVERR, &on, sizeof(on)) < 0) {
+ close(fd);
+ return -1;
+ }
+
+ err = connect(fd, (struct sockaddr *)&addr, len);
+ if (err < 0 && errno != EINPROGRESS) {
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static void read_icmp_errqueue(int sockfd, int expected_code, int af)
+{
+ int expected_ee_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
+ ICMPV6_DEST_UNREACH;
+ int expected_origin = (af == AF_INET) ? SO_EE_ORIGIN_ICMP :
+ SO_EE_ORIGIN_ICMP6;
+ int expected_level = (af == AF_INET) ? IPPROTO_IP : IPPROTO_IPV6;
+ int expected_type = (af == AF_INET) ? IP_RECVERR : IPV6_RECVERR;
+ struct sock_extended_err *sock_err;
+ char ctrl_buf[512];
+ struct msghdr msg = {
+ .msg_control = ctrl_buf,
+ .msg_controllen = sizeof(ctrl_buf),
+ };
+ struct pollfd pfd = {
+ .fd = sockfd,
+ .events = POLLERR,
+ };
+ struct cmsghdr *cm;
+ ssize_t n;
+
+ if (!ASSERT_GE(poll(&pfd, 1, TIMEOUT_MS), 1, "poll_errqueue"))
+ return;
+
+ n = recvmsg(sockfd, &msg, MSG_ERRQUEUE);
+ if (!ASSERT_GE(n, 0, "recvmsg_errqueue"))
+ return;
+
+ cm = CMSG_FIRSTHDR(&msg);
+ if (!ASSERT_NEQ(cm, NULL, "cm_firsthdr_null"))
+ return;
+
+ for (; cm; cm = CMSG_NXTHDR(&msg, cm)) {
+ if (cm->cmsg_level != expected_level ||
+ cm->cmsg_type != expected_type)
+ continue;
+
+ sock_err = (struct sock_extended_err *)CMSG_DATA(cm);
+
+ if (!ASSERT_EQ(sock_err->ee_origin, expected_origin,
+ "sock_err_origin"))
+ return;
+ if (!ASSERT_EQ(sock_err->ee_type, expected_ee_type,
+ "sock_err_type_dest_unreach"))
+ return;
+ ASSERT_EQ(sock_err->ee_code, expected_code, "sock_err_code");
+ return;
+ }
+
+ ASSERT_FAIL("no IP_RECVERR/IPV6_RECVERR control message found");
+}
+
+static bool valid_unreach_code(int code, int af)
+{
+ if (code < 0)
+ return false;
+
+ if (af == AF_INET)
+ return code <= NR_ICMP_UNREACH && code != ICMP_FRAG_NEEDED;
+
+ return code <= ICMPV6_REJECT_ROUTE;
+}
+
+static void trigger_prog_read_icmp_errqueue(struct icmp_send *skel, int code,
+ int af, const char *ip)
+{
+ int srv_fd = -1, client_fd = -1;
+ int port;
+
+ srv_fd = start_server(af, SOCK_STREAM, ip, 0, TIMEOUT_MS);
+ if (!ASSERT_OK_FD(srv_fd, "start_server"))
+ return;
+
+ port = get_socket_local_port(srv_fd);
+ if (!ASSERT_GE(port, 0, "get_socket_local_port")) {
+ close(srv_fd);
+ return;
+ }
+
+ skel->bss->server_port = ntohs(port);
+ skel->bss->unreach_type = (af == AF_INET) ? ICMP_DEST_UNREACH :
+ ICMPV6_DEST_UNREACH;
+ skel->bss->unreach_code = code;
+ skel->data->kfunc_ret = KFUNC_RET_UNSET;
+
+ client_fd = connect_to_fd_nonblock(srv_fd);
+ if (!ASSERT_OK_FD(client_fd, "client_connect_nonblock")) {
+ close(srv_fd);
+ return;
+ }
+
+ if (valid_unreach_code(code, af))
+ read_icmp_errqueue(client_fd, code, af);
+
+ close(client_fd);
+ close(srv_fd);
+}
+
+static void run_icmp_test(struct icmp_send *skel, int af, const char *ip,
+ int max_code)
+{
+ for (int code = 0; code <= max_code; code++) {
+ if (af == AF_INET && code == ICMP_FRAG_NEEDED)
+ continue;
+
+ trigger_prog_read_icmp_errqueue(skel, code, af, ip);
+ ASSERT_EQ(skel->data->kfunc_ret, 0, "kfunc_ret");
+ }
+
+ /* Test invalid codes */
+ trigger_prog_read_icmp_errqueue(skel, -1, af, ip);
+ ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
+
+ trigger_prog_read_icmp_errqueue(skel, max_code + 1, af, ip);
+ ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
+
+ if (af == AF_INET) {
+ trigger_prog_read_icmp_errqueue(skel, ICMP_FRAG_NEEDED, af, ip);
+ ASSERT_EQ(skel->data->kfunc_ret, -EINVAL, "kfunc_ret");
+ }
+}
+
+static void run_icmp_no_route_test(struct icmp_send *skel, int af)
+{
+ union {
+ struct ipv4_packet v4;
+ struct ipv6_packet v6;
+ } pkt;
+ DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts,
+ .data_in = &pkt,
+ );
+ int err;
+
+ switch (af) {
+ case AF_INET:
+ pkt.v4 = pkt_v4;
+ pkt.v4.iph.version = 4;
+ pkt.v4.iph.daddr = htonl(INADDR_LOOPBACK);
+ pkt.v4.tcp.dest = htons(80);
+ opts.data_size_in = sizeof(pkt.v4);
+ skel->bss->unreach_type = ICMP_DEST_UNREACH;
+ break;
+ case AF_INET6:
+ pkt.v6 = pkt_v6;
+ pkt.v6.iph.version = 6;
+ pkt.v6.iph.daddr = in6addr_loopback;
+ pkt.v6.tcp.dest = htons(80);
+ opts.data_size_in = sizeof(pkt.v6);
+ skel->bss->unreach_type = ICMPV6_DEST_UNREACH;
+ break;
+ default:
+ ASSERT_FAIL("af_not_supported");
+ return;
+ }
+
+ skel->bss->server_port = 80;
+ skel->data->kfunc_ret = KFUNC_RET_UNSET;
+
+ err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.egress), &opts);
+ if (!ASSERT_OK(err, "test_run"))
+ return;
+
+ ASSERT_EQ(skel->data->kfunc_ret, -ENETUNREACH, "kfunc_ret_no_route");
+}
+
+void test_icmp_send_unreach_cgroup(void)
+{
+ struct icmp_send *skel;
+ int cgroup_fd = -1;
+
+ skel = icmp_send__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+
+ cgroup_fd = test__join_cgroup("/icmp_send_unreach_cgroup");
+ if (!ASSERT_OK_FD(cgroup_fd, "join_cgroup"))
+ goto cleanup;
+
+ skel->links.egress =
+ bpf_program__attach_cgroup(skel->progs.egress, cgroup_fd);
+ if (!ASSERT_OK_PTR(skel->links.egress, "prog_attach_cgroup"))
+ goto cleanup;
+
+ if (test__start_subtest("ipv4"))
+ run_icmp_test(skel, AF_INET, "127.0.0.1", NR_ICMP_UNREACH);
+
+ if (test__start_subtest("ipv6"))
+ run_icmp_test(skel, AF_INET6, "::1", ICMPV6_REJECT_ROUTE);
+
+ if (test__start_subtest("no_route_ipv4"))
+ run_icmp_no_route_test(skel, AF_INET);
+
+ if (test__start_subtest("no_route_ipv6"))
+ run_icmp_no_route_test(skel, AF_INET6);
+
+cleanup:
+ icmp_send__destroy(skel);
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+}
+
+void test_icmp_send_unreach_recursion(void)
+{
+ struct icmp_send *skel;
+ int cgroup_fd = -1;
+ int err;
+
+ err = setup_cgroup_environment();
+ if (!ASSERT_OK(err, "setup_cgroup_environment"))
+ return;
+
+ skel = icmp_send__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel_open"))
+ goto cleanup;
+
+ cgroup_fd = get_root_cgroup();
+ if (!ASSERT_OK_FD(cgroup_fd, "get_root_cgroup"))
+ goto cleanup;
+
+ skel->data->target_pid = getpid();
+ skel->links.recursion =
+ bpf_program__attach_cgroup(skel->progs.recursion, cgroup_fd);
+ if (!ASSERT_OK_PTR(skel->links.recursion, "prog_attach_cgroup"))
+ goto cleanup;
+
+ trigger_prog_read_icmp_errqueue(skel, ICMP_HOST_UNREACH, AF_INET,
+ "127.0.0.1");
+
+ /*
+ * Because there's recursion involved, the first call will return at
+ * index 1 since it will return the second, and the second call will
+ * return at index 0 since it will return the first.
+ */
+ ASSERT_EQ(skel->bss->rec_count, 2, "rec_count");
+ ASSERT_EQ(skel->data->rec_kfunc_rets[0], -EBUSY, "kfunc_rets[0]");
+ ASSERT_EQ(skel->data->rec_kfunc_rets[1], 0, "kfunc_rets[1]");
+
+cleanup:
+ icmp_send__destroy(skel);
+ if (cgroup_fd >= 0)
+ close(cgroup_fd);
+ cleanup_cgroup_environment();
+}
diff --git a/tools/testing/selftests/bpf/progs/icmp_send.c b/tools/testing/selftests/bpf/progs/icmp_send.c
new file mode 100644
index 00000000000000..c642ccdf9fd544
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/icmp_send.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_endian.h>
+
+/* 127.0.0.1 in host byte order */
+#define SERVER_IP 0x7F000001
+/* ::1 in host byte order (last 32-bit word) */
+#define SERVER_IP6_LO 0x00000001
+
+__u16 server_port = 0;
+int unreach_type = 0;
+int unreach_code = 0;
+int kfunc_ret = -1;
+int target_pid = -1;
+
+unsigned int rec_count = 0;
+int rec_kfunc_rets[] = { -1, -1 };
+
+SEC("cgroup_skb/egress")
+int egress(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct iphdr *iph;
+ struct ipv6hdr *ip6h;
+ struct tcphdr *tcph;
+ __u8 version;
+
+ if (data + 1 > data_end)
+ return SK_PASS;
+
+ version = (*((__u8 *)data)) >> 4;
+
+ if (version == 4) {
+ iph = data;
+ if ((void *)(iph + 1) > data_end ||
+ iph->protocol != IPPROTO_TCP ||
+ iph->daddr != bpf_htonl(SERVER_IP))
+ return SK_PASS;
+
+ tcph = (void *)iph + iph->ihl * 4;
+ if ((void *)(tcph + 1) > data_end ||
+ tcph->dest != bpf_htons(server_port))
+ return SK_PASS;
+
+ } else if (version == 6) {
+ ip6h = data;
+ if ((void *)(ip6h + 1) > data_end ||
+ ip6h->nexthdr != IPPROTO_TCP)
+ return SK_PASS;
+
+ if (ip6h->daddr.in6_u.u6_addr32[0] != 0 ||
+ ip6h->daddr.in6_u.u6_addr32[1] != 0 ||
+ ip6h->daddr.in6_u.u6_addr32[2] != 0 ||
+ ip6h->daddr.in6_u.u6_addr32[3] != bpf_htonl(SERVER_IP6_LO))
+ return SK_PASS;
+
+ tcph = (void *)(ip6h + 1);
+ if ((void *)(tcph + 1) > data_end ||
+ tcph->dest != bpf_htons(server_port))
+ return SK_PASS;
+ } else {
+ return SK_PASS;
+ }
+
+ kfunc_ret = bpf_icmp_send(skb, unreach_type, unreach_code);
+
+ return SK_DROP;
+}
+
+SEC("cgroup_skb/egress")
+int recursion(struct __sk_buff *skb)
+{
+ void *data = (void *)(long)skb->data;
+ void *data_end = (void *)(long)skb->data_end;
+ struct icmphdr *icmph;
+ struct tcphdr *tcph;
+ struct iphdr *iph;
+ int ret;
+
+ if ((bpf_get_current_pid_tgid() >> 32) != target_pid)
+ return SK_PASS;
+
+ iph = data;
+ if ((void *)(iph + 1) > data_end || iph->version != 4)
+ return SK_PASS;
+
+ if (iph->daddr != bpf_htonl(SERVER_IP))
+ return SK_PASS;
+
+ if (iph->protocol == IPPROTO_TCP) {
+ tcph = (void *)iph + iph->ihl * 4;
+ if ((void *)(tcph + 1) > data_end ||
+ tcph->dest != bpf_htons(server_port))
+ return SK_PASS;
+ } else if (iph->protocol == IPPROTO_ICMP) {
+ icmph = (void *)iph + iph->ihl * 4;
+ if ((void *)(icmph + 1) > data_end ||
+ icmph->type != unreach_type || icmph->code != unreach_code)
+ return SK_PASS;
+ } else {
+ return SK_PASS;
+ }
+
+ /*
+ * This call will provoke a recursion: the ICMP packet generated by the
+ * kfunc will re-trigger this program since we are in the root cgroup in
+ * which the kernel ICMP socket belongs. However when re-entering the
+ * kfunc, it should return EBUSY.
+ */
+ ret = bpf_icmp_send(skb, unreach_type, unreach_code);
+ rec_kfunc_rets[rec_count & 1] = ret;
+ __sync_fetch_and_add(&rec_count, 1);
+
+ /* Let the first ICMP error message pass */
+ if (iph->protocol == IPPROTO_ICMP)
+ return SK_PASS;
+
+ return SK_DROP;
+}
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
|
| 3/2 | 2026/07/10 11:25 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 8569 output: 20 thoughts: 321 Thoughts: **Analyzing the ICMP Send BPF Kfunc Patch** Okay, so I'm looking at this patch that introduces `bpf_icmp_send`, a new BPF kfunc. It's modifying `net/core/filter.c` and includes some selftests. Seems straightforward enough – the kfunc takes an `skb_ctx`, `type`, and `code`, then shoots out an ICMP control message. It appears to validate the `skb`, clone it, and then hands it off to either `icmp_send` or `icmpv6_send`. Functionally, this looks good and, given the context, should be perfectly reachable via BPF. Now, let's nail down those configuration dependencies. I'm seeing this code is nestled in `net/core/filter.c`, so it's utilizing standard `CONFIG_INET` and `CONFIG_IPV6`, which is what I would expect. The kfunc gets registered for `BPF_PROG_TYPE_CGROUP_SKB`, so that means we're going to need `CGROUP_BPF` and `BPF_SYSCALL` enabled, as well. Let me double-check `net/core/filter.c` one more time, just to be absolutely certain I'm not missing anything. I see that `bpf_icmp_send` itself is unconditionally compiled, but its internal logic does have `#if IS_ENABLED(CONFIG_INET)` and `#if IS_ENABLED(CONFIG_IPV6)` conditional blocks. Therefore, `INET` and `IPV6` are definitely critical. I'm zeroing in on confirming the exact function name, `bpf_icmp_send`, so that I can look at the code more closely. Let's do a quick grep search just to be sure. |
| 4/2 | 2026/07/10 11:26 | tool | grepper |
0mArgs:{
"Expression": "bpf_icmp_send"
}Results: {
"Output": "net/core/filter.c=12493=__bpf_kfunc int bpf_xdp_pull_data(struct xdp_md *x, u32 len)\n--\nnet/core/filter.c-12554-/**\nnet/core/filter.c:12555: * bpf_icmp_send - Send an ICMP control message\nnet/core/filter.c-12556- * @skb_ctx: Packet that triggered the control message\n--\nnet/core/filter.c-12574- */\nnet/core/filter.c:12575:__bpf_kfunc int bpf_icmp_send(struct __sk_buff *skb_ctx, int type, int code)\nnet/core/filter.c-12576-{\n--\nnet/core/filter.c=12673=BTF_KFUNCS_START(bpf_kfunc_check_set_icmp_send)\nnet/core/filter.c:12674:BTF_ID_FLAGS(func, bpf_icmp_send)\nnet/core/filter.c-12675-BTF_KFUNCS_END(bpf_kfunc_check_set_icmp_send)\n--\ntools/testing/selftests/bpf/progs/icmp_send.c=21=int egress(struct __sk_buff *skb)\n--\ntools/testing/selftests/bpf/progs/icmp_send.c-66-\ntools/testing/selftests/bpf/progs/icmp_send.c:67:\tkfunc_ret = bpf_icmp_send(skb, unreach_type, unreach_code);\ntools/testing/selftests/bpf/progs/icmp_send.c-68-\n--\ntools/testing/selftests/bpf/progs/icmp_send.c=73=int recursion(struct __sk_buff *skb)\n--\ntools/testing/selftests/bpf/progs/icmp_send.c-111-\t */\ntools/testing/selftests/bpf/progs/icmp_send.c:112:\tret = bpf_icmp_send(skb, unreach_type, unreach_code);\ntools/testing/selftests/bpf/progs/icmp_send.c-113-\trec_kfunc_rets[rec_count \u0026 1] = ret;\n"
} |
| 5/2 | 2026/07/10 11:26 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9916 output: 125 thoughts: 283 Thoughts: **Analyzing the bpf_icmp_send Kfunc** Okay, so `bpf_icmp_send` is definitely added and is accessible, as the code confirms. Looking at the configurations, `BPF_SYSCALL`, `CGROUP_BPF`, `INET`, and `IPV6` are all present. From a functional perspective, the addition of the `bpf_icmp_send` kfunc is significant, giving BPF programs the ability to send ICMP control messages. This presents a new attack surface and is absolutely something that needs thorough fuzzing. I quickly checked for other relevant configurations, but `CONFIG_BPF_SYSCALL`, `CONFIG_CGROUP_BPF`, `CONFIG_INET`, and `CONFIG_IPV6` all seem standard, so I'll just list `BPF_SYSCALL`, `CGROUP_BPF`, `INET`, `IPV6` as the relevant configs. Wait a minute... the registration is specifically for `BPF_PROG_TYPE_CGROUP_SKB`. `CGROUP_BPF` is *definitely* required in this scenario. Alright, I think I've gathered everything I need for now, I'll call set-results. |
| 6/2 | 2026/07/10 11:26 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BPF_SYSCALL",
"CGROUP_BPF",
"INET",
"IPV6"
],
"FocusSymbols": [
"bpf_icmp_send"
],
"Reasoning": "The patch introduces a new BPF kfunc `bpf_icmp_send` that allows BPF programs (specifically CGROUP_SKB programs) to send ICMP control messages. This is a functional change that adds new reachable kernel logic, making it a good candidate for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BPF_SYSCALL",
"CGROUP_BPF",
"INET",
"IPV6"
],
"FocusSymbols": [
"bpf_icmp_send"
],
"Reasoning": "The patch introduces a new BPF kfunc `bpf_icmp_send` that allows BPF programs (specifically CGROUP_SKB programs) to send ICMP control messages. This is a functional change that adds new reachable kernel logic, making it a good candidate for fuzzing.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|