Skip to content

fix: disconnect D-Bus system bus connections in destructor before thread exit#556

Merged
Ivy233 merged 1 commit into
linuxdeepin:masterfrom
Ivy233:bugfix/dbus-disconnect-crash-netmanagerthread
Jun 5, 2026
Merged

fix: disconnect D-Bus system bus connections in destructor before thread exit#556
Ivy233 merged 1 commit into
linuxdeepin:masterfrom
Ivy233:bugfix/dbus-disconnect-crash-netmanagerthread

Conversation

@Ivy233
Copy link
Copy Markdown
Contributor

@Ivy233 Ivy233 commented Jun 4, 2026

Disconnect all QDBusConnection::systemBus() signals registered in doInit() from the worker thread before quitting m_thread. This prevents a use-after-free crash in QDBusConnectionManager during application exit when it tries to close qt_default_system_bus and accesses already-freed worker-thread objects.

Log: Fix dcc-network plugin crash on exit due to D-Bus connection leak

fix: 在析构函数中断开D-Bus系统总线连接,避免线程退出后崩溃

在退出工作线程(m_thread)之前,断开doInit()中注册的所有QDBusConnection::systemBus()信号连接。防止应用退出时QDBusConnectionManager清理qt_default_system_bus时访问已释放的工作线程对象导致use-after-free崩溃。

Log: 修复dcc-network插件退出时因D-Bus连接未清理导致的崩溃
PMS: BUG-363425

Summary by Sourcery

Bug Fixes:

  • Prevent a use-after-free crash on application exit by disconnecting QDBusConnection::systemBus() signals registered in the worker thread before quitting the thread.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jun 4, 2026

Reviewer's Guide

Ensures all D-Bus system bus signal connections established in NetManagerThreadPrivate::doInit() are explicitly disconnected in the destructor before the worker thread quits, preventing use-after-free crashes during global QDBusConnectionManager cleanup.

Sequence diagram for NetManagerThreadPrivate destructor D-Bus disconnection

sequenceDiagram
    participant NetManagerThreadPrivate
    participant WorkerThread as m_thread
    participant QDBusConnection_systemBus as QDBusConnection_systemBus
    participant QDBusConnectionManager

    NetManagerThreadPrivate->>NetManagerThreadPrivate: disconnect()
    NetManagerThreadPrivate->>QDBusConnection_systemBus: disconnect(com.deepin.defender.netcheck PropertiesChanged this)
    NetManagerThreadPrivate->>QDBusConnection_systemBus: disconnect(org.freedesktop.login1 PrepareForSleep this)
    NetManagerThreadPrivate->>QDBusConnection_systemBus: disconnect(org.deepin.dde.Network1 PortalDetected this)
    NetManagerThreadPrivate->>QDBusConnection_systemBus: disconnect(org.deepin.dde.Bluetooth1 AdapterAdded this)
    NetManagerThreadPrivate->>QDBusConnection_systemBus: disconnect(org.deepin.dde.Bluetooth1 AdapterRemoved this)
    NetManagerThreadPrivate->>QDBusConnection_systemBus: disconnect(org.deepin.dde.AirplaneMode1 PropertiesChanged this)

    NetManagerThreadPrivate->>WorkerThread: quit()
    NetManagerThreadPrivate->>WorkerThread: wait(QDeadlineTimer)

    QDBusConnectionManager->>QDBusConnection_systemBus: close(qt_default_system_bus)
    QDBusConnection_systemBus-->>QDBusConnectionManager: cleanup without callbacks to NetManagerThreadPrivate
Loading

File-Level Changes

Change Details Files
Explicitly disconnect D-Bus system bus signal handlers in NetManagerThreadPrivate destructor before stopping the worker thread to avoid use-after-free at application shutdown.
  • Call QDBusConnection::systemBus().disconnect for the netcheck PropertiesChanged signal handler bound to this object.
  • Disconnect the login1 PrepareForSleep signal handler from this object on the system bus.
  • Conditionally disconnect the SystemNetwork PortalDetected signal handler when portal prompts are supported.
  • When airplane mode handling is enabled, disconnect Bluetooth1 AdapterAdded/AdapterRemoved and AirplaneMode1 PropertiesChanged signal handlers from this object.
  • Ensure all D-Bus disconnections occur before m_thread->quit() is invoked in the destructor.
