Shung-Hsi Yu 03f754
From: Hao Luo <haoluo@google.com>
Shung-Hsi Yu 03f754
Date: Thu, 16 Dec 2021 16:31:48 -0800
Shung-Hsi Yu 03f754
Subject: bpf: Introduce MEM_RDONLY flag
Shung-Hsi Yu 03f754
Patch-mainline: v5.17-rc1
Shung-Hsi Yu 03f754
Git-commit: 20b2aff4bc15bda809f994761d5719827d66c0b4
Shung-Hsi Yu 03f754
References: bsc#1194111 bsc#1194765 bsc#1196261 CVE-2021-4204 CVE-2022-0500 CVE-2022-23222
Shung-Hsi Yu 03f754
Shung-Hsi Yu 03f754
This patch introduce a flag MEM_RDONLY to tag a reg value
Shung-Hsi Yu 03f754
pointing to read-only memory. It makes the following changes:
Shung-Hsi Yu 03f754
Shung-Hsi Yu 03f754
1. PTR_TO_RDWR_BUF -> PTR_TO_BUF
Shung-Hsi Yu 03f754
2. PTR_TO_RDONLY_BUF -> PTR_TO_BUF | MEM_RDONLY
Shung-Hsi Yu 03f754
Shung-Hsi Yu 03f754
Signed-off-by: Hao Luo <haoluo@google.com>
Shung-Hsi Yu 03f754
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Shung-Hsi Yu 03f754
Link: https://lore.kernel.org/bpf/20211217003152.48334-6-haoluo@google.com
Shung-Hsi Yu 03f754
Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Shung-Hsi Yu 03f754
---
Shung-Hsi Yu 03f754
 include/linux/bpf.h       |    8 ++--
Shung-Hsi Yu 03f754
 kernel/bpf/btf.c          |    3 -
Shung-Hsi Yu 03f754
 kernel/bpf/map_iter.c     |    4 +-
Shung-Hsi Yu 03f754
 kernel/bpf/verifier.c     |   84 +++++++++++++++++++++++++++-------------------
Shung-Hsi Yu 03f754
 net/core/bpf_sk_storage.c |    2 -
Shung-Hsi Yu 03f754
 net/core/sock_map.c       |    2 -
Shung-Hsi Yu 03f754
 6 files changed, 60 insertions(+), 43 deletions(-)
Shung-Hsi Yu 03f754
Shung-Hsi Yu 03f754
--- a/include/linux/bpf.h
Shung-Hsi Yu 03f754
+++ b/include/linux/bpf.h
Shung-Hsi Yu 03f754
@@ -284,7 +284,10 @@ enum bpf_type_flag {
Shung-Hsi Yu 03f754
 	/* PTR may be NULL. */
Shung-Hsi Yu 03f754
 	PTR_MAYBE_NULL		= BIT(0 + BPF_BASE_TYPE_BITS),
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
-	__BPF_TYPE_LAST_FLAG	= PTR_MAYBE_NULL,
Shung-Hsi Yu 03f754
+	/* MEM is read-only. */
Shung-Hsi Yu 03f754
+	MEM_RDONLY		= BIT(1 + BPF_BASE_TYPE_BITS),
Shung-Hsi Yu 03f754
+
Shung-Hsi Yu 03f754
+	__BPF_TYPE_LAST_FLAG	= MEM_RDONLY,
Shung-Hsi Yu 03f754
 };
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
 /* Max number of base types. */
