diff mbox series

[v14,bpf-next,6/6] selftests/bpf: Add test that uses fsverity and xattr to sign a file

Message ID 20231129003656.1165061-7-song@kernel.org (mailing list archive)
State Superseded
Delegated to: Paul Moore
Headers show
Series bpf: File verification with LSM and fsverity | expand

Commit Message

Song Liu Nov. 29, 2023, 12:36 a.m. UTC
This selftests shows a proof of concept method to use BPF LSM to enforce
file signature. This test is added to verify_pkcs7_sig, so that some
existing logic can be reused.

This file signature method uses fsverity, which provides reliable and
efficient hash (known as digest) of the file. The file digest is signed
with asymmetic key, and the signature is stored in xattr. At the run time,
BPF LSM reads file digest and the signature, and then checks them against
the public key.

Note that this solution does NOT require FS_VERITY_BUILTIN_SIGNATURES.
fsverity is only used to provide file digest. The signature verification
and access control is all implemented in BPF LSM.

Signed-off-by: Song Liu <song@kernel.org>
---
 tools/testing/selftests/bpf/bpf_kfuncs.h      |   7 +
 .../bpf/prog_tests/verify_pkcs7_sig.c         | 163 +++++++++++++++++-
 .../selftests/bpf/progs/test_sig_in_xattr.c   |  82 +++++++++
 .../bpf/progs/test_verify_pkcs7_sig.c         |   8 +-
 .../testing/selftests/bpf/verify_sig_setup.sh |  25 +++
 5 files changed, 277 insertions(+), 8 deletions(-)
 create mode 100644 tools/testing/selftests/bpf/progs/test_sig_in_xattr.c

Comments

Alexei Starovoitov Nov. 29, 2023, 6:47 a.m. UTC | #1
On Tue, Nov 28, 2023 at 4:37 PM Song Liu <song@kernel.org> wrote:
> +char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];

when vmlinux is built without CONFIG_FS_VERITY the above fails
in a weird way:
  CLNG-BPF [test_maps] test_sig_in_xattr.bpf.o
progs/test_sig_in_xattr.c:36:26: error: invalid application of
'sizeof' to an incomplete type 'struct fsverity_digest'
   36 | char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) +
SHA256_DIGEST_SIZE];
      |                          ^     ~~~~~~~~~~~~~~~~~~~~~~~~

Is there a way to somehow print a hint during the build what
configs users need to enable to pass the build ?
Song Liu Nov. 29, 2023, 11:20 a.m. UTC | #2
On Tue, Nov 28, 2023 at 10:47 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Tue, Nov 28, 2023 at 4:37 PM Song Liu <song@kernel.org> wrote:
> > +char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];
>
> when vmlinux is built without CONFIG_FS_VERITY the above fails
> in a weird way:
>   CLNG-BPF [test_maps] test_sig_in_xattr.bpf.o
> progs/test_sig_in_xattr.c:36:26: error: invalid application of
> 'sizeof' to an incomplete type 'struct fsverity_digest'
>    36 | char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) +
> SHA256_DIGEST_SIZE];
>       |                          ^     ~~~~~~~~~~~~~~~~~~~~~~~~
>
> Is there a way to somehow print a hint during the build what
> configs users need to enable to pass the build ?

Patch 5/6 added CONFIG_FS_VERITY to tools/testing/selftests/bpf/config.
This is a more general question for all required CONFIG_* specified in the
file (and the config files for other selftests).

In selftests/bpf/Makefile, we have logic to find vmlinux. We can add similar
logic to find .config used to build the vmlinux, and grep for each required
CONFIG_* from the .config file. Does this sound like a viable solution?

