From nobody Sun May 24 18:41:33 2026 Received: from mail-qv1-f52.google.com (mail-qv1-f52.google.com [209.85.219.52]) (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 530131A6839 for ; Fri, 22 May 2026 12:57:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.219.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779454640; cv=none; b=CHW9fibSOtz948cuUUl51Ybr1bHoj2zDbeVpuEF7pUKBmplLqjUqOqK6dc1xtr2buzxe9MjC4C1+JWF2FAkbr++fHoMc9FzusijLDRPY9CsRFvn0jl/gecT+1oICLYx/1DSsmjse+NuVZN+oH+ZMz8e2SXScQmedtzfel5sOzd0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779454640; c=relaxed/simple; bh=b3yQktpC+Sk1axf9uRzfmXChAC0fkKcq145dDjqD6Lw=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=WXDNy2+4lJlIZzljQIcbl3cJS22xcH+DCCXeVbuvSP1OnRIhaBYYrx3C6en37eF/r1iBos5WakJMQqfm2/I6qIx4A6tiDN/6cRM8J4UTH01yjSW5cHiCBhGk5OZu2i8o4hvLTuqxJoWPepzkYokfffUOjROCaXTdUbwjV/JgSOQ= 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=bYrFXBkP; arc=none smtp.client-ip=209.85.219.52 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="bYrFXBkP" Received: by mail-qv1-f52.google.com with SMTP id 6a1803df08f44-8ca12973e15so99397496d6.1 for ; Fri, 22 May 2026 05:57:19 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779454638; x=1780059438; 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=4Y7bHl67RpfSH1i9BIN3c7f7/8VqTkoPZRFRQwzd3Fs=; b=bYrFXBkPHycGMDnWXrt/MQDlhTG0lbmhpY/FquF+mEXRCG3CwniP9oK1MLfXPtlmh+ 7Hu8YMFs+gfkK8h3asJFPVCJKMj8chsMMYR+TWgskc9h80ojcM9ai2MPOSU8rwRXoFwQ CWFrwcwq7HgFa6Oh6zlOju9HyDL+l6u0SdOdLrc4CPSPui8emXKo8gRUTPz3jIJIp88r q2lP9K0gVGdauCQPo8F6ujxztBCbpJnntfwGw6BOB3ByhD47bqwD7af6okXFA61vn2Km 0YtXXWrENly7PC27zdakDXNgmzhf9Cc/PckIdFQLB6u65Q+wbQtAVBuetz8ai7CTPOZi eZTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779454638; x=1780059438; 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=4Y7bHl67RpfSH1i9BIN3c7f7/8VqTkoPZRFRQwzd3Fs=; b=UEaZkSsjIs7KXvuNyKgedZ7wOtbn0Q+sHrhAXHU1+Ttdf068dcBejK/3l/nbkM0Cq+ igpFIkHeKKx+pyY0kVcWdlAe4FadSHVAufYnZqdeOZwOLIBaf/1VX942QJz6PjOp42I6 sP8ry+n6UzqXj/BRwBykwqogkHQVuIToTvf3I27RQQL1rCd0PhfSiDd8HjZT1/sZfMYp 2h4Qb0hYz5q9T5ImKxdghO7Midy7BSoCar8Blo9s7KXDVaPQHgN0Q1r73C+hqsj3jW26 aL6F4WOKwJiYNHkaOxM345mlgv4NiNvmYpQ0JPPl1ahskAT9ALzDTSzt7nbpPbNdQ7+9 BdBQ== X-Forwarded-Encrypted: i=1; AFNElJ92w/8XWjFhNjKjE6oXibQ4ibAhSDbRb3DrO2+TzQ8SHd8cDi8J/skbQ2YEQqWcQRu0U47LjoXlomNDrpo=@vger.kernel.org X-Gm-Message-State: AOJu0YyB7MJeOls2HgWt4qgoe+Q7m+SzQJ3eaDZkHbXeS4NLzAWmdo2A Ij6A9B87XLjalZlWGjGUkoi2aeSlK3De9c17cA7IcZCB6rRBqwNfAPQ6 X-Gm-Gg: Acq92OEI1xVWKlS2iXQ48bLOEXsrQ6wC3ovEjW8uhB9sU3PUM5e6p7yFlnQB93TclCW aHO7ji4qUxAi1rUgdC0mSFjaZIlhhKdLAn17rPJE3G+opk2zJ+gtW0DhLwtJ3MPuvugJnx36Fmr D3y/glgjnFBwHxx/lppyDB7cWfkw7pCo/QwBc8Kz1ZbqvLvE9I+XaMB9yD2ruXpI1/KqcTuHeBe FYjV+wtBe8zb6R14M887JIsLSiWbH8+oEe1ZrtFx6NOYvJLC6xl5v33NKZDSp/LClSDgO432c74 JBJyN9sYXrf49qDuti60ukbzDUMY+7/X95P1zhaanomzdp3p5MziHjP0X0czrMWqpl2679F1hNc 0DDI0Bh+Z5BMuuG9Zdav09ncL9DWJKAw02wNW1+UWiDXDX1ZoZ2GCBTiH8VVXKIzi9UCv+qICQn 2YYmNv2vfeUwdn7ENIfq1R8OlQLCp7cOcDl/INHpGuRBdXh00I5JXLt5TejSwVncwjxHWmvv6mJ l8b5BIx8EW3xC8qEbPEKOVCCGPrPr0= X-Received: by 2002:a05:6214:3018:b0:8ac:a689:34ce with SMTP id 6a1803df08f44-8cc7b6a26e7mr57274876d6.45.1779454638141; Fri, 22 May 2026 05:57:18 -0700 (PDT) Received: from server0 (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-8cc8131acf9sm16948026d6.43.2026.05.22.05.57.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 22 May 2026 05:57:17 -0700 (PDT) From: Michael Bommarito To: Andrii Nakryiko , Eduard Zingerman Cc: Alexei Starovoitov , Daniel Borkmann , Martin KaFai Lau , Kumar Kartikeya Dwivedi , Song Liu , Yonghong Song , Jiri Olsa , bpf@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v3] libbpf: harden parse_vma_segs() path parsing Date: Fri, 22 May 2026 08:56:57 -0400 Message-ID: <20260522125657.328840-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" parse_vma_segs() in tools/lib/bpf/usdt.c parses /proc//maps with two widthless scansets, "%s" into mode[16] and "%[^\n]" into line[PATH_MAX]. Both assume the kernel caps maps records to PATH_MAX; it does not. show_map_vma() emits the path via seq_path() against the seq buffer, which doubles on overflow (m->size <<=3D 1 in fs/seq_file.c), so a VMA whose backing path is a deeply nested directory tree produces a single maps record longer than PATH_MAX. scanf "%s" / "%[^\n]" without a width writes until the field terminator regardless of destination size, so a bpf_program__attach_usdt() consumer attaching against an attacker-controlled PID overflows its own stack inside parse_vma_segs(). Bound both scansets to the declared buffer sizes ("%15s" for mode[16] and "%4095[^\n]" for line[PATH_MAX]) and drain any residue past line[4094] with "%*[^\n]" before the trailing "\n", matching the libbpf-local fscanf style. Without the drain the residue of an over-long record would stay in the stream and break the next "%zx-%zx" parse, so the loop would exit early and any maps records after the over-long entry would be silently skipped. Also stop using sscanf(..., "%s") to peel the /proc//root prefix from lib_path. Build the exact prefix for the requested PID with snprintf(), check it directly, and copy the remainder with libbpf_strlcpy(). That removes a second unbounded stack write and preserves paths containing spaces. Fixes: 74cc6311cec9 ("libbpf: Add USDT notes parsing and resolution logic") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-4-7 Signed-off-by: Michael Bommarito Reviewed-by: Emil Tsalapatis --- v3: - Correct Fixes tag to the initial USDT implementation commit, per BPF CI review after adding second site. v2: - Replace the unbounded /proc//root sscanf() path peeling with snprintf() + prefix check + libbpf_strlcpy(), addressing review feedback on v1 and preserving paths containing spaces. - Keep the v1 maps parser fix using bounded fscanf() scansets and a suppressed scanset drain for over-long records. - Re-ran real parse_vma_segs() ASAN harnesses for the original maps overflow, the proc-root overflow, proc-root paths with spaces, and adjacent successful parses after an over-long maps record. Reproduced with Debian 12 on rootless podman: an unprivileged container process mkdirs 50 nested 200-char directories and mmaps a file at the bottom, producing a 10403-byte /proc//maps line. A harness on the host then calls the real parse_vma_segs() against the container's PID; libbpf is built with -fsanitize=3Daddress and the only local source change is dropping the "static" keyword on parse_vma_segs so the symbol is linkable from the harness. Stock libbpf reports: =3D=3DERROR: AddressSanitizer: stack-buffer-overflow WRITE of size 10349 at thread T0 #0 scanf_common -> #1 __isoc99_fscanf #3 parse_vma_segs tools/lib/bpf/usdt.c:509 Address ... in frame parse_vma_segs at offset 8512, just past line[PATH_MAX]. Patched libbpf parses the same maps cleanly. Follow-up calls return 0 with seg_cnt > 0 for libc.so.6 and for ld-linux-x86-64.so.2 (format drain), which appears in maps after the over-long entry. On normal hardened builds the stack canary aborts the consumer; on builds without stack protector the bytes past line[] are attacker-influenced path bytes. Selftest gate =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D tools/testing/selftests/bpf/test_progs -t usdt under QEMU x86_64 (KVM) on the patched kernel: all 6 subtests pass (usdt/basic, basic_optimized, optimized_attach, multispec, urand_auto_attach, urand_pid_attach) on both stock and patched libbpf, diff-clean. The in-tree selftest does not itself exercise long maps records. tools/lib/bpf/usdt.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tools/lib/bpf/usdt.c b/tools/lib/bpf/usdt.c index e3710933fd52a..2ed792cf11438 100644 --- a/tools/lib/bpf/usdt.c +++ b/tools/lib/bpf/usdt.c @@ -471,7 +471,7 @@ static int parse_vma_segs(int pid, const char *lib_path= , struct elf_seg **segs, char path[PATH_MAX], line[PATH_MAX], mode[16]; size_t seg_start, seg_end, seg_off; struct elf_seg *seg; - int tmp_pid, i, err; + int n, i, err; FILE *f; =20 *seg_cnt =3D 0; @@ -480,8 +480,13 @@ static int parse_vma_segs(int pid, const char *lib_pat= h, struct elf_seg **segs, * /proc//root/. They will be reported as just / in * /proc//maps. */ - if (sscanf(lib_path, "/proc/%d/root%s", &tmp_pid, path) =3D=3D 2 && pid = =3D=3D tmp_pid) + n =3D snprintf(line, sizeof(line), "/proc/%d/root", pid); + if (n < 0 || n >=3D (int)sizeof(line)) + return -ENAMETOOLONG; + if (str_has_pfx(lib_path, line) && lib_path[n] =3D=3D '/') { + libbpf_strlcpy(path, lib_path + n, sizeof(path)); goto proceed; + } =20 if (!realpath(lib_path, path)) { pr_warn("usdt: failed to get absolute path of '%s' (err %s), using path = as is...\n", @@ -504,8 +509,11 @@ static int parse_vma_segs(int pid, const char *lib_pat= h, struct elf_seg **segs, * 7f5c6f5d1000-7f5c6f5d3000 rw-p 001c7000 08:04 21238613 /usr/lib64= /libc-2.17.so * 7f5c6f5d3000-7f5c6f5d8000 rw-p 00000000 00:00 0 * 7f5c6f5d8000-7f5c6f5d9000 r-xp 00000000 103:01 362990598 /data/user= s/andriin/linux/tools/bpf/usdt/libhello_usdt.so + * + * Bound the writes and drain residue: maps lines can exceed + * PATH_MAX when seq_path() uses a larger seq buffer. */ - while (fscanf(f, "%zx-%zx %s %zx %*s %*d%[^\n]\n", + while (fscanf(f, "%zx-%zx %15s %zx %*s %*d%4095[^\n]%*[^\n]\n", &seg_start, &seg_end, mode, &seg_off, line) =3D=3D 5) { void *tmp; =20 --=20 2.53.0