Skip to content

Commit 1030e91

Browse files
liu-song-6Alexei Starovoitov
authored andcommitted
selftests/bpf: Add test that uses fsverity and xattr to sign a file
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 <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Alexei Starovoitov <[email protected]>
1 parent 341f06f commit 1030e91

File tree

5 files changed

+280
-8
lines changed

5 files changed

+280
-8
lines changed

tools/testing/selftests/bpf/bpf_kfuncs.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,11 @@ void *bpf_rdonly_cast(void *obj, __u32 btf_id) __ksym;
5858
extern int bpf_get_file_xattr(struct file *file, const char *name,
5959
struct bpf_dynptr *value_ptr) __ksym;
6060
extern int bpf_get_fsverity_digest(struct file *file, struct bpf_dynptr *digest_ptr) __ksym;
61+
62+
extern struct bpf_key *bpf_lookup_user_key(__u32 serial, __u64 flags) __ksym;
63+
extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
64+
extern void bpf_key_put(struct bpf_key *key) __ksym;
65+
extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
66+
struct bpf_dynptr *sig_ptr,
67+
struct bpf_key *trusted_keyring) __ksym;
6168
#endif

tools/testing/selftests/bpf/prog_tests/verify_pkcs7_sig.c

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,23 @@
1616
#include <sys/wait.h>
1717
#include <sys/mman.h>
1818
#include <linux/keyctl.h>
19+
#include <sys/xattr.h>
20+
#include <linux/fsverity.h>
1921
#include <test_progs.h>
2022

2123
#include "test_verify_pkcs7_sig.skel.h"
24+
#include "test_sig_in_xattr.skel.h"
2225

2326
#define MAX_DATA_SIZE (1024 * 1024)
2427
#define MAX_SIG_SIZE 1024
2528

2629
#define VERIFY_USE_SECONDARY_KEYRING (1UL)
2730
#define VERIFY_USE_PLATFORM_KEYRING (2UL)
2831

32+
#ifndef SHA256_DIGEST_SIZE
33+
#define SHA256_DIGEST_SIZE 32
34+
#endif
35+
2936
/* In stripped ARM and x86-64 modules, ~ is surprisingly rare. */
3037
#define MODULE_SIG_STRING "~Module signature appended~\n"
3138

@@ -254,7 +261,7 @@ static int populate_data_item_mod(struct data *data_item)
254261
return ret;
255262
}
256263