Thanks,
Song
Alexei Starovoitov Nov. 29, 2023, 2:55 p.m. UTC | #3
On Wed, Nov 29, 2023 at 3:20 AM Song Liu <song@kernel.org> wrote:
>
> On Tue, Nov 28, 2023 at 10:47 PM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> >
> > On Tue, Nov 28, 2023 at 4:37 PM Song Liu <song@kernel.org> wrote:
> > > +char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];
> >
> > when vmlinux is built without CONFIG_FS_VERITY the above fails
> > in a weird way:
> >   CLNG-BPF [test_maps] test_sig_in_xattr.bpf.o
> > progs/test_sig_in_xattr.c:36:26: error: invalid application of
> > 'sizeof' to an incomplete type 'struct fsverity_digest'
> >    36 | char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) +
> > SHA256_DIGEST_SIZE];
> >       |                          ^     ~~~~~~~~~~~~~~~~~~~~~~~~
> >
> > Is there a way to somehow print a hint during the build what
> > configs users need to enable to pass the build ?
>
> Patch 5/6 added CONFIG_FS_VERITY to tools/testing/selftests/bpf/config.
> This is a more general question for all required CONFIG_* specified in the
> file (and the config files for other selftests).
>
> In selftests/bpf/Makefile, we have logic to find vmlinux. We can add similar
> logic to find .config used to build the vmlinux, and grep for each required
> CONFIG_* from the .config file. Does this sound like a viable solution?

No need for new logic to parse .config.
libbpf does it already and
extern bool CONFIG_FS_VERITY __kconfig __weak;
works.

Since you hard code MAGIC_SIZE anyway I'm asking
to hard code sizeof(struct fsverity_digest) as well, since the bpf prog
doesn't access it directly. It only needs to know its size.

