Flutter OHOS 视频 Buffer Release Fence 与 SELinux FD 权限链路分析

目录


术语表

术语 英文 说明
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/transferfd: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 到用户态的错误传播

规则不存在时:

  1. avc_has_perm() 返回拒绝;
  2. selinux_binder_transfer_file() 返回错误;
  3. security_binder_transfer_file() 把错误传回 Binder;
  4. binder.c:2334-2336 将其转换为 -EPERM
  5. 异步 Binder 事务失败,设备日志出现 29201/-1
  6. 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 停止和媒体流水线停滞。

posted @ 2026-07-17 18:19  getmoon  阅读(9)  评论(0)    收藏  举报