From patchwork Fri Jun 25 16:56:14 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 12345667 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 51EECC49EB7 for ; Fri, 25 Jun 2021 16:59:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 357AC61945 for ; Fri, 25 Jun 2021 16:59:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230418AbhFYRBq (ORCPT ); Fri, 25 Jun 2021 13:01:46 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]:3319 "EHLO frasgout.his.huawei.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230121AbhFYRBm (ORCPT ); Fri, 25 Jun 2021 13:01:42 -0400 Received: from fraeml714-chm.china.huawei.com (unknown [172.18.147.207]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4GBNJ53l8Fz6G8gh; Sat, 26 Jun 2021 00:49:09 +0800 (CST) Received: from roberto-ThinkStation-P620.huawei.com (10.204.63.22) by fraeml714-chm.china.huawei.com (10.206.15.33) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2176.2; Fri, 25 Jun 2021 18:59:18 +0200 From: Roberto Sassu To: CC: , , , , , Roberto Sassu Subject: [RFC][PATCH 12/12] digest_lists: Tests Date: Fri, 25 Jun 2021 18:56:14 +0200 Message-ID: <20210625165614.2284243-13-roberto.sassu@huawei.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20210625165614.2284243-1-roberto.sassu@huawei.com> References: <20210625165614.2284243-1-roberto.sassu@huawei.com> MIME-Version: 1.0 X-Originating-IP: [10.204.63.22] X-ClientProxiedBy: lhreml751-chm.china.huawei.com (10.201.108.201) To fraeml714-chm.china.huawei.com (10.206.15.33) X-CFilter-Loop: Reflected Precedence: bulk List-ID: This patch introduces a number of tests to ensure that the digest lists feature works as expected: - ``digest_list_add_del_test_file_upload``; - ``digest_list_add_del_test_file_upload_fault``; - ``digest_list_add_del_test_buffer_upload``; - ``digest_list_add_del_test_buffer_upload_fault``; - ``digest_list_fuzzing_test``. The tests are in ``tools/testing/selftests/digest_lists/selftest.c``. A description of the tests can be found in Documentation/security/digest_lists.rst. Signed-off-by: Roberto Sassu --- Documentation/security/digest_lists.rst | 50 + MAINTAINERS | 1 + tools/testing/selftests/Makefile | 1 + tools/testing/selftests/digest_lists/Makefile | 6 + tools/testing/selftests/digest_lists/common.c | 109 ++ tools/testing/selftests/digest_lists/common.h | 37 + tools/testing/selftests/digest_lists/config | 3 + .../testing/selftests/digest_lists/selftest.c | 1169 +++++++++++++++++ 8 files changed, 1376 insertions(+) create mode 100644 tools/testing/selftests/digest_lists/Makefile create mode 100644 tools/testing/selftests/digest_lists/common.c create mode 100644 tools/testing/selftests/digest_lists/common.h create mode 100644 tools/testing/selftests/digest_lists/config create mode 100644 tools/testing/selftests/digest_lists/selftest.c diff --git a/Documentation/security/digest_lists.rst b/Documentation/security/digest_lists.rst index 25b5665bbeaa..fe1313f52e2d 100644 --- a/Documentation/security/digest_lists.rst +++ b/Documentation/security/digest_lists.rst @@ -704,3 +704,53 @@ and to obtain all digest lists that include that digest. ``digests_count`` shows the current number of digests stored in the hash table by type. + + +Testing +======= + +This section introduces a number of tests to ensure that Huawei Digest +Lists works as expected: + +- ``digest_list_add_del_test_file_upload``; +- ``digest_list_add_del_test_file_upload_fault``; +- ``digest_list_add_del_test_buffer_upload``; +- ``digest_list_add_del_test_buffer_upload_fault``; +- ``digest_list_fuzzing_test``. + +The tests are in ``tools/testing/selftests/digest_lists/selftest.c``. + +The first four tests randomly perform add, delete and query of digest +lists. They internally keep track at any time of the digest lists that are +currently uploaded to the kernel. + +Also, digest lists are generated randomly by selecting an arbitrary digest +algorithm and an arbitrary the number of digests. To ensure a good number +of collisions, digests are a sequence of zeros, except for the first four +bytes that are set with a random number within a defined range. + +When a query operation is selected, a digest is chosen by getting another +random number within the same range. Then, the tests count how many times +the digest is found in the internally stored digest lists and in the query +result obtained from the kernel. The tests are successful if the obtained +numbers are the same. + +The ``file_upload`` variant creates a temporary file from a generated +digest list and sends its path to the kernel, so that the file is uploaded. +The ``digest_upload`` variant directly sends the digest list buffer to the +kernel (it will be done by the user space parser after it converts a digest +list not in the compact format). + +The ``fault`` variant performs the test by enabling the ad-hoc fault +injection mechanism in the kernel (accessible through +``/fail_digest_lists``). The fault injection mechanism randomly +injects errors during the addition and deletion of digest lists. When an +error occurs, the rollback mechanism performs the reverse operation until +the point the error occurred, so that the kernel is left in the same state +as when the requested operation began. Since the kernel returns the error +to user space, the tests also know that the operation didn't succeed and +behave accordingly (they also revert the internal state). + +Lastly, the fuzzing test simply sends randomly generated digest lists to +the kernel, to ensure that the parser is robust enough to handle malformed +data. diff --git a/MAINTAINERS b/MAINTAINERS index c86b410f2c2c..359c8ce912e4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -8392,6 +8392,7 @@ F: security/integrity/digest_lists/digest_list.h F: security/integrity/digest_lists/fs.c F: security/integrity/digest_lists/methods.c F: security/integrity/digest_lists/parser.c +F: tools/testing/selftests/digest_lists/ F: uapi/linux/digest_lists.h HUAWEI ETHERNET DRIVER diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index bc3299a20338..f5938fe9f3a7 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -8,6 +8,7 @@ TARGETS += clone3 TARGETS += core TARGETS += cpufreq TARGETS += cpu-hotplug +TARGETS += digest_lists TARGETS += drivers/dma-buf TARGETS += efivarfs TARGETS += exec diff --git a/tools/testing/selftests/digest_lists/Makefile b/tools/testing/selftests/digest_lists/Makefile new file mode 100644 index 000000000000..4e88b5677cc6 --- /dev/null +++ b/tools/testing/selftests/digest_lists/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS += -Wl,-no-as-needed -Wall -ggdb common.c +LDFLAGS += -lcrypto + +TEST_GEN_PROGS := selftest +include ../lib.mk diff --git a/tools/testing/selftests/digest_lists/common.c b/tools/testing/selftests/digest_lists/common.c new file mode 100644 index 000000000000..a1d0706f8c5f --- /dev/null +++ b/tools/testing/selftests/digest_lists/common.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: common.c + * Common functions. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +int write_buffer(char *path, char *buffer, size_t buffer_len) +{ + ssize_t to_write = buffer_len, written = 0; + int ret = 0, fd; + + fd = open(path, O_WRONLY); + if (fd < 0) + return -errno; + + while (to_write) { + written = write(fd, buffer + buffer_len - to_write, to_write); + if (written <= 0) { + ret = -errno; + break; + } + + to_write -= written; + } + + close(fd); + return ret; +} + +int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, + bool is_char) +{ + ssize_t len = 0, read_len; + int ret = 0, fd; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + if (alloc) { + *buffer = NULL; + *buffer_len = 0; + } + + while (1) { + if (alloc) { + if (*buffer_len == len) { + *buffer_len += BUFFER_SIZE; + *buffer = realloc(*buffer, *buffer_len + 1); + if (!*buffer) { + ret = -ENOMEM; + goto out; + } + } + } + + read_len = read(fd, *buffer + len, *buffer_len - len); + if (read_len < 0) { + ret = -errno; + goto out; + } + + if (!read_len) + break; + + len += read_len; + } + + *buffer_len = len; + if (is_char) + (*buffer)[(*buffer_len)++] = '\0'; +out: + close(fd); + if (ret < 0) { + if (alloc) { + free(*buffer); + *buffer = NULL; + } + } + + return ret; +} diff --git a/tools/testing/selftests/digest_lists/common.h b/tools/testing/selftests/digest_lists/common.h new file mode 100644 index 000000000000..60c275f42009 --- /dev/null +++ b/tools/testing/selftests/digest_lists/common.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: common.h + * Header of common.c + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 1024 + +int write_buffer(char *path, char *buffer, size_t buffer_len); +int read_buffer(char *path, char **buffer, size_t *buffer_len, bool alloc, + bool is_char); diff --git a/tools/testing/selftests/digest_lists/config b/tools/testing/selftests/digest_lists/config new file mode 100644 index 000000000000..faafc742974c --- /dev/null +++ b/tools/testing/selftests/digest_lists/config @@ -0,0 +1,3 @@ +CONFIG_DIGEST_LISTS=y +CONFIG_FAULT_INJECTION=y +CONFIG_FAULT_INJECTION_DEBUG_FS=y diff --git a/tools/testing/selftests/digest_lists/selftest.c b/tools/testing/selftests/digest_lists/selftest.c new file mode 100644 index 000000000000..6727e1f3f8fd --- /dev/null +++ b/tools/testing/selftests/digest_lists/selftest.c @@ -0,0 +1,1169 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: selftest.c + * Functions to test Huawei Digest Lists. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "../kselftest_harness.h" + +#define HDR_ASCII_FMT \ + "actions: %d, version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n" +#define QUERY_RESULT_FMT \ + "%s (actions: %d): version: %d, algo: %s, type: %d, modifiers: %d, count: %d, datalen: %d\n" +#define QUERY_RESULT_DIGEST_LIST_FMT "%s (actions: %d): type: %d, size: %lld\n" + +enum compact_types { COMPACT_KEY, COMPACT_PARSER, COMPACT_FILE, + COMPACT_METADATA, COMPACT_DIGEST_LIST, COMPACT__LAST }; + +enum compact_modifiers { COMPACT_MOD_IMMUTABLE, COMPACT_MOD__LAST }; + +enum compact_actions { COMPACT_ACTION_IMA_MEASURED, + COMPACT_ACTION_IMA_APPRAISED, + COMPACT_ACTION_IMA_APPRAISED_DIGSIG, + COMPACT_ACTION__LAST }; + +enum ops { DIGEST_LIST_ADD, DIGEST_LIST_DEL, DIGEST_LIST_OP__LAST }; + +struct compact_list_hdr { + __u8 version; + __u8 _reserved; + __le16 type; + __le16 modifiers; + __le16 algo; + __le32 count; + __le32 datalen; +} __packed; + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +#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 DIGEST_LIST_PATH_TEMPLATE "/tmp/digest_list.XXXXXX" +#define PARSER_BASENAME "manage_digest_lists" + +#define INTEGRITY_DIR "/sys/kernel/security/integrity" + +#define DIGEST_LIST_DIR INTEGRITY_DIR "/digest_lists" +#define DIGEST_QUERY_PATH DIGEST_LIST_DIR "/digest_query" +#define DIGEST_LABEL_PATH DIGEST_LIST_DIR "/digest_label" +#define DIGEST_LIST_ADD_PATH DIGEST_LIST_DIR "/digest_list_add" +#define DIGEST_LIST_DEL_PATH DIGEST_LIST_DIR "/digest_list_del" +#define DIGEST_LISTS_LOADED_PATH DIGEST_LIST_DIR "/digest_lists_loaded" +#define DIGESTS_COUNT DIGEST_LIST_DIR "/digests_count" + +#define IMA_POLICY_PATH INTEGRITY_DIR "/ima/policy" +#define IMA_MEASUREMENTS_PATH INTEGRITY_DIR "/ima/ascii_runtime_measurements" + +#define DIGEST_LIST_DEBUGFS_DIR "/sys/kernel/debug/fail_digest_lists" +#define DIGEST_LIST_DEBUGFS_TASK_FILTER DIGEST_LIST_DEBUGFS_DIR "/task-filter" +#define DIGEST_LIST_DEBUGFS_PROBABILITY DIGEST_LIST_DEBUGFS_DIR "/probability" +#define DIGEST_LIST_DEBUGFS_TIMES DIGEST_LIST_DEBUGFS_DIR "/times" +#define DIGEST_LIST_DEBUGFS_VERBOSE DIGEST_LIST_DEBUGFS_DIR "/verbose" +#define PROCFS_SELF_FAULT "/proc/self/make-it-fail" + +#define MAX_LINE_LENGTH 512 +#define LABEL_LEN 32 +#define MAX_DIGEST_COUNT 100 +#define MAX_DIGEST_LISTS 100 +#define MAX_DIGEST_BLOCKS 10 +#define MAX_DIGEST_VALUE 10 +#define MAX_SEARCH_ATTEMPTS 10 +#define NUM_QUERIES 1000 +#define MAX_DIGEST_LIST_SIZE 10000 +#define NUM_ITERATIONS 100000 + +enum upload_types { UPLOAD_FILE, UPLOAD_BUFFER }; + +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", +}; + +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, +}; + +struct digest_list_item { + loff_t size; + u8 *buf; + u8 actions; + char digest_str[64 * 2 + 1]; + enum hash_algo algo; + char filename_suffix[6 + 1]; +}; + +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] + +static inline char *hex_byte_pack(char *buf, unsigned char byte) +{ + *buf++ = hex_asc_hi(byte); + *buf++ = hex_asc_lo(byte); + return buf; +} + +/* from lib/hexdump.c (Linux kernel) */ +static int hex_to_bin(char ch) +{ + if ((ch >= '0') && (ch <= '9')) + return ch - '0'; + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) + return ch - 'a' + 10; + return -1; +} + +int _hex2bin(unsigned char *dst, const char *src, size_t count) +{ + while (count--) { + int hi = hex_to_bin(*src++); + int lo = hex_to_bin(*src++); + + if ((hi < 0) || (lo < 0)) + return -1; + + *dst++ = (hi << 4) | lo; + } + return 0; +} + +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; +} + +u32 num_max_digest_lists = MAX_DIGEST_LISTS; +u32 digest_lists_pos; +struct digest_list_item *digest_lists[MAX_DIGEST_LISTS]; + +enum hash_algo ima_hash_algo = HASH_ALGO__LAST; + +static enum hash_algo get_ima_hash_algo(void) +{ + char *measurement_list, *measurement_list_ptr; + size_t measurement_list_len; + int ret, i = 0; + + if (ima_hash_algo != HASH_ALGO__LAST) + return ima_hash_algo; + + ret = read_buffer(IMA_MEASUREMENTS_PATH, &measurement_list, + &measurement_list_len, true, true); + if (ret < 0) + return ret; + + measurement_list_ptr = measurement_list; + while ((strsep(&measurement_list_ptr, " ")) && i++ < 2) + ; + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (!strncmp(hash_algo_name[i], measurement_list_ptr, + strlen(hash_algo_name[i]))) { + ima_hash_algo = i; + break; + } + } + + free(measurement_list); + return ima_hash_algo; +} + +int calc_digest(u8 *digest, void *data, u64 len, enum hash_algo algo) +{ + EVP_MD_CTX *mdctx; + const EVP_MD *md; + int ret = -EINVAL; + + OpenSSL_add_all_algorithms(); + + md = EVP_get_digestbyname(hash_algo_name[algo]); + if (!md) + goto out; + + mdctx = EVP_MD_CTX_create(); + if (!mdctx) + goto out; + + if (EVP_DigestInit_ex(mdctx, md, NULL) != 1) + goto out_mdctx; + + if (EVP_DigestUpdate(mdctx, data, len) != 1) + goto out_mdctx; + + if (EVP_DigestFinal_ex(mdctx, digest, NULL) != 1) + goto out_mdctx; + + ret = 0; +out_mdctx: + EVP_MD_CTX_destroy(mdctx); +out: + EVP_cleanup(); + return ret; +} + +int calc_file_digest(u8 *digest, char *path, enum hash_algo algo) +{ + void *data = MAP_FAILED; + struct stat st; + int fd, ret = 0; + + if (stat(path, &st) == -1) + return -EACCES; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + if (st.st_size) { + data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) { + ret = -ENOMEM; + goto out; + } + } + + ret = calc_digest(digest, data, st.st_size, algo); +out: + if (data != MAP_FAILED) + munmap(data, st.st_size); + + close(fd); + return ret; +} + +static struct digest_list_item *digest_list_generate(void) +{ + struct digest_list_item *digest_list; + struct compact_list_hdr *hdr_array; + u8 *buf_ptr; + u32 num_digest_blocks = 0; + u8 digest[64]; + int ret, i, j; + + digest_list = calloc(1, sizeof(*digest_list)); + if (!digest_list) + return NULL; + + while (!num_digest_blocks) { + ret = getrandom(&num_digest_blocks, + sizeof(num_digest_blocks), 0); + if (ret < 0) + goto out; + + num_digest_blocks = num_digest_blocks % MAX_DIGEST_BLOCKS; + } + + hdr_array = calloc(num_digest_blocks, sizeof(*hdr_array)); + if (!hdr_array) + goto out; + + for (i = 0; i < num_digest_blocks; i++) { + ret = getrandom(&hdr_array[i], sizeof(hdr_array[i]), 0); + if (ret < 0) + goto out; + + hdr_array[i].version = 1; + /* COMPACT_DIGEST_LIST type is not allowed. */ + hdr_array[i].type = hdr_array[i].type % (COMPACT__LAST - 1); + hdr_array[i].modifiers = + hdr_array[i].modifiers % (1 << COMPACT_MOD_IMMUTABLE) + 1; + hdr_array[i].algo = hdr_array[i].algo % HASH_ALGO_RIPE_MD_128; + hdr_array[i].count = hdr_array[i].count % MAX_DIGEST_COUNT; + + while (!hdr_array[i].count) { + ret = getrandom(&hdr_array[i].count, + sizeof(hdr_array[i].count), 0); + if (ret < 0) + goto out; + + hdr_array[i].count = + hdr_array[i].count % MAX_DIGEST_COUNT; + } + + hdr_array[i].datalen = + hdr_array[i].count * hash_digest_size[hdr_array[i].algo]; + + digest_list->size += sizeof(*hdr_array) + hdr_array[i].datalen; + } + + digest_list->buf = calloc(digest_list->size, sizeof(unsigned char)); + if (!digest_list->buf) { + free(digest_list); + ret = -ENOMEM; + goto out; + } + + buf_ptr = digest_list->buf; + + for (i = 0; i < num_digest_blocks; i++) { + memcpy(buf_ptr, &hdr_array[i], sizeof(*hdr_array)); + buf_ptr += sizeof(*hdr_array); + + for (j = 0; j < hdr_array[i].count; j++) { + ret = getrandom(buf_ptr, sizeof(u32), 0); + if (ret < 0) + goto out; + + *(u32 *)buf_ptr = *(u32 *)buf_ptr % MAX_DIGEST_VALUE; + buf_ptr += hash_digest_size[hdr_array[i].algo]; + } + } + + digest_list->algo = get_ima_hash_algo(); + if (digest_list->algo == HASH_ALGO__LAST) { + ret = -ENOENT; + goto out; + } + + ret = calc_digest(digest, digest_list->buf, digest_list->size, + digest_list->algo); + if (ret < 0) + goto out; + + _bin2hex(digest_list->digest_str, digest, + hash_digest_size[digest_list->algo]); + + ret = 0; +out: + if (ret < 0) { + free(digest_list->buf); + free(digest_list); + } + + free(hdr_array); + return !ret ? digest_list : NULL; +} + +static struct digest_list_item *digest_list_generate_random(void) +{ + struct digest_list_item *digest_list; + struct compact_list_hdr *hdr; + u32 size = 0; + u8 digest[64]; + int ret; + + digest_list = calloc(1, sizeof(*digest_list)); + if (!digest_list) + return NULL; + + while (!size) { + ret = getrandom(&size, sizeof(size), 0); + if (ret < 0) + goto out; + + size = size % MAX_DIGEST_LIST_SIZE; + } + + digest_list->size = size; + digest_list->buf = calloc(digest_list->size, sizeof(unsigned char)); + if (!digest_list->buf) { + free(digest_list); + ret = -ENOMEM; + goto out; + } + + ret = getrandom(digest_list->buf, digest_list->size, 0); + if (ret < 0) + goto out; + + hdr = (struct compact_list_hdr *)digest_list->buf; + hdr->version = 1; + hdr->type = hdr->type % (COMPACT__LAST - 1); + hdr->algo = hdr->algo % HASH_ALGO__LAST; + + digest_list->algo = get_ima_hash_algo(); + if (digest_list->algo == HASH_ALGO__LAST) { + ret = -ENOENT; + goto out; + } + + ret = calc_digest(digest, digest_list->buf, digest_list->size, + digest_list->algo); + if (ret < 0) + goto out; + + _bin2hex(digest_list->digest_str, digest, + hash_digest_size[digest_list->algo]); + + ret = 0; +out: + if (ret < 0) { + free(digest_list->buf); + free(digest_list); + } + + return !ret ? digest_list : NULL; +} + +static int digest_list_upload(struct digest_list_item *digest_list, enum ops op, + enum upload_types upload_type, char *parser_path, + char *parser_mode) +{ + char path_template[] = DIGEST_LIST_PATH_TEMPLATE; + char *path_upload = DIGEST_LIST_ADD_PATH, *basename; + unsigned char *buffer = digest_list->buf; + size_t buffer_len = digest_list->size; + unsigned char rnd[3]; + int ret = 0, fd; + + if (op == DIGEST_LIST_ADD) { + if (upload_type == UPLOAD_FILE) { + fd = mkstemp(path_template); + if (fd < 0) + return -EPERM; + + close(fd); + ret = write_buffer(path_template, + (char *)digest_list->buf, + digest_list->size); + if (ret < 0) + goto out; + + buffer = (unsigned char *)path_template; + buffer_len = strlen(path_template); + } else { + ret = getrandom(rnd, sizeof(rnd), 0); + if (ret < 0) + goto out; + + _bin2hex(path_template + + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, rnd, + sizeof(rnd)); + } + + memcpy(digest_list->filename_suffix, + path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + 6); + } else { + memcpy(path_template + sizeof(DIGEST_LIST_PATH_TEMPLATE) - 7, + digest_list->filename_suffix, 6); + path_upload = DIGEST_LIST_DEL_PATH; + if (upload_type == UPLOAD_FILE) { + buffer = (unsigned char *)path_template; + buffer_len = strlen(path_template); + } + } + + if (upload_type == UPLOAD_BUFFER) { + basename = strrchr(path_template, '/'); + ret = write_buffer(DIGEST_LABEL_PATH, basename, + strlen(basename)); + if (ret < 0) + goto out; + } + + ret = write_buffer(path_upload, (char *)buffer, buffer_len); +out: + if (ret < 0 || op == DIGEST_LIST_DEL) + unlink(path_template); + + return ret; +} + +static int digest_list_check(struct digest_list_item *digest_list, enum ops op) +{ + char path[PATH_MAX]; + u8 digest_list_buf[MAX_LINE_LENGTH]; + char digest_list_info[MAX_LINE_LENGTH]; + ssize_t size = digest_list->size; + struct compact_list_hdr *hdr; + struct stat st; + int ret = 0, i, fd, path_len, len, read_len; + + path_len = snprintf(path, sizeof(path), "%s/%s-%s-digest_list.%s.ascii", + DIGEST_LISTS_LOADED_PATH, + hash_algo_name[digest_list->algo], + digest_list->digest_str, + digest_list->filename_suffix); + + path[path_len - 6] = '\0'; + + if (op == DIGEST_LIST_DEL) { + if (stat(path, &st) != -1) + return -EEXIST; + + path[path_len - 6] = '.'; + + if (stat(path, &st) != -1) + return -EEXIST; + + return 0; + } + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + while (size) { + len = read(fd, digest_list_buf, sizeof(digest_list_buf)); + if (len <= 0) { + ret = -errno; + goto out; + } + + if (memcmp(digest_list_buf, + digest_list->buf + digest_list->size - size, len)) { + ret = -EINVAL; + goto out; + } + + size -= len; + } + + close(fd); + + path[path_len - 6] = '.'; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + size = digest_list->size; + while (size) { + hdr = (struct compact_list_hdr *)(digest_list->buf + + digest_list->size - size); + + len = snprintf(digest_list_info, sizeof(digest_list_info), + HDR_ASCII_FMT, digest_list->actions, + hdr->version, hash_algo_name[hdr->algo], + hdr->type, hdr->modifiers, hdr->count, + hdr->datalen); + + read_len = read(fd, digest_list_buf, len); + + if (read_len != len || + memcmp(digest_list_info, digest_list_buf, len)) { + ret = -EIO; + goto out; + } + + size -= sizeof(*hdr); + + for (i = 0; i < hdr->count; i++) { + _bin2hex(digest_list_info, + digest_list->buf + digest_list->size - size, + hash_digest_size[hdr->algo]); + + read_len = read(fd, digest_list_buf, + hash_digest_size[hdr->algo] * 2 + 1); + + if (read_len != hash_digest_size[hdr->algo] * 2 + 1 || + memcmp(digest_list_info, digest_list_buf, + read_len - 1) || + digest_list_buf[read_len - 1] != '\n') { + ret = -EIO; + goto out; + } + + size -= hash_digest_size[hdr->algo]; + } + } +out: + close(fd); + return ret; +} + +static int digest_list_query(u8 *digest, enum hash_algo algo, + char **query_result) +{ + ssize_t len, to_write, written; + char query[256] = { 0 }; + size_t query_result_len; + int ret = 0, fd; + + len = snprintf(query, sizeof(query), "%s-", hash_algo_name[algo]); + + _bin2hex(query + len, digest, hash_digest_size[algo]); + len += hash_digest_size[algo] * 2 + 1; + + fd = open(DIGEST_QUERY_PATH, O_WRONLY); + if (fd < 0) + return -errno; + + to_write = len; + + while (to_write) { + written = write(fd, query + len - to_write, to_write); + if (written <= 0) { + ret = -errno; + break; + } + + to_write -= written; + } + + close(fd); + if (ret < 0) + return ret; + + return read_buffer(DIGEST_QUERY_PATH, query_result, &query_result_len, + true, true); +} + +static int *get_count_gen_lists(u8 *digest, enum hash_algo algo) +{ + struct compact_list_hdr *hdr; + u8 *buf_ptr; + loff_t size; + int i, j, *count; + + count = calloc(num_max_digest_lists, sizeof(*count)); + if (!count) + return count; + + for (i = 0; i < num_max_digest_lists; i++) { + if (!digest_lists[i]) + continue; + + size = digest_lists[i]->size; + buf_ptr = digest_lists[i]->buf; + + while (size) { + hdr = (struct compact_list_hdr *)buf_ptr; + if (hdr->algo != algo) { + buf_ptr += sizeof(*hdr) + hdr->datalen; + size -= sizeof(*hdr) + hdr->datalen; + continue; + } + + buf_ptr += sizeof(*hdr); + size -= sizeof(*hdr); + + for (j = 0; j < hdr->count; j++) { + if (!memcmp(digest, buf_ptr, + hash_digest_size[algo])) + count[i]++; + buf_ptr += hash_digest_size[algo]; + size -= hash_digest_size[algo]; + } + } + } + + return count; +} + +static int *get_count_kernel_query(u8 *digest, enum hash_algo algo) +{ + char *query_result = NULL, *query_result_ptr, *line; + char digest_list_info[MAX_LINE_LENGTH]; + char label[256]; + struct compact_list_hdr *hdr; + struct digest_list_item *digest_list; + size_t size, size_info; + int ret, i, *count = NULL; + + count = calloc(num_max_digest_lists, sizeof(*count)); + if (!count) + return count; + + ret = digest_list_query(digest, algo, &query_result); + if (ret < 0) + goto out; + + query_result_ptr = query_result; + + while ((line = strsep(&query_result_ptr, "\n"))) { + if (!strlen(line)) + continue; + + for (i = 0; i < num_max_digest_lists; i++) { + if (!digest_lists[i]) + continue; + + digest_list = digest_lists[i]; + size = digest_list->size; + + while (size) { + hdr = + (struct compact_list_hdr *)(digest_list->buf + + digest_list->size - size); + size -= sizeof(*hdr) + hdr->datalen; + + snprintf(label, sizeof(label), + "%s-%s-digest_list.%s", + hash_algo_name[digest_list->algo], + digest_list->digest_str, + digest_list->filename_suffix); + + /* From digest_query_show(). */ + size_info = snprintf(digest_list_info, + sizeof(digest_list_info), + QUERY_RESULT_FMT, label, + digest_list->actions, hdr->version, + hash_algo_name[hdr->algo], hdr->type, + hdr->modifiers, hdr->count, + hdr->datalen); + + /* strsep() replaced '\n' with '\0' in line. */ + digest_list_info[size_info - 1] = '\0'; + + if (!strcmp(digest_list_info, line)) { + count[i]++; + break; + } + } + } + } +out: + free(query_result); + if (ret < 0) + free(count); + + return (!ret) ? count : NULL; +} + +static int compare_count(u32 value, enum hash_algo algo, + struct __test_metadata *_metadata) +{ + int *count_gen_list_array, *count_kernel_query_array; + int count_gen_list = 0, count_kernel_query = 0; + u8 digest[64] = { 0 }; + int i; + + *(u32 *)digest = value; + + count_gen_list_array = get_count_gen_lists(digest, algo); + if (!count_gen_list_array) + return -EINVAL; + + count_kernel_query_array = get_count_kernel_query(digest, algo); + if (!count_kernel_query_array) { + free(count_gen_list_array); + return -EINVAL; + } + + for (i = 0; i < num_max_digest_lists; i++) { + count_gen_list += count_gen_list_array[i]; + count_kernel_query += count_kernel_query_array[i]; + } + + TH_LOG("value: %d, algo: %s, gen list digests: %d, kernel digests: %d", + value, hash_algo_name[algo], count_gen_list, count_kernel_query); + free(count_gen_list_array); + free(count_kernel_query_array); + return (count_gen_list == count_kernel_query) ? 0 : -EINVAL; +} + +static void digest_list_delete_all(struct __test_metadata *_metadata, + enum upload_types upload_type, + char *parser_path) +{ + int ret, i; + + for (i = 0; i < MAX_DIGEST_LISTS; i++) { + if (!digest_lists[i]) + continue; + + ret = digest_list_upload(digest_lists[i], DIGEST_LIST_DEL, + upload_type, parser_path, "normal"); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed\n"); + } + + free(digest_lists[i]->buf); + free(digest_lists[i]); + digest_lists[i] = NULL; + } +} + +FIXTURE(test) +{ + enum upload_types upload_type; +}; + +FIXTURE_SETUP(test) +{ +} + +FIXTURE_TEARDOWN(test) +{ + digest_list_delete_all(_metadata, self->upload_type, NULL); +} + +static int enable_fault_injection(void) +{ + int ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TASK_FILTER, "Y", 1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_PROBABILITY, "1", 1); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_TIMES, "10000", 5); + if (ret < 0) + return ret; + + ret = write_buffer(DIGEST_LIST_DEBUGFS_VERBOSE, "1", 1); + if (ret < 0) + return ret; + + ret = write_buffer(PROCFS_SELF_FAULT, "1", 1); + if (ret < 0) + return ret; + + return 0; +} + +static void digest_list_add_del_test(struct __test_metadata *_metadata, + int fault_injection, + enum upload_types upload_type) +{ + u32 value; + enum ops op; + enum hash_algo algo; + int ret, i, cur_queries = 1; + + while (cur_queries <= NUM_QUERIES) { + ret = getrandom(&op, 1, 0); + ASSERT_EQ(1, ret) { + TH_LOG("getrandom() failed\n"); + } + + op = op % 2; + + switch (op) { + case DIGEST_LIST_ADD: + TH_LOG("add digest list..."); + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (!digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + + digest_lists[digest_lists_pos] = digest_list_generate(); + ASSERT_NE(NULL, digest_lists[digest_lists_pos]) { + TH_LOG("digest_list_generate() failed"); + } + + ret = digest_list_upload(digest_lists[digest_lists_pos], + op, upload_type, NULL, NULL); + /* Handle failures from fault injection. */ + if (fault_injection && ret < 0) { + TH_LOG("handle failure..."); + ret = digest_list_check( + digest_lists[digest_lists_pos], + DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_lists[digest_lists_pos]->buf); + free(digest_lists[digest_lists_pos]); + digest_lists[digest_lists_pos] = NULL; + break; + } + + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[digest_lists_pos], + op); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + break; + case DIGEST_LIST_DEL: + TH_LOG("delete digest list..."); + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + + for (i = 0; i < MAX_SEARCH_ATTEMPTS; i++) { + ret = getrandom(&digest_lists_pos, + sizeof(digest_lists_pos), 0); + ASSERT_EQ(sizeof(digest_lists_pos), ret) { + TH_LOG("getrandom() failed"); + } + + digest_lists_pos = + digest_lists_pos % num_max_digest_lists; + + if (digest_lists[digest_lists_pos]) + break; + } + + if (i == MAX_SEARCH_ATTEMPTS) { + for (digest_lists_pos = 0; + digest_lists_pos < num_max_digest_lists; + digest_lists_pos++) + if (digest_lists[digest_lists_pos]) + break; + + if (digest_lists_pos == num_max_digest_lists) + continue; + } + + ret = digest_list_upload(digest_lists[digest_lists_pos], + op, upload_type, NULL, NULL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[digest_lists_pos], + op); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + free(digest_lists[digest_lists_pos]->buf); + free(digest_lists[digest_lists_pos]); + digest_lists[digest_lists_pos] = NULL; + break; + default: + break; + } + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % 10; + + if (value != 1) + continue; + + ret = getrandom(&value, sizeof(value), 0); + ASSERT_EQ(sizeof(value), ret) { + TH_LOG("getrandom() failed"); + } + + value = value % MAX_DIGEST_VALUE; + + ret = getrandom(&algo, sizeof(algo), 0); + ASSERT_EQ(sizeof(algo), ret) { + TH_LOG("getrandom() failed"); + } + + algo = algo % HASH_ALGO_RIPE_MD_128; + + ret = compare_count(value, algo, _metadata); + ASSERT_EQ(0, ret) { + TH_LOG("count mismatch"); + } + + TH_LOG("query digest lists (%d/%d)...", cur_queries, + NUM_QUERIES); + + cur_queries++; + } +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload, UINT_MAX) +{ + self->upload_type = UPLOAD_FILE; + digest_list_add_del_test(_metadata, 0, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_file_upload_fault, UINT_MAX) +{ + int ret; + + self->upload_type = UPLOAD_FILE; + + ret = enable_fault_injection(); + ASSERT_EQ(0, ret) { + TH_LOG("enable_fault_injection() failed"); + } + + digest_list_add_del_test(_metadata, 1, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload, UINT_MAX) +{ + self->upload_type = UPLOAD_BUFFER; + digest_list_add_del_test(_metadata, 0, self->upload_type); +} + +TEST_F_TIMEOUT(test, digest_list_add_del_test_buffer_upload_fault, UINT_MAX) +{ + int ret; + + self->upload_type = UPLOAD_BUFFER; + + ret = enable_fault_injection(); + ASSERT_EQ(0, ret) { + TH_LOG("enable_fault_injection() failed"); + } + + digest_list_add_del_test(_metadata, 1, self->upload_type); +} + +FIXTURE(test_fuzzing) +{ +}; + +FIXTURE_SETUP(test_fuzzing) +{ +} + +FIXTURE_TEARDOWN(test_fuzzing) +{ +} + +TEST_F_TIMEOUT(test_fuzzing, digest_list_fuzzing_test, UINT_MAX) +{ + char digests_count_before[256] = { 0 }; + char *digests_count_before_ptr = digests_count_before; + char digests_count_after[256] = { 0 }; + char *digests_count_after_ptr = digests_count_after; + size_t len = sizeof(digests_count_before) - 1; + int ret, i; + + ret = read_buffer(DIGESTS_COUNT, &digests_count_before_ptr, &len, + false, true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + for (i = 1; i <= NUM_ITERATIONS; i++) { + TH_LOG("add digest list (%d/%d)...", i, NUM_ITERATIONS); + + digest_lists[0] = digest_list_generate_random(); + ASSERT_NE(NULL, digest_lists[0]) { + TH_LOG("digest_list_generate() failed"); + } + + ret = digest_list_upload(digest_lists[0], DIGEST_LIST_ADD, + UPLOAD_FILE, NULL, NULL); + if (!ret) { + ret = digest_list_check(digest_lists[0], + DIGEST_LIST_ADD); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + + ret = digest_list_upload(digest_lists[0], + DIGEST_LIST_DEL, UPLOAD_FILE, + NULL, NULL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_upload() failed"); + } + + ret = digest_list_check(digest_lists[0], + DIGEST_LIST_DEL); + ASSERT_EQ(0, ret) { + TH_LOG("digest_list_check() failed"); + } + } + + free(digest_lists[0]->buf); + free(digest_lists[0]); + digest_lists[0] = NULL; + } + + ret = read_buffer(DIGESTS_COUNT, &digests_count_after_ptr, &len, false, + true); + ASSERT_EQ(0, ret) { + TH_LOG("read_buffer() failed"); + } + + ASSERT_STREQ(digests_count_before, digests_count_after); +} + +TEST_HARNESS_MAIN