[PATCH v4 13/14] selftests/digest_cache: Add selftests for digest_cache LSM

Roberto Sassu roberto.sassu at huaweicloud.com
Tue Apr 16 10:39:11 UTC 2024


On 4/15/2024 9:47 PM, Jarkko Sakkinen wrote:
> On Mon Apr 15, 2024 at 5:24 PM EEST, Roberto Sassu wrote:
>> From: Roberto Sassu <roberto.sassu at huawei.com>
>>
>> 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 at huawei.com>
>> ---
>>   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 at huawei.com>
>>   L:	linux-security-module at vger.kernel.org
>>   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.

Ok.

Thanks

Roberto

>>   DIGITEQ AUTOMOTIVE MGB4 V4L2 DRIVER
>>   M:	Martin Tuma <martin.tuma at digiteqautomotive.com>
>> 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 at huawei.com>
>> + *
>> + * 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 at huawei.com>
>> + *
>> + * 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 at huawei.com>
>> + *
>> + * 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 at huawei.com>
>> + *
>> + * 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 at huawei.com>
>> + *
>> + * 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 at huawei.com>
>> + *
>> + * 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 at huawei.com>
>> + *
>> + * 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 at huawei.com>
>> + *
>> + * 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




More information about the Linux-security-module-archive mailing list