| Message ID | 20240122114040.275771-2-libcamera@bzzt.net |
|---|---|
| State | New |
| Headers | show |
| Series |
|
| Related | show |
On Mon, Jan 22, 2024 at 12:40:40PM +0100, libcamera@bzzt.net wrote: > From: Arnout Engelen <arnout@bzzt.net> > > 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. Given the increased importance of reproducible builds, as well as Kate Hsuan's recent patch series to support ML-DSA-65 for post-quantum cryptography (see https://patchwork.libcamera.org/project/libcamera/list/?series=5954), I think we'll need to make a decision on how we want to handle out-of-tree IPA modules. Merging this patch would render ML-DSA-65 support uneeded (sorry about that Kate) and would probably simplify the code base. I'm a bit uncomfortable with the idea of patching the libcamera.so binary though, at least with sed. If we were to go this way, moving the checksums to a separate ELF section would be more robust. Another idea that crossed my mind recently would take this one step further: should link all in-tree IPA modules in the libcamera binary instead of building them as shared objects, and always run external IPA modules in isolation ? There are a few drawbacks, in particular an increase in memory usage. Will that be a scaling issue moving forward ? > Signed-off-by: Arnout Engelen <arnout@bzzt.net> > --- > 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<T> proxy = std::make_unique<T>(m, !self_->isSignatureValid(m)); > + std::unique_ptr<T> proxy = std::make_unique<T>(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<std::vector<uint8_t>> &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<IPAModule *> modules_; > > @@ -72,6 +75,10 @@ private: > static const uint8_t publicKeyData_[]; > static const PubKey pubKey_; > #endif > + > +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS > + std::vector<std::vector<uint8_t>> 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<uint8_t> signature() const; > + const std::vector<uint8_t> checksum() const; > const std::string &path() const; > > bool load(); > @@ -47,6 +48,7 @@ private: > > struct IPAModuleInfo info_; > std::vector<uint8_t> signature_; > + std::vector<uint8_t> 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<std::vector<uint8_t>> trusted; > + IPAManager::loadTrustedChecksums(trusted); > + for (std::vector<uint8_t> 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 <arnout@bzzt.net> > +# > +# 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<std::vector<uint8_t>> &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<uint8_t> *checksum = new std::vector<uint8_t>(); > + for (int c = 0; c < 64; c += 2) { > + char chr[3] = { static_cast<char>(data[i+c]), static_cast<char>(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<uint8_t> data = file.map(); > + if (data.empty()) > + return false; > + > + bool valid = false; > + > + for (std::vector<uint8_t> 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 <openssl/sha.h> > +#elif HAVE_GNUTLS > +#include <gnutls/crypto.h> > +#endif > + > /** > * \file ipa_module.h > * \brief Image Processing Algorithm module > @@ -281,6 +287,17 @@ int IPAModule::loadIPAModuleInfo() > } > > Span<const uint8_t> 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<uint8_t>(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<uint8_t> 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<uint8_t> 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') >
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<T> proxy = std::make_unique<T>(m, !self_->isSignatureValid(m)); + std::unique_ptr<T> proxy = std::make_unique<T>(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<std::vector<uint8_t>> &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<IPAModule *> modules_; @@ -72,6 +75,10 @@ private: static const uint8_t publicKeyData_[]; static const PubKey pubKey_; #endif + +#if HAVE_IPA_TRUSTED_MODULE_CHECKSUMS + std::vector<std::vector<uint8_t>> 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<uint8_t> signature() const; + const std::vector<uint8_t> checksum() const; const std::string &path() const; bool load(); @@ -47,6 +48,7 @@ private: struct IPAModuleInfo info_; std::vector<uint8_t> signature_; + std::vector<uint8_t> 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<std::vector<uint8_t>> trusted; + IPAManager::loadTrustedChecksums(trusted); + for (std::vector<uint8_t> 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 <arnout@bzzt.net> +# +# 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<std::vector<uint8_t>> &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<uint8_t> *checksum = new std::vector<uint8_t>(); + for (int c = 0; c < 64; c += 2) { + char chr[3] = { static_cast<char>(data[i+c]), static_cast<char>(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<uint8_t> data = file.map(); + if (data.empty()) + return false; + + bool valid = false; + + for (std::vector<uint8_t> 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 <openssl/sha.h> +#elif HAVE_GNUTLS +#include <gnutls/crypto.h> +#endif + /** * \file ipa_module.h * \brief Image Processing Algorithm module @@ -281,6 +287,17 @@ int IPAModule::loadIPAModuleInfo() } Span<const uint8_t> 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<uint8_t>(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<uint8_t> 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<uint8_t> 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')