Files
fcitx5-m3ui/patches/fcitx5-icon-cache.patch
T

298 lines
10 KiB
Diff

--- a/src/lib/fcitx/icontheme.cpp
+++ b/src/lib/fcitx/icontheme.cpp
@@ -7,9 +7,11 @@
#include "icontheme.h"
#include <fcntl.h>
#include <sys/stat.h>
+#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
+#include <cstring>
#include <filesystem>
#include <fstream>
#include <functional>
@@ -39,10 +41,6 @@
#include "config.h" // IWYU pragma: keep
#include "misc_p.h"
-#ifdef HAVE_SYS_MMAN_H
-#include <sys/mman.h>
-#endif
-
namespace {
// helper type for the visitor
@@ -194,6 +192,9 @@
FCITX_DEFINE_READ_ONLY_PROPERTY_PRIVATE(IconThemeDirectory, int, threshold);
static uint32_t iconNameHash(const char *p) {
+ if (!*p) {
+ return 0;
+ }
uint32_t h = static_cast<signed char>(*p);
for (p += 1; *p != '\0'; p++) {
h = (h << 5) - h + *p;
@@ -207,7 +208,8 @@
#ifdef _WIN32
FCITX_UNUSED(filename);
#else
- auto fd = StandardPaths::openPath(filename);
+ // Never block opening a FIFO masquerading as a cache.
+ auto fd = StandardPaths::openPath(filename, O_RDONLY | O_NONBLOCK);
if (!fd.isValid()) {
return;
}
@@ -225,35 +227,46 @@
}
struct stat st;
- if (fstat(fd.fd(), &st) < 0) {
+ constexpr size_t maxCacheSize = 16 * 1024 * 1024;
+ if (fstat(fd.fd(), &st) < 0 || !S_ISREG(st.st_mode) ||
+ st.st_size < 12 ||
+ static_cast<uint64_t>(st.st_size) > maxCacheSize) {
return;
}
- memory_ = static_cast<uint8_t *>(
- mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd.fd(), 0));
- if (memory_ == MAP_FAILED) {
- // Failed to mmap is not critical here.
+ // Own a bounded snapshot: a concurrent in-place truncation must not
+ // turn a cache lookup into SIGBUS. Invalid snapshots are discarded.
+ memory_.resize(st.st_size);
+ size_t done = 0;
+ while (done < memory_.size()) {
+ auto n = fs::safeRead(fd.fd(), memory_.data() + done,
+ memory_.size() - done);
+ if (n <= 0) {
+ memory_.clear();
+ return;
+ }
+ done += n;
+ }
+
+ isValid_ = true;
+ if (readWord(0) != 1 || readWord(2) != 0) { // version major minor
+ isValid_ = false;
return;
}
- size_ = st.st_size;
-
- if (readWord(0) != 1 || readWord(2) != 0) { // version major minor
- return;
- }
-
- isValid_ = true;
// Check that all the directories are older than the cache
uint32_t dirListOffset = readDoubleWord(8);
uint32_t dirListLen = readDoubleWord(dirListOffset);
// We assume it won't be so long just in case we hit a corruptted file.
- if (dirListLen > 4096) {
+ if (!isValid_ || dirListLen > 4096 ||
+ !checkRange(static_cast<size_t>(dirListOffset) + 4,
+ static_cast<size_t>(dirListLen) * 4)) {
isValid_ = false;
return;
}
for (uint32_t i = 0; i < dirListLen; ++i) {
uint32_t offset = readDoubleWord(dirListOffset + 4 + (4 * i));
- if (!isValid_ || offset >= size_) {
+ if (!isValid_) {
isValid_ = false;
return;
}
@@ -263,7 +276,8 @@
return;
}
std::filesystem::path subdirName(dir);
- if (subdirName.is_absolute()) {
+ if (subdirName.is_absolute() ||
+ std::ranges::find(subdirName, "..") != subdirName.end()) {
isValid_ = false;
return;
}
@@ -277,65 +291,52 @@
}
IconThemeCache() = default;
- IconThemeCache(IconThemeCache &&other) noexcept
- : isValid_(other.isValid_), memory_(other.memory_), size_(other.size_) {
- other.isValid_ = false;
- other.memory_ = nullptr;
- other.size_ = 0;
- }
-
- IconThemeCache &operator=(IconThemeCache other) {
- std::swap(other.isValid_, isValid_);
- std::swap(other.size_, size_);
- std::swap(other.memory_, memory_);
- return *this;
- }
+ IconThemeCache(IconThemeCache &&other) noexcept = default;
+ IconThemeCache &operator=(IconThemeCache &&other) noexcept = default;
bool isValid() const { return isValid_; }
- ~IconThemeCache() {
-#ifndef _WIN32
- if (memory_) {
- munmap(memory_, size_);
- }
-#endif
- }
-
- uint16_t readWord(uint32_t offset) const {
- if (offset + 2 > size_ || (offset % 2)) {
+ bool checkRange(size_t offset, size_t length) const {
+ if (offset > memory_.size() || length > memory_.size() - offset) {
+ isValid_ = false;
+ return false;
+ }
+ return true;
+ }
+
+ uint16_t readWord(size_t offset) const {
+ if (!checkRange(offset, 2) || (offset % 2)) {
isValid_ = false;
return 0;
}
return memory_[offset + 1] | memory_[offset] << 8;
}
- uint32_t readDoubleWord(unsigned int offset) const {
- if (offset + 4 > size_ || (offset % 4)) {
+ uint32_t readDoubleWord(size_t offset) const {
+ if (!checkRange(offset, 4) || (offset % 4)) {
isValid_ = false;
return 0;
}
- return memory_[offset + 3] | memory_[offset + 2] << 8 |
- memory_[offset + 1] << 16 | memory_[offset] << 24;
- }
-
- char *checkString(uint32_t offset) const {
- // assume string length is less than 1k.
- for (uint32_t i = 0; i < 1024; i++) {
- if (offset + i >= size_) {
- return nullptr;
- }
- auto c = memory_[offset + i];
- if (c == '\0') {
- break;
- }
- }
- return reinterpret_cast<char *>(memory_ + offset);
+ return uint32_t(memory_[offset + 3]) | uint32_t(memory_[offset + 2]) << 8 |
+ uint32_t(memory_[offset + 1]) << 16 | uint32_t(memory_[offset]) << 24;
+ }
+
+ const char *checkString(size_t offset) const {
+ if (!checkRange(offset, 1)) {
+ return nullptr;
+ }
+ const auto length = std::min<size_t>(1024, memory_.size() - offset);
+ auto *str = reinterpret_cast<const char *>(memory_.data() + offset);
+ if (!std::memchr(str, '\0', length)) {
+ isValid_ = false;
+ return nullptr;
+ }
+ return str;
}
std::unordered_set<std::string> lookup(const std::string &name) const;
mutable bool isValid_ = false;
- uint8_t *memory_ = nullptr;
- size_t size_ = 0;
+ std::vector<uint8_t> memory_;
};
std::unordered_set<std::string>
@@ -343,27 +344,43 @@
std::unordered_set<std::string> ret;
auto hash = iconNameHash(name.c_str());
- uint32_t hashOffset = readDoubleWord(4);
+ if (!isValid_) {
+ return ret;
+ }
+ size_t hashOffset = readDoubleWord(4);
uint32_t hashBucketCount = readDoubleWord(hashOffset);
- if (!isValid_ || hashBucketCount == 0) {
+ if (!isValid_ || hashBucketCount == 0 ||
+ hashBucketCount > memory_.size() / 4 ||
+ !checkRange(hashOffset + 4, static_cast<size_t>(hashBucketCount) * 4)) {
isValid_ = false;
return ret;
}
- uint32_t bucketIndex = hash % hashBucketCount;
- uint32_t bucketOffset = readDoubleWord(hashOffset + 4 + (bucketIndex * 4));
- while (bucketOffset > 0 && bucketOffset <= size_ - 12) {
+ size_t bucketIndex = hash % hashBucketCount;
+ size_t bucketOffset = readDoubleWord(hashOffset + 4 + (bucketIndex * 4));
+ std::unordered_set<size_t> visited;
+ while (bucketOffset != 0 && bucketOffset != UINT32_MAX) {
+ // A cache is only an accelerator. Bound even acyclic hostile chains.
+ if (!isValid_ || !checkRange(bucketOffset, 12) ||
+ visited.size() >= 4096 || !visited.insert(bucketOffset).second) {
+ isValid_ = false;
+ return {};
+ }
uint32_t nameOff = readDoubleWord(bucketOffset + 4);
auto *namePtr = checkString(nameOff);
- if (nameOff < size_ && namePtr && name == namePtr) {
- uint32_t dirListOffset = readDoubleWord(8);
+ if (!isValid_ || !namePtr) {
+ return {};
+ }
+ if (name == namePtr) {
+ size_t dirListOffset = readDoubleWord(8);
uint32_t dirListLen = readDoubleWord(dirListOffset);
- uint32_t listOffset = readDoubleWord(bucketOffset + 8);
+ size_t listOffset = readDoubleWord(bucketOffset + 8);
uint32_t listLen = readDoubleWord(listOffset);
- if (!isValid_ || listOffset + 4 + 8 * listLen > size_) {
+ if (!isValid_ || listLen > 4096 ||
+ !checkRange(listOffset + 4, static_cast<size_t>(listLen) * 8)) {
isValid_ = false;
return ret;
}
@@ -371,11 +388,11 @@
ret.reserve(listLen);
for (uint32_t j = 0; j < listLen && isValid_; ++j) {
uint32_t dirIndex = readWord(listOffset + 4 + (8 * j));
- uint32_t o = readDoubleWord(dirListOffset + 4 + (dirIndex * 4));
- if (!isValid_ || dirIndex >= dirListLen || o >= size_) {
+ if (!isValid_ || dirIndex >= dirListLen) {
isValid_ = false;
return ret;
}
+ uint32_t o = readDoubleWord(dirListOffset + 4 + (dirIndex * 4));
if (auto *str = checkString(o)) {
ret.emplace(str);
} else {
@@ -565,7 +582,7 @@
std::unordered_set<std::string> dirFilter;
if (baseDir.second.isValid()) {
dirFilter = baseDir.second.lookup(iconname);
- hasCache = true;
+ hasCache = baseDir.second.isValid();
}
for (const auto &directory : directories_) {
if ((hasCache && !dirFilter.contains(directory.path())) ||
@@ -600,7 +617,7 @@
std::unordered_set<std::string> dirFilter;
if (baseDir.second.isValid()) {
dirFilter = baseDir.second.lookup(iconname);
- hasCache = true;
+ hasCache = baseDir.second.isValid();
}
auto checkDirectoryWithSize =