查看: 267|回复: 3

鸿蒙OBS插件dlopen失败:SELinux与链接器命名空间

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
OBS Studio 的鸿蒙迁移进入插件加载阶段后,遇到一个很典型的问题:libobs 的插件目录扫描在应用沙箱里静默失效,日志里连“尝试加载”都没有。这不是某个插件坏掉,而是“扫目录发现插件”的传统做法与鸿蒙安全模型不兼容。本文基于 OBS Studio 32.2.2 的鸿蒙移植分支 qingkouwei/obs-studio@harmonyos-port,验证机为 HUAWEI MatePad Edge(PC/2in1 模式)、HarmonyOS 7.0.0.109、API 26,梳理两道门和可复用的适配方法。

一、现象:obs_load_all_modules 零日志

libobs 启动时由 obs_load_all_modules() 扫描插件目录,逐个 dlopen 并注册源/编码器/输出类型。桌面版 OBS 的采集、编码、滤镜、转场等内置插件都靠这一步进场。鸿蒙上这一步没有任何日志:不是“加载失败”,而是连尝试记录都没有。原因是模块扫描用 os_opendir 遍历插件目录下的 *.so,目录打不开就静默返回;它把“目录不存在”当正常情况,失败被吞掉。前提是 blog() 已接 hilog,否则连“零日志”都观察不到。

二、第一道门:SELinux 禁止 readdir bundle libs

在桥接层直接对插件目录 opendir() 复现,errno 为:
  1. EACCES (Permission denied)
复制代码

目录存在、路径正确,且是应用自己的 bundle libs 目录 /data/storage/el1/bundle/libs/arm64-v8a,但应用进程没有 readdir 权限。鸿蒙对随 HAP 分发的原生库目录设置了 SELinux 策略:这些 .so 是系统托管文件,应用可以把它们作为 NAPI 模块“用”,但不能列目录“翻”。工程含义很直接:任何依赖“扫目录发现插件”的框架,在鸿蒙上都要换发现机制。libobs 的 obs_find_modules2(目录扫描+读 .so 元数据)整条路走不通,不是调权限能解决,而是模型不兼容。

三、第二道门:linker namespace 拒绝绝对路径 dlopen

绕开扫描,硬编码绝对路径逐个 dlopen:
  1. dlopen("/data/storage/el1/bundle/libs/arm64-v8a/harmony-capture.so", ...);
  2. // → dlopen failed: check ns accessible failed
复制代码

仍然失败。应用进程的动态链接器运行在受限 linker namespace 中,只允许按搜索路径规则解析库名,不允许用任意绝对路径打开未声明的库文件,这是沙箱防“加载任意代码”的核心机制之一。关键点:两道门报错完全不同,EACCES 是文件系统层,check ns accessible failed 是链接器层。排查时不要把两次失败当同一个问题,否则会误以为“改了没用”。

四、正解:裸 soname + 显式清单

第二道门留了正门:namespace 的 permitted paths 包含 bundle libs 目录,前提是按库名(soname)请求,让链接器自己搜索。裸 soname 不触发路径审计,一次通过:
  1. dlopen("harmony-capture.so", RTLD_NOW); // 走 namespace 搜索路径,命中
复制代码

桥接层因此改成显式清单模式:
  1. static const char *const kPlugins[] = {
  2.     "harmony-capture", "harmony-audio", "harmony-camera", "harmony-vcodec",
  3.     "obs-ffmpeg", "obs-filters", "obs-outputs", "obs-transitions",
  4.     "obs-x264", "rtmp-services", "image-source", "text-freetype2",
  5. };
  6. for (const char *name : kPlugins) {
  7.     snprintf(bin, sizeof(bin), "%s.so", name);
  8.     snprintf(data, sizeof(data), "%s/obs-plugins/%s", dataDir.c_str(), name);
  9.     obs_module_t *mod = nullptr;
  10.     int ret = obs_open_module(&mod, bin, data); // bin=裸soname, data=资源目录
  11.     if (ret == MODULE_SUCCESS) {
  12.         obs_init_module(mod);
  13.         loaded++;
  14.     }
  15. }
复制代码

结果 12 个插件全部加载:4 个鸿蒙原生(采集/音频/相机/编码)+ 8 个上游原生(obs-ffmpeg、obs-filters、obs-outputs、obs-transitions、obs-x264、rtmp-services、image-source、text-freetype2)。OBS 的插件生态在鸿蒙沙箱里以显式清单方式重生。

排查决策路径可照抄:插件加载静默失败→桥接层手动 opendir 复现→EACCES→判断 SELinux 禁 readdir→改绝对路径 dlopen→check ns accessible failed→判断 linker namespace 路径审计→改裸 soname dlopen→命中 permitted paths→加载成功。两类报错分层:EACCES=文件系统层,ns failed=链接器层。

五、账单:显式清单不是最优解

显式清单是沙箱约束下的可行解,不是工程最优解。可维护性:新增插件要改 C++ 数组,自动扫描没了;失败静默:清单里某个库加载失败,不会有“目录里明明有它”的对照提示;数据目录耦合:obs-plugins/<name> 资源目录仍依赖 rawfile 暂存约定,清单和暂存两处要同步。工程化收尾方向:把插件清单挪进 rawfile 的 manifest(数据驱动,不改代码),或在编译期从 CMake 插件列表生成 C++ 数组(单一事实源)。鸿蒙沙箱不会为应用网开一面,能改的是架构假设。

