From 9e02d8817665c31c81296448cffa83dc53308c31 Mon Sep 17 00:00:00 2001 From: liujinchang Date: Mon, 7 Sep 2026 16:25:06 +0800 Subject: [PATCH] fix(dfileinfo): fix heap corruption caused by unlocked gfileinfo lifecycle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace direct unref-and-assign in queryInfoSync() and both async query callbacks with atomic CAS exchange plus delayed unref of the old GFileInfo, closing the double-unref / use-after-free window against concurrent readers - Also fixes the GFileInfo leak in queryInfoAsyncCallback/2 where the old pointer was overwritten without unref - Take a temporary reference on gfileinfo via g_atomic_pointer_get + g_object_ref (g_autoptr) at the entry of attributesBySelf / exists / attribute / hasAttribute / customAttribute, so readers survive concurrent refresh - The 3s low-priority delayed unref only covers the load-to-ref instruction gap; it requires the host to iterate the default GLib main context (Qt GUI hosts do) and briefly retains replaced GFileInfo on bulk refresh 修复(dfileinfo): 修复 gfileinfo 无锁生命周期导致的堆损坏 - queryInfoSync() 及两个异步查询回调中的直接 unref 加赋值改为原子 CAS 交换加旧值延迟 unref,关闭与并发读线程之间的 double-unref / use-after-free 窗口 - 同时修复 queryInfoAsyncCallback/2 直接赋值覆盖旧指针导致的 GFileInfo 泄漏 - attributesBySelf / exists / attribute / hasAttribute / customAttribute 入口通过 g_atomic_pointer_get 加 g_object_ref(g_autoptr)持有临时引用,保证并发刷新期间读者安全 - 3 秒低优先级延迟 unref 仅用于覆盖 load 到 ref 的指令间隙;依赖宿主迭代默认 GLib 主上下文(Qt GUI 宿主满足),批量刷新时会短暂驻留被替换的 GFileInfo Log: 修复排序线程并发刷新/读取文件属性时 gfileinfo 无锁替换导致的随机崩溃(堆损坏延迟爆发在 malloc),ASAN 压测原可复现 heap-use-after-free,修复后 60 秒满载并发压测零报错 Task: https://pms.uniontech.com/task-view-395127.html --- src/dfm-io/dfm-io/dfileinfo.cpp | 69 +++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 21 deletions(-) diff --git a/src/dfm-io/dfm-io/dfileinfo.cpp b/src/dfm-io/dfm-io/dfileinfo.cpp index 55f89ea6..f9d85c72 100644 --- a/src/dfm-io/dfm-io/dfileinfo.cpp +++ b/src/dfm-io/dfm-io/dfileinfo.cpp @@ -24,6 +24,32 @@ USING_IO_NAMESPACE * DFileInfoPrivate ***********************************************/ +static gboolean retireUnrefCb(gpointer data) +{ + g_object_unref(static_cast(data)); + return G_SOURCE_REMOVE; +} + +static GFileInfo *atomicLoadGFileInfo(GFileInfo *const *slot) +{ + return static_cast(g_atomic_pointer_get(reinterpret_cast(slot))); +} + +static GFileInfo *refGFileInfo(GFileInfo *info) +{ + return info ? static_cast(g_object_ref(info)) : nullptr; +} + +static void replaceGFileInfo(GFileInfo **slot, GFileInfo *value) +{ + GFileInfo *old = nullptr; + do { + old = atomicLoadGFileInfo(slot); + } while (!g_atomic_pointer_compare_and_exchange(reinterpret_cast(slot), old, value)); + if (old) + g_timeout_add_seconds_full(G_PRIORITY_LOW, 3, retireUnrefCb, old, nullptr); +} + typedef struct { DFileInfo::AttributeAsyncCallback callback; @@ -249,11 +275,7 @@ bool DFileInfoPrivate::queryInfoSync() return false; } - if (this->gfileinfo) { - g_object_unref(this->gfileinfo); - this->gfileinfo = nullptr; - } - this->gfileinfo = fileinfo; + replaceGFileInfo(&this->gfileinfo, fileinfo); initFinished = true; isQuquerying = false; return true; @@ -300,6 +322,7 @@ bool DFileInfoPrivate::ensureStatxCached() const QVariant DFileInfoPrivate::attributesBySelf(DFileInfo::AttributeID id) { + g_autoptr(GFileInfo) gfileinfo = refGFileInfo(atomicLoadGFileInfo(&this->gfileinfo)); QVariant retValue; switch (id) { case DFileInfo::AttributeID::kStandardIsHidden: { @@ -603,6 +626,7 @@ DFile::Permissions DFileInfoPrivate::permissions() const bool DFileInfoPrivate::exists() const { + g_autoptr(GFileInfo) gfileinfo = refGFileInfo(atomicLoadGFileInfo(&this->gfileinfo)); if (!gfileinfo) return false; return g_file_info_get_file_type(gfileinfo) != G_FILE_TYPE_UNKNOWN; @@ -639,7 +663,7 @@ void DFileInfoPrivate::queryInfoAsyncCallback(GObject *sourceObject, GAsyncResul } if (data->me) { - data->me->gfileinfo = fileinfo; + replaceGFileInfo(&data->me->gfileinfo, fileinfo); data->me->initFinished = true; } @@ -677,7 +701,7 @@ void DFileInfoPrivate::queryInfoAsyncCallback2(GObject *sourceObject, GAsyncResu } if (data->me) { - data->me->gfileinfo = fileinfo; + replaceGFileInfo(&data->me->gfileinfo, fileinfo); data->me->initFinished = true; future->finished(); @@ -759,16 +783,17 @@ QVariant DFileInfo::attribute(DFileInfo::AttributeID id, bool *success) const } } + g_autoptr(GFileInfo) gfileinfo = refGFileInfo(atomicLoadGFileInfo(&d->gfileinfo)); QVariant retValue; if (id > DFileInfo::AttributeID::kCustomStart) { const QString &path = d->uri.path(); - retValue = DLocalHelper::customAttributeFromPathAndInfo(path, d->gfileinfo, id); + retValue = DLocalHelper::customAttributeFromPathAndInfo(path, gfileinfo, id); } else { - if (d->gfileinfo) { + if (gfileinfo) { DFMIOErrorCode errorCode(DFM_IO_ERROR_NONE); if (!d->attributesRealizationSelf.contains(id)) { QMutexLocker lk(&d->mutex); - retValue = DLocalHelper::attributeFromGFileInfo(d->gfileinfo, id, errorCode); + retValue = DLocalHelper::attributeFromGFileInfo(gfileinfo, id, errorCode); if (errorCode != DFM_IO_ERROR_NONE) const_cast(d.data())->error.setCode(errorCode); } else { @@ -935,11 +960,12 @@ bool DFileInfo::hasAttribute(DFileInfo::AttributeID id) const return false; } - if (d->gfileinfo) { + g_autoptr(GFileInfo) gfileinfo = refGFileInfo(atomicLoadGFileInfo(&d->gfileinfo)); + if (gfileinfo) { const std::string &key = DLocalHelper::attributeStringById(id); if (key.empty()) return false; - return g_file_info_has_attribute(d->gfileinfo, key.c_str()); + return g_file_info_has_attribute(gfileinfo, key.c_str()); } return false; @@ -991,40 +1017,41 @@ QVariant DFileInfo::customAttribute(const char *key, const DFileInfo::DFileAttri return QVariant(); } - if (!d->gfileinfo) + g_autoptr(GFileInfo) gfileinfo = refGFileInfo(atomicLoadGFileInfo(&d->gfileinfo)); + if (!gfileinfo) return QVariant(); switch (type) { case DFileInfo::DFileAttributeType::kTypeString: { - const char *ret = g_file_info_get_attribute_string(d->gfileinfo, key); + const char *ret = g_file_info_get_attribute_string(gfileinfo, key); return QVariant(ret); } case DFileInfo::DFileAttributeType::kTypeByteString: { - const char *ret = g_file_info_get_attribute_byte_string(d->gfileinfo, key); + const char *ret = g_file_info_get_attribute_byte_string(gfileinfo, key); return QVariant(ret); } case DFileInfo::DFileAttributeType::kTypeBool: { - bool ret = g_file_info_get_attribute_boolean(d->gfileinfo, key); + bool ret = g_file_info_get_attribute_boolean(gfileinfo, key); return QVariant(ret); } case DFileInfo::DFileAttributeType::kTypeUInt32: { - uint32_t ret = g_file_info_get_attribute_uint32(d->gfileinfo, key); + uint32_t ret = g_file_info_get_attribute_uint32(gfileinfo, key); return QVariant(ret); } case DFileInfo::DFileAttributeType::kTypeInt32: { - int32_t ret = g_file_info_get_attribute_int32(d->gfileinfo, key); + int32_t ret = g_file_info_get_attribute_int32(gfileinfo, key); return QVariant(ret); } case DFileInfo::DFileAttributeType::kTypeUInt64: { - uint64_t ret = g_file_info_get_attribute_uint64(d->gfileinfo, key); + uint64_t ret = g_file_info_get_attribute_uint64(gfileinfo, key); return QVariant(qulonglong(ret)); } case DFileInfo::DFileAttributeType::kTypeInt64: { - int64_t ret = g_file_info_get_attribute_int64(d->gfileinfo, key); + int64_t ret = g_file_info_get_attribute_int64(gfileinfo, key); return QVariant(qulonglong(ret)); } case DFileInfo::DFileAttributeType::kTypeStringV: { - char **ret = g_file_info_get_attribute_stringv(d->gfileinfo, key); + char **ret = g_file_info_get_attribute_stringv(gfileinfo, key); QStringList retValue; for (int i = 0; ret && ret[i]; ++i) { retValue.append(QString::fromLocal8Bit(ret[i]));