1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Copyright (C) 2018 Intel Corporation. |
5 | ** Contact: https://www.qt.io/licensing/ |
6 | ** |
7 | ** This file is part of the test suite of the Qt Toolkit. |
8 | ** |
9 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
10 | ** Commercial License Usage |
11 | ** Licensees holding valid commercial Qt licenses may use this file in |
12 | ** accordance with the commercial license agreement provided with the |
13 | ** Software or, alternatively, in accordance with the terms contained in |
14 | ** a written agreement between you and The Qt Company. For licensing terms |
15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
16 | ** information use the contact form at https://www.qt.io/contact-us. |
17 | ** |
18 | ** GNU General Public License Usage |
19 | ** Alternatively, this file may be used under the terms of the GNU |
20 | ** General Public License version 3 as published by the Free Software |
21 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
22 | ** included in the packaging of this file. Please review the following |
23 | ** information to ensure the GNU General Public License requirements will |
24 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
25 | ** |
26 | ** $QT_END_LICENSE$ |
27 | ** |
28 | ****************************************************************************/ |
29 | #include <QtTest/QtTest> |
30 | |
31 | #include <QCoreApplication> |
32 | #include <QDebug> |
33 | #include <QDir> |
34 | #include <qplugin.h> |
35 | #include <QPluginLoader> |
36 | |
37 | #include <private/qplugin_p.h> |
38 | |
39 | class tst_QPlugin : public QObject |
40 | { |
41 | Q_OBJECT |
42 | |
43 | QDir dir; |
44 | |
45 | public: |
46 | tst_QPlugin(); |
47 | |
48 | private slots: |
49 | void initTestCase(); |
50 | void loadDebugPlugin(); |
51 | void loadReleasePlugin(); |
52 | void scanInvalidPlugin_data(); |
53 | void scanInvalidPlugin(); |
54 | }; |
55 | |
56 | tst_QPlugin::tst_QPlugin() |
57 | : dir(QFINDTESTDATA("plugins" )) |
58 | { |
59 | } |
60 | |
61 | void tst_QPlugin::initTestCase() |
62 | { |
63 | QVERIFY2(dir.exists(), |
64 | qPrintable(QString::fromLatin1("Cannot find the 'plugins' directory starting from '%1'" ). |
65 | arg(QDir::toNativeSeparators(QDir::currentPath())))); |
66 | } |
67 | |
68 | void tst_QPlugin::loadDebugPlugin() |
69 | { |
70 | const auto fileNames = dir.entryList(nameFilters: QStringList() << "*debug*" , filters: QDir::Files); |
71 | if (fileNames.isEmpty()) |
72 | QSKIP("No debug plugins found - skipping test" ); |
73 | |
74 | for (const QString &fileName : fileNames) { |
75 | if (!QLibrary::isLibrary(fileName)) |
76 | continue; |
77 | QPluginLoader loader(dir.filePath(fileName)); |
78 | #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) |
79 | // we can always load a plugin on unix |
80 | QVERIFY(loader.load()); |
81 | QObject *object = loader.instance(); |
82 | QVERIFY(object != 0); |
83 | #else |
84 | // loading a plugin is dependent on which lib we are running against |
85 | # if defined(QT_NO_DEBUG) |
86 | // release build, we cannot load debug plugins |
87 | QVERIFY(!loader.load()); |
88 | # else |
89 | // debug build, we can load debug plugins |
90 | QVERIFY(loader.load()); |
91 | QObject *object = loader.instance(); |
92 | QVERIFY(object != 0); |
93 | # endif |
94 | #endif |
95 | } |
96 | } |
97 | |
98 | void tst_QPlugin::loadReleasePlugin() |
99 | { |
100 | const auto fileNames = dir.entryList(nameFilters: QStringList() << "*release*" , filters: QDir::Files); |
101 | if (fileNames.isEmpty()) |
102 | QSKIP("No release plugins found - skipping test" ); |
103 | |
104 | for (const QString &fileName : fileNames) { |
105 | if (!QLibrary::isLibrary(fileName)) |
106 | continue; |
107 | QPluginLoader loader(dir.filePath(fileName)); |
108 | #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) |
109 | // we can always load a plugin on unix |
110 | QVERIFY(loader.load()); |
111 | QObject *object = loader.instance(); |
112 | QVERIFY(object != 0); |
113 | #else |
114 | // loading a plugin is dependent on which lib we are running against |
115 | # if defined(QT_NO_DEBUG) |
116 | // release build, we can load debug plugins |
117 | QVERIFY(loader.load()); |
118 | QObject *object = loader.instance(); |
119 | QVERIFY(object != 0); |
120 | # else |
121 | // debug build, we cannot load debug plugins |
122 | QVERIFY(!loader.load()); |
123 | # endif |
124 | #endif |
125 | } |
126 | } |
127 | |
128 | void tst_QPlugin::scanInvalidPlugin_data() |
129 | { |
130 | QTest::addColumn<QByteArray>(name: "metadata" ); |
131 | QTest::addColumn<bool>(name: "loads" ); |
132 | QTest::addColumn<QString>(name: "errMsg" ); |
133 | |
134 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) |
135 | // Binary JSON metadata |
136 | QByteArray prefix = "QTMETADATA " ; |
137 | |
138 | { |
139 | QJsonObject obj; |
140 | obj.insert(key: "IID" , value: "org.qt-project.tst_qplugin" ); |
141 | obj.insert(key: "className" , value: "tst" ); |
142 | obj.insert(key: "version" , value: int(QT_VERSION)); |
143 | #ifdef QT_NO_DEBUG |
144 | obj.insert("debug" , false); |
145 | #else |
146 | obj.insert(key: "debug" , value: true); |
147 | #endif |
148 | obj.insert(key: "MetaData" , value: QJsonObject()); |
149 | QTest::newRow(dataTag: "json-control" ) << (prefix + QJsonDocument(obj).toBinaryData()) << true << "" ; |
150 | } |
151 | |
152 | QTest::newRow(dataTag: "json-zeroes" ) << prefix << false << " " ; |
153 | |
154 | prefix += "qbjs" ; |
155 | QTest::newRow(dataTag: "bad-json-version0" ) << prefix << false << " " ; |
156 | QTest::newRow(dataTag: "bad-json-version2" ) << (prefix + QByteArray("\2\0\0\0" , 4)) << false << " " ; |
157 | |
158 | // valid qbjs version 1 |
159 | prefix += QByteArray("\1\0\0\0" ); |
160 | |
161 | // too large for the file (100 MB) |
162 | QTest::newRow(dataTag: "bad-json-size-large1" ) << (prefix + QByteArray("\0\0\x40\x06" )) << false << " " ; |
163 | |
164 | // too large for binary JSON (512 MB) |
165 | QTest::newRow(dataTag: "bad-json-size-large2" ) << (prefix + QByteArray("\0\0\0\x20" )) << false << " " ; |
166 | |
167 | // could overflow |
168 | QTest::newRow(dataTag: "bad-json-size-large3" ) << (prefix + "\xff\xff\xff\x7f" ) << false << " " ; |
169 | #endif |
170 | |
171 | // CBOR metadata |
172 | QByteArray cprefix = "QTMETADATA !1234" ; |
173 | cprefix[12] = 0; // current version |
174 | cprefix[13] = QT_VERSION_MAJOR; |
175 | cprefix[14] = QT_VERSION_MINOR; |
176 | cprefix[15] = qPluginArchRequirements(); |
177 | |
178 | QByteArray cborValid = [] { |
179 | QCborMap m; |
180 | m.insert(key: int(QtPluginMetaDataKeys::IID), value_: QLatin1String("org.qt-project.tst_qplugin" )); |
181 | m.insert(key: int(QtPluginMetaDataKeys::ClassName), value_: QLatin1String("tst" )); |
182 | m.insert(key: int(QtPluginMetaDataKeys::MetaData), value_: QCborMap()); |
183 | return QCborValue(m).toCbor(); |
184 | }(); |
185 | QTest::newRow(dataTag: "cbor-control" ) << (cprefix + cborValid) << true << "" ; |
186 | |
187 | cprefix[12] = 1; |
188 | QTest::newRow(dataTag: "cbor-major-too-new" ) << (cprefix + cborValid) << false |
189 | << " Invalid metadata version" ; |
190 | |
191 | cprefix[12] = 0; |
192 | cprefix[13] = QT_VERSION_MAJOR + 1; |
193 | QTest::newRow(dataTag: "cbor-major-too-new" ) << (cprefix + cborValid) << false << "" ; |
194 | |
195 | cprefix[13] = QT_VERSION_MAJOR - 1; |
196 | QTest::newRow(dataTag: "cbor-major-too-old" ) << (cprefix + cborValid) << false << "" ; |
197 | |
198 | cprefix[13] = QT_VERSION_MAJOR; |
199 | cprefix[14] = QT_VERSION_MINOR + 1; |
200 | QTest::newRow(dataTag: "cbor-minor-too-new" ) << (cprefix + cborValid) << false << "" ; |
201 | |
202 | QTest::newRow(dataTag: "cbor-invalid" ) << (cprefix + "\xff" ) << false |
203 | << " Metadata parsing error: Invalid CBOR stream: unexpected 'break' byte" ; |
204 | QTest::newRow(dataTag: "cbor-not-map1" ) << (cprefix + "\x01" ) << false |
205 | << " Unexpected metadata contents" ; |
206 | QTest::newRow(dataTag: "cbor-not-map2" ) << (cprefix + "\x81\x01" ) << false |
207 | << " Unexpected metadata contents" ; |
208 | } |
209 | |
210 | static const char invalidPluginSignature[] = "qplugin testfile" ; |
211 | static qsizetype locateMetadata(const uchar *data, qsizetype len) |
212 | { |
213 | const uchar *dataend = data + len - strlen(s: invalidPluginSignature); |
214 | |
215 | for (const uchar *ptr = data; ptr < dataend; ++ptr) { |
216 | if (*ptr != invalidPluginSignature[0]) |
217 | continue; |
218 | |
219 | int r = memcmp(s1: ptr, s2: invalidPluginSignature, n: strlen(s: invalidPluginSignature)); |
220 | if (r) |
221 | continue; |
222 | |
223 | return ptr - data; |
224 | } |
225 | |
226 | return -1; |
227 | } |
228 | |
229 | void tst_QPlugin::scanInvalidPlugin() |
230 | { |
231 | #if defined(Q_OS_MACOS) && defined(Q_PROCESSOR_ARM) |
232 | QSKIP("This test crashes on ARM macOS" ); |
233 | #endif |
234 | const auto fileNames = dir.entryList(nameFilters: {"*invalid*" }, filters: QDir::Files); |
235 | QString invalidPluginName; |
236 | if (fileNames.isEmpty()) |
237 | QSKIP("No invalid plugin found - skipping test" ); |
238 | else |
239 | invalidPluginName = dir.absoluteFilePath(fileName: fileNames.first()); |
240 | |
241 | |
242 | // copy the file |
243 | QFileInfo fn(invalidPluginName); |
244 | QTemporaryDir tmpdir; |
245 | QVERIFY(tmpdir.isValid()); |
246 | |
247 | QString newName = tmpdir.path() + '/' + fn.fileName(); |
248 | QVERIFY(QFile::copy(invalidPluginName, newName)); |
249 | |
250 | { |
251 | QFile f(newName); |
252 | QVERIFY(f.open(QIODevice::ReadWrite | QIODevice::Unbuffered)); |
253 | QVERIFY(f.size() > qint64(strlen(invalidPluginSignature))); |
254 | uchar *data = f.map(offset: 0, size: f.size()); |
255 | QVERIFY(data); |
256 | |
257 | static const qsizetype offset = locateMetadata(data, len: f.size()); |
258 | QVERIFY(offset > 0); |
259 | |
260 | QFETCH(QByteArray, metadata); |
261 | |
262 | // sanity check |
263 | QVERIFY(metadata.size() < 512); |
264 | |
265 | // replace the data |
266 | memcpy(dest: data + offset, src: metadata.constData(), n: metadata.size()); |
267 | memset(s: data + offset + metadata.size(), c: 0, n: 512 - metadata.size()); |
268 | } |
269 | |
270 | // now try to load this |
271 | QFETCH(bool, loads); |
272 | QFETCH(QString, errMsg); |
273 | if (!errMsg.isEmpty()) |
274 | QTest::ignoreMessage(type: QtWarningMsg, |
275 | message: "Found invalid metadata in lib " + QFile::encodeName(fileName: newName) + |
276 | ":" + errMsg.toUtf8()); |
277 | QPluginLoader loader(newName); |
278 | QCOMPARE(loader.load(), loads); |
279 | if (loads) |
280 | loader.unload(); |
281 | } |
282 | |
283 | QTEST_MAIN(tst_QPlugin) |
284 | #include "tst_qplugin.moc" |
285 | |