linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Roberto Sassu <roberto.sassu@huawei.com>
To: <ast@kernel.org>, <daniel@iogearbox.net>, <andrii@kernel.org>,
	<kpsingh@kernel.org>
Cc: <bpf@vger.kernel.org>, <netdev@vger.kernel.org>,
	<linux-kselftest@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
	Roberto Sassu <roberto.sassu@huawei.com>
Subject: [PATCH 1/3] bpf: Add BPF_F_VERIFY_ELEM to require signature verification on map values
Date: Wed, 25 May 2022 15:21:13 +0200	[thread overview]
Message-ID: <20220525132115.896698-2-roberto.sassu@huawei.com> (raw)
In-Reply-To: <20220525132115.896698-1-roberto.sassu@huawei.com>

In some cases, it is desirable to ensure that a map contains data from
authenticated sources, for example if map data are used for making security
decisions.

Such restriction is achieved by verifying the signature of map values, at
the time those values are added to the map with the bpf() system call (more
specifically, when the commands passed to bpf() are BPF_MAP_UPDATE_ELEM or
BPF_MAP_UPDATE_BATCH). Mmappable maps are not allowed in this case.

Signature verification is initially done with keys in the primary and
secondary kernel keyrings, similarly to kernel modules. This allows system
owners to enforce a system-wide policy based on the keys they trust.
Support for additional keyrings could be added later, based on use case
needs.

Signature verification is done only for those maps for which the new map
flag BPF_F_VERIFY_ELEM is set. When the flag is set, the kernel expects map
values to be in the following format:

+-------------------------------+---------------+-----+-----------------+
| verified data+sig size (be32) | verified data | sig | unverified data |
+-------------------------------+---------------+-----+-----------------+

where sig is a module-style appended signature as generated by the
sign-file tool. The verified data+sig size (in big endian) must be
explicitly provided (it is not generated by sign-file), as it cannot be
determined in other ways (currently, the map value size is fixed). It can
be obtained from the size of the file created by sign-file.

Introduce the new map flag BPF_F_VERIFY_ELEM, and additionally call the new
function bpf_map_verify_value_sig() from bpf_map_update_value() if the flag
is set. bpf_map_verify_value_sig(), declared as global for a new helper, is
basically equivalent to mod_verify_sig(). It additionally does the marker
check, that for kernel modules is done in module_sig_check(), and the
parsing of the verified data+sig size.

Currently, enable the usage of the flag only for the array map. Support for
more map types can be added later.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 include/linux/bpf.h            |  7 ++++
 include/uapi/linux/bpf.h       |  3 ++
 kernel/bpf/arraymap.c          |  2 +-
 kernel/bpf/syscall.c           | 70 ++++++++++++++++++++++++++++++++++
 tools/include/uapi/linux/bpf.h |  3 ++
 5 files changed, 84 insertions(+), 1 deletion(-)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index a7080c86fa76..8f5c042e70a7 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1825,6 +1825,8 @@ static inline bool unprivileged_ebpf_enabled(void)
 	return !sysctl_unprivileged_bpf_disabled;
 }
 
+int bpf_map_verify_value_sig(const void *mod, size_t modlen, bool verify);
+
 #else /* !CONFIG_BPF_SYSCALL */
 static inline struct bpf_prog *bpf_prog_get(u32 ufd)
 {
@@ -2034,6 +2036,11 @@ static inline bool unprivileged_ebpf_enabled(void)
 	return false;
 }
 
