USBVault-USB外接存储设备加密格式化管理工具
下载(1 个文件)
软件使用说明
Peony 右键"打开终端"时好时坏 Bug:根因分析与修复记录
环境:Kylin-Desktop-V10-SP1-HWE-2403(x86_64)· UKUI · peony 3.26.0.0-0k6.17 ·
peony-open-terminal 插件 3.26.0.0-0k1.22 · mate-terminal 1.24.0 · Qt 5.12.12-kylin
结论:插件 mate-terminal-menu-plugin.cpp 中一处 C++
临时对象生命周期错误(悬空指针,
use-after-free)导致 g_spawn_async() 拿到被复用的堆内存当作工作目录,chdir 失败后静默退出。
已修复、编译并部署到本机,真机验证通过。
- 问题现象
在 peony 文件管理器中,于文件夹内右键选择"打开终端"(插件 peony-open-terminal 提供的菜单项),
有时终端完全不弹出、无任何报错;
表现为"按路径决定":例如在 /home/new211/peony/peony-3.2.3 下必现失败,在 /tmp、
/home/new211 等路径下大多能成功,且同一目录的行为在同一系统上是稳定的;
该 bug 在多个 Kylin-V10 版本(peony 3.2.x ~ 3.26)上长期存在;
对照组:PCManFM 1.3.1"在终端中打开当前文件夹"以及 Nautilus 脚本方式 100% 成功,
说明 mate-terminal 本身和桌面环境没有问题,问题只在 peony 插件。 - 排查过程
定位功能实现:右键"打开终端"由 peony-extensions 的
peony-menu-plugin-mate-terminal 插件实现(系统内为
/usr/lib/x86_64-linux-gnu/peony-extensions/libpeony-menu-plugin-mate-terminal.so,
源码对应工作区 peony-extensions-3.2.3/peony-menu-plugin-mate-terminal/mate-terminal-menu-plugin.cpp)。
反汇编已安装的 3.26 二进制,确认其 openTerminal() 与 3.2.3 源码同构:
QUrl::path() → QString::toUtf8_helper() → g_shell_parse_argv → g_spawn_async,
且 3.26 版本在 spawn 失败后只写日志、没有任何兜底重试(旧版源码中的 tryOpenAgain()
已不再被调用)。
真实桌面复现:在本机图形会话用 xdotool 模拟"打开 peony 窗口 → 右键 → 点击『打开终端』"
(菜单行坐标通过对菜单截图做图像识别 + X 窗口几何差集获得)。在
/home/new211/peony/peony-3.2.3 连续 3 次点击,0 次弹出终端、0 个 mate-terminal 进程,
与用户报告一致。
无头差分测试(A/B harness):编写 Qt 测试程序直接 QPluginLoader 加载插件 .so,
反复调用 menuActions()->trigger(),并用一个"假 mate-terminal"脚本(放在 PATH 前部)
记录每次被 spawn 时的工作目录。新旧插件对比数据见 §5。 - 根因分析
3.1 问题代码(修复前)
CPP
复制
void MateTerminalMenuPlugin::openTerminal(){
QUrl url = m_uri;
auto directory = url.path().toUtf8().constData(); // <-- BUG
gchar *argv = nullptr;
g_shell_parse_argv (terminal_cmd.toUtf8().constData(), nullptr, &argv, nullptr);
GError err = nullptr;
g_spawn_async (directory, // <-- 使用已释放的指针
argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &err);
...
}
url.path().toUtf8() 返回一个临时 QByteArray,constData() 返回其内部缓冲区指针;
auto directory = ... 这条语句结束时,临时对象被析构、堆缓冲区被 free,
directory 成为悬空指针。随后 g_shell_parse_argv() 的堆分配(恰好复制了字符串
"mate-terminal")极可能复用这块刚释放的内存,于是传给 g_spawn_async() 的
"工作目录"变成了乱数据 —— harness 抓到的真实报错:
TEXT
复制
openTerminal failed: 更改到目录"mate-terminal"失败(没有那个文件或目录)
即 chdir 目标竟然是字符串 mate-terminal —— 与上述内存复用机制完全吻合。
chdir 失败 → g_spawn_async 返回 FALSE → 3.26 版本无兜底、无提示 → 用户看到"点了没反应"。
3.2 为什么"有的目录能开、有的不能"、"跨版本一直存在"
目标路径长度决定 toUtf8() 缓冲区落在哪个 glibc 堆尺寸桶(tcache bin);
该桶在语句结束到 g_spawn_async() 之间是否被后续小分配复用,决定了路径字符串是否被破坏。
因此对同一系统同一目录行为稳定,对不同路径(长度)结果不同。
具体分配序列随 peony/Qt/glib 版本和进程堆历史变化,所以各 Kylin 版本上"哪些目录失败"不一样,
但 bug 本身代代相传(上游 GitHub ukui/peony-extensions master 分支至今仍是同样写法)。
实测(harness,每路径触发 N 次):旧插件在 /tmp 20/20 成功(路径太短不触发复用)、
/home/new211 14/20 成功(间歇失败)、/home/new211/peony/peony-3.2.3 0/40(必现失败)。
PCManFM / Nautilus 不经过这段代码,它们以各自正确的方式传工作目录,因此从未失败。
4. 修复方案
修改文件:peony-extensions-3.2.3/peony-menu-plugin-mate-terminal/mate-terminal-menu-plugin.cpp
要点:
让 QByteArray 在 g_spawn_async() 调用期间存活(根治 UAF);
g_shell_parse_argv 失败/返回空 argv 时兜底为 mate-terminal;
恢复并修复 tryOpenAgain() 兜底(主路径失败时用
QProcess::startDetached("mate-terminal", {"--working-directory", path}) 重试,
仍失败则弹 QMessageBox 提示,而不是无声无息);去掉对已 detach 进程无意义的
waitForFinished(-1)。
4.1 Diff
DIFF
复制
--- a/mate-terminal-menu-plugin.cpp
+++ b/mate-terminal-menu-plugin.cpp
#include
#include
+#include
void MateTerminalMenuPlugin::openTerminal(){
//qDebug()<<"triggered"<<uri;
QUrl url = m_uri;
- auto directory = url.path().toUtf8().constData();
- //the QByteArray returned by toUtf8() must outlive the g_spawn_async() call,
- //otherwise g_spawn_async() gets a dangling pointer as working directory and
- //the spawn fails silently (bug: open terminal in right click menu does nothing).
- QByteArray dirData = url.path().toUtf8();
- const char directory = dirData.constData();
gchar *argv = nullptr; - g_shell_parse_argv (terminal_cmd.toUtf8().constData(), nullptr, &argv, nullptr);
- if (!g_shell_parse_argv (terminal_cmd.toUtf8().constData(), nullptr, &argv, nullptr) || !argv) {
- const gchar *fallback[] = {"mate-terminal", nullptr};
- argv = g_strdupv (const_cast
(fallback)); - }
GError *err = nullptr;
g_spawn_async (directory, argv, nullptr, G_SPAWN_SEARCH_PATH,
nullptr, nullptr, nullptr, &err);
if (err) { - qDebug()<
message; - qDebug()<<"openTerminal failed:"<
message;
g_error_free(err);
err = nullptr;
//try again to open terminal
//maybe this method is better,but still need more test to confirm
tryOpenAgain();
}
g_strfreev (argv);
}
void MateTerminalMenuPlugin::tryOpenAgain()
{
QUrl url = m_uri;
auto absPath = url.path();
qDebug() << "tryOpenAgain terminal url:" <<absPath;
QProcess p;
p.setProgram(terminal_cmd);
p.setArguments(QStringList()<<"--working-directory"<<absPath);
-// p.startDetached();
- p.startDetached(p.program(), p.arguments());
- p.waitForFinished(-1);
+ bool result = QProcess::startDetached(p.program(), p.arguments());
+
+ if (!result) {
+ QMessageBox::critical(nullptr, tr("Open terminal fail"),
+ tr("Open terminal failed, did you removed the default terminal?"
+ " If it's true please reinstall it."));
+ }
}
4.2 构建与部署(本机实际执行的步骤)
BASH
复制
依赖(一次性)
sudo apt-get install -y qtbase5-dev libpeony-dev
编译(在 peony-extensions 源码树外复制一份,common.json 需与源码同目录)
cp -r peony-extensions-3.2.3/peony-menu-plugin-mate-terminal /tmp/plugin-build
cp peony-extensions-3.2.3/common.json /tmp/plugin-build/
cd /tmp/plugin-build
qmake peony-menu-plugin-mate-terminal.pro "CONFIG-=debug" "CONFIG+=release"
make -j4
strip --strip-unneeded libpeony-menu-plugin-mate-terminal.so
部署(先备份原版)
DST=/usr/lib/x86_64-linux-gnu/peony-extensions
sudo cp -a $DST/libpeony-menu-plugin-mate-terminal.so $DST/libpeony-menu-plugin-mate-terminal.so.kylin-bak-20261025
sudo cp libpeony-menu-plugin-mate-terminal.so $DST/
sudo chown root:root $DST/libpeony-menu-plugin-mate-terminal.so && sudo chmod 644 $DST/libpeony-menu-plugin-mate-terminal.so
重启 peony 加载新插件(peony-qt-desktop 桌面进程不受影响)
killall peony && peony /home/new211/peony/peony-3.2.3 &
回滚:sudo cp $DST/libpeony-menu-plugin-mate-terminal.so.kylin-bak-20261025 $DST/libpeony-menu-plugin-mate-terminal.so && killall peony
- 验证结果
5.1 无头 A/B 测试(同一加载器、同一触发流程,仅插件 .so 不同)
插件 触发路径 触发次数 成功 spawn 工作目录正确
旧(系统 3.26) /home/new211/peony/peony-3.2.3 40 0 -
旧(系统 3.26) /home/new211 20 14 14
旧(系统 3.26) /tmp 20 20 20
新(修复版) /home/new211/peony/peony-3.2.3 40 40 40
新(修复版) /home/new211/peony 20 20 20
新(修复版) /home/new211 20 20 20
新(修复版) /tmp 20 20 20
旧插件抓到的典型报错(即 §3.1 的内存复用证据):
openTerminal failed: 更改到目录"mate-terminal"失败(没有那个文件或目录)
5.2 真实桌面端到端验证
部署修复版并重启 peony 后,在 /home/new211/peony/peony-3.2.3 窗口内右键点击"打开终端":
终端窗口立即弹出,标题 new211@new211-pc: ~/peony/peony-3.2.3;
该终端内 shell 进程的 /proc/
用户人工反复测试确认恢复正常。
附注:mate-terminal 1.24 为单进程多窗口(factory)模型——新调用的 mate-terminal
会把"开新窗口"请求转给已运行实例后立即退出,因此验证时应看窗口/标题/shell 的 cwd,
而不是找"新 mate-terminal 进程"。
- 测试工具(可复用)
A/B harness:QPluginLoader 加载 .so → menuActions(DirectoryView, uri, {}) →
对返回的 QAction trigger();两次触发之间做随机堆分配/释放模拟真实堆状态;
QT_QPA_PLATFORM=offscreen 运行,不干扰桌面。
假终端:把记录脚本放到 PATH 前部即可统计 spawn 成功率和实际 cwd:
BASH
复制
/tmp/fakebin/mate-terminal
!/bin/bash
echo "$PWD | $*" >> /tmp/spawned_markers.txt
运行:PATH=/tmp/fakebin:$PATH /tmp/harness/harness <插件.so> file:///某路径 40
7. 注意事项
系统升级会覆盖本修复:apt 升级 peony-open-terminal 包会重装官方 .so(官方包内
仍是带 bug 的版本)。如复发,按 §4.2 重新编译部署即可;原版备份
libpeony-menu-plugin-mate-terminal.so.kylin-bak-20261025 保留在插件目录。
上游 ukui/peony-extensions(含 Debian 分支/master)至今未修复此 bug,建议将本文
提交给上游(4 行核心改动,见 §4.1 diff 中的 dirData 部分)。
修复针对的是"工作目录悬空指针"这一根本问题;顺带恢复了旧版被移除的失败兜底和失败提示,
今后即使出现其他失败(如终端被卸载)也会弹窗告知而不是无声无息。
还没有评论,来抢沙发~