mirror of
https://bitbucket.org/smil3y/katie.git
synced 2025-02-23 18:32:55 +00:00
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:
parent
6996092330
commit
3deb8da473
3 changed files with 41 additions and 169 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; \
|
||||
|
|
Loading…
Add table
Reference in a new issue