Flutter OHOS 视频 Buffer Release Fence 与 SELinux FD 权限链路分析
目录
- 1. 问题与结论
- 2. 参与角色与同步语义
- 3. Flutter Engine 创建并消费视频 Surface
- 4. Flutter Engine 开始释放 Buffer
- 5. OHOS Graphic Surface 如何发送 Fence FD
- 6. Kernel Binder 与 SELinux 的权限判定
- 7. 缺少权限后的故障传播
- 8. 完整源码调用链
- 9. 源码位置速查
- 10. 总结
术语表
| 术语 | 英文 | 说明 |
|---|---|---|
| FD | File Descriptor | Linux 文件描述符,Fence 在跨进程传输时以 FD 表示 |
| EGL | Embedded-System Graphics Library | OpenGL ES 与窗口系统之间的接口层 |
| GPU | Graphics Processing Unit | 执行 Flutter 图形采样和渲染任务的处理器 |
| IPC | Inter-Process Communication | 应用与 av_codec_service 之间的跨进程通信 |
| LSM | Linux Security Modules | Linux 安全模块框架,Binder 通过它调用 SELinux |
| AVC | Access Vector Cache | SELinux 权限查询和缓存机制 |
| CIL | Common Intermediate Language | SELinux Policy 的中间表示形式 |
| SID | Security Identifier | SELinux 内部用于表示安全上下文的标识 |
| GL | OpenGL | Flutter OHOS 外接纹理的一种渲染后端 |
| VK | Vulkan | Flutter Impeller 在 OHOS 上的 Vulkan 渲染后端 |
注:术语按在本文中出现的先后顺序排列。
1. 问题与结论
1.1 目标权限
本文分析下面这条 OpenHarmony SELinux Policy 实际保护的源码路径:
allow av_codec_service normal_hap_attr:fd { use };
它不是允许应用调用 Codec 服务的普通 Binder 权限。普通 Binder 调用由另一条规则控制:
allow av_codec_service normal_hap_attr:binder { call transfer };
binder:call/transfer 与 fd:use 是两个独立的 SELinux Class/Permission。前者存在,并不代表携带 FD 的 Binder 事务一定能够成功。
1.2 关键结论
该权限保护的是以下具体数据流:
Flutter Engine 在应用进程中消费视频 Buffer
→ Flutter GL/Vulkan 创建 GPU Release Fence FD
→ Flutter 调用 OH_NativeImage_ReleaseNativeWindowBuffer()
→ OHOS Graphic Surface 将 Fence FD 写入 ProducerListener Binder 回调
→ av_codec_service 成为 FD 接收者
→ Kernel SELinux 检查 av_codec_service 是否可以 use 应用域来源的 FD
Flutter 侧的正常 Release 起点是:
~/code/flutter/engine_make_3_27_release/src/flutter/shell/platform/ohos/
ohos_external_texture.cpp:593-606
Flutter 与 OHOS Graphic 的边界是:
ohos_external_texture.cpp:478-479
OH_NativeImage_ReleaseNativeWindowBuffer()
最终由 Policy 直接决定的 Kernel 检查点是:
kernel/linux/linux-6.6/security/selinux/hooks.c:2068-2071
rc = avc_has_perm(sid, fsec->sid,
SECCLASS_FD,
FD__USE,
&ad);
2. 参与角色与同步语义
2.1 Producer 与 Consumer
在 Flutter video_player 的 External Texture 链路中,各参与方职责如下。
| 角色 | 进程/模块 | 职责 |
|---|---|---|
| Producer | av_codec_service |
解码视频帧并写入 Surface BufferQueue |
| Consumer | Flutter 应用进程 | 通过 OHOSExternalTexture Acquire、采样和 Release 视频 Buffer |
| Surface 中间层 | Graphic Surface | 管理 Buffer 状态、Fence 和 ProducerListener 回调 |
| 安全判定 | Binder Driver + SELinux | 检查跨进程 FD 是否允许被接收端使用 |
这里容易产生一个命名误区:OH_NativeImage_AcquireNativeWindow() 返回的是供 Producer 使用的 NativeWindow,但 OH_NativeImage 本身位于 Flutter 应用进程,并承担 Consumer 侧的 Acquire/Release。
2.2 Acquire Fence 与 Release Fence
两个 Fence 的方向和所有者不同。
| 维度 | Acquire Fence | Release Fence |
|---|---|---|
| 创建方 | Producer/Codec | Flutter Consumer 的 GL/Vulkan |
| 接收方 | Flutter Consumer | Producer/av_codec_service |
| 语义 | Producer 写完后 Consumer 才能采样 | Consumer/GPU 用完后 Producer 才能复用 |
| Flutter 处理 | WaitGPUFence() |
SetGPUFence() 后随 Release 发送 |
| 本次 Policy 是否直接影响 | 否 | 是 |
目标权限控制的是 Release Fence:它由 Flutter 应用进程创建,随后需要由 av_codec_service 接收和使用。
3. Flutter Engine 创建并消费视频 Surface
3.1 创建 NativeImage Consumer
源码位置:
~/code/flutter/engine_make_3_27_release/src/flutter/shell/platform/ohos/
ohos_external_texture.cpp:68-95
构造函数第 71 行创建 OH_NativeImage:
native_image_source_ = OH_NativeImage_Create(0, GL_TEXTURE_EXTERNAL_OES);
第 77~78 行取得 Producer NativeWindow:
producer_nativewindow_ =
OH_NativeImage_AcquireNativeWindow(native_image_source_);
随后 GetProducerSurfaceId() 将 Surface ID 暴露给上层播放器:
ohos_external_texture.cpp:351-362
OH_NativeImage_GetSurfaceId(native_image_source_, &producer_surface_id_);
播放器把该 Surface 交给 Codec,av_codec_service 因此成为 Buffer Producer,Flutter Engine 成为 Consumer。
3.2 新帧通知与正常 Acquire
Codec 写入新视频帧后,Flutter 收到:
ohos_external_texture.cpp:196
OHOSExternalTexture::MarkNewFrameAvailable()
该函数推进新帧序号并标记 Producer 已有可消费帧:
now_new_frame_seq_num_++;
producer_has_frame_ = true;
Flutter raster thread 绘制时经过:
Paint()
→ GetNextDrawImage()
→ GetConsumerNativeBuffer()
正常 Acquire 位于:
ohos_external_texture.cpp:522-523
int ret = OH_NativeImage_AcquireNativeWindowBuffer(
native_image_source_, &now_nw_buffer, fence_fd);
返回值包括当前视频 Buffer 和 Producer 提供的 Acquire Fence。
3.3 等待 Producer 的 Acquire Fence
GetNextDrawImage() 在创建或复用 DlImage 后执行:
ohos_external_texture.cpp:690-692
WaitGPUFence(fence_fd);
GL 后端通过 eglCreateSyncKHR() 和 eglWaitSyncKHR() 等待;Vulkan 后端把 FD 导入 Semaphore 并纳入 GPU 提交同步。该阶段保证 Flutter 不会在 Codec 尚未完成写入时采样 Buffer。
这条同步方向是 Producer 到 Consumer,不是目标 Policy 的直接作用点。
4. Flutter Engine 开始释放 Buffer
4.1 正常 Release 的准确起点
Flutter 不会在 Acquire 后立即释放当前 Buffer,而是把它保存为 last_native_window_buffer_。下一帧 Acquire 成功后,上一帧才进入 Release。
准确起点位于:
~/code/flutter/engine_make_3_27_release/src/flutter/shell/platform/ohos/
ohos_external_texture.cpp:593
if (last_native_window_buffer_ != nullptr) {
随后第 598~602 行关闭旧 Fence,并创建新的 GPU Release Fence:
if (FdIsValid(last_fence_fd_)) {
close(last_fence_fd_);
}
last_fence_fd_ = -1;
SetGPUFence(last_native_window_buffer_, &last_fence_fd_);
第 605~606 行正式进入统一 Release 封装:
ReleaseWindowBuffer(native_image_source_, last_native_window_buffer_,
&last_fence_fd_, "replace_last_buffer");
这里的 last_fence_fd_ 已不再是 Producer 的 Acquire Fence,而是 Flutter GL/Vulkan 创建的 Release Fence。
4.2 GL 创建 Release Fence FD
GL 实现位于:
~/code/flutter/engine_make_3_27_release/src/flutter/shell/platform/ohos/
ohos_external_texture_gl.cpp:50-103
关键代码是第 77~83 行:
EGLSyncKHR fence_sync =
eglCreateSyncKHR_(disp, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
*fence_fd = eglDupNativeFenceFDANDROID_(disp, fence_sync);
glFlush();
其中第 79 行:
*fence_fd = eglDupNativeFenceFDANDROID_(disp, fence_sync);
在 Flutter 应用进程中生成 Native Fence FD。该 FD 的安全所有者是 Flutter HAP 域,例如 debug_hap;该域属于策略属性 normal_hap_attr。
4.3 Vulkan 创建 Release Fence FD
Vulkan 实现位于:
~/code/flutter/engine_make_3_27_release/src/flutter/shell/platform/ohos/
ohos_external_texture_vulkan.cpp:35-131
第 71~77 行创建可导出为 Sync FD 的 Semaphore:
impeller::vk::ExportSemaphoreCreateInfo export_info;
export_info.setHandleTypes(
impeller::vk::ExternalSemaphoreHandleTypeFlagBits::eSyncFd);
impeller::vk::SemaphoreCreateInfo create_info;
create_info.setPNext(&export_info);
auto ret = device.createSemaphoreUnique(create_info);
第 95~99 行生成 Release Fence:
auto result =
impeller_context_->GetGraphicsQueue()->QueueSignalReleaseImageOHOS(
semaphore_vector,
vk_resources_[buffer_id].texture->GetTextureSource()->GetImage(),
fence_fd);
若 Fence 已经信号完成,第 117~122 行会关闭并改为 -1,避免发送没有等待价值的 FD;未完成的有效 Fence 才进入后续 Binder FD 链路。
4.4 Flutter 与 OHOS Graphic 的边界
所有 Flutter Release 场景最终汇聚到:
~/code/flutter/engine_make_3_27_release/src/flutter/shell/platform/ohos/
ohos_external_texture.cpp:456-492
真正调用 OHOS NativeImage API 的位置是第 478~479 行:
int ret = OH_NativeImage_ReleaseNativeWindowBuffer(
native_image, buffer, *fence_fd);
参数语义如下。
| 参数 | 内容 |
|---|---|
native_image |
Flutter Consumer Surface |
buffer |
Flutter 已完成使用的上一帧视频 Buffer |
*fence_fd |
Flutter GPU Release Fence |
这两行是 Flutter Engine 与 OHOS Graphic Surface 的明确边界。
4.5 其他 Release 场景
统一的 ReleaseWindowBuffer() 不只服务正常绘制路径。
| 场景 | 源码位置 | reason |
|---|---|---|
| 正常替换上一帧 | ohos_external_texture.cpp:605-606 |
replace_last_buffer |
| 慢消费者排空旧帧 | :258-259 |
slow_consumer_drain |
| 尺寸变化替换旧 Buffer | :558-560 |
replace_size_change_buffer |
| 延迟帧跳帧 | :628-629 |
delayed_frame_skip |
| 替换外部 NativeImage 前排空 | :771-772 |
set_external_native_image_drain |
| 销毁时释放最后 Buffer | :896-897 |
destroy_last_buffer |
| 销毁时释放尺寸变化 Buffer | :902-904 |
destroy_size_change_buffer |
| 无有效 Consumer 时直接释放 | :956-957 |
default_no_consumer |
因此缺少 FD 权限不只影响稳定播放,也可能影响丢帧、尺寸变化、Texture 替换和销毁清理。
5. OHOS Graphic Surface 如何发送 Fence FD
5.1 BufferQueue 发起 ProducerListener 回调
Flutter 调用 OH_NativeImage_ReleaseNativeWindowBuffer() 后,Graphic Surface 将 Buffer 状态更新为 Released,并准备通知 Producer。
ProducerListener 调用位置:
foundation/graphic/graphic_surface/surface/src/buffer_queue.cpp:1141-1156
auto ret = listener->OnBufferReleasedWithSequenceAndFence(
requestBuffersAndFence.first,
requestBuffersAndFence.second);
这里的 Listener 远端位于 Buffer Producer,即 Codec 侧。
5.2 SyncFence 写入 MessageParcel
Fence 序列化位置:
foundation/graphic/graphic_surface/sync_fence/src/sync_fence.cpp:277-296
关键代码:
if (fence < 0) {
return true;
}
if (!parcel.WriteFileDescriptor(fence)) {
return false;
}
第 293 行:
parcel.WriteFileDescriptor(fence)
把普通整数 FD 转换为 Binder 可识别的 FD Object。只有 fence >= 0 才会触发该路径,因此无效 Fence -1 无法覆盖本次权限检查。
5.3 异步 Binder 请求
ProducerListener Proxy 位于:
foundation/graphic/graphic_surface/surface/include/
buffer_producer_listener.h:75-101
第 87 行写入 Fence:
fence->WriteToMessageParcel(arguments);
第 89 行设置异步事务:
option.SetFlags(MessageOption::TF_ASYNC);
第 95~96 行发送:
int32_t ret = remote->SendRequest(
IProducerListener::ON_BUFFER_RELEASED_WITH_SEQUENCE_AND_FENCE,
arguments, reply, option);
事务中的 FD 来源于 Flutter 应用进程,接收端是 av_codec_service。
6. Kernel Binder 与 SELinux 的权限判定
6.1 Binder Driver 的 FD 安全 Hook
Binder Driver 处理事务中的 FD 时,执行:
kernel/linux/linux-6.6/drivers/android/binder.c:2326-2336
file = fget(fd);
if (!file) {
ret = -EBADF;
goto err_fget;
}
ret = security_binder_transfer_file(
proc->cred, target_proc->cred, file);
if (ret < 0) {
ret = -EPERM;
goto err_security;
}
第 2333 行通过 Linux Security Modules(LSM)通用接口请求安全判定。如果任一安全模块拒绝,Binder 把结果转为 -EPERM,不再为目标进程安装该 FD。
6.2 SELinux 的最终检查点
SELinux Hook 位于:
kernel/linux/linux-6.6/security/selinux/hooks.c:2053-2088
最关键的第 2068~2071 行:
rc = avc_has_perm(sid, fsec->sid,
SECCLASS_FD,
FD__USE,
&ad);
变量来源:
u32 sid = cred_sid(to);
struct file_security_struct *fsec = selinux_file(file);
因此:
sid是 Binder 接收进程 SID;fsec->sid是 FD 的安全所有者 SID;SECCLASS_FD对应 Policy 的fd;FD__USE对应 Policy 的use。
这就是目标规则直接决定返回值的源码位置。
6.3 Policy 四元组如何映射
本次 Flutter 链路的实际映射如下。
| Policy 元素 | 运行时对象 | 源码变量 |
|---|---|---|
| Source type | av_codec_service |
sid = cred_sid(to) |
| Target type/attribute | Flutter HAP 的 debug_hap,属于 normal_hap_attr |
fsec->sid |
| Class | fd |
SECCLASS_FD |
| Permission | use |
FD__USE |
实际查询等价于:
avc_has_perm(
av_codec_service,
debug_hap/normal_hap_attr,
fd,
use)
对应:
allow av_codec_service normal_hap_attr:fd { use };
这里 Source 是接收 FD 的 av_codec_service,不是发送 FD 的 Flutter 应用。规则方向由“谁要使用该 FD”决定,而不是由 Binder 事务的发送方向决定。
7. 缺少权限后的故障传播
7.1 Kernel 到用户态的错误传播
规则不存在时:
avc_has_perm()返回拒绝;selinux_binder_transfer_file()返回错误;security_binder_transfer_file()把错误传回 Binder;binder.c:2334-2336将其转换为-EPERM;- 异步 Binder 事务失败,设备日志出现
29201/-1; - ProducerListener 无法收到带 Fence 的 Buffer Release 通知。
原始 OHOS 镜像实测 PID 映射为:
Flutter/HAP PID 3166 → av_codec_service PID 575
对应日志:
binder: 3166:5001 transaction async to 575:0 failed .../29201/-1
7.2 BufferQueue 的失败处理
BufferQueue::OnReleaseBufferWithSequenceAndFence() 检查 Listener 返回值:
foundation/graphic/graphic_surface/surface/src/buffer_queue.cpp:1147-1154
auto ret = listener->OnBufferReleasedWithSequenceAndFence(...);
if (ret != GSERROR_OK) {
std::unique_lock<std::mutex> lock(mutex_);
auto iter = bufferQueueCache_.find(requestBuffersAndFence.first);
if (iter != bufferQueueCache_.end()) {
CancelBufferLocked(...);
}
}
失败后执行 CancelBufferLocked(),会使 Buffer 再次进入可请求路径。根因未消失时,这可能形成“请求—回调失败—取消—重新请求”的故障放大。
需要注意:ProducerListener 使用异步 Binder。部分系统版本中,Flutter 侧 OH_NativeImage_ReleaseNativeWindowBuffer() 的同步返回值可能仍然是 0,而真正的远端回调失败稍后才在 Kernel 和 BufferQueue 路径中显现。因此不能仅凭 Flutter API 返回值判断修复是否有效。
7.3 为什么媒体流水线最终停滞
完整因果关系不是“SELinux 直接让 AVPlayer 报错”,而是:
Release Fence FD 被拒绝
→ Codec Producer 没有收到可安全复用 Buffer 的通知
→ Buffer 生命周期无法正常闭环
→ 可用 Buffer 数逐步下降或状态反复
→ Demux/Codec 无法持续获得输出空间
→ AudioSink 空队列、媒体时钟落后、请求 Buffer 失败
原始镜像 120 秒实测:
| 指标 | 结果 |
|---|---|
| 最后成功 Release | 1 |
| HAP → Codec 29201 | 5 |
| AudioSink 空队列 | 1555 |
| Media progress lag | 160 |
Demux ret:61447 |
20 |
加入目标权限后,同一测试 HAP 的 Buffer Release 推进到 6960,目标 FD 翻译成功 27 次,29201 为 0。
8. 完整源码调用链
8.1 成功路径
av_codec_service 解码并 Queue 视频 Buffer
→ Flutter OHOSExternalTexture::MarkNewFrameAvailable()
ohos_external_texture.cpp:196
→ Flutter raster thread 调用 Paint()
ohos_external_texture.cpp:120-194
→ GetNextDrawImage() 调用 GetConsumerNativeBuffer()
ohos_external_texture.cpp:663-664
→ OH_NativeImage_AcquireNativeWindowBuffer()
ohos_external_texture.cpp:522-523
→ WaitGPUFence() 等待 Producer Acquire Fence
ohos_external_texture.cpp:690-692
→ Flutter GPU 采样和绘制 Buffer
→ 下一帧到达,last_native_window_buffer_ 可释放
ohos_external_texture.cpp:593
→ SetGPUFence() 创建 Flutter GPU Release Fence
ohos_external_texture.cpp:598-602
→ GL: eglDupNativeFenceFDANDROID()
ohos_external_texture_gl.cpp:77-83
或 Vulkan: QueueSignalReleaseImageOHOS()
ohos_external_texture_vulkan.cpp:95-99
→ ReleaseWindowBuffer()
ohos_external_texture.cpp:605-606
→ OH_NativeImage_ReleaseNativeWindowBuffer()
ohos_external_texture.cpp:478-479
→ BufferQueue 调用 ProducerListener
buffer_queue.cpp:1147-1148
→ SyncFence::WriteToMessageParcel()
sync_fence.cpp:277-296
→ MessageParcel::WriteFileDescriptor()
sync_fence.cpp:293
→ ProducerListenerProxy::SendRequest(TF_ASYNC)
buffer_producer_listener.h:89-96
→ binder_translate_fd()
binder.c:2301-2362
→ security_binder_transfer_file()
binder.c:2333
→ selinux_binder_transfer_file()
hooks.c:2053-2088
→ avc_has_perm(av_codec_service, normal_hap_attr, fd, use)
hooks.c:2068-2071
→ Policy 允许
→ FD 安装到 av_codec_service
→ Codec 等待 Fence 后安全复用 Buffer
→ Buffer 生命周期闭环
8.2 拒绝路径
Flutter 创建 Release Fence FD
→ OH_NativeImage_ReleaseNativeWindowBuffer()
→ Graphic Surface 通过 ProducerListener 发送 FD
→ Binder security_binder_transfer_file()
→ SELinux 查询 fd:use
→ 缺少 allow av_codec_service normal_hap_attr:fd { use }
→ avc_has_perm() 返回拒绝
→ Binder 转为 -EPERM
→ 异步事务 29201/-1
→ Codec 未收到 Release Fence
→ Buffer 无法正常回到 Producer 可用池
→ 后续 Acquire/Release 停止推进
→ 媒体流水线停滞
9. 源码位置速查
| 层级 | 源码位置 | 关键行为 |
|---|---|---|
| Flutter Surface 创建 | ohos_external_texture.cpp:71-78 |
创建 NativeImage 和 Producer NativeWindow |
| Flutter 新帧入口 | ohos_external_texture.cpp:196-210 |
标记新视频帧可用 |
| Flutter Acquire | ohos_external_texture.cpp:522-523 |
获取视频 Buffer 和 Acquire Fence |
| Flutter 等待 Acquire Fence | ohos_external_texture.cpp:690-692 |
GPU 等待 Producer 完成写入 |
| Flutter 正常 Release 起点 | ohos_external_texture.cpp:593 |
上一帧 Buffer 可释放 |
| Flutter 创建 Release Fence | ohos_external_texture.cpp:598-602 |
调用渲染后端 SetGPUFence() |
| GL Fence FD | ohos_external_texture_gl.cpp:77-83 |
EGL Native Fence FD |
| Vulkan Fence FD | ohos_external_texture_vulkan.cpp:95-99 |
Vulkan Release Image Fence FD |
| Flutter 正式 Release | ohos_external_texture.cpp:605-606 |
调用统一 Release 封装 |
| Flutter/OHOS 边界 | ohos_external_texture.cpp:478-479 |
调用 NativeImage Release API |
| Surface Listener 调用 | buffer_queue.cpp:1147-1148 |
通知 Producer Buffer 已释放 |
| Fence 写入 Binder | sync_fence.cpp:293 |
WriteFileDescriptor() |
| 异步 IPC | buffer_producer_listener.h:89-96 |
ProducerListener SendRequest() |
| Binder FD Hook | binder.c:2333 |
调用 LSM 检查 |
| SELinux 最终判定 | hooks.c:2068-2071 |
SECCLASS_FD + FD__USE |
| Policy 源码位置 | av_codec_service.te:112 附近 |
需补充 normal_hap_attr:fd use |
10. 总结
这条权限的本质可以概括为:
允许 av_codec_service 使用 Flutter HAP 创建的 GPU Release Fence FD。
它保护的不是 Flutter 调用 Codec 的普通业务接口,而是视频 Buffer 生命周期闭环中的反向同步通道:
Codec 写 Buffer
→ Flutter 等待 Acquire Fence
→ Flutter GPU 使用 Buffer
→ Flutter 创建 Release Fence
→ Codec 接收 Fence 后复用 Buffer
关键边界只有三处:
| 边界 | 源码 | 含义 |
|---|---|---|
| Flutter 开始 Release | ohos_external_texture.cpp:593-606 |
为上一帧创建 Release Fence 并释放 |
| Flutter 进入 OHOS Graphic | ohos_external_texture.cpp:478-479 |
调用 NativeImage Release API |
| SELinux 最终决策 | hooks.c:2068-2071 |
判断 av_codec_service → HAP FD:use |
最终规则:
allow av_codec_service normal_hap_attr:fd { use };
如果缺少它,普通 Binder call 仍可能成功,但一旦 ProducerListener 事务携带 Flutter 创建的 Release Fence FD,就会在 Kernel FD 安全检查处失败,表现为 29201、Buffer Release 停止和媒体流水线停滞。

浙公网安备 33010602011771号