On Mon Apr 15, 2024 at 5:24 PM EEST, Roberto Sassu wrote: > From: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > > Add tests to verify the correctness of the digest_cache LSM, in all_test.c. > > Add the kernel module digest_cache_kern.ko, to let all_test call the API > of the digest_cache LSM through the newly introduced digest_cache_test file > in securityfs. > > Test coverage information: > > File 'security/digest_cache/notifier.c' > Lines executed:100.00% of 31 > File 'security/digest_cache/reset.c' > Lines executed:98.36% of 61 > File 'security/digest_cache/main.c' > Lines executed:90.29% of 206 > File 'security/digest_cache/modsig.c' > Lines executed:42.86% of 21 > File 'security/digest_cache/htable.c' > Lines executed:93.02% of 86 > File 'security/digest_cache/populate.c' > Lines executed:92.86% of 56 > File 'security/digest_cache/verif.c' > Lines executed:89.74% of 39 > File 'security/digest_cache/dir.c' > Lines executed:90.62% of 96 > File 'security/digest_cache/secfs.c' > Lines executed:57.14% of 21 > File 'security/digest_cache/parsers/tlv.c' > Lines executed:79.75% of 79 > File 'security/digest_cache/parsers/rpm.c' > Lines executed:88.46% of 78 > > Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > --- > MAINTAINERS | 1 + > tools/testing/selftests/Makefile | 1 + > .../testing/selftests/digest_cache/.gitignore | 3 + > tools/testing/selftests/digest_cache/Makefile | 24 + > .../testing/selftests/digest_cache/all_test.c | 815 ++++++++++++++++++ > tools/testing/selftests/digest_cache/common.c | 78 ++ > tools/testing/selftests/digest_cache/common.h | 135 +++ > .../selftests/digest_cache/common_user.c | 47 + > .../selftests/digest_cache/common_user.h | 17 + > tools/testing/selftests/digest_cache/config | 1 + > .../selftests/digest_cache/generators.c | 248 ++++++ > .../selftests/digest_cache/generators.h | 19 + > .../selftests/digest_cache/testmod/Makefile | 16 + > .../selftests/digest_cache/testmod/kern.c | 564 ++++++++++++ > 14 files changed, 1969 insertions(+) > create mode 100644 tools/testing/selftests/digest_cache/.gitignore > create mode 100644 tools/testing/selftests/digest_cache/Makefile > create mode 100644 tools/testing/selftests/digest_cache/all_test.c > create mode 100644 tools/testing/selftests/digest_cache/common.c > create mode 100644 tools/testing/selftests/digest_cache/common.h > create mode 100644 tools/testing/selftests/digest_cache/common_user.c > create mode 100644 tools/testing/selftests/digest_cache/common_user.h > create mode 100644 tools/testing/selftests/digest_cache/config > create mode 100644 tools/testing/selftests/digest_cache/generators.c > create mode 100644 tools/testing/selftests/digest_cache/generators.h > create mode 100644 tools/testing/selftests/digest_cache/testmod/Makefile > create mode 100644 tools/testing/selftests/digest_cache/testmod/kern.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 72801a88449c..d7f700da009e 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -6198,6 +6198,7 @@ M: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > L: linux-security-module@xxxxxxxxxxxxxxx > S: Maintained > F: security/digest_cache/ > +F: tools/testing/selftests/digest_cache/ > A common convetion is to have one patch with MAINTAINERS update in the tail. This is now sprinkled to multiple patches which is not good. > DIGITEQ AUTOMOTIVE MGB4 V4L2 DRIVER > M: Martin Tuma <martin.tuma@xxxxxxxxxxxxxxxxxxxxx> > diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile > index 15b6a111c3be..3c5965a62d28 100644 > --- a/tools/testing/selftests/Makefile > +++ b/tools/testing/selftests/Makefile > @@ -13,6 +13,7 @@ TARGETS += core > TARGETS += cpufreq > TARGETS += cpu-hotplug > TARGETS += damon > +TARGETS += digest_cache > TARGETS += dmabuf-heaps > TARGETS += drivers/dma-buf > TARGETS += drivers/s390x/uvdevice > diff --git a/tools/testing/selftests/digest_cache/.gitignore b/tools/testing/selftests/digest_cache/.gitignore > new file mode 100644 > index 000000000000..392096e18f4e > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/.gitignore > @@ -0,0 +1,3 @@ > +/*.mod > +/*_test > +/*.ko > diff --git a/tools/testing/selftests/digest_cache/Makefile b/tools/testing/selftests/digest_cache/Makefile > new file mode 100644 > index 000000000000..6b1e0d3c08cf > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/Makefile > @@ -0,0 +1,24 @@ > +# SPDX-License-Identifier: GPL-2.0 > +TEST_GEN_PROGS_EXTENDED = digest_cache_kern.ko > +TEST_GEN_PROGS := all_test > + > +$(OUTPUT)/%.ko: $(wildcard common.[ch]) testmod/Makefile testmod/kern.c > + $(call msg,MOD,,$@) > + $(Q)$(MAKE) -C testmod > + $(Q)cp testmod/digest_cache_kern.ko $@ > + > +LOCAL_HDRS += common.h common_user.h generators.h > +CFLAGS += -ggdb -Wall -Wextra $(KHDR_INCLUDES) > + > +OVERRIDE_TARGETS := 1 > +override define CLEAN > + $(call msg,CLEAN) > + $(Q)$(MAKE) -C testmod clean > + rm -Rf $(TEST_GEN_PROGS) > + rm -Rf $(OUTPUT)/common.o $(OUTPUT)/common_user.o $(OUTPUT)/generators.o > + rm -Rf $(OUTPUT)/common.mod > +endef > + > +include ../lib.mk > + > +$(OUTPUT)/all_test: common.c common.h common_user.c common_user.h generators.c > diff --git a/tools/testing/selftests/digest_cache/all_test.c b/tools/testing/selftests/digest_cache/all_test.c > new file mode 100644 > index 000000000000..9f45e522c43c > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/all_test.c > @@ -0,0 +1,815 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Implement the tests of the digest_cache LSM. > + */ > + > +#include <errno.h> > +#include <fcntl.h> > +#include <string.h> > +#include <stdlib.h> > +#include <unistd.h> > +#include <limits.h> > +#include <fts.h> > +#include <sys/types.h> > +#include <sys/stat.h> > +#include <sys/xattr.h> > +#include <sys/syscall.h> > +#include <linux/module.h> > + > +#include "generators.h" > + > +#include "../kselftest_harness.h" > +#include "../../../../include/uapi/linux/xattr.h" > + > +#define BASE_DIR_TEMPLATE "/tmp/digest_cache_test_dirXXXXXX" > +#define DIGEST_LISTS_SUBDIR "digest_lists" > +#define NUM_DIGEST_LISTS_PREFETCH MAX_WORKS > + > +FIXTURE(shared_data) { > + char base_dir[sizeof(BASE_DIR_TEMPLATE)]; > + char digest_lists_dir[sizeof(BASE_DIR_TEMPLATE) + > + sizeof(DIGEST_LISTS_SUBDIR)]; > + int base_dirfd, digest_lists_dirfd, kernfd, pathfd, cmd_len; > + int notify_inodesfd; > +}; > + > +FIXTURE_SETUP(shared_data) > +{ > + char cmd[1024]; > + int fd, i, cmd_len; > + > + /* Create the base directory. */ > + snprintf(self->base_dir, sizeof(self->base_dir), BASE_DIR_TEMPLATE); > + ASSERT_NE(NULL, mkdtemp(self->base_dir)); > + > + /* Open base directory. */ > + self->base_dirfd = open(self->base_dir, O_RDONLY | O_DIRECTORY); > + ASSERT_NE(-1, self->base_dirfd); > + > + /* Create the digest_lists subdirectory. */ > + snprintf(self->digest_lists_dir, sizeof(self->digest_lists_dir), > + "%s/%s", self->base_dir, DIGEST_LISTS_SUBDIR); > + ASSERT_EQ(0, mkdirat(self->base_dirfd, DIGEST_LISTS_SUBDIR, 0600)); > + self->digest_lists_dirfd = openat(self->base_dirfd, DIGEST_LISTS_SUBDIR, > + O_RDONLY | O_DIRECTORY); > + ASSERT_NE(-1, self->digest_lists_dirfd); > + > + fd = open("digest_cache_kern.ko", O_RDONLY); > + ASSERT_LT(0, fd); > + > + ASSERT_EQ(0, syscall(SYS_finit_module, fd, "", 0)); > + close(fd); > + > + /* Open kernel test interface. */ > + self->kernfd = open(DIGEST_CACHE_TEST_INTERFACE, O_RDWR, 0600); > + ASSERT_NE(-1, self->kernfd); > + > + /* Open kernel notify inodes interface. */ > + self->notify_inodesfd = open(DIGEST_CACHE_NOTIFY_INODES_INTERFACE, > + O_RDWR, 0600); > + ASSERT_NE(-1, self->notify_inodesfd); > + > + /* Open kernel digest list path interface. */ > + self->pathfd = open(DIGEST_CACHE_PATH_INTERFACE, O_RDWR, 0600); > + ASSERT_NE(-1, self->pathfd); > + > + /* Write the path of the digest lists directory. */ > + ASSERT_LT(0, write(self->pathfd, self->digest_lists_dir, > + strlen(self->digest_lists_dir))); > + > + /* Ensure that no verifier is enabled at the beginning of a test. */ > + for (i = 0; i < VERIF__LAST; i++) { > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s", > + commands_str[DIGEST_CACHE_DISABLE_VERIF], > + verifs_str[i]); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + } > +} > + > +FIXTURE_TEARDOWN(shared_data) > +{ > + FTS *fts = NULL; > + FTSENT *ftsent; > + int fts_flags = (FTS_PHYSICAL | FTS_COMFOLLOW | FTS_NOCHDIR | FTS_XDEV); > + char *paths[2] = { self->base_dir, NULL }; > + char cmd[1024]; > + int cmd_len; > + > + /* Close digest_lists subdirectory. */ > + close(self->digest_lists_dirfd); > + > + /* Close base directory. */ > + close(self->base_dirfd); > + > + /* Delete files and directories. */ > + fts = fts_open(paths, fts_flags, NULL); > + if (fts) { > + while ((ftsent = fts_read(fts)) != NULL) { > + switch (ftsent->fts_info) { > + case FTS_DP: > + rmdir(ftsent->fts_accpath); > + break; > + case FTS_F: > + case FTS_SL: > + case FTS_SLNONE: > + case FTS_DEFAULT: > + unlink(ftsent->fts_accpath); > + break; > + default: > + break; > + } > + } > + } > + > + /* Release digest cache reference, if the test was interrupted. */ > + cmd_len = snprintf(cmd, sizeof(cmd), "%s", > + commands_str[DIGEST_CACHE_PUT]); > + write(self->kernfd, cmd, cmd_len); > + > + /* Close kernel notify inodes interface. */ > + close(self->notify_inodesfd); > + > + /* Close kernel test interface. */ > + close(self->kernfd); > + > + /* Close kernel digest list path interface. */ > + close(self->pathfd); > + > + syscall(SYS_delete_module, "digest_cache_kern", 0); > +} > + > +static int query_test(int kernfd, char *base_dir, char *filename, > + enum hash_algo algo, int start_number, int num_digests) > +{ > + u8 digest[MAX_DIGEST_SIZE] = { 0 }; > + char digest_str[MAX_DIGEST_SIZE * 2 + 1] = { 0 }; > + int digest_len = hash_digest_size[algo]; > + char cmd[1024]; > + int ret, i, cmd_len; > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s/%s", > + commands_str[DIGEST_CACHE_GET], base_dir, filename); > + ret = write(kernfd, cmd, cmd_len); > + if (ret != cmd_len) > + return -errno; > + > + ret = 0; > + > + *(u32 *)digest = start_number; > + > + for (i = 0; i < num_digests; i++) { > + bin2hex(digest_str, digest, digest_len); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s/%s|%s:%s", > + commands_str[DIGEST_CACHE_LOOKUP], base_dir, > + filename, hash_algo_name[algo], digest_str); > + ret = write(kernfd, cmd, cmd_len); > + if (ret != cmd_len) { > + ret = -errno; > + goto out; > + } else { > + ret = 0; > + } > + > + (*(u32 *)digest)++; > + } > +out: > + cmd_len = snprintf(cmd, sizeof(cmd), "%s", > + commands_str[DIGEST_CACHE_PUT]); > + write(kernfd, cmd, cmd_len); > + return ret; > +} > + > +static enum pgp_algos get_pgp_algo(enum hash_algo algo) > +{ > + unsigned long i; > + > + for (i = DIGEST_ALGO_MD5; i < ARRAY_SIZE(pgp_algo_mapping); i++) > + if (pgp_algo_mapping[i] == algo) > + return i; > + > + return DIGEST_ALGO_SHA224 + 1; > +} > + > +static void test_parser(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata, > + char *digest_list_filename, char *filename, > + enum hash_algo algo, int start_number, int num_digests, > + unsigned int failure) > +{ > + int expected_ret = (failure) ? -ENOENT : 0; > + > + if (!strncmp(digest_list_filename, "tlv-", 4)) { > + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, > + digest_list_filename, algo, > + start_number, num_digests, > + (enum tlv_failures)failure)); > + } else if (!strncmp(digest_list_filename, "rpm-", 4)) { > + enum pgp_algos pgp_algo = get_pgp_algo(algo); > + > + if (pgp_algo == DIGEST_ALGO_SHA224 + 1) > + return; > + > + ASSERT_EQ(0, gen_rpm_list(self->digest_lists_dirfd, > + digest_list_filename, algo, pgp_algo, > + start_number, num_digests, > + (enum rpm_failures)failure)); > + } > + > + ASSERT_EQ(0, create_file(self->base_dirfd, filename, > + digest_list_filename)); > + ASSERT_EQ(expected_ret, query_test(self->kernfd, self->base_dir, > + filename, algo, start_number, > + num_digests)); > + > + unlinkat(self->digest_lists_dirfd, digest_list_filename, 0); > + unlinkat(self->base_dirfd, filename, 0); > +} > + > +/* > + * Verify that the tlv digest list parser returns success on well-formatted > + * digest lists, for each defined hash algorithm. > + */ > +TEST_F(shared_data, tlv_parser_ok) > +{ > + enum hash_algo algo; > + > + /* Test every known algorithm. */ > + for (algo = 0; algo < HASH_ALGO__LAST; algo++) > + test_parser(self, _metadata, "tlv-digest_list", "file", algo, > + 0, 5, TLV_NO_FAILURE); > +} > + > +/* > + * Verify that the tlv digest list parser returns failure on invalid digest > + * lists. > + */ > +TEST_F(shared_data, tlv_parser_error) > +{ > + enum tlv_failures failure; > + > + /* Test every failure. */ > + for (failure = 0; failure < TLV_FAILURE__LAST; failure++) > + test_parser(self, _metadata, "tlv-digest_list", "file", > + HASH_ALGO_SHA224, 0, 1, failure); > +} > + > +/* > + * Verify that the rpm digest list parser returns success on well-formatted > + * digest lists, for each defined hash algorithm. > + */ > +TEST_F(shared_data, rpm_parser_ok) > +{ > + enum hash_algo algo; > + > + /* Test every known algorithm. */ > + for (algo = 0; algo < HASH_ALGO__LAST; algo++) > + test_parser(self, _metadata, "rpm-digest_list", "file", algo, > + 0, 5, RPM_NO_FAILURE); > +} > + > +/* > + * Verify that the rpm digest list parser returns failure on invalid digest > + * lists. > + */ > +TEST_F(shared_data, rpm_parser_error) > +{ > + enum rpm_failures failure; > + > + /* Test every failure. */ > + for (failure = 0; failure < RPM_FAILURE__LAST; failure++) > + test_parser(self, _metadata, "rpm-digest_list", "file", > + HASH_ALGO_SHA224, 0, 1, failure); > +} > + > +static void test_default_path(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata, bool file) > +{ > + char path[PATH_MAX]; > + size_t path_len; > + > + if (file) { > + path_len = snprintf(path, sizeof(path), > + "%s/%s/tlv-digest_list", self->base_dir, > + DIGEST_LISTS_SUBDIR); > + ASSERT_LT(0, write(self->pathfd, path, path_len)); > + } > + > + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, "tlv-digest_list", > + HASH_ALGO_SHA1, 0, 1, TLV_NO_FAILURE)); > + > + ASSERT_EQ(0, create_file(self->base_dirfd, "file", NULL)); > + > + ASSERT_EQ(0, query_test(self->kernfd, self->base_dir, "file", > + HASH_ALGO_SHA1, 0, 1)); > +} > + > +/* > + * Verify that the digest cache created from the default path (regular file) > + * can be retrieved and used for lookup. > + */ > +TEST_F(shared_data, default_path_file) > +{ > + test_default_path(self, _metadata, true); > +} > + > +/* > + * Verify that the digest cache created from the default path (directory) > + * can be retrieved and used for lookup. > + */ > +TEST_F(shared_data, default_path_dir) > +{ > + test_default_path(self, _metadata, false); > +} > + > +static void notify_inode_init(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata) > +{ > + /* Clear buffer. */ > + ASSERT_EQ(1, write(self->notify_inodesfd, "1", 1)); > +} > + > +static void notify_inodes_check(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata, > + char *filenames) > +{ > + char notify_inodes_buf[1024] = { 0 }; > + char notify_inodes_buf_kernel[1024] = { 0 }; > + char *filename, *filenames_copy, *buf_ptr = notify_inodes_buf; > + struct stat st; > + int fd; > + > + ASSERT_LT(0, read(self->notify_inodesfd, notify_inodes_buf_kernel, > + sizeof(notify_inodes_buf_kernel))); > + > + filenames_copy = strdup(filenames); > + ASSERT_NE(NULL, filenames_copy); > + > + while ((filename = strsep(&filenames_copy, ","))) { > + fd = openat(self->base_dirfd, filename, O_RDONLY); > + ASSERT_NE(-1, fd); > + ASSERT_EQ(0, fstat(fd, &st)); > + close(fd); > + > + buf_ptr += snprintf(buf_ptr, > + sizeof(notify_inodes_buf) - > + (buf_ptr - notify_inodes_buf), "%s%lu", > + notify_inodes_buf[0] ? "," : "", st.st_ino); > + } > + > + free(filenames_copy); > + > + ASSERT_EQ(0, strcmp(notify_inodes_buf, notify_inodes_buf_kernel)); > +} > + > +static void test_file_changes(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata, > + enum file_changes change) > +{ > + char digest_list_filename[] = "tlv-digest_list"; > + char digest_list_filename_new[] = "tlv-digest_list6"; > + char digest_list_filename_xattr[] = "tlv-digest_list7"; > + char digest_list_path[sizeof(self->digest_lists_dir) + > + sizeof(digest_list_filename)]; > + int fd; > + > + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, > + digest_list_filename, HASH_ALGO_SHA1, 0, 1, > + TLV_NO_FAILURE)); > + > + ASSERT_EQ(0, create_file(self->base_dirfd, "file", > + digest_list_filename)); > + > + ASSERT_EQ(0, query_test(self->kernfd, self->base_dir, "file", > + HASH_ALGO_SHA1, 0, 1)); > + > + notify_inode_init(self, _metadata); > + > + switch (change) { > + case FILE_WRITE: > + fd = openat(self->digest_lists_dirfd, digest_list_filename, > + O_WRONLY); > + ASSERT_NE(-1, fd); > + > + ASSERT_EQ(4, write(fd, "1234", 4)); > + close(fd); > + break; > + case FILE_TRUNCATE: > + snprintf(digest_list_path, sizeof(digest_list_path), > + "%s/%s", self->digest_lists_dir, digest_list_filename); > + ASSERT_EQ(0, truncate(digest_list_path, 4)); > + break; > + case FILE_FTRUNCATE: > + fd = openat(self->digest_lists_dirfd, digest_list_filename, > + O_WRONLY); > + ASSERT_NE(-1, fd); > + ASSERT_EQ(0, ftruncate(fd, 4)); > + close(fd); > + break; > + case FILE_UNLINK: > + ASSERT_EQ(0, unlinkat(self->digest_lists_dirfd, > + digest_list_filename, 0)); > + break; > + case FILE_RENAME: > + ASSERT_EQ(0, renameat(self->digest_lists_dirfd, > + digest_list_filename, > + self->digest_lists_dirfd, > + digest_list_filename_new)); > + break; > + case FILE_SETXATTR: > + fd = openat(self->base_dirfd, "file", O_WRONLY); > + ASSERT_NE(-1, fd); > + > + ASSERT_EQ(0, fsetxattr(fd, XATTR_NAME_DIGEST_LIST, > + digest_list_filename_xattr, > + strlen(digest_list_filename_xattr) + 1, > + 0)); > + close(fd); > + break; > + case FILE_REMOVEXATTR: > + fd = openat(self->base_dirfd, "file", O_WRONLY); > + ASSERT_NE(-1, fd); > + > + ASSERT_EQ(0, fremovexattr(fd, XATTR_NAME_DIGEST_LIST)); > + close(fd); > + > + /* > + * Removing security.digest_list does not cause a failure, > + * the digest can be still retrieved via directory lookup. > + */ > + ASSERT_EQ(0, query_test(self->kernfd, self->base_dir, "file", > + HASH_ALGO_SHA1, 0, 1)); > + > + notify_inodes_check(self, _metadata, "file"); > + return; > + default: > + break; > + } > + > + ASSERT_NE(0, query_test(self->kernfd, self->base_dir, "file", > + HASH_ALGO_SHA1, 0, 1)); > + > + notify_inodes_check(self, _metadata, "file"); > +} > + > +/* > + * Verify that operations on a digest list cause a reset of the digest cache, > + * and that the digest is not found in the invalid/missing digest list. > + */ > +TEST_F(shared_data, file_reset) > +{ > + enum file_changes change; > + > + /* Test for every file change. */ > + for (change = 0; change < FILE_CHANGE__LAST; change++) > + test_file_changes(self, _metadata, change); > +} > + > +static void query_test_with_failures(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata, > + int start_number, int num_digests, > + int *removed, int num_removed) > +{ > + int i, j, expected_ret; > + > + for (i = start_number; i < start_number + num_digests; i++) { > + expected_ret = 0; > + > + for (j = 0; j < num_removed; j++) { > + if (removed[j] == i) { > + expected_ret = -ENOENT; > + break; > + } > + } > + > + ASSERT_EQ(expected_ret, query_test(self->kernfd, self->base_dir, > + "file", HASH_ALGO_SHA1, i, > + 1)); > + } > +} > + > +/* > + * Verify that changes in the digest list directory are monitored and that > + * a digest cannot be found if the respective digest list file has been moved > + * away from the directory, and that a digest can be found if the respective > + * digest list has been moved/created in the directory. > + */ > +TEST_F(shared_data, dir_reset) > +{ > + char digest_list_filename[NAME_MAX + 1]; > + int i, removed[10]; > + > + for (i = 0; i < 10; i++) { > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "tlv-digest_list%d", i); > + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, > + digest_list_filename, HASH_ALGO_SHA1, > + i, 1, TLV_NO_FAILURE)); > + } > + > + ASSERT_EQ(0, create_file(self->base_dirfd, "file", NULL)); > + /* The second file is to have duplicate notifications (file and dir). */ > + ASSERT_EQ(0, create_file(self->base_dirfd, "file2", > + "tlv-digest_list7")); > + /* The query adds file2 inode to the file digest cache notif. list. */ > + ASSERT_NE(0, query_test(self->kernfd, self->base_dir, "file2", > + HASH_ALGO_SHA1, 0, 1)); > + > + query_test_with_failures(self, _metadata, 0, 10, removed, 0); > + > + notify_inode_init(self, _metadata); > + ASSERT_EQ(0, unlinkat(self->digest_lists_dirfd, "tlv-digest_list7", 0)); > + /* File notification comes before directory notification. */ > + notify_inodes_check(self, _metadata, "file2,file"); > + > + removed[0] = 7; > + > + query_test_with_failures(self, _metadata, 0, 10, removed, 1); > + > + notify_inode_init(self, _metadata); > + ASSERT_EQ(0, renameat(self->digest_lists_dirfd, "tlv-digest_list6", > + self->base_dirfd, "tlv-digest_list6")); > + notify_inodes_check(self, _metadata, "file"); > + > + removed[1] = 6; > + > + query_test_with_failures(self, _metadata, 0, 10, removed, 2); > + > + notify_inode_init(self, _metadata); > + ASSERT_EQ(0, renameat(self->base_dirfd, "tlv-digest_list6", > + self->digest_lists_dirfd, "tlv-digest_list6")); > + notify_inodes_check(self, _metadata, "file"); > + > + query_test_with_failures(self, _metadata, 0, 10, removed, 1); > + > + notify_inode_init(self, _metadata); > + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, "tlv-digest_list10", > + HASH_ALGO_SHA1, 10, 1, TLV_NO_FAILURE)); > + notify_inodes_check(self, _metadata, "file"); > + > + query_test_with_failures(self, _metadata, 0, 11, removed, 1); > +} > + > +static void _check_verif_data(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata, > + char *digest_list_filename, int num, > + enum hash_algo algo, bool check_dir) > +{ > + char digest_list_filename_kernel[NAME_MAX + 1]; > + char cmd[1024], number[20]; > + u8 digest[MAX_DIGEST_SIZE] = { 0 }; > + char digest_str[MAX_DIGEST_SIZE * 2 + 1] = { 0 }; > + int len, cmd_len; > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s/file", > + commands_str[DIGEST_CACHE_GET], self->base_dir); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + /* > + * If a directory digest cache was requested, we need to do a lookup, > + * to make the kernel module retrieve verification data from the digest > + * cache of the directory entry. > + */ > + if (check_dir) { > + *(u32 *)digest = num; > + > + bin2hex(digest_str, digest, hash_digest_size[algo]); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s/file|%s:%s", > + commands_str[DIGEST_CACHE_LOOKUP], > + self->base_dir, hash_algo_name[algo], > + digest_str); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + } > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s", > + commands_str[DIGEST_CACHE_SET_VERIF], > + verifs_str[VERIF_FILENAMES]); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + ASSERT_LT(0, read(self->kernfd, digest_list_filename_kernel, > + sizeof(digest_list_filename_kernel))); > + ASSERT_EQ(0, strcmp(digest_list_filename, digest_list_filename_kernel)); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s", > + commands_str[DIGEST_CACHE_SET_VERIF], > + verifs_str[VERIF_NUMBER]); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + len = read(self->kernfd, number, sizeof(number) - 1); > + ASSERT_LT(0, len); > + number[len] = '\0'; > + ASSERT_EQ(num, atoi(number)); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s", > + commands_str[DIGEST_CACHE_PUT]); > + write(self->kernfd, cmd, cmd_len); > +} > + > +static void check_verif_data(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata) > +{ > + char digest_list_filename[NAME_MAX + 1]; > + char cmd[1024]; > + int i, cmd_len; > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s", > + commands_str[DIGEST_CACHE_ENABLE_VERIF], > + verifs_str[VERIF_FILENAMES]); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s", > + commands_str[DIGEST_CACHE_ENABLE_VERIF], > + verifs_str[VERIF_NUMBER]); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + /* > + * Reverse order is intentional, so that directory entries are created > + * in the opposite order as when they are searched (when prefetching is > + * requested). > + */ > + for (i = 10; i >= 0; i--) { > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "%d-tlv-digest_list%d", i, i); > + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, > + digest_list_filename, HASH_ALGO_SHA1, > + i, 1, TLV_NO_FAILURE)); > + > + ASSERT_EQ(0, create_file(self->base_dirfd, "file", > + digest_list_filename)); > + > + _check_verif_data(self, _metadata, digest_list_filename, i, > + HASH_ALGO_SHA1, false); > + > + ASSERT_EQ(0, unlinkat(self->base_dirfd, "file", 0)); > + } > + > + ASSERT_EQ(0, create_file(self->base_dirfd, "file", NULL)); > + > + for (i = 0; i < 11; i++) { > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "%d-tlv-digest_list%d", i, i); > + _check_verif_data(self, _metadata, digest_list_filename, i, > + HASH_ALGO_SHA1, true); > + } > + > + ASSERT_EQ(0, unlinkat(self->base_dirfd, "file", 0)); > +} > + > +/* > + * Verify that the correct verification data can be retrieved from the digest > + * caches (without digest list prefetching). > + */ > +TEST_F(shared_data, verif_data_no_prefetch) > +{ > + check_verif_data(self, _metadata); > +} > + > +/* > + * Verify that the correct verification data can be retrieved from the digest > + * caches (with digest list prefetching). > + */ > +TEST_F(shared_data, verif_data_prefetch) > +{ > + ASSERT_EQ(0, lsetxattr(self->base_dir, XATTR_NAME_DIG_PREFETCH, > + "1", 1, 0)); > + > + check_verif_data(self, _metadata); > +} > + > +static void check_prefetch_list(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata, > + int start_number, int end_number) > +{ > + char digest_list_filename[NAME_MAX + 1], filename[NAME_MAX + 1]; > + char digest_lists[1024], digest_lists_kernel[1024] = { 0 }; > + char cmd[1024]; > + int i, cmd_len; > + > + snprintf(filename, sizeof(filename), "file%d", end_number); > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "%d-tlv-digest_list%d", end_number, end_number); > + ASSERT_EQ(0, create_file(self->base_dirfd, filename, > + digest_list_filename)); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s/%s", > + commands_str[DIGEST_CACHE_GET], self->base_dir, > + filename); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + ASSERT_LT(0, read(self->kernfd, digest_lists, sizeof(digest_lists))); > + > + for (i = start_number; i <= end_number; i++) { > + if (digest_lists_kernel[0]) > + strcat(digest_lists_kernel, ","); > + > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "%d-tlv-digest_list%d", i, i); > + strcat(digest_lists_kernel, digest_list_filename); > + } > + > + ASSERT_EQ(0, strcmp(digest_lists, digest_lists_kernel)); > + > + ASSERT_EQ(0, unlinkat(self->base_dirfd, filename, 0)); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s", > + commands_str[DIGEST_CACHE_PUT]); > + write(self->kernfd, cmd, cmd_len); > +} > + > +static void check_prefetch_list_async(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata) > +{ > + char digest_list_filename[NAME_MAX + 1], filename[NAME_MAX + 1]; > + char digest_lists[1024], digest_lists_kernel[1024] = { 0 }; > + char cmd[1024]; > + int i, cmd_len; > + > + for (i = 0; i < NUM_DIGEST_LISTS_PREFETCH; i++) { > + snprintf(filename, sizeof(filename), "file%d", > + NUM_DIGEST_LISTS_PREFETCH - 1 - i); > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "%d-tlv-digest_list%d", i, i); > + ASSERT_EQ(0, create_file(self->base_dirfd, filename, > + digest_list_filename)); > + } > + > + /* Do batch of get/put to test the kernel for concurrent requests. */ > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s/file|%d|%d", > + commands_str[DIGEST_CACHE_GET_PUT_ASYNC], > + self->base_dir, 0, NUM_DIGEST_LISTS_PREFETCH - 1); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + ASSERT_LT(0, read(self->kernfd, digest_lists, sizeof(digest_lists))); > + > + for (i = 0; i < NUM_DIGEST_LISTS_PREFETCH; i++) { > + if (digest_lists_kernel[0]) > + strcat(digest_lists_kernel, ","); > + > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "%d-tlv-digest_list%d", i, i); > + strcat(digest_lists_kernel, digest_list_filename); > + } > + > + ASSERT_EQ(0, strcmp(digest_lists, digest_lists_kernel)); > +} > + > +static void prepare_prefetch(struct _test_data_shared_data *self, > + struct __test_metadata *_metadata) > +{ > + char digest_list_filename[NAME_MAX + 1]; > + char cmd[1024]; > + int i, cmd_len; > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s", > + commands_str[DIGEST_CACHE_ENABLE_VERIF], > + verifs_str[VERIF_PREFETCH]); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + cmd_len = snprintf(cmd, sizeof(cmd), "%s|%s", > + commands_str[DIGEST_CACHE_SET_VERIF], > + verifs_str[VERIF_PREFETCH]); > + ASSERT_EQ(cmd_len, write(self->kernfd, cmd, cmd_len)); > + > + for (i = NUM_DIGEST_LISTS_PREFETCH - 1; i >= 0; i--) { > + snprintf(digest_list_filename, sizeof(digest_list_filename), > + "%d-tlv-digest_list%d", i, i); > + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, > + digest_list_filename, HASH_ALGO_SHA1, > + i, 1, TLV_NO_FAILURE)); > + } > + > + ASSERT_EQ(0, fsetxattr(self->digest_lists_dirfd, > + XATTR_NAME_DIG_PREFETCH, "1", 1, 0)); > +} > + > +/* > + * Verify that digest lists are prefetched when requested, in the correct order > + * (synchronous version). > + */ > +TEST_F(shared_data, prefetch_sync) > +{ > + int i; > + > + prepare_prefetch(self, _metadata); > + > + for (i = 2; i < NUM_DIGEST_LISTS_PREFETCH; i += 3) > + check_prefetch_list(self, _metadata, i - 2, i); > +} > + > +/* > + * Verify that digest lists are prefetched when requested, in the correct order > + * (asynchronous version). > + */ > +TEST_F(shared_data, prefetch_async) > +{ > + prepare_prefetch(self, _metadata); > + > + check_prefetch_list_async(self, _metadata); > +} > + > +TEST_HARNESS_MAIN > diff --git a/tools/testing/selftests/digest_cache/common.c b/tools/testing/selftests/digest_cache/common.c > new file mode 100644 > index 000000000000..2123f7d937ce > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/common.c > @@ -0,0 +1,78 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Add common code for testing the digest_cache LSM. > + */ > + > +#include "common.h" > + > +const char *commands_str[DIGEST_CACHE__LAST] = { > + [DIGEST_CACHE_GET] = "get", > + [DIGEST_CACHE_LOOKUP] = "lookup", > + [DIGEST_CACHE_PUT] = "put", > + [DIGEST_CACHE_ENABLE_VERIF] = "enable_verif", > + [DIGEST_CACHE_DISABLE_VERIF] = "disable_verif", > + [DIGEST_CACHE_SET_VERIF] = "set_verif", > + [DIGEST_CACHE_GET_PUT_ASYNC] = "get_put_async", > +}; > + > +const char *const hash_algo_name[HASH_ALGO__LAST] = { > + [HASH_ALGO_MD4] = "md4", > + [HASH_ALGO_MD5] = "md5", > + [HASH_ALGO_SHA1] = "sha1", > + [HASH_ALGO_RIPE_MD_160] = "rmd160", > + [HASH_ALGO_SHA256] = "sha256", > + [HASH_ALGO_SHA384] = "sha384", > + [HASH_ALGO_SHA512] = "sha512", > + [HASH_ALGO_SHA224] = "sha224", > + [HASH_ALGO_RIPE_MD_128] = "rmd128", > + [HASH_ALGO_RIPE_MD_256] = "rmd256", > + [HASH_ALGO_RIPE_MD_320] = "rmd320", > + [HASH_ALGO_WP_256] = "wp256", > + [HASH_ALGO_WP_384] = "wp384", > + [HASH_ALGO_WP_512] = "wp512", > + [HASH_ALGO_TGR_128] = "tgr128", > + [HASH_ALGO_TGR_160] = "tgr160", > + [HASH_ALGO_TGR_192] = "tgr192", > + [HASH_ALGO_SM3_256] = "sm3", > + [HASH_ALGO_STREEBOG_256] = "streebog256", > + [HASH_ALGO_STREEBOG_512] = "streebog512", > + [HASH_ALGO_SHA3_256] = "sha3-256", > + [HASH_ALGO_SHA3_384] = "sha3-384", > + [HASH_ALGO_SHA3_512] = "sha3-512", > +}; > + > +const int hash_digest_size[HASH_ALGO__LAST] = { > + [HASH_ALGO_MD4] = MD5_DIGEST_SIZE, > + [HASH_ALGO_MD5] = MD5_DIGEST_SIZE, > + [HASH_ALGO_SHA1] = SHA1_DIGEST_SIZE, > + [HASH_ALGO_RIPE_MD_160] = RMD160_DIGEST_SIZE, > + [HASH_ALGO_SHA256] = SHA256_DIGEST_SIZE, > + [HASH_ALGO_SHA384] = SHA384_DIGEST_SIZE, > + [HASH_ALGO_SHA512] = SHA512_DIGEST_SIZE, > + [HASH_ALGO_SHA224] = SHA224_DIGEST_SIZE, > + [HASH_ALGO_RIPE_MD_128] = RMD128_DIGEST_SIZE, > + [HASH_ALGO_RIPE_MD_256] = RMD256_DIGEST_SIZE, > + [HASH_ALGO_RIPE_MD_320] = RMD320_DIGEST_SIZE, > + [HASH_ALGO_WP_256] = WP256_DIGEST_SIZE, > + [HASH_ALGO_WP_384] = WP384_DIGEST_SIZE, > + [HASH_ALGO_WP_512] = WP512_DIGEST_SIZE, > + [HASH_ALGO_TGR_128] = TGR128_DIGEST_SIZE, > + [HASH_ALGO_TGR_160] = TGR160_DIGEST_SIZE, > + [HASH_ALGO_TGR_192] = TGR192_DIGEST_SIZE, > + [HASH_ALGO_SM3_256] = SM3256_DIGEST_SIZE, > + [HASH_ALGO_STREEBOG_256] = STREEBOG256_DIGEST_SIZE, > + [HASH_ALGO_STREEBOG_512] = STREEBOG512_DIGEST_SIZE, > + [HASH_ALGO_SHA3_256] = SHA3_256_DIGEST_SIZE, > + [HASH_ALGO_SHA3_384] = SHA3_384_DIGEST_SIZE, > + [HASH_ALGO_SHA3_512] = SHA3_512_DIGEST_SIZE, > +}; > + > +const char *verifs_str[] = { > + [VERIF_FILENAMES] = "filenames", > + [VERIF_NUMBER] = "number", > + [VERIF_PREFETCH] = "prefetch", > +}; > diff --git a/tools/testing/selftests/digest_cache/common.h b/tools/testing/selftests/digest_cache/common.h > new file mode 100644 > index 000000000000..e52e4b137807 > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/common.h > @@ -0,0 +1,135 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Header of common.c. > + */ > + > +#ifndef _COMMON_H > +#define _COMMON_H > +#include <linux/types.h> > + > +#include "../../../../include/uapi/linux/hash_info.h" > + > +#define MD5_DIGEST_SIZE 16 > +#define SHA1_DIGEST_SIZE 20 > +#define RMD160_DIGEST_SIZE 20 > +#define SHA256_DIGEST_SIZE 32 > +#define SHA384_DIGEST_SIZE 48 > +#define SHA512_DIGEST_SIZE 64 > +#define SHA224_DIGEST_SIZE 28 > +#define RMD128_DIGEST_SIZE 16 > +#define RMD256_DIGEST_SIZE 32 > +#define RMD320_DIGEST_SIZE 40 > +#define WP256_DIGEST_SIZE 32 > +#define WP384_DIGEST_SIZE 48 > +#define WP512_DIGEST_SIZE 64 > +#define TGR128_DIGEST_SIZE 16 > +#define TGR160_DIGEST_SIZE 20 > +#define TGR192_DIGEST_SIZE 24 > +#define SM3256_DIGEST_SIZE 32 > +#define STREEBOG256_DIGEST_SIZE 32 > +#define STREEBOG512_DIGEST_SIZE 64 > +#define SHA3_224_DIGEST_SIZE (224 / 8) > +#define SHA3_256_DIGEST_SIZE (256 / 8) > +#define SHA3_384_DIGEST_SIZE (384 / 8) > +#define SHA3_512_DIGEST_SIZE (512 / 8) > + > +#define DIGEST_CACHE_TEST_INTERFACE "/sys/kernel/security/digest_cache_test" > +#define DIGEST_CACHE_PATH_INTERFACE "/sys/kernel/security/digest_cache_path" > +#define DIGEST_CACHE_NOTIFY_INODES_INTERFACE \ > + "/sys/kernel/security/digest_cache_notify_inodes" > +#define MAX_DIGEST_SIZE 64 > + > +#define RPMTAG_FILEDIGESTS 1035 > +#define RPMTAG_FILEDIGESTALGO 5011 > + > +#define RPM_INT32_TYPE 4 > +#define RPM_STRING_ARRAY_TYPE 8 > + > +#define MAX_WORKS 21 > + > +typedef __u8 u8; > +typedef __u16 u16; > +typedef __u32 u32; > +typedef __s32 s32; > +typedef __u64 u64; > + > +enum commands { > + DIGEST_CACHE_GET, // args: <path> > + DIGEST_CACHE_LOOKUP, // args: <algo>|<digest> > + DIGEST_CACHE_PUT, // args: > + DIGEST_CACHE_ENABLE_VERIF, // args: <verif name> > + DIGEST_CACHE_DISABLE_VERIF, // args: <verif name> > + DIGEST_CACHE_SET_VERIF, // args: <verif name> > + DIGEST_CACHE_GET_PUT_ASYNC, // args: <path>|<start#>|<end#> > + DIGEST_CACHE__LAST, > +}; > + > +enum tlv_failures { TLV_NO_FAILURE, > + TLV_FAILURE_ALGO_LEN, > + TLV_FAILURE_HDR_LEN, > + TLV_FAILURE_ALGO_MISMATCH, > + TLV_FAILURE_NUM_DIGESTS, > + TLV_FAILURE__LAST > +}; > + > +enum rpm_failures { RPM_NO_FAILURE, > + RPM_FAILURE_WRONG_MAGIC, > + RPM_FAILURE_BAD_DATA_OFFSET, > + RPM_FAILURE_WRONG_TAGS, > + RPM_FAILURE_WRONG_DIGEST_COUNT, > + RPM_FAILURE_DIGEST_WRONG_TYPE, > + RPM_FAILURE__LAST > +}; > + > +enum file_changes { FILE_WRITE, > + FILE_TRUNCATE, > + FILE_FTRUNCATE, > + FILE_UNLINK, > + FILE_RENAME, > + FILE_SETXATTR, > + FILE_REMOVEXATTR, > + FILE_CHANGE__LAST > +}; > + > +enum VERIFS { > + VERIF_FILENAMES, > + VERIF_NUMBER, > + VERIF_PREFETCH, > + VERIF__LAST > +}; > + > +enum pgp_algos { > + DIGEST_ALGO_MD5 = 1, > + DIGEST_ALGO_SHA1 = 2, > + DIGEST_ALGO_RMD160 = 3, > + /* 4, 5, 6, and 7 are reserved. */ > + DIGEST_ALGO_SHA256 = 8, > + DIGEST_ALGO_SHA384 = 9, > + DIGEST_ALGO_SHA512 = 10, > + DIGEST_ALGO_SHA224 = 11, > +}; > + > +struct rpm_hdr { > + u32 magic; > + u32 reserved; > + u32 tags; > + u32 datasize; > +} __attribute__ ((__packed__)); > + > +struct rpm_entryinfo { > + s32 tag; > + u32 type; > + s32 offset; > + u32 count; > +} __attribute__ ((__packed__)); > + > +extern const char *commands_str[DIGEST_CACHE__LAST]; > +extern const char *const hash_algo_name[HASH_ALGO__LAST]; > +extern const int hash_digest_size[HASH_ALGO__LAST]; > +extern const char *verifs_str[VERIF__LAST]; > + > +#endif /* _COMMON_H */ > diff --git a/tools/testing/selftests/digest_cache/common_user.c b/tools/testing/selftests/digest_cache/common_user.c > new file mode 100644 > index 000000000000..1bacadad6b6a > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/common_user.c > @@ -0,0 +1,47 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Add common code in user space for testing the digest_cache LSM. > + */ > + > +#include <stddef.h> > + > +#include "common_user.h" > + > +static const char hex_asc[] = "0123456789abcdef"; > + > +#define hex_asc_lo(x) hex_asc[((x) & 0x0f)] > +#define hex_asc_hi(x) hex_asc[((x) & 0xf0) >> 4] > + > +const enum hash_algo pgp_algo_mapping[DIGEST_ALGO_SHA224 + 1] = { > + [DIGEST_ALGO_MD5] = HASH_ALGO_MD5, > + [DIGEST_ALGO_SHA1] = HASH_ALGO_SHA1, > + [DIGEST_ALGO_RMD160] = HASH_ALGO_RIPE_MD_160, > + [4] = HASH_ALGO__LAST, > + [5] = HASH_ALGO__LAST, > + [6] = HASH_ALGO__LAST, > + [7] = HASH_ALGO__LAST, > + [DIGEST_ALGO_SHA256] = HASH_ALGO_SHA256, > + [DIGEST_ALGO_SHA384] = HASH_ALGO_SHA384, > + [DIGEST_ALGO_SHA512] = HASH_ALGO_SHA512, > + [DIGEST_ALGO_SHA224] = HASH_ALGO_SHA224, > +}; > + > +static inline char *hex_byte_pack(char *buf, unsigned char byte) > +{ > + *buf++ = hex_asc_hi(byte); > + *buf++ = hex_asc_lo(byte); > + return buf; > +} > + > +char *bin2hex(char *dst, const void *src, size_t count) > +{ > + const unsigned char *_src = src; > + > + while (count--) > + dst = hex_byte_pack(dst, *_src++); > + return dst; > +} > diff --git a/tools/testing/selftests/digest_cache/common_user.h b/tools/testing/selftests/digest_cache/common_user.h > new file mode 100644 > index 000000000000..4eef52cc5c27 > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/common_user.h > @@ -0,0 +1,17 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Header of common_user.c. > + */ > + > +#include <linux/types.h> > +#include <stddef.h> > + > +#include "common.h" > + > +extern const enum hash_algo pgp_algo_mapping[DIGEST_ALGO_SHA224 + 1]; > + > +char *bin2hex(char *dst, const void *src, size_t count); > diff --git a/tools/testing/selftests/digest_cache/config b/tools/testing/selftests/digest_cache/config > new file mode 100644 > index 000000000000..075a06cc4f8e > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/config > @@ -0,0 +1 @@ > +CONFIG_SECURITY_DIGEST_CACHE=y > diff --git a/tools/testing/selftests/digest_cache/generators.c b/tools/testing/selftests/digest_cache/generators.c > new file mode 100644 > index 000000000000..c7791a3589f2 > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/generators.c > @@ -0,0 +1,248 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Generate digest lists for testing. > + */ > + > +#include <stddef.h> > +#include <fcntl.h> > +#include <errno.h> > +#include <limits.h> > +#include <string.h> > +#include <unistd.h> > +#include <sys/xattr.h> > +#include <asm/byteorder.h> > + > +#include "generators.h" > +#include "../../../../include/uapi/linux/hash_info.h" > +#include "../../../../include/uapi/linux/xattr.h" > +#include "../../../../include/uapi/linux/tlv_digest_list.h" > +#include "../../../../include/uapi/linux/tlv_parser.h" > + > +int gen_tlv_list(int temp_dirfd, char *digest_list_filename, > + enum hash_algo algo, int start_number, int num_digests, > + enum tlv_failures failure) > +{ > + u64 _algo = __cpu_to_be64(algo); > + u8 digest[MAX_DIGEST_SIZE] = { 0 }; > + int digest_len = hash_digest_size[algo]; > + int digest_len_to_copy = digest_len; > + int ret, fd, i; > + > + struct tlv_data_entry algo_entry = { > + .field = __cpu_to_be64(DIGEST_LIST_ALGO), > + .length = __cpu_to_be64(sizeof(_algo)), > + }; > + > + struct tlv_data_entry entry_digest = { > + .field = __cpu_to_be64(DIGEST_LIST_ENTRY_DIGEST), > + .length = __cpu_to_be64(digest_len), > + }; > + > + struct tlv_hdr entry_hdr = { > + .data_type = __cpu_to_be64(DIGEST_LIST_ENTRY_DATA), > + ._reserved = 0, > + .num_entries = __cpu_to_be64(1), > + .total_len = __cpu_to_be64(sizeof(entry_digest) + digest_len), > + }; > + > + struct tlv_data_entry entry_entry = { > + .field = __cpu_to_be64(DIGEST_LIST_ENTRY), > + .length = __cpu_to_be64(sizeof(entry_hdr) + > + __be64_to_cpu(entry_hdr.total_len)), > + }; > + > + struct tlv_hdr hdr = { > + .data_type = __cpu_to_be64(DIGEST_LIST_FILE), > + ._reserved = 0, > + .num_entries = __cpu_to_be64(1 + num_digests), > + .total_len = __cpu_to_be64(sizeof(algo_entry) + > + __be64_to_cpu(algo_entry.length) + > + num_digests * (sizeof(entry_entry) + > + __be64_to_cpu(entry_entry.length))) > + }; > + > + switch (failure) { > + case TLV_FAILURE_ALGO_LEN: > + algo_entry.length = algo_entry.length / 2; > + break; > + case TLV_FAILURE_HDR_LEN: > + hdr.total_len--; > + break; > + case TLV_FAILURE_ALGO_MISMATCH: > + _algo = __cpu_to_be64(algo - 1); > + break; > + case TLV_FAILURE_NUM_DIGESTS: > + num_digests = 0; > + break; > + default: > + break; > + } > + > + fd = openat(temp_dirfd, digest_list_filename, > + O_WRONLY | O_CREAT | O_TRUNC, 0600); > + if (fd == -1) > + return -errno; > + > + ret = write(fd, (u8 *)&hdr, sizeof(hdr)); > + if (ret != sizeof(hdr)) > + return -errno; > + > + ret = write(fd, (u8 *)&algo_entry, sizeof(algo_entry)); > + if (ret != sizeof(algo_entry)) > + return -errno; > + > + ret = write(fd, (u8 *)&_algo, sizeof(_algo)); > + if (ret != sizeof(_algo)) > + return -errno; > + > + *(u32 *)digest = start_number; > + > + for (i = 0; i < num_digests; i++) { > + ret = write(fd, (u8 *)&entry_entry, sizeof(entry_entry)); > + if (ret != sizeof(entry_entry)) > + return -errno; > + > + ret = write(fd, (u8 *)&entry_hdr, sizeof(entry_hdr)); > + if (ret != sizeof(entry_hdr)) > + return -errno; > + > + ret = write(fd, (u8 *)&entry_digest, sizeof(entry_digest)); > + if (ret != sizeof(entry_digest)) > + return -errno; > + > + ret = write(fd, digest, digest_len_to_copy); > + if (ret != digest_len_to_copy) > + return -errno; > + > + (*(u32 *)digest)++; > + } > + > + close(fd); > + return 0; > +} > + > +int gen_rpm_list(int temp_dirfd, char *digest_list_filename, > + enum hash_algo algo, enum pgp_algos pgp_algo, int start_number, > + int num_digests, enum rpm_failures failure) > +{ > + u32 _pgp_algo = __cpu_to_be32(pgp_algo); > + u8 digest[MAX_DIGEST_SIZE] = { 0 }; > + char digest_str[MAX_DIGEST_SIZE * 2 + 1]; > + struct rpm_hdr hdr; > + struct rpm_entryinfo algo_entry, digest_entry; > + int digest_len = hash_digest_size[algo]; > + int ret, fd, d_len, i; > + > + d_len = hash_digest_size[algo] * 2 + 1; > + > + hdr.magic = __cpu_to_be32(0x8eade801); > + hdr.reserved = 0; > + hdr.tags = __cpu_to_be32(1); > + > + /* > + * Skip the algo section, to ensure that the parser recognizes MD5 as > + * the default hash algorithm. > + */ > + if (algo != HASH_ALGO_MD5) > + hdr.tags = __cpu_to_be32(2); > + > + hdr.datasize = __cpu_to_be32(d_len * num_digests); > + > + if (algo != HASH_ALGO_MD5) > + hdr.datasize = __cpu_to_be32(sizeof(u32) + d_len * num_digests); > + > + digest_entry.tag = __cpu_to_be32(RPMTAG_FILEDIGESTS); > + digest_entry.type = __cpu_to_be32(RPM_STRING_ARRAY_TYPE); > + digest_entry.offset = 0; > + digest_entry.count = __cpu_to_be32(num_digests); > + > + algo_entry.tag = __cpu_to_be32(RPMTAG_FILEDIGESTALGO); > + algo_entry.type = __cpu_to_be32(RPM_INT32_TYPE); > + algo_entry.offset = __cpu_to_be32(d_len * num_digests); > + algo_entry.count = __cpu_to_be32(1); > + > + switch (failure) { > + case RPM_FAILURE_WRONG_MAGIC: > + hdr.magic++; > + break; > + case RPM_FAILURE_BAD_DATA_OFFSET: > + algo_entry.offset = __cpu_to_be32(UINT_MAX); > + break; > + case RPM_FAILURE_WRONG_TAGS: > + hdr.tags = __cpu_to_be32(2 + 10); > + break; > + case RPM_FAILURE_WRONG_DIGEST_COUNT: > + /* We need to go beyond the algorithm, to fail. */ > + digest_entry.count = __cpu_to_be32(num_digests + 5); > + break; > + case RPM_FAILURE_DIGEST_WRONG_TYPE: > + digest_entry.type = __cpu_to_be32(RPM_INT32_TYPE); > + break; > + default: > + break; > + } > + > + fd = openat(temp_dirfd, digest_list_filename, > + O_WRONLY | O_CREAT | O_TRUNC, 0600); > + if (fd == -1) > + return -errno; > + > + ret = write(fd, (u8 *)&hdr, sizeof(hdr)); > + if (ret != sizeof(hdr)) > + return -errno; > + > + if (algo != HASH_ALGO_MD5) { > + ret = write(fd, (u8 *)&algo_entry, sizeof(algo_entry)); > + if (ret != sizeof(algo_entry)) > + return -errno; > + } > + > + ret = write(fd, (u8 *)&digest_entry, sizeof(digest_entry)); > + if (ret != sizeof(digest_entry)) > + return -errno; > + > + *(u32 *)digest = start_number; > + > + for (i = 0; i < num_digests; i++) { > + bin2hex(digest_str, digest, digest_len); > + > + ret = write(fd, (u8 *)digest_str, d_len); > + if (ret != d_len) > + return -errno; > + > + (*(u32 *)digest)++; > + } > + > + if (algo != HASH_ALGO_MD5) { > + ret = write(fd, (u8 *)&_pgp_algo, sizeof(_pgp_algo)); > + if (ret != sizeof(_pgp_algo)) > + return -errno; > + } > + > + close(fd); > + return 0; > +} > + > +int create_file(int temp_dirfd, char *filename, char *digest_list_filename) > +{ > + int ret = 0, fd; > + > + fd = openat(temp_dirfd, filename, O_WRONLY | O_CREAT | O_TRUNC, 0600); > + if (fd == -1) > + return -errno; > + > + if (!digest_list_filename) > + goto out; > + > + ret = fsetxattr(fd, XATTR_NAME_DIGEST_LIST, digest_list_filename, > + strlen(digest_list_filename) + 1, 0); > + if (ret == -1) > + ret = -errno; > +out: > + close(fd); > + return ret; > +} > diff --git a/tools/testing/selftests/digest_cache/generators.h b/tools/testing/selftests/digest_cache/generators.h > new file mode 100644 > index 000000000000..1c83e531b799 > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/generators.h > @@ -0,0 +1,19 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Header of generators.c. > + */ > + > +#include "common.h" > +#include "common_user.h" > + > +int gen_tlv_list(int temp_dirfd, char *digest_list_filename, > + enum hash_algo algo, int start_number, int num_digests, > + enum tlv_failures failure); > +int gen_rpm_list(int temp_dirfd, char *digest_list_filename, > + enum hash_algo algo, enum pgp_algos pgp_algo, int start_number, > + int num_digests, enum rpm_failures failure); > +int create_file(int temp_dirfd, char *filename, char *digest_list_filename); > diff --git a/tools/testing/selftests/digest_cache/testmod/Makefile b/tools/testing/selftests/digest_cache/testmod/Makefile > new file mode 100644 > index 000000000000..1ba1c7f08658 > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/testmod/Makefile > @@ -0,0 +1,16 @@ > +KDIR ?= ../../../../.. > + > +MODULES = digest_cache_kern.ko > + > +obj-m += digest_cache_kern.o > + > +digest_cache_kern-y := kern.o ../common.o > + > +all: > + +$(Q)$(MAKE) -C $(KDIR) M=$$PWD modules > + > +clean: > + +$(Q)$(MAKE) -C $(KDIR) M=$$PWD clean > + > +install: all > + +$(Q)$(MAKE) -C $(KDIR) M=$$PWD modules_install > diff --git a/tools/testing/selftests/digest_cache/testmod/kern.c b/tools/testing/selftests/digest_cache/testmod/kern.c > new file mode 100644 > index 000000000000..7215ef638e66 > --- /dev/null > +++ b/tools/testing/selftests/digest_cache/testmod/kern.c > @@ -0,0 +1,564 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> > + * > + * Implement the kernel module to interact with the digest_cache LSM. > + */ > + > +#define pr_fmt(fmt) "DIGEST CACHE TEST: "fmt > +#include <linux/module.h> > +#include <linux/namei.h> > +#include <linux/security.h> > +#include <linux/dynamic_debug.h> > +#include <linux/digest_cache.h> > +#include <linux/kprobes.h> > +#include <linux/cpu.h> > +#include <linux/kernel_read_file.h> > +#include <crypto/hash_info.h> > + > +#include "../common.h" > + > +struct verif { > + int (*update)(struct file *file); > + ssize_t (*read)(struct file *file, char __user *buf, size_t datalen, > + loff_t *ppos); > + bool enabled; > +}; > + > +struct read_work { > + struct work_struct work; > + char *path_str; > + int ret; > +}; > + > +static struct dentry *test, *notify_inodes; > +static struct digest_cache *digest_cache; > +static digest_cache_found_t found; > +static int cur_verif_index; > +static u8 prefetch_buf[4096]; > +static u8 notify_inodes_buf[4096]; > +static struct read_work w[MAX_WORKS]; > + > +static int filenames_update(struct file *file) > +{ > + char *filename = (char *)file->f_path.dentry->d_name.name; > + > + return digest_cache_verif_set(file, "filenames", filename, > + strlen(filename) + 1); > +} > + > +static int number_update(struct file *file) > +{ > + const char *filename = file_dentry(file)->d_name.name; > + size_t filename_len = strlen(filename); > + u64 number = U64_MAX; > + int ret; > + > + while (filename_len) { > + if (filename[filename_len - 1] < '0' || > + filename[filename_len - 1] > '9') > + break; > + > + filename_len--; > + } > + > + ret = kstrtoull(filename + filename_len, 10, &number); > + if (ret < 0) { > + pr_debug("Failed to convert filename %s into number\n", > + file_dentry(file)->d_name.name); > + return ret; > + } > + > + return digest_cache_verif_set(file, "number", &number, sizeof(number)); > +} > + > +static ssize_t filenames_read(struct file *file, char __user *buf, > + size_t datalen, loff_t *ppos) > +{ > + loff_t _ppos = 0; > + char *filenames_list; > + > + filenames_list = digest_cache_verif_get(found ? > + digest_cache_from_found_t(found) : digest_cache, > + verifs_str[VERIF_FILENAMES]); > + if (!filenames_list) > + return -ENOENT; > + > + return simple_read_from_buffer(buf, datalen, &_ppos, filenames_list, > + strlen(filenames_list) + 1); > +} > + > +static ssize_t number_read(struct file *file, char __user *buf, size_t datalen, > + loff_t *ppos) > +{ > + loff_t _ppos = 0; > + u64 *number; > + char temp[20]; > + ssize_t len; > + > + number = digest_cache_verif_get(found ? > + digest_cache_from_found_t(found) : > + digest_cache, verifs_str[VERIF_NUMBER]); > + if (!number) > + return -ENOENT; > + > + len = snprintf(temp, sizeof(temp), "%llu", *number); > + > + return simple_read_from_buffer(buf, datalen, &_ppos, temp, len); > +} > + > +static int prefetch_update(struct file *file) > +{ > + char *filename = (char *)file->f_path.dentry->d_name.name; > + char *start_ptr = prefetch_buf, *end_ptr; > + int ret; > + > + ret = digest_cache_verif_set(file, "probe_digest_cache", "1", 1); > + if (!ret) { > + /* Don't include duplicates of requested digest lists. */ > + while ((end_ptr = strchrnul(start_ptr, ','))) { > + if (end_ptr > start_ptr && > + !strncmp(start_ptr, filename, end_ptr - start_ptr)) > + return 0; > + > + if (!*end_ptr) > + break; > + > + start_ptr = end_ptr + 1; > + } > + } > + > + if (prefetch_buf[0]) > + strlcat(prefetch_buf, ",", sizeof(prefetch_buf)); > + > + strlcat(prefetch_buf, filename, sizeof(prefetch_buf)); > + return 0; > +} > + > +static ssize_t prefetch_read(struct file *file, char __user *buf, > + size_t datalen, loff_t *ppos) > +{ > + loff_t _ppos = 0; > + ssize_t ret; > + > + ret = simple_read_from_buffer(buf, datalen, &_ppos, prefetch_buf, > + strlen(prefetch_buf) + 1); > + memset(prefetch_buf, 0, sizeof(prefetch_buf)); > + return ret; > +} > + > +static int test_digest_cache_change(struct notifier_block *nb, > + unsigned long event, void *data) > +{ > + struct digest_cache_event_data *event_data = data; > + char i_ino_str[10]; > + > + if (event != DIGEST_CACHE_RESET) > + return NOTIFY_DONE; > + > + if (notify_inodes_buf[0]) > + strlcat(notify_inodes_buf, ",", sizeof(notify_inodes_buf)); > + > + snprintf(i_ino_str, sizeof(i_ino_str), "%lu", event_data->inode->i_ino); > + strlcat(notify_inodes_buf, i_ino_str, sizeof(notify_inodes_buf)); > + return 0; > +} > + > +static struct notifier_block digest_cache_notifier = { > + .notifier_call = test_digest_cache_change, > +}; > + > +static ssize_t write_notify_inodes(struct file *file, const char __user *buf, > + size_t datalen, loff_t *ppos) > +{ > + memset(notify_inodes_buf, 0, sizeof(notify_inodes_buf)); > + return datalen; > +} > + > +static ssize_t read_notify_inodes(struct file *file, char __user *buf, > + size_t datalen, loff_t *ppos) > +{ > + loff_t _ppos = 0; > + > + return simple_read_from_buffer(buf, datalen, &_ppos, notify_inodes_buf, > + strlen(notify_inodes_buf) + 1); > +} > + > +static struct verif verifs_methods[] = { > + [VERIF_FILENAMES] = { .update = filenames_update, > + .read = filenames_read }, > + [VERIF_NUMBER] = { .update = number_update, .read = number_read }, > + [VERIF_PREFETCH] = { .update = prefetch_update, .read = prefetch_read }, > +}; > + > +static void digest_cache_get_put_work(struct work_struct *work) > +{ > + struct read_work *w = container_of(work, struct read_work, work); > + struct digest_cache *digest_cache; > + struct path path; > + > + w->ret = kern_path(w->path_str, 0, &path); > + if (w->ret < 0) > + return; > + > + digest_cache = digest_cache_get(path.dentry); > + > + path_put(&path); > + > + if (!digest_cache) { > + w->ret = -ENOENT; > + return; > + } > + > + digest_cache_put(digest_cache); > + w->ret = 0; > +} > + > +static int digest_cache_get_put_async(char *path_str, int start_number, > + int end_number) > +{ > + int ret = 0, i; > + > + cpus_read_lock(); > + for (i = start_number; i <= end_number; i++) { > + w[i].path_str = kasprintf(GFP_KERNEL, "%s%u", path_str, i); > + if (!w[i].path_str) { > + ret = -ENOMEM; > + break; > + } > + > + INIT_WORK_ONSTACK(&w[i].work, digest_cache_get_put_work); > + schedule_work_on(i % num_online_cpus(), &w[i].work); > + } > + cpus_read_unlock(); > + > + for (i = start_number; i <= end_number; i++) { > + if (!w[i].path_str) > + continue; > + > + flush_work(&w[i].work); > + destroy_work_on_stack(&w[i].work); > + kfree(w[i].path_str); > + w[i].path_str = NULL; > + if (!ret) > + ret = w[i].ret; > + } > + > + return ret; > +} > + > +static ssize_t write_request(struct file *file, const char __user *buf, > + size_t datalen, loff_t *ppos) > +{ > + char *data, *data_ptr, *cmd_str, *path_str, *algo_str, *digest_str; > + char *verif_name_str, *start_number_str, *end_number_str; > + u8 digest[64]; > + struct path path; > + int ret, cmd, algo, verif_index, start_number, end_number; > + > + data = memdup_user_nul(buf, datalen); > + if (IS_ERR(data)) > + return PTR_ERR(data); > + > + data_ptr = data; > + > + cmd_str = strsep(&data_ptr, "|"); > + if (!cmd_str) { > + pr_debug("No command\n"); > + ret = -EINVAL; > + goto out; > + } > + > + cmd = match_string(commands_str, DIGEST_CACHE__LAST, cmd_str); > + if (cmd < 0) { > + pr_err("Unknown command %s\n", cmd_str); > + ret = -ENOENT; > + goto out; > + } > + > + switch (cmd) { > + case DIGEST_CACHE_GET: > + found = 0UL; > + > + path_str = strsep(&data_ptr, "|"); > + if (!path_str) { > + pr_debug("No path\n"); > + ret = -EINVAL; > + goto out; > + } > + > + ret = kern_path(path_str, 0, &path); > + if (ret < 0) { > + pr_debug("Cannot find file %s\n", path_str); > + goto out; > + } > + > + if (digest_cache) { > + pr_debug("Digest cache exists, doing a put\n"); > + digest_cache_put(digest_cache); > + } > + > + digest_cache = digest_cache_get(path.dentry); > + ret = digest_cache ? 0 : -ENOENT; > + pr_debug("digest cache get %s, ret: %d\n", path_str, ret); > + path_put(&path); > + break; > + case DIGEST_CACHE_LOOKUP: > + if (!digest_cache) { > + pr_debug("No digest cache\n"); > + ret = -ENOENT; > + goto out; > + } > + > + path_str = strsep(&data_ptr, "|"); > + if (!path_str) { > + pr_debug("No path\n"); > + ret = -EINVAL; > + goto out; > + } > + > + algo_str = strsep(&data_ptr, ":"); > + digest_str = data_ptr; > + > + if (!algo_str || !digest_str) { > + pr_debug("No algo or digest\n"); > + ret = -EINVAL; > + goto out; > + } > + > + algo = match_string(hash_algo_name, HASH_ALGO__LAST, algo_str); > + if (algo < 0) { > + pr_err("Unknown algorithm %s", algo_str); > + ret = -ENOENT; > + goto out; > + } > + > + ret = hex2bin(digest, digest_str, hash_digest_size[algo]); > + if (ret < 0) { > + pr_debug("Invalid digest %s\n", digest_str); > + goto out; > + } > + > + ret = kern_path(path_str, 0, &path); > + if (ret < 0) { > + pr_debug("Cannot find file %s\n", path_str); > + goto out; > + } > + > + ret = -ENOENT; > + > + found = digest_cache_lookup(path.dentry, digest_cache, digest, > + algo); > + path_put(&path); > + if (found) > + ret = 0; > + > + pr_debug("%s:%s lookup %s, ret: %d\n", algo_str, digest_str, > + path_str, ret); > + break; > + case DIGEST_CACHE_PUT: > + if (digest_cache) { > + digest_cache_put(digest_cache); > + digest_cache = NULL; > + } > + ret = 0; > + pr_debug("digest cache put, ret: %d\n", ret); > + break; > + case DIGEST_CACHE_ENABLE_VERIF: > + case DIGEST_CACHE_DISABLE_VERIF: > + memset(prefetch_buf, 0, sizeof(prefetch_buf)); > + fallthrough; > + case DIGEST_CACHE_SET_VERIF: > + verif_name_str = strsep(&data_ptr, "|"); > + if (!verif_name_str) { > + pr_debug("No verifier name\n"); > + ret = -EINVAL; > + goto out; > + } > + > + verif_index = match_string(verifs_str, ARRAY_SIZE(verifs_str), > + verif_name_str); > + if (verif_index < 0) { > + pr_err("Unknown verifier name %s\n", verif_name_str); > + ret = -ENOENT; > + goto out; > + } > + > + if (cmd == DIGEST_CACHE_ENABLE_VERIF) > + verifs_methods[verif_index].enabled = true; > + else if (cmd == DIGEST_CACHE_DISABLE_VERIF) > + verifs_methods[verif_index].enabled = false; > + else > + cur_verif_index = verif_index; > + > + ret = 0; > + pr_debug("digest cache %s %s, ret: %d\n", cmd_str, > + verif_name_str, ret); > + break; > + case DIGEST_CACHE_GET_PUT_ASYNC: > + path_str = strsep(&data_ptr, "|"); > + if (!path_str) { > + pr_debug("No path\n"); > + ret = -EINVAL; > + goto out; > + } > + > + start_number_str = strsep(&data_ptr, "|"); > + if (!start_number_str) { > + pr_debug("No start number\n"); > + ret = -EINVAL; > + goto out; > + } > + > + ret = kstrtoint(start_number_str, 10, &start_number); > + if (ret < 0) { > + pr_debug("Invalid start number %s\n", start_number_str); > + ret = -EINVAL; > + goto out; > + } > + > + end_number_str = strsep(&data_ptr, "|"); > + if (!end_number_str) { > + pr_debug("No end number\n"); > + ret = -EINVAL; > + goto out; > + } > + > + ret = kstrtoint(end_number_str, 10, &end_number); > + if (ret < 0) { > + pr_debug("Invalid end number %s\n", end_number_str); > + ret = -EINVAL; > + goto out; > + } > + > + if (end_number - start_number >= MAX_WORKS) { > + pr_debug("Too many works (%d), max %d\n", > + end_number - start_number, MAX_WORKS - 1); > + ret = -EINVAL; > + goto out; > + } > + > + ret = digest_cache_get_put_async(path_str, start_number, > + end_number); > + pr_debug("digest cache %s on %s, start: %d, end: %d, ret: %d\n", > + cmd_str, path_str, start_number, end_number, ret); > + break; > + default: > + ret = -EINVAL; > + break; > + } > +out: > + kfree(data); > + return ret ?: datalen; > +} > + > +static ssize_t read_request(struct file *file, char __user *buf, size_t datalen, > + loff_t *ppos) > +{ > + return verifs_methods[cur_verif_index].read(file, buf, datalen, ppos); > +} > + > +static const struct file_operations digest_cache_test_ops = { > + .open = generic_file_open, > + .write = write_request, > + .read = read_request, > + .llseek = generic_file_llseek, > +}; > + > +static const struct file_operations digest_cache_notify_inodes_ops = { > + .open = generic_file_open, > + .write = write_notify_inodes, > + .read = read_notify_inodes, > + .llseek = generic_file_llseek, > +}; > + > +static int __kprobes kernel_post_read_file_hook(struct kprobe *p, > + struct pt_regs *regs) > +{ > +#ifdef CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS > + struct file *file = (struct file *)regs_get_kernel_argument(regs, 0); > + enum kernel_read_file_id id = regs_get_kernel_argument(regs, 3); > +#else > + struct file *file = NULL; > + enum kernel_read_file_id id = READING_UNKNOWN; > +#endif > + int ret, i; > + > + if (id != READING_DIGEST_LIST) > + return 0; > + > + for (i = 0; i < ARRAY_SIZE(verifs_methods); i++) { > + if (!verifs_methods[i].enabled) > + continue; > + > + ret = verifs_methods[i].update(file); > + if (ret < 0) > + return 0; > + } > + > + return 0; > +} > + > +static struct kprobe kp = { > + .symbol_name = "security_kernel_post_read_file", > +}; > + > +static int __init digest_cache_test_init(void) > +{ > + int ret; > + > + kp.pre_handler = kernel_post_read_file_hook; > + > + ret = register_kprobe(&kp); > + if (ret < 0) { > + pr_err("register_kprobe failed, returned %d\n", ret); > + return ret; > + } > + > + test = securityfs_create_file("digest_cache_test", 0660, NULL, NULL, > + &digest_cache_test_ops); > + if (IS_ERR(test)) { > + ret = PTR_ERR(test); > + goto out_kprobe; > + } > + > + notify_inodes = securityfs_create_file("digest_cache_notify_inodes", > + 0660, NULL, NULL, > + &digest_cache_notify_inodes_ops); > + if (IS_ERR(notify_inodes)) { > + ret = PTR_ERR(notify_inodes); > + goto out_test; > + } > + > + ret = digest_cache_register_notifier(&digest_cache_notifier); > + if (ret < 0) > + goto out_notify_inodes; > + > + return 0; > + > +out_notify_inodes: > + securityfs_remove(notify_inodes); > +out_test: > + securityfs_remove(test); > +out_kprobe: > + unregister_kprobe(&kp); > + return ret; > +} > + > +static void __exit digest_cache_test_fini(void) > +{ > + if (digest_cache) > + digest_cache_put(digest_cache); > + > + digest_cache_unregister_notifier(&digest_cache_notifier); > + securityfs_remove(notify_inodes); > + securityfs_remove(test); > + unregister_kprobe(&kp); > + pr_debug("kprobe at %p unregistered\n", kp.addr); > +} > + > +module_init(digest_cache_test_init); > +module_exit(digest_cache_test_fini); > +MODULE_LICENSE("GPL"); BR, Jarkko