While inside:
int BPF_PROG(test_file_open, struct file *f)
{
  if (!CONFIG_FS_VERITY) {
     skip_fs_verity_test = true;
     return 0;
  }

and report it as a clean error message in test_progs.

We keep adding new config requirements selftests/bpf/config which
forces all developers to keep adding new configs to their builds.
In the past, when we didn't have BPF CI, that was necessary, but now
BPF CI does it for us.
With clean error message from test_progs the developers can either
ignore the error and proceed with their work or adjust their .config
eventually. While hard selftest build error forces all devs to
update .config right away and build error has no info of what needs
to be done which is not developer friendly.

pw-bot: cr
Song Liu Nov. 29, 2023, 5:13 p.m. UTC | #4
On Wed, Nov 29, 2023 at 6:56 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Wed, Nov 29, 2023 at 3:20 AM Song Liu <song@kernel.org> wrote:
> >
> > On Tue, Nov 28, 2023 at 10:47 PM Alexei Starovoitov
> > <alexei.starovoitov@gmail.com> wrote:
> > >
> > > On Tue, Nov 28, 2023 at 4:37 PM Song Liu <song@kernel.org> wrote:
> > > > +char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];
> > >
> > > when vmlinux is built without CONFIG_FS_VERITY the above fails
> > > in a weird way:
> > >   CLNG-BPF [test_maps] test_sig_in_xattr.bpf.o
> > > progs/test_sig_in_xattr.c:36:26: error: invalid application of
> > > 'sizeof' to an incomplete type 'struct fsverity_digest'
> > >    36 | char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) +
> > > SHA256_DIGEST_SIZE];
> > >       |                          ^     ~~~~~~~~~~~~~~~~~~~~~~~~
> > >
> > > Is there a way to somehow print a hint during the build what
> > > configs users need to enable to pass the build ?
> >
> > Patch 5/6 added CONFIG_FS_VERITY to tools/testing/selftests/bpf/config.
> > This is a more general question for all required CONFIG_* specified in the
> > file (and the config files for other selftests).
> >
> > In selftests/bpf/Makefile, we have logic to find vmlinux. We can add similar
> > logic to find .config used to build the vmlinux, and grep for each required
> > CONFIG_* from the .config file. Does this sound like a viable solution?
>
> No need for new logic to parse .config.
> libbpf does it already and
> extern bool CONFIG_FS_VERITY __kconfig __weak;
> works.
>
> Since you hard code MAGIC_SIZE anyway I'm asking
> to hard code sizeof(struct fsverity_digest) as well, since the bpf prog
> doesn't access it directly. It only needs to know its size.
>
> While inside:
> int BPF_PROG(test_file_open, struct file *f)
> {
>   if (!CONFIG_FS_VERITY) {
>      skip_fs_verity_test = true;
>      return 0;
>   }
>
> and report it as a clean error message in test_progs.

Yeah, this makes sense. Let me update the tests.

Thanks,
Song

> We keep adding new config requirements selftests/bpf/config which
> forces all developers to keep adding new configs to their builds.
> In the past, when we didn't have BPF CI, that was necessary, but now
> BPF CI does it for us.
> With clean error message from test_progs the developers can either
> ignore the error and proceed with their work or adjust their .config
> eventually. While hard selftest build error forces all devs to
> update .config right away and build error has no info of what needs
> to be done which is not developer friendly.
>
> pw-bot: cr
>
Song Liu Nov. 29, 2023, 5:58 p.m. UTC | #5
On Wed, Nov 29, 2023 at 9:13 AM Song Liu <song@kernel.org> wrote:
>
> On Wed, Nov 29, 2023 at 6:56 AM Alexei Starovoitov
> <alexei.starovoitov@gmail.com> wrote:
> >
> > On Wed, Nov 29, 2023 at 3:20 AM Song Liu <song@kernel.org> wrote:
> > >
> > > On Tue, Nov 28, 2023 at 10:47 PM Alexei Starovoitov
> > > <alexei.starovoitov@gmail.com> wrote:
> > > >
> > > > On Tue, Nov 28, 2023 at 4:37 PM Song Liu <song@kernel.org> wrote:
> > > > > +char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];
> > > >
> > > > when vmlinux is built without CONFIG_FS_VERITY the above fails
> > > > in a weird way:
> > > >   CLNG-BPF [test_maps] test_sig_in_xattr.bpf.o
> > > > progs/test_sig_in_xattr.c:36:26: error: invalid application of
> > > > 'sizeof' to an incomplete type 'struct fsverity_digest'
> > > >    36 | char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) +
> > > > SHA256_DIGEST_SIZE];
> > > >       |                          ^     ~~~~~~~~~~~~~~~~~~~~~~~~
> > > >
> > > > Is there a way to somehow print a hint during the build what
> > > > configs users need to enable to pass the build ?
> > >
> > > Patch 5/6 added CONFIG_FS_VERITY to tools/testing/selftests/bpf/config.
> > > This is a more general question for all required CONFIG_* specified in the
> > > file (and the config files for other selftests).
> > >
> > > In selftests/bpf/Makefile, we have logic to find vmlinux. We can add similar
> > > logic to find .config used to build the vmlinux, and grep for each required
> > > CONFIG_* from the .config file. Does this sound like a viable solution?
> >
> > No need for new logic to parse .config.
> > libbpf does it already and
> > extern bool CONFIG_FS_VERITY __kconfig __weak;
> > works.
> >
> > Since you hard code MAGIC_SIZE anyway I'm asking
> > to hard code sizeof(struct fsverity_digest) as well, since the bpf prog
> > doesn't access it directly. It only needs to know its size.
> >
> > While inside:
> > int BPF_PROG(test_file_open, struct file *f)
> > {
> >   if (!CONFIG_FS_VERITY) {
> >      skip_fs_verity_test = true;
> >      return 0;
> >   }
> >
> > and report it as a clean error message in test_progs.
>
> Yeah, this makes sense. Let me update the tests.

Actually, it is easier. We already have skip-test logic for cases
where FS verity is not supported (as we need to enable it in
vmlinux and enable it per filesystem). So we only need to hard
code sizeof(struct fsverity_digest).

Thanks,
Song
kernel test robot Nov. 30, 2023, 4:02 a.m. UTC | #6
Hi Song,

kernel test robot noticed the following build errors:

[auto build test ERROR on bpf-next/master]

url:    https://github.com/intel-lab-lkp/linux/commits/Song-Liu/bpf-Add-kfunc-bpf_get_file_xattr/20231129-084133
base:   https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master
patch link:    https://lore.kernel.org/r/20231129003656.1165061-7-song%40kernel.org
patch subject: [PATCH v14 bpf-next 6/6] selftests/bpf: Add test that uses fsverity and xattr to sign a file
compiler: gcc-12 (Debian 12.2.0-14) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231130/202311301002.mGJ2mc2v-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202311301002.mGJ2mc2v-lkp@intel.com/

All errors (new ones prefixed by >>):

>> progs/test_sig_in_xattr.c:36:26: error: invalid application of 'sizeof' to an incomplete type 'struct fsverity_digest'
      36 | char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];
         |                          ^     ~~~~~~~~~~~~~~~~~~~~~~~~
   progs/test_sig_in_xattr.c:36:40: note: forward declaration of 'struct fsverity_digest'
      36 | char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];
         |                                        ^
   1 error generated.
