Commit cf2d0a5e authored by Alexei Starovoitov's avatar Alexei Starovoitov
Browse files

Merge branch 'allow variable-offset stack acces'



Andrei Matei says:

====================

Before this patch, variable offset access to the stack was dissalowed
for regular instructions, but was allowed for "indirect" accesses (i.e.
helpers). This patch removes the restriction, allowing reading and
writing to the stack through stack pointers with variable offsets. This
makes stack-allocated buffers more usable in programs, and brings stack
pointers closer to other types of pointers.

The motivation is being able to use stack-allocated buffers for data
manipulation. When the stack size limit is sufficient, allocating
buffers on the stack is simpler than per-cpu arrays, or other
alternatives.

V2 -> V3

- var-offset writes mark all the stack slots in range as initialized, so
  that future reads are not rejected.
- rewrote the C test to not use uprobes, as per Andrii's suggestion.
- addressed other review comments from Alexei.

V1 -> V2

- add support for var-offset stack writes, in addition to reads
- add a C test
- made variable offset direct reads no longer destroy spilled registers
  in the access range
- address review nits
====================

Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
parents ee5cc036 0fd7562a
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -1290,6 +1290,11 @@ static inline bool bpf_allow_ptr_leaks(void)
	return perfmon_capable();
}

static inline bool bpf_allow_uninit_stack(void)
{
	return perfmon_capable();
}

static inline bool bpf_allow_ptr_to_map_access(void)
{
	return perfmon_capable();
+2 −1
Original line number Diff line number Diff line
@@ -195,7 +195,7 @@ struct bpf_func_state {
	 * 0 = main function, 1 = first callee.
	 */
	u32 frameno;
	/* subprog number == index within subprog_stack_depth
	/* subprog number == index within subprog_info
	 * zero == main subprog
	 */
	u32 subprogno;
@@ -404,6 +404,7 @@ struct bpf_verifier_env {
	u32 used_btf_cnt;		/* number of used BTF objects */
	u32 id_gen;			/* used to generate unique reg IDs */
	bool allow_ptr_leaks;
	bool allow_uninit_stack;
	bool allow_ptr_to_map_access;
	bool bpf_capable;
	bool bypass_spec_v1;
+511 −146

File changed.

Preview size limit exceeded, changes collapsed.

+35 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include "test_stack_var_off.skel.h"

/* Test read and writes to the stack performed with offsets that are not
 * statically known.
 */
void test_stack_var_off(void)
{
	int duration = 0;
	struct test_stack_var_off *skel;

	skel = test_stack_var_off__open_and_load();
	if (CHECK(!skel, "skel_open", "failed to open skeleton\n"))
		return;

	/* Give pid to bpf prog so it doesn't trigger for anyone else. */
	skel->bss->test_pid = getpid();
	/* Initialize the probe's input. */
	skel->bss->input[0] = 2;
	skel->bss->input[1] = 42;  /* This will be returned in probe_res. */

	if (!ASSERT_OK(test_stack_var_off__attach(skel), "skel_attach"))
		goto cleanup;

	/* Trigger probe. */
	usleep(1);

	if (CHECK(skel->bss->probe_res != 42, "check_probe_res",
		  "wrong probe res: %d\n", skel->bss->probe_res))
		goto cleanup;

cleanup:
	test_stack_var_off__destroy(skel);
}
+51 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

int probe_res;

char input[4] = {};
int test_pid;

SEC("tracepoint/syscalls/sys_enter_nanosleep")
int probe(void *ctx)
{
	/* This BPF program performs variable-offset reads and writes on a
	 * stack-allocated buffer.
	 */
	char stack_buf[16];
	unsigned long len;
	unsigned long last;

	if ((bpf_get_current_pid_tgid() >> 32) != test_pid)
		return 0;

	/* Copy the input to the stack. */
	__builtin_memcpy(stack_buf, input, 4);

	/* The first byte in the buffer indicates the length. */
	len = stack_buf[0] & 0xf;
	last = (len - 1) & 0xf;

	/* Append something to the buffer. The offset where we write is not
	 * statically known; this is a variable-offset stack write.
	 */
	stack_buf[len] = 42;

	/* Index into the buffer at an unknown offset. This is a
	 * variable-offset stack read.
	 *
	 * Note that if it wasn't for the preceding variable-offset write, this
	 * read would be rejected because the stack slot cannot be verified as
	 * being initialized. With the preceding variable-offset write, the
	 * stack slot still cannot be verified, but the write inhibits the
	 * respective check on the reasoning that, if there was a
	 * variable-offset to a higher-or-equal spot, we're probably reading
	 * what we just wrote.
	 */
	probe_res = stack_buf[last];
	return 0;
}

char _license[] SEC("license") = "GPL";
Loading