Skip to content

~QSettingBackend() 空析构无同步保护:跨线程销毁 backend 时 QSettings UAF 且 writeLock 带锁析构 #587

Description

@add-uos

问题描述

QSettingBackend 的析构函数为空实现,且 QSettings 以 backend 为 parent:
backend 被销毁(尤其从主线程跨线程销毁)时会连带销毁 QSettings,与写线程在途的
doSetOption 存在 use-after-free 竞态;同时 writeLock 可能在锁定状态下被
析构
(Qt 报 QMutex: destroying locked mutex,属未定义行为)。

机制(dtkcore 6.7.44)

  1. DSettings::setBackend() 将 backend moveToThread 到专门的写线程,
    doSetOption/doSync 经队列在该线程异步执行;
  2. 写线程生命周期仅由 DSettings::destroyed 信号管理(quit()+wait());
  3. ~QSettingBackend() 为空——无锁保护、无线程同步:
QSettingBackend::~QSettingBackend() { }   // 空析构

void QSettingBackend::doSetOption(...) {
    d->writeLock.lock();
    d->settings->setValue(...);   // 写线程访问 QSettings
    ...
    d->writeLock.unlock();
}

现场证据(deepin-editor CI UT,SIGSEGV exit 139)

使用方(deepin-editor)在 ~Settings() 中先 delete backend(未先析构
DSettings),core 堆栈显示崩溃发生在 DTK 写线程:

#0  QSettings::setValue(...)                      ← UAF
#1  Dtk::Core::QSettingBackend::doSetOption(...)
#2  QObject::event(QEvent*)                       ← 队列事件派发
#12 QThread::exec()                               ← 写线程事件循环

析构期间日志同时出现 QMutex: destroying locked mutex,与 UAF 为同一竞态的两个表现。
全量测试套件下必现;单用例不触发(竞态依赖在途写事件)。

建议(任选其一或组合)

  1. ~QSettingBackend() 内先 d->writeLock.lock(),锁内清理 settings
    (QSettings 改为手动管理,不挂 parent 自动销毁),确保与在途写操作互斥;
  2. 或将写线程生命周期收归 QSettingBackend 自身管理(析构时 quit+wait),
    不依赖 DSettings::destroyed;
  3. 至少在文档中明确销毁顺序契约:backend 必须晚于所属 DSettings 析构。

注:deepin-editor 侧已按"先析构 DSettings 再删除 backend"规避
(linuxdeepin/deepin-editor#629),但库层面的跨线程销毁风险仍在。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions