// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2022 Huawei Technologies Duesseldorf GmbH * * Author: Roberto Sassu */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #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" /* * Module signature information block. * * The constituents of the signature section are, in order: * * - Signer's name * - Key identifier * - Signature data * - Information block */ struct module_signature { __u8 algo; /* Public-key crypto algorithm [0] */ __u8 hash; /* Digest algorithm [0] */ __u8 id_type; /* Key identifier type [PKEY_ID_PKCS7] */ __u8 signer_len; /* Length of signer's name [0] */ __u8 key_id_len; /* Length of key identifier [0] */ __u8 __pad[3]; __be32 sig_len; /* Length of signature data */ }; struct data { __u8 data[MAX_DATA_SIZE]; __u32 data_len; __u8 sig[MAX_SIG_SIZE]; __u32 sig_len; }; static bool kfunc_not_supported; static int libbpf_print_cb(enum libbpf_print_level level, const char *fmt, va_list args) { if (level == LIBBPF_WARN) vprintf(fmt, args); if (strcmp(fmt, "libbpf: extern (func ksym) '%s': not found in kernel or module BTFs\n")) return 0; if (strcmp(va_arg(args, char *), "bpf_verify_pkcs7_signature")) return 0; kfunc_not_supported = true; return 0; } static int _run_setup_process(const char *setup_dir, const char *cmd) { int child_pid, child_status; child_pid = fork(); if (child_pid == 0) { execlp("./verify_sig_setup.sh", "./verify_sig_setup.sh", cmd, setup_dir, NULL); exit(errno); } else if (child_pid > 0) { waitpid(child_pid, &child_status, 0); return WEXITSTATUS(child_status); } return -EINVAL; } static int populate_data_item_str(const char *tmp_dir, struct data *data_item) { struct stat st; char data_template[] = "/tmp/dataXXXXXX"; char path[PATH_MAX]; int ret, fd, child_status, child_pid; data_item->data_len = 4; memcpy(data_item->data, "test", data_item->data_len); fd = mkstemp(data_template); if (fd == -1) return -errno; ret = write(fd, data_item->data, data_item->data_len); close(fd); if (ret != data_item->data_len) { ret = -EIO; goto out; } child_pid = fork(); if (child_pid == -1) { ret = -errno; goto out; } if (child_pid == 0) { snprintf(path, sizeof(path), "%s/signing_key.pem", tmp_dir); return execlp("./sign-file", "./sign-file", "-d", "sha256", path, path, data_template, NULL); } waitpid(child_pid, &child_status, 0); ret = WEXITSTATUS(child_status); if (ret) goto out; snprintf(path, sizeof(path), "%s.p7s", data_template); ret = stat(path, &st); if (ret == -1) { ret = -errno; goto out; } if (st.st_size > sizeof(data_item->sig)) { ret = -EINVAL; goto out_sig; } data_item->sig_len = st.st_size; fd = open(path, O_RDONLY); if (fd == -1) { ret = -errno; goto out_sig; } ret = read(fd, data_item->sig, data_item->sig_len); close(fd); if (ret != data_item->sig_len) { ret = -EIO; goto out_sig; } ret = 0; out_sig: unlink(path); out: unlink(data_template); return ret; } static int populate_data_item_mod(struct data *data_item) { char mod_path[PATH_MAX], *mod_path_ptr; struct stat st; void *mod; FILE *fp; struct module_signature ms; int ret, fd, modlen, marker_len, sig_len; data_item->data_len = 0; if (stat("/lib/modules", &st) == -1) return 0; /* Requires CONFIG_TCP_CONG_BIC=m. */ fp = popen("find /lib/modules/$(uname -r) -name tcp_bic.ko", "r"); if (!fp) return 0; mod_path_ptr = fgets(mod_path, sizeof(mod_path), fp); pclose(fp); if (!mod_path_ptr) return 0; mod_path_ptr = strchr(mod_path, '\n'); if (!mod_path_ptr) return 0; *mod_path_ptr = '\0'; if (stat(mod_path, &st) == -1) return 0; modlen = st.st_size; marker_len = sizeof(MODULE_SIG_STRING) - 1; fd = open(mod_path, O_RDONLY); if (fd == -1) return -errno; mod = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (mod == MAP_FAILED) return -errno; if (strncmp(mod + modlen - marker_len, MODULE_SIG_STRING, marker_len)) { ret = -EINVAL; goto out; } modlen -= marker_len; memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms)); sig_len = __be32_to_cpu(ms.sig_len); modlen -= sig_len + sizeof(ms); if (modlen > sizeof(data_item->data)) { ret = -E2BIG; goto out; } memcpy(data_item->data, mod, modlen); data_item->data_len = modlen; if (sig_len > sizeof(data_item->sig)) { ret = -E2BIG; goto out; } memcpy(data_item->sig, mod + modlen, sig_len); data_item->sig_len = sig_len; ret = 0; out: munmap(mod, st.st_size); return ret; } static void test_verify_pkcs7_sig_from_map(void) { libbpf_print_fn_t old_print_cb; char tmp_dir_template[] = "/tmp/verify_sigXXXXXX"; char *tmp_dir; struct test_verify_pkcs7_sig *skel = NULL; struct bpf_map *map; struct data data; int ret, zero = 0; /* Trigger creation of session keyring. */ syscall(__NR_request_key, "keyring", "_uid.0", NULL, KEY_SPEC_SESSION_KEYRING); tmp_dir = mkdtemp(tmp_dir_template); if (!ASSERT_OK_PTR(tmp_dir, "mkdtemp")) return; ret = _run_setup_process(tmp_dir, "setup"); if (!ASSERT_OK(ret, "_run_setup_process")) goto close_prog; skel = test_verify_pkcs7_sig__open(); if (!ASSERT_OK_PTR(skel, "test_verify_pkcs7_sig__open")) goto close_prog; old_print_cb = libbpf_set_print(libbpf_print_cb); ret = test_verify_pkcs7_sig__load(skel); libbpf_set_print(old_print_cb); if (ret < 0 && kfunc_not_supported) { printf( "%s:SKIP:bpf_verify_pkcs7_signature() kfunc not supported\n", __func__); test__skip(); goto close_prog; } if (!ASSERT_OK(ret, "test_verify_pkcs7_sig__load")) goto close_prog; ret = test_verify_pkcs7_sig__attach(skel); if (!ASSERT_OK(ret, "test_verify_pkcs7_sig__attach")) goto close_prog; map = bpf_object__find_map_by_name(skel->obj, "data_input"); if (!ASSERT_OK_PTR(map, "data_input not found")) goto close_prog; skel->bss->monitored_pid = getpid(); /* Test without data and signature. */ skel->bss->user_keyring_serial = KEY_SPEC_SESSION_KEYRING; ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_LT(ret, 0, "bpf_map_update_elem data_input")) goto close_prog; /* Test successful signature verification with session keyring. */ ret = populate_data_item_str(tmp_dir, &data); if (!ASSERT_OK(ret, "populate_data_item_str")) goto close_prog; ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_OK(ret, "bpf_map_update_elem data_input")) goto close_prog; /* Test successful signature verification with testing keyring. */ skel->bss->user_keyring_serial = syscall(__NR_request_key, "keyring", "ebpf_testing_keyring", NULL, KEY_SPEC_SESSION_KEYRING); ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_OK(ret, "bpf_map_update_elem data_input")) goto close_prog; /* * Ensure key_task_permission() is called and rejects the keyring * (no Search permission). */ syscall(__NR_keyctl, KEYCTL_SETPERM, skel->bss->user_keyring_serial, 0x37373737); ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_LT(ret, 0, "bpf_map_update_elem data_input")) goto close_prog; syscall(__NR_keyctl, KEYCTL_SETPERM, skel->bss->user_keyring_serial, 0x3f3f3f3f); /* * Ensure key_validate() is called and rejects the keyring (key expired) */ syscall(__NR_keyctl, KEYCTL_SET_TIMEOUT, skel->bss->user_keyring_serial, 1); sleep(1); ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_LT(ret, 0, "bpf_map_update_elem data_input")) goto close_prog; skel->bss->user_keyring_serial = KEY_SPEC_SESSION_KEYRING; /* Test with corrupted data (signature verification should fail). */ data.data[0] = 'a'; ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_LT(ret, 0, "bpf_map_update_elem data_input")) goto close_prog; ret = populate_data_item_mod(&data); if (!ASSERT_OK(ret, "populate_data_item_mod")) goto close_prog; /* Test signature verification with system keyrings. */ if (data.data_len) { skel->bss->user_keyring_serial = 0; skel->bss->system_keyring_id = 0; ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_OK(ret, "bpf_map_update_elem data_input")) goto close_prog; skel->bss->system_keyring_id = VERIFY_USE_SECONDARY_KEYRING; ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); if (!ASSERT_OK(ret, "bpf_map_update_elem data_input")) goto close_prog; skel->bss->system_keyring_id = VERIFY_USE_PLATFORM_KEYRING; ret = bpf_map_update_elem(bpf_map__fd(map), &zero, &data, BPF_ANY); ASSERT_LT(ret, 0, "bpf_map_update_elem data_input"); } close_prog: _run_setup_process(tmp_dir, "cleanup"); if (!skel) return; 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" "To run this test, try enable CONFIG_FS_VERITY and enable FSVerity for the filesystem.\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_signature_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(); }