[PATCH 07/12] bpf: Return hashes of maps in BPF_OBJ_GET_INFO_BY_FD

Alexei Starovoitov alexei.starovoitov at gmail.com
Mon Jun 9 21:30:32 UTC 2025


On Fri, Jun 6, 2025 at 4:29 PM KP Singh <kpsingh at kernel.org> wrote:
>
> Currently only array maps are supported, but the implementation can be
> extended for other maps and objects. The hash is memoized only for
> exclusive and frozen maps as their content is stable until the exclusive
> program modifies the map.
>
> This is required  for BPF signing, enabling a trusted loader program to
> verify a map's integrity. The loader retrieves
> the map's runtime hash from the kernel and compares it against an
> expected hash computed at build time.
>
> Signed-off-by: KP Singh <kpsingh at kernel.org>
> ---
>  include/linux/bpf.h            |  3 +++
>  include/uapi/linux/bpf.h       |  2 ++
>  kernel/bpf/arraymap.c          | 13 ++++++++++++
>  kernel/bpf/syscall.c           | 38 ++++++++++++++++++++++++++++++++++
>  tools/include/uapi/linux/bpf.h |  2 ++
>  5 files changed, 58 insertions(+)
>
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index cb1bea99702a..35f1a633d87a 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -7,6 +7,7 @@
>  #include <uapi/linux/bpf.h>
>  #include <uapi/linux/filter.h>
>
> +#include <crypto/sha2.h>
>  #include <linux/workqueue.h>
>  #include <linux/file.h>
>  #include <linux/percpu.h>
> @@ -110,6 +111,7 @@ struct bpf_map_ops {
>         long (*map_pop_elem)(struct bpf_map *map, void *value);
>         long (*map_peek_elem)(struct bpf_map *map, void *value);
>         void *(*map_lookup_percpu_elem)(struct bpf_map *map, void *key, u32 cpu);
> +       int (*map_get_hash)(struct bpf_map *map, u32 hash_buf_size, void *hash_buf);
>
>         /* funcs called by prog_array and perf_event_array map */
>         void *(*map_fd_get_ptr)(struct bpf_map *map, struct file *map_file,
> @@ -262,6 +264,7 @@ struct bpf_list_node_kern {
>  } __attribute__((aligned(8)));
>
>  struct bpf_map {
> +       u8 sha[SHA256_DIGEST_SIZE];
>         const struct bpf_map_ops *ops;
>         struct bpf_map *inner_map_meta;
>  #ifdef CONFIG_SECURITY
> diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
> index 6f2f4f3b3822..ffd9e11befc2 100644
> --- a/include/uapi/linux/bpf.h
> +++ b/include/uapi/linux/bpf.h
> @@ -6630,6 +6630,8 @@ struct bpf_map_info {
>         __u32 btf_value_type_id;
>         __u32 btf_vmlinux_id;
>         __u64 map_extra;
> +       __aligned_u64 hash;
> +       __u32 hash_size;
>  } __attribute__((aligned(8)));
>
>  struct bpf_btf_info {
> diff --git a/kernel/bpf/arraymap.c b/kernel/bpf/arraymap.c
> index 8719aa821b63..1fb989db03a2 100644
> --- a/kernel/bpf/arraymap.c
> +++ b/kernel/bpf/arraymap.c
> @@ -12,6 +12,7 @@
>  #include <uapi/linux/btf.h>
>  #include <linux/rcupdate_trace.h>
>  #include <linux/btf_ids.h>
> +#include <crypto/sha256_base.h>
>
>  #include "map_in_map.h"
>
> @@ -174,6 +175,17 @@ static void *array_map_lookup_elem(struct bpf_map *map, void *key)
>         return array->value + (u64)array->elem_size * (index & array->index_mask);
>  }
>
> +static int array_map_get_hash(struct bpf_map *map, u32 hash_buf_size,
> +                              void *hash_buf)
> +{
> +       struct bpf_array *array = container_of(map, struct bpf_array, map);
> +
> +       bpf_sha256(array->value, (u64)array->elem_size * array->map.max_entries,
> +              hash_buf);
> +       memcpy(array->map.sha, hash_buf, sizeof(array->map.sha));
> +       return 0;
> +}
> +
>  static int array_map_direct_value_addr(const struct bpf_map *map, u64 *imm,
>                                        u32 off)
>  {
> @@ -805,6 +817,7 @@ const struct bpf_map_ops array_map_ops = {
>         .map_mem_usage = array_map_mem_usage,
>         .map_btf_id = &array_map_btf_ids[0],
>         .iter_seq_info = &iter_seq_info,
> +       .map_get_hash = &array_map_get_hash,
>  };
>
>  const struct bpf_map_ops percpu_array_map_ops = {
> diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
> index bef9edcfdb76..c81be07fa4fa 100644
> --- a/kernel/bpf/syscall.c
> +++ b/kernel/bpf/syscall.c
> @@ -1,6 +1,7 @@
>  // SPDX-License-Identifier: GPL-2.0-only
>  /* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com
>   */
> +#include <crypto/sha2.h>
>  #include <linux/bpf.h>
>  #include <linux/bpf-cgroup.h>
>  #include <linux/bpf_trace.h>
> @@ -5027,6 +5028,9 @@ static int bpf_map_get_info_by_fd(struct file *file,
>         info_len = min_t(u32, sizeof(info), info_len);
>
>         memset(&info, 0, sizeof(info));
> +       if (copy_from_user(&info, uinfo, info_len))
> +               return -EFAULT;
> +
>         info.type = map->map_type;
>         info.id = map->id;
>         info.key_size = map->key_size;
> @@ -5051,6 +5055,40 @@ static int bpf_map_get_info_by_fd(struct file *file,
>                         return err;
>         }
>
> +       if (map->ops->map_get_hash && map->frozen && map->excl_prog_sha) {
> +               err = map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, &map->sha);

& in &map->sha looks suspicious. Should be just map->sha ?

> +               if (err != 0)
> +                       return err;
> +       }
> +
> +       if (info.hash) {
> +               char __user *uhash = u64_to_user_ptr(info.hash);
> +
> +               if (!map->ops->map_get_hash)
> +                       return -EINVAL;
> +
> +               if (info.hash_size < SHA256_DIGEST_SIZE)

Similar to prog let's == here?

> +                       return -EINVAL;
> +
> +               info.hash_size  = SHA256_DIGEST_SIZE;
> +
> +               if (map->excl_prog_sha && map->frozen) {
> +                       if (copy_to_user(uhash, map->sha, SHA256_DIGEST_SIZE) !=
> +                           0)
> +                               return -EFAULT;

I would drop above and keep below part only.

> +               } else {
> +                       u8 sha[SHA256_DIGEST_SIZE];
> +
> +                       err = map->ops->map_get_hash(map, SHA256_DIGEST_SIZE,
> +                                                    sha);

Here the kernel can write into map->sha and then copy it to uhash.
I think the concern was to disallow 2nd map_get_hash on exclusive
and frozen map, right?
But I think that won't be an issue for signed lskel loader.
Since the map is frozen the user space cannot modify it.
Since the map is exclusive another bpf prog cannot modify it.
If user space calls map_get_hash 2nd time the sha will be
exactly the same until loader prog writes into the map.
So I see no harm generalizing this bit of code.
I don't have a particular use case in mind,
but it seems fine to allow user space to recompute sha
of exclusive and frozen map.
The loader will check the sha of its map as the very first operation,
so if user space did two map_get_hash() it just wasted cpu cycles.
If user space is calling map_get_hash() while loader prog
reads and writes into it the map->sha will change, but
it doesn't matter to the loader program anymore.

Also I wouldn't special case the !info.hash case for exclusive maps.
It seems cleaner to waste few bytes on stack in
skel_obj_get_info_by_fd() later in patch 9.
Let it point to valid u8 sha[] on stack.
The skel won't use it, but this way we can kernel behavior
consistent.
if info.hash != NULL -> compute sha, update map->sha, copy to user space.



More information about the Linux-security-module-archive mailing list