From patchwork Tue Nov 19 10:49:08 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844380 Received: from frasgout11.his.huawei.com (frasgout11.his.huawei.com [14.137.139.23]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 193F01C4A18; Tue, 19 Nov 2024 11:10:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.23 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732014636; cv=none; b=CTHVFWh9w994CgDI/13SMLjpBbsOlEVKYcHmsa8kFXXDjkhg6otrNu55cmcObjPfSmvlR2Gf7fryqLGGzXHdvkMCYFoayh2vxb/Dx6eO75hW29nh/fhuxVwZbgX85Xcg3Qs6Q+o2QrMlvf3RfIQpJQghdMvxkwSfHasa2dkS51g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732014636; c=relaxed/simple; bh=suQp6CI3XDMPcDm8PiVZCYv/COsGp+w2F16O72yFRh0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kcKfhYjbfaLJDAGXvr1uRRVuaYxweE4fPo15bng1eSy+7wdCSTuvEoLLTg6Rm8jZx4vp3Rh3pEQFLHCmlnIN4ad9NbesDRAco1NDt/YAe7gZZFwuimwBgJzMJ/sZgM5cT3HyQUWbjLTSTdkQv7Xbo0UV4wIpbbcMiCtDTf7XowA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.23 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.29]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4Xt12N57Nwz9v7Hv; Tue, 19 Nov 2024 18:29:32 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id 8144514011F; Tue, 19 Nov 2024 18:50:26 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwA3nn1LbTxnNp7pAQ--.49675S3; Tue, 19 Nov 2024 11:50:25 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 01/15] lib: Add TLV parser Date: Tue, 19 Nov 2024 11:49:08 +0100 Message-ID: <20241119104922.2772571-2-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: GxC2BwA3nn1LbTxnNp7pAQ--.49675S3 X-Coremail-Antispam: 1UD129KBjvJXoW3GFW3ZF45tryxXry3XF4fKrg_yoW3ury3p3 ZxWw1fGr48Jw1xCrWfGr4Utr1rXrs5uFy7tFy8WryFvrs2yr1DGrWDGry0gryxtryvkr1q qa4agFy5Kr1DXw7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPIb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUGw A2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxS w2x7M28EF7xvwVC0I7IYx2IY67AKxVWUJVWUCwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxV WxJVW8Jr1l84ACjcxK6I8E87Iv67AKxVW8JVWxJwA2z4x0Y4vEx4A2jsIEc7CjxVAFwI0_ Gr0_Gr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F40Ex7xfMc Ij6xIIjxv20xvE14v26r106r15McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC6x0Yz7v_ Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxVAaw2AFwI 0_Wrv_ZF1l42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAqx4xG 67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26rWY6r4UJw CIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r1j6r1xMIIF0xvE2Ix0cI8IcVCY1x02 67AKxVWxJVW8Jr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14v26r 1j6r4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr0_Gr1UYxBIdaVFxhVjvjDU0xZFpf9x07j- kuxUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAMBGc79-IEfwABsp From: Roberto Sassu Add a parser of a generic Type-Length-Value (TLV) format: +--------------+--+---------+--------+---------+ | field1 (u16) | len1 (u32) | value1 (u8 len1) | +--------------+------------+------------------+ | ... | ... | ... | +--------------+------------+------------------+ | fieldN (u16) | lenN (u32) | valueN (u8 lenN) | +--------------+------------+------------------+ Each adopter can define its own fields. The TLV parser does not need to be aware of those, but lets the adopter obtain the data and decide how to continue. After processing a TLV entry, call the callback function also with the callback data provided by the adopter. The latter can decide how to interpret the TLV entry depending on the field ID. Nesting TLVs is also possible, the callback function can call tlv_parse() to parse the inner structure. Signed-off-by: Roberto Sassu --- MAINTAINERS | 8 +++ include/linux/tlv_parser.h | 32 ++++++++++++ include/uapi/linux/tlv_parser.h | 41 ++++++++++++++++ lib/Kconfig | 3 ++ lib/Makefile | 2 + lib/tlv_parser.c | 87 +++++++++++++++++++++++++++++++++ lib/tlv_parser.h | 18 +++++++ 7 files changed, 191 insertions(+) create mode 100644 include/linux/tlv_parser.h create mode 100644 include/uapi/linux/tlv_parser.h create mode 100644 lib/tlv_parser.c create mode 100644 lib/tlv_parser.h diff --git a/MAINTAINERS b/MAINTAINERS index a097afd76ded..1f7ffa1c9dbd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23388,6 +23388,14 @@ W: http://sourceforge.net/projects/tlan/ F: Documentation/networking/device_drivers/ethernet/ti/tlan.rst F: drivers/net/ethernet/ti/tlan.* +TLV PARSER +M: Roberto Sassu +L: linux-kernel@vger.kernel.org +S: Maintained +F: include/linux/tlv_parser.h +F: include/uapi/linux/tlv_parser.h +F: lib/tlv_parser.* + TMIO/SDHI MMC DRIVER M: Wolfram Sang L: linux-mmc@vger.kernel.org diff --git a/include/linux/tlv_parser.h b/include/linux/tlv_parser.h new file mode 100644 index 000000000000..0c72742af548 --- /dev/null +++ b/include/linux/tlv_parser.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header file of TLV parser. + */ + +#ifndef _LINUX_TLV_PARSER_H +#define _LINUX_TLV_PARSER_H + +#include + +/** + * typedef callback - Callback after parsing TLV entry + * @callback_data: Opaque data to supply to the callback function + * @field: Field identifier + * @field_data: Field data + * @field_len: Length of @field_data + * + * This callback is invoked after a TLV entry is parsed. + * + * Return: Zero on success, a negative value on error. + */ +typedef int (*callback)(void *callback_data, __u16 field, + const __u8 *field_data, __u32 field_len); + +int tlv_parse(callback callback, void *callback_data, const __u8 *data, + size_t data_len, const char **fields, __u32 num_fields); + +#endif /* _LINUX_TLV_PARSER_H */ diff --git a/include/uapi/linux/tlv_parser.h b/include/uapi/linux/tlv_parser.h new file mode 100644 index 000000000000..171d0cfd2c4c --- /dev/null +++ b/include/uapi/linux/tlv_parser.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the user space interface for the TLV parser. + */ + +#ifndef _UAPI_LINUX_TLV_PARSER_H +#define _UAPI_LINUX_TLV_PARSER_H + +#include + +/* + * TLV format: + * + * +--------------+--+---------+--------+---------+ + * | field1 (u16) | len1 (u32) | value1 (u8 len1) | + * +--------------+------------+------------------+ + * | ... | ... | ... | + * +--------------+------------+------------------+ + * | fieldN (u16) | lenN (u32) | valueN (u8 lenN) | + * +--------------+------------+------------------+ + */ + +/** + * struct tlv_entry - Entry of TLV format + * @field: Field identifier + * @length: Data length + * @data: Data + * + * This structure represents an entry of the TLV format. + */ +struct tlv_entry { + __u16 field; + __u32 length; + __u8 data[]; +} __attribute__((packed)); + +#endif /* _UAPI_LINUX_TLV_PARSER_H */ diff --git a/lib/Kconfig b/lib/Kconfig index b38849af6f13..9141dcfc1704 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -777,3 +777,6 @@ config POLYNOMIAL config FIRMWARE_TABLE bool + +config TLV_PARSER + bool diff --git a/lib/Makefile b/lib/Makefile index 773adf88af41..630de494eab5 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -393,5 +393,7 @@ obj-$(CONFIG_USERCOPY_KUNIT_TEST) += usercopy_kunit.o obj-$(CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED) += devmem_is_allowed.o obj-$(CONFIG_FIRMWARE_TABLE) += fw_table.o +obj-$(CONFIG_TLV_PARSER) += tlv_parser.o +CFLAGS_tlv_parser.o += -I lib subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify diff --git a/lib/tlv_parser.c b/lib/tlv_parser.c new file mode 100644 index 000000000000..dbbe08018b4d --- /dev/null +++ b/lib/tlv_parser.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the TLV parser. + */ + +#define pr_fmt(fmt) "tlv_parser: "fmt +#include + +/** + * tlv_parse - Parse TLV data + * @callback: Callback function to call to parse the entries + * @callback_data: Opaque data to supply to the callback function + * @data: Data to parse + * @data_len: Length of @data + * @fields: Array of field strings + * @num_fields: Number of elements of @fields + * + * Parse the TLV data format and call the supplied callback function for each + * entry, passing also the opaque data pointer. + * + * The callback function decides how to process data depending on the field. + * + * Return: Zero on success, a negative value on error. + */ +int tlv_parse(callback callback, void *callback_data, const __u8 *data, + size_t data_len, const char **fields, __u32 num_fields) +{ + const __u8 *data_ptr = data; + struct tlv_entry *entry; + __u16 parsed_field; + __u32 len; + int ret; + + if (data_len > U32_MAX) { + pr_debug("Data too big, size: %zd\n", data_len); + return -E2BIG; + } + + while (data_len) { + if (data_len < sizeof(*entry)) + return -EBADMSG; + + entry = (struct tlv_entry *)data_ptr; + data_ptr += sizeof(*entry); + data_len -= sizeof(*entry); + + parsed_field = __be16_to_cpu(entry->field); + if (parsed_field >= num_fields) { + pr_debug("Invalid field %u, max: %u\n", + parsed_field, num_fields - 1); + return -EBADMSG; + } + + len = __be32_to_cpu(entry->length); + + if (data_len < len) + return -EBADMSG; + + pr_debug("Data: field: %s, len: %u\n", fields[parsed_field], + len); + + if (!len) + continue; + + ret = callback(callback_data, parsed_field, data_ptr, len); + if (ret < 0) { + pr_debug("Parsing of field %s failed, ret: %d\n", + fields[parsed_field], ret); + return ret; + } + + data_ptr += len; + data_len -= len; + } + + if (data_len) { + pr_debug("Excess data: %zu bytes\n", data_len); + return -EBADMSG; + } + + return 0; +} +EXPORT_SYMBOL_GPL(tlv_parse); diff --git a/lib/tlv_parser.h b/lib/tlv_parser.h new file mode 100644 index 000000000000..e663966deac5 --- /dev/null +++ b/lib/tlv_parser.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header file of TLV parser. + */ + +#ifndef _LIB_TLV_PARSER_H +#define _LIB_TLV_PARSER_H + +#include +#include +#include +#include + +#endif /* _LIB_TLV_PARSER_H */ From patchwork Tue Nov 19 10:49:09 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844387 Received: from frasgout11.his.huawei.com (frasgout11.his.huawei.com [14.137.139.23]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 73E431BD9C8; Tue, 19 Nov 2024 10:50:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.23 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013452; cv=none; b=DrvEl6qY7YsD9VMZHJvIsXg4FWxxDRTlMYsXcSfSV9ZMUHKdiDFN2apyeRp12aI8ZIu67/O3HhRw0vuUnEMTOYbedVaC+xeq4b6D6/mXIq7C56TUPHEBSNNqWyjtFVcGph6EiSZ9Cnh7VAjOAnKVYwZxwH453JN4UrOclp+BSHk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013452; c=relaxed/simple; bh=G7RoBGkStSSVy2yB0dVJ9/CUQPsYjk9IqRXaK6YZCok=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=BbSlOBTPuFuYqOOMd6q1TdwrkKJWv/UYDfbD6S1Qyy30XSAuduIAITE5W5I60pTxucyEAwUxSVNXGHu3rdZ5Yhi6NaSK5fXXzd7cqB7lotR53XJLb2hu6/NbzQ3huTTYvHOBvIg7Iz7bSigbX0jrUrwO8ZM3DexyjzvXGEIuVEI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.23 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.29]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4Xt12n6NKdz9v7JS; Tue, 19 Nov 2024 18:29:53 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id AE98B1400CD; Tue, 19 Nov 2024 18:50:47 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwA3nn1LbTxnNp7pAQ--.49675S4; Tue, 19 Nov 2024 11:50:41 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 02/15] module: Introduce ksys_finit_module() Date: Tue, 19 Nov 2024 11:49:09 +0100 Message-ID: <20241119104922.2772571-3-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: GxC2BwA3nn1LbTxnNp7pAQ--.49675S4 X-Coremail-Antispam: 1UD129KBjvJXoWxXr1ftryxtw15KFWDKFy7Awb_yoWrKF48pF Z3Gas8WFsagr4fua1ftrWDWw13KrW8Cr4avFy3Crn3AF1vgrWjkF4Fy3ZI9a45GrW8GF4k GF4Fqry8GFW7JF7anT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPvb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUXw A2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxS w2x7M28EF7xvwVC0I7IYx2IY67AKxVWUJVWUCwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxV W8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrVC2j2 WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE7xkE bVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262kKe7 AKxVWrXVW3AwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02 F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Wrv_Gr 1UMIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Jr0_JF4lIxAIcVC0I7IYx2IY6xkF 7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14 v26r1j6r4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuY vjxUsJPEDUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAMBGc79-IEfwAEss From: Roberto Sassu Introduce ksys_finit_module() to let kernel components request a kernel module without requiring running modprobe. Signed-off-by: Roberto Sassu --- include/linux/syscalls.h | 10 ++++++++++ kernel/module/main.c | 43 ++++++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 5758104921e6..18bb346bb793 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -1233,6 +1233,16 @@ int ksys_ipc(unsigned int call, int first, unsigned long second, int compat_ksys_ipc(u32 call, int first, int second, u32 third, u32 ptr, u32 fifth); +#ifdef CONFIG_MODULES +int ksys_finit_module(struct file *f, const char *kargs, int flags); +#else +static inline int ksys_finit_module(struct file *f, const char *kargs, + int flags) +{ + return -EOPNOTSUPP; +} +#endif + /* * The following kernel syscall equivalents are just wrappers to fs-internal * functions. Therefore, provide stubs to be inlined at the callsites. diff --git a/kernel/module/main.c b/kernel/module/main.c index 49b9bca9de12..81f79c9ea637 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -2852,7 +2852,7 @@ static int early_mod_check(struct load_info *info, int flags) * zero, and we rely on this for optional sections. */ static int load_module(struct load_info *info, const char __user *uargs, - int flags) + const char *kargs, int flags) { struct module *mod; bool module_allocated = false; @@ -2953,7 +2953,13 @@ static int load_module(struct load_info *info, const char __user *uargs, flush_module_icache(mod); /* Now copy in args */ - mod->args = strndup_user(uargs, ~0UL >> 1); + if (kargs) { + mod->args = kstrndup(kargs, ~0UL >> 1, GFP_KERNEL); + if (!mod->args) + mod->args = ERR_PTR(-ENOMEM); + } else { + mod->args = strndup_user(uargs, ~0UL >> 1); + } if (IS_ERR(mod->args)) { err = PTR_ERR(mod->args); goto free_arch_cleanup; @@ -3083,7 +3089,7 @@ SYSCALL_DEFINE3(init_module, void __user *, umod, return err; } - return load_module(&info, uargs, 0); + return load_module(&info, uargs, NULL, 0); } struct idempotent { @@ -3170,7 +3176,8 @@ static int idempotent_wait_for_completion(struct idempotent *u) return u->ret; } -static int init_module_from_file(struct file *f, const char __user * uargs, int flags) +static int init_module_from_file(struct file *f, const char __user * uargs, + const char *kargs, int flags) { struct load_info info = { }; void *buf = NULL; @@ -3195,10 +3202,11 @@ static int init_module_from_file(struct file *f, const char __user * uargs, int info.len = len; } - return load_module(&info, uargs, flags); + return load_module(&info, uargs, kargs, flags); } -static int idempotent_init_module(struct file *f, const char __user * uargs, int flags) +static int idempotent_init_module(struct file *f, const char __user * uargs, + const char *kargs, int flags) { struct idempotent idem; @@ -3207,7 +3215,7 @@ static int idempotent_init_module(struct file *f, const char __user * uargs, int /* Are we the winners of the race and get to do this? */ if (!idempotent(&idem, file_inode(f))) { - int ret = init_module_from_file(f, uargs, flags); + int ret = init_module_from_file(f, uargs, kargs, flags); return idempotent_complete(&idem, ret); } @@ -3217,15 +3225,16 @@ static int idempotent_init_module(struct file *f, const char __user * uargs, int return idempotent_wait_for_completion(&idem); } -SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) +static int _ksys_finit_module(struct file *f, int fd, const char __user * uargs, + const char *kargs, int flags) { int err; - struct fd f; err = may_init_module(); if (err) return err; + /* fd = -1 if called from the kernel. */ pr_debug("finit_module: fd=%d, uargs=%p, flags=%i\n", fd, uargs, flags); if (flags & ~(MODULE_INIT_IGNORE_MODVERSIONS @@ -3233,8 +3242,22 @@ SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) |MODULE_INIT_COMPRESSED_FILE)) return -EINVAL; + err = idempotent_init_module(f, uargs, kargs, flags); + return err; +} + +int ksys_finit_module(struct file *f, const char *kargs, int flags) +{ + return _ksys_finit_module(f, -1, NULL, kargs, flags); +} + +SYSCALL_DEFINE3(finit_module, int, fd, const char __user *, uargs, int, flags) +{ + int err; + struct fd f; + f = fdget(fd); - err = idempotent_init_module(fd_file(f), uargs, flags); + err = _ksys_finit_module(fd_file(f), fd, uargs, NULL, flags); fdput(f); return err; } From patchwork Tue Nov 19 10:49:11 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844386 Received: from frasgout13.his.huawei.com (frasgout13.his.huawei.com [14.137.139.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A7EE11C07F9; Tue, 19 Nov 2024 10:51:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.46 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013492; cv=none; b=lclY/qEBTWC4xu0wYkVHm6Bw8RrcwGyN61N/dN+FX93PApyz0IHsfi4neMBwpAG9sQcLuv5Ia6A/D/ESspmJScIIe0BlH1e5mYWP//W0nSYBw/jQ/fqqt0IIjWyYqYknxFIBH1DCuDPx6shNpNoi5XMzVvv2Ow5Ke/YPXp1LFow= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013492; c=relaxed/simple; bh=JIGVLKN9mATQFq7HcL3xBrqInCMEQGt9xDSrrJAmQik=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=opTLSBO025KjUpjhdQX4x2Sfl381ElIIciFZ+K3ShJQmY69Qdsr4YxvkXuGWQsTOGLi+jxzpiX55RiawWoJPI/9Ol233qGREgms/j0Qup49KcQVFxg1WsaRolWm0gXpIAiylxQmBJStFiLBhRa0SZe17hDj4LlVt1RsAOfsWlhg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.29]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4Xt13T3pJ0z9v7Nh; Tue, 19 Nov 2024 18:30:29 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id 38DBF140CAB; Tue, 19 Nov 2024 18:51:15 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwA3nn1LbTxnNp7pAQ--.49675S6; Tue, 19 Nov 2024 11:51:14 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 04/15] digest_cache: Initialize digest caches Date: Tue, 19 Nov 2024 11:49:11 +0100 Message-ID: <20241119104922.2772571-5-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: GxC2BwA3nn1LbTxnNp7pAQ--.49675S6 X-Coremail-Antispam: 1UD129KBjvJXoW3Jr13urWfCr1xuryxuF47twb_yoW7WryDpa sFk3W5Krs5ZryxCw17CF12yw4Fqr9YqF47Gws8uw1ayFs2vr1qv3W0yw15ZryUXr4Uua17 tr45K3WUur1DXaUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPGb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVWUJVWUCwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JrI_JrylYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVWrXVW3AwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Wr v_Gr1UMIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Jr0_JF4lIxAIcVC0I7IYx2IY 6xkF7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2js IE14v26r1j6r4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIF yTuYvjxUsCztUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAMBGc797QEdwACsn From: Roberto Sassu Introduce digest_cache_init() to initialize created digest caches. Since initialization happens after releasing both the dig_owner_mutex and dig_user_mutex locks (to avoid a lock inversion with VFS locks), any caller of digest_cache_get() can potentially be in charge of initializing them, provided that it got the digest list path from digest_cache_new(). Introduce the INIT_STARTED flag, to atomically determine whether the digest cache is being initialized and eventually do it if the flag is not yet set. Introduce the INIT_IN_PROGRESS flag, for the other callers to wait until the caller in charge of the initialization finishes initializing the digest cache. Set INIT_IN_PROGRESS in digest_cache_create() and clear it in digest_cache_init(). Finally, call clear_and_wake_up_bit() to wake up the other callers. Finally, introduce the INVALID flag, to let the callers which didn't initialize the digest cache know that an error occurred during initialization and, consequently, prevent them from using that digest cache. Signed-off-by: Roberto Sassu --- security/integrity/digest_cache/internal.h | 8 ++++ security/integrity/digest_cache/main.c | 48 ++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/security/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h index fa76ab2672ea..29bf98a974f3 100644 --- a/security/integrity/digest_cache/internal.h +++ b/security/integrity/digest_cache/internal.h @@ -13,6 +13,11 @@ #include #include +/* Digest cache bits in flags. */ +#define INIT_IN_PROGRESS 0 /* Digest cache being initialized. */ +#define INIT_STARTED 1 /* Digest cache init started. */ +#define INVALID 2 /* Digest cache marked as invalid. */ + /** * struct digest_cache - Digest cache * @ref_count: Number of references to the digest cache @@ -110,6 +115,9 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, struct path *default_path, struct path *digest_list_path, char *path_str, char *filename); +struct digest_cache *digest_cache_init(struct dentry *dentry, + struct path *digest_list_path, + struct digest_cache *digest_cache); int __init digest_cache_do_init(const struct lsm_id *lsm_id, loff_t inode_offset, loff_t file_offset); diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c index c644cdc2ebd7..6e94cff2b0dc 100644 --- a/security/integrity/digest_cache/main.c +++ b/security/integrity/digest_cache/main.c @@ -156,6 +156,9 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, /* Increment ref. count for reference returned to the caller. */ digest_cache = digest_cache_ref(dig_sec->dig_owner); + + /* Make other digest cache requestors wait until creation complete. */ + set_bit(INIT_IN_PROGRESS, &digest_cache->flags); out: mutex_unlock(&dig_sec->dig_owner_mutex); return digest_cache; @@ -238,6 +241,47 @@ static struct digest_cache *digest_cache_new(struct dentry *dentry, return digest_cache; } +/** + * digest_cache_init - Initialize a digest cache + * @dentry: Dentry of the inode for which the digest cache will be used + * @digest_list_path: Path structure of the digest list + * @digest_cache: Digest cache to initialize + * + * This function checks if the INIT_STARTED digest cache flag is set. If it is, + * or the caller didn't provide the digest list path, it waits until the caller + * that saw INIT_STARTED unset and had the path completes the initialization. + * + * The latter sets INIT_STARTED (atomically), performs the initialization, + * clears the INIT_IN_PROGRESS digest cache flag, and wakes up the other + * callers. + * + * Return: A valid and initialized digest cache on success, NULL otherwise. + */ +struct digest_cache *digest_cache_init(struct dentry *dentry, + struct path *digest_list_path, + struct digest_cache *digest_cache) +{ + /* Wait for digest cache initialization. */ + if (!digest_list_path->dentry || + test_and_set_bit(INIT_STARTED, &digest_cache->flags)) { + wait_on_bit(&digest_cache->flags, INIT_IN_PROGRESS, + TASK_UNINTERRUPTIBLE); + goto out; + } + + /* Notify initialization complete. */ + clear_and_wake_up_bit(INIT_IN_PROGRESS, &digest_cache->flags); +out: + if (test_bit(INVALID, &digest_cache->flags)) { + pr_debug("Digest cache %s is invalid, don't return it\n", + digest_cache->path_str); + digest_cache_put(digest_cache); + digest_cache = NULL; + } + + return digest_cache; +} + /** * digest_cache_get - Get a digest cache for a given inode * @file: File descriptor of the inode for which the digest cache will be used @@ -287,6 +331,10 @@ struct digest_cache *digest_cache_get(struct file *file) mutex_unlock(&dig_sec->dig_user_mutex); + if (digest_cache) + digest_cache = digest_cache_init(dentry, &digest_list_path, + digest_cache); + if (digest_list_path.dentry) path_put(&digest_list_path); From patchwork Tue Nov 19 10:49:13 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844385 Received: from frasgout11.his.huawei.com (frasgout11.his.huawei.com [14.137.139.23]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A81F71C3F0B; Tue, 19 Nov 2024 10:51:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.23 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013513; cv=none; b=U2PXxKYD3d2x0GFF5qbGN85DfHVXTSHjmXssekRN604dlhBJ93+LxHZs4zFyJzU0Wz6vGkVZvM+v7bt7H/FoNrb6XnLIoLQgybnElj05d9iLJWx/jNc9CaxgHXOT80g65RGwBe+i0u0JSn6ncwqVUnX5tqL/Psa+/9XwrA2uXIQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013513; c=relaxed/simple; bh=gzyjz9aXL9JdhHZ6liJcTpjENCVWqUFNFclFGNdLwUo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fV8acflJzaf6haUVPEzj/7ClXJPIKA9PaB1vN3ZabJf3pcly6nlGoEecJwC8zw/YPvokKLDxiwMky59I+r7Tc1L4Rnx3qei8Y7rYaYgEC+0qPM+42ew4v8uUVW+IksW8YPcAgVjQx9udQL2rRtCgbWOI0D9kI2n8pApnURX97No= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.23 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.29]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4Xt13x6c3Pz9v7JC; Tue, 19 Nov 2024 18:30:53 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id B402F140119; Tue, 19 Nov 2024 18:51:47 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwA3nn1LbTxnNp7pAQ--.49675S8; Tue, 19 Nov 2024 11:51:46 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 06/15] digest_cache: Add hash tables and operations Date: Tue, 19 Nov 2024 11:49:13 +0100 Message-ID: <20241119104922.2772571-7-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: GxC2BwA3nn1LbTxnNp7pAQ--.49675S8 X-Coremail-Antispam: 1UD129KBjvAXoW3tF4fCFW8Xw4xuFW8ZF1UWrg_yoW8XFWDJo ZIkF45Jw48WFy3uw4DCF17Z3WUW34rt34xAr4kXrWDX3Z2qryUJ3ZFkFn8Jry3Xr18GrZ7 Aw1kJ3yUJF48tr93n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOr7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_JFI_Gr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F4 0Ex7xfMcIj6xIIjxv20xvE14v26r106r15McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC 6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxV Aaw2AFwI0_Wrv_ZF1l42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2Iq xVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r WY6r4UJwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r1I6r4UMIIF0xvE2Ix0cI8I cVCY1x0267AKxVW8Jr0_Cr1UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87 Iv67AKxVWUJVW8JwCI42IY6I8E87Iv6xkF7I0E14v26r4UJVWxJrUvcSsGvfC2KfnxnUUI 43ZEXa7IU0l4iUUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAMBGc79-IEiAAAsf From: Roberto Sassu Add a linked list of hash tables to the digest cache, one per algorithm, containing the digests extracted from digest lists. The number of hash table slots is determined by dividing the number of digests to add to the average depth of the collision list defined with CONFIG_DIGEST_CACHE_HTABLE_DEPTH (currently set to 30). It can be changed in the kernel configuration. Add digest_cache_htable_init(), digest_cache_htable_add() and digest_cache_htable_lookup() to the Parser API, so that digest list parsers can allocate the hash tables and add/lookup extracted digests. Add digest_cache_htable_free(), to let the Integrity Digest Cache free the hash tables at the time a digest cache is freed. Add digest_cache_lookup() to the Client API, to let users of the Integrity Digest Cache search a digest in a digest cache and, in a subsequent patch, to search it in the digest caches for each directory entry. digest_cache_lookup() returns a digest cache reference that must be released by calling digest_cache_put(). Finally, introduce digest_cache_hash_key() to compute the hash table key from the first two bytes of the digest (modulo the number of slots). Signed-off-by: Roberto Sassu --- include/linux/digest_cache.h | 39 ++++ security/integrity/digest_cache/Kconfig | 11 + security/integrity/digest_cache/Makefile | 2 +- security/integrity/digest_cache/htable.c | 251 +++++++++++++++++++++ security/integrity/digest_cache/internal.h | 36 +++ security/integrity/digest_cache/main.c | 3 + 6 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 security/integrity/digest_cache/htable.c diff --git a/include/linux/digest_cache.h b/include/linux/digest_cache.h index 1f88b61fb7cd..59a42c04cbb8 100644 --- a/include/linux/digest_cache.h +++ b/include/linux/digest_cache.h @@ -11,12 +11,25 @@ #define _LINUX_DIGEST_CACHE_H #include +#include #ifdef CONFIG_INTEGRITY_DIGEST_CACHE /* Client API */ struct digest_cache *digest_cache_get(struct file *file); void digest_cache_put(struct digest_cache *digest_cache); bool digest_cache_opened_fd(struct file *file); +struct digest_cache *digest_cache_lookup(struct dentry *dentry, + struct digest_cache *digest_cache, + u8 *digest, enum hash_algo algo); + +/* Parser API */ +int digest_cache_htable_init(struct digest_cache *digest_cache, u64 num_digests, + enum hash_algo algo); +int digest_cache_htable_add(struct digest_cache *digest_cache, u8 *digest, + enum hash_algo algo); +int digest_cache_htable_lookup(struct dentry *dentry, + struct digest_cache *digest_cache, u8 *digest, + enum hash_algo algo); #else static inline struct digest_cache *digest_cache_get(struct file *file) @@ -33,5 +46,31 @@ static inline bool digest_cache_opened_fd(struct file *file) return false; } +static inline struct digest_cache * +digest_cache_lookup(struct dentry *dentry, struct digest_cache *digest_cache, + u8 *digest, enum hash_algo algo) +{ + return NULL; +} + +static inline int digest_cache_htable_init(struct digest_cache *digest_cache, + u64 num_digests, enum hash_algo algo) +{ + return -EOPNOTSUPP; +} + +static inline int digest_cache_htable_add(struct digest_cache *digest_cache, + u8 *digest, enum hash_algo algo) +{ + return -EOPNOTSUPP; +} + +static inline int digest_cache_htable_lookup(struct dentry *dentry, + struct digest_cache *digest_cache, + u8 *digest, enum hash_algo algo) +{ + return -EOPNOTSUPP; +} + #endif /* CONFIG_INTEGRITY_DIGEST_CACHE */ #endif /* _LINUX_DIGEST_CACHE_H */ diff --git a/security/integrity/digest_cache/Kconfig b/security/integrity/digest_cache/Kconfig index a4bfa8287b8d..419011fb52c9 100644 --- a/security/integrity/digest_cache/Kconfig +++ b/security/integrity/digest_cache/Kconfig @@ -21,3 +21,14 @@ config DIGEST_LIST_DEFAULT_PATH It can be changed at run-time, by writing the new path to the securityfs interface. Digest caches created with the old path are not affected by the change. + +config DIGEST_CACHE_HTABLE_DEPTH + int + default 30 + help + Desired average depth of the collision list in the digest cache + hash tables. + + A smaller number will increase the amount of hash table slots, and + make the search faster. A bigger number will decrease the number of + hash table slots, but make the search slower. diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile index c351186d4e1e..0092c913979d 100644 --- a/security/integrity/digest_cache/Makefile +++ b/security/integrity/digest_cache/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o -digest_cache-y := main.o secfs.o +digest_cache-y := main.o secfs.o htable.o diff --git a/security/integrity/digest_cache/htable.c b/security/integrity/digest_cache/htable.c new file mode 100644 index 000000000000..8aa6d50a0cb5 --- /dev/null +++ b/security/integrity/digest_cache/htable.c @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement hash table operations for the digest cache. + */ + +#define pr_fmt(fmt) "digest_cache: "fmt +#include "internal.h" + +/** + * digest_cache_hash_key - Compute hash key + * @digest: Digest cache + * @num_slots: Number of slots in the hash table + * + * This function computes a hash key based on the first two bytes of the + * digest and the number of slots of the hash table. + * + * Return: Hash key. + */ +static inline unsigned int digest_cache_hash_key(u8 *digest, + unsigned int num_slots) +{ + /* Same as ima_hash_key() but parametrized. */ + return (digest[0] | digest[1] << 8) % num_slots; +} + +/** + * lookup_htable - Lookup a hash table + * @digest_cache: Digest cache + * @algo: Algorithm of the desired hash table + * + * This function searches the hash table for a given algorithm in the digest + * cache. + * + * Return: A hash table if found, NULL otherwise. + */ +static struct htable *lookup_htable(struct digest_cache *digest_cache, + enum hash_algo algo) +{ + struct htable *h; + + list_for_each_entry(h, &digest_cache->htables, next) + if (h->algo == algo) + return h; + + return NULL; +} + +/** + * digest_cache_htable_init - Allocate and initialize the hash table + * @digest_cache: Digest cache + * @num_digests: Number of digests to add to the hash table + * @algo: Algorithm of the digests + * + * This function allocates and initializes the hash table for a given algorithm. + * The number of slots depends on the number of digests to add to the digest + * cache, and the constant CONFIG_DIGEST_CACHE_HTABLE_DEPTH stating the desired + * average depth of the collision list. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +int digest_cache_htable_init(struct digest_cache *digest_cache, u64 num_digests, + enum hash_algo algo) +{ + struct htable *h; + unsigned int i; + + if (!num_digests) + return -EINVAL; + + h = lookup_htable(digest_cache, algo); + if (h) + return 0; + + h = kmalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return -ENOMEM; + + h->num_slots = DIV_ROUND_UP(num_digests, + CONFIG_DIGEST_CACHE_HTABLE_DEPTH); + h->slots = kmalloc_array(h->num_slots, sizeof(*h->slots), GFP_KERNEL); + if (!h->slots) { + kfree(h); + return -ENOMEM; + } + + for (i = 0; i < h->num_slots; i++) + INIT_HLIST_HEAD(&h->slots[i]); + + h->num_digests = 0; + h->algo = algo; + + list_add_tail(&h->next, &digest_cache->htables); + + pr_debug("Initialized hash table for digest list %s, digests: %llu, slots: %u, algo: %s\n", + digest_cache->path_str, num_digests, h->num_slots, + hash_algo_name[algo]); + return 0; +} +EXPORT_SYMBOL_GPL(digest_cache_htable_init); + +/** + * digest_cache_htable_add - Add a new digest to the digest cache + * @digest_cache: Digest cache + * @digest: Digest to add + * @algo: Algorithm of the digest + * + * This function adds a digest extracted from a digest list to the digest cache. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +int digest_cache_htable_add(struct digest_cache *digest_cache, u8 *digest, + enum hash_algo algo) +{ + struct htable *h; + struct digest_cache_entry *entry; + unsigned int key; + int digest_len; + + h = lookup_htable(digest_cache, algo); + if (!h) { + pr_debug("No hash table for algorithm %s was found in digest cache %s, initialize one\n", + hash_algo_name[algo], digest_cache->path_str); + return -ENOENT; + } + + digest_len = hash_digest_size[algo]; + + entry = kmalloc(sizeof(*entry) + digest_len, GFP_KERNEL); + if (!entry) + return -ENOMEM; + + memcpy(entry->digest, digest, digest_len); + + key = digest_cache_hash_key(digest, h->num_slots); + hlist_add_head(&entry->hnext, &h->slots[key]); + h->num_digests++; + pr_debug("Added digest %s:%*phN to digest cache %s, num of %s digests: %llu\n", + hash_algo_name[algo], digest_len, digest, + digest_cache->path_str, hash_algo_name[algo], h->num_digests); + return 0; +} +EXPORT_SYMBOL_GPL(digest_cache_htable_add); + +/** + * digest_cache_htable_lookup - Search a digest in the digest cache + * @dentry: Dentry of the file whose digest is looked up + * @digest_cache: Digest cache + * @digest: Digest to search + * @algo: Algorithm of the digest to search + * + * This function searches the passed digest and algorithm in the digest cache. + * + * Return: Zero if the digest is found, a POSIX error code otherwise. + */ +int digest_cache_htable_lookup(struct dentry *dentry, + struct digest_cache *digest_cache, u8 *digest, + enum hash_algo algo) +{ + struct digest_cache_entry *entry; + struct htable *h; + unsigned int key; + int digest_len = hash_digest_size[algo]; + int search_depth = 0, ret = -ENOENT; + + h = lookup_htable(digest_cache, algo); + if (!h) + goto out; + + key = digest_cache_hash_key(digest, h->num_slots); + + hlist_for_each_entry(entry, &h->slots[key], hnext) { + if (!memcmp(entry->digest, digest, digest_len)) { + pr_debug("Cache hit at depth %d for file %s, digest %s:%*phN in digest cache %s\n", + search_depth, dentry->d_name.name, + hash_algo_name[algo], digest_len, digest, + digest_cache->path_str); + + return 0; + } + + search_depth++; + } +out: + pr_debug("Cache miss for file %s, digest %s:%*phN not in digest cache %s\n", + dentry->d_name.name, hash_algo_name[algo], digest_len, digest, + digest_cache->path_str); + return ret; +} +EXPORT_SYMBOL_GPL(digest_cache_htable_lookup); + +/** + * digest_cache_lookup - Search a digest in the digest cache + * @dentry: Dentry of the file whose digest is looked up + * @digest_cache: Digest cache + * @digest: Digest to search + * @algo: Algorithm of the digest to search + * + * This function calls digest_cache_htable_lookup() to search a digest in the + * passed digest cache, obtained with digest_cache_get(). + * + * Return: A digest cache reference the digest is found, NULL if not. + */ +struct digest_cache *digest_cache_lookup(struct dentry *dentry, + struct digest_cache *digest_cache, + u8 *digest, enum hash_algo algo) +{ + int ret; + + ret = digest_cache_htable_lookup(dentry, digest_cache, digest, algo); + if (ret < 0) + return NULL; + + return digest_cache_ref(digest_cache); +} +EXPORT_SYMBOL_GPL(digest_cache_lookup); + +/** + * digest_cache_htable_free - Free the hash tables + * @digest_cache: Digest cache + * + * This function removes all digests from all hash tables in the digest cache, + * and frees the memory. + */ +void digest_cache_htable_free(struct digest_cache *digest_cache) +{ + struct htable *h, *h_tmp; + struct digest_cache_entry *p; + struct hlist_node *q; + unsigned int i; + + list_for_each_entry_safe(h, h_tmp, &digest_cache->htables, next) { + for (i = 0; i < h->num_slots; i++) { + hlist_for_each_entry_safe(p, q, &h->slots[i], hnext) { + hlist_del(&p->hnext); + pr_debug("Removed digest %s:%*phN from digest cache %s\n", + hash_algo_name[h->algo], + hash_digest_size[h->algo], p->digest, + digest_cache->path_str); + kfree(p); + } + } + + list_del(&h->next); + kfree(h->slots); + kfree(h); + } +} diff --git a/security/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h index 82d9c894c8fc..e14343e96caa 100644 --- a/security/integrity/digest_cache/internal.h +++ b/security/integrity/digest_cache/internal.h @@ -18,8 +18,40 @@ #define INIT_STARTED 1 /* Digest cache init started. */ #define INVALID 2 /* Digest cache marked as invalid. */ +/** + * struct digest_cache_entry - Entry of a digest cache hash table + * @hnext: Pointer to the next element in the collision list + * @digest: Stored digest + * + * This structure represents an entry of a digest cache hash table, storing a + * digest. + */ +struct digest_cache_entry { + struct hlist_node hnext; + u8 digest[]; +}; + +/** + * struct htable - Hash table + * @next: Next hash table in the linked list + * @slots: Hash table slots + * @num_slots: Number of slots + * @num_digests: Number of digests stored in the hash table + * @algo: Algorithm of the digests + * + * This structure is a hash table storing digests of file data or metadata. + */ +struct htable { + struct list_head next; + struct hlist_head *slots; + unsigned int num_slots; + u64 num_digests; + enum hash_algo algo; +}; + /** * struct digest_cache - Digest cache + * @htables: Hash tables (one per algorithm) * @ref_count: Number of references to the digest cache * @path_str: Path of the digest list the digest cache was created from * @flags: Control flags @@ -27,6 +59,7 @@ * This structure represents a cache of digests extracted from a digest list. */ struct digest_cache { + struct list_head htables; atomic_t ref_count; char *path_str; unsigned long flags; @@ -125,4 +158,7 @@ int __init digest_cache_do_init(const struct lsm_id *lsm_id, /* secfs.c */ int __init digest_cache_secfs_init(struct dentry *dir); +/* htable.c */ +void digest_cache_htable_free(struct digest_cache *digest_cache); + #endif /* _DIGEST_CACHE_INTERNAL_H */ diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c index 6724471914da..ebc5dc09a62b 100644 --- a/security/integrity/digest_cache/main.c +++ b/security/integrity/digest_cache/main.c @@ -51,6 +51,7 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str, atomic_set(&digest_cache->ref_count, 1); digest_cache->flags = 0UL; + INIT_LIST_HEAD(&digest_cache->htables); pr_debug("New digest cache %s (ref count: %d)\n", digest_cache->path_str, atomic_read(&digest_cache->ref_count)); @@ -66,6 +67,8 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str, */ static void digest_cache_free(struct digest_cache *digest_cache) { + digest_cache_htable_free(digest_cache); + pr_debug("Freed digest cache %s\n", digest_cache->path_str); kfree(digest_cache->path_str); kmem_cache_free(digest_cache_cache, digest_cache); From patchwork Tue Nov 19 10:49:15 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844384 Received: from frasgout12.his.huawei.com (frasgout12.his.huawei.com [14.137.139.154]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C411A1C5793; Tue, 19 Nov 2024 10:52:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.154 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013546; cv=none; b=L6qM0zSoGDBpnaEONb6bQ2B/s1Ydzq7FZfWq7Ru6NlUYOVn/8/KlPujuv8soDrReF0PW3Zy+rDiHlYC1qLH/KSO3+Ig19n+CKaw7+2v0vcJGiOCWZEOx9yKMZiIY2JEiuqQ17G5VXYkF2iqRxmcYq2i+b7jfNyzU28MG5dSTVOY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013546; c=relaxed/simple; bh=e3ZFRZaAMQyCMmESBfpNUUcwhqDJx5Q1m5Q0JgxBdeI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=pPjE0aN0GICeL5gwqkhIX1SQ6/8XLlCXcrdyx60+EnPmIJ94GG9Gdiz9QNaaObt3/Z0gfCXb+h42Cyz/d384YCi65DpxDSrCvJafpjoThhboUgYSd6kcH6xl6Fao+zz5LYRFqJmnIMYvNFY9bD3f5szndlvp9l2k/UHD2XY0OLY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.154 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.29]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4Xt0xK6bxwz9v7JS; Tue, 19 Nov 2024 18:25:09 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id 03F0F140119; Tue, 19 Nov 2024 18:52:21 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwA3nn1LbTxnNp7pAQ--.49675S10; Tue, 19 Nov 2024 11:52:19 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 08/15] digest_cache: Parse tlv digest lists Date: Tue, 19 Nov 2024 11:49:15 +0100 Message-ID: <20241119104922.2772571-9-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: GxC2BwA3nn1LbTxnNp7pAQ--.49675S10 X-Coremail-Antispam: 1UD129KBjvAXoW3uF13Zr15JFW3urWxKrW3Jrb_yoW8Wr4xCo ZIvr45Aw4rtrsIkF4kAF13Jr4rG3yYqFyrJw4fWr4DWa4rJF15tan2ka13Gas5Zw1rta9F yr18J3yaqw48trs7n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOr7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_JFI_Gr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F4 0Ex7xfMcIj6xIIjxv20xvE14v26r106r15McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC 6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxV Aaw2AFwI0_Wrv_ZF1l42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2Iq xVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26r WY6r4UJwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r1I6r4UMIIF0xvE2Ix0cI8I cVCY1x0267AKxVW8Jr0_Cr1UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87 Iv67AKxVWUJVW8JwCI42IY6I8E87Iv6xkF7I0E14v26r4UJVWxJrUvcSsGvfC2KfnxnUUI 43ZEXa7IU0l4iUUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAMBGc79-IEigAAsd From: Roberto Sassu Add digest_list_parse_tlv(), to parse TLV-formatted (Type Length Value) digest lists. Their structure is: [field: DIGEST_LIST_ALGO, length, value] [field: DIGEST_LIST_NUM_ENTRIES, length, value] [field: DIGEST_LIST_ENTRY#1, length, value (below)] |- [DIGEST_LIST_ENTRY_DIGEST#1, length, file digest] |- [DIGEST_LIST_ENTRY_PATH#1, length, file path] [field: DIGEST_LIST_ENTRY#N, length, value (below)] |- [DIGEST_LIST_ENTRY_DIGEST#N, length, file digest] |- [DIGEST_LIST_ENTRY_PATH#N, length, file path] DIGEST_LIST_ALGO and DIGEST_LIST_NUM_ENTRIES must have a fixed length respectively of sizeof(u16) and sizeof(u32). The data of the DIGEST_LIST_ENTRY field are itself in TLV format, for which the DIGEST_LIST_ENTRY_DIGEST and DIGEST_LIST_ENTRY_PATH fields are defined. Currently defined fields are sufficient for measurement/appraisal of file data. More fields will be introduced later for file metadata. Introduce digest_list_callback() to handle the digest list fields, DIGEST_LIST_ALGO, DIGEST_LIST_NUM_ENTRIES and DIGEST_LIST_ENTRY, and the respective field parsers parse_digest_list_algo(), parse_digest_list_num_entries() and parse_digest_list_entry(). Introduce digest_list_entry_callback(), to handle the DIGEST_LIST_ENTRY fields, DIGEST_LIST_ENTRY_DIGEST and DIGEST_LIST_ENTRY_PATH, and the respective field parsers parse_digest_list_entry_digest() and parse_digest_list_entry_path(). The TLV parser itself is implemented in lib/tlv_parser.c. Both the TLV parser and the tlv digest list parser have been formally verified with Frama-C (https://frama-c.com/). The analysis has been done on this file: https://github.com/robertosassu/rpm-formal/blob/main/validate_tlv.c Here is the result of the analysis: [eva:summary] ====== ANALYSIS SUMMARY ====== --------------------------------------------------------------------------- 12 functions analyzed (out of 12): 100% coverage. In these functions, 177 statements reached (out of 191): 92% coverage. --------------------------------------------------------------------------- Some errors and warnings have been raised during the analysis: by the Eva analyzer: 0 errors 2 warnings by the Frama-C kernel: 0 errors 0 warnings --------------------------------------------------------------------------- 0 alarms generated by the analysis. --------------------------------------------------------------------------- Evaluation of the logical properties reached by the analysis: Assertions 5 valid 0 unknown 0 invalid 5 total Preconditions 22 valid 0 unknown 0 invalid 22 total 100% of the logical properties reached have been proven. --------------------------------------------------------------------------- The warnings are: [eva] validate_tlv.c:256: Warning: this partitioning parameter cannot be evaluated safely on all states [eva] validate_tlv.c:284: Warning: this partitioning parameter cannot be evaluated safely on all states Signed-off-by: Roberto Sassu --- include/uapi/linux/tlv_digest_list.h | 47 +++ security/integrity/digest_cache/Kconfig | 8 + security/integrity/digest_cache/Makefile | 1 + security/integrity/digest_cache/parsers/tlv.c | 341 ++++++++++++++++++ 4 files changed, 397 insertions(+) create mode 100644 include/uapi/linux/tlv_digest_list.h create mode 100644 security/integrity/digest_cache/parsers/tlv.c diff --git a/include/uapi/linux/tlv_digest_list.h b/include/uapi/linux/tlv_digest_list.h new file mode 100644 index 000000000000..f2031cd70e64 --- /dev/null +++ b/include/uapi/linux/tlv_digest_list.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * Copyright (C) 2017-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Export definitions of the tlv digest list. + */ + +#ifndef _UAPI_LINUX_TLV_DIGEST_LIST_H +#define _UAPI_LINUX_TLV_DIGEST_LIST_H + +#include + +#define FOR_EACH_DIGEST_LIST_FIELD(DIGEST_LIST_FIELD) \ + DIGEST_LIST_FIELD(DIGEST_LIST_ALGO) \ + DIGEST_LIST_FIELD(DIGEST_LIST_NUM_ENTRIES) \ + DIGEST_LIST_FIELD(DIGEST_LIST_ENTRY) \ + DIGEST_LIST_FIELD(DIGEST_LIST_FIELD__LAST) + +#define FOR_EACH_DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_FIELD) \ + DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_DIGEST) \ + DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_PATH) \ + DIGEST_LIST_ENTRY_FIELD(DIGEST_LIST_ENTRY_FIELD__LAST) + +#define GENERATE_ENUM(ENUM) ENUM, +#define GENERATE_STRING(STRING) #STRING, + +/** + * enum digest_list_fields - Digest list fields + * + * Enumerates the digest list fields. + */ +enum digest_list_fields { + FOR_EACH_DIGEST_LIST_FIELD(GENERATE_ENUM) +}; + +/** + * enum digest_list_entry_fields - DIGEST_LIST_ENTRY fields + * + * Enumerates the DIGEST_LIST_ENTRY fields. + */ +enum digest_list_entry_fields { + FOR_EACH_DIGEST_LIST_ENTRY_FIELD(GENERATE_ENUM) +}; + +#endif /* _UAPI_LINUX_TLV_DIGEST_LIST_H */ diff --git a/security/integrity/digest_cache/Kconfig b/security/integrity/digest_cache/Kconfig index 65c07110911b..972bcf8bb765 100644 --- a/security/integrity/digest_cache/Kconfig +++ b/security/integrity/digest_cache/Kconfig @@ -33,3 +33,11 @@ config DIGEST_CACHE_HTABLE_DEPTH A smaller number will increase the amount of hash table slots, and make the search faster. A bigger number will decrease the number of hash table slots, but make the search slower. + +config DIGEST_CACHE_TLV_PARSER + tristate "TLV digest list parser" + depends on INTEGRITY_DIGEST_CACHE + select TLV_PARSER + help + Add support for parsing TLV-formatted (Type Length Value) + digest list. diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile index d68cae690241..3b42b20d1bc0 100644 --- a/security/integrity/digest_cache/Makefile +++ b/security/integrity/digest_cache/Makefile @@ -3,6 +3,7 @@ # Makefile for building the Integrity Digest Cache. obj-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o +obj-$(CONFIG_DIGEST_CACHE_TLV_PARSER) += parsers/tlv.o digest_cache-y := main.o secfs.o htable.o parsers.o diff --git a/security/integrity/digest_cache/parsers/tlv.c b/security/integrity/digest_cache/parsers/tlv.c new file mode 100644 index 000000000000..31e407f0a43b --- /dev/null +++ b/security/integrity/digest_cache/parsers/tlv.c @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Parse a tlv digest list. + */ + +#define pr_fmt(fmt) "digest_cache TLV PARSER: "fmt +#include +#include +#include +#include + +#define kenter(FMT, ...) \ + pr_debug("==> %s(" FMT ")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + pr_debug("<== %s()" FMT "\n", __func__, ##__VA_ARGS__) + +static const char *digest_list_fields_str[] = { + FOR_EACH_DIGEST_LIST_FIELD(GENERATE_STRING) +}; + +static const char *digest_list_entry_fields_str[] = { + FOR_EACH_DIGEST_LIST_ENTRY_FIELD(GENERATE_STRING) +}; + +struct tlv_callback_data { + struct digest_cache *digest_cache; + enum hash_algo algo; +}; + +/** + * parse_digest_list_entry_digest - Parse DIGEST_LIST_ENTRY_DIGEST field + * @tlv_data: Callback data + * @field: Field identifier + * @field_data: Field data + * @field_data_len: Length of @field_data + * + * This function parses the DIGEST_LIST_ENTRY_DIGEST field (file digest). + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int parse_digest_list_entry_digest(struct tlv_callback_data *tlv_data, + enum digest_list_entry_fields field, + const __u8 *field_data, + __u32 field_data_len) +{ + int ret; + + kenter(",%u,%u", field, field_data_len); + + if (tlv_data->algo == HASH_ALGO__LAST) { + pr_debug("Digest algo not set\n"); + ret = -EBADMSG; + goto out; + } + + if (field_data_len != hash_digest_size[tlv_data->algo]) { + pr_debug("Unexpected data length %u, expected %d\n", + field_data_len, hash_digest_size[tlv_data->algo]); + ret = -EBADMSG; + goto out; + } + + ret = digest_cache_htable_add(tlv_data->digest_cache, + (__u8 *)field_data, tlv_data->algo); +out: + kleave(" = %d", ret); + return ret; +} + +/** + * parse_digest_list_entry_path - Parse DIGEST_LIST_ENTRY_PATH field + * @tlv_data: Callback data + * @field: Field identifier + * @field_data: Field data + * @field_data_len: Length of @field_data + * + * This function handles the DIGEST_LIST_ENTRY_PATH field (file path). It + * currently does not parse the data. + * + * Return: Zero. + */ +static int parse_digest_list_entry_path(struct tlv_callback_data *tlv_data, + enum digest_list_entry_fields field, + const __u8 *field_data, + __u32 field_data_len) +{ + kenter(",%u,%u", field, field_data_len); + + kleave(" = 0"); + return 0; +} + +/** + * digest_list_entry_callback - DIGEST_LIST_ENTRY callback + * @callback_data: Callback data + * @field: Field identifier + * @field_data: Field data + * @field_data_len: Length of @field_data + * + * This callback handles the fields of DIGEST_LIST_ENTRY (nested) data, and + * calls the appropriate parser. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int digest_list_entry_callback(void *callback_data, __u16 field, + const __u8 *field_data, + __u32 field_data_len) +{ + struct tlv_callback_data *tlv_data; + int ret; + + tlv_data = (struct tlv_callback_data *)callback_data; + + switch (field) { + case DIGEST_LIST_ENTRY_DIGEST: + ret = parse_digest_list_entry_digest(tlv_data, field, + field_data, + field_data_len); + break; + case DIGEST_LIST_ENTRY_PATH: + ret = parse_digest_list_entry_path(tlv_data, field, field_data, + field_data_len); + break; + default: + pr_debug("Unhandled field %s\n", + digest_list_entry_fields_str[field]); + /* Just ignore non-relevant fields. */ + ret = 0; + break; + } + + return ret; +} + +/** + * parse_digest_list_algo - Parse DIGEST_LIST_ALGO field + * @tlv_data: Callback data + * @field: Field identifier + * @field_data: Field data + * @field_data_len: Length of @field_data + * + * This function parses the DIGEST_LIST_ALGO field (digest algorithm). + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int parse_digest_list_algo(struct tlv_callback_data *tlv_data, + enum digest_list_fields field, + const __u8 *field_data, __u32 field_data_len) +{ + __u16 algo; + int ret = 0; + + kenter(",%u,%u", field, field_data_len); + + if (field_data_len != sizeof(__u16)) { + pr_debug("Unexpected data length %u, expected %zu\n", + field_data_len, sizeof(__u16)); + ret = -EBADMSG; + goto out; + } + + algo = __be16_to_cpu(*(__u16 *)field_data); + + if (algo >= HASH_ALGO__LAST) { + pr_debug("Unexpected digest algo %u\n", algo); + ret = -EBADMSG; + goto out; + } + + tlv_data->algo = algo; + + pr_debug("Digest algo: %s\n", hash_algo_name[algo]); +out: + kleave(" = %d", ret); + return ret; +} + +/** + * parse_digest_list_num_entries - Parse DIGEST_LIST_NUM_ENTRIES field + * @tlv_data: Callback data + * @field: Field identifier + * @field_data: Field data + * @field_data_len: Length of @field_data + * + * This function parses the DIGEST_LIST_NUM_ENTRIES field (digest list entries). + * This field must appear after DIGEST_LIST_ALGO. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int parse_digest_list_num_entries(struct tlv_callback_data *tlv_data, + enum digest_list_fields field, + const __u8 *field_data, + __u32 field_data_len) +{ + __u32 num_entries; + int ret; + + kenter(",%u,%u", field, field_data_len); + + if (field_data_len != sizeof(__u32)) { + pr_debug("Unexpected data length %u, expected %zu\n", + field_data_len, sizeof(__u32)); + ret = -EBADMSG; + goto out; + } + + if (tlv_data->algo == HASH_ALGO__LAST) { + pr_debug("Digest algo not yet initialized\n"); + ret = -EBADMSG; + goto out; + } + + num_entries = __be32_to_cpu(*(__u32 *)field_data); + + ret = digest_cache_htable_init(tlv_data->digest_cache, num_entries, + tlv_data->algo); +out: + kleave(" = %d", ret); + return ret; +} + +/** + * parse_digest_list_entry - Parse DIGEST_LIST_ENTRY field + * @tlv_data: Callback data + * @field: Field identifier + * @field_data: Field data + * @field_data_len: Length of @field_data + * + * This function parses the DIGEST_LIST_ENTRY field. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int parse_digest_list_entry(struct tlv_callback_data *tlv_data, + enum digest_list_fields field, + const __u8 *field_data, __u32 field_data_len) +{ + int ret; + + kenter(",%u,%u", field, field_data_len); + + ret = tlv_parse(digest_list_entry_callback, tlv_data, field_data, + field_data_len, digest_list_entry_fields_str, + DIGEST_LIST_ENTRY_FIELD__LAST); + + kleave(" = %d", ret); + return ret; +} + +/** + * digest_list_callback - Digest list callback + * @callback_data: Callback data + * @field: Field identifier + * @field_data: Field data + * @field_data_len: Length of @field_data + * + * This callback handles the digest list fields, and calls the appropriate + * parser. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int digest_list_callback(void *callback_data, __u16 field, + const __u8 *field_data, __u32 field_data_len) +{ + struct tlv_callback_data *tlv_data; + int ret; + + tlv_data = (struct tlv_callback_data *)callback_data; + + switch (field) { + case DIGEST_LIST_ALGO: + ret = parse_digest_list_algo(tlv_data, field, field_data, + field_data_len); + break; + case DIGEST_LIST_NUM_ENTRIES: + ret = parse_digest_list_num_entries(tlv_data, field, field_data, + field_data_len); + break; + case DIGEST_LIST_ENTRY: + ret = parse_digest_list_entry(tlv_data, field, field_data, + field_data_len); + break; + default: + pr_debug("Unhandled field %s\n", + digest_list_fields_str[field]); + /* Just ignore non-relevant fields. */ + ret = 0; + break; + } + + return ret; +} + +/** + * digest_list_parse_tlv - Parse a tlv digest list + * @digest_cache: Digest cache + * @data: Data to parse + * @data_len: Length of @data + * + * This function parses a tlv digest list. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int digest_list_parse_tlv(struct digest_cache *digest_cache, + const __u8 *data, size_t data_len) +{ + struct tlv_callback_data tlv_data = { + .digest_cache = digest_cache, + .algo = HASH_ALGO__LAST, + }; + + return tlv_parse(digest_list_callback, &tlv_data, data, data_len, + digest_list_fields_str, DIGEST_LIST_FIELD__LAST); +} + +static struct parser tlv_parser = { + .name = "tlv", + .owner = THIS_MODULE, + .func = digest_list_parse_tlv, +}; + +static int __init tlv_parser_init(void) +{ + return digest_cache_register_parser(&tlv_parser); +} + +static void __exit tlv_parser_exit(void) +{ + digest_cache_unregister_parser(&tlv_parser); +} + +module_init(tlv_parser_init); +module_exit(tlv_parser_exit); + +MODULE_AUTHOR("Roberto Sassu"); +MODULE_DESCRIPTION("TLV digest list parser"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0.0"); From patchwork Tue Nov 19 10:49:17 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844383 Received: from frasgout11.his.huawei.com (frasgout11.his.huawei.com [14.137.139.23]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C0D9719C553; Tue, 19 Nov 2024 10:54:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.23 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013669; cv=none; b=iKN8JqQPzzk8UYrdyFWeHpqrnMFo/cWp3mXEGIxTYf1DSwYVOJ9ZEg1WLM2zX1V7ccmiDnkLmnQT1+7oiKgI8PAc8byneLQFfwePAyrhNFMKqj9OravsueNHglk9TO6D++BWqh1W+ERIZ6piA2NALGLwE17j1QE+hJtWRp5LJ8o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013669; c=relaxed/simple; bh=y6p5Z7O9gV4y2W4jbw8MsZjbIEern3boBWypsYiDbLk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=kVhqMh9Nv7JrYxL0+9IYQkGZTNb03NhZrq3UwS29IHlnzN1aZxeHiL2VYNPH8QLZboOLN7MCd33GH+ZCR6U7NNqbBihqxesRKUT4nsCog3cjk7eif3KyiVyqUjUQDbH5+XDVJ8Mo7vufpIPGN1mkuUxU1w+qD3DASsH7bABfpKE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.23 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.29]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4Xt16y2HnVz9v7NH; Tue, 19 Nov 2024 18:33:30 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.47]) by mail.maildlp.com (Postfix) with ESMTP id 79ED114093B; Tue, 19 Nov 2024 18:54:14 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwAHsyNDbjxnj1znAQ--.1193S2; Tue, 19 Nov 2024 11:54:13 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 10/15] digest_cache: Add management of verification data Date: Tue, 19 Nov 2024 11:49:17 +0100 Message-ID: <20241119104922.2772571-11-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwAHsyNDbjxnj1znAQ--.1193S2 X-Coremail-Antispam: 1UD129KBjvJXoWfGr1rKrWDtF4UAFWfGryxGrg_yoWkJw47p3 s29F1DKr4rZr1fCwnrAF129r1rKFZ5tF47Jw48ur15ZF45Xr1jv3W8A34UuryrJrW8Wa17 tr42gw1Uur4DXaDanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUvmb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28lY4IEw2IIxxk0rwA2F7IY1VAKz4 vEj48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7Cj xVAFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x 0267AKxVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02 F40Ex7xfMcIj6xIIjxv20xvE14v26r1Y6r17McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4I kC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7Cj xVAaw2AFwI0_Wrv_ZF1l42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2 IqxVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v2 6rWY6r4UJwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r4j6ryUMIIF0xvE2Ix0cI 8IcVCY1x0267AKxVW8Jr0_Cr1UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E 87Iv67AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26r4UJVWxJrUvcSsGvfC2KfnxnU UI43ZEXa7IU0uMKtUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAMBGc79-IEkAAAsH From: Roberto Sassu The Integrity Digest Cache can support other LSMs in their decisions of granting access to file data and metadata. However, the information alone about whether a digest was found in a digest cache might not be sufficient, because for example those LSMs wouldn't know about the integrity of the digest list digests were extracted from. Introduce digest_cache_verif_set() to let the same LSMs (or a chosen integrity provider) evaluate the digest list being read during the creation of the digest cache, by implementing the kernel_post_read_file LSM hook, and let them attach their verification data to that digest cache. digest_cache_verif_set() receives as argument a file descriptor and calls digest_cache_from_file_sec() to obtain back the digest cache being created from that file descriptor. The digest cache being created was associated to the file descriptor by digest_cache_populate(), before reading the digest list from the kernel, by calling digest_cache_to_file_sec(). Multiple providers are supported, in the event there are multiple integrity LSMs active. Each provider should also provide a unique verifier ID as an argument to digest_cache_verif_set(), so that verification data can be distinguished. Concurrent set are protected by the verif_data_lock spinlock. A caller of digest_cache_get() can retrieve back the verification data by calling digest_cache_verif_get() and passing a digest cache pointer and the desired verifier ID. Since directory digest caches are not populated themselves, LSMs have to do a lookup first to get the digest cache containing the digest, and pass the returned digest cache reference to digest_cache_verif_get(). Signed-off-by: Roberto Sassu --- include/linux/digest_cache.h | 17 +++ security/integrity/digest_cache/Makefile | 3 +- security/integrity/digest_cache/internal.h | 22 ++++ security/integrity/digest_cache/main.c | 3 + security/integrity/digest_cache/verif.c | 131 +++++++++++++++++++++ 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 security/integrity/digest_cache/verif.c diff --git a/include/linux/digest_cache.h b/include/linux/digest_cache.h index a9d731990b7c..d2483fe588be 100644 --- a/include/linux/digest_cache.h +++ b/include/linux/digest_cache.h @@ -47,6 +47,10 @@ bool digest_cache_opened_fd(struct file *file); struct digest_cache *digest_cache_lookup(struct dentry *dentry, struct digest_cache *digest_cache, u8 *digest, enum hash_algo algo); +int digest_cache_verif_set(struct file *file, const char *verif_id, void *data, + size_t size); +void *digest_cache_verif_get(struct digest_cache *digest_cache, + const char *verif_id); /* Parser API */ int digest_cache_htable_init(struct digest_cache *digest_cache, u64 num_digests, @@ -81,6 +85,19 @@ digest_cache_lookup(struct dentry *dentry, struct digest_cache *digest_cache, return NULL; } +static inline int digest_cache_verif_set(struct file *file, + const char *verif_id, void *data, + size_t size) +{ + return -EOPNOTSUPP; +} + +static inline void *digest_cache_verif_get(struct digest_cache *digest_cache, + const char *verif_id) +{ + return NULL; +} + static inline int digest_cache_htable_init(struct digest_cache *digest_cache, u64 num_digests, enum hash_algo algo) { diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile index 3b81edea065b..2a0f2500e227 100644 --- a/security/integrity/digest_cache/Makefile +++ b/security/integrity/digest_cache/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o obj-$(CONFIG_DIGEST_CACHE_TLV_PARSER) += parsers/tlv.o -digest_cache-y := main.o secfs.o htable.o parsers.o populate.o modsig.o +digest_cache-y := main.o secfs.o htable.o parsers.o populate.o modsig.o \ + verif.o CFLAGS_parsers.o += -DPARSERS_DIR=\"$(MODLIB)/kernel/security/integrity/digest_cache/parsers\" diff --git a/security/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h index 2171ea8423ff..c64e91b75a47 100644 --- a/security/integrity/digest_cache/internal.h +++ b/security/integrity/digest_cache/internal.h @@ -18,6 +18,21 @@ #define INIT_STARTED 1 /* Digest cache init started. */ #define INVALID 2 /* Digest cache marked as invalid. */ +/** + * struct digest_cache_verif + * @list: Linked list + * @verif_id: Identifier of who verified the digest list + * @data: Opaque data set by the digest list verifier + * + * This structure contains opaque data containing the result of verification + * of the digest list by a verifier. + */ +struct digest_cache_verif { + struct list_head list; + char *verif_id; + void *data; +}; + /** * struct read_work - Structure to schedule reading a digest list * @work: Work structure @@ -72,6 +87,8 @@ struct htable { * @ref_count: Number of references to the digest cache * @path_str: Path of the digest list the digest cache was created from * @flags: Control flags + * @verif_data: Verification data regarding the digest list + * @verif_data_lock: Protects verification data modifications * * This structure represents a cache of digests extracted from a digest list. */ @@ -80,6 +97,8 @@ struct digest_cache { atomic_t ref_count; char *path_str; unsigned long flags; + struct list_head verif_data; + spinlock_t verif_data_lock; }; /** @@ -191,4 +210,7 @@ int digest_cache_populate(struct dentry *dentry, /* modsig.c */ size_t digest_cache_strip_modsig(__u8 *data, size_t data_len); +/* verif.c */ +void digest_cache_verif_free(struct digest_cache *digest_cache); + #endif /* _DIGEST_CACHE_INTERNAL_H */ diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c index ad0f34c7ef9b..11a0445592f0 100644 --- a/security/integrity/digest_cache/main.c +++ b/security/integrity/digest_cache/main.c @@ -52,6 +52,8 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str, atomic_set(&digest_cache->ref_count, 1); digest_cache->flags = 0UL; INIT_LIST_HEAD(&digest_cache->htables); + INIT_LIST_HEAD(&digest_cache->verif_data); + spin_lock_init(&digest_cache->verif_data_lock); pr_debug("New digest cache %s (ref count: %d)\n", digest_cache->path_str, atomic_read(&digest_cache->ref_count)); @@ -68,6 +70,7 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str, static void digest_cache_free(struct digest_cache *digest_cache) { digest_cache_htable_free(digest_cache); + digest_cache_verif_free(digest_cache); pr_debug("Freed digest cache %s\n", digest_cache->path_str); kfree(digest_cache->path_str); diff --git a/security/integrity/digest_cache/verif.c b/security/integrity/digest_cache/verif.c new file mode 100644 index 000000000000..03ebf0de764b --- /dev/null +++ b/security/integrity/digest_cache/verif.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Manage verification data regarding digest lists. + */ + +#define pr_fmt(fmt) "digest_cache: "fmt +#include "internal.h" + +/** + * free_verif - Free a digest_cache_verif structure + * @verif: digest_cache_verif structure + * + * Free the space allocated for a digest_cache_verif structure. + */ +static void free_verif(struct digest_cache_verif *verif) +{ + kfree(verif->data); + kfree(verif->verif_id); + kfree(verif); +} + +/** + * digest_cache_verif_set - Set digest cache verification data + * @file: File descriptor of the digest list being read to populate digest cache + * @verif_id: Verifier ID + * @data: Verification data (opaque) + * @size: Size of @data + * + * This function lets a verifier supply verification data about a digest list + * being read to populate the digest cache. Verifier ID must be unique. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +int digest_cache_verif_set(struct file *file, const char *verif_id, void *data, + size_t size) +{ + struct digest_cache *digest_cache = digest_cache_from_file_sec(file); + struct digest_cache_verif *new_verif, *verif; + /* All allocations done by kprobe must be atomic (non-sleepable). */ + gfp_t flags = !strncmp(verif_id, "kprobe", 6) ? GFP_ATOMIC : GFP_KERNEL; + int ret = 0; + + /* + * Zero the data, so that we can always call free_verif() to free a + * partially filled structure (if a pointer is NULL, will not be freed). + */ + new_verif = kzalloc(sizeof(*new_verif), flags); + if (!new_verif) + return -ENOMEM; + + new_verif->verif_id = kstrdup(verif_id, flags); + if (!new_verif->verif_id) { + free_verif(new_verif); + return -ENOMEM; + } + + new_verif->data = kmemdup(data, size, flags); + if (!new_verif->data) { + free_verif(new_verif); + return -ENOMEM; + } + + spin_lock(&digest_cache->verif_data_lock); + list_for_each_entry(verif, &digest_cache->verif_data, list) { + if (!strcmp(verif->verif_id, verif_id)) { + ret = -EEXIST; + goto out; + } + } + + list_add_tail_rcu(&new_verif->list, &digest_cache->verif_data); +out: + spin_unlock(&digest_cache->verif_data_lock); + + if (ret < 0) + free_verif(new_verif); + + return ret; +} +EXPORT_SYMBOL_GPL(digest_cache_verif_set); + +/** + * digest_cache_verif_get - Get digest cache verification data + * @digest_cache: Digest cache + * @verif_id: Verifier ID + * + * This function returns the verification data previously set by a verifier + * with digest_cache_verif_set(). + * + * Return: Verification data if found, NULL otherwise. + */ +void *digest_cache_verif_get(struct digest_cache *digest_cache, + const char *verif_id) +{ + struct digest_cache_verif *verif; + void *verif_data = NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(verif, &digest_cache->verif_data, list) { + if (!strcmp(verif->verif_id, verif_id)) { + verif_data = verif->data; + break; + } + } + rcu_read_unlock(); + + return verif_data; +} +EXPORT_SYMBOL_GPL(digest_cache_verif_get); + +/** + * digest_cache_verif_free - Free all digest_cache_verif structures + * @digest_cache: Digest cache + * + * This function frees the space allocated for all digest_cache_verif + * structures in the digest cache. + */ +void digest_cache_verif_free(struct digest_cache *digest_cache) +{ + struct digest_cache_verif *p, *q; + + /* No need to lock, called when nobody else has a digest cache ref. */ + list_for_each_entry_safe(p, q, &digest_cache->verif_data, list) { + list_del(&p->list); + free_verif(p); + } +} From patchwork Tue Nov 19 10:49:19 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844382 Received: from frasgout12.his.huawei.com (frasgout12.his.huawei.com [14.137.139.154]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6F9161C4A3F; Tue, 19 Nov 2024 10:54:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.154 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013692; cv=none; b=DQ30Z73LXAL/kW/eLChM+pQYDchpJRWQizcINVkBP4hYs58aDhz5F0t1yzzwaJLLTIaoWHOuAyb4CnItHQCWqlnuT9LyinPrT2IUoNZY+9zvKksl9fwslcka28bEKT5iZv9GcDGgZjdi62BueWjH7N8y5lj+YqF1iihSrcwNJ/w= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013692; c=relaxed/simple; bh=4S1UJBXtM5KHQLq/OrbrojZKuVYHJkbNsUkoSxHvM8s=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=aT3c+mdkZrJEpLZgyhMaMx/5DFEVUp4OvsytU45CZ/CWFKsAFiWheMjLb6HfiDhMrO4pplx0f1AAzJ5tmK3mCGcghBxDvMFGuoPOAKevZrOCjXNRnmkTMw2M9R7wgyNjc+tDLo6Udb6G9jcfyBAvYeJLBh7gENt5T51q1qzAE5g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.154 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.29]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4Xt1076wSZz9v7Nc; Tue, 19 Nov 2024 18:27:35 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.47]) by mail.maildlp.com (Postfix) with ESMTP id 12100140418; Tue, 19 Nov 2024 18:54:47 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwAHsyNDbjxnj1znAQ--.1193S4; Tue, 19 Nov 2024 11:54:46 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 12/15] digest cache: Prefetch digest lists if requested Date: Tue, 19 Nov 2024 11:49:19 +0100 Message-ID: <20241119104922.2772571-13-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwAHsyNDbjxnj1znAQ--.1193S4 X-Coremail-Antispam: 1UD129KBjvAXoWftr4rGw1UuF45Ww4rWFyfJFb_yoW8urWrZo ZakF47Aw48WFyUurs8CF17Aa1DW34Fq34xAr1kGFZ8Z3Z7AFWUGasrC3WDJFy5Jr18JFZ7 Zwn7Xw4UJFWxtr93n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOY7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jr yl82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x0267AK xVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F40Ex7 xfMcIj6xIIjxv20xvE14v26r1Y6r17McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC6x0Y z7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxVAaw2 AFwI0_Wrv_ZF1l42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAq x4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26rWY6r 4UJwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r4j6ryUMIIF0xvE2Ix0cI8IcVCY 1x0267AKxVW8Jr0_Cr1UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67 AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26r4UJVWxJrUvcSsGvfC2KfnxnUUI43ZE Xa7IU0KiiDUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAMBGc79-IElAACsB From: Roberto Sassu A desirable goal when doing integrity measurements is that they are done always in the same order across boots, so that the resulting PCR value becomes predictable and suitable for sealing policies. However, due to parallel execution of system services at boot, a deterministic order of measurements is difficult to achieve. The Integrity Digest Cache is not exempted from this issue. If callers of digest_cache_get() pass file descriptors of inodes in an unpredictable order, this very likely will result in reading digest lists in an unpredictable order too. Overcome this issue by introducing digest_cache_dir_prefetch() and digest_cache_dir_lookup_filename(), to sequentially search the digest list the digest cache will be populated from in the parent directory, instead of directly accessing it from the full path. If during the file name search there is no match, read the digest list to trigger its measurement (if an appropriate policy exists). If there is a match, attempt to create and initialize the digest cache from the found file name. This ensures that measurements are always done in the same order as the file names stored in the digest cache of the parent directory, regardless of which digest list was requested. However, the PCR still changes every time a not yet read digest list is requested. In any case, the possible PCR values are more manageable than in the unpredictable file access case, at most the number of digest lists. Of course, PCR values further change when digest lists are added/removed to/from the parent directory, or if the parent directory itself is modified through securityfs. The prefetching mechanism has been designed to be more like a aid to get predictable measurements, and does not defend against path change attacks. It assumes that the path does not change between retrieving the digest cache associated to an inode and prefetching (i.e. the prefetching mechanism gets the same digest cache). If the path changed, and the prefetching mechanism retrieved a different digest cache, digest_cache_get() still continues to use the one it retrieved, and will ignore the prefetched one. Prefetching is enabled by setting the newly introduced 'security.dig_prefetch' xattr to '1'. digest_cache_new() and digest_cache_dir_create() call digest_cache_prefetch_requested() to check it. They pass the check result to digest_cache_create(), which in turn sets the DIR_PREFETCH and FILE_PREFETCH bit respectively in the parent directory and regular file digest cache. On subsequent calls to digest_cache_prefetch_requested(), the latter can just test for DIR_PREFETCH bit (faster). digest_cache_get(), after digest cache creation, checks if the digest cache has the FILE_PREFETCH bit set and, if yes, calls digest_cache_dir_prefetch() to do prefetching. For just reading digest lists, digest_cache_dir_prefetch() asks digest_cache_create() to create special digest caches with the FILE_READ bit set, so that digest_cache_populate() does not attempt to extract digests. The special digest caches also are not attached to the corresponding inode. Signed-off-by: Roberto Sassu --- include/uapi/linux/xattr.h | 3 + security/integrity/digest_cache/dir.c | 135 ++++++++++++++++++++- security/integrity/digest_cache/internal.h | 12 +- security/integrity/digest_cache/main.c | 96 ++++++++++++++- security/integrity/digest_cache/populate.c | 9 +- security/integrity/digest_cache/verif.c | 4 + 6 files changed, 251 insertions(+), 8 deletions(-) diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h index 8a58cf4bce65..8af33d38d9e8 100644 --- a/include/uapi/linux/xattr.h +++ b/include/uapi/linux/xattr.h @@ -57,6 +57,9 @@ #define XATTR_DIGEST_LIST_SUFFIX "digest_list" #define XATTR_NAME_DIGEST_LIST XATTR_SECURITY_PREFIX XATTR_DIGEST_LIST_SUFFIX +#define XATTR_DIG_PREFETCH_SUFFIX "dig_prefetch" +#define XATTR_NAME_DIG_PREFETCH XATTR_SECURITY_PREFIX XATTR_DIG_PREFETCH_SUFFIX + #define XATTR_SELINUX_SUFFIX "selinux" #define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX diff --git a/security/integrity/digest_cache/dir.c b/security/integrity/digest_cache/dir.c index 8f9e550c365f..a292a9c25119 100644 --- a/security/integrity/digest_cache/dir.c +++ b/security/integrity/digest_cache/dir.c @@ -56,6 +56,7 @@ static bool digest_cache_dir_iter(struct dir_context *__ctx, const char *name, new_entry->seq_num = UINT_MAX; new_entry->digest_cache = NULL; mutex_init(&new_entry->digest_cache_mutex); + new_entry->prefetched = false; if (new_entry->name[0] < '0' || new_entry->name[0] > '9') goto out; @@ -134,6 +135,11 @@ int digest_cache_dir_add_entries(struct digest_cache *digest_cache, * created/obtained from, so that the caller can use it to perform lookup * operations. * + * Before creating the digest cache, this function first calls + * digest_cache_prefetch_requested() to check if prefetching has been requested + * on the directory, and passes the result to digest_cache_create(), so that the + * latter sets the DIR_PREFETCH bit in the directory digest cache. + * * Return: A directory digest cache on success, NULL otherwise. */ static struct digest_cache *digest_cache_dir_create(struct dentry *dentry, @@ -142,6 +148,7 @@ static struct digest_cache *digest_cache_dir_create(struct dentry *dentry, { struct digest_cache *digest_cache; struct path _dir_path; + bool prefetch_req; int ret; ret = kern_path(path_str, 0, &_dir_path); @@ -150,8 +157,10 @@ static struct digest_cache *digest_cache_dir_create(struct dentry *dentry, return NULL; } + prefetch_req = digest_cache_prefetch_requested(&_dir_path, path_str); + digest_cache = digest_cache_create(dentry, &_dir_path, dir_path, - path_str, ""); + path_str, "", prefetch_req, false); if (digest_cache) digest_cache = digest_cache_init(dentry, dir_path, digest_cache); @@ -204,7 +213,8 @@ digest_cache_dir_lookup_digest(struct dentry *dentry, cache = digest_cache_create(dentry, &dir_path, &digest_list_path, dir_cache->path_str, - dir_entry->name); + dir_entry->name, false, + false); if (cache) cache = digest_cache_init(dentry, &digest_list_path, @@ -221,6 +231,8 @@ digest_cache_dir_lookup_digest(struct dentry *dentry, /* Consume extra ref. from digest_cache_create(). */ dir_entry->digest_cache = cache; + /* Digest list was read, mark entry as prefetched. */ + dir_entry->prefetched = true; } mutex_unlock(&dir_entry->digest_cache_mutex); @@ -241,6 +253,125 @@ digest_cache_dir_lookup_digest(struct dentry *dentry, return found; } +/** + * digest_cache_dir_lookup_filename - Lookup a digest list + * @dentry: Dentry of the file whose digest list is looked up + * @dir_path: Path structure of the digest list directory + * @digest_cache: Dir digest cache + * @filename: File name of the digest list to search + * + * This function iterates over the linked list created by + * digest_cache_dir_add_entries() and looks up a digest list with a matching + * file name among the entries. If there is no match, it prefetches (reads) the + * current digest list. Otherwise, it creates and initializes a new digest + * cache. + * + * The new digest cache is not returned, since it might have been created from + * a different inode than the one originally found during digest_cache_create(). + * + * If it is the right one, digest_cache_init() called from digest_cache_get() + * will go ahead and simply use the initialized digest cache. If for any reason + * the inode was different, digest_cache_init() will use the one previously + * returned by digest_cache_create(). + */ +static void digest_cache_dir_lookup_filename(struct dentry *dentry, + struct path *dir_path, + struct digest_cache *digest_cache, + char *filename) +{ + struct digest_cache *cache; + struct dir_entry *dir_entry; + struct path digest_list_path; + bool filename_found; + + list_for_each_entry(dir_entry, &digest_cache->dir_entries, list) { + mutex_lock(&dir_entry->digest_cache_mutex); + filename_found = !strcmp(dir_entry->name, filename); + if (!filename_found && dir_entry->prefetched) { + mutex_unlock(&dir_entry->digest_cache_mutex); + continue; + } + + digest_list_path.dentry = NULL; + digest_list_path.mnt = NULL; + + cache = digest_cache_create(dentry, dir_path, &digest_list_path, + digest_cache->path_str, + dir_entry->name, false, + filename_found ? false : true); + if (cache) + cache = digest_cache_init(dentry, &digest_list_path, + cache); + + if (digest_list_path.dentry) + path_put(&digest_list_path); + + if (!filename_found) + pr_debug("Digest list %s/%s %s prefetched\n", + digest_cache->path_str, dir_entry->name, + cache ? "has been" : "cannot be"); + + if (cache) + digest_cache_put(cache); + + dir_entry->prefetched = true; + mutex_unlock(&dir_entry->digest_cache_mutex); + + if (filename_found) + break; + } + + /* Prefetching done, no need to repeat. */ + clear_bit(FILE_PREFETCH, &digest_cache->flags); +} + +/** + * digest_cache_dir_prefetch - Prefetch digest lists in parent directory + * @dentry: Dentry of the file whose digest list is looked up + * @digest_cache: Digest cache + * + * This function prefetches the digest lists in the parent directory of the + * passed digest cache. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +int digest_cache_dir_prefetch(struct dentry *dentry, + struct digest_cache *digest_cache) +{ + struct digest_cache *dir_cache; + char *path_str, *last; + struct path dir_path = { .dentry = NULL, .mnt = NULL }; + int ret = 0; + + last = strrchr(digest_cache->path_str, '/'); + if (!last) + return -EINVAL; + + path_str = kstrndup(digest_cache->path_str, + last - digest_cache->path_str, GFP_KERNEL); + if (!path_str) + return -ENOMEM; + + dir_cache = digest_cache_dir_create(dentry, &dir_path, path_str); + + kfree(path_str); + + if (!dir_cache) { + ret = -ENOENT; + goto out; + } + + digest_cache_dir_lookup_filename(dentry, &dir_path, dir_cache, + last + 1); + + digest_cache_put(dir_cache); +out: + if (dir_path.dentry) + path_put(&dir_path); + + return ret; +} + /** * digest_cache_dir_free - Free the stored file list and put digest caches * @digest_cache: Dir digest cache diff --git a/security/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h index f849afe5e47b..5af7f6e7bdf6 100644 --- a/security/integrity/digest_cache/internal.h +++ b/security/integrity/digest_cache/internal.h @@ -18,6 +18,9 @@ #define INIT_STARTED 1 /* Digest cache init started. */ #define INVALID 2 /* Digest cache marked as invalid. */ #define IS_DIR 3 /* Digest cache created from dir. */ +#define DIR_PREFETCH 4 /* Prefetch enabled for dir. */ +#define FILE_PREFETCH 5 /* Prefetch enabled for dir entry. */ +#define FILE_READ 6 /* Digest cache for reading file. */ /** * struct readdir_callback - Structure to store information for dir iteration @@ -38,6 +41,7 @@ struct readdir_callback { * @digest_cache: Digest cache associated to the directory entry * @digest_cache_mutex: Protects @digest_cache * @seq_num: Sequence number of the directory entry from file name + * @prefetched: Whether the digest list has been already prefetched * @name: File name of the directory entry * * This structure represents a directory entry with a digest cache created @@ -48,6 +52,7 @@ struct dir_entry { struct digest_cache *digest_cache; struct mutex digest_cache_mutex; unsigned int seq_num; + bool prefetched; char name[]; }; @@ -219,7 +224,10 @@ digest_cache_from_file_sec(const struct file *file) struct digest_cache *digest_cache_create(struct dentry *dentry, struct path *default_path, struct path *digest_list_path, - char *path_str, char *filename); + char *path_str, char *filename, + bool prefetch_req, bool prefetch); +bool digest_cache_prefetch_requested(struct path *digest_list_path, + char *path_str); struct digest_cache *digest_cache_init(struct dentry *dentry, struct path *digest_list_path, struct digest_cache *digest_cache); @@ -255,6 +263,8 @@ struct digest_cache * digest_cache_dir_lookup_digest(struct dentry *dentry, struct digest_cache *digest_cache, u8 *digest, enum hash_algo algo); +int digest_cache_dir_prefetch(struct dentry *dentry, + struct digest_cache *digest_cache); void digest_cache_dir_free(struct digest_cache *digest_cache); #endif /* _DIGEST_CACHE_INTERNAL_H */ diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c index 9df819c323c7..ca1f0bc8ec94 100644 --- a/security/integrity/digest_cache/main.c +++ b/security/integrity/digest_cache/main.c @@ -86,6 +86,8 @@ static void digest_cache_free(struct digest_cache *digest_cache) * @digest_list_path: Path structure of the digest list * @path_str: Path string of the digest list * @filename: Digest list file name (can be an empty string) + * @prefetch_req: Whether prefetching has been requested + * @prefetch: Whether prefetching of a digest list is being done * * This function first locates, from the passed path and filename, the digest * list inode from which the digest cache will be created or retrieved (if it @@ -107,7 +109,8 @@ static void digest_cache_free(struct digest_cache *digest_cache) struct digest_cache *digest_cache_create(struct dentry *dentry, struct path *default_path, struct path *digest_list_path, - char *path_str, char *filename) + char *path_str, char *filename, + bool prefetch_req, bool prefetch) { struct digest_cache *digest_cache = NULL; struct digest_cache_security *dig_sec; @@ -160,6 +163,16 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, goto out; } + if (prefetch) { + digest_cache = digest_cache_alloc_init(path_str, filename); + if (digest_cache) { + set_bit(FILE_READ, &digest_cache->flags); + goto out_set; + } + + goto out; + } + /* Ref. count is already 1 for this reference. */ dig_sec->dig_owner = digest_cache_alloc_init(path_str, filename); if (!dig_sec->dig_owner) @@ -167,14 +180,68 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, /* Increment ref. count for reference returned to the caller. */ digest_cache = digest_cache_ref(dig_sec->dig_owner); - +out_set: /* Make other digest cache requestors wait until creation complete. */ set_bit(INIT_IN_PROGRESS, &digest_cache->flags); + + /* Set bit if prefetching was requested. */ + if (prefetch_req) { + if (S_ISREG(inode->i_mode)) + set_bit(FILE_PREFETCH, &digest_cache->flags); + else + set_bit(DIR_PREFETCH, &digest_cache->flags); + } out: mutex_unlock(&dig_sec->dig_owner_mutex); return digest_cache; } +/** + * digest_cache_prefetch_requested - Verify if prefetching is requested + * @digest_list_path: Path structure of the digest list directory + * @path_str: Path string of the digest list directory + * + * This function verifies whether or not digest list prefetching is requested. + * If dig_owner exists in the inode security blob, it checks the DIR_PREFETCH + * bit (faster). Otherwise, it reads the security.dig_prefetch xattr. + * + * Return: True if prefetching is requested, false otherwise. + */ +bool digest_cache_prefetch_requested(struct path *digest_list_path, + char *path_str) +{ + struct digest_cache_security *dig_sec; + bool prefetch_req = false; + char prefetch_value; + struct inode *inode; + int ret; + + inode = d_backing_inode(digest_list_path->dentry); + dig_sec = digest_cache_get_security(inode); + if (unlikely(!dig_sec)) + return false; + + mutex_lock(&dig_sec->dig_owner_mutex); + if (dig_sec->dig_owner) { + /* Reliable test: DIR_PREFETCH set with dig_owner_mutex held. */ + prefetch_req = test_bit(DIR_PREFETCH, + &dig_sec->dig_owner->flags); + mutex_unlock(&dig_sec->dig_owner_mutex); + return prefetch_req; + } + mutex_unlock(&dig_sec->dig_owner_mutex); + + ret = vfs_getxattr(&nop_mnt_idmap, digest_list_path->dentry, + XATTR_NAME_DIG_PREFETCH, &prefetch_value, 1); + if (ret == 1 && prefetch_value == '1') { + pr_debug("Prefetching has been enabled for directory %s\n", + path_str); + prefetch_req = true; + } + + return prefetch_req; +} + /** * digest_cache_new - Retrieve digest list file name and request digest cache * @dentry: Dentry of the inode for which the digest cache will be used @@ -186,6 +253,12 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, * with that file name. If security.digest_list is empty or not found, this * function requests the creation of a digest cache on the parent directory. * + * In either case, this function first calls digest_cache_prefetch_requested() + * to check if prefetching has been requested on the parent directory, and + * passes the result to digest_cache_create(), so that the latter sets the + * FILE_PREFETCH and DIR_PREFETCH bit respectively in the file or directory + * digest caches. + * * This function passes to the caller the path of the digest list from which * the digest cache was created (file or parent directory), so that * digest_cache_init() can reuse the same path, and to prevent the inode from @@ -199,6 +272,7 @@ static struct digest_cache *digest_cache_new(struct dentry *dentry, char filename[NAME_MAX + 1] = { 0 }; struct digest_cache *digest_cache = NULL; struct path default_path; + bool prefetch_req = false; int ret; ret = kern_path(default_path_str, 0, &default_path); @@ -244,9 +318,12 @@ static struct digest_cache *digest_cache_new(struct dentry *dentry, XATTR_NAME_DIGEST_LIST, dentry->d_name.name, default_path_str, filename); create: + prefetch_req = digest_cache_prefetch_requested(&default_path, + default_path_str); + digest_cache = digest_cache_create(dentry, &default_path, digest_list_path, default_path_str, - filename); + filename, prefetch_req, false); out: path_put(&default_path); return digest_cache; @@ -372,9 +449,20 @@ struct digest_cache *digest_cache_get(struct file *file) mutex_unlock(&dig_sec->dig_user_mutex); - if (digest_cache) + if (digest_cache) { + /* + * Prefetching should also initialize our digest cache, if the + * path didn't change meanwhile (otherwise the prefetching + * mechanism will create and initialize the digest cache of a + * different inode). + */ + if (test_bit(FILE_PREFETCH, &digest_cache->flags)) + digest_cache_dir_prefetch(dentry, digest_cache); + + /* Make sure we initialize our digest cache. */ digest_cache = digest_cache_init(dentry, &digest_list_path, digest_cache); + } if (digest_list_path.dentry) path_put(&digest_list_path); diff --git a/security/integrity/digest_cache/populate.c b/security/integrity/digest_cache/populate.c index 54f7f95f5794..b1646da07f36 100644 --- a/security/integrity/digest_cache/populate.c +++ b/security/integrity/digest_cache/populate.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "internal.h" @@ -82,6 +83,12 @@ int digest_cache_populate(struct dentry *dentry, return ret; } + /* The caller wants just to read digest lists. */ + if (test_bit(FILE_READ, &digest_cache->flags)) { + ret = 0; + goto out_vfree; + } + data_len = digest_cache_strip_modsig(data, ret); /* Digest list parsers initialize the hash table and add the digests. */ @@ -91,7 +98,7 @@ int digest_cache_populate(struct dentry *dentry, if (ret < 0) pr_debug("Error parsing digest list %s, ret: %d\n", digest_cache->path_str, ret); - +out_vfree: vfree(data); return ret; } diff --git a/security/integrity/digest_cache/verif.c b/security/integrity/digest_cache/verif.c index 03ebf0de764b..2854bdab3e07 100644 --- a/security/integrity/digest_cache/verif.c +++ b/security/integrity/digest_cache/verif.c @@ -44,6 +44,10 @@ int digest_cache_verif_set(struct file *file, const char *verif_id, void *data, gfp_t flags = !strncmp(verif_id, "kprobe", 6) ? GFP_ATOMIC : GFP_KERNEL; int ret = 0; + /* Allows us to detect that we are prefetching in the tests. */ + if (test_bit(FILE_READ, &digest_cache->flags)) + return -ENOENT; + /* * Zero the data, so that we can always call free_verif() to free a * partially filled structure (if a pointer is NULL, will not be freed). From patchwork Tue Nov 19 10:49:21 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 844381 Received: from frasgout11.his.huawei.com (frasgout11.his.huawei.com [14.137.139.23]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 008631C4A22; Tue, 19 Nov 2024 10:55:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=14.137.139.23 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013735; cv=none; b=p+ZggOkfwCsyG/FfcqVN+xiTXaBGGjoBHqUFzByueVeThsrt8ZtbgMRsM4tCbvHPBTo8A2A0Bo+62Llvmil9hoPbYMWtioLS3VIpz60pJ046VjZuUtHIApJXPdEFske2ibecBI7y9n7KDDbqKt+fzYBHBH8O9I5GaKrauj8VSpk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1732013735; c=relaxed/simple; bh=xvbfxwjf0+iEehIIM547dBNMdLXHYVrfxjY6ISAHpKs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=fRtYC+3JmSIO2/XXdyKQlBAcLa/NFsree/yz3iMIeZYf62cEV9AKzzkV0x+6q7Sbq7vNLFOmwzQbSIG/SNiGBQwnJZoL2mJZKVyV63qumwSNjVArOivUcZcK8VIT4A/BqH10dtTTYt0pTubLZkf5zcHz78UAlmZEuIViTFZ+KHo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com; spf=pass smtp.mailfrom=huaweicloud.com; arc=none smtp.client-ip=14.137.139.23 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=huaweicloud.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=huaweicloud.com Received: from mail.maildlp.com (unknown [172.18.186.51]) by frasgout11.his.huawei.com (SkyGuard) with ESMTP id 4Xt18C2qFMz9v7Hp; Tue, 19 Nov 2024 18:34:35 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.47]) by mail.maildlp.com (Postfix) with ESMTP id 18CCB140EA5; Tue, 19 Nov 2024 18:55:20 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwAHsyNDbjxnj1znAQ--.1193S6; Tue, 19 Nov 2024 11:55:19 +0100 (CET) From: Roberto Sassu To: zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, eric.snowberg@oracle.com, corbet@lwn.net, mcgrof@kernel.org, petr.pavlu@suse.com, samitolvanen@google.com, da.gomez@samsung.com, akpm@linux-foundation.org, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com Cc: linux-integrity@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-api@vger.kernel.org, linux-modules@vger.kernel.org, linux-security-module@vger.kernel.org, linux-kselftest@vger.kernel.org, wufan@linux.microsoft.com, pbrobinson@gmail.com, zbyszek@in.waw.pl, hch@lst.de, mjg59@srcf.ucam.org, pmatilai@redhat.com, jannh@google.com, dhowells@redhat.com, jikos@kernel.org, mkoutny@suse.com, ppavlu@suse.com, petr.vorel@gmail.com, mzerqung@0pointer.de, kgold@linux.ibm.com, Roberto Sassu Subject: [PATCH v6 14/15] selftests/digest_cache: Add selftests for the Integrity Digest Cache Date: Tue, 19 Nov 2024 11:49:21 +0100 Message-ID: <20241119104922.2772571-15-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.47.0.118.gfd3785337b In-Reply-To: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> References: <20241119104922.2772571-1-roberto.sassu@huaweicloud.com> Precedence: bulk X-Mailing-List: linux-kselftest@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-CM-TRANSID: LxC2BwAHsyNDbjxnj1znAQ--.1193S6 X-Coremail-Antispam: 1UD129KBjvAXoWDWrW7ur47WF1rAF1fKFykXwb_yoW7CF4UGo Zagw4UJw18Wr17CFWkuF13Jay3Ww4ft347JryrXrWqqF1rAryUK3Z2ka1Utry5W34rtr9x ZFsaqw13Ar48tr97n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUO57kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Cr1j6rxdM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26F4UJVW0owAS0I0E0xvYzxvE52x082IY62kv0487Mc02F40EFcxC0VAKzVAqx4xG6I 80ewAv7VC0I7IYx2IY67AKxVWUXVWUAwAv7VC2z280aVAFwI0_Jr0_Gr1lOx8S6xCaFVCj c4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JM4IIrI8v6xkF7I0E8cxan2IY04v7MxkF7I0En4 kS14v26rWY6Fy7MxAIw28IcxkI7VAKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E 5I8CrVAFwI0_Jr0_Jr4lx2IqxVCjr7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWrXV W8Jr1lIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Cr1j6rxdMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67 AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26F4UJVW0obIYCTnIWIevJa73UjIFyTuY vjxUVdgAUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAMBGc797QEhAADsV From: Roberto Sassu Add tests to verify the correctness of the Integrity Digest Cache, in all_test.c. Add the kernel module digest_cache_kern.ko, to let all_test call the API of the Integrity Digest Cache through the newly introduced digest_cache_test file in /integrity/digest_cache. Test coverage information: File 'security/integrity/digest_cache/reset.c' Lines executed:100.00% of 53 File 'security/integrity/digest_cache/main.c' Lines executed:90.29% of 206 File 'security/integrity/digest_cache/modsig.c' Lines executed:42.86% of 21 File 'security/integrity/digest_cache/htable.c' Lines executed:93.90% of 82 File 'security/integrity/digest_cache/populate.c' Lines executed:89.19% of 37 File 'security/integrity/digest_cache/verif.c' Lines executed:85.11% of 47 File 'security/integrity/digest_cache/dir.c' Lines executed:90.18% of 163 File 'security/integrity/digest_cache/parsers.c' Lines executed:45.74% of 94 File 'security/integrity/digest_cache/secfs.c' Lines executed:56.00% of 25 File 'security/integrity/digest_cache/parsers/tlv.c' Lines executed:75.26% of 97 Signed-off-by: Roberto Sassu --- 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 | 769 ++++++++++++++++++ tools/testing/selftests/digest_cache/common.c | 78 ++ tools/testing/selftests/digest_cache/common.h | 93 +++ .../selftests/digest_cache/common_user.c | 33 + .../selftests/digest_cache/common_user.h | 15 + tools/testing/selftests/digest_cache/config | 2 + .../selftests/digest_cache/generators.c | 130 +++ .../selftests/digest_cache/generators.h | 16 + .../selftests/digest_cache/testmod/Makefile | 16 + .../selftests/digest_cache/testmod/kern.c | 551 +++++++++++++ 13 files changed, 1731 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/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index b38199965f99..e6fc3c984923 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -16,6 +16,7 @@ TARGETS += cpu-hotplug TARGETS += damon TARGETS += devices/error_logs TARGETS += devices/probe +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..1b819201f406 --- /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) $(TEST_GEN_PROGS_EXTENDED) + 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..a283068f3cab --- /dev/null +++ b/tools/testing/selftests/digest_cache/all_test.c @@ -0,0 +1,769 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the tests of the Integrity Digest Cache. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, no_kprobe; +}; + +FIXTURE_SETUP(shared_data) +{ + char cmd[1024]; + int ret, 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 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]); + ret = write(self->kernfd, cmd, cmd_len); + if (ret < 0 && errno == EOPNOTSUPP) { + self->no_kprobe = 1; + break; + } + + ASSERT_EQ(cmd_len, ret); + } +} + +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 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, + bool write_digest_list, int digest_lists_dirfd, + char *digest_list_filename) +{ + 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, fd, 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; + + if (write_digest_list) { + fd = openat(digest_lists_dirfd, digest_list_filename, O_WRONLY); + if (fd < 0) + return -errno; + + close(fd); + } + + 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 int query_test(int kernfd, char *base_dir, char *filename, + enum hash_algo algo, int start_number, int num_digests) +{ + return _query_test(kernfd, base_dir, filename, algo, start_number, + num_digests, false, -1, NULL); +} + +static int query_test_write(int kernfd, char *base_dir, char *filename, + enum hash_algo algo, int start_number, + int num_digests, int digest_lists_dirfd, + char *digest_list_filename) +{ + return _query_test(kernfd, base_dir, filename, algo, start_number, + num_digests, true, digest_lists_dirfd, + digest_list_filename); +} + +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)); + } + + 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); +} + +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 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)); + + 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)); + + return; + default: + break; + } + + ASSERT_NE(0, query_test(self->kernfd, self->base_dir, "file", + HASH_ALGO_SHA1, 0, 1)); +} + +/* + * 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); +} + +/* + * Verify that digest lookup does not work after a reset, and attempt the + * query a second time, which should instead succeed. + */ +TEST_F(shared_data, file_reset_again) +{ + char digest_list_filename[] = "tlv-digest_list"; + + 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_NE(0, query_test_write(self->kernfd, self->base_dir, "file", + HASH_ALGO_SHA1, 0, 1, + self->digest_lists_dirfd, + digest_list_filename)); + + ASSERT_EQ(0, query_test(self->kernfd, self->base_dir, "file", + HASH_ALGO_SHA1, 0, 1)); +} + +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) +{ + char filename[NAME_MAX + 1]; + 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; + } + } + + snprintf(filename, sizeof(filename), "file%d", i); + + ASSERT_EQ(expected_ret, query_test(self->kernfd, self->base_dir, + filename, 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 filename[NAME_MAX + 1]; + int i, removed[10]; + + for (i = 0; i < 10; i++) { + snprintf(filename, sizeof(filename), "file%d", i); + ASSERT_EQ(0, create_file(self->base_dirfd, filename, NULL)); + snprintf(filename, sizeof(filename), "tlv-digest_list%d", i); + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, + filename, HASH_ALGO_SHA1, i, 1, + TLV_NO_FAILURE)); + } + + query_test_with_failures(self, _metadata, 0, 10, removed, 0); + + ASSERT_EQ(0, unlinkat(self->digest_lists_dirfd, "tlv-digest_list7", 0)); + + removed[0] = 7; + + query_test_with_failures(self, _metadata, 0, 10, removed, 1); + + ASSERT_EQ(0, renameat(self->digest_lists_dirfd, "tlv-digest_list6", + self->base_dirfd, "tlv-digest_list6")); + + removed[1] = 6; + + query_test_with_failures(self, _metadata, 0, 10, removed, 2); + + ASSERT_EQ(0, renameat(self->base_dirfd, "tlv-digest_list6", + self->digest_lists_dirfd, "tlv-digest_list6")); + + query_test_with_failures(self, _metadata, 0, 10, removed, 1); + + ASSERT_EQ(0, create_file(self->base_dirfd, "file10", NULL)); + ASSERT_EQ(0, gen_tlv_list(self->digest_lists_dirfd, "tlv-digest_list10", + HASH_ALGO_SHA1, 10, 1, TLV_NO_FAILURE)); + + 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)); + } + + for (i = 0; i < 11; i++) { + ASSERT_EQ(0, create_file(self->base_dirfd, "file", NULL)); + 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) +{ + if (self->no_kprobe) + SKIP(return, "CONFIG_DYNAMIC_FTRACE_WITH_ARGS not enabled"); + + 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) +{ + if (self->no_kprobe) + SKIP(return, "CONFIG_DYNAMIC_FTRACE_WITH_ARGS not enabled"); + + 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] = { 0 }, digest_lists_kernel[1024]; + 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_kernel, + sizeof(digest_lists_kernel))); + + for (i = start_number; i <= end_number; i++) { + if (digest_lists[0]) + strcat(digest_lists, ","); + + snprintf(digest_list_filename, sizeof(digest_list_filename), + "%d-tlv-digest_list%d", i, i); + strcat(digest_lists, 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] = { 0 }, digest_lists_kernel[1024]; + 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_kernel, + sizeof(digest_lists_kernel))); + + for (i = 0; i < NUM_DIGEST_LISTS_PREFETCH; i++) { + if (digest_lists[0]) + strcat(digest_lists, ","); + + snprintf(digest_list_filename, sizeof(digest_list_filename), + "%d-tlv-digest_list%d", i, i); + strcat(digest_lists, 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; + + if (self->no_kprobe) + SKIP(return, "CONFIG_DYNAMIC_FTRACE_WITH_ARGS not enabled"); + + 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) +{ + if (self->no_kprobe) + SKIP(return, "CONFIG_DYNAMIC_FTRACE_WITH_ARGS not enabled"); + + 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..75c0ef50deb8 --- /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 + * + * Add common code for testing the Integrity Digest Cache. + */ + +#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] = "kprobe_filenames", + [VERIF_NUMBER] = "kprobe_number", + [VERIF_PREFETCH] = "kprobe_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..3fd3c249ff6f --- /dev/null +++ b/tools/testing/selftests/digest_cache/common.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header of common.c. + */ + +#ifndef _COMMON_H +#define _COMMON_H +#include + +#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_DIR "/sys/kernel/security/integrity/digest_cache" +#define DIGEST_CACHE_TEST_INTERFACE "/sys/kernel/security/digest_cache_test" +#define DIGEST_CACHE_PATH_INTERFACE DIGEST_CACHE_DIR "/default_path" +#define MAX_DIGEST_SIZE 64 + +#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: + DIGEST_CACHE_LOOKUP, // args: | + DIGEST_CACHE_PUT, // args: + DIGEST_CACHE_ENABLE_VERIF, // args: + DIGEST_CACHE_DISABLE_VERIF, // args: + DIGEST_CACHE_SET_VERIF, // args: + DIGEST_CACHE_GET_PUT_ASYNC, // args: || + DIGEST_CACHE__LAST, +}; + +enum tlv_failures { TLV_NO_FAILURE, + TLV_FAILURE_ALGO_LEN, + TLV_FAILURE_ALGO_MISMATCH, + TLV_FAILURE_NUM_DIGESTS, + TLV_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 +}; + +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..02f661b942f7 --- /dev/null +++ b/tools/testing/selftests/digest_cache/common_user.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Add common code in user space for testing the Integrity Digest Cache. + */ + +#include + +#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] + +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..8d1f6631ef83 --- /dev/null +++ b/tools/testing/selftests/digest_cache/common_user.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Header of common_user.c. + */ + +#include +#include + +#include "common.h" + +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..7e0ea7d18828 --- /dev/null +++ b/tools/testing/selftests/digest_cache/config @@ -0,0 +1,2 @@ +CONFIG_INTEGRITY_DIGEST_CACHE=y +CONFIG_DYNAMIC_FTRACE=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..8c36c22e8bcc --- /dev/null +++ b/tools/testing/selftests/digest_cache/generators.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Generate digest lists for testing. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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) +{ + __u16 _algo = __cpu_to_be16(algo); + __u32 _num_entries = __cpu_to_be32(num_digests); + u8 digest[MAX_DIGEST_SIZE] = { 0 }; + int digest_len = hash_digest_size[algo]; + int ret, fd, i; + + struct tlv_entry algo_entry = { + .field = __cpu_to_be16(DIGEST_LIST_ALGO), + .length = __cpu_to_be32(sizeof(_algo)), + }; + + struct tlv_entry num_entries_entry = { + .field = __cpu_to_be16(DIGEST_LIST_NUM_ENTRIES), + .length = __cpu_to_be32(sizeof(_num_entries)), + }; + + struct tlv_entry entry_digest = { + .field = __cpu_to_be16(DIGEST_LIST_ENTRY_DIGEST), + .length = __cpu_to_be32(digest_len), + }; + + struct tlv_entry entry_entry = { + .field = __cpu_to_be16(DIGEST_LIST_ENTRY), + .length = __cpu_to_be32(sizeof(entry_digest) + digest_len), + }; + + switch (failure) { + case TLV_FAILURE_ALGO_LEN: + algo_entry.length = algo_entry.length / 2; + break; + case TLV_FAILURE_ALGO_MISMATCH: + _algo = __cpu_to_be16(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 *)&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; + + ret = write(fd, (u8 *)&num_entries_entry, sizeof(num_entries_entry)); + if (ret != sizeof(num_entries_entry)) + return -errno; + + ret = write(fd, (u8 *)&_num_entries, sizeof(_num_entries)); + if (ret != sizeof(_num_entries)) + 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_digest, sizeof(entry_digest)); + if (ret != sizeof(entry_digest)) + return -errno; + + ret = write(fd, digest, digest_len); + if (ret != digest_len) + return -errno; + + (*(u32 *)digest)++; + } + + 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..eeeb3c020891 --- /dev/null +++ b/tools/testing/selftests/digest_cache/generators.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * 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 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..4c215fd0b50f --- /dev/null +++ b/tools/testing/selftests/digest_cache/testmod/kern.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the kernel module to interact with the Integrity Digest Cache. + */ + +#define pr_fmt(fmt) "digest_cache TESTMOD: "fmt +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +static struct digest_cache *digest_cache, *found; +static int cur_verif_index; +static u8 prefetch_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, verifs_str[VERIF_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, verifs_str[VERIF_NUMBER], &number, + sizeof(number)); +} + +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, "kprobe_test", "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 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, + 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, + 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 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 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; + struct file *file; + + w->ret = kern_path(w->path_str, 0, &path); + if (w->ret < 0) + return; + + file = kernel_file_open(&path, O_RDONLY, current_cred()); + if (IS_ERR(file)) { + path_put(&path); + return; + } + + digest_cache = digest_cache_get(file); + + fput(file); + 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; + struct file *filp; + 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: + if (found) { + if (!IS_ERR(found)) + digest_cache_put(found); + + found = NULL; + } + + 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); + } + + filp = kernel_file_open(&path, O_RDONLY, current_cred()); + if (IS_ERR(filp)) { + pr_debug("Cannot open file %s\n", path_str); + path_put(&path); + goto out; + } + + digest_cache = digest_cache_get(filp); + ret = digest_cache ? 0 : -ENOENT; + pr_debug("digest cache get %s, ret: %d\n", path_str, ret); + fput(filp); + path_put(&path); + break; + case DIGEST_CACHE_LOOKUP: + if (found) { + if (!IS_ERR(found)) + digest_cache_put(found); + + found = NULL; + } + + 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 && !IS_ERR(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; + } + + if (found) { + if (!IS_ERR(found)) + digest_cache_put(found); + + found = NULL; + } + ret = 0; + pr_debug("digest cache put, ret: %d\n", ret); + break; + case DIGEST_CACHE_ENABLE_VERIF: + case DIGEST_CACHE_DISABLE_VERIF: + if (!IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_ARGS)) { + ret = -EOPNOTSUPP; + goto out; + } + + 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 int __kprobes kernel_post_read_file_hook(struct kprobe *p, + struct pt_regs *regs) +{ +#ifdef CONFIG_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; + + if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_ARGS)) { + 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; + } + + return 0; +out_kprobe: + if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_ARGS)) + unregister_kprobe(&kp); + return ret; +} + +static void __exit digest_cache_test_fini(void) +{ + if (digest_cache) + digest_cache_put(digest_cache); + + if (found) { + if (!IS_ERR(found)) + digest_cache_put(found); + + found = NULL; + } + + securityfs_remove(test); + if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_ARGS)) + 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"); +MODULE_DESCRIPTION("Kernel module for testing Integrity Digest Cache");