+static inline int bpf_map_verify_value_sig(const void *mod, size_t modlen,
+					   bool verify)
+{
+	return -EOPNOTSUPP;
+}
 #endif /* CONFIG_BPF_SYSCALL */
 
 void __bpf_free_used_btfs(struct bpf_prog_aux *aux,
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
index f4009dbdf62d..a8e7803d2593 100644
--- a/include/uapi/linux/bpf.h
+++ b/include/uapi/linux/bpf.h
@@ -1226,6 +1226,9 @@ enum {
 
 /* Create a map that is suitable to be an inner map with dynamic max entries */
 	BPF_F_INNER_MAP		= (1U << 12),
+
+/* Verify map value (fmt: ver data+sig size(be32), ver data, sig, unver data) */
+	BPF_F_VERIFY_ELEM	= (1U << 13)
 };
 
 /* Flags for BPF_PROG_QUERY. */
diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
index fe40d3b9458f..b430fdd0e892 100644
--- a/kernel/bpf/arraymap.c
+++ b/kernel/bpf/arraymap.c
@@ -17,7 +17,7 @@
 
 #define ARRAY_CREATE_FLAG_MASK \
 	(BPF_F_NUMA_NODE | BPF_F_MMAPABLE | BPF_F_ACCESS_MASK | \
-	 BPF_F_PRESERVE_ELEMS | BPF_F_INNER_MAP)
+	 BPF_F_PRESERVE_ELEMS | BPF_F_INNER_MAP | BPF_F_VERIFY_ELEM)
 
 static void bpf_array_free_percpu(struct bpf_array *array)
 {
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 2b69306d3c6e..ca9e4a284120 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -35,6 +35,8 @@
 #include <linux/rcupdate_trace.h>
 #include <linux/memcontrol.h>
 #include <linux/trace_events.h>
+#include <linux/verification.h>
+#include <linux/module_signature.h>
 
 #define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
 			  (map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
@@ -180,6 +182,13 @@ static int bpf_map_update_value(struct bpf_map *map, struct fd f, void *key,
 {
 	int err;
 
+	if (map->map_flags & BPF_F_VERIFY_ELEM) {
+		err = bpf_map_verify_value_sig(value, bpf_map_value_size(map),
+					       true);
+		if (err < 0)
+			return err;
+	}
+
 	/* Need to create a kthread, thus must support schedule */
 	if (bpf_map_is_dev_bound(map)) {
 		return bpf_map_offload_update_elem(map, key, value, flags);
@@ -1057,6 +1066,11 @@ static int map_create(union bpf_attr *attr)
 	if (err)
 		return -EINVAL;
 
+	/* Allow signed data to go through update/push methods only. */
+	if ((attr->map_flags & BPF_F_VERIFY_ELEM) &&
+	    (attr->map_flags & BPF_F_MMAPABLE))
+		return -EINVAL;
+
 	if (attr->btf_vmlinux_value_type_id) {
 		if (attr->map_type != BPF_MAP_TYPE_STRUCT_OPS ||
 		    attr->btf_key_type_id || attr->btf_value_type_id)
@@ -1353,6 +1367,62 @@ static int map_lookup_elem(union bpf_attr *attr)
 	return err;
 }
 
+int bpf_map_verify_value_sig(const void *mod, size_t modlen, bool verify)
+{
+	const size_t marker_len = strlen(MODULE_SIG_STRING);
+	struct module_signature ms;
+	size_t sig_len;
+	u32 _modlen;
+	int ret;
+
+	/*
+	 * Format of mod:
+	 *
+	 * verified data+sig size (be32), verified data, sig, unverified data
+	 */
+	if (modlen <= sizeof(u32))
+		return -ENOENT;
+
+	_modlen = be32_to_cpu(*(u32 *)(mod));
+
+	if (_modlen > modlen - sizeof(u32))
+		return -EINVAL;
+
+	modlen = _modlen;
+	mod += sizeof(u32);
+
+	if (modlen <= marker_len)
+		return -ENOENT;
+
+	if (memcmp(mod + modlen - marker_len, MODULE_SIG_STRING, marker_len))
+		return -ENOENT;
+
+	modlen -= marker_len;
+
+	if (modlen <= sizeof(ms))
+		return -EBADMSG;
+
+	memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
+
+	ret = mod_check_sig(&ms, modlen, "bpf_map_value");
+	if (ret)
+		return ret;
+
+	sig_len = be32_to_cpu(ms.sig_len);
+	modlen -= sig_len + sizeof(ms);
+
+	if (verify) {
+		ret = verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
+					     VERIFY_USE_SECONDARY_KEYRING,
+					     VERIFYING_UNSPECIFIED_SIGNATURE,
+					     NULL, NULL);
+		if (ret < 0)
+			return ret;
+	}
+
+	return modlen;
+}
+EXPORT_SYMBOL_GPL(bpf_map_verify_value_sig);
 
 #define BPF_MAP_UPDATE_ELEM_LAST_FIELD flags
 
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index f4009dbdf62d..a8e7803d2593 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -1226,6 +1226,9 @@ enum {
 
 /* Create a map that is suitable to be an inner map with dynamic max entries */
 	BPF_F_INNER_MAP		= (1U << 12),
+
+/* Verify map value (fmt: ver data+sig size(be32), ver data, sig, unver data) */
+	BPF_F_VERIFY_ELEM	= (1U << 13)
 };
 
 /* Flags for BPF_PROG_QUERY. */
-- 
2.25.1


  reply	other threads:[~2022-05-25 13:21 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-05-25 13:21 [PATCH 0/3] bpf: Add support for maps with authenticated values Roberto Sassu
2022-05-25 13:21 ` Roberto Sassu [this message]
2022-05-25 16:51   ` [PATCH 1/3] bpf: Add BPF_F_VERIFY_ELEM to require signature verification on map values kernel test robot
2022-05-25 18:50   ` kernel test robot
2022-05-25 22:53   ` kernel test robot
2022-06-03 12:07   ` KP Singh
2022-06-03 13:11     ` Roberto Sassu
2022-06-03 15:17       ` KP Singh
2022-06-03 15:43         ` Roberto Sassu
2022-06-04  9:32           ` Alexei Starovoitov
2022-05-25 13:21 ` [PATCH 2/3] bpf: Introduce bpf_map_verified_data_size() helper Roberto Sassu
2022-05-25 13:21 ` [PATCH 3/3] bpf: Add tests for signed map values Roberto Sassu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220525132115.896698-2-roberto.sassu@huawei.com \
    --to=roberto.sassu@huawei.com \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=kpsingh@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).