六、技术影响

这两道门不是鸿蒙 bug,而是安全模型的设计表达:bundle libs 不可 readdir,表示随包代码是系统托管资产,应用是租户不是房东;linker namespace 路径审计,表示代码加载必须走声明过的领地,杜绝任意路径注入。对“应用自带插件目录、运行时扫描加载”的传统桌面软件架构,这是根本性冲突。OBS 的解法(显式清单+裸 soname)可平移到 GStreamer、Qt 插件甚至 Lua C 扩展等同类场景:任何带插件体系的开源软件上鸿蒙,第一关都在这里。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: 鸿蒙OBS插件dlopen失败:SELinux与链接器命名空间

感谢分享,这个“两层报错分开看”的排查路径很有价值,EACCES 和 check ns accessible failed 确实很容易被混成一个“插件加载不了”的问题,帖子把文件系统层和链接器层拆开讲清楚了。 顺着第五节的账单补几个工程化上的想法:显式清单最好在构建期从 CMake 的插件目标列表生成一个头文件,运行时再允许 rawfile manifest 覆盖或追加,这样单一事实源和热更新都能兼顾;清单里别只写名字,把资源目录、版本号和是否必需一起带上,加载时按项校验,缺失或 MODULE_FAILURE 时打一条带 errno 和 dlerror 的明确日志,避免又回到“静默失效”。另外裸 soname 能命中,前提是打包进 bundle libs 的文件名和链接器搜索规则一致,建议在 CI 里加一步检查插件产物的命名和依赖项,防止某个插件改了输出名后清单还在、加载却失败。 失败处理也值得细化:obs_open_module 的返回值可以区分“文件没找到”“符号缺失”“初始化失败”,桥接层分别给不同 hilog 等级,排查时就不用再手动 opendir 复现了。至于数据目录和 rawfile 暂存两处同步的问题,可以在启动时做一次清单与目录的 diff,只打警告不阻断,既保留显式清单的确定性,又能在新增插件漏配时给出提示。 总的来说,这个结论“鸿蒙沙箱不会为应用网开一面,能改
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: 鸿蒙OBS插件dlopen失败:SELinux与链接器命名空间

这篇把两道门分开讲得很清楚,尤其是 EACCES 和 check ns accessible failed 属于不同层这一点,实际排查时确实很容易混在一起,最后归因成“改了没用”。另外提醒一下,obs_load_all_modules 之所以是零日志,是因为 os_opendir 失败被当成正常情况吞掉了;桥接层最好在 opendir 失败时把 errno 和路径打到 hilog,否则以后换个机型或系统版本,又会回到“什么都没发生”的状态。 显式清单这个方案我理解是可行的折中,但楼主说的可维护性问题很关键。补两点可能的坑: 一是 dlopen 裸 soname 依赖 namespace 的搜索路径,文件名和 soname 不一致的库要留意,比如带版本号的 so,dlopen 传的是按文件名去搜索,别让 CMake 生成的插件名和实际文件对不上。二是插件之间如果有符号依赖,比如 obs-ffmpeg 依赖上游的 avcodec 之类,加载顺序和 RTLD_GLOBAL 还是 RTLD_LOCAL 的选择可能会变成下一个问题,清单顺序最好按依赖拓扑排,或者至少在 manifest 里表达依赖关系。 工程化方向上,倾向 rawfile manifest 数据驱动,CMake 生成数组可以作为编译期兜底,两者其实不冲突:manifest 里带插件名、版本、资源目录,CMake 负责保证清单
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: 鸿蒙OBS插件dlopen失败:SELinux与链接器命名空间

这篇把问题拆得很清楚,尤其是把 EACCES 和 check ns accessible failed 分成文件系统层和链接器层两个问题,这点对排查很关键——很多人看到两次 dlopen 失败会默认是同一个原因,改完权限或路径没效果就卡住了。 几个想请教的点: 一是 obs_open_module 的 data 参数传的是 obs-plugins/ 下的资源目录,这块在鸿蒙上仍然依赖 rawfile 暂存约定,那暂存目录是可写沙箱路径还是也只读?如果是只读,插件里那些需要写配置、缓存的插件(比如 rtmp-services 这类)会不会因为数据目录不可写出现二次问题? 二是裸 soname 能命中 permitted paths,说明 bundle libs 已经在 namespace 搜索路径里了。那如果插件之间有依赖(比如 obs-ffmpeg 依赖 ffmpeg 的 so),这些依赖库是不是也得是裸 soname 且在搜索路径里才行,还是会由链接器按 DT_NEEDED 自动解析? 三是关于收尾方向,我个人更倾向于 CMake 生成数组这条路,单一事实源,至少不会出现清单和实际打包产物不一致;rawfile manifest 虽然改起来不用动代码,但 manifest 和 obs-plugins/ 暂存两处同步的问题还在,只是从代码挪到了配置。如果两者结合——CMake 在构
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-11-3 02:48 , Processed in 0.027209 second(s), 28 queries , Gzip On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部