257-
void test_verify_pkcs7_sig(void)
264+
static void test_verify_pkcs7_sig_from_map(void)
258265
{
259266
libbpf_print_fn_t old_print_cb;
260267
char tmp_dir_template[] = "/tmp/verify_sigXXXXXX";
@@ -400,3 +407,159 @@ void test_verify_pkcs7_sig(void)
400407
skel->bss->monitored_pid = 0;
401408
test_verify_pkcs7_sig__destroy(skel);
402409
}
410+
411+
static int get_signature_size(const char *sig_path)
412+
{
413+
struct stat st;
414+
415+
if (stat(sig_path, &st) == -1)
416+
return -1;
417+
418+
return st.st_size;
419+
}
420+
421+
static int add_signature_to_xattr(const char *data_path, const char *sig_path)
422+
{
423+
char sig[MAX_SIG_SIZE] = {0};
424+
int fd, size, ret;
425+
426+
if (sig_path) {
427+
fd = open(sig_path, O_RDONLY);
428+
if (fd < 0)
429+
return -1;
430+
431+
size = read(fd, sig, MAX_SIG_SIZE);
432+
close(fd);
433+
if (size <= 0)
434+
return -1;
435+
} else {
436+
/* no sig_path, just write 32 bytes of zeros */
437+
size = 32;
438+
}
439+
ret = setxattr(data_path, "user.sig", sig, size, 0);
440+
if (!ASSERT_OK(ret, "setxattr"))
441+
return -1;
442+
443+
return 0;
444+
}
445+
446+
static int test_open_file(struct test_sig_in_xattr *skel, char *data_path,
447+
pid_t pid, bool should_success, char *name)
448+
{
449+
int ret;
450+
451+
skel->bss->monitored_pid = pid;
452+
ret = open(data_path, O_RDONLY);
453+
close(ret);
454+
skel->bss->monitored_pid = 0;
455+
456+
if (should_success) {
457+
if (!ASSERT_GE(ret, 0, name))
458+
return -1;
459+
} else {
460+
if (!ASSERT_LT(ret, 0, name))
461+
return -1;
462+
}
463+
return 0;
464+
}
465+
466+
static void test_pkcs7_sig_fsverity(void)
467+
{
468+
char data_path[PATH_MAX];
469+
char sig_path[PATH_MAX];
470+
char tmp_dir_template[] = "/tmp/verify_sigXXXXXX";
471+
char *tmp_dir;
472+
struct test_sig_in_xattr *skel = NULL;
473+
pid_t pid;
474+
int ret;
475+
476+
tmp_dir = mkdtemp(tmp_dir_template);
477+
if (!ASSERT_OK_PTR(tmp_dir, "mkdtemp"))
478+
return;
479+
480+
snprintf(data_path, PATH_MAX, "%s/data-file", tmp_dir);
481+
snprintf(sig_path, PATH_MAX, "%s/sig-file", tmp_dir);
482+
483+
ret = _run_setup_process(tmp_dir, "setup");
484+
if (!ASSERT_OK(ret, "_run_setup_process"))
485+
goto out;
486+
487+
ret = _run_setup_process(tmp_dir, "fsverity-create-sign");
488+
489+
if (ret) {
490+
printf("%s: SKIP: fsverity [sign|enable] doesn't work.\n"
491+
"To run this test, try enable CONFIG_FS_VERITY and enable FSVerity for the filesystem.\n",
492+
__func__);
493+
test__skip();
494+
goto out;
495+
}
496+
497+
skel = test_sig_in_xattr__open();
498+
if (!ASSERT_OK_PTR(skel, "test_sig_in_xattr__open"))
499+
goto out;
500+
ret = get_signature_size(sig_path);
501+
if (!ASSERT_GT(ret, 0, "get_signaure_size"))
502+
goto out;
503+
skel->bss->sig_size = ret;
504+
skel->bss->user_keyring_serial = syscall(__NR_request_key, "keyring",
505+
"ebpf_testing_keyring", NULL,
506+
KEY_SPEC_SESSION_KEYRING);
507+
memcpy(skel->bss->digest, "FSVerity", 8);
508+
509+
ret = test_sig_in_xattr__load(skel);
510+
if (!ASSERT_OK(ret, "test_sig_in_xattr__load"))
511+
goto out;
512+
513+
ret = test_sig_in_xattr__attach(skel);
514+
if (!ASSERT_OK(ret, "test_sig_in_xattr__attach"))
515+
goto out;
516+
517+
pid = getpid();
518+
519+
/* Case 1: fsverity is not enabled, open should succeed */
520+
if (test_open_file(skel, data_path, pid, true, "open_1"))
521+
goto out;
522+
523+
/* Case 2: fsverity is enabled, xattr is missing, open should
524+
* fail
525+
*/
526+
ret = _run_setup_process(tmp_dir, "fsverity-enable");
527+
if (!ASSERT_OK(ret, "fsverity-enable"))
528+
goto out;
529+
if (test_open_file(skel, data_path, pid, false, "open_2"))
530+
goto out;
531+
532+
/* Case 3: fsverity is enabled, xattr has valid signature, open
533+
* should succeed
534+
*/
535+
ret = add_signature_to_xattr(data_path, sig_path);
536+
if (!ASSERT_OK(ret, "add_signature_to_xattr_1"))
537+
goto out;
538+
539+
if (test_open_file(skel, data_path, pid, true, "open_3"))
540+
goto out;
541+
542+
/* Case 4: fsverity is enabled, xattr has invalid signature, open
543+
* should fail
544+
*/
545+
ret = add_signature_to_xattr(data_path, NULL);
546+
if (!ASSERT_OK(ret, "add_signature_to_xattr_2"))
547+
goto out;
548+
test_open_file(skel, data_path, pid, false, "open_4");
549+
550+
out:
551+
_run_setup_process(tmp_dir, "cleanup");
552+
if (!skel)
553+
return;
554+
555+
skel->bss->monitored_pid = 0;
556+
test_sig_in_xattr__destroy(skel);
557+
}
558+
559+
void test_verify_pkcs7_sig(void)
560+
{
561+
if (test__start_subtest("pkcs7_sig_from_map"))
562+
test_verify_pkcs7_sig_from_map();
563+
if (test__start_subtest("pkcs7_sig_fsverity"))
564+
test_pkcs7_sig_fsverity();
565+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
3+
4+
#include "vmlinux.h"
5+
#include <errno.h>
6+
#include <bpf/bpf_helpers.h>
7+
#include <bpf/bpf_tracing.h>
8+
#include "bpf_kfuncs.h"
9+
10+
char _license[] SEC("license") = "GPL";
11+
12+
#ifndef SHA256_DIGEST_SIZE
13+
#define SHA256_DIGEST_SIZE 32
14+
#endif
15+
16+
#define MAX_SIG_SIZE 1024
17+
18+
/* By default, "fsverity sign" signs a file with fsverity_formatted_digest
19+
* of the file. fsverity_formatted_digest on the kernel side is only used
20+
* with CONFIG_FS_VERITY_BUILTIN_SIGNATURES. However, BPF LSM doesn't not
21+
* require CONFIG_FS_VERITY_BUILTIN_SIGNATURES, so vmlinux.h may not have
22+
* fsverity_formatted_digest. In this test, we intentionally avoid using
23+
* fsverity_formatted_digest.
24+
*
25+
* Luckily, fsverity_formatted_digest is simply 8-byte magic followed by
26+
* fsverity_digest. We use a char array of size fsverity_formatted_digest
27+
* plus SHA256_DIGEST_SIZE. The magic part of it is filled by user space,
28+
* and the rest of it is filled by bpf_get_fsverity_digest.
29+
*
30+
* Note that, generating signatures based on fsverity_formatted_digest is
31+
* the design choice of this selftest (and "fsverity sign"). With BPF
32+
* LSM, we have the flexibility to generate signature based on other data
33+
* sets, for example, fsverity_digest or only the digest[] part of it.
34+
*/
35+
#define MAGIC_SIZE 8
36+
#define SIZEOF_STRUCT_FSVERITY_DIGEST 4 /* sizeof(struct fsverity_digest) */
37+
char digest[MAGIC_SIZE + SIZEOF_STRUCT_FSVERITY_DIGEST + SHA256_DIGEST_SIZE];
38+
39+
__u32 monitored_pid;
40+
char sig[MAX_SIG_SIZE];
41+
__u32 sig_size;
42+
__u32 user_keyring_serial;
43+
44+
SEC("lsm.s/file_open")
45+
int BPF_PROG(test_file_open, struct file *f)
46+
{
47+
struct bpf_dynptr digest_ptr, sig_ptr;
48+
struct bpf_key *trusted_keyring;
49+
__u32 pid;
50+
int ret;
51+
52+
pid = bpf_get_current_pid_tgid() >> 32;
53+
if (pid != monitored_pid)
54+
return 0;
55+
56+
/* digest_ptr points to fsverity_digest */
57+
bpf_dynptr_from_mem(digest + MAGIC_SIZE, sizeof(digest) - MAGIC_SIZE, 0, &digest_ptr);
58+
59+
ret = bpf_get_fsverity_digest(f, &digest_ptr);
60+
/* No verity, allow access */
61+
if (ret < 0)
62+
return 0;
63+
64+
/* Move digest_ptr to fsverity_formatted_digest */
65+
bpf_dynptr_from_mem(digest, sizeof(digest), 0, &digest_ptr);
66+
67+
/* Read signature from xattr */
68+
bpf_dynptr_from_mem(sig, sizeof(sig), 0, &sig_ptr);
69+
ret = bpf_get_file_xattr(f, "user.sig", &sig_ptr);
70+
/* No signature, reject access */
71+
if (ret < 0)
72+
return -EPERM;
73+
74+
trusted_keyring = bpf_lookup_user_key(user_keyring_serial, 0);
75+
if (!trusted_keyring)
76+
return -ENOENT;
77+
78+
/* Verify signature */
79+
ret = bpf_verify_pkcs7_signature(&digest_ptr, &sig_ptr, trusted_keyring);
80+
81+
bpf_key_put(trusted_keyring);
82+
return ret;
83+
}

tools/testing/selftests/bpf/progs/test_verify_pkcs7_sig.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,11 @@
1010
#include <errno.h>
1111
#include <bpf/bpf_helpers.h>
1212
#include <bpf/bpf_tracing.h>
13+
#include "bpf_kfuncs.h"
1314

1415
#define MAX_DATA_SIZE (1024 * 1024)
1516
#define MAX_SIG_SIZE 1024
1617

17-
extern struct bpf_key *bpf_lookup_user_key(__u32 serial, __u64 flags) __ksym;
18-
extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
19-
extern void bpf_key_put(struct bpf_key *key) __ksym;
20-
extern int bpf_verify_pkcs7_signature(struct bpf_dynptr *data_ptr,
21-
struct bpf_dynptr *sig_ptr,
22-
struct bpf_key *trusted_keyring) __ksym;
23-
2418
__u32 monitored_pid;
2519
__u32 user_keyring_serial;
2620
__u64 system_keyring_id;

tools/testing/selftests/bpf/verify_sig_setup.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,27 @@ cleanup() {
6060
rm -rf ${tmp_dir}
6161
}
6262

63+
fsverity_create_sign_file() {
64+
local tmp_dir="$1"
65+
66+
data_file=${tmp_dir}/data-file
67+
sig_file=${tmp_dir}/sig-file
68+
dd if=/dev/urandom of=$data_file bs=1 count=12345 2> /dev/null
69+
fsverity sign --key ${tmp_dir}/signing_key.pem $data_file $sig_file
70+
71+
# We do not want to enable fsverity on $data_file yet. Try whether
72+
# the file system support fsverity on a different file.
73+
touch ${tmp_dir}/tmp-file
74+
fsverity enable ${tmp_dir}/tmp-file
75+
}
76+
77+
fsverity_enable_file() {
78+
local tmp_dir="$1"
79+
80+
data_file=${tmp_dir}/data-file
81+
fsverity enable $data_file
82+
}
83+
6384
catch()
6485
{
6586
local exit_code="$1"
@@ -86,6 +107,10 @@ main()
86107
setup "${tmp_dir}"
87108
elif [[ "${action}" == "cleanup" ]]; then
88109
cleanup "${tmp_dir}"
110+
elif [[ "${action}" == "fsverity-create-sign" ]]; then
111+
fsverity_create_sign_file "${tmp_dir}"
112+
elif [[ "${action}" == "fsverity-enable" ]]; then
113+
fsverity_enable_file "${tmp_dir}"
89114
else
90115
echo "Unknown action: ${action}"
91116
exit 1

0 commit comments

Comments
 (0)