net-view/operation/private/netmanagerthreadprivate.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The destructor now duplicates the signal list from doInit(); consider extracting shared helper methods for registering/unregistering the D-Bus connections so changes to one side can't get out of sync.
  • Relying on ConfigSetting::instance() in the destructor can be fragile if that singleton's lifetime changes; it may be safer to cache the supportPortalPromp flag during initialization and reuse the cached value here.
  • To reduce the risk of missing future signals, consider using a broader disconnect pattern (e.g., grouping related connections or storing QMetaObject::Connection handles) instead of repeating all systemBus() signature strings in the destructor.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The destructor now duplicates the signal list from doInit(); consider extracting shared helper methods for registering/unregistering the D-Bus connections so changes to one side can't get out of sync.
- Relying on ConfigSetting::instance() in the destructor can be fragile if that singleton's lifetime changes; it may be safer to cache the supportPortalPromp flag during initialization and reuse the cached value here.
- To reduce the risk of missing future signals, consider using a broader disconnect pattern (e.g., grouping related connections or storing QMetaObject::Connection handles) instead of repeating all systemBus() signature strings in the destructor.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Ivy233 Ivy233 requested a review from BLumia June 4, 2026 09:44
// 断开 doInit() 中注册的 D-Bus 系统总线连接
// 必须在 m_thread->quit() 之前执行,否则 QDBusConnectionManager 线程
// 在全局清理时会访问已释放的工作线程对象,导致 use-after-free 崩溃
QDBusConnection::systemBus().disconnect("com.deepin.defender.netcheck", "/com/deepin/defender/netcheck", "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onNetCheckPropertiesChanged(QString, QVariantMap, QStringList)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上面的disconnect(); 不会断开所有跟它的连接么,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QObject::disconnect()(第111行)只会断开 Qt QMetaObject 管理的 signal-slot 连接,即通过 connect(sender, signal, receiver, slot) 注册的那些连接,以及清除队列中的 QMetaObject::invokeMethod 调用。

QDBusConnection::systemBus().connect() 在 doInit() 中注册的 D-Bus 信号监听,是存储在 QDBusConnection 内部独立数据结构中的。它在底层做的是:

  1. 向 D-Bus 守护进程添加匹配规则(match rule)
  2. 在 QDBusConnection 内部维护一个回调注册表

QObject::disconnect() 感知不到这个注册表,因此无法断开这些 D-Bus 连接。

如果没有第 116-130 行的显式 QDBusConnection::systemBus().disconnect() 调用,当应用退出时 QDBusConnectionManager 全局清理并关闭 qt_default_system_bus 时,仍然会通过其内部注册表尝试向已销毁的 this 对象派发信号,从而导致 use-after-free 崩溃——这正是我们要修复的那个崩溃。

所以这两者是必须的,并不多余。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QDBusConnection::systemBus().connect()连接的槽函数,receiver是this,这样this->disconnect也不行么?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 disconnect() 断开的是 this 作为 sender 的连接,而 QDBusConnection::systemBus().connect() 注册的信号钩子中,thisreceiver。两者作用方向不同。

关键崩溃点在 Qt6 qdbusintegrator.cpp:1140-1152QDBusConnectionPrivate::closeConnection() 在全局清理时会遍历内部的 signalHooks 表直接访问 hook.obj(即 this)。而这个表里的条目要通过 destroyed 信号以 BlockingQueuedConnection 投递到 D-Bus 线程来清理。如果退出时 D-Bus 线程事件循环已停止,objectDestroyed 回调不会执行,signalHooks 里就留下指向已释放对象的悬空指针。

所以两者解决不同层次的问题:this->disconnect() 清理 this 作为 sender 的连接;QDBusConnection::systemBus().disconnect() 主动从 signalHooks 中移除条目,确保 closeConnection() 不会踩到悬空指针。详见 Qt6 qdbusintegrator.cpp:1343hook.obj = receiverqdbusintegrator.cpp:1237-1251objectDestroyed 正常清理流程。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上,这个 disconnect() 断开的是 this 作为 sender 的连接,D-Bus 的 signalHooksthisreceiver 存在 hook.obj 里。所以需要显式 QDBusConnection::systemBus().disconnect() 来清理 QDBusConnectionPrivate 内部的 signalHooks 表,详见上一条回复中的 Qt6 源码分析。

…ead exit

Disconnect all QDBusConnection::systemBus() signals registered in doInit()
from the worker thread before quitting m_thread. This prevents a
use-after-free crash in QDBusConnectionManager during application exit
when it tries to close qt_default_system_bus and accesses already-freed
worker-thread objects.

Log: Fix dcc-network plugin crash on exit due to D-Bus connection leak

fix: 在析构函数中断开D-Bus系统总线连接,避免线程退出后崩溃

在退出工作线程(m_thread)之前,断开doInit()中注册的所有
QDBusConnection::systemBus()信号连接。防止应用退出时
QDBusConnectionManager清理qt_default_system_bus时
访问已释放的工作线程对象导致use-after-free崩溃。

Log: 修复dcc-network插件退出时因D-Bus连接未清理导致的崩溃
PMS: BUG-363425
@Ivy233 Ivy233 force-pushed the bugfix/dbus-disconnect-crash-netmanagerthread branch from 1dff451 to cb793d0 Compare June 5, 2026 03:25
@deepin-ci-robot
Copy link
Copy Markdown

deepin pr auto review

你好!我是CodeGeeX。我已仔细审查了你提供的Git Diff代码。这次修改的主要目的是在析构函数中提前断开D-Bus系统总线的连接,以防止在对象析构及子线程退出期间,D-Bus信号回调访问已释放的对象,从而引发use-after-free崩溃。这是一个非常关键的并发与生命周期管理的修复。

以下是我对这段代码在语法逻辑、代码质量、代码性能和代码安全方面的详细审查意见及改进建议:

1. 语法与逻辑

  • testFlags 逻辑隐患:代码中使用了 m_flags.testFlags(NetType::NetManagerFlag::Net_Airplane)。在Qt中,QFlags::testFlags 要求参数中的所有标志位都被设置才返回 true。如果 Net_Airplane 是一个单值(枚举值而非位掩码组合),使用 testFlag(单数)更为准确和安全。如果它是多标志位的组合,testFlags 是正确的,但通常断开连接的逻辑应该与注册连接时的条件完全一致。
  • 断开连接的返回值未检查QDBusConnection::disconnect() 会返回一个 bool 值表示断开是否成功。虽然在析构函数中失败通常不需要抛出异常,但如果断开失败,可能意味着连接早已意外断开或参数有误,这在调试期间是非常有价值的信息。

2. 代码质量

  • 代码重复与可维护性:D-Bus的断开连接代码与连接代码存在高度重复(硬编码的服务名、路径、接口、信号名和槽函数)。如果未来D-Bus接口发生变更,开发者需要同时修改 doInit() 和析构函数,极易遗漏,违反了DRY(Don't Repeat Yourself)原则。
  • 硬编码字符串:大量的D-Bus签名直接以字符串常量形式散落在代码中,建议将其提取为静态常量或宏,集中管理。
  • 改进建议:建议在类中增加一个 uninitDBusConnections() 方法,将断开连接的逻辑封装起来,或者更好的是,在 doInit() 连接时记录连接信息,析构时统一遍历断开。

3. 代码性能

  • 频繁调用 systemBus():代码中连续调用了 6 次 QDBusConnection::systemBus()。虽然 systemBus() 内部通常有缓存,不会每次都创建新连接,但每次调用仍需经过一定的函数调用开销和锁检查。
  • 改进建议:在局部作用域内获取一次 QDBusConnection 引用并复用。

4. 代码安全

  • use-after-free 修复的正确性:你的修复思路非常正确。在多线程和事件驱动的环境中,析构期间信号触发是导致UAF的常见原因。将 disconnect() 放在 m_thread->quit() 之前,确保了工作线程还在存活且事件循环未退出时,切断了外部信号源,非常棒。
  • 潜在的跨线程析构风险:如果 NetManagerThreadPrivate 对象是在主线程中被析构,而D-Bus的回调槽函数是在主线程(QDBus默认使用主线程的信号槽机制)执行,那么当前的 disconnect 是安全的。但如果该对象被依附到了 m_thread 中(即 moveToThread),则在主线程直接调用 disconnect 和后续的 m_thread->quit() 可能存在潜在的线程竞争。假设该对象本身就在主线程运行,则当前逻辑是安全的。

综合改进后的代码示例

基于以上分析,我为你重构了这部分代码,提升了可读性、性能和安全性:

NetManagerThreadPrivate::~NetManagerThreadPrivate()
{
    // 先断开所有信号,防止析构期间再有新任务(如singleShot)入队
    disconnect();

    // 断开 doInit() 中注册的 D-Bus 系统总线连接
    // 必须在 m_thread->quit() 之前执行,否则 QDBusConnectionManager 线程
    // 在全局清理时会访问已释放的工作线程对象,导致 use-after-free 崩溃
    uninitDBusConnections();

    m_thread->quit();
    // 增大等待时间至1000ms,避免50ms定时器回调等正在执行的任务被terminate强杀
    m_thread->wait(QDeadlineTimer(1000));
}

// 建议在类中新增该私有函数,与 doInit() 形成对称逻辑
void NetManagerThreadPrivate::uninitDBusConnections()
{
    const QDBusConnection &bus = QDBusConnection::systemBus();
    
    // 1. 断开 netcheck 相关
    bus.disconnect("com.deepin.defender.netcheck", 
                   "/com/deepin/defender/netcheck", 
                   "org.freedesktop.DBus.Properties", 
                   "PropertiesChanged", 
                   this, SLOT(onNetCheckPropertiesChanged(QString, QVariantMap, QStringList)));
                   
    // 2. 断开 login1 相关
    bus.disconnect("org.freedesktop.login1", 
                   "/org/freedesktop/login1", 
                   "org.freedesktop.login1.Manager", 
                   "PrepareForSleep", 
                   this, SLOT(onPrepareForSleep(bool)));
                   
    // 3. 断开 Portal 相关
    if (ConfigSetting::instance()->supportPortalPromp()) {
        bus.disconnect("org.deepin.dde.Network1",
                       "/org/deepin/service/SystemNetwork",
                       "org.deepin.service.SystemNetwork",
                       "PortalDetected",
                       this,
                       SLOT(onPortalDetected(const QString &)));
    }
    
    // 4. 断开飞行模式相关
    // 注意:如果是单标志位判断,建议改为 testFlag (单数)
    if (m_flags.testFlag(NetType::NetManagerFlag::Net_Airplane)) {
        bus.disconnect("org.deepin.dde.Bluetooth1", 
                       "/org/deepin/dde/Bluetooth1", 
                       "org.deepin.dde.Bluetooth1", 
                       "AdapterAdded", 
                       this, SLOT(getAirplaneModeEnabled()));
        bus.disconnect("org.deepin.dde.Bluetooth1", 
                       "/org/deepin/dde/Bluetooth1", 
                       "org.deepin.dde.Bluetooth1", 
                       "AdapterRemoved", 
                       this, SLOT(getAirplaneModeEnabled()));
        bus.disconnect("org.deepin.dde.AirplaneMode1", 
                       "/org/deepin/dde/AirplaneMode1", 
                       "org.freedesktop.DBus.Properties", 
                       "PropertiesChanged", 
                       this, SLOT(onAirplaneModeEnabledPropertiesChanged(QString, QVariantMap, QStringList)));
    }
}

进一步优化的思考
如果你能修改 doInit() 的结构,建议在 doInit() 中使用 QMap 或结构体将成功建立的 D-Bus 连接信息(Service, Path, Interface, Signal, Slot)记录下来。在析构时,只需遍历这个数据结构进行 disconnect,这样能彻底消除硬编码重复,保证注册和注销的绝对对称,代码会更加健壮和优雅。

@deepin-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, Ivy233

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@Ivy233 Ivy233 merged commit 69065b5 into linuxdeepin:master Jun 5, 2026
17 of 19 checks passed
@Ivy233 Ivy233 deleted the bugfix/dbus-disconnect-crash-netmanagerthread branch June 5, 2026 10:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants