403 lines
9.2 KiB
C
403 lines
9.2 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
|
||
|
/*
|
||
|
* Copyright (C) 2022 Huawei Technologies Duesseldorf GmbH
|
||
|
*
|
||
|
* Author: Roberto Sassu <roberto.sassu@huawei.com>
|
||
|
*/
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <errno.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <endian.h>
|
||
|
#include <limits.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <sys/mman.h>
|
||
|
#include <linux/keyctl.h>
|
||
|
#include <test_progs.h>
|
||
|
|
||
|
#include "test_verify_pkcs7_sig.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)
|
||
|
|
||
|
/* 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;
|
||
|
}
|
||
|
|
||
|
void test_verify_pkcs7_sig(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);
|
||
|
}
|