diff options
Diffstat (limited to 'src/corelib/io/qresource.cpp')
-rw-r--r-- | src/corelib/io/qresource.cpp | 215 |
1 files changed, 127 insertions, 88 deletions
diff --git a/src/corelib/io/qresource.cpp b/src/corelib/io/qresource.cpp index 7eac497cea..df57d12efc 100644 --- a/src/corelib/io/qresource.cpp +++ b/src/corelib/io/qresource.cpp @@ -33,10 +33,13 @@ # include <zstd.h> #endif -#if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY) +#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY) # define QT_USE_MMAP # include <sys/mman.h> #endif +#ifdef Q_OS_WIN +# include <qt_windows.h> +#endif //#define DEBUG_RESOURCE_MATCH @@ -65,6 +68,7 @@ RCC_FEATURE_SYMBOL(Zstd) #undef RCC_FEATURE_SYMBOL +namespace { class QStringSplitter { public: @@ -82,7 +86,7 @@ public: inline QStringView next() { - int start = m_pos; + const qsizetype start = m_pos; while (m_pos < m_len && m_data[m_pos] != m_splitChar) ++m_pos; return QStringView(m_data + start, m_pos - start); @@ -130,7 +134,7 @@ public: return QResource::NoCompression; } const uchar *data(int node, qint64 *size) const; - quint64 lastModified(int node) const; + qint64 lastModified(int node) const; QStringList children(int node) const; virtual QString mappingRoot() const { return QString(); } bool mappingRootSubdir(const QString &path, QString *match = nullptr) const; @@ -159,16 +163,18 @@ static QString cleanPath(const QString &_path) path.remove(0, 1); return path; } +} // unnamed namespace Q_DECLARE_TYPEINFO(QResourceRoot, Q_RELOCATABLE_TYPE); typedef QList<QResourceRoot*> ResourceList; +namespace { struct QResourceGlobalData { QRecursiveMutex resourceMutex; ResourceList resourceList; - QStringList resourceSearchPaths; }; +} Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData) static inline QRecursiveMutex &resourceMutex() @@ -177,9 +183,6 @@ static inline QRecursiveMutex &resourceMutex() static inline ResourceList *resourceList() { return &resourceGlobalData->resourceList; } -static inline QStringList *resourceSearchPaths() -{ return &resourceGlobalData->resourceSearchPaths; } - /*! \class QResource \inmodule QtCore @@ -237,6 +240,19 @@ static inline QStringList *resourceSearchPaths() itself will be unmapped from memory when the last QResource that points to it is destroyed. + \section2 Corruption and Security + + The QResource class performs some checks on the file passed to determine + whether it is supported by the current version of Qt. Those tests are only + to check the file header does not request features (such as Zstandard + decompression) that have not been compiled in or that the file is not of a + future version of Qt. They do not confirm the validity of the entire file. + + QResource should not be used on files whose provenance cannot be trusted. + Applications should be designed to attempt to load only resource files + whose provenance is at least as trustworthy as that of the application + itself or its plugins. + \sa {The Qt Resource System}, QFile, QDir, QFileInfo */ @@ -273,11 +289,11 @@ public: QLocale locale; QString fileName, absoluteFilePath; QList<QResourceRoot *> related; - mutable qint64 size; - mutable quint64 lastModified; - mutable const uchar *data; + qint64 size; + qint64 lastModified; + const uchar *data; mutable QStringList children; - mutable quint8 compressionAlgo; + quint8 compressionAlgo; bool container; /* 2 or 6 padding bytes */ @@ -360,16 +376,10 @@ void QResourcePrivate::ensureInitialized() const if (path.startsWith(u'/')) { that->load(path.toString()); } else { - const auto locker = qt_scoped_lock(resourceMutex()); - QStringList searchPaths = *resourceSearchPaths(); - searchPaths << ""_L1; - for (int i = 0; i < searchPaths.size(); ++i) { - const QString searchPath(searchPaths.at(i) + u'/' + path); - if (that->load(searchPath)) { - that->absoluteFilePath = u':' + searchPath; - break; - } - } + // Should we search QDir::searchPath() before falling back to root ? + const QString searchPath(u'/' + path); + if (that->load(searchPath)) + that->absoluteFilePath = u':' + searchPath; } } @@ -763,7 +773,7 @@ inline QString QResourceRoot::name(int node) const ret.resize(name_length); QChar *strData = ret.data(); - qFromBigEndian<ushort>(names + name_offset, name_length, strData); + qFromBigEndian<char16_t>(names + name_offset, name_length, strData); return ret; } @@ -779,7 +789,7 @@ int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const if (!root.endsWith(u'/')) root += u'/'; if (path.size() >= root.size() && path.startsWith(root)) - path = path.mid(root.length() - 1); + path = path.mid(root.size() - 1); if (path.isEmpty()) path = u'/'; } @@ -925,14 +935,14 @@ const uchar *QResourceRoot::data(int node, qint64 *size) const return nullptr; } -quint64 QResourceRoot::lastModified(int node) const +qint64 QResourceRoot::lastModified(int node) const { if (node == -1 || version < 0x02) return 0; const int offset = findOffset(node) + 14; - return qFromBigEndian<quint64>(tree + offset); + return qFromBigEndian<qint64>(tree + offset); } QStringList QResourceRoot::children(int node) const @@ -1028,8 +1038,8 @@ Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tre return false; } +namespace { // run time resource creation - class QDynamicBufferResourceRoot : public QResourceRoot { QString root; @@ -1102,6 +1112,11 @@ public: class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot { +public: + static uchar *map_sys(QFile &file, qint64 base, qsizetype size); + static void unmap_sys(void *base, qsizetype size); + +private: QString fileName; // for mmap'ed files, this is what needs to be unmapped. uchar *unmapPointer; @@ -1112,22 +1127,17 @@ public: : QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0) { } ~QDynamicFileResourceRoot() { -#if defined(QT_USE_MMAP) - if (unmapPointer) { - munmap(reinterpret_cast<char *>(unmapPointer), unmapLength); - unmapPointer = nullptr; - unmapLength = 0; - } else -#endif - { + if (unmapPointer) + unmap_sys(unmapPointer, unmapLength); + else delete[] mappingBuffer(); - } } QString mappingFile() const { return fileName; } ResourceRootType type() const override { return Resource_File; } bool registerSelf(const QString &f); }; +} // unnamed namespace #ifndef MAP_FILE # define MAP_FILE 0 @@ -1136,49 +1146,69 @@ public: # define MAP_FAILED reinterpret_cast<void *>(-1) #endif -bool QDynamicFileResourceRoot::registerSelf(const QString &f) +void QDynamicFileResourceRoot::unmap_sys(void *base, qsizetype size) { - bool fromMM = false; - uchar *data = nullptr; - qsizetype data_len = 0; +#if defined(QT_USE_MMAP) + munmap(base, size); +#elif defined(Q_OS_WIN) + Q_UNUSED(size) + UnmapViewOfFile(reinterpret_cast<void *>(base)); +#endif +} +// Note: caller must ensure \a offset and \a size are acceptable to the OS. +uchar *QDynamicFileResourceRoot::map_sys(QFile &file, qint64 offset, qsizetype size) +{ + Q_ASSERT(file.isOpen()); + void *ptr = nullptr; + if (size < 0) + size = qMin(file.size() - offset, (std::numeric_limits<qsizetype>::max)()); + + // We don't use QFile::map() here because we want to dispose of the QFile object #if defined(QT_USE_MMAP) - int fd = QT_OPEN(QFile::encodeName(f), O_RDONLY); - if (fd >= 0) { - QT_STATBUF st; - if (!QT_FSTAT(fd, &st) && st.st_size <= std::numeric_limits<qsizetype>::max()) { - int protection = PROT_READ; // read-only memory - int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file - void *ptr = QT_MMAP(nullptr, st.st_size, // any address, whole file - protection, flags, - fd, 0); // from offset 0 of fd - if (ptr != MAP_FAILED) { - data = static_cast<uchar *>(ptr); - data_len = st.st_size; - fromMM = true; - } + int fd = file.handle(); + int protection = PROT_READ; // read-only memory + int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file + ptr = QT_MMAP(nullptr, size, protection, flags, fd, offset); + if (ptr == MAP_FAILED) + ptr = nullptr; +#elif defined(Q_OS_WIN) + int fd = file.handle(); + HANDLE fileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd)); + if (fileHandle != INVALID_HANDLE_VALUE) { + HANDLE mapHandle = CreateFileMapping(fileHandle, 0, PAGE_WRITECOPY, 0, 0, 0); + if (mapHandle) { + ptr = MapViewOfFile(mapHandle, FILE_MAP_COPY, DWORD(offset >> 32), DWORD(offset), size); + CloseHandle(mapHandle); } - QT_CLOSE(fd); } #endif // QT_USE_MMAP - if (!data) { - QFile file(f); + return static_cast<uchar *>(ptr); +} + +bool QDynamicFileResourceRoot::registerSelf(const QString &f) +{ + QFile file(f); + if (!file.open(QIODevice::ReadOnly)) + return false; + + qint64 data_len = file.size(); + if (data_len > std::numeric_limits<qsizetype>::max()) + return false; + + uchar *data = map_sys(file, 0, data_len); + bool fromMM = !!data; + + if (!fromMM) { bool ok = false; - if (file.open(QIODevice::ReadOnly)) { - qint64 fsize = file.size(); - if (fsize <= std::numeric_limits<qsizetype>::max()) { - data_len = file.size(); - data = new uchar[data_len]; - ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len)); - } - } + data = new uchar[data_len]; + ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len)); if (!ok) { delete[] data; data = nullptr; data_len = 0; return false; } - fromMM = false; } if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len)) { if (fromMM) { @@ -1346,6 +1376,7 @@ private: uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags); bool unmap(uchar *ptr); void uncompress() const; + void mapUncompressed(); qint64 offset; QResource resource; mutable QByteArray uncompressed; @@ -1488,14 +1519,14 @@ QString QResourceFileEngine::fileName(FileName file) const { Q_D(const QResourceFileEngine); if (file == BaseName) { - int slash = d->resource.fileName().lastIndexOf(u'/'); + const qsizetype slash = d->resource.fileName().lastIndexOf(u'/'); if (slash == -1) return d->resource.fileName(); return d->resource.fileName().mid(slash + 1); } else if (file == PathName || file == AbsolutePathName) { const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath() : d->resource.fileName(); - const int slash = path.lastIndexOf(u'/'); + const qsizetype slash = path.lastIndexOf(u'/'); if (slash == -1) return ":"_L1; else if (slash <= 1) @@ -1505,7 +1536,7 @@ QString QResourceFileEngine::fileName(FileName file) const } else if (file == CanonicalName || file == CanonicalPathName) { const QString absoluteFilePath = d->resource.absoluteFilePath(); if (file == CanonicalPathName) { - const int slash = absoluteFilePath.lastIndexOf(u'/'); + const qsizetype slash = absoluteFilePath.lastIndexOf(u'/'); if (slash != -1) return absoluteFilePath.left(slash); } @@ -1520,10 +1551,10 @@ uint QResourceFileEngine::ownerId(FileOwner) const return nobodyID; } -QDateTime QResourceFileEngine::fileTime(FileTime time) const +QDateTime QResourceFileEngine::fileTime(QFile::FileTime time) const { Q_D(const QResourceFileEngine); - if (time == ModificationTime) + if (time == QFile::FileModificationTime) return d->resource.lastModified(); return QDateTime(); } @@ -1531,18 +1562,11 @@ QDateTime QResourceFileEngine::fileTime(FileTime time) const /*! \internal */ -QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters, - const QStringList &filterNames) -{ - return new QResourceFileEngineIterator(filters, filterNames); -} - -/*! - \internal -*/ -QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList() +QAbstractFileEngine::IteratorUniquePtr +QResourceFileEngine::beginEntryList(const QString &path, QDir::Filters filters, + const QStringList &filterNames) { - return nullptr; + return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames); } bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) @@ -1569,21 +1593,27 @@ bool QResourceFileEngine::supportsExtension(Extension extension) const uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags) { Q_Q(QResourceFileEngine); - Q_UNUSED(flags); + Q_ASSERT_X(resource.compressionAlgorithm() == QResource::NoCompression + || !uncompressed.isNull(), "QFile::map()", + "open() should have uncompressed compressed resources"); qint64 max = resource.uncompressedSize(); qint64 end; if (offset < 0 || size <= 0 || !resource.isValid() || - add_overflow(offset, size, &end) || end > max) { + qAddOverflow(offset, size, &end) || end > max) { q->setError(QFile::UnspecifiedError, QString()); return nullptr; } - const uchar *address = resource.data(); - if (resource.compressionAlgorithm() != QResource::NoCompression) { - uncompress(); - if (uncompressed.isNull()) - return nullptr; + const uchar *address = reinterpret_cast<const uchar *>(uncompressed.constBegin()); + if (!uncompressed.isNull()) + return const_cast<uchar *>(address) + offset; + + // resource was not compressed + address = resource.data(); + if (flags & QFile::MapPrivateOption) { + // We need to provide read-write memory + mapUncompressed(); address = reinterpret_cast<const uchar *>(uncompressed.constData()); } @@ -1604,6 +1634,15 @@ void QResourceFileEnginePrivate::uncompress() const uncompressed = resource.uncompressedData(); } +void QResourceFileEnginePrivate::mapUncompressed() +{ + Q_ASSERT(resource.compressionAlgorithm() == QResource::NoCompression); + if (!uncompressed.isNull()) + return; // nothing to do + uncompressed = resource.uncompressedData(); + uncompressed.detach(); +} + #endif // !defined(QT_BOOTSTRAPPED) QT_END_NAMESPACE |