From patchwork Mon Apr 15 14:24:23 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 789071 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 083F82119; Mon, 15 Apr 2024 14:42:07 +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=1713192132; cv=none; b=K98nfdwt21YseMjF2iRte6l7I1IbSEjiUxjgHnfebgwyTJtFQHX8iK/lP0Xi0jtScDdbUglbXXsc00nXrCn7a6eU93/OJpODDMd27zPvEoZ2VajkO+YZBP8IlovNWqB3tRyHvSru39i6PQntrferpFgUcEQPVXOLlvjzmBJKst8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713192132; c=relaxed/simple; bh=f6afaogS3EpN5Oa5pEvlWWVqIX4vOSrgpujgS6+Rsas=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=pup6Yz/9qggHIVyDXPAMQZUXOj11LJ/7ZkxSk4Tm89+SrUYyszfKeBZLtONPIF1Xg5F76E60jPpr5JPQcXNj1J1d340aE6wRid09N28hEoTbQUn8JHiLUV8OXRHsn8qxmNIOD8Ps7hQspBEtwYIV5xYwQl0+mMFIxP0ql+xYvcw= 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 4VJ8DH42B9z9ttCm; Mon, 15 Apr 2024 22:09:03 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id 9E6B1140628; Mon, 15 Apr 2024 22:25:20 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwBnoSWrOB1myEJGBg--.9473S3; Mon, 15 Apr 2024 15:25:17 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, akpm@linux-foundation.org, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, mic@digikod.net Cc: linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, linux-integrity@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 v4 01/14] lib: Add TLV parser Date: Mon, 15 Apr 2024 16:24:23 +0200 Message-Id: <20240415142436.2545003-2-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240415142436.2545003-1-roberto.sassu@huaweicloud.com> References: <20240415142436.2545003-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: GxC2BwBnoSWrOB1myEJGBg--.9473S3 X-Coremail-Antispam: 1UD129KBjvAXoW3Cr18Kw1UGrW5tF1kCw15Jwb_yoW8Jw1DKo ZI9rW5ur4rXr1293W8Za1kZr1UXry0gr43Aw13GrW3ua4IkayUKr43tw43G3y3Aws8Kr45 t3sxX3y3Xw4UKrn3n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOY7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jr 4l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x0267AK xVW8Jr0_Cr1UM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02F40Ex7 xfMcIj6xIIjxv20xvE14v26r126r1DMcIj6I8E87Iv67AKxVWUJVW8JwAm72CE4IkC6x0Y z7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7CjxVAaw2 AFwI0_GFv_Wryl42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2IqxVAq x4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v26rWY6r 4UJwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r4j6ryUMIIF0xvE2Ix0cI8IcVCY 1x0267AKxVW8Jr0_Cr1UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCwCI42IY6I8E87Iv67 AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26r4UJVWxJrUvcSsGvfC2KfnxnUUI43ZE Xa7IU07rcDUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAOBF1jj5x10AAAsD From: Roberto Sassu Add a parser of a generic TLV format: +-----------------+------------------+-----------------+ | data type (u64) | num fields (u64) | total len (u64) | # header +--------------+--+---------+--------+---------+-------+ | field1 (u64) | len1 (u64) | value1 (u8 len1) | +--------------+------------+------------------+ | ... | ... | ... | # data +--------------+------------+------------------+ | fieldN (u64) | lenN (u64) | valueN (u8 lenN) | +--------------+------------+------------------+ Each adopter can define its own data types and fields. The TLV parser does not need to be aware of those, and calls a callback function with the callback data, both supplied by the adopter, for every encountered field during parsing. The adopter can decide in the callback function how each defined field should be handled/parsed. Normally, calling tlv_parse() is sufficient for most of the use cases. In addition, tlv_parse_hdr() and tlv_parse_data() are also provided for more advanced use cases. Nesting TLVs is also possible, a callback function can call tlv_parse() to parse the inner structure. Signed-off-by: Roberto Sassu --- MAINTAINERS | 8 ++ include/linux/tlv_parser.h | 28 +++++ include/uapi/linux/tlv_parser.h | 59 +++++++++ lib/Kconfig | 3 + lib/Makefile | 3 + lib/tlv_parser.c | 214 ++++++++++++++++++++++++++++++++ lib/tlv_parser.h | 17 +++ 7 files changed, 332 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 96a3b60cfc67..b1ca23ab8732 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22148,6 +22148,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..565743b3cb30 --- /dev/null +++ b/include/linux/tlv_parser.h @@ -0,0 +1,28 @@ +/* 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 int (*parse_callback)(void *, __u64, const __u8 *, __u64); + +int tlv_parse_hdr(const __u8 **data, size_t *data_len, __u64 *parsed_data_type, + __u64 *parsed_num_entries, __u64 *parsed_total_len, + const char **data_types, __u64 num_data_types); +int tlv_parse_data(parse_callback callback, void *callback_data, + __u64 num_entries, const __u8 *data, size_t data_len, + const char **fields, __u64 num_fields); +int tlv_parse(__u64 expected_data_type, parse_callback callback, + void *callback_data, const __u8 *data, size_t data_len, + const char **data_types, __u64 num_data_types, + const char **fields, __u64 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..3968c96f2518 --- /dev/null +++ b/include/uapi/linux/tlv_parser.h @@ -0,0 +1,59 @@ +/* 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: + * + * +-----------------+------------------+-----------------+ + * | data type (u64) | num fields (u64) | total len (u64) | # header + * +--------------+--+---------+--------+---------+-------+ + * | field1 (u64) | len1 (u64) | value1 (u8 len1) | + * +--------------+------------+------------------+ + * | ... | ... | ... | # data + * +--------------+------------+------------------+ + * | fieldN (u64) | lenN (u64) | valueN (u8 lenN) | + * +--------------+------------+------------------+ + */ + +/** + * struct tlv_hdr - Header of TLV format + * @data_type: Type of data to parse + * @num_entries: Number of data entries provided + * @_reserved: Reserved for future use (must be equal to zero) + * @total_len: Total length of the data blob, excluding the header + * + * This structure represents the header of the TLV data format. + */ +struct tlv_hdr { + __u64 data_type; + __u64 num_entries; + __u64 _reserved; + __u64 total_len; +} __attribute__((packed)); + +/** + * struct tlv_data_entry - Data entry of TLV format + * @field: Data field identifier + * @length: Data length + * @data: Data + * + * This structure represents a TLV entry of the data part of TLV data format. + */ +struct tlv_data_entry { + __u64 field; + __u64 length; + __u8 data[]; +} __attribute__((packed)); + +#endif /* _UAPI_LINUX_TLV_PARSER_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 5ddda7c2ed9b..d4d3c4435427 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -785,3 +785,6 @@ config POLYNOMIAL config FIRMWARE_TABLE bool + +config TLV_PARSER + bool diff --git a/lib/Makefile b/lib/Makefile index 6b09731d8e61..23f0b770a639 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -444,3 +444,6 @@ $(obj)/$(TEST_FORTIFY_LOG): $(addprefix $(obj)/, $(TEST_FORTIFY_LOGS)) FORCE ifeq ($(CONFIG_FORTIFY_SOURCE),y) $(obj)/string.o: $(obj)/$(TEST_FORTIFY_LOG) endif + +obj-$(CONFIG_TLV_PARSER) += tlv_parser.o +CFLAGS_tlv_parser.o += -I lib diff --git a/lib/tlv_parser.c b/lib/tlv_parser.c new file mode 100644 index 000000000000..9565542932eb --- /dev/null +++ b/lib/tlv_parser.c @@ -0,0 +1,214 @@ +// 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_hdr - Parse TLV header + * @data: Data to parse (updated) + * @data_len: Length of @data (updated) + * @parsed_data_type: Parsed data type (updated) + * @parsed_num_entries: Parsed number of data entries (updated) + * @parsed_total_len: Parsed length of TLV data, excluding the header (updated) + * @data_types: Array of data type strings + * @num_data_types: Number of elements of @data_types + * + * Parse the header of the TLV data format, move the data pointer to the TLV + * data part, decrease the data length by the length of the header, and provide + * the data type, number of entries and the total data length extracted from the + * header. + * + * Return: Zero on success, a negative value on error. + */ +int tlv_parse_hdr(const __u8 **data, size_t *data_len, __u64 *parsed_data_type, + __u64 *parsed_num_entries, __u64 *parsed_total_len, + const char **data_types, __u64 num_data_types) +{ + struct tlv_hdr *hdr; + + if (*data_len < sizeof(*hdr)) { + pr_debug("Data blob too short, %lu bytes, expected %lu\n", + *data_len, sizeof(*hdr)); + return -EBADMSG; + } + + hdr = (struct tlv_hdr *)*data; + + *data += sizeof(*hdr); + *data_len -= sizeof(*hdr); + + *parsed_data_type = __be64_to_cpu(hdr->data_type); + if (*parsed_data_type >= num_data_types) { + pr_debug("Invalid data type %llu, max: %llu\n", + *parsed_data_type, num_data_types - 1); + return -EBADMSG; + } + + *parsed_num_entries = __be64_to_cpu(hdr->num_entries); + + if (hdr->_reserved != 0) { + pr_debug("_reserved must be zero\n"); + return -EBADMSG; + } + + *parsed_total_len = __be64_to_cpu(hdr->total_len); + if (*parsed_total_len > *data_len) { + pr_debug("Invalid total length %llu, expected: %lu\n", + *parsed_total_len, *data_len); + return -EBADMSG; + } + + pr_debug("Header: type: %s, num entries: %llu, total len: %lld\n", + data_types[*parsed_data_type], *parsed_num_entries, + *parsed_total_len); + + return 0; +} + +/** + * tlv_parse_data - Parse TLV data + * @callback: Callback function to call to parse the entries + * @callback_data: Opaque data to supply to the callback function + * @num_entries: Number of data entries to parse + * @data: Data to parse + * @data_len: Length of @data + * @fields: Array of field strings + * @num_fields: Number of elements of @fields + * + * Parse the data part of the TLV data format and call the supplied callback + * function for each data 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_data(parse_callback callback, void *callback_data, + __u64 num_entries, const __u8 *data, size_t data_len, + const char **fields, __u64 num_fields) +{ + const __u8 *data_ptr = data; + struct tlv_data_entry *entry; + __u64 parsed_field, len, i, max_num_entries; + int ret; + + max_num_entries = data_len / sizeof(*entry); + + /* Finite termination on num_entries. */ + if (num_entries > max_num_entries) + return -EBADMSG; + + for (i = 0; i < num_entries; i++) { + if (data_len < sizeof(*entry)) + return -EBADMSG; + + entry = (struct tlv_data_entry *)data_ptr; + data_ptr += sizeof(*entry); + data_len -= sizeof(*entry); + + parsed_field = __be64_to_cpu(entry->field); + if (parsed_field >= num_fields) { + pr_debug("Invalid field %llu, max: %llu\n", + parsed_field, num_fields - 1); + return -EBADMSG; + } + + len = __be64_to_cpu(entry->length); + + if (data_len < len) + return -EBADMSG; + + pr_debug("Data: field: %s, len: %llu\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: %lu bytes\n", data_len); + return -EBADMSG; + } + + return 0; +} + +/** + * tlv_parse - Parse data in TLV format + * @expected_data_type: Desired data type + * @callback: Callback function to call to parse the data entries + * @callback_data: Opaque data to supply to the callback function + * @data: Data to parse + * @data_len: Length of @data + * @data_types: Array of data type strings + * @num_data_types: Number of elements of @data_types + * @fields: Array of field strings + * @num_fields: Number of elements of @fields + * + * Parse data in TLV format and call tlv_parse_data() each time the header has + * the same data type as the expected one. + * + * Return: Zero on success, a negative value on error. + */ +int tlv_parse(__u64 expected_data_type, parse_callback callback, + void *callback_data, const __u8 *data, size_t data_len, + const char **data_types, __u64 num_data_types, + const char **fields, __u64 num_fields) +{ + __u64 parsed_data_type, parsed_num_entries, parsed_total_len; + const __u8 *data_ptr = data; + int ret = 0; + + pr_debug("Start parsing data blob, size: %lu, expected data type: %s\n", + data_len, data_types[expected_data_type]); + + while (data_len) { + ret = tlv_parse_hdr(&data_ptr, &data_len, &parsed_data_type, + &parsed_num_entries, &parsed_total_len, + data_types, num_data_types); + if (ret < 0) + goto out; + + /* Skip data with a different data type than expected. */ + if (parsed_data_type != expected_data_type) { + /* + * tlv_parse_hdr() already checked that + * parsed_total_len <= data_len. + */ + data_ptr += parsed_total_len; + data_len -= parsed_total_len; + continue; + } + + pr_debug("Found data type %s at offset %ld\n", + data_types[parsed_data_type], data_ptr - data); + + ret = tlv_parse_data(callback, callback_data, + parsed_num_entries, data_ptr, + parsed_total_len, fields, num_fields); + if (ret < 0) + goto out; + + data_ptr += parsed_total_len; + data_len -= parsed_total_len; + } +out: + pr_debug("End of parsing data blob, ret: %d\n", ret); + return ret; +} diff --git a/lib/tlv_parser.h b/lib/tlv_parser.h new file mode 100644 index 000000000000..8fa8127bd13e --- /dev/null +++ b/lib/tlv_parser.h @@ -0,0 +1,17 @@ +/* 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 + +#endif /* _LIB_TLV_PARSER_H */ From patchwork Mon Apr 15 14:24:25 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 789077 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 648CF757E1; Mon, 15 Apr 2024 14:25:59 +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=1713191161; cv=none; b=sbq9ifUfVWvd3KF26gumdwta68Erhi3MBERns5tzeRB0ow3UyTQmncVjqBCdhT1vari5w9PXKE4iIfTaywuNDNaPp+nGFYDi8oyJ8e9r2tu3yZxWBO9UF/PxEBurGVCrEkW7lMhQ1VTZyMLMjHXftorpSFZGMF/igWEIQHYRVbs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713191161; c=relaxed/simple; bh=4YogMc3bBBFZcievjqeeR/1jzuCv2Qh7C5E3wQz7ArE=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=eJwwxRLE8rNCgz3Fi/2a/W4rfc2AC5c/P1iXJIHE4CvfA2NZt/ZGEpxlhLGmr2mQisMtE8OUdLKjnMWUVjCJ2y4K4Aw+HGsDMmIz9o6vIR9EyO+Tvc1Yag1kb9xfmCcRvgqMqnIQ7c11Vstz+adE2+ILPjgke1TfJjKxU6euckk= 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 4VJ87g1L1zz9xGWd; Mon, 15 Apr 2024 22:05:03 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id 9B365140854; Mon, 15 Apr 2024 22:25:50 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwBnoSWrOB1myEJGBg--.9473S5; Mon, 15 Apr 2024 15:25:49 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, akpm@linux-foundation.org, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, mic@digikod.net Cc: linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, linux-integrity@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 v4 03/14] digest_cache: Add securityfs interface Date: Mon, 15 Apr 2024 16:24:25 +0200 Message-Id: <20240415142436.2545003-4-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240415142436.2545003-1-roberto.sassu@huaweicloud.com> References: <20240415142436.2545003-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: GxC2BwBnoSWrOB1myEJGBg--.9473S5 X-Coremail-Antispam: 1UD129KBjvJXoW3XryrAw4rtr4UWF4UWryxuFg_yoWxWw1rp3 9Ik3WUKr4xZF13Awn7A3W7CF1rK398tF47Cw4DW343AFW3uwn0va4Iyr1UZryUXr4UZayI yw4jkr15Xr4qqaUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPvb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUWw A2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0rcxS w2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267AKxV W8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v2 6r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrVC2j2 WlYx0E2Ix0cI8IcVAFwI0_JF0_Jw1lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE7xkE bVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262kKe7 AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02 F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Wrv_Gr 1UMIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Gr0_Xr1lIxAIcVC0I7IYx2IY6xkF 7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14 v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIFyTuY vjxUIUUUUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAOBF1jj5h0rgAAs7 From: Roberto Sassu Add the digest_cache_path file in securityfs, to let root change/read the default path (file or directory) from where digest lists are looked up. An RW semaphore prevents the default path from changing while digest_list_new() and read_default_path() are executed, so that those read a stable value. Multiple digest_list_new() and read_default_path() calls, instead, can be done in parallel, since they are the readers. Changing the default path does not affect digest caches created with the old path. Signed-off-by: Roberto Sassu --- security/digest_cache/Kconfig | 4 ++ security/digest_cache/Makefile | 2 +- security/digest_cache/internal.h | 1 + security/digest_cache/main.c | 10 +++- security/digest_cache/secfs.c | 87 ++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 security/digest_cache/secfs.c diff --git a/security/digest_cache/Kconfig b/security/digest_cache/Kconfig index e53fbf0779d6..dfabe5d6e3ca 100644 --- a/security/digest_cache/Kconfig +++ b/security/digest_cache/Kconfig @@ -14,3 +14,7 @@ config DIGEST_LIST_DEFAULT_PATH default "/etc/digest_lists" help Default directory where digest_cache LSM expects to find digest lists. + + 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. diff --git a/security/digest_cache/Makefile b/security/digest_cache/Makefile index 48848c41253e..1330655e33b1 100644 --- a/security/digest_cache/Makefile +++ b/security/digest_cache/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_SECURITY_DIGEST_CACHE) += digest_cache.o -digest_cache-y := main.o +digest_cache-y := main.o secfs.o diff --git a/security/digest_cache/internal.h b/security/digest_cache/internal.h index 5f04844af3a5..bbf5eefe5c82 100644 --- a/security/digest_cache/internal.h +++ b/security/digest_cache/internal.h @@ -49,6 +49,7 @@ struct digest_cache_security { extern struct lsm_blob_sizes digest_cache_blob_sizes; extern char *default_path_str; +extern struct rw_semaphore default_path_sem; static inline struct digest_cache_security * digest_cache_get_security(const struct inode *inode) diff --git a/security/digest_cache/main.c b/security/digest_cache/main.c index 14dba8915e99..661c8d106791 100644 --- a/security/digest_cache/main.c +++ b/security/digest_cache/main.c @@ -18,6 +18,9 @@ static struct kmem_cache *digest_cache_cache __read_mostly; char *default_path_str = CONFIG_DIGEST_LIST_DEFAULT_PATH; +/* Protects default_path_str. */ +struct rw_semaphore default_path_sem; + /** * digest_cache_alloc_init - Allocate and initialize a new digest cache * @path_str: Path string of the digest list @@ -274,9 +277,12 @@ struct digest_cache *digest_cache_get(struct dentry *dentry) /* Serialize accesses to inode for which the digest cache is used. */ mutex_lock(&dig_sec->dig_user_mutex); - if (!dig_sec->dig_user) + if (!dig_sec->dig_user) { + down_read(&default_path_sem); /* Consume extra reference from digest_cache_create(). */ dig_sec->dig_user = digest_cache_new(dentry); + up_read(&default_path_sem); + } if (dig_sec->dig_user) /* Increment ref. count for reference returned to the caller. */ @@ -386,6 +392,8 @@ static const struct lsm_id digest_cache_lsmid = { */ static int __init digest_cache_init(void) { + init_rwsem(&default_path_sem); + digest_cache_cache = kmem_cache_create("digest_cache_cache", sizeof(struct digest_cache), 0, SLAB_PANIC, diff --git a/security/digest_cache/secfs.c b/security/digest_cache/secfs.c new file mode 100644 index 000000000000..d3a37bf3588e --- /dev/null +++ b/security/digest_cache/secfs.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the securityfs interface of the digest_cache LSM. + */ + +#define pr_fmt(fmt) "DIGEST CACHE: "fmt +#include + +#include "internal.h" + +static struct dentry *default_path_dentry; + +/** + * write_default_path - Write default path + * @file: File descriptor of the securityfs file + * @buf: User space buffer + * @datalen: Amount of data to write + * @ppos: Current position in the file + * + * This function sets the new default path where digest lists can be found. + * Can be either a regular file or a directory. + * + * Return: Length of path written on success, a POSIX error code otherwise. + */ +static ssize_t write_default_path(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + char *new_default_path_str; + + new_default_path_str = memdup_user_nul(buf, datalen); + if (IS_ERR(new_default_path_str)) + return PTR_ERR(new_default_path_str); + + down_write(&default_path_sem); + kfree_const(default_path_str); + default_path_str = new_default_path_str; + up_write(&default_path_sem); + return datalen; +} + +/** + * read_default_path - Read default path + * @file: File descriptor of the securityfs file + * @buf: User space buffer + * @datalen: Amount of data to read + * @ppos: Current position in the file + * + * This function returns the current default path where digest lists can be + * found. Can be either a regular file or a directory. + * + * Return: Length of path read on success, a POSIX error code otherwise. + */ +static ssize_t read_default_path(struct file *file, char __user *buf, + size_t datalen, loff_t *ppos) +{ + int ret; + + down_read(&default_path_sem); + ret = simple_read_from_buffer(buf, datalen, ppos, default_path_str, + strlen(default_path_str) + 1); + up_read(&default_path_sem); + return ret; +} + +static const struct file_operations default_path_ops = { + .open = generic_file_open, + .write = write_default_path, + .read = read_default_path, + .llseek = generic_file_llseek, +}; + +static int __init digest_cache_path_init(void) +{ + default_path_dentry = securityfs_create_file("digest_cache_path", 0660, + NULL, NULL, + &default_path_ops); + if (IS_ERR(default_path_dentry)) + return -EFAULT; + + return 0; +} + +late_initcall(digest_cache_path_init); From patchwork Mon Apr 15 14:24:27 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 789076 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 4A33178C8B; Mon, 15 Apr 2024 14:26:38 +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=1713191200; cv=none; b=Fa6BWXjO/b3Lj1nJbe+B3hQC6PiDTypVKNlaTJgBQS3k8cFB/0njV9cs6lL+EPNjuc3z1KLC6lksJ41peFwX4QNXz+D8SawA10GEdCIVhwXQxnnFF7yFovvZfTM/DckLRER6UUc/0RPUPDANxKzdbw4zzNBCZmRDJUVBqfFmMVg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713191200; c=relaxed/simple; bh=5VnuoTe6EwDxH7KJa5pLQY6uvcMSRd2El7fC+ZzNd/E=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=pH34/ZN3Gw2oBKeDEpN6mxXLVHoTwmCvknxUkRyzjfoAkt/x4dvVaqjiaHr5Olt0VuGn3wsg63N5cu0n4sBBAmKDYg8/qX80W7hFqtPo9iLdrkY/S343tAdiCh8Me+maH/i/ti/UXkq+cmXYxOwPXaFfozGqtxdb1jqYWMBB69E= 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 4VJ88N02WBz9xGX7; Mon, 15 Apr 2024 22:05:40 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id EAD4D14037F; Mon, 15 Apr 2024 22:26:22 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwBnoSWrOB1myEJGBg--.9473S7; Mon, 15 Apr 2024 15:26:22 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, akpm@linux-foundation.org, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, mic@digikod.net Cc: linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, linux-integrity@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 v4 05/14] digest_cache: Populate the digest cache from a digest list Date: Mon, 15 Apr 2024 16:24:27 +0200 Message-Id: <20240415142436.2545003-6-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240415142436.2545003-1-roberto.sassu@huaweicloud.com> References: <20240415142436.2545003-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: GxC2BwBnoSWrOB1myEJGBg--.9473S7 X-Coremail-Antispam: 1UD129KBjvJXoWfGr45Zr4DWryxJr15tw15CFg_yoWktrykp3 sxCF15trWrJF1fCw4xAF12kryfKrWktF42qws5ur1ayr47Xr1Yy3WIk3Wjvry5Gr4Uua17 Ar4UKFyUKr4DXaDanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPGb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26r4UJVWxJr1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_JF0_Jw1lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Wr v_Gr1UMIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Gr0_Xr1lIxAIcVC0I7IYx2IY 6xkF7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2js IE14v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr1j6F4UJbIYCTnIWIevJa73UjIF yTuYvjxUIrWFUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAOBF1jj5x10gAAsB From: Roberto Sassu Introduce digest_cache_populate() to populate the digest cache from a digest list. It opens the file and then schedules a work to read the content (with new file type READING_DIGEST_LIST). Scheduling a work solves the problem of kernel_read_file() returning -EINTR. Once the work is done, this function calls digest_cache_strip_modsig() to strip a module-style appended signature, if present, and finally calls digest_cache_parse_digest_list() to parse the data. The latter function, which at the moment does nothing, will be completed with calls to parsing functions selected from the digest list file name. It expects digest lists file names to be in the format: [-]- - is an optional prefix to impose in which order digest lists in a directory should be parsed. Failing to populate a digest cache causes it to be marked as invalid and to not be returned by digest_cache_create(). Dig_owner however is kept, to avoid an excessive number of retries, which would probably not succeed either. Signed-off-by: Roberto Sassu --- include/linux/kernel_read_file.h | 1 + security/digest_cache/Makefile | 2 +- security/digest_cache/internal.h | 26 ++++++ security/digest_cache/main.c | 18 ++++ security/digest_cache/modsig.c | 66 ++++++++++++++ security/digest_cache/populate.c | 149 +++++++++++++++++++++++++++++++ 6 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 security/digest_cache/modsig.c create mode 100644 security/digest_cache/populate.c diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h index 90451e2e12bd..85f602e49e2f 100644 --- a/include/linux/kernel_read_file.h +++ b/include/linux/kernel_read_file.h @@ -14,6 +14,7 @@ id(KEXEC_INITRAMFS, kexec-initramfs) \ id(POLICY, security-policy) \ id(X509_CERTIFICATE, x509-certificate) \ + id(DIGEST_LIST, digest-list) \ id(MAX_ID, ) #define __fid_enumify(ENUM, dummy) READING_ ## ENUM, diff --git a/security/digest_cache/Makefile b/security/digest_cache/Makefile index 7e00c53d8f55..c1452437d02f 100644 --- a/security/digest_cache/Makefile +++ b/security/digest_cache/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_SECURITY_DIGEST_CACHE) += digest_cache.o -digest_cache-y := main.o secfs.o htable.o +digest_cache-y := main.o secfs.o htable.o populate.o modsig.o diff --git a/security/digest_cache/internal.h b/security/digest_cache/internal.h index f6ffeaa25288..cc6752a8683e 100644 --- a/security/digest_cache/internal.h +++ b/security/digest_cache/internal.h @@ -15,6 +15,24 @@ /* Digest cache bits in flags. */ #define INIT_IN_PROGRESS 0 /* Digest cache being initialized. */ +#define INVALID 1 /* Digest cache marked as invalid. */ + +/** + * struct read_work - Structure to schedule reading a digest list + * @work: Work structure + * @file: File descriptor of the digest list to read + * @data: Digest list data (updated) + * @ret: Return value from kernel_read_file() (updated) + * + * This structure contains the necessary information to schedule reading a + * digest list. + */ +struct read_work { + struct work_struct work; + struct file *file; + void *data; + int ret; +}; /** * struct digest_cache_entry - Entry of a digest cache hash table @@ -127,4 +145,12 @@ int digest_cache_htable_lookup(struct dentry *dentry, enum hash_algo algo); void digest_cache_htable_free(struct digest_cache *digest_cache); +/* populate.c */ +int digest_cache_populate(struct digest_cache *digest_cache, + struct path *digest_list_path, char *path_str, + char *filename); + +/* modsig.c */ +size_t digest_cache_strip_modsig(__u8 *data, size_t data_len); + #endif /* _DIGEST_CACHE_INTERNAL_H */ diff --git a/security/digest_cache/main.c b/security/digest_cache/main.c index 0b201af6432c..6e9ed1b5040a 100644 --- a/security/digest_cache/main.c +++ b/security/digest_cache/main.c @@ -172,6 +172,17 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, set_bit(INIT_IN_PROGRESS, &dig_sec->dig_owner->flags); mutex_unlock(&dig_sec->dig_owner_mutex); + if (S_ISREG(inode->i_mode)) { + ret = digest_cache_populate(digest_cache, digest_list_path, + path_str, filename); + if (ret < 0) { + pr_debug("Failed to populate digest cache %s ret: %d (keep digest cache)\n", + digest_cache->path_str, ret); + /* Prevent usage of partially-populated digest cache. */ + set_bit(INVALID, &digest_cache->flags); + } + } + /* Creation complete, notify the other lock contenders. */ clear_and_wake_up_bit(INIT_IN_PROGRESS, &dig_sec->dig_owner->flags); exists: @@ -179,6 +190,13 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, /* Wait until creation complete. */ wait_on_bit(&dig_sec->dig_owner->flags, INIT_IN_PROGRESS, TASK_UNINTERRUPTIBLE); + + 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; + } out: if (digest_list_path == &file_path) path_put(&file_path); diff --git a/security/digest_cache/modsig.c b/security/digest_cache/modsig.c new file mode 100644 index 000000000000..3bdda00d8bb2 --- /dev/null +++ b/security/digest_cache/modsig.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Copyright (C) 2019 IBM Corporation + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Strip module-style appended signatures. + */ + +#define pr_fmt(fmt) "DIGEST CACHE: "fmt +#include +#include + +#include "internal.h" + +/** + * digest_cache_strip_modsig - Strip module-style appended sig from digest list + * @data: Data to parse + * @data_len: Length of @data + * + * This function strips the module-style appended signature from a digest list, + * if present. + * + * Return: Size of stripped data on success, original size otherwise. + */ +size_t digest_cache_strip_modsig(__u8 *data, size_t data_len) +{ + const size_t marker_len = strlen(MODULE_SIG_STRING); + const struct module_signature *sig; + size_t parsed_data_len = data_len; + size_t sig_len; + const void *p; + + /* From ima_modsig.c */ + if (data_len <= marker_len + sizeof(*sig)) + return data_len; + + p = data + parsed_data_len - marker_len; + if (memcmp(p, MODULE_SIG_STRING, marker_len)) + return data_len; + + parsed_data_len -= marker_len; + sig = (const struct module_signature *)(p - sizeof(*sig)); + + /* From module_signature.c */ + if (be32_to_cpu(sig->sig_len) >= parsed_data_len - sizeof(*sig)) + return data_len; + + /* Unlike for module signatures, accept all signature types. */ + if (sig->algo != 0 || + sig->hash != 0 || + sig->signer_len != 0 || + sig->key_id_len != 0 || + sig->__pad[0] != 0 || + sig->__pad[1] != 0 || + sig->__pad[2] != 0) { + pr_debug("Signature info has unexpected non-zero params\n"); + return data_len; + } + + sig_len = be32_to_cpu(sig->sig_len); + parsed_data_len -= sig_len + sizeof(*sig); + return parsed_data_len; +} diff --git a/security/digest_cache/populate.c b/security/digest_cache/populate.c new file mode 100644 index 000000000000..415e638f587b --- /dev/null +++ b/security/digest_cache/populate.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the code to populate a digest cache. + */ + +#define pr_fmt(fmt) "DIGEST CACHE: "fmt +#include +#include + +#include "internal.h" + +/** + * digest_cache_parse_digest_list - Parse a digest list + * @digest_cache: Digest cache + * @path_str: Path string of the digest list + * @filename: Digest list file name (can be an empty string) + * @data: Data to parse + * @data_len: Length of @data + * + * This function selects a parser for a digest list depending on its file name, + * and calls the appropriate parsing function. It expects the file name to be + * in the format: [-]-. is + * optional. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +static int digest_cache_parse_digest_list(struct digest_cache *digest_cache, + char *path_str, char *filename, + void *data, size_t data_len) +{ + char *format, *next_sep; + int ret = -EINVAL; + + if (!filename[0]) { + filename = strrchr(path_str, '/'); + if (!filename) + return ret; + + filename++; + } + + format = filename; + + /* + * Since we expect that all files start with a digest list format, this + * check is reliable to detect . + */ + if (filename[0] >= '0' && filename[0] <= '9') { + format = strchr(filename, '-'); + if (!format) + return ret; + + format++; + } + + next_sep = strchr(format, '-'); + if (!next_sep) + return ret; + + pr_debug("Parsing %s%s%s, format: %.*s, size: %ld\n", path_str, + filename[0] ? "/" : "", filename, (int)(next_sep - format), + format, data_len); + + return ret; +} + +/** + * digest_cache_read_digest_list - Read a digest list + * @work: Work structure + * + * This function is invoked by schedule_work() to read a digest list. + * + * It does not return a value, but stores the result in the passed structure. + */ +static void digest_cache_read_digest_list(struct work_struct *work) +{ + struct read_work *w = container_of(work, struct read_work, work); + + w->ret = kernel_read_file(w->file, 0, &w->data, INT_MAX, NULL, + READING_DIGEST_LIST); +} + +/** + * digest_cache_populate - Populate a digest cache from a digest list + * @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) + * + * This function opens the digest list for reading it. Then, it schedules a + * work to read the digest list and, once the work is done, it calls + * digest_cache_strip_modsig() to strip a module-style appended signature and + * digest_cache_parse_digest_list() for extracting and adding digests to the + * digest cache. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +int digest_cache_populate(struct digest_cache *digest_cache, + struct path *digest_list_path, char *path_str, + char *filename) +{ + struct file *file; + void *data; + size_t data_len; + struct read_work w; + int ret; + + file = dentry_open(digest_list_path, O_RDONLY, &init_cred); + if (IS_ERR(file)) { + pr_debug("Unable to open digest list %s%s%s, ret: %ld\n", + path_str, filename[0] ? "/" : "", filename, + PTR_ERR(file)); + return PTR_ERR(file); + } + + w.data = NULL; + w.file = file; + INIT_WORK_ONSTACK(&w.work, digest_cache_read_digest_list); + + schedule_work(&w.work); + flush_work(&w.work); + destroy_work_on_stack(&w.work); + fput(file); + + ret = w.ret; + data = w.data; + + if (ret < 0) { + pr_debug("Unable to read digest list %s%s%s, ret: %d\n", + path_str, filename[0] ? "/" : "", filename, ret); + return ret; + } + + data_len = digest_cache_strip_modsig(data, ret); + + /* Digest list parsers initialize the hash table and add the digests. */ + ret = digest_cache_parse_digest_list(digest_cache, path_str, filename, + data, data_len); + if (ret < 0) + pr_debug("Error parsing digest list %s%s%s, ret: %d\n", + path_str, filename[0] ? "/" : "", filename, ret); + + vfree(data); + return ret; +} From patchwork Mon Apr 15 14:24:29 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 789075 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 0D556757EA; Mon, 15 Apr 2024 14:27:01 +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=1713191224; cv=none; b=n6LVE9+6ecRZ5fNjPtLzaRZcw5mBI/zNEJZhW90QfZmCqBgaJOM2NpqUOAX3HcakcgHXETF+sAB2y5R4DpWmojIvhGzTNDGxUMK0VMnCgKBLbOU0L/gjigVbtCFYwbiHQtrRuEb1e7P7FAA4ni5XexRRqDfNcgwonExMu5nKfhU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713191224; c=relaxed/simple; bh=1w6+ARBuKc1e3AGxOhMbRPheBxo51IgHoyMaEowjJCw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=gyQp3HzI40XazwxIKsH3PV3/QQP1HD/4HqjKb2+ydc6O4dCQjSi96Db/ktbo2aokyx5htqFBzqzw0ej/Wi+fCwYD9AhetR38G9gmSDCQYxu81FieMvuq02NFPW/BDR4NY5MzId2CCQExiSHMpODqpbeBUjJfeknul+yvM6nycLY= 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.51]) by frasgout12.his.huawei.com (SkyGuard) with ESMTP id 4VJ88t2X8Mz9xGYB; Mon, 15 Apr 2024 22:06:06 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.27]) by mail.maildlp.com (Postfix) with ESMTP id D47AD1407FC; Mon, 15 Apr 2024 22:26:56 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP2 (Coremail) with SMTP id GxC2BwBnoSWrOB1myEJGBg--.9473S9; Mon, 15 Apr 2024 15:26:56 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, akpm@linux-foundation.org, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, mic@digikod.net Cc: linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, linux-integrity@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 v4 07/14] digest_cache: Parse rpm digest lists Date: Mon, 15 Apr 2024 16:24:29 +0200 Message-Id: <20240415142436.2545003-8-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240415142436.2545003-1-roberto.sassu@huaweicloud.com> References: <20240415142436.2545003-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: GxC2BwBnoSWrOB1myEJGBg--.9473S9 X-Coremail-Antispam: 1UD129KBjvJXoW3GryrtF43AF18Xry5XFWkZwb_yoWfCF47pa sxGr17trs5XF1xAw4xAF17tr1xt34DtFsrXrW8urnayrZFyr1UA3W8AryIvry5JrWDZFy7 Gr4YgF17Xr4DJaUanT9S1TB71UUUUU7qnTZGkaVYY2UrUUUUjbIjqfuFe4nvWSU5nxnvy2 9KBjDU0xBIdaVrnRJUUUPmb4IE77IF4wAFF20E14v26rWj6s0DM7CY07I20VC2zVCF04k2 6cxKx2IYs7xG6rWj6s0DM7CIcVAFz4kK6r1j6r18M28IrcIa0xkI8VA2jI8067AKxVWUAV Cq3wA2048vs2IY020Ec7CjxVAFwI0_Xr0E3s1l8cAvFVAK0II2c7xJM28CjxkF64kEwVA0 rcxSw2x7M28EF7xvwVC0I7IYx2IY67AKxVW8JVW5JwA2z4x0Y4vE2Ix0cI8IcVCY1x0267 AKxVW8Jr0_Cr1UM28EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E 14v26F4UJVW0owAS0I0E0xvYzxvE52x082IY62kv0487Mc02F40EFcxC0VAKzVAqx4xG6I 80ewAv7VC0I7IYx2IY67AKxVWUAVWUtwAv7VC2z280aVAFwI0_Jr0_Gr1lOx8S6xCaFVCj c4AY6r1j6r4UM4x0Y48IcxkI7VAKI48JM4IIrI8v6xkF7I0E8cxan2IY04v7MxkF7I0En4 kS14v26r4a6rW5MxAIw28IcxkI7VAKI48JMxC20s026xCaFVCjc4AY6r1j6r4UMI8I3I0E 5I8CrVAFwI0_Jr0_Jr4lx2IqxVCjr7xvwVAFwI0_JrI_JrWlx4CE17CEb7AF67AKxVWrXV W8Jr1lIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW8JVW5JwCI42IY6xIIjxv20xvE c7CjxVAFwI0_Gr1j6F4UJwCI42IY6xAIw20EY4v20xvaj40_Jr0_JF4lIxAIcVC2z280aV AFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVWxJr0_GcJvcSsGvfC2KfnxnUUI43ZE Xa7IU04rW7UUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAOBF1jj5x11gAAsF From: Roberto Sassu Implement a simple parser of RPM headers, that extracts the digest and the algorithm of the packaged files from the RPMTAG_FILEDIGESTS and RPMTAG_FILEDIGESTALGO section, and add them to the digest cache. The rpm digest list parser has been 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_rpm.c Here is the result of the analysis: [eva:summary] ====== ANALYSIS SUMMARY ====== --------------------------------------------------------------------------- 7 functions analyzed (out of 7): 100% coverage. In these functions, 228 statements reached (out of 246): 92% coverage. --------------------------------------------------------------------------- No errors or warnings raised during the analysis. --------------------------------------------------------------------------- 0 alarms generated by the analysis. --------------------------------------------------------------------------- Evaluation of the logical properties reached by the analysis: Assertions 6 valid 0 unknown 0 invalid 6 total Preconditions 29 valid 0 unknown 0 invalid 29 total 100% of the logical properties reached have been proven. --------------------------------------------------------------------------- Signed-off-by: Roberto Sassu --- security/digest_cache/Makefile | 1 + security/digest_cache/parsers/parsers.h | 2 + security/digest_cache/parsers/rpm.c | 223 ++++++++++++++++++++++++ security/digest_cache/populate.c | 2 + 4 files changed, 228 insertions(+) create mode 100644 security/digest_cache/parsers/rpm.c diff --git a/security/digest_cache/Makefile b/security/digest_cache/Makefile index a383b6ef2550..eca4076497e6 100644 --- a/security/digest_cache/Makefile +++ b/security/digest_cache/Makefile @@ -7,3 +7,4 @@ obj-$(CONFIG_SECURITY_DIGEST_CACHE) += digest_cache.o digest_cache-y := main.o secfs.o htable.o populate.o modsig.o digest_cache-y += parsers/tlv.o +digest_cache-y += parsers/rpm.o diff --git a/security/digest_cache/parsers/parsers.h b/security/digest_cache/parsers/parsers.h index 1bbae426ab9f..3f00d29ed92a 100644 --- a/security/digest_cache/parsers/parsers.h +++ b/security/digest_cache/parsers/parsers.h @@ -11,3 +11,5 @@ int digest_list_parse_tlv(struct digest_cache *digest_cache, const u8 *data, size_t data_len); +int digest_list_parse_rpm(struct digest_cache *digest_cache, const u8 *data, + size_t data_len); diff --git a/security/digest_cache/parsers/rpm.c b/security/digest_cache/parsers/rpm.c new file mode 100644 index 000000000000..6c7fe9c9121c --- /dev/null +++ b/security/digest_cache/parsers/rpm.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Parse an rpm digest list (RPM package header). + */ + +#define pr_fmt(fmt) "RPM DIGEST LIST: "fmt +#include + +#include "parsers.h" + +#define RPMTAG_FILEDIGESTS 1035 +#define RPMTAG_FILEDIGESTALGO 5011 + +#define RPM_INT32_TYPE 4 +#define RPM_STRING_ARRAY_TYPE 8 + +struct rpm_hdr { + u32 magic; + u32 reserved; + u32 tags; + u32 datasize; +} __packed; + +struct rpm_entryinfo { + s32 tag; + u32 type; + s32 offset; + u32 count; +} __packed; + +enum pgp_algos { + DIGEST_ALGO_MD5 = 1, + DIGEST_ALGO_SHA1 = 2, + DIGEST_ALGO_RMD160 = 3, + /* 4, 5, 6, and 7 are reserved. */ + DIGEST_ALGO_SHA256 = 8, + DIGEST_ALGO_SHA384 = 9, + DIGEST_ALGO_SHA512 = 10, + DIGEST_ALGO_SHA224 = 11, +}; + +static const enum hash_algo pgp_algo_mapping[DIGEST_ALGO_SHA224 + 1] = { + [DIGEST_ALGO_MD5] = HASH_ALGO_MD5, + [DIGEST_ALGO_SHA1] = HASH_ALGO_SHA1, + [DIGEST_ALGO_RMD160] = HASH_ALGO_RIPE_MD_160, + [4] = HASH_ALGO__LAST, + [5] = HASH_ALGO__LAST, + [6] = HASH_ALGO__LAST, + [7] = HASH_ALGO__LAST, + [DIGEST_ALGO_SHA256] = HASH_ALGO_SHA256, + [DIGEST_ALGO_SHA384] = HASH_ALGO_SHA384, + [DIGEST_ALGO_SHA512] = HASH_ALGO_SHA512, + [DIGEST_ALGO_SHA224] = HASH_ALGO_SHA224, +}; + +/** + * digest_list_parse_rpm - Parse an rpm digest list + * @digest_cache: Digest cache + * @data: Data to parse + * @data_len: Length of @data + * + * This function parses an rpm digest list. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +int digest_list_parse_rpm(struct digest_cache *digest_cache, const u8 *data, + size_t data_len) +{ + const unsigned char rpm_header_magic[8] = { + 0x8e, 0xad, 0xe8, 0x01, 0x00, 0x00, 0x00, 0x00 + }; + const struct rpm_hdr *hdr; + const struct rpm_entryinfo *entry; + u32 tags, max_tags, datasize; + u32 digests_count, max_digests_count; + u32 digests_offset, algo_offset; + u32 digest_len, pkg_pgp_algo, i; + bool algo_offset_set = false, digests_offset_set = false; + enum hash_algo pkg_kernel_algo = HASH_ALGO_MD5; + u8 rpm_digest[SHA512_DIGEST_SIZE]; + int ret; + + if (data_len < sizeof(*hdr)) { + pr_debug("Not enough data for RPM header, current %ld, expected: %ld\n", + data_len, sizeof(*hdr)); + return -EINVAL; + } + + if (memcmp(data, rpm_header_magic, sizeof(rpm_header_magic))) { + pr_debug("RPM header magic mismatch\n"); + return -EINVAL; + } + + hdr = (const struct rpm_hdr *)data; + data += sizeof(*hdr); + data_len -= sizeof(*hdr); + + tags = __be32_to_cpu(hdr->tags); + max_tags = data_len / sizeof(*entry); + + /* Finite termination on tags loop. */ + if (tags > max_tags) + return -EINVAL; + + datasize = __be32_to_cpu(hdr->datasize); + if (datasize != data_len - tags * sizeof(*entry)) + return -EINVAL; + + pr_debug("Scanning %d RPM header sections\n", tags); + for (i = 0; i < tags; i++) { + if (data_len < sizeof(*entry)) + return -EINVAL; + + entry = (const struct rpm_entryinfo *)data; + data += sizeof(*entry); + data_len -= sizeof(*entry); + + switch (__be32_to_cpu(entry->tag)) { + case RPMTAG_FILEDIGESTS: + if (__be32_to_cpu(entry->type) != RPM_STRING_ARRAY_TYPE) + return -EINVAL; + + digests_offset = __be32_to_cpu(entry->offset); + digests_count = __be32_to_cpu(entry->count); + digests_offset_set = true; + + pr_debug("Found RPMTAG_FILEDIGESTS at offset %u, count: %u\n", + digests_offset, digests_count); + break; + case RPMTAG_FILEDIGESTALGO: + if (__be32_to_cpu(entry->type) != RPM_INT32_TYPE) + return -EINVAL; + + algo_offset = __be32_to_cpu(entry->offset); + algo_offset_set = true; + + pr_debug("Found RPMTAG_FILEDIGESTALGO at offset %u\n", + algo_offset); + break; + default: + break; + } + } + + if (!digests_offset_set) + return 0; + + if (algo_offset_set) { + if (algo_offset >= data_len) + return -EINVAL; + + if (data_len - algo_offset < sizeof(u32)) + return -EINVAL; + + pkg_pgp_algo = *(u32 *)&data[algo_offset]; + pkg_pgp_algo = __be32_to_cpu(pkg_pgp_algo); + if (pkg_pgp_algo > DIGEST_ALGO_SHA224) { + pr_debug("Unknown PGP algo %d\n", pkg_pgp_algo); + return -EINVAL; + } + + pkg_kernel_algo = pgp_algo_mapping[pkg_pgp_algo]; + if (pkg_kernel_algo >= HASH_ALGO__LAST) { + pr_debug("Unknown mapping for PGP algo %d\n", + pkg_pgp_algo); + return -EINVAL; + } + + pr_debug("Found mapping for PGP algo %d: %s\n", pkg_pgp_algo, + hash_algo_name[pkg_kernel_algo]); + } + + digest_len = hash_digest_size[pkg_kernel_algo]; + + if (digests_offset > data_len) + return -EINVAL; + + /* Worst case, every digest is a \0. */ + max_digests_count = data_len - digests_offset; + + /* Finite termination on digests_count loop. */ + if (digests_count > max_digests_count) + return -EINVAL; + + ret = digest_cache_htable_init(digest_cache, digests_count, + pkg_kernel_algo); + if (ret < 0) + return ret; + + for (i = 0; i < digests_count; i++) { + if (digests_offset == data_len) + return -EINVAL; + + if (!data[digests_offset]) { + digests_offset++; + continue; + } + + if (data_len - digests_offset < digest_len * 2 + 1) + return -EINVAL; + + ret = hex2bin(rpm_digest, (const char *)&data[digests_offset], + digest_len); + if (ret < 0) { + pr_debug("Invalid hex format for digest %s\n", + &data[digests_offset]); + return -EINVAL; + } + + ret = digest_cache_htable_add(digest_cache, rpm_digest, + pkg_kernel_algo); + if (ret < 0) + return ret; + + digests_offset += digest_len * 2 + 1; + } + + return ret; +} diff --git a/security/digest_cache/populate.c b/security/digest_cache/populate.c index 13645ec4bb2b..1770c8385017 100644 --- a/security/digest_cache/populate.c +++ b/security/digest_cache/populate.c @@ -68,6 +68,8 @@ static int digest_cache_parse_digest_list(struct digest_cache *digest_cache, if (!strncmp(format, "tlv-", 4)) ret = digest_list_parse_tlv(digest_cache, data, data_len); + else if (!strncmp(format, "rpm-", 4)) + ret = digest_list_parse_rpm(digest_cache, data, data_len); return ret; } From patchwork Mon Apr 15 14:24:32 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 789074 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 4E14E757E1; Mon, 15 Apr 2024 14:28:53 +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=1713191335; cv=none; b=QHG6dh23IOWE75amZxQmSD2nY5Xu7oCOC9cCGA/SxIqhNxaFzYS5J4RPRie1AKo3zic0zDLvd0UQhzz7i5eLpAwSoKHGONZ5eTHFtJGX1M4Wm43k17uUiQ+OiJ9VC2IUgAgR6XreO5Ls70NbzEfVnD7byFQNYNBCyUxXHfzHj1U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713191335; c=relaxed/simple; bh=IxU1qUWz87TI99CG0fmKy+D2K0yWgcT+xL9xt+qwOkw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=XeZGQ9c9XXej9lSVtxWrot4rzzGxT+yWASe+UlPrepLR5i+MpPaYFiw934n3TmB/b8MNgofYEoIdapsHZEsib2p8by+CN8FESTDLRHLN6nHyR7Fmvwu8L+BYescgVrR1fpqWjMG5Rmkoc9BOtWJ0qOf3ZCBALaep79vG1Tc4QJc= 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 4VJ8J541BJz9xGgX; Mon, 15 Apr 2024 22:12:21 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.47]) by mail.maildlp.com (Postfix) with ESMTP id 880D61404A9; Mon, 15 Apr 2024 22:28:50 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwAHshqQOR1mrL1NBg--.21472S2; Mon, 15 Apr 2024 15:28:49 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, akpm@linux-foundation.org, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, mic@digikod.net Cc: linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, linux-integrity@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 v4 10/14] digest cache: Prefetch digest lists if requested Date: Mon, 15 Apr 2024 16:24:32 +0200 Message-Id: <20240415142436.2545003-11-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240415142436.2545003-1-roberto.sassu@huaweicloud.com> References: <20240415142436.2545003-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: LxC2BwAHshqQOR1mrL1NBg--.21472S2 X-Coremail-Antispam: 1UD129KBjvAXoWfJFykCF1DWF18Jr4kGF1rZwb_yoW8Wr4rAo Za9F4UAw48WFyUurs8uF17Aa1DW34Yg34xAr1kGFW5Z3WkAFyUG3ZrC3WDJFy5Xr18JFZ7 Zw1xJw45JrWUtr97n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUYa7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2ocxC64kIII0Yj41l84x0c7CEw4 AK67xGY2AK021l84ACjcxK6xIIjxv20xvE14v26r4j6ryUM28EF7xvwVC0I7IYx2IY6xkF 7I0E14v26r4UJVWxJr1l84ACjcxK6I8E87Iv67AKxVW8JVWxJwA2z4x0Y4vEx4A2jsIEc7 CjxVAFwI0_Cr1j6rxdM2AIxVAIcxkEcVAq07x20xvEncxIr21l5I8CrVACY4xI64kE6c02 F40Ex7xfMcIj6xIIjxv20xvE14v26r1Y6r17McIj6I8E87Iv67AKxVWUJVW8JwAm72CE4I kC6x0Yz7v_Jr0_Gr1lF7xvr2IYc2Ij64vIr41lFIxGxcIEc7CjxVA2Y2ka0xkIwI1lc7Cj xVAaw2AFwI0_GFv_Wryl42xK82IYc2Ij64vIr41l4I8I3I0E4IkC6x0Yz7v_Jr0_Gr1lx2 IqxVAqx4xG67AKxVWUJVWUGwC20s026x8GjcxK67AKxVWUGVWUWwC2zVAF1VAY17CE14v2 6rWY6r4UJwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r4j6ryUMIIF0xvE2Ix0cI 8IcVCY1x0267AKxVW8Jr0_Cr1UMIIF0xvE42xK8VAvwI8IcIk0rVW3JVWrJr1lIxAIcVC2 z280aVAFwI0_Gr0_Cr1lIxAIcVC2z280aVCY1x0267AKxVWxJr0_GcJvcSsGvfC2KfnxnU UI43ZEXa7IU0FdgtUUUUU== X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAQAOBF1jj5x13AADsM 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 digest_cache LSM is not exempted from this issue. Under the assumption that only the digest list is measured, and file measurements are omitted if their digest is found in that digest list, a PCR can be predictable only if all files belong to the same digest list. Otherwise, it will still be unpredictable, since files accessed in a non-deterministic order will cause digest lists to be measured in a non-deterministic order too. Overcome this issue, if prefetching is enabled, by searching a digest list file name in digest_list_dir_lookup_filename() among the entries of the linked list built by digest_cache_dir_create(). If the file name does not match, read the digest list to trigger its measurement. Otherwise, also create a digest cache and return that to the caller. Release the extra reference of the directory digest cache in digest_cache_new(), since it was only used for the search and it is not going to be returned. Prefetching needs to be explicitly enabled by setting the new security.dig_prefetch xattr to 1 in the directory containing the digest lists. The newly introduced function digest_cache_prefetch_requested() checks first if the DIR_PREFETCH bit is set in dig_owner, otherwise it reads the xattr. digest_cache_create() sets DIR_PREFETCH in dig_owner, if prefetching is enabled, before declaring the digest cache as initialized. Signed-off-by: Roberto Sassu --- include/uapi/linux/xattr.h | 3 + security/digest_cache/dir.c | 55 +++++++++++++++++- security/digest_cache/internal.h | 11 +++- security/digest_cache/main.c | 95 +++++++++++++++++++++++++++++++- security/digest_cache/populate.c | 8 ++- security/digest_cache/verif.c | 5 +- 6 files changed, 170 insertions(+), 7 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/digest_cache/dir.c b/security/digest_cache/dir.c index 7bfcdd5f7ef1..a7d203c15386 100644 --- a/security/digest_cache/dir.c +++ b/security/digest_cache/dir.c @@ -54,6 +54,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; @@ -127,6 +128,7 @@ int digest_cache_dir_create(struct digest_cache *digest_cache, * @digest_cache: Digest cache * @digest: Digest to search * @algo: Algorithm of the digest to search + * @filename: File name of the digest list to search * * This function iterates over the linked list created by * digest_cache_dir_create() and looks up the digest in the digest cache of @@ -149,7 +151,8 @@ digest_cache_dir_lookup_digest(struct dentry *dentry, if (!dir_entry->digest_cache) { cache = digest_cache_create(dentry, digest_list_path, digest_cache->path_str, - dir_entry->name); + dir_entry->name, false, + false); /* Ignore digest caches that cannot be instantiated. */ if (!cache) { mutex_unlock(&dir_entry->digest_cache_mutex); @@ -158,6 +161,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); @@ -171,6 +176,54 @@ digest_cache_dir_lookup_digest(struct dentry *dentry, return 0UL; } +/** + * digest_cache_dir_lookup_filename - Lookup a digest list + * @dentry: Dentry of the file whose digest list is looked up + * @digest_list_path: Path structure of the digest list directory + * @digest_cache: Digest cache + * @filename: File name of the digest list to search + * + * This function iterates over the linked list created by + * digest_cache_dir_create() 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 returns the digest cache pointer from + * digest_cache_create() to the caller. + * + * Return: A digest cache pointer if the digest list if found, NULL otherwise. + */ +struct digest_cache * +digest_cache_dir_lookup_filename(struct dentry *dentry, + struct path *digest_list_path, + struct digest_cache *digest_cache, + char *filename) +{ + struct digest_cache *cache; + struct dir_entry *dir_entry; + 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; + } + + cache = digest_cache_create(dentry, digest_list_path, + digest_cache->path_str, + dir_entry->name, false, + filename_found ? false : true); + + dir_entry->prefetched = true; + mutex_unlock(&dir_entry->digest_cache_mutex); + + if (filename_found) + return cache; + } + + return NULL; +} + /** * digest_cache_dir_free - Free the stored file list and put digest caches * @digest_cache: Digest cache diff --git a/security/digest_cache/internal.h b/security/digest_cache/internal.h index b7afca8e04da..c13b35f6b2c0 100644 --- a/security/digest_cache/internal.h +++ b/security/digest_cache/internal.h @@ -17,6 +17,7 @@ #define INIT_IN_PROGRESS 0 /* Digest cache being initialized. */ #define INVALID 1 /* Digest cache marked as invalid. */ #define IS_DIR 2 /* Digest cache created from dir. */ +#define DIR_PREFETCH 3 /* Prefetching requested for dir. */ /** * struct readdir_callback - Structure to store information for dir iteration @@ -37,6 +38,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 @@ -47,6 +49,7 @@ struct dir_entry { struct digest_cache *digest_cache; struct mutex digest_cache_mutex; unsigned int seq_num; + bool prefetched; char name[]; } __packed; @@ -205,7 +208,8 @@ digest_cache_from_file_sec(const struct file *file) /* main.c */ struct digest_cache *digest_cache_create(struct dentry *dentry, struct path *digest_list_path, - char *path_str, char *filename); + char *path_str, char *filename, + bool prefetch_req, bool prefetch); /* htable.c */ int digest_cache_htable_init(struct digest_cache *digest_cache, u64 num_digests, @@ -236,6 +240,11 @@ digest_cache_dir_lookup_digest(struct dentry *dentry, struct path *digest_list_path, struct digest_cache *digest_cache, u8 *digest, enum hash_algo algo); +struct digest_cache * +digest_cache_dir_lookup_filename(struct dentry *dentry, + struct path *digest_list_path, + struct digest_cache *digest_cache, + char *filename); void digest_cache_dir_free(struct digest_cache *digest_cache); #endif /* _DIGEST_CACHE_INTERNAL_H */ diff --git a/security/digest_cache/main.c b/security/digest_cache/main.c index 15f1486610a3..a5616fd07c1d 100644 --- a/security/digest_cache/main.c +++ b/security/digest_cache/main.c @@ -83,6 +83,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, the digest list inode * from which the digest cache will be created or retrieved (if it already @@ -109,7 +111,8 @@ static void digest_cache_free(struct digest_cache *digest_cache) */ struct digest_cache *digest_cache_create(struct dentry *dentry, struct path *digest_list_path, - char *path_str, char *filename) + char *path_str, char *filename, + bool prefetch_req, bool prefetch) { struct path file_path; struct digest_cache *digest_cache = NULL; @@ -148,6 +151,16 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, dentry->d_name.name); goto out; } + + if (prefetch) { + /* Fine to fail, we are just prefetching. */ + ret = digest_cache_populate(NULL, digest_list_path, + path_str, filename); + pr_debug("Digest list %s/%s %s prefetched\n", + path_str, filename, + !ret ? "has been" : "cannot be"); + goto out; + } } dig_sec = digest_cache_get_security(inode); @@ -176,6 +189,11 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, /* Make the other lock contenders wait until creation complete. */ set_bit(INIT_IN_PROGRESS, &dig_sec->dig_owner->flags); + + /* Set DIR_PREFETCH if prefetching was requested. */ + if (prefetch_req) + set_bit(DIR_PREFETCH, &digest_cache->flags); + mutex_unlock(&dig_sec->dig_owner_mutex); if (S_ISREG(inode->i_mode)) { @@ -220,6 +238,52 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, 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 new security.dig_prefetch xattr. + * + * Return: True if prefetching is requested, false otherwise. + */ +static 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 @@ -230,13 +294,19 @@ struct digest_cache *digest_cache_create(struct dentry *dentry, * with that file name. If security.digest_list is not found, this function * requests the creation of a digest cache on the parent directory. * + * On prefetching, if the default path is a directory and if + * security.digest_list is found, this function first retrieves the directory + * digest cache, and then calls digest_cache_dir_lookup_filename() to retrieve + * the desired digest cache in that directory. + * * Return: A new digest cache on success, NULL on error. */ static struct digest_cache *digest_cache_new(struct dentry *dentry) { char filename[NAME_MAX + 1] = { 0 }; - struct digest_cache *digest_cache = NULL; + struct digest_cache *digest_cache = NULL, *found; struct path default_path; + bool prefetch_req = false; int ret; ret = kern_path(default_path_str, 0, &default_path); @@ -273,9 +343,28 @@ static struct digest_cache *digest_cache_new(struct dentry *dentry) pr_debug("Found %s xattr in %s, default path: %s, digest list: %s\n", XATTR_NAME_DIGEST_LIST, dentry->d_name.name, default_path_str, filename); + + if (filename[0]) + prefetch_req = digest_cache_prefetch_requested(&default_path, + default_path_str); create: + /* On prefetching, retrieve the directory digest cache. */ digest_cache = digest_cache_create(dentry, &default_path, - default_path_str, filename); + default_path_str, + !prefetch_req ? filename : "", + prefetch_req, false); + if (!digest_cache) + goto out; + + if (prefetch_req) { + /* Find the digest cache with a matching file name. */ + found = digest_cache_dir_lookup_filename(dentry, &default_path, + digest_cache, + filename); + /* Release ref. to the directory digest cache. */ + digest_cache_put(digest_cache); + digest_cache = found; + } out: path_put(&default_path); return digest_cache; diff --git a/security/digest_cache/populate.c b/security/digest_cache/populate.c index 9c2fc2295310..17e7b011c367 100644 --- a/security/digest_cache/populate.c +++ b/security/digest_cache/populate.c @@ -143,6 +143,12 @@ int digest_cache_populate(struct digest_cache *digest_cache, return ret; } + /* The caller wants just to read digest lists. */ + if (!digest_cache) { + ret = 0; + goto out_vfree; + } + data_len = digest_cache_strip_modsig(data, ret); /* Digest list parsers initialize the hash table and add the digests. */ @@ -151,7 +157,7 @@ int digest_cache_populate(struct digest_cache *digest_cache, if (ret < 0) pr_debug("Error parsing digest list %s%s%s, ret: %d\n", path_str, filename[0] ? "/" : "", filename, ret); - +out_vfree: vfree(data); return ret; } diff --git a/security/digest_cache/verif.c b/security/digest_cache/verif.c index 04023240d3b4..c42ae93261e2 100644 --- a/security/digest_cache/verif.c +++ b/security/digest_cache/verif.c @@ -33,7 +33,7 @@ static void free_verif(struct digest_cache_verif *verif) * This function lets a verifier supply verification data about a digest list * being read to populate the digest cache. * - * Return: Zero on success, -ENOMEM if out of memory. + * Return: Zero on success, -ENOMEM if out of memory, -ENOENT on prefetching. */ int digest_cache_verif_set(struct file *file, const char *verif_id, void *data, size_t size) @@ -41,6 +41,9 @@ int digest_cache_verif_set(struct file *file, const char *verif_id, void *data, struct digest_cache *digest_cache = digest_cache_from_file_sec(file); struct digest_cache_verif *new_verif; + if (!digest_cache) + return -ENOENT; + /* * All allocations must be atomic (non-sleepable) since kprobe does not * allow otherwise (kprobe is needed for testing). From patchwork Mon Apr 15 14:24:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 789073 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 8C5817581F; Mon, 15 Apr 2024 14:29:30 +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=1713191372; cv=none; b=RuHtcp/Eu58Gujt9rVT7Y/eVskVBuOLXd5dK9Zomz8XdzxVKbAlxPUiUvp02Dr6fFfGy5pQ+e89WX2xSKAVMXXJaQsUMyOC3A9gcRghQVuUzA68NahFsHBuRP6zcvEaggNsak5Bvhk7AV4mbiB3M0gnZfPF1erZYOo2VHk6hGRU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713191372; c=relaxed/simple; bh=eZVYIloM9fbMgIPKvwe5elCHLztDhHM/7Cp7llmzsw8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Wb3zbKM+gJSnYhaQLimQOWPdl9RuuhuUQZLZdg0i2bBOOoxKS+p+w2tp/N5Yl2MYjwGkJ354CYu64NScZHxu1RVo2jU9QvP+UzV+3pAdyeD/TdY34qCzQ7JoCv38xe9HKrex8fit/NTOhNUWzIFyAiANa6K0TG3ngjOy45FQpKw= 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 4VJ8Ck4rrHz9v7PM; Mon, 15 Apr 2024 22:08:34 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.47]) by mail.maildlp.com (Postfix) with ESMTP id 9E0DE140801; Mon, 15 Apr 2024 22:29:22 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwAHshqQOR1mrL1NBg--.21472S4; Mon, 15 Apr 2024 15:29:21 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, akpm@linux-foundation.org, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, mic@digikod.net Cc: linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, linux-integrity@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 v4 12/14] digest_cache: Notify digest cache events Date: Mon, 15 Apr 2024 16:24:34 +0200 Message-Id: <20240415142436.2545003-13-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240415142436.2545003-1-roberto.sassu@huaweicloud.com> References: <20240415142436.2545003-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: LxC2BwAHshqQOR1mrL1NBg--.21472S4 X-Coremail-Antispam: 1UD129KBjvAXoW3tw47KFyfCFWxXr47uF4xtFb_yoW8Wry7Go ZYyF47uw48WFy5uFWkCFy7AayUW3ySgw4xAr1kGrZ8ZF18t34UJ3Z7GF1DJFy3Gr18GrZ7 A34kX3yUJFW8Jr97n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOe7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_Jr yl82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vEj48v e4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxVAFwI 0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x0267AK xVWxJr0_GcWle2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrVC2j2 WlYx0E2Ix0cI8IcVAFwI0_Jrv_JF1lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE7xkE bVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262kKe7 AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s026c02 F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Wrv_Gr 1UMIIYrxkI7VAKI48JMIIF0xvE2Ix0cI8IcVAFwI0_Gr0_Xr1lIxAIcVC0I7IYx2IY6xkF 7I0E14v26r4UJVWxJr1lIxAIcVCF04k26cxKx2IYs7xG6r1j6r1xMIIF0xvEx4A2jsIE14 v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Cr1j6rxdYxBIdaVFxhVjvjDU0xZFpf9x 07j4MKtUUUUU= X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAOBF1jj5h0wAAAsV From: Roberto Sassu Introduce digest_cache_register_notifier() and digest_cache_unregister_notifier() to let the subscribers know about events occurring on digest caches. Subscribers should provide a callback, to be invoked for every notification. Introduce a notification list for each digest cache, and populate it with inodes passed to digest_cache_get(), when dig_user of that inode is still NULL so that there are no duplicates. Then, on setting the RESET bit, emit a DIGEST_CACHE_RESET notification, passing the affected digest cache and inodes, so that subscribers can eventually invalidate a cached security decision on that inode. On a file digest cache reset, emit a DIGEST_CACHE_RESET notification also for the parent directory digest cache and the inodes using it, since requestors might have looked up digests through that digest cache. Those requestors will see changes by performing another lookup. On setting the RESET_USER bit, emit a notification only for the inode signaled by the LSM hook, since only the link between that inode and its digest cache is changing (due to changing the security.digest_list xattr). Finally, free the notification list with digest_cache_notify_inodes_free(), when the digest cache is freed. Signed-off-by: Roberto Sassu --- include/linux/digest_cache.h | 34 ++++++++ security/digest_cache/Makefile | 2 +- security/digest_cache/internal.h | 26 ++++++ security/digest_cache/main.c | 12 +++ security/digest_cache/notifier.c | 135 +++++++++++++++++++++++++++++++ security/digest_cache/reset.c | 42 +++++++++- 6 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 security/digest_cache/notifier.c diff --git a/include/linux/digest_cache.h b/include/linux/digest_cache.h index 9db8128513ca..950f3a58a861 100644 --- a/include/linux/digest_cache.h +++ b/include/linux/digest_cache.h @@ -15,6 +15,28 @@ struct digest_cache; +/** + * enum digest_cache_event - Events occurring on a digest cache + * + * This enum lists all the events occurring on a digest cache, to be notified + * outside the digest_cache LSM. + */ +enum digest_cache_event { + DIGEST_CACHE_RESET, +}; + +/** + * struct digest_cache_event_data - Information on digest cache events + * @digest_cache: Digest cache + * @inode: Inode for which the digest cache was requested + * + * This structure holds information about events occurring on a digest cache. + */ +struct digest_cache_event_data { + struct digest_cache *digest_cache; + struct inode *inode; +}; + /** * typedef digest_cache_found_t - Digest cache reference as numeric value * @@ -48,6 +70,8 @@ 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); +int digest_cache_register_notifier(struct notifier_block *nb); +int digest_cache_unregister_notifier(struct notifier_block *nb); #else static inline struct digest_cache *digest_cache_get(struct dentry *dentry) @@ -79,5 +103,15 @@ static inline void *digest_cache_verif_get(struct digest_cache *digest_cache, return NULL; } +static inline int digest_cache_register_notifier(struct notifier_block *nb) +{ + return -EOPNOTSUPP; +} + +static inline int digest_cache_unregister_notifier(struct notifier_block *nb) +{ + return -EOPNOTSUPP; +} + #endif /* CONFIG_SECURITY_DIGEST_CACHE */ #endif /* _LINUX_DIGEST_CACHE_H */ diff --git a/security/digest_cache/Makefile b/security/digest_cache/Makefile index 3d5e600a2c45..7577b099c170 100644 --- a/security/digest_cache/Makefile +++ b/security/digest_cache/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_SECURITY_DIGEST_CACHE) += digest_cache.o digest_cache-y := main.o secfs.o htable.o populate.o modsig.o verif.o dir.o \ - reset.o + reset.o notifier.o digest_cache-y += parsers/tlv.o digest_cache-y += parsers/rpm.o diff --git a/security/digest_cache/internal.h b/security/digest_cache/internal.h index c816929c4743..d930132cc963 100644 --- a/security/digest_cache/internal.h +++ b/security/digest_cache/internal.h @@ -21,6 +21,19 @@ #define RESET 4 /* Digest cache to be recreated. */ #define RESET_USER 5 /* Dig_user pointer to be released. */ +/** + * struct notify_inode - Structure with inode for which notification is emitted + * @list: Linked list + * @inode: Inode for which a notification is emitted + * + * This structure contains an inode for which a notification of a digest cache + * event is emitted. + */ +struct notify_inode { + struct list_head list; + struct inode *inode; +}; + /** * struct readdir_callback - Structure to store information for dir iteration * @ctx: Context structure @@ -126,6 +139,7 @@ struct htable { * @path_str: Path of the digest list the digest cache was created from * @flags: Control flags * @verif_data: Verification data regarding the digest list + * @notify_inodes: List of inodes for which a notification is emitted * @mutex: Protect digest cache modifications * * This structure represents a cache of digests extracted from a digest list. @@ -137,6 +151,7 @@ struct digest_cache { char *path_str; unsigned long flags; struct list_head verif_data; + struct list_head notify_inodes; struct mutex mutex; }; @@ -261,4 +276,15 @@ void digest_cache_inode_post_setxattr(struct dentry *dentry, const char *name, void digest_cache_inode_post_removexattr(struct dentry *dentry, const char *name); +/* notifier.c */ +int digest_cache_notify_inode_add(struct digest_cache *digest_cache, + struct inode *inode); +void digest_cache_notify_inodes_free(struct digest_cache *digest_cache); +void digest_cache_notify(struct digest_cache *digest_cache, struct inode *inode, + enum digest_cache_event event); +void digest_cache_notify_multiple(struct digest_cache *digest_cache, + enum digest_cache_event event); +int digest_cache_register_notifier(struct notifier_block *nb); +int digest_cache_unregister_notifier(struct notifier_block *nb); + #endif /* _DIGEST_CACHE_INTERNAL_H */ diff --git a/security/digest_cache/main.c b/security/digest_cache/main.c index ce3518a33c80..6fe0864f938f 100644 --- a/security/digest_cache/main.c +++ b/security/digest_cache/main.c @@ -51,6 +51,7 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str, INIT_LIST_HEAD(&digest_cache->htables); INIT_LIST_HEAD(&digest_cache->verif_data); INIT_LIST_HEAD(&digest_cache->dir_entries); + INIT_LIST_HEAD(&digest_cache->notify_inodes); mutex_init(&digest_cache->mutex); pr_debug("New digest cache %s (ref count: %d)\n", @@ -70,6 +71,7 @@ static void digest_cache_free(struct digest_cache *digest_cache) digest_cache_htable_free(digest_cache); digest_cache_verif_free(digest_cache); digest_cache_dir_free(digest_cache); + digest_cache_notify_inodes_free(digest_cache); mutex_destroy(&digest_cache->mutex); pr_debug("Freed digest cache %s\n", digest_cache->path_str); @@ -398,6 +400,7 @@ struct digest_cache *digest_cache_get(struct dentry *dentry) struct digest_cache_security *dig_sec; struct digest_cache *digest_cache = NULL; struct inode *inode = d_backing_inode(dentry); + int ret; if (!digest_cache_enabled) return NULL; @@ -420,6 +423,15 @@ struct digest_cache *digest_cache_get(struct dentry *dentry) /* Consume extra reference from digest_cache_create(). */ dig_sec->dig_user = digest_cache_new(dentry); up_read(&default_path_sem); + + if (dig_sec->dig_user) { + ret = digest_cache_notify_inode_add(dig_sec->dig_user, + inode); + if (ret < 0) { + digest_cache_put(dig_sec->dig_user); + dig_sec->dig_user = NULL; + } + } } if (dig_sec->dig_user) diff --git a/security/digest_cache/notifier.c b/security/digest_cache/notifier.c new file mode 100644 index 000000000000..06e4730e4434 --- /dev/null +++ b/security/digest_cache/notifier.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu + * + * Implement the notifier of the digest_cache LSM. + */ + +#define pr_fmt(fmt) "DIGEST CACHE: "fmt + +#include "internal.h" + +static BLOCKING_NOTIFIER_HEAD(chain); + +static const char * const events_str[] = { + [DIGEST_CACHE_RESET] = __stringify(DIGEST_CACHE_RESET), +}; + +/** + * digest_cache_notify_inode_add - Add inode to digest cache notification list + * @digest_cache: Digest cache + * @inode: Inode to add + * + * This function adds an inode to the digest cache notification list, so that + * notifications are emitted for that inode. + * + * Return: Zero on success, -ENOMEM on error. + */ +int digest_cache_notify_inode_add(struct digest_cache *digest_cache, + struct inode *inode) +{ + struct notify_inode *notify_inode; + + notify_inode = kmalloc(sizeof(*notify_inode), GFP_KERNEL); + if (!notify_inode) + return -ENOMEM; + + notify_inode->inode = inode; + + mutex_lock(&digest_cache->mutex); + list_add_tail(¬ify_inode->list, &digest_cache->notify_inodes); + pr_debug("Added inode %lu to notification list of digest cache %s\n", + inode->i_ino, digest_cache->path_str); + mutex_unlock(&digest_cache->mutex); + return 0; +} + +/** + * digest_cache_notify_inodes_free - Free digest cache notification list + * @digest_cache: Digest cache + * + * This function removes all inodes from the notification list of the passed + * digest cache and frees the memory. Does not need locking, since it is called + * only at the time the digest cache is freed. + */ +void digest_cache_notify_inodes_free(struct digest_cache *digest_cache) +{ + struct notify_inode *p, *q; + + list_for_each_entry_safe(p, q, &digest_cache->notify_inodes, list) { + list_del(&p->list); + pr_debug("Removed inode %lu from notification list of digest cache %s\n", + p->inode->i_ino, digest_cache->path_str); + kfree(p); + } +} + +/** + * digest_cache_notify - Emit notification for an inode + * @digest_cache: Digest cache + * @inode: Inode for which a digest cache is used + * @event: Event to notify + * + * This function emits a notification of a digest cache event for an affected + * inode. + */ +void digest_cache_notify(struct digest_cache *digest_cache, struct inode *inode, + enum digest_cache_event event) +{ + struct digest_cache_event_data event_data = { + .digest_cache = digest_cache, + .inode = inode, + }; + + pr_debug("Notify event %s for inode %lu using digest cache %s\n", + events_str[event], inode->i_ino, digest_cache->path_str); + + blocking_notifier_call_chain(&chain, event, &event_data); +} + +/** + * digest_cache_notify_multiple - Emit notification for all inodes in the list + * @digest_cache: Digest cache + * @event: Event to notify + * + * This function emits a notification for all inodes in the notification list + * of the passed digest cache. + */ +void digest_cache_notify_multiple(struct digest_cache *digest_cache, + enum digest_cache_event event) +{ + struct notify_inode *p; + + list_for_each_entry(p, &digest_cache->notify_inodes, list) + digest_cache_notify(digest_cache, p->inode, event); +} + +/** + * digest_cache_register_notifier() - Register a digest cache notifier + * @nb: Notifier block with the callback + * + * This function registers a new notifier for events occurring on digest caches. + * + * Return: Zero on success, -EEXIST on error. + */ +int digest_cache_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&chain, nb); +} +EXPORT_SYMBOL(digest_cache_register_notifier); + +/** + * digest_cache_unregister_notifier() - Unregister a digest cache notifier + * @nb: Notifier block with the callback + * + * This function unregisters a previously registered notifier. + * + * Return: Zero on success, -ENOENT on error. + */ +int digest_cache_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&chain, nb); +} +EXPORT_SYMBOL(digest_cache_unregister_notifier); diff --git a/security/digest_cache/reset.c b/security/digest_cache/reset.c index e07222b0e0e8..bb3b4108ba3b 100644 --- a/security/digest_cache/reset.c +++ b/security/digest_cache/reset.c @@ -10,6 +10,29 @@ #define pr_fmt(fmt) "DIGEST CACHE: "fmt #include "internal.h" +/** + * digest_cache_notify_dir_entry_reset - Notify dir entry reset + * @dir: Directory containing the digest lists + * + * Emit a notification for the directory containing the digest lists about a + * reset occurring on a directory entry. + */ + +static void digest_cache_notify_dir_entry_reset(struct inode *dir) +{ + struct digest_cache_security *dir_dig_sec; + + dir_dig_sec = digest_cache_get_security(dir); + if (unlikely(!dir_dig_sec)) + return; + + mutex_lock(&dir_dig_sec->dig_owner_mutex); + if (dir_dig_sec->dig_owner) + digest_cache_notify_multiple(dir_dig_sec->dig_owner, + DIGEST_CACHE_RESET); + mutex_unlock(&dir_dig_sec->dig_owner_mutex); +} + /** * digest_cache_reset_owner - Reset dig_owner * @inode: Inode of the digest list/directory containing the digest list @@ -19,6 +42,9 @@ * (if unset), so that digest_cache_get() and digest_cache_create() respectively * release and clear dig_user and dig_owner in the inode security blob. This * causes new callers of digest_cache_get() to get a new digest cache. + * + * After setting RESET, it emits a notification for all inodes using the digest + * cache. */ static void digest_cache_reset_owner(struct inode *inode, const char *reason) { @@ -30,9 +56,12 @@ static void digest_cache_reset_owner(struct inode *inode, const char *reason) mutex_lock(&dig_sec->dig_owner_mutex); if (dig_sec->dig_owner && - !test_and_set_bit(RESET, &dig_sec->dig_owner->flags)) + !test_and_set_bit(RESET, &dig_sec->dig_owner->flags)) { pr_debug("Resetting %s (dig_owner), reason: %s\n", dig_sec->dig_owner->path_str, reason); + digest_cache_notify_multiple(dig_sec->dig_owner, + DIGEST_CACHE_RESET); + } mutex_unlock(&dig_sec->dig_owner_mutex); } @@ -55,9 +84,12 @@ static void digest_cache_reset_user(struct inode *inode, const char *reason) mutex_lock(&dig_sec->dig_user_mutex); if (dig_sec->dig_user && - !test_and_set_bit(RESET_USER, &dig_sec->dig_user->flags)) + !test_and_set_bit(RESET_USER, &dig_sec->dig_user->flags)) { pr_debug("Resetting %s (dig_user), reason: %s\n", dig_sec->dig_user->path_str, reason); + digest_cache_notify(dig_sec->dig_user, inode, + DIGEST_CACHE_RESET); + } mutex_unlock(&dig_sec->dig_user_mutex); } @@ -74,11 +106,14 @@ static void digest_cache_reset_user(struct inode *inode, const char *reason) int digest_cache_path_truncate(const struct path *path) { struct inode *inode = d_backing_inode(path->dentry); + struct inode *dir = d_backing_inode(path->dentry->d_parent); if (!S_ISREG(inode->i_mode)) return 0; digest_cache_reset_owner(inode, "file_truncate"); + /* Dir digest cache users should know a dir entry changed. */ + digest_cache_notify_dir_entry_reset(dir); return 0; } @@ -101,6 +136,9 @@ void digest_cache_file_release(struct file *file) digest_cache_reset_owner(file_inode(file), "file_file_release"); if (file->f_mode & FMODE_CREATED) digest_cache_reset_owner(dir, "dir_file_release"); + else + /* Dir digest cache users should know a dir entry changed. */ + digest_cache_notify_dir_entry_reset(dir); } /** From patchwork Mon Apr 15 14:24:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Roberto Sassu X-Patchwork-Id: 789072 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 5E69B381C4; Mon, 15 Apr 2024 14:30:06 +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=1713191409; cv=none; b=mBcqRrCiAUTgF5sQDSXDyeniYVg5TjK/scnfrqJr2TZyC+jAE5PBSebFe+9y9KFWBvQVLoyVbwJCGquMQe3H72+VuYfp+oddnL5tHk18KBiezYXXsxWl2ZTMqLRSRemFGCFrFcxeDQWjnVMW7hImf7g2PZboDwP0FgPKq/tFERQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1713191409; c=relaxed/simple; bh=YzQ4i5qrhN9V57ocPuGAtqUzHjSjdvr1xff+mlGg8B0=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=tm7jqgNMY/6U86We0GyzYkRe0qbcFYdXEqGoBcPQES1Hj55R1FLMqgRLOq4X+2NfH9XlD/ImV8lBZAIUwcbJsvXGsYaK1wV9LwflFo9zjio4UcDp4pouLidcmZc0hvcR3qYob2aX++q6MZ9dt0nSGJNBPiQlI29AaC32smIUZ1Q= 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.51]) by frasgout13.his.huawei.com (SkyGuard) with ESMTP id 4VJ8KY0xlnz9v7HR; Mon, 15 Apr 2024 22:13:37 +0800 (CST) Received: from mail02.huawei.com (unknown [7.182.16.47]) by mail.maildlp.com (Postfix) with ESMTP id AC8BB1405DF; Mon, 15 Apr 2024 22:29:55 +0800 (CST) Received: from huaweicloud.com (unknown [10.204.63.22]) by APP1 (Coremail) with SMTP id LxC2BwAHshqQOR1mrL1NBg--.21472S6; Mon, 15 Apr 2024 15:29:54 +0100 (CET) From: Roberto Sassu To: corbet@lwn.net, paul@paul-moore.com, jmorris@namei.org, serge@hallyn.com, akpm@linux-foundation.org, shuah@kernel.org, mcoquelin.stm32@gmail.com, alexandre.torgue@foss.st.com, mic@digikod.net Cc: linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, bpf@vger.kernel.org, zohar@linux.ibm.com, dmitry.kasatkin@gmail.com, linux-integrity@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 v4 14/14] docs: Add documentation of the digest_cache LSM Date: Mon, 15 Apr 2024 16:24:36 +0200 Message-Id: <20240415142436.2545003-15-roberto.sassu@huaweicloud.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20240415142436.2545003-1-roberto.sassu@huaweicloud.com> References: <20240415142436.2545003-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: LxC2BwAHshqQOR1mrL1NBg--.21472S6 X-Coremail-Antispam: 1UD129KBjvAXoWftryDGw1xCF1rGFy8AryxKrg_yoW5urWUGo ZY9r4jyw1UKr15ZF4kCFnrAw13GwnYgwn7Ar18Kw45WF1rXFW5J3WDC3WUGFW3Jr4rGr9r A348J3yUJF1Utrn3n29KB7ZKAUJUUUU8529EdanIXcx71UUUUU7v73VFW2AGmfu7bjvjm3 AaLaJ3UjIYCTnIWjp_UUUOx7kC6x804xWl14x267AKxVWrJVCq3wAFc2x0x2IEx4CE42xK 8VAvwI8IcIk0rVWrJVCq3wAFIxvE14AKwVWUJVWUGwA2048vs2IY020E87I2jVAFwI0_JF 0E3s1l82xGYIkIc2x26xkF7I0E14v26ryj6s0DM28lY4IEw2IIxxk0rwA2F7IY1VAKz4vE j48ve4kI8wA2z4x0Y4vE2Ix0cI8IcVAFwI0_Gr0_Xr1l84ACjcxK6xIIjxv20xvEc7CjxV AFwI0_Gr1j6F4UJwA2z4x0Y4vEx4A2jsIE14v26r4j6F4UM28EF7xvwVC2z280aVCY1x02 67AKxVWxJr0_GcWle2I262IYc4CY6c8Ij28IcVAaY2xG8wAqx4xG64xvF2IEw4CE5I8CrV C2j2WlYx0E2Ix0cI8IcVAFwI0_Jrv_JF1lYx0Ex4A2jsIE14v26r1j6r4UMcvjeVCFs4IE 7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwACI402YVCY1x02628vn2kIc2xKxwCY1x0262 kKe7AKxVW8ZVWrXwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwC20s02 6c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r1rMI8E67AF67kF1VAFwI0_Wr v_Gr1UMIIYY7kG6xAYrwCIc40Y0x0EwIxGrwCI42IY6xIIjxv20xvE14v26r4j6ryUMIIF 0xvE2Ix0cI8IcVCY1x0267AKxVW8Jr0_Cr1UMIIF0xvE42xK8VAvwI8IcIk0rVWUJVWUCw CI42IY6I8E87Iv67AKxVW8JVWxJwCI42IY6I8E87Iv6xkF7I0E14v26F4UJVW0obIYCTnI WIevJa73UjIFyTuYvjxU9eOJUUUUU X-CM-SenderInfo: purev21wro2thvvxqx5xdzvxpfor3voofrz/1tbiAgAOBF1jj5h0wgABsW From: Roberto Sassu Add the documentation of the digest_cache LSM in Documentation/security. Signed-off-by: Roberto Sassu --- Documentation/security/digest_cache.rst | 763 ++++++++++++++++++++++++ Documentation/security/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 765 insertions(+) create mode 100644 Documentation/security/digest_cache.rst diff --git a/Documentation/security/digest_cache.rst b/Documentation/security/digest_cache.rst new file mode 100644 index 000000000000..f7c2b1bcf25b --- /dev/null +++ b/Documentation/security/digest_cache.rst @@ -0,0 +1,763 @@ +.. SPDX-License-Identifier: GPL-2.0 + +================ +Digest_cache LSM +================ + +Introduction +============ + +Integrity detection and protection has long been a desirable feature, to +reach a large user base and mitigate the risk of flaws in the software and +attacks. + +However, while solutions exist, they struggle to reach the large user base, +due to requiring higher than desired constraints on performance, +flexibility and configurability, that only security conscious people are +willing to accept. + +This is where the new digest_cache LSM comes into play, it offers +additional support for new and existing integrity solutions, to make them +faster and easier to deploy. + + +Motivation +========== + +The digest_cache LSM helps to address two important shortcomings of the +Integrity Measurement Architecture (IMA): predictability of the Platform +Configuration Registers (PCRs), and the provisioning of reference values to +compare the calculated file digest against. + +Remote attestation, according to Trusted Computing Group (TCG) +specifications, is done by replicating the PCR extend operation in +software with the digests in the event log (in this case the IMA +measurement list), and by comparing the obtained value with the PCR value +signed by the TPM with the quote operation. + +Due to how the extend operation is performed, if measurements are done in +a different order, the final PCR value will be different. That means that +if measurements are done in parallel, there is no way to predict what the +final PCR value will be, making impossible to seal data to a PCR value. If +the PCR value was predictable, a system could for example prove its +integrity by unsealing and using its private key, without sending every +time the full list of measurements. + +Provisioning reference values for file digests is also a difficult task. +The solution so far was to add file signatures to RPM packages, and +possibly to DEB packages, so that IMA can verify them. While this undoubtly +works, it also requires Linux distribution vendors to support the feature +by rebuilding all their packages, and eventually extending their PKI to +perform the additional signatures. It could also require developers extra +work to deal with the additional data. + +On the other hand, since often packages carry the file digests themselves, +it won't be actually needed to add file signatures. If the kernel was able +to extract the file digests by itself, all the tasks mentioned above for +the Linux distribution vendors won't be needed too. All current and past +Linux distributions can be easily retrofitted to enable IMA appraisal with +the file digests from the packages. + +Narrowing down the scope of a package parser to only extract specific +information makes it small enough to accurately verify that it cannot harm +the kernel. In fact, the parsers included with the digest_cache LSM have +been verified with the formal verification tool Frama-C, albeit with a +limited buffer size (the verification time grows considerably with bigger +buffer sizes). The parsers with the Frama-C assertions are available here: + +https://github.com/robertosassu/rpm-formal/ + +Frama-C asserts that the parsers don't read beyond their assigned buffer +for any byte combination. + +An additional mitigation against corrupted digest lists consists in +verifying the signature of the package first, before attempting to extract +the file digests. + + +Solution +======== + +The digest_cache LSM can help IMA to extend a PCR in a deterministic way. +If IMA knows that a file comes from a Linux distribution, it can measure +files in a different way: measure the list of digests coming from the +distribution (e.g. RPM package headers), and subsequently measure a file if +it is not found in that list. + +If the system executes known files, it does not matter in which order they +are executed, because the PCR is not extended. That however means that the +lists of digests must be measured in a deterministic way. The digest_cache +LSM has a prefetching mechanism to make this happen, consisting in +sequentially reading digest lists in a directory until it finds the +requested one. + +The resulting IMA measurement list however has a disadvantage: it does not +tell to remote verifiers whether files with digest in the measured digest +lists have been accessed or not and when. Also the IMA measurement list +would change after a software update. + +The digest_cache LSM can also help IMA for appraisal. Currently, IMA has +to evaluate the signature of each file individually, and expects that the +Linux vendors include those signatures together with the files in the +packages. + +With the digest_cache LSM, IMA can simply lookup in the list of digests +extracted from package headers, once the signature of those headers has +been verified. The same approach can be followed by other LSMs, such as +Integrity Policy Enforcement (IPE). + + +Design +====== + +Digest cache +------------ + +Main idea +~~~~~~~~~ + +The digest_cache LSM extracts digests from a file, referred to as a digest +list, and stores them in kernel memory in a structure named digest_cache. + +The digest_cache structure contains a set of per algorithm hash tables, +where digests are stored, the digest list pathname, a reference counter, +the integrity state of the digest list, and the inodes for which the digest +cache is used. + +If a digest cache is created from a directory, its hash tables are empty +and instead it contains a snapshot of the directory entries discovered with +iterate_dir(). + +The integrity state of digest caches created from regular files is +evaluated independently by other LSMs, for example by verifying the +signature of the digest list, and is provided to the digest_cache LSM +through a dedicated API. + +The extracted digests can be used as reference values initially for +integrity verification of file data and at a later stage for integrity +verification of file metadata. + +The digest_cache LSM can extract digests from a digest list, only if it has +a parser for its format. Currently, it supports a TLV-based and the RPM +package header formats, and can support more in the future. + + +Digest list lookup +~~~~~~~~~~~~~~~~~~ + +In order to build a digest cache and return it to the caller for performing +a query, the digest_cache LSM must know which digest list to use. There are +a few alternatives. + +(1) There is only one digest list and its path is specified as default +location at build-time in the kernel configuration or at run-time through +securityfs. The digest_cache LSM builds a single digest cache from that +digest list and returns it to the caller. + +(2) The default location is a directory containing multiple digest lists. +Unlike (1), the digest_cache LSM does not know which digest list to select, +and creates an iterator with a snapshot of the directory entries. During a +query, the digest_cache LSMs iteratively creates a digest cache for each +directory entry and searches for the digest until there is a match. + +(3) Same as (2), but the digest list file name is stored as value of the +new security.digest_list xattr in the inode for which the digest cache is +requested. The digest_cache LSM can directly retrieve the digest list using +the default directory as the base path and the xattr value as last path +component. + +(4) Similar to (3), but the digest_cache LSM still creates a directory +iterator like in (2). It reads digest lists with a file name that does not +match the security.digest_list xattr, to trigger a measurement, and creates +a digest cache from the matching one. This is also known as the prefetching +mechanism, introduced later. + + +Digest cache creation +~~~~~~~~~~~~~~~~~~~~~ + +Digest list naming convention +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Once the digest_cache LSM selected the digest list to use, it reads the +file and calls the appropriate parser to extract the digests, based on the +filename prefix. + +The expected digest list file name format is:: + + - + +where format can be for example ``tlv`` or ``rpm``, which make the +digest_cache LSM call respectively the TLV or RPM parser. + +Alternatively, also the following format is supported:: + + -- + +``-`` defines how directory entries should be ordered in the +directory iterator. + +Digest cache create API +^^^^^^^^^^^^^^^^^^^^^^^ + +The digest_cache LSM offers an API for parsers to initialize and add +digests to the digest cache hash tables. + +It exposes digest_cache_htable_init() to initialize a hash table for a +given algorithm, and to size it depending on the number of digests to add, +normally known by the parsers before adding digests. + +The number of hash table slots is determined by dividing the number of +digests to add by the desired average collision depth. The latter can be +changed in the kernel configuration, to have a different tradeoff between +digest lookup speed and memory occupation. + +It also exposes digest_cache_htable_add(), to let parsers add extracted +digests to the new hash table. If parsers need to add digests created with +different algorithms, they can create as many hash tables as they need. + +Finally, parsers can also call digest_cache_htable_lookup() to lookup a +digest in the passed digest_cache. + +Digest cache caching on create +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since the same digest cache can be requested multiple times for +verification of different inodes (e.g. installed files belonging to the +same software package), a pointer to the newly created digest cache (named +dig_owner) is stored in the inode security blob of the digest list. + +Dig_owner check and assignment is protected by the dig_owner_mutex, also +stored in the inode security blob. The first requestor instantiates and +populates the new digest cache. The other lock contenders wait until the +lock is released and until the first requestor clears the INIT_IN_PROGRESS +bit in the digest cache bit mask. The latter is needed to avoid lock +inversion with the code tracking changes on digest lists/default directory. + + +Digest cache request +~~~~~~~~~~~~~~~~~~~~ + +Users of the digest_cache LSM can request a digest cache by calling +digest_cache_get(), passing the inode for which they need a digest cache, +and can release it with digest_cache_put() once they are done. As mentioned +above, the digest_cache LSM determines which digest list the digest cache +should be built/retrieved from. + +Digest cache caching on request +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To avoid having to find the digest list inode for every digest_cache_get() +call, also a pointer to the retrieved digest cache (named dig_user) is +stored in the security blob of the inode for which the digest cache is +requested. + +Dig_user is also protected by its own dig_user_mutex (stored in the same +inode security blob) for check and assignment. Multiple requestors of a +digest cache for the same inode have to wait until the first requestor +finds the digest list inode and obtains the digest cache. + +Digest cache reference count +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since digest cache pointers are stored in the inode security blobs and +returned to the digest_cache_get() callers, the digest_cache LSM must track +how many pointers are around, to avoid freeing a digest cache while it is +still in use. + +The digest_cache LSM records the number of such pointers in a per digest +cache reference count, and increments it every time the pointer is stored +in a new inode security blob (either dig_owner or dig_user), or returned by +digest_cache_get(), and decrements it when an inode is evicted from memory +or a caller of digest_cache_get() calls digest_cache_put(). + + +Digest lookup +~~~~~~~~~~~~~ + +After a caller of digest_cache_get() obtains the desired digest cache, it +can perform operations on it. The most important operation is querying for +a digest, which can be performed by calling digest_cache_lookup(). + +digest_cache_lookup() returns a numeric reference (digest_cache_found_t +type), representing the digest cache containing the queried digest. It is +not a pointer, to avoid it being accidentally passed to digest_cache_put(). + +If the digest_cache LSM took the option (2) (multiple digest lists in the +default directory but which one to use is unknown), digest_cache_get() +returns an iterator instead, to be passed to digest_cache_lookup(). + +Only the latter finally returns the digest cache containing the searched +digest. If the digest is not found, digest_cache_lookup() returns zero. + + +Verification data +~~~~~~~~~~~~~~~~~ + +Until now, the caller of the digest_cache LSM is assumed to always trust +the returned digest cache from being created from authentic data. Or, there +are security measures in place but not able to correlate reading a digest +list with building a digest cache from it. + +The digest_cache LSM introduces a new mechanism for integrity providers to +store verification data, i.e. their evaluation result of a digest list. It +also allows callers of digest_cache_get() to later retrieve that +information and decide whether or not they should use that digest cache. + +It achieves that by reserving space in the file descriptor security blob, +and by setting the digest cache pointer in the digest list file descriptor. + +The digest_cache LSM supports multiple integrity providers at the same +time, since multiple LSMs can implement the kernel_post_read_file LSM hook. +Each provider is expected to choose an unique ID, so that the verification +data can be given back through the same ID. + +Those integrity providers should implement the kernel_post_read_file LSM +hook and call digest_cache_verif_set(), passing the digest list file +descriptor, the unique ID and their evaluation result of the digest list. + +Callers of digest_cache_get() can call digest_cache_verif_get() to get +the verification data, passing the returned digest cache pointer and the +desired integrity provider ID. However, if the digest cache returned was an +iterator, that call results in a NULL pointer, since the iterator is +not populated with any digest list. + +In that case, those callers have to call digest_cache_lookup() to get the +numeric reference of the digest cache containing the digest (thus populated +from a digest list), and pass it to digest_cache_verif_get() after +conversion to a digest cache pointer (with digest_cache_from_found_t()). + + +Tracking changes +~~~~~~~~~~~~~~~~ + +After a digest cache has been built and its pointer has been set in the +inode security blob, it might happen that there are changes in the digest +lists, in the default directory and in the value of the +security.digest_list xattr. + +All these changes may influence which digest cache is returned to callers +of digest_cache_get() and which digests in the digest cache might be +searched. + +The digest_cache LSM monitors such changes by registering to multiple LSM +hooks (path_truncate, file_release, inode_unlink, inode_rename, +inode_post_setxattr and inode_post_removexattr). Except for the last two, +it accesses the dig_owner pointer in the affected inode security blob and +sets the RESET bit. + +The next time that digest cache is requested, both dig_user and dig_owner +are passed to digest_cache_put() and cleared. A new digest cache is +created, as if there wasn't one in the first place. + +For the last two hooks, the RESET_USER bit is set instead, to limit +clearing dig_user, since only retrieval of the digest list could change +after modifying the security.digest_list xattr, and not the digest cache +itself. + +Nothing changes for callers of digest_cache_get(), since they still hold +the old digest cache pointer, despite that has been replaced in the inode +security blobs. The old digest cache pointer will not be freed until those +callers also call digest_cache_put() and the reference count reaches zero. + +Notify changes +~~~~~~~~~~~~~~ + +While new calls to digest_cache_get() result in a new digest cache to be +returned, resetting the previous digest cache does not reflect in a reset +of possibly cached security decisions based on that digest cache. + +IMA for example, would not be able to recheck a file digest against a +modified digest cache, since it is not aware of the reset in the first +place. + +Introduce a subscription-based notification mechanism, that dispatches to +the interested parties events which include the type of event (e.g. reset) +and the digest cache and inodes affected. A user of the digest_cache LSM +can become a subscriber by calling digest_cache_register_notifier() and can +unsubscribe by calling digest_cache_unregister_notifier(). + +During a digest_cache_get(), add the inode for which the digest cache was +requested to a notification list of the same digest cache. When the RESET +bit is set, emit a event for each inode in that notification list, so that +IMA and the other integrity providers can eventually invalidate their +cached security decision on that inode. + +On a file digest cache reset, notify also users of the parent directory +digest cache, since they might have looked up digests through that digest +cache. Those users will see changes by performing another lookup. + +When the RESET_USER bit is set, emit a notification just for the inode +signalled by the LSM hook, since the operation causing a reset +(set/removexattr) only affects the link between the inode and the digest +cache, and not the digest cache itself. + +Prefetching mechanism +~~~~~~~~~~~~~~~~~~~~~ + +One of the objectives of the digest_cache LSM is to make a TPM PCR +predictable, by having digest lists measured in a deterministic order. +Without the prefetching mechanism, digest lists are measured in a +non-deterministic order, since the inodes for which a digest cache can be +requested are accessed in a non-deterministic order too. + +The prefetching mechanism, when enabled by setting the new +security.dig_prefetch xattr to 1, forces digest lists to be looked up by +their file name in the list of the directory entries of the iterator +created for the default directory. + +The predictability of the PCR is ensured by reading both matching and +non-matching digest lists during the search, so that integrity providers +can measure them, and by only creating a digest cache for the matching one. +In this way, it does not matter if a digest list later in the list of +directory entries is requested before a earlier one, since all digest lists +until that point are measured anyway. + +However, while this mechanism ensures predictability of the PCR, it could +also introduce significant latencies, especially if the matching digest +list is very late in the list of directory entries. Before a digest cache +is returned from that digest list, hundreds or thousands of digest lists +could have to be read first. + +Then, the ``[-]`` prefix in the digest list file name comes at +hand, since it determines the order of directory entries in the iterator +(entries with lower seq nums are before entries with higher seq nums). +Digest lists without that prefix are added at the end of iterator list, +in the same order as iterate_dir() shows them. + +With ``[-]``, the latency of digest cache creation when the +prefetching mechanism is enabled can be significantly reduced for example +by ordering digest lists by their appearance in the IMA measurement list, +since that list reflects the order in which digest lists are requested at +boot. + +While digest lists can be requested in a slightly different order due to +the non-deterministic access to inodes, the differences should be minimal, +causing only fewer extra digest lists to be read before the right one is +found. + +Ordering directory entries can also improve digest queries requiring +iteration on all digest lists in the default directory. If directory +entries are ordered by their appearance in the IMA measurement list, a +digest is found faster because most likely it is searched in the same +order as when the IMA measurement list was recorded, and thus its +digest list comes earlier than the others in the list of the directory +entries of the iterator. + + +Data structures and API +======================= + +Data structures +--------------- + +These are the data structures defined and used internally by the +digest_cache LSM. + +.. kernel-doc:: security/digest_cache/internal.h + + +Public API +---------- + +This API is meant to be used by users of the digest_cache LSM. + +.. kernel-doc:: include/linux/digest_cache.h + :identifiers: digest_cache_found_t + digest_cache_from_found_t + +.. kernel-doc:: security/digest_cache/main.c + :identifiers: digest_cache_get digest_cache_put + +.. kernel-doc:: security/digest_cache/htable.c + :identifiers: digest_cache_lookup + +.. kernel-doc:: security/digest_cache/verif.c + :identifiers: digest_cache_verif_set digest_cache_verif_get + +.. kernel-doc:: security/digest_cache/notifier.c + :identifiers: digest_cache_register_notifier + digest_cache_unregister_notifier + + +Parser API +---------- + +This API is meant to be used by digest list parsers. + +.. kernel-doc:: security/digest_cache/htable.c + :identifiers: digest_cache_htable_init + digest_cache_htable_add + digest_cache_htable_lookup + + +Digest List Formats +=================== + +tlv +--- + +The Type-Length-Value (TLV) format was chosen for its extensibility. +Additional fields can be added without breaking compatibility with old +versions of the parser. + +The layout of a tlv digest list is the following:: + + [header: DIGEST_LIST_FILE, num fields, total len] + [field: DIGEST_LIST_ALGO, length, value] + [field: DIGEST_LIST_ENTRY#1, length, value (below)] + |- [header: DIGEST_LIST_ENTRY_DATA, num fields, total len] + |- [DIGEST_LIST_ENTRY_DIGEST#1, length, file digest] + |- [DIGEST_LIST_ENTRY_PATH#1, length, file path] + [field: DIGEST_LIST_ENTRY#N, length, value (below)] + |- [header: DIGEST_LIST_ENTRY_DATA, num fields, total len] + |- [DIGEST_LIST_ENTRY_DIGEST#N, length, file digest] + |- [DIGEST_LIST_ENTRY_PATH#N, length, file path] + +DIGEST_LIST_ALGO is a field to specify the algorithm of the file digest. +DIGEST_LIST_ENTRY is a nested TLV structure with the following fields: +DIGEST_LIST_ENTRY_DIGEST contains the file digest; DIGEST_LIST_ENTRY_PATH +contains the file path. + + +rpm +--- + +The rpm digest list is basically a subset of the RPM package header. +Its format is:: + + [RPM magic number] + [RPMTAG_IMMUTABLE] + +RPMTAG_IMMUTABLE is a section of the full RPM header containing the part +of the header that was signed, and whose signature is stored in the +RPMTAG_RSAHEADER section. + + +Appended Signature +------------------ + +Digest lists can have a module-style appended signature, that can be used +for appraisal with IMA. The signature type can be PKCS#7, as for kernel +modules, or a different type. + + +History +======= + +The original name of this work was IMA Digest Lists, which was somehow +considered too invasive. The code was moved to a separate component named +DIGLIM (DIGest Lists Integrity Module), with the purpose of removing the +complexity away of IMA, and also adding the possibility of using it with +other kernel components (e.g. Integrity Policy Enforcement, or IPE). + +The design changed significantly, so DIGLIM was renamed to digest_cache +LSM, as the name better reflects what the new component does. + +Since it was originally proposed, in 2017, this work grew up a lot thanks +to various comments/suggestions. It became integrally part of the openEuler +distribution since end of 2020. + +The most important difference between the old the current version is moving +from a centralized repository of file digests to a per-package repository. +This significantly reduces the memory pressure, since digest lists are +loaded into kernel memory only when they are actually needed. Also, file +digests are automatically unloaded from kernel memory at the same time +inodes are evicted from memory during reclamation. + + +Performance +=========== + +System specification +-------------------- + +The tests have been performed on a Fedora 38 virtual machine with 4 cores +(AMD EPYC-Rome, no hyperthreading), 16 GB of RAM, no TPM/TPM passthrough/ +emulated. The QEMU process has been pinned to 4 real CPU cores and its +priority was set to -20. + + +Benchmark tool +-------------- + +The digest_cache LSM has been tested with an ad-hoc benchmark tool that +creates 20000 files with a random size up to 100 bytes and randomly adds +their digest to one of 303 digest lists. The number of digest lists has +been derived from the ratio (66) digests/packages (124174/1883) found in +the testing virtual machine (hence, 20000/66 = 303). IMA signatures have +been done with ECDSA NIST P-384. + +The benchmark tool then creates a list of 20000 files to be accessed, +randomly chosen (there can be duplicates). This is necessary to make the +results reproducible across reboots (by always replaying the same +operations). The benchmark reads (sequentially and in parallel) the files +from the list 2 times, flushing the kernel caches before each read. + +Each test has been performed 5 times, and the average value is taken. + + +Purpose of the benchmark +------------------------ + +The purpose of the benchmark is to show the performance difference of IMA +between the current behavior, and by using the digest_cache LSM. + + +IMA measurement policy: no cache +-------------------------------- + +.. code-block:: bash + + measure func=FILE_CHECK fowner=2001 pcr=12 + + +IMA measurement policy: cache +----------------------------- + +.. code-block:: bash + + measure func=DIGEST_LIST_CHECK pcr=12 + measure func=FILE_CHECK fowner=2001 digest_cache=data pcr=12 + + +IMA Measurement Results +----------------------- + +Sequential +~~~~~~~~~~ + +This test was performed reading files sequentially, and waiting for the +current read to terminate before beginning a new one. + +:: + + +-------+------------------------+-----------+ + | meas. | time no/p/vTPM (sec.) | slab (KB) | + +--------------------+-------+------------------------+-----------+ + | no cache | 12313 | 33.65 / 102.51 / 47.13 | 84170 | + +--------------------+-------+------------------------+-----------+ + | cache, no prefetch | 304 | 34.04 / 33.32 / 33.09 | 81159 | + +--------------------+-------+------------------------+-----------+ + | cache, prefetch | 304 | 34.02 / 33.31 / 33.15 | 81122 | + +--------------------+-------+------------------------+-----------+ + +The table shows that 12313 measurements (boot_aggregate + files) have been +made without the digest cache, and 304 with the digest cache +(boot_aggregate + digest lists). Consequently, the memory occupation +without the cache is higher due to the higher number of measurements. + +Not surprisingly, for the same reason, also the test time is significantly +higher without the digest cache when the physical or virtual TPM is used. + +In terms of pure performance, first number in the third column, it can be +seen that there are not really performance differences between using or not +using the digest cache. + +Prefetching does not add overhead, also because digest lists were ordered +according to their appearance in the IMA measurement list (which minimize +the digest lists to prefetch). + + +Parallel +~~~~~~~~ + +This test was performed reading files in parallel, not waiting for the +current read to terminate. + +:: + + +-------+-----------------------+-----------+ + | meas. | time no/p/vTPM (sec.) | slab (KB) | + +--------------------+-------+-----------------------+-----------+ + | no cache | 12313 | 14.08 / 79.09 / 22.70 | 85138 | + +--------------------+-------+-----------------------+-----------+ + | cache, no prefetch | 304 | 14.44 / 15.11 / 14.96 | 85777 | + +--------------------+-------+-----------------------+-----------+ + | cache, prefetch | 304 | 14.30 / 15.41 / 14.40 | 83294 | + +--------------------+-------+-----------------------+-----------+ + +Also in this case, the physical TPM causes the biggest delay especially +without digest cache, where a higher number of measurements need to be +extended in the TPM. + +The digest_cache LSM does not introduce a noticeable overhead in all +scenarios. + + +IMA appraisal policy: no cache +------------------------------ + +.. code-block:: bash + + appraise func=FILE_CHECK fowner=2001 + + +IMA appraisal policy: cache +--------------------------- + +.. code-block:: bash + + appraise func=DIGEST_LIST_CHECK + appraise func=FILE_CHECK fowner=2001 digest_cache=data + + +IMA Appraisal Results +--------------------- + +Sequential +~~~~~~~~~~ + +This test was performed reading files sequentially, and waiting for the +current read to terminate before beginning a new one. + +:: + + +-------------+-------------+-----------+ + | files | time (sec.) | slab (KB) | + +----------------------------+-------------+-------------+-----------+ + | appraise (ECDSA sig) | 12312 | 96.74 | 78827 | + +----------------------------+-------------+-------------+-----------+ + | appraise (cache) | 12312 + 303 | 33.09 | 80854 | + +----------------------------+-------------+-------------+-----------+ + | appraise (cache, prefetch) | 12312 + 303 | 33.42 | 81050 | + +----------------------------+-------------+-------------+-----------+ + +This test shows a huge performance difference from verifying the signature +of 12312 files as opposed to just verifying the signature of 303 digest +lists, and looking up the digest of the files being read. + +There are some differences in terms of memory occupation, which is quite +expected due to the fact that we have to take into account the digest +caches loaded in memory, while with the standard appraisal they don't +exist. + + +Parallel +~~~~~~~~ + +This test was performed reading files in parallel, not waiting for the +current read to terminate. + +:: + + +-------------+-------------+-----------+ + | files | time (sec.) | slab (KB) | + +----------------------------+-------------+-------------+-----------+ + | appraise (ECDSA sig) | 12312 | 27.68 | 80596 | + +----------------------------+-------------+-------------+-----------+ + | appraise (cache) | 12313 + 303 | 14.96 | 80778 | + +----------------------------+-------------+-------------+-----------+ + | appraise (cache, prefetch) | 12313 + 303 | 14.78 | 83354 | + +----------------------------+-------------+-------------+-----------+ + +The difference is less marked when performing the read in parallel. Also, +more memory seems to be occupied in the prefetch case. + + +How to Test +=========== + +Please follow the instructions here: + +https://github.com/linux-integrity/digest-cache-tools diff --git a/Documentation/security/index.rst b/Documentation/security/index.rst index 59f8fc106cb0..34933e13c509 100644 --- a/Documentation/security/index.rst +++ b/Documentation/security/index.rst @@ -19,3 +19,4 @@ Security Documentation digsig landlock secrets/index + digest_cache diff --git a/MAINTAINERS b/MAINTAINERS index d7f700da009e..67b1fb3ab0ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6197,6 +6197,7 @@ DIGEST_CACHE LSM M: Roberto Sassu L: linux-security-module@vger.kernel.org S: Maintained +F: Documentation/security/digest_cache.rst F: security/digest_cache/ F: tools/testing/selftests/digest_cache/