Commit d895a0f1 authored by Ilya Leoshkevich's avatar Ilya Leoshkevich Committed by Daniel Borkmann
Browse files

bpf: fix accessing bpf_sysctl.file_pos on s390



"ctx:file_pos sysctl:read write ok" fails on s390 with "Read value  !=
nux". This is because verifier rewrites a complete 32-bit
bpf_sysctl.file_pos update to a partial update of the first 32 bits of
64-bit *bpf_sysctl_kern.ppos, which is not correct on big-endian
systems.

Fix by using an offset on big-endian systems.

Ditto for bpf_sysctl.file_pos reads. Currently the test does not detect
a problem there, since it expects to see 0, which it gets with high
probability in error cases, so change it to seek to offset 3 and expect
3 in bpf_sysctl.file_pos.

Fixes: e1550bfe ("bpf: Add file_pos field to bpf_sysctl ctx")
Signed-off-by: default avatarIlya Leoshkevich <iii@linux.ibm.com>
Acked-by: default avatarYonghong Song <yhs@fb.com>
Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
Link: https://lore.kernel.org/bpf/20190816105300.49035-1-iii@linux.ibm.com/
parent af58e7ee
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -749,14 +749,14 @@ bpf_ctx_narrow_access_ok(u32 off, u32 size, u32 size_default)
}

static inline u8
bpf_ctx_narrow_load_shift(u32 off, u32 size, u32 size_default)
bpf_ctx_narrow_access_offset(u32 off, u32 size, u32 size_default)
{
	u8 load_off = off & (size_default - 1);
	u8 access_off = off & (size_default - 1);

#ifdef __LITTLE_ENDIAN
	return load_off * 8;
	return access_off;
#else
	return (size_default - (load_off + size)) * 8;
	return size_default - (access_off + size);
#endif
}

+8 −2
Original line number Diff line number Diff line
@@ -1334,6 +1334,7 @@ static u32 sysctl_convert_ctx_access(enum bpf_access_type type,
				     struct bpf_prog *prog, u32 *target_size)
{
	struct bpf_insn *insn = insn_buf;
	u32 read_size;

	switch (si->off) {
	case offsetof(struct bpf_sysctl, write):
@@ -1365,7 +1366,9 @@ static u32 sysctl_convert_ctx_access(enum bpf_access_type type,
				treg, si->dst_reg,
				offsetof(struct bpf_sysctl_kern, ppos));
			*insn++ = BPF_STX_MEM(
				BPF_SIZEOF(u32), treg, si->src_reg, 0);
				BPF_SIZEOF(u32), treg, si->src_reg,
				bpf_ctx_narrow_access_offset(
					0, sizeof(u32), sizeof(loff_t)));
			*insn++ = BPF_LDX_MEM(
				BPF_DW, treg, si->dst_reg,
				offsetof(struct bpf_sysctl_kern, tmp_reg));
@@ -1374,8 +1377,11 @@ static u32 sysctl_convert_ctx_access(enum bpf_access_type type,
				BPF_FIELD_SIZEOF(struct bpf_sysctl_kern, ppos),
				si->dst_reg, si->src_reg,
				offsetof(struct bpf_sysctl_kern, ppos));
			read_size = bpf_size_to_bytes(BPF_SIZE(si->code));
			*insn++ = BPF_LDX_MEM(
				BPF_SIZE(si->code), si->dst_reg, si->dst_reg, 0);
				BPF_SIZE(si->code), si->dst_reg, si->dst_reg,
				bpf_ctx_narrow_access_offset(
					0, read_size, sizeof(loff_t)));
		}
		*target_size = sizeof(u32);
		break;
+2 −2
Original line number Diff line number Diff line
@@ -8619,8 +8619,8 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
		}

		if (is_narrower_load && size < target_size) {
			u8 shift = bpf_ctx_narrow_load_shift(off, size,
							     size_default);
			u8 shift = bpf_ctx_narrow_access_offset(
				off, size, size_default) * 8;
			if (ctx_field_size <= 4) {
				if (shift)
					insn_buf[cnt++] = BPF_ALU32_IMM(BPF_RSH,
+8 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ struct sysctl_test {
	enum bpf_attach_type attach_type;
	const char *sysctl;
	int open_flags;
	int seek;
	const char *newval;
	const char *oldval;
	enum {
@@ -140,7 +141,7 @@ static struct sysctl_test tests[] = {
			/* If (file_pos == X) */
			BPF_LDX_MEM(BPF_W, BPF_REG_7, BPF_REG_1,
				    offsetof(struct bpf_sysctl, file_pos)),
			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 0, 2),
			BPF_JMP_IMM(BPF_JNE, BPF_REG_7, 3, 2),

			/* return ALLOW; */
			BPF_MOV64_IMM(BPF_REG_0, 1),
@@ -153,6 +154,7 @@ static struct sysctl_test tests[] = {
		.attach_type = BPF_CGROUP_SYSCTL,
		.sysctl = "kernel/ostype",
		.open_flags = O_RDONLY,
		.seek = 3,
		.result = SUCCESS,
	},
	{
@@ -1481,6 +1483,11 @@ static int access_sysctl(const char *sysctl_path,
	if (fd < 0)
		return fd;

	if (test->seek && lseek(fd, test->seek, SEEK_SET) == -1) {
		log_err("lseek(%d) failed", test->seek);
		goto err;
	}

	if (test->open_flags == O_RDONLY) {
		char buf[128];