diff mbox series

Patch

diff --git a/tools/testing/selftests/bpf/bpf_kfuncs.h b/tools/testing/selftests/bpf/bpf_kfuncs.h
index c2c084a44eae..b4e78c1eb37b 100644
--- a/tools/testing/selftests/bpf/bpf_kfuncs.h
+++ b/tools/testing/selftests/bpf/bpf_kfuncs.h
@@ -58,4 +58,11 @@  void *bpf_rdonly_cast(void *obj, __u32 btf_id) __ksym;
 extern int bpf_get_file_xattr(struct file *file, const char *name,
 			      struct bpf_dynptr *value_ptr) __ksym;
 extern int bpf_get_fsverity_digest(struct file *file, struct bpf_dynptr *digest_ptr) __ksym;
+
+extern struct bpf_key *bpf_lookup_user_key(__u32 serial, __u64 flags) __ksym;
+extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
+extern void bpf_key_put(struct bpf_key *key) __ksym;
+extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
+				      struct bpf_dynptr *sig_ptr,
+				      struct bpf_key *trusted_keyring) __ksym;
 #endif
diff --git a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
index dd7f2bc70048..682b6af8d0a4 100644
--- a/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
+++ b/tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c
@@ -16,9 +16,12 @@ 
 #include <sys/wait.h>
 #include <sys/mman.h>
 #include <linux/keyctl.h>
+#include <sys/xattr.h>
+#include <linux/fsverity.h>
 #include <test_progs.h>
 
 #include "test_verify_pkcs7_sig.skel.h"
+#include "test_sig_in_xattr.skel.h"
 
 #define MAX_DATA_SIZE (1024 * 1024)
 #define MAX_SIG_SIZE 1024
@@ -26,6 +29,10 @@ 
 #define VERIFY_USE_SECONDARY_KEYRING (1UL)
 #define VERIFY_USE_PLATFORM_KEYRING  (2UL)
 
+#ifndef SHA256_DIGEST_SIZE
+#define SHA256_DIGEST_SIZE      32
+#endif
+
 /* In stripped ARM and x86-64 modules, ~ is surprisingly rare. */
 #define MODULE_SIG_STRING "~Module signature appended~\n"
 
@@ -254,7 +261,7 @@  static int populate_data_item_mod(struct data *data_item)
 	return ret;
 }
 
-void test_verify_pkcs7_sig(void)
+static void test_verify_pkcs7_sig_from_map(void)
 {
 	libbpf_print_fn_t old_print_cb;
 	char tmp_dir_template[] = "/tmp/verify_sigXXXXXX";
@@ -400,3 +407,157 @@  void test_verify_pkcs7_sig(void)
 	skel->bss->monitored_pid = 0;
 	test_verify_pkcs7_sig__destroy(skel);
 }
