[libcamera-devel] [PATCH] libcamera: ipa: allow trusting modules by checksum

Arnout Engelen arnout at bzzt.net
Sat Jan 20 15:37:42 CET 2024


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 <arnout at bzzt.net>
---
 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 <IPA module_>`__).
 
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<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,8 @@ public:
 		return pubKey_;
 	}
 #endif
+	static int loadTrustedChecksums(const char *path,
+					std::vector<std::vector<uint8_t>> &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<IPAModule *> modules_;
 
@@ -72,6 +76,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..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<std::vector<uint8_t>> 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<uint8_t> 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 <arnout at bzzt.net>
+#
+# 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<std::vector<uint8_t>> &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<const uint8_t> span = file.map();
+	const uint8_t* data = span.data();
+	const int size = span.size();
+	for (int i = 0; i < size - 64;) {
+		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);
+		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<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..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')
 
-- 
2.43.0



More information about the libcamera-devel mailing list