Files
external_libcamera/src/libcamera/ipa_manager.cpp
Laurent Pinchart eab143ee69 libcamera: ipa_manager: Verify IPA module signature
Decide whether to isolate the IPA module using the module signature
instead of its license.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
2020-04-14 02:03:29 +03:00

325 lines
9.2 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019, Google Inc.
*
* ipa_manager.cpp - Image Processing Algorithm module manager
*/
#include "ipa_manager.h"
#include <algorithm>
#include <dirent.h>
#include <string.h>
#include <sys/types.h>
#include "file.h"
#include "ipa_module.h"
#include "ipa_proxy.h"
#include "log.h"
#include "pipeline_handler.h"
#include "utils.h"
/**
* \file ipa_manager.h
* \brief Image Processing Algorithm module manager
*/
namespace libcamera {
LOG_DEFINE_CATEGORY(IPAManager)
/**
* \class IPAManager
* \brief Manager for IPA modules
*
* The IPA module manager discovers IPA modules from disk, queries and loads
* them, and creates IPA contexts. It supports isolation of the modules in a
* separate process with IPC communication and offers a unified IPAInterface
* view of the IPA contexts to pipeline handlers regardless of whether the
* modules are isolated or loaded in the same process.
*
* Module isolation is based on the module licence. Open-source modules are
* loaded without isolation, while closed-source module are forcefully isolated.
* The isolation mechanism ensures that no code from a closed-source module is
* ever run in the libcamera process.
*
* To create an IPA context, pipeline handlers call the IPAManager::ipaCreate()
* method. For a directly loaded module, the manager calls the module's
* ipaCreate() function directly and wraps the returned context in an
* IPAContextWrapper that exposes an IPAInterface.
*
* ~~~~
* +---------------+
* | Pipeline |
* | Handler |
* +---------------+
* |
* v
* +---------------+ +---------------+
* | IPA | | Open Source |
* | Interface | | IPA Module |
* | - - - - - - - | | - - - - - - - |
* | IPA Context | ipa_context_ops | ipa_context |
* | Wrapper | ----------------> | |
* +---------------+ +---------------+
* ~~~~
*
* For an isolated module, the manager instantiates an IPAProxy which spawns a
* new process for an IPA proxy worker. The worker loads the IPA module and
* creates the IPA context. The IPAProxy alse exposes an IPAInterface.
*
* ~~~~
* +---------------+ +---------------+
* | Pipeline | | Closed Source |
* | Handler | | IPA Module |
* +---------------+ | - - - - - - - |
* | | ipa_context |
* v | |
* +---------------+ +---------------+
* | IPA | ipa_context_ops ^
* | Interface | |
* | - - - - - - - | +---------------+
* | IPA Proxy | operations | IPA Proxy |
* | | ----------------> | Worker |
* +---------------+ over IPC +---------------+
* ~~~~
*
* The IPAInterface implemented by the IPAContextWrapper or IPAProxy is
* returned to the pipeline handler, and all interactions with the IPA context
* go the same interface regardless of process isolation.
*
* In all cases the data passed to the IPAInterface methods is serialized to
* Plain Old Data, either for the purpose of passing it to the IPA context
* plain C API, or to transmit the data to the isolated process through IPC.
*/
IPAManager::IPAManager()
{
unsigned int ipaCount = 0;
/* User-specified paths take precedence. */
const char *modulePaths = utils::secure_getenv("LIBCAMERA_IPA_MODULE_PATH");
if (modulePaths) {
for (const auto &dir : utils::split(modulePaths, ":")) {
if (dir.empty())
continue;
ipaCount += addDir(dir.c_str());
}
if (!ipaCount)
LOG(IPAManager, Warning)
<< "No IPA found in '" << modulePaths << "'";
}
/*
* When libcamera is used before it is installed, load IPAs from the
* same build directory as the libcamera library itself. This requires
* identifying the path of the libcamera.so, and referencing a relative
* path for the IPA from that point. We need to recurse one level of
* sub-directories to match the build tree.
*/
std::string root = utils::libcameraBuildPath();
if (!root.empty()) {
std::string ipaBuildPath = root + "src/ipa";
constexpr int maxDepth = 1;
LOG(IPAManager, Info)
<< "libcamera is not installed. Adding '"
<< ipaBuildPath << "' to the IPA search path";
ipaCount += addDir(ipaBuildPath.c_str(), maxDepth);
}
/* Finally try to load IPAs from the installed system path. */
ipaCount += addDir(IPA_MODULE_DIR);
if (!ipaCount)
LOG(IPAManager, Warning)
<< "No IPA found in '" IPA_MODULE_DIR "'";
}
IPAManager::~IPAManager()
{
for (IPAModule *module : modules_)
delete module;
}
/**
* \brief Retrieve the IPA manager instance
*
* The IPAManager is a singleton and can't be constructed manually. This
* function shall instead be used to retrieve the single global instance of the
* manager.
*
* \return The IPA manager instance
*/
IPAManager *IPAManager::instance()
{
static IPAManager ipaManager;
return &ipaManager;
}
/**
* \brief Identify shared library objects within a directory
* \param[in] libDir The directory to search for shared objects
* \param[in] maxDepth The maximum depth of sub-directories to parse
* \param[out] files A vector of paths to shared object library files
*
* Search a directory for .so files, allowing recursion down to sub-directories
* no further than the depth specified by \a maxDepth.
*
* Discovered shared objects are added to the \a files vector.
*/
void IPAManager::parseDir(const char *libDir, unsigned int maxDepth,
std::vector<std::string> &files)
{
struct dirent *ent;
DIR *dir;
dir = opendir(libDir);
if (!dir)
return;
while ((ent = readdir(dir)) != nullptr) {
if (ent->d_type == DT_DIR && maxDepth) {
if (strcmp(ent->d_name, ".") == 0 ||
strcmp(ent->d_name, "..") == 0)
continue;
std::string subdir = std::string(libDir) + "/" + ent->d_name;
/* Recursion is limited to maxDepth. */
parseDir(subdir.c_str(), maxDepth - 1, files);
continue;
}
int offset = strlen(ent->d_name) - 3;
if (offset < 0)
continue;
if (strcmp(&ent->d_name[offset], ".so"))
continue;
files.push_back(std::string(libDir) + "/" + ent->d_name);
}
closedir(dir);
}
/**
* \brief Load IPA modules from a directory
* \param[in] libDir The directory to search for IPA modules
* \param[in] maxDepth The maximum depth of sub-directories to search
*
* This method tries to create an IPAModule instance for every shared object
* found in \a libDir, and skips invalid IPA modules.
*
* Sub-directories are searched up to a depth of \a maxDepth. A \a maxDepth
* value of 0 only searches the directory specified in \a libDir.
*
* \return Number of modules loaded by this call
*/
unsigned int IPAManager::addDir(const char *libDir, unsigned int maxDepth)
{
std::vector<std::string> files;
parseDir(libDir, maxDepth, files);
/* Ensure a stable ordering of modules. */
std::sort(files.begin(), files.end());
unsigned int count = 0;
for (const std::string &file : files) {
IPAModule *ipaModule = new IPAModule(file);
if (!ipaModule->isValid()) {
delete ipaModule;
continue;
}
LOG(IPAManager, Debug) << "Loaded IPA module '" << file << "'";
modules_.push_back(ipaModule);
count++;
}
return count;
}
/**
* \brief Create an IPA interface that matches a given pipeline handler
* \param[in] pipe The pipeline handler that wants a matching IPA interface
* \param[in] minVersion Minimum acceptable version of IPA module
* \param[in] maxVersion Maximum acceptable version of IPA module
*
* \return A newly created IPA interface, or nullptr if no matching
* IPA module is found or if the IPA interface fails to initialize
*/
std::unique_ptr<IPAInterface> IPAManager::createIPA(PipelineHandler *pipe,
uint32_t maxVersion,
uint32_t minVersion)
{
IPAModule *m = nullptr;
for (IPAModule *module : modules_) {
if (module->match(pipe, minVersion, maxVersion)) {
m = module;
break;
}
}
if (!m)
return nullptr;
/*
* Load and run the IPA module in a thread if it has a valid signature,
* or isolate it in a separate process otherwise.
*
* \todo Implement a better proxy selection
*/
const char *proxyName = isSignatureValid(m)
? "IPAProxyThread" : "IPAProxyLinux";
IPAProxyFactory *pf = nullptr;
for (IPAProxyFactory *factory : IPAProxyFactory::factories()) {
if (!strcmp(factory->name().c_str(), proxyName)) {
pf = factory;
break;
}
}
if (!pf) {
LOG(IPAManager, Error) << "Failed to get proxy factory";
return nullptr;
}
std::unique_ptr<IPAProxy> proxy = pf->create(m);
if (!proxy->isValid()) {
LOG(IPAManager, Error) << "Failed to load proxy";
return nullptr;
}
return proxy;
}
bool IPAManager::isSignatureValid(IPAModule *ipa) const
{
File file{ ipa->path() };
if (!file.open(File::ReadOnly))
return false;
Span<uint8_t> data = file.map();
if (data.empty())
return false;
bool valid = pubKey_.verify(data, ipa->signature());
LOG(IPAManager, Debug)
<< "IPA module " << ipa->path() << " signature is "
<< (valid ? "valid" : "not valid");
return valid;
}
} /* namespace libcamera */