+
+static int get_signature_size(const char *sig_path)
+{
+	struct stat st;
+
+	if (stat(sig_path, &st) == -1)
+		return -1;
+
+	return st.st_size;
+}
+
+static int add_signature_to_xattr(const char *data_path, const char *sig_path)
+{
+	char sig[MAX_SIG_SIZE] = {0};
+	int fd, size, ret;
+
+	if (sig_path) {
+		fd = open(sig_path, O_RDONLY);
+		if (fd < 0)
+			return -1;
+
+		size = read(fd, sig, MAX_SIG_SIZE);
+		close(fd);
+		if (size <= 0)
+			return -1;
+	} else {
+		/* no sig_path, just write 32 bytes of zeros */
+		size = 32;
+	}
+	ret = setxattr(data_path, "user.sig", sig, size, 0);
+	if (!ASSERT_OK(ret, "setxattr"))
+		return -1;
+
+	return 0;
+}
+
+static int test_open_file(struct test_sig_in_xattr *skel, char *data_path,
+			  pid_t pid, bool should_success, char *name)
+{
+	int ret;
+
+	skel->bss->monitored_pid = pid;
+	ret = open(data_path, O_RDONLY);
+	close(ret);
+	skel->bss->monitored_pid = 0;
+
+	if (should_success) {
+		if (!ASSERT_GE(ret, 0, name))
+			return -1;
+	} else {
+		if (!ASSERT_LT(ret, 0, name))
+			return -1;
+	}
+	return 0;
+}
+
+static void test_pkcs7_sig_fsverity(void)
+{
+	char data_path[PATH_MAX];
+	char sig_path[PATH_MAX];
+	char tmp_dir_template[] = "/tmp/verify_sigXXXXXX";
+	char *tmp_dir;
+	struct test_sig_in_xattr *skel = NULL;
+	pid_t pid;
+	int ret;
+
+	tmp_dir = mkdtemp(tmp_dir_template);
+	if (!ASSERT_OK_PTR(tmp_dir, "mkdtemp"))
+		return;
+
+	snprintf(data_path, PATH_MAX, "%s/data-file", tmp_dir);
+	snprintf(sig_path, PATH_MAX, "%s/sig-file", tmp_dir);
+
+	ret = _run_setup_process(tmp_dir, "setup");
+	if (!ASSERT_OK(ret, "_run_setup_process"))
+		goto out;
+
+	ret = _run_setup_process(tmp_dir, "fsverity-create-sign");
+
+	if (ret) {
+		printf("%s: SKIP: fsverity [sign|enable] doesn't work.\n", __func__);
+		test__skip();
+		goto out;
+	}
+
+	skel = test_sig_in_xattr__open();
+	if (!ASSERT_OK_PTR(skel, "test_sig_in_xattr__open"))
+		goto out;
+	ret = get_signature_size(sig_path);
+	if (!ASSERT_GT(ret, 0, "get_signaure_size"))
+		goto out;
+	skel->bss->sig_size = ret;
+	skel->bss->user_keyring_serial = syscall(__NR_request_key, "keyring",
+						 "ebpf_testing_keyring", NULL,
+						 KEY_SPEC_SESSION_KEYRING);
+	memcpy(skel->bss->digest, "FSVerity", 8);
+
+	ret = test_sig_in_xattr__load(skel);
+	if (!ASSERT_OK(ret, "test_sig_in_xattr__load"))
+		goto out;
+
+	ret = test_sig_in_xattr__attach(skel);
+	if (!ASSERT_OK(ret, "test_sig_in_xattr__attach"))
+		goto out;
+
+	pid = getpid();
+
+	/* Case 1: fsverity is not enabled, open should succeed */
+	if (test_open_file(skel, data_path, pid, true, "open_1"))
+		goto out;
+
+	/* Case 2: fsverity is enabled, xattr is missing, open should
+	 * fail
+	 */
+	ret = _run_setup_process(tmp_dir, "fsverity-enable");
+	if (!ASSERT_OK(ret, "fsverity-enable"))
+		goto out;
+	if (test_open_file(skel, data_path, pid, false, "open_2"))
+		goto out;
+
+	/* Case 3: fsverity is enabled, xattr has valid signature, open
+	 * should succeed
+	 */
+	ret = add_signature_to_xattr(data_path, sig_path);
+	if (!ASSERT_OK(ret, "add_signature_to_xattr_1"))
+		goto out;
+
+	if (test_open_file(skel, data_path, pid, true, "open_3"))
+		goto out;
+
+	/* Case 4: fsverity is enabled, xattr has invalid signature, open
+	 * should fail
+	 */
+	ret = add_signature_to_xattr(data_path, NULL);
+	if (!ASSERT_OK(ret, "add_signature_to_xattr_2"))
+		goto out;
+	test_open_file(skel, data_path, pid, false, "open_4");
+
+out:
+	_run_setup_process(tmp_dir, "cleanup");
+	if (!skel)
+		return;
+
+	skel->bss->monitored_pid = 0;
+	test_sig_in_xattr__destroy(skel);
+}
+
+void test_verify_pkcs7_sig(void)
+{
+	if (test__start_subtest("pkcs7_sig_from_map"))
+		test_verify_pkcs7_sig_from_map();
+	if (test__start_subtest("pkcs7_sig_fsverity"))
+		test_pkcs7_sig_fsverity();
+}
diff --git a/tools/testing/selftests/bpf/progs/test_sig_in_xattr.c b/tools/testing/selftests/bpf/progs/test_sig_in_xattr.c
new file mode 100644
index 000000000000..820b891171d8
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_sig_in_xattr.c
@@ -0,0 +1,82 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
+
+#include "vmlinux.h"
+#include <errno.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
+
+char _license[] SEC("license") = "GPL";
+
+#ifndef SHA256_DIGEST_SIZE
+#define SHA256_DIGEST_SIZE      32
+#endif
+
+#define MAX_SIG_SIZE 1024
+
+/* By default, "fsverity sign" signs a file with fsverity_formatted_digest
+ * of the file. fsverity_formatted_digest on the kernel side is only used
+ * with CONFIG_FS_VERITY_BUILTIN_SIGNATURES. However, BPF LSM doesn't not
+ * require CONFIG_FS_VERITY_BUILTIN_SIGNATURES, so vmlinux.h may not have
+ * fsverity_formatted_digest. In this test, we intentionally avoid using
+ * fsverity_formatted_digest.
+ *
+ * Luckily, fsverity_formatted_digest is simply 8-byte magic followed by
+ * fsverity_digest. We use a char array of size fsverity_formatted_digest
+ * plus SHA256_DIGEST_SIZE. The magic part of it is filled by user space,
+ * and the rest of it is filled by bpf_get_fsverity_digest.
+ *
+ * Note that, generating signatures based on fsverity_formatted_digest is
+ * the design choice of this selftest (and "fsverity sign"). With BPF
+ * LSM, we have the flexibility to generate signature based on other data
+ * sets, for example, fsverity_digest or only the digest[] part of it.
+ */
+#define MAGIC_SIZE 8
+char digest[MAGIC_SIZE + sizeof(struct fsverity_digest) + SHA256_DIGEST_SIZE];
+
+__u32 monitored_pid;
+char sig[MAX_SIG_SIZE];
+__u32 sig_size;
+__u32 user_keyring_serial;
+
+SEC("lsm.s/file_open")
+int BPF_PROG(test_file_open, struct file *f)
+{
+	struct bpf_dynptr digest_ptr, sig_ptr;
+	struct bpf_key *trusted_keyring;
+	__u32 pid;
+	int ret;
+
+	pid = bpf_get_current_pid_tgid() >> 32;
+	if (pid != monitored_pid)
+		return 0;
+
+	/* digest_ptr points to fsverity_digest */
+	bpf_dynptr_from_mem(digest + MAGIC_SIZE, sizeof(digest) - MAGIC_SIZE, 0, &digest_ptr);
+
+	ret = bpf_get_fsverity_digest(f, &digest_ptr);
+	/* No verity, allow access */
+	if (ret < 0)
+		return 0;
+
+	/* Move digest_ptr to fsverity_formatted_digest */
+	bpf_dynptr_from_mem(digest, sizeof(digest), 0, &digest_ptr);
+
+	/* Read signature from xattr */
+	bpf_dynptr_from_mem(sig, sizeof(sig), 0, &sig_ptr);
+	ret = bpf_get_file_xattr(f, "user.sig", &sig_ptr);
+	/* No signature, reject access */
+	if (ret < 0)
+		return -EPERM;
+
+	trusted_keyring = bpf_lookup_user_key(user_keyring_serial, 0);
+	if (!trusted_keyring)
+		return -ENOENT;
+
+	/* Verify signature */
+	ret = bpf_verify_pkcs7_signature(&digest_ptr, &sig_ptr, trusted_keyring);
+
+	bpf_key_put(trusted_keyring);
+	return ret;
+}
diff --git a/tools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c b/tools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c
index 7748cc23de8a..f42e9f3831a1 100644
--- a/tools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c
+++ b/tools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c
@@ -10,17 +10,11 @@ 
 #include <errno.h>
 #include <bpf/bpf_helpers.h>
 #include <bpf/bpf_tracing.h>