Shung-Hsi Yu 03f754
@@ -464,8 +467,7 @@ enum bpf_reg_type {
Shung-Hsi Yu 03f754
 	 * an explicit null check is required for this struct.
Shung-Hsi Yu 03f754
 	 */
Shung-Hsi Yu 03f754
 	PTR_TO_MEM,		 /* reg points to valid memory region */
Shung-Hsi Yu 03f754
-	PTR_TO_RDONLY_BUF,	 /* reg points to a readonly buffer */
Shung-Hsi Yu 03f754
-	PTR_TO_RDWR_BUF,	 /* reg points to a read/write buffer */
Shung-Hsi Yu 03f754
+	PTR_TO_BUF,		 /* reg points to a read/write buffer */
Shung-Hsi Yu 03f754
 	PTR_TO_PERCPU_BTF_ID,	 /* reg points to a percpu kernel variable */
Shung-Hsi Yu 03f754
 	PTR_TO_FUNC,		 /* reg points to a bpf program function */
Shung-Hsi Yu 03f754
 	__BPF_REG_TYPE_MAX,
Shung-Hsi Yu 03f754
--- a/kernel/bpf/btf.c
Shung-Hsi Yu 03f754
+++ b/kernel/bpf/btf.c
Shung-Hsi Yu 03f754
@@ -4755,8 +4755,7 @@ bool btf_ctx_access(int off, int size, e
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
 		type = base_type(ctx_arg_info->reg_type);
Shung-Hsi Yu 03f754
 		flag = type_flag(ctx_arg_info->reg_type);
Shung-Hsi Yu 03f754
-		if (ctx_arg_info->offset == off &&
Shung-Hsi Yu 03f754
-		    (type == PTR_TO_RDWR_BUF || type == PTR_TO_RDONLY_BUF) &&
Shung-Hsi Yu 03f754
+		if (ctx_arg_info->offset == off && type == PTR_TO_BUF &&
Shung-Hsi Yu 03f754
 		    (flag & PTR_MAYBE_NULL)) {
Shung-Hsi Yu 03f754
 			info->reg_type = ctx_arg_info->reg_type;
Shung-Hsi Yu 03f754
 			return true;
Shung-Hsi Yu 03f754
--- a/kernel/bpf/map_iter.c
Shung-Hsi Yu 03f754
+++ b/kernel/bpf/map_iter.c
Shung-Hsi Yu 03f754
@@ -174,9 +174,9 @@ static const struct bpf_iter_reg bpf_map
Shung-Hsi Yu 03f754
 	.ctx_arg_info_size	= 2,
Shung-Hsi Yu 03f754
 	.ctx_arg_info		= {
Shung-Hsi Yu 03f754
 		{ offsetof(struct bpf_iter__bpf_map_elem, key),
Shung-Hsi Yu 03f754
-		  PTR_TO_RDONLY_BUF | PTR_MAYBE_NULL },
Shung-Hsi Yu 03f754
+		  PTR_TO_BUF | PTR_MAYBE_NULL | MEM_RDONLY },
Shung-Hsi Yu 03f754
 		{ offsetof(struct bpf_iter__bpf_map_elem, value),
Shung-Hsi Yu 03f754
-		  PTR_TO_RDWR_BUF | PTR_MAYBE_NULL },
Shung-Hsi Yu 03f754
+		  PTR_TO_BUF | PTR_MAYBE_NULL },
Shung-Hsi Yu 03f754
 	},
Shung-Hsi Yu 03f754
 };
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
--- a/kernel/bpf/verifier.c
Shung-Hsi Yu 03f754
+++ b/kernel/bpf/verifier.c
Shung-Hsi Yu 03f754
@@ -451,6 +451,11 @@ static bool reg_type_may_be_refcounted_o
Shung-Hsi Yu 03f754
 		base_type(type) == PTR_TO_MEM;
Shung-Hsi Yu 03f754
 }
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
+static bool type_is_rdonly_mem(u32 type)
Shung-Hsi Yu 03f754
+{
Shung-Hsi Yu 03f754
+	return type & MEM_RDONLY;
Shung-Hsi Yu 03f754
+}
Shung-Hsi Yu 03f754
+
Shung-Hsi Yu 03f754
 static bool arg_type_may_be_refcounted(enum bpf_arg_type type)
