summaryrefslogtreecommitdiffstats
path: root/src/corelib/plugin
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2023-01-12 14:59:27 +0100
committerTor Arne Vestbø <tor.arne.vestbo@qt.io>2023-02-17 15:43:07 +0100
commit667aec810f71ac378a618032846df4bb6d76c647 (patch)
tree86f14bcb1ac75a3065d1da690abea6450cb2ef5f /src/corelib/plugin
parent61bfe87a64ca322de0ebf9bf61a0a0a81ee5bf7d (diff)
Darwin: Ensure encrypted library is loaded before parsing plugin metadata
Application delivered via the macOS or iOS App Store might have their libraries encrypted, in which case we can not read any of the sections of the binary until it has been dlopened. This was causing issues for our plugin loading code, which assumed we could read the .qtmetadata section of a yet to be loaded plugin to determine its suitability, before loading it. We now detect whether a library is encrypted during the Mach-O parsing, and propagate this back to QLibraryPrivate::updatePluginState(), which can handle the case by explicitly loading the library before continuing with metadata validation. We still ensure that the library has a .qtmetadata section, so that we don't need to dlopen any random library in our path. This does mean that we will potentially load more plugins than we need, and since the Qt version validation happens as part of meta data validation, we might dlopen() incompatible plugins, but it's expected that in an App Store deployment scenario you control both the versioning and set of shipped plugins, so this should not be an issue in practice. As encrypted libraries are only produced for apps that are fully published to the App Store, and then deployed via MDM, VPP, or Apple Configurator 2, we don't have an easy way to test this, but the existing code paths should be unaffected, and hopefully this patch improves the situation for the encrypted library case. Pick-to: 6.5 6.4 6.2 5.15 Change-Id: Iff733505f7067ce5571854ea978bc95e8376e347 Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
Diffstat (limited to 'src/corelib/plugin')
-rw-r--r--src/corelib/plugin/qlibrary.cpp31
-rw-r--r--src/corelib/plugin/qlibrary_p.h3
-rw-r--r--src/corelib/plugin/qmachparser.cpp25
3 files changed, 51 insertions, 8 deletions
diff --git a/src/corelib/plugin/qlibrary.cpp b/src/corelib/plugin/qlibrary.cpp
index 3b39e51e19..d4f9235538 100644
--- a/src/corelib/plugin/qlibrary.cpp
+++ b/src/corelib/plugin/qlibrary.cpp
@@ -210,7 +210,7 @@ static QLibraryScanResult qt_find_pattern(const char *s, qsizetype s_len, QStrin
information could not be read.
Returns true if version information is present and successfully read.
*/
-static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
+static QLibraryScanResult findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
{
QFile file(library);
if (!file.open(QIODevice::ReadOnly)) {
@@ -218,7 +218,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
lib->errorString = file.errorString();
qCWarning(qt_lcDebugPlugins, "%ls: cannot open: %ls", qUtf16Printable(library),
qUtf16Printable(file.errorString()));
- return false;
+ return {};
}
// Files can be bigger than the virtual memory size on 32-bit systems, so
@@ -235,7 +235,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
// This can't be used as a plugin.
qCWarning(qt_lcDebugPlugins, "%ls: failed to map to memory: %ls",
qUtf16Printable(library), qUtf16Printable(file.errorString()));
- return false;
+ return {};
}
#else
QByteArray data;
@@ -252,6 +252,10 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
QString errMsg = library;
QLibraryScanResult r = qt_find_pattern(filedata, fdlen, &errMsg);
if (r.length) {
+#if defined(Q_OF_MACH_O)
+ if (r.isEncrypted)
+ return r;
+#endif
if (!lib->metaData.parse(QByteArrayView(filedata + r.pos, r.length))) {
errMsg = lib->metaData.errorString();
qCWarning(qt_lcDebugPlugins, "Found invalid metadata in lib %ls: %ls",
@@ -260,7 +264,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
qCDebug(qt_lcDebugPlugins, "Found metadata in lib %ls, metadata=\n%s\n",
qUtf16Printable(library),
QJsonDocument(lib->metaData.toJson()).toJson().constData());
- return true;
+ return r;
}
} else {
qCDebug(qt_lcDebugPlugins, "Failed to find metadata in lib %ls: %ls",
@@ -269,7 +273,7 @@ static bool findPatternUnloaded(const QString &library, QLibraryPrivate *lib)
lib->errorString = QLibrary::tr("Failed to extract plugin meta data from '%1': %2")
.arg(library, errMsg);
- return false;
+ return {};
}
static void installCoverageTool(QLibraryPrivate *libPrivate)
@@ -724,7 +728,22 @@ void QLibraryPrivate::updatePluginState()
if (!pHnd.loadRelaxed()) {
// scan for the plugin metadata without loading
- success = findPatternUnloaded(fileName, this);
+ QLibraryScanResult result = findPatternUnloaded(fileName, this);
+#if defined(Q_OF_MACH_O)
+ if (result.length && result.isEncrypted) {
+ // We found the .qtmetadata section, but since the library is encrypted
+ // we need to dlopen() it before we can parse the metadata for further
+ // validation.
+ qCDebug(qt_lcDebugPlugins, "Library is encrypted. Doing prospective load before parsing metadata");
+ locker.unlock();
+ load();
+ locker.relock();
+ success = qt_get_metadata(this, &errorString);
+ } else
+#endif
+ {
+ success = result.length != 0;
+ }
} else {
// library is already loaded (probably via QLibrary)
// simply get the target function and call it.
diff --git a/src/corelib/plugin/qlibrary_p.h b/src/corelib/plugin/qlibrary_p.h
index 8c722a3283..e3bbbe104b 100644
--- a/src/corelib/plugin/qlibrary_p.h
+++ b/src/corelib/plugin/qlibrary_p.h
@@ -38,6 +38,9 @@ struct QLibraryScanResult
{
qsizetype pos;
qsizetype length;
+#if defined(Q_OF_MACH_O)
+ bool isEncrypted = false;
+#endif
};
class QLibraryStore;
diff --git a/src/corelib/plugin/qmachparser.cpp b/src/corelib/plugin/qmachparser.cpp
index 979ce2c7de..7a82b84cb3 100644
--- a/src/corelib/plugin/qmachparser.cpp
+++ b/src/corelib/plugin/qmachparser.cpp
@@ -56,6 +56,23 @@ static QLibraryScanResult notfound(const QString &reason, QString *errorString)
return {};
}
+static bool isEncrypted(const my_mach_header *header)
+{
+ auto commandCursor = uintptr_t(header) + sizeof(my_mach_header);
+ for (uint32_t i = 0; i < header->ncmds; ++i) {
+ load_command *loadCommand = reinterpret_cast<load_command *>(commandCursor);
+ if (loadCommand->cmd == LC_ENCRYPTION_INFO || loadCommand->cmd == LC_ENCRYPTION_INFO_64) {
+ // The layout of encryption_info_command and encryption_info_command_64 is the same
+ // up until and including cryptid, so we can treat it as encryption_info_command.
+ auto encryptionInfoCommand = reinterpret_cast<encryption_info_command*>(loadCommand);
+ return encryptionInfoCommand->cryptid != 0;
+ }
+ commandCursor += loadCommand->cmdsize;
+ }
+
+ return false;
+}
+
QLibraryScanResult QMachOParser::parse(const char *m_s, ulong fdlen, QString *errorString)
{
// The minimum size of a Mach-O binary we're interested in.
@@ -166,8 +183,12 @@ QLibraryScanResult QMachOParser::parse(const char *m_s, ulong fdlen, QString *e
if (sect[j].size < sizeof(QPluginMetaData::MagicHeader))
return notfound(QLibrary::tr(".qtmetadata section is too small"), errorString);
+ const bool binaryIsEncrypted = isEncrypted(header);
qsizetype pos = reinterpret_cast<const char *>(header) - m_s + sect[j].offset;
- if (IncludeValidityChecks) {
+
+ // We can not read the section data of encrypted libraries until they
+ // have been dlopened(), so skip validity check if that's the case.
+ if (IncludeValidityChecks && !binaryIsEncrypted) {
QByteArrayView expectedMagic = QByteArrayView::fromArray(QPluginMetaData::MagicString);
QByteArrayView actualMagic = QByteArrayView(m_s + pos, expectedMagic.size());
if (expectedMagic != actualMagic)
@@ -175,7 +196,7 @@ QLibraryScanResult QMachOParser::parse(const char *m_s, ulong fdlen, QString *e
}
pos += sizeof(QPluginMetaData::MagicString);
- return { pos, qsizetype(sect[j].size - sizeof(QPluginMetaData::MagicString)) };
+ return { pos, qsizetype(sect[j].size - sizeof(QPluginMetaData::MagicString)), binaryIsEncrypted };
}
}