From patchwork Mon Jan 22 11:40:40 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arnout Engelen X-Patchwork-Id: 19444 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 34F1DC3243 for ; Mon, 22 Jan 2024 11:41:05 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id CFB776294B; Mon, 22 Jan 2024 12:41:04 +0100 (CET) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (2048-bit key; unprotected) header.d=bzzt.net header.i=@bzzt.net header.b="KDEgHwKn"; dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="l0MZceEe"; dkim-atps=neutral Received: from out2-smtp.messagingengine.com (out2-smtp.messagingengine.com [66.111.4.26]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 08929628B7 for ; Mon, 22 Jan 2024 12:41:03 +0100 (CET) Received: from compute5.internal (compute5.nyi.internal [10.202.2.45]) by mailout.nyi.internal (Postfix) with ESMTP id 361B45C0070; Mon, 22 Jan 2024 06:41:02 -0500 (EST) Received: from mailfrontend1 ([10.202.2.162]) by compute5.internal (MEProxy); Mon, 22 Jan 2024 06:41:02 -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:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to; s=fm1; t=1705923662; x= 1706010062; bh=yR9oxHScZvpdNBrMhwgw4I8WA+zw/fQHnLmbgdjfuTE=; b=K DEgHwKn0zD23DUB0eERosgYUlJk2IWWhfjMe7XgcrmaxZVkbt8a8O936XgOhtroi tz7GnySUBMMeeKnl4po06ScGhj+TPyZzQLtd9jN4VSzs5Y1vlbuXrbWF8nxHhEs7 AiB3tOUSxixVcsFpQyvn5q3L2bUHHSnXENY9kT4bsj1iZODkY88ZDQLrZFYvYtG4 +Fjuj7mNFoO/4AOPwp0lIZ/tPeKsIVlZHqvk+qJFFR07c9s8jtUl/UfgN+sMYFv3 k8teNTWrGvBjCaBWbP5bqk20XIn5hyFufk0jwj4Kg+ncrx1U35ImbkLDmTXNT+BP bmQ5wdGEhPen1obSGuHjw== 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:in-reply-to:message-id:mime-version:references :reply-to:subject:subject:to:to:x-me-proxy:x-me-proxy :x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t=1705923662; x= 1706010062; bh=yR9oxHScZvpdNBrMhwgw4I8WA+zw/fQHnLmbgdjfuTE=; b=l 0MZceEegmkRbGl8xcIk7CC8Zd31PfnC7d3ZlbwmPLsY/k+vR9LTa8L7ybrqLjtLy BcOPiCDHDwwgOjd9TqxYsfL6f4QW5+UHF0DMwmAF2pBzTTkVm1Nz8Ay+1YeTwmVT ZCE9Dpx9L6U/Yfp11DykpA6OksYc+dhFQC4bTg3oQrnZWRy5lfkcDXXfYMqcyIOM F47F2RdCdg/Ryz9DFOkXO5SKZnYFrl5ja9yUtAb9D0paefGqyF6b2xr69WFTEmUK ihZsKwIk8OTTwZBq91ULRQ8eYMsIJcekxxmI1XzQX7a5TYeq8DZqs7LRQadEyDyV im6sILMum5saKm7g6IvMA== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedvkedrvdekiedgfedtucetufdoteggodetrfdotf fvucfrrhhofhhilhgvmecuhfgrshhtofgrihhlpdfqfgfvpdfurfetoffkrfgpnffqhgen uceurghilhhouhhtmecufedttdenucesvcftvggtihhpihgvnhhtshculddquddttddmne cujfgurhephffvvefufffkofgjfhgggfestdekredtredttdenucfhrhhomheplhhisggt rghmvghrrgessgiiiihtrdhnvghtnecuggftrfgrthhtvghrnhepheekkeegfedtjeehvd fggeevgedvudduudejveevfeefhedutdetgeevheehkeelnecuffhomhgrihhnpehrvghp rhhoughutghisghlvgdqsghuihhlughsrdhorhhgnecuvehluhhsthgvrhfuihiivgeptd enucfrrghrrghmpehmrghilhhfrhhomheplhhisggtrghmvghrrgessgiiiihtrdhnvght X-ME-Proxy: Feedback-ID: i7559471f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Mon, 22 Jan 2024 06:41:01 -0500 (EST) From: libcamera@bzzt.net To: libcamera-devel@lists.libcamera.org Subject: [PATCH 1/1] libcamera: ipa: allow trusting modules by checksum Date: Mon, 22 Jan 2024 12:40:40 +0100 Message-ID: <20240122114040.275771-2-libcamera@bzzt.net> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20240122114040.275771-1-libcamera@bzzt.net> References: <20240122114040.275771-1-libcamera@bzzt.net> MIME-Version: 1.0 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: , Cc: Arnout Engelen Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" From: Arnout Engelen 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 installation 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 --- include/libcamera/internal/ipa_manager.h | 9 ++- include/libcamera/internal/ipa_module.h | 2 + meson_options.txt | 8 +++ src/apps/ipa-verify/main.cpp | 43 +++++++++++- src/apps/ipa-verify/meson.build | 2 +- src/ipa/ipa-checksum-install.sh | 24 +++++++ src/ipa/meson.build | 7 ++ src/libcamera/ipa_manager.cpp | 88 +++++++++++++++++++++--- src/libcamera/ipa_module.cpp | 30 ++++++++ src/meson.build | 18 ++++- 10 files changed, 215 insertions(+), 16 deletions(-) create mode 100644 src/ipa/ipa-checksum-install.sh diff --git a/include/libcamera/internal/ipa_manager.h b/include/libcamera/internal/ipa_manager.h index bf823563..2475e26a 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,7 @@ public: return pubKey_; } #endif + static void loadTrustedChecksums(std::vector> &checksums); private: static IPAManager *self_; @@ -65,6 +66,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 +75,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..dd753ca8 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -30,6 +30,14 @@ 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('ipa_checksum_trusted_modules', + type : 'feature', + description : 'enable IPA trusted module checksums') + 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..facfd481 100644 --- a/src/apps/ipa-verify/main.cpp +++ b/src/apps/ipa-verify/main.cpp @@ -18,7 +18,8 @@ using namespace libcamera; namespace { -bool isSignatureValid(IPAModule *ipa) +#if HAVE_IPA_PUBKEY +bool isSignatureValid([[maybe_unused]] IPAModule *ipa) { File file{ ipa->path() }; if (!file.open(File::OpenModeFlag::ReadOnly)) @@ -30,6 +31,22 @@ bool isSignatureValid(IPAModule *ipa) return IPAManager::pubKey().verify(data, ipa->signature()); } +#endif + +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS +bool isChecksumTrusted([[maybe_unused]] IPAModule *ipa) +{ + std::vector> trusted; + IPAManager::loadTrustedChecksums(trusted); + for (std::vector t : trusted) { + if (std::equal(t.begin(), t.begin() + 32, + ipa->checksum().begin())) { + return true; + } + } + return false; +} +#endif void usage(char *argv0) { @@ -54,11 +71,31 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - if (!isSignatureValid(&module)) { + bool ok = false; +#if HAVE_IPA_PUBKEY + if (isSignatureValid(&module)) { + std::cout << "IPA module signature is valid" << std::endl; + ok = true; + } else { std::cout << "IPA module signature is invalid" << std::endl; + } +#else + std::cout << "IPA module signing is disabled" << std::endl; +#endif +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + 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; + } +#else + std::cout << "IPA module checksums are disabled" << std::endl; +#endif + + 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..41a1cb48 --- /dev/null +++ b/src/ipa/ipa-checksum-install.sh @@ -0,0 +1,24 @@ +#!/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" + +lib_file=$(find ${MESON_INSTALL_DESTDIR_PREFIX}/lib -type f -regex .*/libcamera.so.*) + +i=1 +for module in ${modules} ; do + module="${MESON_INSTALL_DESTDIR_PREFIX}/${module}" + if [ -f "${module}" ] ; then + checksum=$(sha256sum -b "${module}" | cut -d " " -f 1) + # FIXME support for trusting multiple modules + sed -b -e "s/embedded_ipa_trusted_module_checksum_sha256_placeholder_0000000${i}/${checksum}/" -i ${lib_file} + i=$((i+1)) + fi +done diff --git a/src/ipa/meson.build b/src/ipa/meson.build index 48793e07..00299a7c 100644 --- a/src/ipa/meson.build +++ b/src/ipa/meson.build @@ -75,3 +75,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..96281758 100644 --- a/src/libcamera/ipa_manager.cpp +++ b/src/libcamera/ipa_manager.cpp @@ -114,6 +114,10 @@ IPAManager::IPAManager() LOG(IPAManager, Warning) << "Public key not valid"; #endif +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + IPAManager::loadTrustedChecksums(trusted_checksums_); +#endif + unsigned int ipaCount = 0; /* User-specified paths take precedence. */ @@ -165,6 +169,38 @@ IPAManager::~IPAManager() self_ = nullptr; } +/** + * \brief Load trusted checksums + * \param[out] checksums A vector of checksums as 32-element byte vectors + */ +void IPAManager::loadTrustedChecksums([[maybe_unused]] std::vector> &checksums) +{ +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + // Since there are legitimate reasons for post-processing the modules + // in the install phase, we use static placeholders here and allow + // replacing them in the binary in the install phase. + std::string embeddedChecksums = + "embedded_ipa_trusted_module_checksum_sha256_placeholder_00000001\n" + "embedded_ipa_trusted_module_checksum_sha256_placeholder_00000002\n" + "embedded_ipa_trusted_module_checksum_sha256_placeholder_00000003\n" + "embedded_ipa_trusted_module_checksum_sha256_placeholder_00000004\n" + "embedded_ipa_trusted_module_checksum_sha256_placeholder_00000005\n" + "embedded_ipa_trusted_module_checksum_sha256_placeholder_00000006\n" + "embedded_ipa_trusted_module_checksum_sha256_placeholder_00000007\n"; + + char* data = embeddedChecksums.data(); + const int size = strlen(data); + for (int i = 0; i < size; i += 65) { + 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); + } +#endif +} + /** * \brief Identify shared library objects within a directory * \param[in] libDir The directory to search for shared objects @@ -295,14 +331,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 +351,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..2096ff79 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,19 +14,31 @@ summary({ 'LIBCAMERA_SYSCONF_DIR' : config_h.get('LIBCAMERA_SYSCONF_DIR'), }, section : 'Paths') +# Trusted module checksumming +if get_option('ipa_checksum_trusted_modules').enabled() or get_option('ipa_checksum_trusted_modules').auto() + ipa_checksum_trusted_modules = true + config_h.set('HAVE_IPA_TRUSTED_MODULE_CHECKSUMS', 1) +else + ipa_checksum_trusted_modules = false +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() or get_option('ipa_checksum_trusted_modules').auto()) 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')