Shung-Hsi Yu 03f754
 {
Shung-Hsi Yu 03f754
 	return type == ARG_PTR_TO_SOCK_COMMON;
Shung-Hsi Yu 03f754
@@ -526,7 +531,7 @@ static bool is_cmpxchg_insn(const struct
Shung-Hsi Yu 03f754
 static const char *reg_type_str(struct bpf_verifier_env *env,
Shung-Hsi Yu 03f754
 				enum bpf_reg_type type)
Shung-Hsi Yu 03f754
 {
Shung-Hsi Yu 03f754
-	char postfix[16] = {0};
Shung-Hsi Yu 03f754
+	char postfix[16] = {0}, prefix[16] = {0};
Shung-Hsi Yu 03f754
 	static const char * const str[] = {
Shung-Hsi Yu 03f754
 		[NOT_INIT]		= "?",
Shung-Hsi Yu 03f754
 		[SCALAR_VALUE]		= "inv",
Shung-Hsi Yu 03f754
@@ -546,8 +551,7 @@ static const char *reg_type_str(struct b
Shung-Hsi Yu 03f754
 		[PTR_TO_BTF_ID]		= "ptr_",
Shung-Hsi Yu 03f754
 		[PTR_TO_PERCPU_BTF_ID]	= "percpu_ptr_",
Shung-Hsi Yu 03f754
 		[PTR_TO_MEM]		= "mem",
Shung-Hsi Yu 03f754
-		[PTR_TO_RDONLY_BUF]	= "rdonly_buf",
Shung-Hsi Yu 03f754
-		[PTR_TO_RDWR_BUF]	= "rdwr_buf",
Shung-Hsi Yu 03f754
+		[PTR_TO_BUF]		= "buf",
Shung-Hsi Yu 03f754
 		[PTR_TO_FUNC]		= "func",
Shung-Hsi Yu 03f754
 		[PTR_TO_MAP_KEY]	= "map_key",
Shung-Hsi Yu 03f754
 	};
Shung-Hsi Yu 03f754
@@ -560,8 +564,11 @@ static const char *reg_type_str(struct b
Shung-Hsi Yu 03f754
 			strncpy(postfix, "_or_null", 16);
Shung-Hsi Yu 03f754
 	}
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
-	snprintf(env->type_str_buf, TYPE_STR_BUF_LEN, "%s%s",
Shung-Hsi Yu 03f754
-		 str[base_type(type)], postfix);
Shung-Hsi Yu 03f754
+	if (type & MEM_RDONLY)
Shung-Hsi Yu 03f754
+		strncpy(prefix, "rdonly_", 16);
Shung-Hsi Yu 03f754
+
Shung-Hsi Yu 03f754
+	snprintf(env->type_str_buf, TYPE_STR_BUF_LEN, "%s%s%s",
Shung-Hsi Yu 03f754
+		 prefix, str[base_type(type)], postfix);
Shung-Hsi Yu 03f754
 	return env->type_str_buf;
Shung-Hsi Yu 03f754
 }
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
@@ -2470,8 +2477,7 @@ static bool is_spillable_regtype(enum bp
Shung-Hsi Yu 03f754
 	case PTR_TO_TCP_SOCK:
Shung-Hsi Yu 03f754
 	case PTR_TO_XDP_SOCK:
Shung-Hsi Yu 03f754
 	case PTR_TO_BTF_ID:
Shung-Hsi Yu 03f754
-	case PTR_TO_RDONLY_BUF:
Shung-Hsi Yu 03f754
-	case PTR_TO_RDWR_BUF:
Shung-Hsi Yu 03f754
+	case PTR_TO_BUF:
Shung-Hsi Yu 03f754
 	case PTR_TO_PERCPU_BTF_ID:
Shung-Hsi Yu 03f754
 	case PTR_TO_MEM:
Shung-Hsi Yu 03f754
 	case PTR_TO_FUNC:
Shung-Hsi Yu 03f754
@@ -4179,22 +4185,28 @@ static int check_mem_access(struct bpf_v
Shung-Hsi Yu 03f754
 	} else if (reg->type == CONST_PTR_TO_MAP) {
Shung-Hsi Yu 03f754
 		err = check_ptr_to_map_access(env, regs, regno, off, size, t,
Shung-Hsi Yu 03f754
 					      value_regno);
Shung-Hsi Yu 03f754
-	} else if (reg->type == PTR_TO_RDONLY_BUF) {
Shung-Hsi Yu 03f754
-		if (t == BPF_WRITE) {
Shung-Hsi Yu 03f754
-			verbose(env, "R%d cannot write into %s\n",
Shung-Hsi Yu 03f754
-				regno, reg_type_str(env, reg->type));
Shung-Hsi Yu 03f754
-			return -EACCES;
Shung-Hsi Yu 03f754
+	} else if (base_type(reg->type) == PTR_TO_BUF) {
Shung-Hsi Yu 03f754
+		bool rdonly_mem = type_is_rdonly_mem(reg->type);
Shung-Hsi Yu 03f754
+		const char *buf_info;
Shung-Hsi Yu 03f754
+		u32 *max_access;
Shung-Hsi Yu 03f754
+
Shung-Hsi Yu 03f754
+		if (rdonly_mem) {
Shung-Hsi Yu 03f754
+			if (t == BPF_WRITE) {
Shung-Hsi Yu 03f754
+				verbose(env, "R%d cannot write into %s\n",
Shung-Hsi Yu 03f754
+					regno, reg_type_str(env, reg->type));
Shung-Hsi Yu 03f754
+				return -EACCES;
Shung-Hsi Yu 03f754
+			}
Shung-Hsi Yu 03f754
+			buf_info = "rdonly";
Shung-Hsi Yu 03f754
+			max_access = &env->prog->aux->max_rdonly_access;
Shung-Hsi Yu 03f754
+		} else {
Shung-Hsi Yu 03f754
+			buf_info = "rdwr";
Shung-Hsi Yu 03f754
+			max_access = &env->prog->aux->max_rdwr_access;
Shung-Hsi Yu 03f754
 		}
Shung-Hsi Yu 03f754
+
Shung-Hsi Yu 03f754
 		err = check_buffer_access(env, reg, regno, off, size, false,
Shung-Hsi Yu 03f754
-					  "rdonly",
Shung-Hsi Yu 03f754
-					  &env->prog->aux->max_rdonly_access);
Shung-Hsi Yu 03f754
-		if (!err && value_regno >= 0)
Shung-Hsi Yu 03f754
-			mark_reg_unknown(env, regs, value_regno);
Shung-Hsi Yu 03f754
-	} else if (reg->type == PTR_TO_RDWR_BUF) {
Shung-Hsi Yu 03f754
-		err = check_buffer_access(env, reg, regno, off, size, false,
Shung-Hsi Yu 03f754
-					  "rdwr",
Shung-Hsi Yu 03f754
-					  &env->prog->aux->max_rdwr_access);
Shung-Hsi Yu 03f754
-		if (!err && t == BPF_READ && value_regno >= 0)
Shung-Hsi Yu 03f754
+					  buf_info, max_access);
Shung-Hsi Yu 03f754
+
Shung-Hsi Yu 03f754
+		if (!err && value_regno >= 0 && (rdonly_mem || t == BPF_READ))
Shung-Hsi Yu 03f754
 			mark_reg_unknown(env, regs, value_regno);
Shung-Hsi Yu 03f754
 	} else {
Shung-Hsi Yu 03f754
 		verbose(env, "R%d invalid mem access '%s'\n", regno,
Shung-Hsi Yu 03f754
@@ -4448,8 +4460,10 @@ static int check_helper_mem_access(struc
Shung-Hsi Yu 03f754
 				   struct bpf_call_arg_meta *meta)
Shung-Hsi Yu 03f754
 {
Shung-Hsi Yu 03f754
 	struct bpf_reg_state *regs = cur_regs(env), *reg = &regs[regno];
Shung-Hsi Yu 03f754
+	const char *buf_info;
Shung-Hsi Yu 03f754
+	u32 *max_access;
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
-	switch (reg->type) {
Shung-Hsi Yu 03f754
+	switch (base_type(reg->type)) {
Shung-Hsi Yu 03f754
 	case PTR_TO_PACKET:
Shung-Hsi Yu 03f754
 	case PTR_TO_PACKET_META:
Shung-Hsi Yu 03f754
 		return check_packet_access(env, regno, reg->off, access_size,
Shung-Hsi Yu 03f754
@@ -4468,18 +4482,20 @@ static int check_helper_mem_access(struc
Shung-Hsi Yu 03f754
 		return check_mem_region_access(env, regno, reg->off,
Shung-Hsi Yu 03f754
 					       access_size, reg->mem_size,
Shung-Hsi Yu 03f754
 					       zero_size_allowed);
Shung-Hsi Yu 03f754
-	case PTR_TO_RDONLY_BUF:
Shung-Hsi Yu 03f754
-		if (meta && meta->raw_mode)
Shung-Hsi Yu 03f754
-			return -EACCES;
Shung-Hsi Yu 03f754
-		return check_buffer_access(env, reg, regno, reg->off,
Shung-Hsi Yu 03f754
-					   access_size, zero_size_allowed,
Shung-Hsi Yu 03f754
-					   "rdonly",
Shung-Hsi Yu 03f754
-					   &env->prog->aux->max_rdonly_access);
Shung-Hsi Yu 03f754
-	case PTR_TO_RDWR_BUF:
Shung-Hsi Yu 03f754
+	case PTR_TO_BUF:
Shung-Hsi Yu 03f754
+		if (type_is_rdonly_mem(reg->type)) {
Shung-Hsi Yu 03f754
+			if (meta && meta->raw_mode)
Shung-Hsi Yu 03f754
+				return -EACCES;
Shung-Hsi Yu 03f754
+
Shung-Hsi Yu 03f754
+			buf_info = "rdonly";
Shung-Hsi Yu 03f754
+			max_access = &env->prog->aux->max_rdonly_access;
Shung-Hsi Yu 03f754
+		} else {
Shung-Hsi Yu 03f754
+			buf_info = "rdwr";
Shung-Hsi Yu 03f754
+			max_access = &env->prog->aux->max_rdwr_access;
Shung-Hsi Yu 03f754
+		}
Shung-Hsi Yu 03f754
 		return check_buffer_access(env, reg, regno, reg->off,
Shung-Hsi Yu 03f754
 					   access_size, zero_size_allowed,
Shung-Hsi Yu 03f754
-					   "rdwr",
Shung-Hsi Yu 03f754
-					   &env->prog->aux->max_rdwr_access);
Shung-Hsi Yu 03f754
+					   buf_info, max_access);
Shung-Hsi Yu 03f754
 	case PTR_TO_STACK:
Shung-Hsi Yu 03f754
 		return check_stack_range_initialized(
Shung-Hsi Yu 03f754
 				env,
Shung-Hsi Yu 03f754
@@ -4707,8 +4723,8 @@ static const struct bpf_reg_types mem_ty
Shung-Hsi Yu 03f754
 		PTR_TO_MAP_KEY,
Shung-Hsi Yu 03f754
 		PTR_TO_MAP_VALUE,
Shung-Hsi Yu 03f754
 		PTR_TO_MEM,
Shung-Hsi Yu 03f754
-		PTR_TO_RDONLY_BUF,
Shung-Hsi Yu 03f754
-		PTR_TO_RDWR_BUF,
Shung-Hsi Yu 03f754
+		PTR_TO_BUF,
Shung-Hsi Yu 03f754
+		PTR_TO_BUF | MEM_RDONLY,
Shung-Hsi Yu 03f754
 	},
Shung-Hsi Yu 03f754
 };
Shung-Hsi Yu 03f754
 
Shung-Hsi Yu 03f754
--- a/net/core/bpf_sk_storage.c
Shung-Hsi Yu 03f754
+++ b/net/core/bpf_sk_storage.c
Shung-Hsi Yu 03f754
@@ -929,7 +929,7 @@ static struct bpf_iter_reg bpf_sk_storag
Shung-Hsi Yu 03f754
 		{ offsetof(struct bpf_iter__bpf_sk_storage_map, sk),
Shung-Hsi Yu 03f754
 		  PTR_TO_BTF_ID_OR_NULL },
Shung-Hsi Yu 03f754
 		{ offsetof(struct bpf_iter__bpf_sk_storage_map, value),
Shung-Hsi Yu 03f754
-		  PTR_TO_RDWR_BUF | PTR_MAYBE_NULL },
Shung-Hsi Yu 03f754
+		  PTR_TO_BUF | PTR_MAYBE_NULL },
Shung-Hsi Yu 03f754
 	},
Shung-Hsi Yu 03f754
 	.seq_info		= &iter_seq_info,
Shung-Hsi Yu 03f754
 };
Shung-Hsi Yu 03f754
--- a/net/core/sock_map.c
Shung-Hsi Yu 03f754
+++ b/net/core/sock_map.c
Shung-Hsi Yu 03f754
@@ -1582,7 +1582,7 @@ static struct bpf_iter_reg sock_map_iter
Shung-Hsi Yu 03f754
 	.ctx_arg_info_size	= 2,
Shung-Hsi Yu 03f754
 	.ctx_arg_info		= {
Shung-Hsi Yu 03f754
 		{ offsetof(struct bpf_iter__sockmap, key),
Shung-Hsi Yu 03f754
-		  PTR_TO_RDONLY_BUF | PTR_MAYBE_NULL },
Shung-Hsi Yu 03f754
+		  PTR_TO_BUF | PTR_MAYBE_NULL | MEM_RDONLY },
Shung-Hsi Yu 03f754
 		{ offsetof(struct bpf_iter__sockmap, sk),
Shung-Hsi Yu 03f754
 		  PTR_TO_BTF_ID_OR_NULL },
Shung-Hsi Yu 03f754
 	},