linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying
@ 2020-11-17 20:05 Daniel Xu
  2020-11-17 20:05 ` [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator Daniel Xu
                   ` (2 more replies)
  0 siblings, 3 replies; 8+ messages in thread
From: Daniel Xu @ 2020-11-17 20:05 UTC (permalink / raw)
  To: bpf, linux-kernel, ast, daniel, songliubraving, andrii.nakryiko,
	torvalds
  Cc: Daniel Xu, kernel-team

6ae08ae3dea2 ("bpf: Add probe_read_{user, kernel} and probe_read_{user,
kernel}_str helpers") introduced a subtle bug where
bpf_probe_read_user_str() would potentially copy a few extra bytes after
the NUL terminator.

This issue is particularly nefarious when strings are used as map keys,
as seemingly identical strings can occupy multiple entries in a map.

This patchset fixes the issue and introduces a selftest to prevent
future regressions.

v6 -> v7:
* Add comments

v5 -> v6:
* zero-pad up to sizeof(unsigned long) after NUL

v4 -> v5:
* don't read potentially uninitialized memory

v3 -> v4:
* directly pass userspace pointer to prog
* test more strings of different length

v2 -> v3:
* set pid filter before attaching prog in selftest
* use long instead of int as bpf_probe_read_user_str() retval
* style changes

v1 -> v2:
* add Fixes: tag
* add selftest

Daniel Xu (2):
  lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
  selftest/bpf: Test bpf_probe_read_user_str() strips trailing bytes
    after NUL

 kernel/trace/bpf_trace.c                      | 10 +++
 lib/strncpy_from_user.c                       | 19 ++++-
 .../bpf/prog_tests/probe_read_user_str.c      | 71 +++++++++++++++++++
 .../bpf/progs/test_probe_read_user_str.c      | 25 +++++++
 4 files changed, 123 insertions(+), 2 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/probe_read_user_str.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_probe_read_user_str.c

-- 
2.29.2


