From patchwork Sat Jan 20 14:37:42 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arnout Engelen X-Patchwork-Id: 19419 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 62ECFC323E for ; Sat, 20 Jan 2024 14:56:17 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 5249762916; Sat, 20 Jan 2024 15:56:16 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org; s=mail; t=1705762576; bh=kw5BTiQzmmYcKnfUwiIbvcebmI5PhOava7XV6mK56TY=; h=To:Date:Subject:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:Cc:From; b=xFivMQZo31B4CMfQ+sdvvEt7+CyLy2qani68HYwWT+YnZtRPbrAq/dFGmTEbZQZc8 qoaPKQdhUEeRxylYF+3lGrMr9d6KYUClq7Qm+gBC7GXzzKYpS189mWvaw62GmxSRLm 6WMLC/dfn1hHtElKVrYIOTmK/1RpV3OJVS7sx6QLRw4Swgs+1BHZlfQOxIh6j5FCPN XNPE+l4wVPoOzc4gYQQL8YoLGjxPP/DzIoHjZJlF115Zr61XSBpPPAoI1vXfIYN/my CaBLfkItHKdH8MeleTJFyXxcVReV/WRh2MGvXdDh+NCtR0e64oUV0MlZtWB9Sv2aN9 o+6ZmU0A+sOcg== Received: from wfhigh4-smtp.messagingengine.com (wfhigh4-smtp.messagingengine.com [64.147.123.155]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id A909761D41 for ; Sat, 20 Jan 2024 15:38:17 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=bzzt.net header.i=@bzzt.net header.b="iKokYVv1"; dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="mvXZMFIB"; dkim-atps=neutral Received: from compute4.internal (compute4.nyi.internal [10.202.2.44]) by mailfhigh.west.internal (Postfix) with ESMTP id C11BB180006C; Sat, 20 Jan 2024 09:38:12 -0500 (EST) Received: from mailfrontend2 ([10.202.2.163]) by compute4.internal (MEProxy); Sat, 20 Jan 2024 09:38:13 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bzzt.net; h=cc :cc:content-transfer-encoding:content-type:date:date:from:from :in-reply-to:message-id:mime-version:reply-to:subject:subject:to :to; s=fm1; t=1705761492; x=1705847892; bh=YFRsnRsyGZ/KFR1r47jQt pLKtU8kQ8rqTXjElMZZL7Y=; b=iKokYVv1gIqGIZDnohAOf9Eiq7zrucHUHkPxt OF/BqAPIyUy7VnlnOivF/dnWajfhkv2S8zc5nlh2Fw1oXRxtzsJq3X5fbt7Ms+/M MX0yECchkeYZt1vjyMJzdr8PRX3coEeRUyVP3gnCJZI7BicaTngx+RwLw6BCBh/I RjkU2HkoTW5gV/82s4NnouwxAfZ/E4CrChR8pMbj/oBtTZTGn/wOPpZp3/v6L/ia fZSdAckEoazYpa63u08d9S7P9KWRqE+Ihe/qHyRRZJygIe1w82jgRbRICJ2mYWXx t5f/1VVucyQb5b0RQLNl1y+qDKGksvxxFp2Rhg6L5N/Gf4/AA== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:date:date:feedback-id:feedback-id:from:from :in-reply-to:message-id:mime-version:reply-to:subject:subject:to :to:x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm3; t=1705761492; x=1705847892; bh=YFRsnRsyGZ/KFR1r47jQtpLKtU8k Q8rqTXjElMZZL7Y=; b=mvXZMFIBf80HXXtl/LjSl6XSML8uMnYfMsXJJhFW3tox aLvf4oVnJrKHjdWlxYsKRMzG02f4AKPYgaXbb6vaH8vEBJjPBvGm6L+lMJkOp66F VuOXPsPmOlue7omfdHR5T2VwbxKaJJ5YZFe/fV1CFdM4o6aSWxTWXkBwBVphklTB UISJZ3hoSILU/Z/wFciiWuhfMU9K8B8DoVig36TgAMqtNwE9Cr1Hi55PlpUI+Qwu VqPDbcXrrJT6XIRLGB+zg1S76ZWcblSHTWWrrDkt1HKyG0cR2abTwo+66en+kG3l Xxepoa8sBQKapSrJbIkZ+LZs2AItFWdjAVw93xVOBw== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvkedrvdekvddgieekucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucenucfjughrpefhvfevufffkffoggfgsedtkeertd ertddtnecuhfhrohhmpeetrhhnohhuthcugfhnghgvlhgvnhcuoegrrhhnohhuthessgii iihtrdhnvghtqeenucggtffrrghtthgvrhhnpefhkeegiefgieeutdehvedthfdtjeevfe ejvdevfeehffduudekhefgfeekvdekleenucffohhmrghinheprhgvphhrohguuhgtihgs lhgvqdgsuhhilhgushdrohhrghenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmh epmhgrihhlfhhrohhmpegrrhhnohhuthessgiiiihtrdhnvght X-ME-Proxy: Feedback-ID: i8a1146c4:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Sat, 20 Jan 2024 09:38:11 -0500 (EST) To: libcamera-devel@lists.libcamera.org Date: Sat, 20 Jan 2024 15:37:42 +0100 Message-ID: <20240120143742.1302914-1-arnout@bzzt.net> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 X-Mailman-Approved-At: Sat, 20 Jan 2024 15:56:14 +0100 Subject: [libcamera-devel] [PATCH] libcamera: ipa: allow trusting modules by checksum X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Arnout Engelen via libcamera-devel From: Arnout Engelen Reply-To: Arnout Engelen Cc: Arnout Engelen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Currently, libcamera signs the in-tree IPA modules during the build, and embeds the public key so that only trusted IPA modules will be run in-process. Out-of-tree modules will be run using runtime isolation. This commit adds a second mechanism to achieve the same: during packaging the checksums of the in-tree IPA modules are recorded, and at run time the modules are considered trusted when they match the recorded trusted checksums. The motivation behind adding this mechanism is that this allows rebuilding the library and getting a bit-by-bit identical result, without having to share the keys with which to sign the trusted modules. This is known as 'Reproducible Builds', and you can read more about its advantages on https://reproducible-builds.org/. With this feature, packagers that care about reproducible builds can disable the module signing, and enjoy equivalent security and performance while also allowing independent rebuilds. Signed-off-by: Arnout Engelen --- Documentation/environment_variables.rst | 3 + include/libcamera/internal/ipa_manager.h | 10 ++- include/libcamera/internal/ipa_module.h | 2 + meson_options.txt | 4 + src/apps/ipa-verify/main.cpp | 45 ++++++++++- src/apps/ipa-verify/meson.build | 2 +- src/ipa/ipa-checksum-install.sh | 22 ++++++ src/ipa/meson.build | 11 +++ src/libcamera/ipa_manager.cpp | 99 ++++++++++++++++++++++-- src/libcamera/ipa_module.cpp | 30 +++++++ src/meson.build | 17 +++- 11 files changed, 229 insertions(+), 16 deletions(-) create mode 100644 src/ipa/ipa-checksum-install.sh diff --git a/Documentation/environment_variables.rst b/Documentation/environment_variables.rst index a9b230bc..baae225c 100644 --- a/Documentation/environment_variables.rst +++ b/Documentation/environment_variables.rst @@ -32,6 +32,9 @@ LIBCAMERA_IPA_FORCE_ISOLATION Example value: ``1`` +LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE + Define custom location of the trusted IPA module checksums file. + LIBCAMERA_IPA_MODULE_PATH Define custom search locations for IPA modules (`more `__). diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h index bf823563..c05583e8 100644 --- a/include/libcamera/internal/ipa_manager.h +++ b/include/libcamera/internal/ipa_manager.h @@ -38,7 +38,7 @@ public: if (!m) return nullptr; - std::unique_ptr proxy = std::make_unique(m, !self_->isSignatureValid(m)); + std::unique_ptr proxy = std::make_unique(m, !self_->isTrusted(m)); if (!proxy->isValid()) { LOG(IPAManager, Error) << "Failed to load proxy"; return nullptr; @@ -53,6 +53,8 @@ public: return pubKey_; } #endif + static int loadTrustedChecksums(const char *path, + std::vector> &checksums); private: static IPAManager *self_; @@ -65,6 +67,8 @@ private: uint32_t maxVersion); bool isSignatureValid(IPAModule *ipa) const; + bool isTrustedChecksum(IPAModule *ipa) const; + bool isTrusted(IPAModule *ipa) const; std::vector modules_; @@ -72,6 +76,10 @@ private: static const uint8_t publicKeyData_[]; static const PubKey pubKey_; #endif + +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + std::vector> trusted_checksums_; +#endif }; } /* namespace libcamera */ diff --git a/include/libcamera/internal/ipa_module.h b/include/libcamera/internal/ipa_module.h index 8038bdee..f4c0ec5c 100644 --- a/include/libcamera/internal/ipa_module.h +++ b/include/libcamera/internal/ipa_module.h @@ -30,6 +30,7 @@ public: const struct IPAModuleInfo &info() const; const std::vector signature() const; + const std::vector checksum() const; const std::string &path() const; bool load(); @@ -47,6 +48,7 @@ private: struct IPAModuleInfo info_; std::vector signature_; + std::vector checksum_; std::string libPath_; bool valid_; diff --git a/meson_options.txt b/meson_options.txt index 5fdc7be8..a970ac30 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -30,6 +30,10 @@ option('ipas', choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'vimc'], description : 'Select which IPA modules to build') +option('ipa_sign_modules', + type : 'feature', + description : 'enable IPA trusted module signing') + option('lc-compliance', type : 'feature', value : 'auto', diff --git a/src/apps/ipa-verify/main.cpp b/src/apps/ipa-verify/main.cpp index 76ba5073..368de4a0 100644 --- a/src/apps/ipa-verify/main.cpp +++ b/src/apps/ipa-verify/main.cpp @@ -18,8 +18,9 @@ using namespace libcamera; namespace { -bool isSignatureValid(IPAModule *ipa) +bool isSignatureValid([[maybe_unused]] IPAModule *ipa) { +#if HAVE_IPA_PUBKEY File file{ ipa->path() }; if (!file.open(File::OpenModeFlag::ReadOnly)) return false; @@ -29,6 +30,32 @@ bool isSignatureValid(IPAModule *ipa) return false; return IPAManager::pubKey().verify(data, ipa->signature()); +#else + return false; +#endif +} + +bool isChecksumTrusted([[maybe_unused]] IPAModule *ipa) +{ +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + std::vector> trusted; + const char *checksumsFile = + utils::secure_getenv("LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE"); + if (checksumsFile) { + IPAManager::loadTrustedChecksums(checksumsFile, trusted); + } else { + IPAManager::loadTrustedChecksums(IPA_TRUSTED_MODULE_CHECKSUMS_FILE, trusted); + } + for (std::vector t : trusted) { + if (std::equal(t.begin(), t.begin() + 32, + ipa->checksum().begin())) { + return true; + } + } + return false; +#else + return false; +#endif } void usage(char *argv0) @@ -54,11 +81,23 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - if (!isSignatureValid(&module)) { + bool ok = false; + if (isSignatureValid(&module)) { + std::cout << "IPA module signature is valid" << std::endl; + ok = true; + } else { std::cout << "IPA module signature is invalid" << std::endl; + } + if (isChecksumTrusted(&module)) { + std::cout << "IPA module checksum is trusted" << std::endl; + ok = true; + } else { + std::cout << "IPA module checksum is not trusted" << std::endl; + } + + if (!ok) { return EXIT_FAILURE; } - std::cout << "IPA module signature is valid" << std::endl; return 0; } diff --git a/src/apps/ipa-verify/meson.build b/src/apps/ipa-verify/meson.build index 7fdda3b9..feffb538 100644 --- a/src/apps/ipa-verify/meson.build +++ b/src/apps/ipa-verify/meson.build @@ -1,6 +1,6 @@ # SPDX-License-Identifier: CC0-1.0 -if not ipa_sign_module +if not ipa_sign_module and not ipa_checksum_trusted_modules subdir_done() endif diff --git a/src/ipa/ipa-checksum-install.sh b/src/ipa/ipa-checksum-install.sh new file mode 100644 index 00000000..d8341c0c --- /dev/null +++ b/src/ipa/ipa-checksum-install.sh @@ -0,0 +1,22 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +# Copyright (C) 2020, Google Inc. +# +# Author: Arnout Engelen +# +# ipa-checksum-install.sh - Generate IPA module checksums when installing + +modules=$* + +echo "Generating trusted IPA module checksums" + +checksum_file=${MESON_INSTALL_DESTDIR_PREFIX}/share/libcamera/ipa/trusted_module_checksums.txt + +rm ${checksum_file} || true + +for module in ${modules} ; do + module="${MESON_INSTALL_DESTDIR_PREFIX}/${module}" + if [ -f "${module}" ] ; then + sha256sum -b "${module}" | sed -e "s| .*/| |" >> ${checksum_file} + fi +done diff --git a/src/ipa/meson.build b/src/ipa/meson.build index 48793e07..c3ae24ba 100644 --- a/src/ipa/meson.build +++ b/src/ipa/meson.build @@ -8,6 +8,10 @@ ipa_install_dir = libcamera_libdir ipa_data_dir = libcamera_datadir / 'ipa' ipa_sysconf_dir = libcamera_sysconfdir / 'ipa' +config_h.set('IPA_TRUSTED_MODULE_CHECKSUMS_FILE', + '"' + get_option('prefix') / ipa_data_dir + + '/trusted_module_checksums.txt"') + config_h.set('IPA_CONFIG_DIR', '"' + get_option('prefix') / ipa_sysconf_dir + ':' + get_option('prefix') / ipa_data_dir + '"') @@ -75,3 +79,10 @@ if ipa_sign_module enabled_ipa_modules, install_tag : 'runtime') endif + +if ipa_checksum_trusted_modules + # Similarly, calculate checksums for the installed artifacts + meson.add_install_script('ipa-checksum-install.sh', + enabled_ipa_modules, + install_tag : 'runtime') +endif diff --git a/src/libcamera/ipa_manager.cpp b/src/libcamera/ipa_manager.cpp index 7a4515d9..beb93de6 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -114,6 +114,18 @@ IPAManager::IPAManager() LOG(IPAManager, Warning) << "Public key not valid"; #endif +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + const char *checksumsFile = + utils::secure_getenv("LIBCAMERA_IPA_TRUSTED_MODULE_CHECKSUMS_FILE"); + if (checksumsFile) { + IPAManager::loadTrustedChecksums(checksumsFile, + trusted_checksums_); + } else { + IPAManager::loadTrustedChecksums(IPA_TRUSTED_MODULE_CHECKSUMS_FILE, + trusted_checksums_); + } +#endif + unsigned int ipaCount = 0; /* User-specified paths take precedence. */ @@ -165,6 +177,41 @@ IPAManager::~IPAManager() self_ = nullptr; } +/** + * \brief Load trusted checksums + * \param[in] path The path to the file containing the trusted checksums + * \param[out] checksums A vector of checksums as 32-element byte vectors + * + * \return Zero on success, an error code on failure. + */ +int IPAManager::loadTrustedChecksums([[maybe_unused]] const char *path, [[maybe_unused]] std::vector> &checksums) +{ +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + File file{ path }; + if (!file.open(File::OpenModeFlag::ReadOnly)) { + LOG(IPAManager, Warning) << "Failed to open trusted IPA checksums: " + << strerror(-file.error()); + return file.error(); + } + Span span = file.map(); + const uint8_t* data = span.data(); + const int size = span.size(); + for (int i = 0; i < size - 64;) { + std::vector *checksum = new std::vector(); + for (int c = 0; c < 64; c += 2) { + char chr[3] = { static_cast(data[i+c]), static_cast(data[i+c+1]), '\0' }; + checksum->push_back(strtol(chr, nullptr, 16)); + } + checksums.push_back(*checksum); + while (data[i] != '\n' && i < size) { + i++; + }; + i++; + } +#endif + return 0; +} + /** * \brief Identify shared library objects within a directory * \param[in] libDir The directory to search for shared objects @@ -295,14 +342,6 @@ IPAModule *IPAManager::module(PipelineHandler *pipe, uint32_t minVersion, bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const { #if HAVE_IPA_PUBKEY - char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION"); - if (force && force[0] != '\0') { - LOG(IPAManager, Debug) - << "Isolation of IPA module " << ipa->path() - << " forced through environment variable"; - return false; - } - File file{ ipa->path() }; if (!file.open(File::OpenModeFlag::ReadOnly)) return false; @@ -323,4 +362,48 @@ bool IPAManager::isSignatureValid([[maybe_unused]] IPAModule *ipa) const #endif } +bool IPAManager::isTrustedChecksum([[maybe_unused]] IPAModule *ipa) const +{ +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + File file{ ipa->path() }; + if (!file.open(File::OpenModeFlag::ReadOnly)) + return false; + + Span data = file.map(); + if (data.empty()) + return false; + + bool valid = false; + + for (std::vector t : trusted_checksums_) { + if (std::equal(t.begin(), t.begin() + 32, + ipa->checksum().begin())) { + valid = true; + continue; + } + } + + LOG(IPAManager, Debug) + << "IPA module " << ipa->path() << " checksum is " + << (valid ? "trusted" : "not trusted"); + + return valid; +#else + return false; +#endif +} + +bool IPAManager::isTrusted(IPAModule *ipa) const +{ + char *force = utils::secure_getenv("LIBCAMERA_IPA_FORCE_ISOLATION"); + if (force && force[0] != '\0') { + LOG(IPAManager, Debug) + << "Isolation of IPA module " << ipa->path() + << " forced through environment variable"; + return false; + } + return isTrustedChecksum(ipa) || isSignatureValid(ipa); +} + + } /* namespace libcamera */ diff --git a/src/libcamera/ipa_module.cpp b/src/libcamera/ipa_module.cpp index f2dd87e5..3d49432b 100644 --- a/src/libcamera/ipa_module.cpp +++ b/src/libcamera/ipa_module.cpp @@ -27,6 +27,12 @@ #include "libcamera/internal/pipeline_handler.h" +#if HAVE_CRYPTO +#include +#elif HAVE_GNUTLS +#include +#endif + /** * \file ipa_module.h * \brief Image Processing Algorithm module @@ -281,6 +287,17 @@ int IPAModule::loadIPAModuleInfo() } Span data = file.map(); + + /* Calculate the module checksum. */ + uint8_t digest[32] = { 0 }; +#if HAVE_CRYPTO + SHA256(data.data(), data.size(), digest); +#elif HAVE_GNUTLS + gnutls_hash_fast(GNUTLS_DIG_SHA256, data.data(), data.size(), digest); +#endif + checksum_ = std::vector(digest, digest + 32); + + /* Interpret the file. */ int ret = elfVerifyIdent(data); if (ret) { LOG(IPAModule, Error) << "IPA module is not an ELF file"; @@ -379,6 +396,19 @@ const std::vector IPAModule::signature() const return signature_; } +/** + * \brief Retrieve the IPA module checksum + * + * The IPA module checksum is loaded when the IPAModule instance is created. + * + * \return The IPA module checksum + */ +const std::vector IPAModule::checksum() const +{ + return checksum_; +} + + /** * \brief Retrieve the IPA module path * diff --git a/src/meson.build b/src/meson.build index 165a77bb..6cfc3316 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,19 +14,30 @@ summary({ 'LIBCAMERA_SYSCONF_DIR' : config_h.get('LIBCAMERA_SYSCONF_DIR'), }, section : 'Paths') +# Trusted module checksumming +ipa_checksum_trusted_modules = true + +if ipa_checksum_trusted_modules + config_h.set('HAVE_IPA_TRUSTED_MODULE_CHECKSUMS', 1) +endif + # Module Signing -openssl = find_program('openssl', required : false) -if openssl.found() +openssl = find_program('openssl', required : get_option('ipa_sign_modules')) +if get_option('ipa_sign_modules').enabled() and openssl.found() ipa_priv_key = custom_target('ipa-priv-key', output : ['ipa-priv-key.pem'], command : [gen_ipa_priv_key, '@OUTPUT@']) config_h.set('HAVE_IPA_PUBKEY', 1) ipa_sign_module = true else - warning('openssl not found, all IPA modules will be isolated') ipa_sign_module = false endif +if not ipa_checksum_trusted_modules and not ipa_sign_module + warning('neither checksums nor signatures enabled,') + warning('all IPA modules will be isolated') +endif + # libcamera must be built first as a dependency to the other components. subdir('libcamera')