reimplement plugin verification via system provided ELF structures

this is likely unsafe to use on multi-library hosts but the option to
disable the verification is there

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2020-12-13 13:35:58 +00:00
parent 6996092330
commit 3deb8da473
3 changed files with 41 additions and 169 deletions

View file

@ -24,8 +24,8 @@ All standard requirements besides C++11 compatible runtime and compiler
should be checked for during build if they are newer than POSIX.1c
(https://en.wikipedia.org/wiki/POSIX). Read any documentation that may be
relevant to the changes you make such as manual pages for Linux
(https://linux.die.net/man/), FreeBSD (https://www.freebsd.org/cgi/man.cgi)
and OpenBSD (https://man.openbsd.org/).
(https://linux.die.net/man/), FreeBSD (https://www.freebsd.org/cgi/man.cgi),
OpenBSD (https://man.openbsd.org/) and Solaris (https://docs.oracle.com/en/).
## Tests
You can import tests and adjust them as needed from stock Qt4 copy

View file

@ -54,6 +54,17 @@
#include <errno.h>
#ifndef QT_NO_PLUGIN_CHECK
# include <elf.h>
# if QT_POINTER_SIZE == 8
# define QT_ELF_EHDR_TYPE Elf64_Ehdr
# define QT_ELF_SHDR_TYPE Elf64_Shdr
# else
# define QT_ELF_EHDR_TYPE Elf32_Ehdr
# define QT_ELF_SHDR_TYPE Elf32_Shdr
# endif
#endif
QT_BEGIN_NAMESPACE
Q_GLOBAL_STATIC(QMutex, qt_library_mutex)
@ -75,11 +86,10 @@ Q_GLOBAL_STATIC(QMutex, qt_library_mutex)
system-specific library locations (e.g. \c LD_LIBRARY_PATH on
Unix), unless the file name has an absolute path. If the file
cannot be found, QLibrary tries the name with different
platform-specific file suffixes, like ".so" on Unix, ".dylib" on
the Mac, or ".dll" on Windows. This makes it possible
to specify shared libraries that are only identified by their
basename (i.e. without their suffix), so the same code will work
on different operating systems.
platform-specific file suffixes, like ".so" on Unix. This makes
it possible to specify shared libraries that are only identified
by their basename (i.e. without their suffix), so the same code
will work on different operating systems.
The most important functions are load() to dynamically load the
library file, isLoaded() to check whether loading was successful,
@ -138,157 +148,10 @@ Q_GLOBAL_STATIC(QMutex, qt_library_mutex)
\sa loadHints
*/
#ifndef QT_NO_PLUGIN_CHECK
struct qt_token_info
{
qt_token_info(const char *f, const ulong fc)
: fields(f), field_count(fc), results(fc), lengths(fc)
{
results.fill(0);
lengths.fill(0);
}
const char *fields;
const ulong field_count;
QVector<const char *> results;
QVector<ulong> lengths;
};
/*
return values:
1 parse ok
0 eos
-1 parse error
*/
static int qt_tokenize(const char *s, ulong s_len, ulong *advance,
qt_token_info &token_info)
{
if (!s)
return -1;
ulong pos = 0, field = 0, fieldlen = 0;
char current;
int ret = -1;
*advance = 0;
for (;;) {
current = s[pos];
// next char
++pos;
++fieldlen;
++*advance;
if (! current || pos == s_len + 1) {
// save result
token_info.results[(int)field] = s;
token_info.lengths[(int)field] = fieldlen - 1;
// end of string
ret = 0;
break;
}
if (current == token_info.fields[field]) {
// save result
token_info.results[(int)field] = s;
token_info.lengths[(int)field] = fieldlen - 1;
// end of field
fieldlen = 0;
++field;
if (field == token_info.field_count - 1) {
// parse ok
ret = 1;
}
if (field == token_info.field_count) {
// done parsing
break;
}
// reset string and its length
s = s + pos;
s_len -= pos;
pos = 0;
}
}
return ret;
}
/*
returns true if the string s was correctly parsed, false otherwise.
*/
static bool qt_parse_pattern(const char *s, uint *version)
{
bool ret = true;
qt_token_info pinfo("=\n", 2);
int parse;
ulong at = 0, advance, parselen = qstrlen(s);
do {
parse = qt_tokenize(s + at, parselen, &advance, pinfo);
if (parse == -1) {
ret = false;
break;
}
at += advance;
parselen -= advance;
if (qstrncmp("version", pinfo.results[0], pinfo.lengths[0]) == 0) {
QByteArray qv(pinfo.results[1], pinfo.lengths[1]);
bool ok;
*version = qv.toUInt(&ok, 0);
}
} while (parse == 1 && parselen > 0);
return ret;
}
static long qt_find_pattern(const char *s, ulong s_len)
{
/*
we search from the end of the file because on the supported
systems, the read-only data/text segments are placed at the end
of the file. HOWEVER, when building with debugging enabled, all
the debug symbols are placed AFTER the data/text segments.
what does this mean? when building in release mode, the search
is fast because the data we are looking for is at the end of the
file... when building in debug mode, the search is slower
because we have to skip over all the debugging symbols first
*/
static const char pattern[] = "pattern=KT_PLUGIN_VERIFICATION_DATA";
static const ulong p_len = qstrlen(pattern);
if (!s || p_len > s_len)
return -1;
ulong i, hs = 0, hp = 0, delta = s_len - p_len;
for (i = 0; i < p_len; ++i) {
hs += s[delta + i];
hp += pattern[i];
}
i = delta;
for (;;) {
if (hs == hp && qstrncmp(s + i, pattern, p_len) == 0)
return i;
if (i == 0)
break;
--i;
hs -= s[i + p_len];
hs += s[i];
}
return -1;
}
/*
This opens the specified library, mmaps it into memory, and searches
for the KT_PLUGIN_VERIFICATION_DATA. The advantage of this approach is that
for the plugin seciton. The advantage of this approach is that
we can get the verification data without have to actually load the library.
This lets us detect mismatches more safely.
@ -308,29 +171,41 @@ static bool qt_unix_query(const QString &library, uint *version, QLibraryPrivate
return false;
}
ulong fdlen = file.size();
const char *filedata = reinterpret_cast<char*>(file.map(0, fdlen));
const char *filedata = reinterpret_cast<char*>(file.map(0, file.size()));
if (filedata == 0) {
// try reading the data into memory instead
const QByteArray data = file.readAll();
filedata = data.constData();
fdlen = data.size();
}
/*
ELF binaries build with GNU or Clang have .ktplugin sections.
*/
const long pos = qt_find_pattern(filedata, fdlen);
bool ret = false;
if (pos >= 0)
ret = qt_parse_pattern(filedata + pos, version);
QT_ELF_EHDR_TYPE *ehdr = (QT_ELF_EHDR_TYPE*)(filedata);
QT_ELF_SHDR_TYPE *shdr = (QT_ELF_SHDR_TYPE*)(filedata + ehdr->e_shoff);
QT_ELF_SHDR_TYPE *sh_strtab = &shdr[ehdr->e_shstrndx];
const char *const sh_strtab_p = filedata + sh_strtab->sh_offset;
for (int i = 0; i < ehdr->e_shnum; ++i) {
const char* sectioname = sh_strtab_p + shdr[i].sh_name;
if (qstrcmp(sectioname, ".ktplugin") == 0) {
ret = true;
}
/*
compatiblity between releases is not guratneed thus no version matching is done
*/
*version = QT_VERSION;
}
if (!ret)
lib->errorString = QLibrary::tr("Plugin verification data mismatch in '%1'").arg(library);
file.close();
return ret;
}
#endif // QT_NO_PLUGIN_CHECK
typedef QMap<QString, QLibraryPrivate*> LibraryMap;

View file

@ -47,9 +47,10 @@ typedef QObject *(*QtPluginInstanceFunction)();
#define Q_EXPORT_PLUGIN(PLUGIN) \
Q_EXPORT_PLUGIN2(PLUGIN, PLUGIN)
#if defined(__ELF__)
#if !defined(QT_NO_PLUGIN_CHECK)
# define Q_PLUGIN_VERIFICATION_SECTION \
__attribute__ ((section (".ktplugin"))) __attribute__((used))
__attribute__ ((section (".ktplugin"))) __attribute__((used)) \
static const char kt_plugin_verification_data[] = QT_VERSION_HEX_STR;
#else
# define Q_PLUGIN_VERIFICATION_SECTION
#endif
@ -58,11 +59,7 @@ typedef QObject *(*QtPluginInstanceFunction)();
// qlibrary.cpp as well. changing the pattern will break all
// backwards compatibility as well (no old plugins will be loaded).
#define Q_EXPORT_PLUGIN2(PLUGIN, PLUGINCLASS) \
Q_PLUGIN_VERIFICATION_SECTION static const char kt_plugin_verification_data[] = \
"pattern=KT_PLUGIN_VERIFICATION_DATA\n" \
"version=" QT_VERSION_HEX_STR "\n"; \
Q_EXTERN_C Q_DECL_EXPORT const char * kt_plugin_query_verification_data() \
{ return kt_plugin_verification_data; } \
Q_PLUGIN_VERIFICATION_SECTION \
Q_EXTERN_C Q_DECL_EXPORT QT_PREPEND_NAMESPACE(QObject) * kt_plugin_instance() \
{ \
static QT_PREPEND_NAMESPACE(QPointer)<QT_PREPEND_NAMESPACE(QObject)> _instance; \