^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
  2020-11-17 20:05 [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying Daniel Xu
@ 2020-11-17 20:05 ` Daniel Xu
  2020-11-17 20:14   ` Alexei Starovoitov
  2020-11-17 20:05 ` [PATCH bpf v7 2/2] selftest/bpf: Test bpf_probe_read_user_str() strips trailing bytes after NUL Daniel Xu
  2020-11-19 19:00 ` [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying patchwork-bot+netdevbpf
  2 siblings, 1 reply; 8+ messages in thread
From: Daniel Xu @ 2020-11-17 20:05 UTC (permalink / raw)
  To: bpf, linux-kernel, ast, daniel, songliubraving, andrii.nakryiko,
	torvalds
  Cc: Daniel Xu, kernel-team

do_strncpy_from_user() may copy some extra bytes after the NUL
terminator into the destination buffer. This usually does not matter for
normal string operations. However, when BPF programs key BPF maps with
strings, this matters a lot.

A BPF program may read strings from user memory by calling the
bpf_probe_read_user_str() helper which eventually calls
do_strncpy_from_user(). The program can then key a map with the
resulting string. BPF map keys are fixed-width and string-agnostic,
meaning that map keys are treated as a set of bytes.

The issue is when do_strncpy_from_user() overcopies bytes after the NUL
terminator, it can result in seemingly identical strings occupying
multiple slots in a BPF map. This behavior is subtle and totally
unexpected by the user.

This commit uses the proper word-at-a-time APIs to avoid overcopying.

Fixes: 6ae08ae3dea2 ("bpf: Add probe_read_{user, kernel} and probe_read_{user, kernel}_str helpers")
Signed-off-by: Daniel Xu <dxu@dxuuu.xyz>
---
As mentioned in the v6 discussion, I didn't think it would make a lot
of sense to put a comment in kernel/bpf/hashtab.c:alloc_htab_elem .
I opted to add the comment to bpf_probe_read_user_str_common() b/c it
seems like the next best place. Just let me know if you want it
somewhere else.

 kernel/trace/bpf_trace.c | 10 ++++++++++
 lib/strncpy_from_user.c  | 19 +++++++++++++++++--
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/kernel/trace/bpf_trace.c b/kernel/trace/bpf_trace.c
index 5113fd423cdf..048c655315f1 100644
--- a/kernel/trace/bpf_trace.c
+++ b/kernel/trace/bpf_trace.c
@@ -181,6 +181,16 @@ bpf_probe_read_user_str_common(void *dst, u32 size,
 {
 	int ret;
 
+	/*
+	 * NB: We rely on strncpy_from_user() not copying junk past the NUL
+	 * terminator into `dst`.
+	 *
+	 * strncpy_from_user() does long-sized strides in the fast path. If the
+	 * strncpy does not mask out the bytes after the NUL in `unsafe_ptr`,
+	 * then there could be junk after the NUL in `dst`. If user takes `dst`
+	 * and keys a hash map with it, then semantically identical strings can
+	 * occupy multiple entries in the map.
+	 */
 	ret = strncpy_from_user_nofault(dst, unsafe_ptr, size);
 	if (unlikely(ret < 0))
 		memset(dst, 0, size);
diff --git a/lib/strncpy_from_user.c b/lib/strncpy_from_user.c
index e6d5fcc2cdf3..122d8d0e253c 100644
--- a/lib/strncpy_from_user.c
+++ b/lib/strncpy_from_user.c
@@ -35,17 +35,32 @@ static inline long do_strncpy_from_user(char *dst, const char __user *src,
 		goto byte_at_a_time;
 
 	while (max >= sizeof(unsigned long)) {
-		unsigned long c, data;
+		unsigned long c, data, mask;
 
 		/* Fall back to byte-at-a-time if we get a page fault */
 		unsafe_get_user(c, (unsigned long __user *)(src+res), byte_at_a_time);
 
-		*(unsigned long *)(dst+res) = c;
+		/*
+		 * Note that we mask out the bytes following the NUL. This is
+		 * important to do because string oblivious code may read past
+		 * the NUL. For those routines, we don't want to give them
+		 * potentially random bytes after the NUL in `src`.
+		 *
+		 * One example of such code is BPF map keys. BPF treats map keys
+		 * as an opaque set of bytes. Without the post-NUL mask, any BPF
+		 * maps keyed by strings returned from strncpy_from_user() may
+		 * have multiple entries for semantically identical strings.
+		 */
 		if (has_zero(c, &data, &constants)) {
 			data = prep_zero_mask(c, data, &constants);
 			data = create_zero_mask(data);
+			mask = zero_bytemask(data);
+			*(unsigned long *)(dst+res) = c & mask;
 			return res + find_zero(data);
 		}
+
+		*(unsigned long *)(dst+res) = c;
+
 		res += sizeof(unsigned long);
 		max -= sizeof(unsigned long);
 	}
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH bpf v7 2/2] selftest/bpf: Test bpf_probe_read_user_str() strips trailing bytes after NUL
  2020-11-17 20:05 [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying Daniel Xu
  2020-11-17 20:05 ` [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator Daniel Xu
@ 2020-11-17 20:05 ` Daniel Xu
  2020-11-19 19:00 ` [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying patchwork-bot+netdevbpf
  2 siblings, 0 replies; 8+ messages in thread
From: Daniel Xu @ 2020-11-17 20:05 UTC (permalink / raw)
  To: bpf, linux-kernel, ast, daniel, songliubraving, andrii.nakryiko,
	torvalds
  Cc: Daniel Xu, kernel-team, Andrii Nakryiko

Previously, bpf_probe_read_user_str() could potentially overcopy the
trailing bytes after the NUL due to how do_strncpy_from_user() does the
copy in long-sized strides. The issue has been fixed in the previous
commit.

This commit adds a selftest that ensures we don't regress
bpf_probe_read_user_str() again.

Acked-by: Song Liu <songliubraving@fb.com>
Acked-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Daniel Xu <dxu@dxuuu.xyz>
---
 .../bpf/prog_tests/probe_read_user_str.c      | 71 +++++++++++++++++++
 .../bpf/progs/test_probe_read_user_str.c      | 25 +++++++
 2 files changed, 96 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/prog_tests/probe_read_user_str.c
 create mode 100644 tools/testing/selftests/bpf/progs/test_probe_read_user_str.c

diff --git a/tools/testing/selftests/bpf/prog_tests/probe_read_user_str.c b/tools/testing/selftests/bpf/prog_tests/probe_read_user_str.c
new file mode 100644
index 000000000000..e419298132b5
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/probe_read_user_str.c
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <test_progs.h>
+#include "test_probe_read_user_str.skel.h"
+
+static const char str1[] = "mestring";
+static const char str2[] = "mestringalittlebigger";
+static const char str3[] = "mestringblubblubblubblubblub";
+
+static int test_one_str(struct test_probe_read_user_str *skel, const char *str,
+			size_t len)
+{
+	int err, duration = 0;
+	char buf[256];
+
+	/* Ensure bytes after string are ones */
+	memset(buf, 1, sizeof(buf));
+	memcpy(buf, str, len);
+
+	/* Give prog our userspace pointer */
+	skel->bss->user_ptr = buf;
+
+	/* Trigger tracepoint */
+	usleep(1);
+
+	/* Did helper fail? */
+	if (CHECK(skel->bss->ret < 0, "prog_ret", "prog returned: %ld\n",
+		  skel->bss->ret))
+		return 1;
+
+	/* Check that string was copied correctly */
+	err = memcmp(skel->bss->buf, str, len);
+	if (CHECK(err, "memcmp", "prog copied wrong string"))
+		return 1;
+
+	/* Now check that no extra trailing bytes were copied */
+	memset(buf, 0, sizeof(buf));
+	err = memcmp(skel->bss->buf + len, buf, sizeof(buf) - len);
+	if (CHECK(err, "memcmp", "trailing bytes were not stripped"))
+		return 1;
+
+	return 0;
+}
+
+void test_probe_read_user_str(void)
+{
+	struct test_probe_read_user_str *skel;
+	int err, duration = 0;
+
+	skel = test_probe_read_user_str__open_and_load();
+	if (CHECK(!skel, "test_probe_read_user_str__open_and_load",
+		  "skeleton open and load failed\n"))
+		return;
+
+	/* Give pid to bpf prog so it doesn't read from anyone else */
+	skel->bss->pid = getpid();
+
+	err = test_probe_read_user_str__attach(skel);
+	if (CHECK(err, "test_probe_read_user_str__attach",
+		  "skeleton attach failed: %d\n", err))
+		goto out;
+
+	if (test_one_str(skel, str1, sizeof(str1)))
+		goto out;
+	if (test_one_str(skel, str2, sizeof(str2)))
+		goto out;
+	if (test_one_str(skel, str3, sizeof(str3)))
+		goto out;
+
+out:
+	test_probe_read_user_str__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/test_probe_read_user_str.c b/tools/testing/selftests/bpf/progs/test_probe_read_user_str.c
new file mode 100644
index 000000000000..3ae398b75dcd
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_probe_read_user_str.c
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#include <sys/types.h>
+
+pid_t pid = 0;
+long ret = 0;
+void *user_ptr = 0;
+char buf[256] = {};
+
+SEC("tracepoint/syscalls/sys_enter_nanosleep")
+int on_write(void *ctx)
+{
+	if (pid != (bpf_get_current_pid_tgid() >> 32))
+		return 0;
+
+	ret = bpf_probe_read_user_str(buf, sizeof(buf), user_ptr);
+
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
  2020-11-17 20:05 ` [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator Daniel Xu
@ 2020-11-17 20:14   ` Alexei Starovoitov
  2020-11-19 18:33     ` Alexei Starovoitov
  0 siblings, 1 reply; 8+ messages in thread
From: Alexei Starovoitov @ 2020-11-17 20:14 UTC (permalink / raw)
  To: Daniel Xu
  Cc: bpf, LKML, Alexei Starovoitov, Daniel Borkmann, Song Liu,
	Andrii Nakryiko, Linus Torvalds, Kernel Team

On Tue, Nov 17, 2020 at 12:05 PM Daniel Xu <dxu@dxuuu.xyz> wrote:
>
> This commit uses the proper word-at-a-time APIs to avoid overcopying.

that part of the commit log is no longer correct. I can fix it up while applying
if Linus doesn't have an issue with the rest.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
  2020-11-17 20:14   ` Alexei Starovoitov
@ 2020-11-19 18:33     ` Alexei Starovoitov
  2020-11-19 18:40       ` Linus Torvalds
  0 siblings, 1 reply; 8+ messages in thread
From: Alexei Starovoitov @ 2020-11-19 18:33 UTC (permalink / raw)
  To: Daniel Xu
  Cc: bpf, LKML, Alexei Starovoitov, Daniel Borkmann, Song Liu,
	Andrii Nakryiko, Linus Torvalds, Kernel Team

On Tue, Nov 17, 2020 at 12:14 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Tue, Nov 17, 2020 at 12:05 PM Daniel Xu <dxu@dxuuu.xyz> wrote:
> >
> > This commit uses the proper word-at-a-time APIs to avoid overcopying.
>
> that part of the commit log is no longer correct. I can fix it up while applying
> if Linus doesn't have an issue with the rest.

Linus,
ping.

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
  2020-11-19 18:33     ` Alexei Starovoitov
@ 2020-11-19 18:40       ` Linus Torvalds
  2020-11-19 18:44         ` Alexei Starovoitov
  0 siblings, 1 reply; 8+ messages in thread
From: Linus Torvalds @ 2020-11-19 18:40 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Daniel Xu, bpf, LKML, Alexei Starovoitov, Daniel Borkmann,
	Song Liu, Andrii Nakryiko, Kernel Team

On Thu, Nov 19, 2020 at 10:34 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> ping.

I'm ok with this series that adds explanations for why you care and
what bpf does that makes it valid.

So this one you can put in the bpf tree.

Or, if you want me to just apply it as a series, I can do that too, I
just generally assume that when there's a git tree I usually get
things from, that's the default, so then it needs ot be a very loud
and explicit "Linus, can you apply this directly".

              Linus

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
  2020-11-19 18:40       ` Linus Torvalds
@ 2020-11-19 18:44         ` Alexei Starovoitov
  0 siblings, 0 replies; 8+ messages in thread
From: Alexei Starovoitov @ 2020-11-19 18:44 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Daniel Xu, bpf, LKML, Alexei Starovoitov, Daniel Borkmann,
	Song Liu, Andrii Nakryiko, Kernel Team

On Thu, Nov 19, 2020 at 10:40:21AM -0800, Linus Torvalds wrote:
> On Thu, Nov 19, 2020 at 10:34 AM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> >
> > ping.
> 
> I'm ok with this series that adds explanations for why you care and
> what bpf does that makes it valid.

Great.

> So this one you can put in the bpf tree.
> 
> Or, if you want me to just apply it as a series, I can do that too, I
> just generally assume that when there's a git tree I usually get
> things from, that's the default, so then it needs ot be a very loud
> and explicit "Linus, can you apply this directly".

Right. The set will go through the normal bpf.git->net.git route to make
sure it is tested by humans and CIs.
Thanks!

^ permalink raw reply	[flat|nested] 8+ messages in thread

* Re: [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying
  2020-11-17 20:05 [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying Daniel Xu
  2020-11-17 20:05 ` [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator Daniel Xu
  2020-11-17 20:05 ` [PATCH bpf v7 2/2] selftest/bpf: Test bpf_probe_read_user_str() strips trailing bytes after NUL Daniel Xu
@ 2020-11-19 19:00 ` patchwork-bot+netdevbpf
  2 siblings, 0 replies; 8+ messages in thread
From: patchwork-bot+netdevbpf @ 2020-11-19 19:00 UTC (permalink / raw)
  To: Daniel Xu
  Cc: bpf, linux-kernel, ast, daniel, songliubraving, andrii.nakryiko,
	torvalds, kernel-team

Hello:

This series was applied to bpf/bpf.git (refs/heads/master):

On Tue, 17 Nov 2020 12:05:44 -0800 you wrote:
> 6ae08ae3dea2 ("bpf: Add probe_read_{user, kernel} and probe_read_{user,
> kernel}_str helpers") introduced a subtle bug where
> bpf_probe_read_user_str() would potentially copy a few extra bytes after
> the NUL terminator.
> 
> This issue is particularly nefarious when strings are used as map keys,
> as seemingly identical strings can occupy multiple entries in a map.
> 
> [...]

Here is the summary with links:
  - [bpf,v7,1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator
    https://git.kernel.org/bpf/bpf/c/33b97ea52713
  - [bpf,v7,2/2] selftest/bpf: Test bpf_probe_read_user_str() strips trailing bytes after NUL
    https://git.kernel.org/bpf/bpf/c/a8005439fc81

You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2020-11-19 19:00 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-17 20:05 [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying Daniel Xu
2020-11-17 20:05 ` [PATCH bpf v7 1/2] lib/strncpy_from_user.c: Don't overcopy bytes after NUL terminator Daniel Xu
2020-11-17 20:14   ` Alexei Starovoitov
2020-11-19 18:33     ` Alexei Starovoitov
2020-11-19 18:40       ` Linus Torvalds
2020-11-19 18:44         ` Alexei Starovoitov
2020-11-17 20:05 ` [PATCH bpf v7 2/2] selftest/bpf: Test bpf_probe_read_user_str() strips trailing bytes after NUL Daniel Xu
2020-11-19 19:00 ` [PATCH bpf v7 0/2] Fix bpf_probe_read_user_str() overcopying patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).