+#include "bpf_kfuncs.h"
 
 #define MAX_DATA_SIZE (1024 * 1024)
 #define MAX_SIG_SIZE 1024
 
-extern struct bpf_key *bpf_lookup_user_key(__u32 serial, __u64 flags) __ksym;
-extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
-extern void bpf_key_put(struct bpf_key *key) __ksym;
-extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
-				      struct bpf_dynptr *sig_ptr,
-				      struct bpf_key *trusted_keyring) __ksym;
-
 __u32 monitored_pid;
 __u32 user_keyring_serial;
 __u64 system_keyring_id;
diff --git a/tools/testing/selftests/bpf/verify_sig_setup.sh b/tools/testing/selftests/bpf/verify_sig_setup.sh
index ba08922b4a27..f2cac42298ba 100755
--- a/tools/testing/selftests/bpf/verify_sig_setup.sh
+++ b/tools/testing/selftests/bpf/verify_sig_setup.sh
@@ -60,6 +60,27 @@  cleanup() {
 	rm -rf ${tmp_dir}
 }
 
+fsverity_create_sign_file() {
+	local tmp_dir="$1"
+
+	data_file=${tmp_dir}/data-file
+	sig_file=${tmp_dir}/sig-file
+	dd if=/dev/urandom of=$data_file bs=1 count=12345 2> /dev/null
+	fsverity sign --key ${tmp_dir}/signing_key.pem $data_file $sig_file
+
+	# We do not want to enable fsverity on $data_file yet. Try whether
+	# the file system support fsverity on a different file.
+	touch ${tmp_dir}/tmp-file
+	fsverity enable ${tmp_dir}/tmp-file
+}
+
+fsverity_enable_file() {
+	local tmp_dir="$1"
+
+	data_file=${tmp_dir}/data-file
+	fsverity enable $data_file
+}
+
 catch()
 {
 	local exit_code="$1"
@@ -86,6 +107,10 @@  main()
 		setup "${tmp_dir}"
 	elif [[ "${action}" == "cleanup" ]]; then
 		cleanup "${tmp_dir}"
+	elif [[ "${action}" == "fsverity-create-sign" ]]; then
+		fsverity_create_sign_file "${tmp_dir}"
+	elif [[ "${action}" == "fsverity-enable" ]]; then
+		fsverity_enable_file "${tmp_dir}"
 	else
 		echo "Unknown action: ${action}"
 		exit 1