[1/1] libcamera: ipa: allow trusting modules by checksum
diff mbox series

Message ID 20240122114040.275771-2-libcamera@bzzt.net
State New
Headers show
Series
  • libcamera: ipa: allow trusting modules by checksum
Related show

Commit Message

Arnout Engelen Jan. 22, 2024, 11:40 a.m. UTC
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.

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

Patch
diff mbox series

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')