From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-ed1-f43.google.com (mail-ed1-f43.google.com [209.85.208.43]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 23E92202F71 for ; Thu, 19 Mar 2026 12:49:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.208.43 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773924551; cv=none; b=QpXrXqTgcq6Z0zdbg9c6QT2bjFJqeb4JxF+GkTg2EHf1oVCXrS3+4nQIYqbvYDqKqUfWSwSHLH5WTBUS+LOwI6Ykp6IcQkLr6mfUQXVrfP5PvVrwjIhkfYdAzY89G2CwW1GQ7wV0Cz/Kv9W0MGtY9shxyMBfPv/iNSjib0oVYq8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773924551; c=relaxed/simple; bh=ynQBMSRXQm7aPAjLKgXfqgaJLoAvfb9D/ictZQSt4f4=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=ZgUu9AHowlU8YjFJRxyp66BH/ZUKGSK0lLqYR/XgAKtGNKgEPtOuuEa8+QUPihYwewEc7x7nWMNND2nZkZigjW3stCHfePQetQpDN3YnyPX5RYMMm3ibzTtw9YV0SbGQM//E30qTPp8CX6LoS8CTFN10gZIjR7ma2PxEnXjSmGg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=bkbKAOIO; arc=none smtp.client-ip=209.85.208.43 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="bkbKAOIO" Received: by mail-ed1-f43.google.com with SMTP id 4fb4d7f45d1cf-66873d8cc9dso537282a12.1 for ; Thu, 19 Mar 2026 05:49:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773924548; x=1774529348; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=Hp70Q1L+cat8b+0lFNo0m7Hp9q8eqDxwQih/uHUWYr4=; b=bkbKAOIOjlqs8eOFbmtw4InFT3IIuBlo0pZsej0n9sVrvtn+PITZg+JIHivP1WbZl8 JfpLk1/lJ4X1gi9UADqHPTZdJFjwRRO7YApPh5hH6lCdXhnSKu+JULcoUzWANDcLZr1F V42m+JGmGgrFXwUXY0SLSRAX1ijge8gkX6P5qFpRowWRvPKu8+wCVVmFz8fmUkQ0vVVl kH7oQArzPCxtx88nM3GQN+0oebIuH2412CABkLXs+PXYrXmJDWpBLl5x2sO7Vi6iFYqd XjyFZvMq9/m55peubkELekQf0kmmVjh1iIzHKHwyTCPN4TFe4bpas4MSooQUCrxnfP0V 6r5w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773924548; x=1774529348; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=Hp70Q1L+cat8b+0lFNo0m7Hp9q8eqDxwQih/uHUWYr4=; b=W+Nlj0iAcMcAmpt+JBsk9FHWRKNCKSUFRQmVKmzKfK7f1sq0rXW1Faf2EsdxmbOjV1 lMsCoIP7aTf07TgtFq6GU2cN3fxSchTW+nLSQ6jEPGo0lje6aeOEoZ5+RQMayf4ZKVUv +9336GdpoAK3EWtdm3r/+Qo3DQ8HzSCjp4/guZuMJ1zekp9FkmGKcyy5xQFXxxdUmulM Jti6B1xkGewbf5qGBlC7SciACH3xalun+qs1UVI0K5eQPe3czmj7EAdrBXCSUO4x+zHZ d7OJEF2auQlAiVcoSp8SOVkS+0Z7pZs6Fq1Gzo2HJx4SirhnKROWTmkJfuFmkhtJ27P6 tusw== X-Forwarded-Encrypted: i=1; AJvYcCVJX+Axl3K/Zx2XsBbWPIuXGFWXm0hZ+XnBFeUr1wFGOtIi7BBPfyh+MECCJTmVH70T24E=@vger.kernel.org X-Gm-Message-State: AOJu0Yw4E6vAAYd7G9Mx7CqXK7l01jsJpKOLgr90lN80zwn2s7p4F6XE 7kVFtcgkkLVcghPCBUXf/1Gzsx5gyDuNuvOEBv2CL4wzcEeZOsdb/c8= X-Gm-Gg: ATEYQzx9VR0AOd3FfQedodRV0H3UHs/jJzgkqI4Y07OrIL/4YKrwQtz9WlUwSW+gN70 VcFckYe9+qcr5eNjFo2cNwPpeVvW2PsRLQRafx8Ie033icFNPIy3f2O9mGtKVuaTx9ROs5EVd4b NWwKeXfQrbsSoEffwF4HbK/FKj/TXQldHdPyDY/hcZj1SWG5fectJ4mQbYEVU1YvWHspnw2HDM+ UDcSSJGN7genjZqYlb394p4N1Pal2XOOboAFow2AIBVWnqT/B6pROy6nOgcJTMdhbiYIYpeTYyO m2FKgBLW24D609dUX/QwjdNnAXCFd2mqGL0S7GfRjyOQMuZTH7BbdiG7FsTH6BNHPDpZkCU1lKY ppXR7Zats9bD0cE8cVm+zgDNR6rS97xcwMFOcRjCDzgFqvi2SjERlA9T28/CHcWfQV4b1n9tgXQ mF5NMAM1qym/agm1hyKP3YPxpCVJjo+J6MsI9F4v2l338M7iLjlXLqnOKNClu46LPSGkJnGvyjv NDvTkcOw11Afn2gds2wXMPcdA== X-Received: by 2002:a17:907:c905:b0:b98:2493:4770 with SMTP id a640c23a62f3a-b9824934c60mr60103766b.21.1773924548195; Thu, 19 Mar 2026 05:49:08 -0700 (PDT) Received: from codespaces-35910c.m1k4x3cq3poebda54cltipn05g.ax.internal.cloudapp.net ([4.210.177.134]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-b97f1751cfesm432990166b.63.2026.03.19.05.49.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 19 Mar 2026 05:49:07 -0700 (PDT) From: Ibrahim Zein To: ast@kernel.org Cc: daniel@iogearbox.net, martin.lau@linux.dev, andrii@kernel.org, bpf@vger.kernel.org, jolsa@kernel.org, kpsingh@kernel.org, linux-kselftest@vger.kernel.org, haoluo@google.com, revest@chromium.org, john.fastabend@gmail.com, shuah@kernel.org, sdf@fomichev.me, yonghong.song@linux.dev, song@kernel.org, eddyz87@gmail.com, Ibrahim Zein Subject: [PATCH bpf-next v3] bpf: fix out-of-bounds write in bpf_bprintf_prepare with %pI4/%pI6 Date: Thu, 19 Mar 2026 12:49:00 +0000 Message-ID: <20260319124901.9207-1-zeroxjacks@gmail.com> X-Mailer: git-send-email 2.52.0 Precedence: bulk X-Mailing-List: bpf@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit In bpf_bprintf_prepare(), the bounds check for %pI4 and %pI6 format specifiers uses sizeof_cur_ip (4 for IPv4, 16 for IPv6), which is the raw byte count of the IP address. However, snprintf() returns the length of the formatted string, not the raw bytes. For IPv4 this can be up to 15 characters (255.255.255.255) and for IPv6 up to 39. tmp_buf is then advanced by (err + 1) using the full string length, which can push tmp_buf past tmp_buf_end. The next iteration's bounds check underflows due to unsigned arithmetic and passes, allowing a write past the end of the per-CPU bin_args buffer. Fix this by checking against the maximum formatted string size: 16 bytes for IPv4 and 40 bytes for IPv6. Fixes: 48cac3f4a96d ("bpf: Implement formatted output helpers with bstr_printf") Signed-off-by: Ibrahim Zein --- kernel/bpf/helpers.c | 2 +- .../bpf/prog_tests/test_snprintf_ip.c | 54 +++++++++++++ .../selftests/bpf/progs/test_snprintf_ip.c | 78 +++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 tools/testing/selftests/bpf/prog_tests/test_snprintf_ip.c create mode 100644 tools/testing/selftests/bpf/progs/test_snprintf_ip.c diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index cb6d242bd..dcaa822ba 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -930,7 +930,7 @@ int bpf_bprintf_prepare(const char *fmt, u32 fmt_size, const u64 *raw_args, goto nocopy_fmt; sizeof_cur_ip = (fmt[i] == '4') ? 4 : 16; - if (tmp_buf_end - tmp_buf < sizeof_cur_ip) { + if (tmp_buf_end - tmp_buf < (size_t)((fmt[i] == '4') ? 16 : 40)) { err = -ENOSPC; goto out; } diff --git a/tools/testing/selftests/bpf/prog_tests/test_snprintf_ip.c b/tools/testing/selftests/bpf/prog_tests/test_snprintf_ip.c new file mode 100644 index 000000000..5b000d6d1 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/test_snprintf_ip.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Ibrahim Zein */ +#include +#include "test_snprintf_ip.skel.h" + +#define EXP_IP4_OUT "192.168.1.1" +#define EXP_IP4_RET sizeof(EXP_IP4_OUT) + +#define EXP_WORST_IP4_OUT "255.255.255.255" +#define EXP_WORST_IP4_RET sizeof(EXP_WORST_IP4_OUT) + +#define EXP_IP6_OUT "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" +#define EXP_IP6_RET sizeof(EXP_IP6_OUT) + +void test_snprintf_ip(void) +{ + struct test_snprintf_ip *skel; + + skel = test_snprintf_ip__open_and_load(); + if (!ASSERT_OK_PTR(skel, "skel_open")) + return; + + skel->bss->pid = getpid(); + + if (!ASSERT_OK(test_snprintf_ip__attach(skel), "skel_attach")) + goto cleanup; + + /* trigger tracepoint */ + usleep(1); + + /* Test 1: normal IPv4 */ + ASSERT_STREQ(skel->bss->ip4_out, EXP_IP4_OUT, "ip4_out"); + ASSERT_EQ(skel->bss->ip4_ret, EXP_IP4_RET, "ip4_ret"); + + /* Test 2: normal IPv6 */ + ASSERT_STREQ(skel->bss->ip6_out, EXP_IP6_OUT, "ip6_out"); + ASSERT_EQ(skel->bss->ip6_ret, EXP_IP6_RET, "ip6_ret"); + + /* Test 3: worst-case IPv4 "255.255.255.255" */ + ASSERT_STREQ(skel->bss->worst_ip4_out, EXP_WORST_IP4_OUT, + "worst_ip4_out"); + ASSERT_EQ(skel->bss->worst_ip4_ret, EXP_WORST_IP4_RET, + "worst_ip4_ret"); + + /* + * Test 4: near-overflow scenario. + * Before fix: tmp_buf overflows, kernel may crash or corrupt memory. + * After fix: returns valid length without overflow. + */ + ASSERT_GE(skel->bss->near_overflow_ret, 0, "near_overflow_ret"); + +cleanup: + test_snprintf_ip__destroy(skel); +} diff --git a/tools/testing/selftests/bpf/progs/test_snprintf_ip.c b/tools/testing/selftests/bpf/progs/test_snprintf_ip.c new file mode 100644 index 000000000..a372b35c3 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/test_snprintf_ip.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Ibrahim Zein */ +#include +#include + +/* + * Test that bpf_snprintf() with %pI4/%pI6 does not overflow the + * internal bin_args buffer when the buffer is nearly full. + * + * The bug: sizeof_cur_ip (4 for IPv4) was used for the bounds check, + * but snprintf() returns the full formatted string length (up to 15 + * for "255.255.255.255"), pushing tmp_buf past tmp_buf_end. + */ + +/* Output buffers */ +char ip4_out[64] = {}; +long ip4_ret = 0; + +char ip6_out[128] = {}; +long ip6_ret = 0; + +/* Test %pI4 with worst-case IP (255.255.255.255) */ +char worst_ip4_out[64] = {}; +long worst_ip4_ret = 0; + +/* Return value for the near-overflow test */ +long near_overflow_ret = 0; + +__u32 pid = 0; + +SEC("raw_tp/sys_enter") +int handler(const void *ctx) +{ + /* Worst-case IPv4: "255.255.255.255" = 15 chars */ + const __u8 ip4_worst[] = {255, 255, 255, 255}; + /* Normal IPv4 */ + const __u8 ip4_normal[] = {192, 168, 1, 1}; + /* IPv6 worst-case */ + const __u8 ip6_worst[] = {0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff}; + + if ((int)bpf_get_current_pid_tgid() != pid) + return 0; + + /* Test 1: normal %pI4 usage */ + ip4_ret = BPF_SNPRINTF(ip4_out, sizeof(ip4_out), + "%pI4", &ip4_normal); + + /* Test 2: normal %pI6 usage */ + ip6_ret = BPF_SNPRINTF(ip6_out, sizeof(ip6_out), + "%pI6", &ip6_worst); + + /* Test 3: worst-case %pI4 "255.255.255.255" */ + worst_ip4_ret = BPF_SNPRINTF(worst_ip4_out, + sizeof(worst_ip4_out), + "%pI4", &ip4_worst); + + /* + * Test 4: near-overflow scenario. + * Fill bin_args with scalar args (%d), then use %pI4. + * Before the fix: tmp_buf overflows by 8 bytes. + * After the fix: correctly returns -ENOSPC or fits exactly. + * + * We use fewer args here since BPF_SNPRINTF macro has a limit, + * but this validates the format string parsing path. + */ + /* 11 x %d + 1 x %pI4 = 12 args (BPF_SNPRINTF max) */ + near_overflow_ret = BPF_SNPRINTF(NULL, 0, + "%d %d %d %d %d %d %d %d %d %d %d %pI4", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, + &ip4_worst); + + return 0; +} + +char _license[] SEC("license") = "GPL"; -- 2.52.0