OpenHarmony 沙箱机制+分布式文件总结
适用范围:OpenHarmony 5.0.1标准系统(init 服务层 + appspawn 应用层)
参考代码:
//base/startup/init、//base/startup/appspawn路径约定:文中
//xxx/...表示 OpenHarmony 源码根路径;/xxx/...表示设备运行时路径)关联案例:
- 基础对照 :
//foundation/filemanagement/dfs_service/services/distributedfile.cfg中distributedfiledaemon的sandbox: 0(官方 dfs 服务,需访问动态挂载的/mnt/hmdfs/)
0. 各操作系统沙箱路径对比
说明 :本节用于建立直觉,便于从路径视角理解 OH 沙箱;各系统实现细节、版本差异较大,不作为安全审计或合规依据。OH 机制以 §2--§5 及源码为准。
沙箱并非 OpenHarmony 独有,但各系统的隔离粒度、路径约定、是否强制差异很大。
0.1 总览对比
| 操作系统 | 是否有沙箱 | 系统服务/守护进程 | 应用/第三方程序 | 典型沙箱数据路径 | 核心隔离手段 |
|---|---|---|---|---|---|
| OpenHarmony | ✅ 有(双层) | init 服务沙箱,可配 sandbox: 0/1 |
HAP 默认进 appspawn 沙箱 | 服务:/mnt/sandbox/system 应用默认根:/mnt/sandbox/<userId>/<PackageName> 应用数据(进程内视角):/data/storage/el2/base/ |
mount namespace;应用层可选 pid/net namespace |
| Android | ✅ 有(应用强制) | 系统服务按 uid/SELinux 域隔离,无统一「沙箱路径」 | 第三方 APK 强制沙箱 | /data/data/<包名>/ 多用户:/data/user/<uid>/<包名>/ |
Linux uid 隔离 + SELinux + mount namespace(Android 7+) |
| Linux(桌面/服务器) | ⚠️ 可选,非默认 | 一般无沙箱,以 uid/gid + 文件权限为主 | 原生程序默认无应用沙箱 | 用户目录:/home/<user>/ 容器:/var/lib/docker/... |
discretionary ACL;可选:AppArmor、SELinux、namespace、chroot |
| Windows | ⚠️ 部分有 | Windows 服务以 Service Account 运行,非路径沙箱 | UWP/商店应用有沙箱;传统 Win32 默认无 | UWP:%LOCALAPPDATA%\Packages\<包族名>\LocalState Win32:无固定沙箱路径 |
AppContainer、完整性级别(MIC);Win32 靠用户权限 |
| macOS | ✅ 有(应用级) | launchd 系统服务,权限较宽 | 签名应用可启用 App Sandbox(商店/公证常见) | ~/Library/Containers/<BundleID>/Data/ 临时:/private/var/folders/... |
sandbox-exec(Seatbelt)+ entitlement |
| iOS / iPadOS(苹果移动端) | ✅ 有(最严格) | 系统进程高度受限 | 所有 App 强制沙箱,无法关闭 | 容器:/var/mobile/Containers/Data/Application/<UUID>/ (开发者通过 API 访问,不直接暴露路径) |
内核强制 + entitlement;无用户可选关闭 |
0.2 路径视角详细对照
| 对比维度 | OpenHarmony | Android | Linux | Windows | macOS | iOS |
|---|---|---|---|---|---|---|
| 应用私有数据 | /data/storage/el2/base/ |
/data/data/<包名>/ |
~/ 或 $XDG_DATA_HOME |
UWP 包目录内 | ~/Library/Containers/<BundleID>/ |
App 容器 Documents 等 |
| 应用安装目录 | /system/app/<PackageName>/(只读映射) |
/data/app/...(系统管理) |
/usr/bin、/opt/... |
Program Files\ |
/Applications/<App>.app |
只读 App Bundle |
| 沙箱根目录(假根) | 服务:/mnt/sandbox/system;应用默认 /mnt/sandbox/<userId>/<PackageName>(//base/startup/appspawn/modules/sandbox/sandbox_utils.cpp) |
无统一 pivot 假根,主要靠 uid 隔离数据目录 | 容器内 /(Docker 等) |
UWP 虚拟化文件系统 | 容器 Data 子树 | 整个 App 容器 |
| 系统服务数据 | /data/service/el1/... |
/data/system/、各服务私有目录 |
/var/lib/...、/etc/ |
C:\Windows\System32\... |
/Library/... |
系统分区(App 不可达) |
| 跨应用共享文件 | remotefileshare / HMDFS |
ContentProvider、SAF | 共享目录 + 权限 | 公共文档库 | App Group 容器 | App Group、DocumentPicker |
| 能否关闭应用沙箱 | 仅系统集成可关(sandbox-switch: OFF) |
否(第三方 APK) | N/A(默认无沙箱) | UWP 不可关;Win32 本身无沙箱 | 开发者可选择是否启用(上架常强制) | 否 |
| 系统服务能否不进沙箱 | ✅ sandbox: 0(如 dfs、需直访 HMDFS 的业务 SA) |
系统服务通常高权限运行 | 默认即全局视野 | 服务以 SYSTEM 等高权限账户运行 | 系统守护进程无 App Sandbox | 系统守护进程无 App Sandbox |
0.3 与 OpenHarmony 的关键差异
text
OpenHarmony 的独特之处
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
双层沙箱体系 服务可显式豁免 分布式文件路径复杂
init(服务)+ cfg 中 sandbox:0/1 HAP:distributedfiles
appspawn(应用) 不像 Android 应用 SA:/mnt/hmdfs/device_view
那样一律强制
| 差异点 | 说明 |
|---|---|
| 双层 vs 单层 | OH 对系统 SA 和 HAP 应用分别用 init / appspawn 两套沙箱;Android 主要约束应用层,系统服务靠 SELinux 域区分 |
| 服务可退出沙箱 | OH 的 sandbox: 0 是显式配置项;Android/Linux 系统服务通常以高权限账户直接运行,概念上类似「不进沙箱」但无统一开关 |
| 路径假根 | 服务沙箱:PrepareSandbox 阶段 pivot_root 到 /mnt/sandbox/system;应用沙箱默认 /mnt/sandbox/<userId>/<PackageName> |
| 与 Android 的相似性 | 均基于 Linux 内核;OH 应用层沙箱(mount namespace + uid + SELinux)设计思路与 Android 接近,但服务层多了 init sandbox |
| 与苹果生态的相似性 | iOS/macOS 应用沙箱同样强制、靠 entitlement 授权额外能力;OH 的 permission + module.json5 与之类似 |
| Linux 桌面差异 | 传统 Linux 原生程序默认无沙箱,自由度最高、隔离最弱;需 Flatpak/Snap/容器等额外机制 |
0.4 读本文档时的定位
- 若熟悉 Android :把 OH 应用沙箱类比为「强制 mount namespace + uid 隔离」;额外关注 init 服务层
sandbox: 0/1(Android 应用开发者通常接触不到这层)。 - 若熟悉 iOS/macOS :把 OH
permission类比为 entitlement;context.filesDir类比为 App 沙箱容器路径 API。 - 若熟悉 Linux 桌面:OH 沙箱比传统 Linux 严格得多,更接近 Android/iOS 的移动安全模型,而非 Ubuntu 默认体验。
- 若熟悉 Windows :OH 应用沙箱类似 UWP AppContainer;
sandbox: 0的系统服务类似以 SYSTEM/管理员权限运行的 Windows 服务。
1. 为什么需要沙箱
1.1 背景问题
OpenHarmony 设备上同时运行大量互不信任的代码:系统服务、第三方 HAP、厂商扩展组件。若所有进程共享同一套文件系统视图,一旦某个组件被攻破或存在漏洞,攻击者可以:
- 读取其他应用/服务的私有数据(通讯录、密钥、业务缓存)
- 篡改系统配置或关键二进制
- 横向移动到其他进程(读写
/data、挂载点、设备节点) - 持久化驻留或提权
沙箱的核心诉求是:在操作系统层面,把「谁能看到什么」限制在最小必要范围内。
1.2 沙箱最大的作用
沙箱的最大作用可以概括为一句话:
通过隔离进程的文件系统视图(及可选的 pid/net namespace),实现最小权限与故障/攻击 containment(把影响限制在边界内)。
具体体现在三个层面:
| 作用 | 说明 | 举例 |
|---|---|---|
| 隔离(Isolation) | 每个进程只能看到自己沙箱内的路径 | 应用 A 无法直接读应用 B 的 /data/storage/el2/base/ |
| 最小权限(Least Privilege) | 只挂载业务必需的白名单路径 | 普通 SA 只能看 /system、/data 等预置目录 |
| 遏制扩散(Containment) | 单点被攻破后,难以访问沙箱外资源 | 普通 HAP 通常无法直接对 HMDFS 执行底层 ioctl(还受权限/SELinux 约束) |
沙箱不是 加密、不是 身份认证,也不能替代 SELinux / AccessToken / uid 隔离------它们与沙箱叠加,构成纵深防御。
text
纵深防御栈(由外到内):
应用权限(AccessToken / module.json5)
↓
SELinux(secon 标签,**若产品启用**)
↓
uid/gid + Linux capabilities
↓
沙箱(mount namespace 文件视图隔离) ← 本文重点
↓
内核(HMDFS / devsl / ioctl 权限)
1.3 什么时候必须关心沙箱
| 场景 | 是否必须显式配置 |
|---|---|
| 新增系统 SA 服务 | 是(*.cfg 中 sandbox) |
| 新增 HAP / ArkUI 应用 | 通常否(默认进应用沙箱) |
访问 /mnt/hmdfs/、ioctl share |
是(服务层 sandbox: 0 + gid/permission) |
插件 .so 随宿主加载 |
继承宿主,不单独配置 |
hdc shell 调试命令 |
否(见下表 §1.3.1) |
1.3.1 hdc shell 到底有没有沙箱?
直接回答:没有。 hdc shell 执行的命令既不进 init 服务沙箱,也不进 appspawn 应用沙箱。
| 沙箱类型 | hdc shell 是否进入 |
说明 |
|---|---|---|
| init 服务沙箱 | ❌ 不进 | 命令由 hdcd fork 出 /system/bin/sh 执行;hdcd 在 cfg 中 sandbox: 0(//developtools/hdc/src/daemon/etc/hdcd.cfg),子进程继承其 全局 mount namespace |
| appspawn 应用沙箱 | ❌ 不进 | 不经过 appspawn,不会进入 /mnt/sandbox/<userId>/<PackageName>/ |
| uid / SELinux 限制 | ✅ 仍有 | cfg 中 hdcd 默认 uid: shell;实测 调试产品(const.debuggable=1)上 hdc shell 与 hdcd 进程均为 root (persist.hdc.root 未设置时 hdc 默认保持 root)。无论 uid 为何,均不进 init/appspawn 沙箱 |
text
hdc shell ls /mnt/hmdfs/
│
├─ 不是 HAP → 无应用沙箱
├─ 不是 init sandbox:1 服务 → 无服务沙箱裁剪
└─ 是 shell 用户 + 全局文件系统视图 → 所以常能直接看到 /mnt/hmdfs/
(与 HAP 内 context.distributedFilesDir 视角完全不同)
「继承 shell 上下文」的旧说法 :指进程以
shell用户身份、在hdcd的全局 mount namespace 中运行,不是指进了某种沙箱。旧表述易误解,已改为上表。
开发者误区 :shell 里能 ls 到的路径,不能推断 HAP 应用内也能访问。
2. 总览
OpenHarmony 的「沙箱」不是单一机制,而是两套独立体系,分别约束不同运行形态的进程:
| 维度 | 服务层沙箱(init) | 应用层沙箱(appspawn) |
|---|---|---|
| 约束对象 | 原生系统服务(SA / daemon) | HAP 应用进程 |
| 配置入口 | *.cfg 中 "sandbox": 0/1(服务 cfg 位于子系统 etc/ 或 //vendor/.../etc/init/) |
源码 //base/startup/appspawn/appdata-sandbox.json,设备 /etc/sandbox/*.json |
| 管理者 | init 进程 | appspawn / nwebspawn |
| 隔离手段 | mount namespace(服务:PrepareSandbox + EnterSandbox) |
mount namespace + bind mount(应用) |
| 典型路径 | 服务:/mnt/sandbox/system |
应用:/mnt/sandbox/<userId>/<PackageName> |
| 数据目录 | /data/service/... |
/data/storage/el2/base/... |
text
init (PID 1) appspawn (sandbox: 0)
│ │
├── example_business_sa ├── 拉起 HAP 进程
│ sandbox: 0 │ → SandboxUtils::SetAppSandboxProperty()
├── distributedfiledaemon │ → 进入 /mnt/sandbox/<userId>/<包名>/
│ sandbox: 0 │
└── 某系统服务 └── 应用只能看白名单路径
sandbox: 1
→ EnterSandbox("system")
核心原则 :沙箱是隔离 (限制每个进程能看什么),不是共享工作区(大家进同一个沙箱互访)。
3. 服务层沙箱(init Service Sandbox)
3.1 机制说明
服务层沙箱由 init 实施,通过 Linux mount namespace 隔离文件系统视图。完整流程分两段(均在 //base/startup/init/services/sandbox/sandbox.c):
- 启动阶段 (
PrepareSandbox):在/mnt/sandbox/<name>/建沙箱根,按 JSON 白名单 bind mount,并pivot_root到该根目录 - 服务拉起阶段 (
SetServiceEnterSandbox→EnterSandbox):子进程fork后、exec前,切换进已建好的 mount namespace
子进程不会再次 执行 pivot_root;它进入的是启动阶段已准备好的沙箱视图。
init 进程自身不在沙箱内 ;sandbox 配置只作用于 init 拉起的子服务。
3.2 在沙箱内 vs 不在沙箱内
可以把「在沙箱内 / 不在沙箱内」理解成:进程看到的文件系统是不是被「裁剪过」。
一句话区别
| 状态 | 本质 |
|---|---|
| 不在沙箱内 | 进程与系统共用同一套 mount namespace ,/mnt/hmdfs/ 等后续挂载都能实时看到 |
| 在沙箱内 | 进程拥有独立的 mount namespace ,根目录被切到 /mnt/sandbox/system,只能看到白名单内的路径 |
进程视角对比
text
【不在沙箱内】example_business_sa (sandbox: 0)
进程视角的 "/" = 系统真实根目录
/mnt/hmdfs/... ← dfs 后来 mount 的,能立刻看到
【在沙箱内】某普通 SA (sandbox: 1)
进程视角的 "/" = /mnt/sandbox/system/(假根)
/system、/data ← 白名单 bind mount 进来的
/mnt ← 启动时快照,private 传播
/mnt/hmdfs/... ← dfs 后来 mount 的,看不到 ❌
init 拉起子服务时做了什么
init 在 fork 之后、exec 之前调用 SetServiceEnterSandbox()(//base/startup/init/services/init/standard/init_service.c):
c
// sandbox: 0 → 直接 return,不做任何事
if (service->attribute & SERVICE_ATTR_WITHOUT_SANDBOX) {
return 0; // 子进程继续用 init 的 mount namespace
}
// sandbox: 1 → 子进程切换进已准备好的沙箱 mount namespace
EnterSandbox("system"); // SetNamespace(CLONE_NEWNS),见 sandbox.c
→ 沙箱视图在启动阶段 PrepareSandbox 中已 pivot_root 建好
→ 白名单来自 //base/startup/init/services/sandbox/system-sandbox64.json
→ 子进程 "/" 对应沙箱内的文件系统视图
init 自身从不调用 EnterSandbox(),原因:
- init 是 PID 1,负责 mount 全局文件系统、创建
/mnt/sandbox/、拉起所有服务 - 若 init 也进沙箱,会被限制在白名单内,无法管理后续动态挂载
- init 是沙箱的管理者,不是被管理者
四个维度对比
| 维度 | 不在沙箱内(sandbox: 0) |
在沙箱内(sandbox: 1) |
|---|---|---|
| mount namespace | 与 init / 宿主机共享 | 独立 namespace |
根目录 / |
系统真实根 | /mnt/sandbox/system(假根) |
| 可见路径 | 全局文件系统(仍受 uid/selinux 约束) | 仅 //base/startup/init/services/sandbox/system-sandbox64.json 白名单 |
| 动态挂载 | /mnt/hmdfs/ 等后续 mount 可见 |
private bind 时不可见(启动快照) |
| 安全性 | 较低(视野大) | 较高(视野小,最小权限) |
| 典型服务 | distributedfiledaemon、需访问 HMDFS 的业务 SA |
可进沙箱的 /system/bin/ SA(多数需动态挂载的系统服务实际仍设 0) |
需访问 HMDFS 的 SA 实例
某业务 SA 设 sandbox: 0(不在沙箱内)的典型表现:
bash
# 进程内能直接看到 dfs 动态挂载的路径
ls /mnt/hmdfs/100/account/device_view/local/services/<service_name>/.share/
ioctl(fd, HMDFS_IOC_SET_SHARE_PATH, ...) # 能操作
若误设 sandbox: 1(在沙箱内):
bash
# 沙箱内 /mnt 是启动时 private bind 的快照
ls /mnt/hmdfs/ # 目录不存在或为空
ioctl(...) # ENOENT,share 注册失败
与 uid / SELinux 的区别
「不在沙箱内」不等于「无限制」:
text
example_business_sa (sandbox: 0)
├── mount namespace:全局可见(不进沙箱)
├── uid/gid:业务账户 / dfs_share(身份限制)
├── SELinux:u:r:<service>_domain:s0(标签限制)
└── permission:DISTRIBUTED_DATASYNC(令牌限制)
沙箱管的是文件系统视野 ;uid、SELinux、permission 管的是在这个视野里能做什么 。sandbox: 0 只是视野不被裁剪,其余安全机制仍然生效。
类比
| 角色 | 类比 |
|---|---|
| init | 大楼管理员,有全部钥匙,能开所有门、装新设备 |
| sandbox: 1 的服务 | 租户,只能进自己房间和白名单公共区域 |
| sandbox: 0 的服务 | 物业维修工,能进大楼所有区域,但仍需工牌(uid/selinux) |
init 必须是管理员,否则无法给各服务建沙箱、做全局 mount;普通服务默认当租户,需要更大权限时才设 sandbox: 0。
3.3 生效条件(两层开关)
服务是否真正进入沙箱,需同时满足:
| 层级 | 条件 | 说明 |
|---|---|---|
| 系统开关 | const.sandbox=enable |
读自系统参数(init_service.c 中 SystemReadParam("const.sandbox"));未 enable 时 sandbox: 1 不生效 |
| 服务开关 | cfg 中 "sandbox": 1(非 0) |
sandbox: 0 会设置 SERVICE_ATTR_WITHOUT_SANDBOX 并直接跳过 |
| 可执行路径 | path[0] 以 /system/bin/ 或 /vendor/bin/ 开头 |
否则 SetServiceEnterSandbox 不会 调用 EnterSandbox(见下方源码) |
省略 sandbox 字段时 :GetServiceSandbox 不设置 WITHOUT_SANDBOX 标志(服务 calloc 初始为 0),在 const.sandbox=enable 且路径匹配时会尝试进沙箱 。建议显式写 0 或 1。
源码逻辑(//base/startup/init/services/init/standard/init_service.c):
c
// sandbox: 0 → MarkServiceWithoutSandbox → 直接返回,不进沙箱
if ((service->attribute & SERVICE_ATTR_WITHOUT_SANDBOX) == SERVICE_ATTR_WITHOUT_SANDBOX) {
return 0;
}
// const.sandbox 未 enable → 也不进沙箱
if (g_enableSandbox == false) {
return 0;
}
// 按可执行文件路径选择沙箱类型
if (strncmp(execPath, "/system/bin/", ...) == 0) {
EnterSandbox("system"); // system-sandbox64.json
} else if (strncmp(execPath, "/vendor/bin/", ...) == 0) {
EnterSandbox("chipset"); // chipset-sandbox64.json
}
// 路径不在上述前缀 → 返回 0,实际不进沙箱(即使 cfg 写了 sandbox:1)
3.4 配置方式
在服务的 *.cfg 文件中设置(示例为需访问 HMDFS 的业务 SA):
json
{
"services": [{
"name": "example_business_service",
"path": ["/system/bin/sa_main", "/system/profile/example_business_service.json"],
"uid": "business",
"gid": ["business", "shell", "dfs_share"],
"secon": "u:r:example_business_service:s0",
"sandbox": 0,
"permission": ["ohos.permission.DISTRIBUTED_DATASYNC", "..."]
}]
}
sandbox 值 |
含义 | 典型场景 |
|---|---|---|
**0** |
设置 SERVICE_ATTR_WITHOUT_SANDBOX,不进 init 沙箱 |
需访问动态挂载路径的系统服务(HMDFS、存储等) |
**1**(或非 0) |
清除 WITHOUT_SANDBOX;在 const.sandbox=enable 且路径为 /system/bin/ 或 /vendor/bin/ 时进沙箱 |
仅需白名单内路径的 SA |
| 省略 | 等同未标记 WITHOUT_SANDBOX;满足条件时会进沙箱 |
务必显式写 0 或 1,避免歧义 |
注意 :只有
0和「非 0」两种有效语义,没有2、3等级 。非 0 一般写1。
3.5 沙箱策略文件
系统预置沙箱白名单,源码位于 //base/startup/init/services/sandbox/,打包后位于设备 /etc/sandbox/:
| 源码文件 | 设备路径 | 沙箱根 | 适用服务 |
|---|---|---|---|
//base/startup/init/services/sandbox/system-sandbox64.json |
/etc/sandbox/system-sandbox.json |
/mnt/sandbox/system |
/system/bin/ 下的服务 |
//base/startup/init/services/sandbox/chipset-sandbox64.json |
/etc/sandbox/chipset-sandbox.json |
/mnt/sandbox/chipset |
/vendor/bin/ 下的服务 |
白名单结构示例(//base/startup/init/services/sandbox/system-sandbox64.json 节选):
json
{
"sandbox-root": "/mnt/sandbox/system",
"mount-bind-paths": [{
"src-path": "/system/bin",
"sandbox-path": "/system/bin",
"sandbox-flags": ["bind", "rec", "private"]
}, {
"src-path": "/data",
"sandbox-path": "/data",
"sandbox-flags": ["bind", "rec", "private"]
}, {
"src-path": "/mnt",
"sandbox-path": "/mnt",
"sandbox-flags": ["bind", "rec", "private"]
}]
}
| 字段 | 含义 |
|---|---|
sandbox-root |
沙箱进程的「假根」目录 |
src-path |
宿主机真实路径 |
sandbox-path |
沙箱内映射路径 |
sandbox-flags |
挂载传播属性;private 表示后续新挂载不传播进沙箱 |
3.6 为什么很多系统服务设 sandbox: 0
嵌入式 OH 产品上,大量系统服务使用 sandbox: 0,例如:
| 服务 | 源码 cfg | sandbox | 原因 |
|---|---|---|---|
distributedfiledaemon |
//foundation/filemanagement/dfs_service/services/distributedfile.cfg |
0 |
需访问 /mnt/hmdfs/ 动态挂载、负责 HMDFS 挂载 |
hilogd |
//base/hiviewdfx/hilog/services/hilogd/etc/hilogd.cfg |
0 |
需写 /log、读全局设备节点 |
storage_daemon |
//foundation/filemanagement/storage_service/services/storage_daemon/storage_daemon.cfg |
0 |
管理存储挂载 |
| 需直访 HMDFS 的业务 SA | 对应服务的 *.cfg(置于产品子系统或 //vendor/.../etc/init/) |
0 |
ioctl share、读 device_view 路径 |
典型踩坑 :HMDFS 在 dfs_service 启动后才 mount 到 /mnt/hmdfs/。若服务 sandbox: 1 且 /mnt 以 private 绑定,沙箱内看到的是启动时刻的快照,看不到后续动态挂载。
3.7 服务层其他安全约束(与 sandbox 并行)
sandbox: 0 不等于无安全限制,服务仍受以下约束:
| 机制 | 配置项 | 作用 |
|---|---|---|
| 用户/组 | uid、gid |
进程身份 |
| SELinux | secon |
强制访问控制标签 |
| 能力 | caps |
Linux capabilities |
| 权限 | permission |
OH 访问令牌(AccessToken) |
| APL | apl |
应用权限等级(system_core / system_basic 等) |
4. 应用层沙箱(appspawn App Sandbox)
4.1 机制说明
HAP 应用由 appspawn 进程孵化,启动时自动构建应用沙箱:
- 为每个应用创建沙箱目录,默认根路径
/mnt/sandbox/<userId>/<PackageName>(userId = uid / 200000,见sandbox_utils.cpp) - 按
//base/startup/appspawn/appdata-sandbox.json白名单 bind mount 所需路径 - 按配置创建 namespace:
sandbox-ns-flags可含net、pid(默认app-base仅配置net,见源码 JSON) - 应用数据目录在进程内映射为
/data/storage/el2/base/、/data/storage/el2/distributedfiles/等
appspawn 自身在 cfg 中也是 sandbox: 0(需管理所有应用沙箱):
json
// 源码://base/startup/appspawn/appspawn.cfg
{
"name": "appspawn",
"path": ["/system/bin/appspawn", "-mode appspawn", "--sandbox-switch on", "..."],
"sandbox": 0
}
4.2 沙箱策略文件
| 源码文件 | 设备路径 | 用途 |
|---|---|---|
//base/startup/appspawn/appdata-sandbox.json |
/etc/sandbox/appdata-sandbox.json |
普通应用沙箱主配置 |
//base/startup/appspawn/appdata-sandbox-isolated.json |
/etc/sandbox/appdata-sandbox-isolated.json |
隔离模式应用 |
vendor overlay(如 //vendor/<产品名>/appspawn/appdata-sandbox.json) |
覆盖设备 /etc/sandbox/ |
按包名覆盖挂载规则 |
//base/startup/appspawn/appdata-sandbox.json 结构(节选):
json
// 节选://base/startup/appspawn/appdata-sandbox.json(common.app-base)
{
"common": [{
"top-sandbox-switch": "ON",
"app-base": [{
"sandbox-ns-flags": ["net"],
"mount-paths": [{
"src-path": "/system/app",
"sandbox-path": "/system/app",
"sandbox-flags": ["bind", "rec"]
}]
}]
}]
}
sandbox-root未在app-base中配置时,运行时默认/mnt/sandbox/<userId>/<PackageName>(//base/startup/appspawn/modules/sandbox/sandbox_utils.cpp→GetSandboxRootPath)。
| 字段 | 含义 |
|---|---|
top-sandbox-switch |
全局沙箱总开关:ON / OFF |
sandbox-root |
可选;未配置时默认 /mnt/sandbox/<userId>/<PackageName>;配置 <PackageName> 占位符时运行时替换 |
sandbox-ns-flags |
额外 namespace:pid(进程隔离)、net(网络隔离) |
mount-paths |
应用可见路径白名单 |
sandbox-switch |
单个应用/场景级开关(可覆盖) |
4.3 应用层如何配置是否进沙箱
应用层没有 类似服务 cfg 的 sandbox: 0/1 单字段,而是通过多层配置组合:
| 配置层 | 源码位置 | 作用 |
|---|---|---|
| 全局开关 | //base/startup/appspawn/appdata-sandbox.json → top-sandbox-switch |
ON:默认所有应用进沙箱;OFF:全局关闭 |
| 应用级开关 | 同文件或 vendor overlay 中 sandbox-switch |
按包名/场景单独 ON/OFF |
| appspawn 启动参数 | //base/startup/appspawn/appspawn.cfg → --sandbox-switch on/off |
appspawn 守护进程默认策略 |
| 包类型 | HAP 包 module.json5 属性(normal / system / privileged 等) |
影响沙箱严格程度和挂载范围 |
| 隔离模式 | //base/startup/appspawn/appdata-sandbox-isolated.json |
更严格的隔离策略 |
应用开发者通常不直接改 sandbox 开关,而是通过:
module.json5声明权限与requestPermissions- 使用应用沙箱内合法路径(如
context.filesDir) - 分布式文件通过
remotefileshare/distributedfiles路径访问(注意 :@ohos.remotefileshare为产品集成 NAPI,标准公共 SDK 无此模块,见 §4.6.4)
4.4 应用沙箱内的路径视角
| 应用看到的路径 | 实际对应 |
|---|---|
/data/storage/el2/base/ |
应用私有数据目录 |
/data/storage/el2/distributedfiles/ |
分布式文件目录 |
/data/storage/el2/distributedfiles/.share/ |
HAP 侧 share 注册路径(对比 SA 的 ioctl 方式) |
/system/app/<PackageName>/ |
系统应用安装目录(只读映射;三方应用路径因安装方式而异) |
应用无法直接访问(未经沙箱映射的系统级路径):
/mnt/hmdfs/.../device_view/{networkId}/services/...(系统 HMDFS 真实消费路径)/data/service/(系统服务数据)- 其他应用的数据目录
注:应用沙箱会将
/mnt/hmdfs/<userId>和distributedfiles做有限 bind mount(见 §5.5),但不能 替代 SA 层的device_view+ioctl能力。
4.5 应用层其他安全约束
| 机制 | 说明 |
|---|---|
| AccessToken | module.json5 声明的 requestPermissions |
| UID 隔离 | 每个应用独立 uid |
| SELinux | 应用进程有独立 secon 标签 |
| sandbox_manager | 跨应用文件访问策略(persist policy) |
| 沙箱路径校验 | 应用只能读写自己沙箱内的路径 |
4.6 案例:分布式音视频 HAP 与分布式文件
本节以典型的分布式音视频流转 HAP 为例,说明应用沙箱内如何使用分布式文件;不涉及具体产品包名与源码路径。
4.6.1 核心结论:应用不需要关闭沙箱
此类 HAP 没有 任何 sandbox-switch: OFF 或 vendor overlay 豁免配置。它默认留在应用沙箱内 ,通过系统预置的 distributedfiles 路径映射 + 框架 API 使用分布式文件。
text
HAP 使用分布式文件 ≠ 关闭沙箱
HAP 使用分布式文件 = 在应用沙箱内,走合法路径 + remotefileshare API
与需直访 device_view 的业务 SA(sandbox: 0 + ioctl)是两条不同路线,见 §5.6、§7。
4.6.2 系统侧(应用开发者通常不改)
应用能访问分布式文件,依赖系统 //base/startup/appspawn/appdata-sandbox.json 中已预置的 bind mount(应用进沙箱时自动生效):
| 应用进程内路径(代码中使用) | 宿主机 HMDFS 映射 |
|---|---|
/data/storage/el2/distributedfiles |
/mnt/hmdfs/<userId>/account/merge_view/data/<PackageName> |
/data/storage/el2/distributedfiles/.share/ |
同上目录下的 .share 子目录(share 注册落点) |
/mnt/hmdfs/<userId> |
同路径有限 bind mount |
应用开发者不需要 也无法 在 HAP 包里修改上述系统沙箱白名单;若产品缺映射,需系统集成工程师改
appdata-sandbox.jsonoverlay。
板端实测 (userId=100):com.ohos.launcher的 mountinfo 显示
/data/storage/el2/distributedfiles← bind ←/mnt/hmdfs/100/account/merge_view/data/com.ohos.launcher(见 §9.2.3)。
4.6.3 应用侧必须配置(典型做法)
① 声明权限 (module.json5 → requestPermissions):
json
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.DISTRIBUTED_DATASYNC" },
{ "name": "ohos.permission.ACCESS_SERVICE_DM" },
{ "name": "ohos.permission.MANAGE_DISTRIBUTED_ACCOUNTS" },
{ "name": "ohos.permission.INTERNET" },
{ "name": "ohos.permission.GET_NETWORK_INFO" },
{ "name": "ohos.permission.READ_MEDIA" }
]
}
}
② 运行时申请关键权限(Stage 模型推荐):
typescript
import abilityAccessCtrl from '@ohos.abilityAccessCtrl';
const atManager = abilityAccessCtrl.createAtManager();
await atManager.requestPermissionsFromUser(context, ['ohos.permission.DISTRIBUTED_DATASYNC']);
FA 模型可使用
context.requestPermissionsFromUser(标注@FAModelOnly);Stage 模型请用abilityAccessCtrl。
③ 只使用沙箱内合法路径 (禁止硬编码 /mnt/hmdfs/.../device_view/):
| 用途 | 典型路径常量 |
|---|---|
| 本地媒体资源 | /data/storage/el1/bundle/ 或 context.filesDir 下路径 |
| 分布式 share 目录 | /data/storage/el2/distributedfiles/.share |
| 对端流转播放路径 | want.parameters.filePath(如 .../distributedfiles/.share/xxx.mp4) |
④ 通过框架 API 注册 share (不是 应用内 ioctl):
typescript
import remotefileshare from '@ohos.remotefileshare'
// fileId 为本地文件 fd(先 open 沙箱内路径获得)
let sharePath = await remotefileshare.createSharePath(fileId, targetDeviceId)
// 返回:/data/storage/el2/distributedfiles/.share/...
⑤ 创建分布式目录 & 设置安全标签(Stage 模型):
typescript
// Stage:直接读 context.distributedFilesDir(映射到 distributedfiles)
const distDir = context.distributedFilesDir;
const fullpath = distDir + '/.share/' + fileName;
securityLabel.setSecurityLabel(fullpath, 's0');
FA 模型可使用
context.getOrCreateDistributedDir()(同为@FAModelOnly);路径语义与 Stage 的distributedFilesDir一致。
4.6.4 SDK 与系统库依赖(标准 OH vs 产品集成)
HAP 包内通常无自研 native 库 :此类应用多为纯 ArkTS/ETS;分布式文件能力来自系统镜像 中的 native 模块,运行时由 requireNapi 加载 /system/lib/module/libremotefileshare.z.so(模块名以产品集成为准)。
依赖分层(典型 import 结构):
| 层级 | 模块类型 | 来源 | 用途 |
|---|---|---|---|
| 应用封装 | 业务 ArkTS 封装类 | 应用自写 | 封装设备列表、share、远程拉起 |
| 文件共享 | @ohos.remotefileshare |
产品集成的系统 NAPI (如 libremotefileshare.z.so) |
createSharePath(fd, networkId) |
| 设备发现 | @ohos.distributedDeviceManager |
标准公共 SDK | 信任设备列表、networkId |
| 远程拉起 | 厂商扩展 NAPI(如 @ohos.*RemoteAppAbility) |
厂商/产品扩展 | 远程拉起对端应用并传递 filePath、续播进度 |
| 流转开关 | 厂商扩展 NAPI(如 @ohos.distributedSchedule.*) |
厂商/产品扩展 | 控制是否允许被远程流转拉起 |
| 辅助 | securityLabel、distributedData、file.fs |
标准公共 SDK | 安全标签、元数据同步、本地文件 fd |
标准 OH 公共 SDK 与产品侧实现的差异:
| 组件 | 标准 OH(//foundation/filemanagement/app_file_service) |
产品集成(厂商按需补充) |
|---|---|---|
| C++ 能力 | RemoteFileShare::CreateSharePath(innerkit,供框架/SA) |
可对 innerkit 封装或独立实现(底层多为 ioctl HMDFS_IOC_SET_SHARE_PATH) |
| ArkTS NAPI | 无 (仅 C++ innerkit + 单测,无 interfaces/kits/js) |
需编译 NAPI 模块(如 libremotefileshare.z.so)+ 对应 .d.ts |
| SDK 类型声明 | 无 @ohos.remotefileshare |
需在 SDK API 声明目录补充类型定义(见下节) |
| 完整音视频流转 | 无等价的远程应用调度 API | 通常依赖厂商扩展的远程拉起 / 流转控制 NAPI |
仅用标准 OH 公共 SDK(不集成产品侧 NAPI)时,HAP 侧 share-path 路线基本不可行:
- 公共 SDK 没有
@ohos.remotefileshare模块与 d.ts;HAP 无法通过 ArkTS 调用createSharePath。 - 标准 OH 提供的是 C++ innerkit
RemoteFileShare(//foundation/filemanagement/app_file_service/interfaces/innerkits/native/remote_file_share/)及 dfs_service 另一套分布式文件机制(P2P 会话、拷贝等),不面向 HAP 暴露与上述相同的 share-path 注册 API。 - 若产品未将 NAPI 编进镜像并部署到
/system/lib/module/,应用编译期可能仅有类型占位,运行期requireNapi会失败。
厂商新建 NAPI 的集成要点 (以 remotefileshare 类模块为例,具体命名由产品定义):
| 步骤 | 做什么 | 通常放在哪 |
|---|---|---|
| 1. 实现 NAPI 桥接 | 将 innerkit 的 CreateSharePath 等能力导出为 ArkTS 可调用接口 |
//foundation/filemanagement/app_file_service/interfaces/kits/js/ 下新建子目录,或纳入厂商文件管理子系统 |
| 2. 编译进镜像 | 生成 lib<模块名>.z.so |
对应子系统 BUILD.gn 增加 NAPI target,随产品镜像打包 |
| 3. 部署路径 | 运行时 requireNapi 加载 |
设备 /system/lib/module/lib<模块名>.z.so |
| 4. SDK 类型声明 | 供应用编译期类型检查 | //interface/sdk-js/api/@ohos.<模块名>.d.ts(或产品 SDK 声明目录) |
| 5. 应用权限 | 调用方 HAP 声明分布式相关权限 | module.json5 → requestPermissions |
| 6. 沙箱映射 | 确保 distributedfiles 已映射 |
//base/startup/appspawn/appdata-sandbox.json 或 vendor overlay |
当前系统库能否支撑 HAP 分布式文件?
| 场景 | 是否可行 | 条件 |
|---|---|---|
HAP 文件共享(createSharePath) |
⚠️ 视产品镜像 | 需镜像集成 NAPI(如 libremotefileshare.z.so);实测本产品线镜像未集成该模块 |
| 完整音视频流转(远程拉起 + 续播) | ✅(含厂商扩展的产品) | 另需远程应用调度、流转控制等 vendor NAPI |
| 仅标准 OH 公共 SDK、零产品补丁 | ❌(HAP share-path 路线) | 无 ArkTS API;需改走 SA/innerkit 或其他分布式机制 |
业务 SA(sandbox: 0) |
✅ | 直链 innerkit 或 ioctl,见 §4.6.7、§7 |
text
HAP(应用沙箱内)
ArkTS: remotefileshare.createSharePath(fd, cid)
↓ requireNapi
系统: libremotefileshare.z.so(产品集成,非 HAP 自带)
↓ ioctl HMDFS_IOC_SET_SHARE_PATH
内核: HMDFS → 沙箱映射路径 distributedfiles/.share/
4.6.5 端到端流程(与沙箱的关系)
text
发布端(主设备 HAP,在应用沙箱内)
1. open(沙箱内媒体路径) → 获得 fd
2. remotefileshare.createSharePath(fd, targetDeviceId)
→ 框架在沙箱映射路径下创建 .share 条目
→ 应用看到:/data/storage/el2/distributedfiles/.share/xxx.mp4
3. 远程应用调度 API 把 sharePath 传给对端
消费端(从设备 HAP,仍在应用沙箱内)
1. onCreate 收到 want.parameters.filePath
→ /data/storage/el2/distributedfiles/.share/xxx.mp4
2. 播放器直接播放沙箱内映射路径
4.6.6 开发者检查清单
| 检查项 | 推荐做法 | 错误做法 |
|---|---|---|
| 是否关闭应用沙箱 | ❌ 不关闭,默认 top-sandbox-switch: ON |
在 overlay 设 sandbox-switch: OFF |
| 文件路径 | ✅ /data/storage/el2/distributedfiles/.share/ |
❌ /mnt/hmdfs/.../device_view/... |
| Share 注册 | ✅ remotefileshare.createSharePath |
❌ ioctl(HMDFS_IOC_SET_SHARE_PATH) |
| 权限 | ✅ DISTRIBUTED_DATASYNC + ACCESS_SERVICE_DM |
仅声明不申请 |
| 调试对比 | shell 能访问不代表 HAP 能访问 | 用 hdc shell 结果推断应用行为(见 §1.3.1) |
| 系统 NAPI | ✅ 镜像含对应 lib*.z.so |
假定公共 SDK 自带 @ohos.remotefileshare |
| 流转拉起 | ✅ 使用产品提供的远程调度 NAPI | 仅用标准 SDK 复现完整流转链路 |
4.6.7 与业务 SA 方案对比(见 §7)
| 维度 | HAP(应用沙箱) | 业务 SA(sandbox: 0) |
|---|---|---|
| 沙箱 | 默认进应用沙箱 | 不进 init 沙箱 |
| Share | remotefileshare.createSharePath(产品 NAPI) |
ioctl SET_SHARE_PATH(innerkit / 直调) |
| NAPI / SDK | 依赖镜像 NAPI + 可选厂商扩展 API | 不依赖 HAP NAPI,C++ innerkit 即可 |
| 路径 | distributedfiles/.share/ |
/mnt/hmdfs/.../device_view/.../.share/ |
| 应用需改沙箱? | 否 | N/A(非 HAP) |
5. 各运行形态如何配置沙箱
在 OpenHarmony 中,不同形态的代码如何决定「是否进沙箱、进哪种沙箱」。
5.1 总览对照表
| 运行形态 | 是否有独立进程 | 沙箱体系 | 配置入口 | 默认是否进沙箱 |
|---|---|---|---|---|
| SA 系统服务 | 是 | init 服务层 | 服务 *.cfg(如 //vendor/.../etc/init/ 或子系统 etc/)→ "sandbox": 0/1 |
视 cfg 与 const.sandbox |
| 普通 SO 库 | 否(被 dlopen) | 继承宿主 | 无独立配置 | 随宿主进程 |
| 命令行应用/工具 | 视拉起方式 | init 或 shell | 见 §5.3 | 视拉起方式 |
| ArkUI / HAP 应用 | 是 | appspawn 应用层 | //base/startup/appspawn/appdata-sandbox.json 等 |
默认进 |
| 分布式文件场景 | 跨服务+应用 | 双层叠加 | 服务 cfg + 应用沙箱 + HMDFS 策略 | 最复杂,见 §5.6 |
text
┌─────────────────────────────────────┐
│ 是否独立进程? │
└──────────────┬──────────────────────┘
否 │ 是
┌────────────┴────────────┐
▼ ▼
SO 库(继承宿主) 谁拉起的?
│ ┌───────┴────────┐
│ ▼ ▼
│ init (.cfg) appspawn (HAP)
│ sandbox:0/1 top-sandbox-switch
│ │ │
└──────────────┴────────────────┘
最终 mount namespace
5.2 SA 系统服务
SA(System Ability)服务由 init 通过 sa_main 或独立二进制拉起,是服务层沙箱的直接配置对象。
配置步骤:
- 在服务的
*.cfg中设置"sandbox": 0或1 - 确认系统参数
const.sandbox=enable(仅sandbox: 1时需要) - 若
sandbox: 1,确认//base/startup/init/services/sandbox/system-sandbox64.json白名单覆盖所有依赖路径 - 并行配置
uid/gid/secon/permission/caps
示例(需访问 HMDFS 的业务 SA,不进沙箱):
json
{
"name": "example_business_service",
"path": ["/system/bin/sa_main", "/system/profile/example_business_service.json"],
"uid": "business",
"gid": ["business", "shell", "dfs_share"],
"secon": "u:r:example_business_service:s0",
"sandbox": 0,
"permission": ["ohos.permission.DISTRIBUTED_DATASYNC"]
}
示例(普通 SA,进沙箱):
json
{
"name": "some_system_service",
"path": ["/system/bin/sa_main", "/system/profile/some_service.json"],
"uid": "someuser",
"gid": ["someuser"],
"secon": "u:r:some_service:s0",
"sandbox": 1
}
| 决策 | 选 sandbox: 0 |
选 sandbox: 1 |
|---|---|---|
需访问 /mnt/hmdfs/ 动态挂载 |
✅ | ❌(通常看不到后续挂载) |
需 ioctl / setxattr 操作 HMDFS |
✅ | ❌ |
仅需 /system、/data 静态路径 |
可选 | ✅ 推荐 |
| 安全审计要求严格隔离 | --- | ✅ |
系统总开关:
bash
# 查看
param get const.sandbox
# 产品集成时通常在 build/产品配置中设定,运行时只读
5.3 普通 SO 库(动态库)
.so 动态库不是独立进程,没有自己的沙箱配置项。
| 要点 | 说明 |
|---|---|
| 加载方式 | 宿主进程通过 dlopen / 链接器加载 |
| 沙箱归属 | 完全继承宿主进程的 mount namespace |
能否单独设 sandbox |
不能 |
| 代码权限 | 与宿主共享 uid/gid/SELinux 上下文 |
典型场景(SA 加载业务插件 .so):
text
example_business_service (SA, sandbox: 0)
└── dlopen("lib<plugin>.z.so") // 插件随 SA 同进程加载
└── 插件代码与 SA 同进程、同沙箱、同 uid/gid
若宿主 SA 为 sandbox: 0,插件 .so 可直接访问 /mnt/hmdfs/;若宿主为 sandbox: 1,插件同样受 init 沙箱限制。
集成建议:
- 需要底层文件系统能力的逻辑,放在 SA 进程 + sandbox: 0 ,而非 HAP 内嵌 native
.so - 插件
.so的安全边界 = 宿主 SA 的安全边界,需在 SA 的 cfg 中一并规划
5.4 普通命令行应用
命令行程序的沙箱取决于如何被拉起 ,没有统一的 sandbox 字段。
| 拉起方式 | 沙箱行为 | 配置方法 |
|---|---|---|
| init 注册为 service | 与服务层 SA 相同 | 对应 *.cfg 中 "sandbox": 0/1 |
**hdc shell 手动执行** |
两层沙箱都不进 ;以 shell 用户 + 全局 mount namespace 运行(同 hdcd 的 sandbox: 0) |
无沙箱开关;受 uid/gid、SELinux、文件权限约束(见 §1.3.1) |
| init job 一次性命令 | 在 init 上下文中执行,通常无沙箱 | jobs.cmds 中定义 |
| nativespawn 拉起的 native 应用 | 走 appspawn 应用沙箱 | //base/startup/appspawn/nativespawn.cfg + //base/startup/appspawn/appdata-sandbox.json |
init 注册的命令行服务示例 (来源 //developtools/hdc/src/daemon/etc/hdcd.cfg,节选):
json
{
"name": "hdcd",
"path": ["/system/bin/hdcd"],
"uid": "shell",
"gid": ["shell", "log", "readproc", "file_manager"],
"sandbox": 0
}
hdcd 作为调试守护进程需访问全局文件系统,故 sandbox: 0;cfg 中 uid 为 shell (非 root)。部分产品 cfg 含 "disabled": 1,由产品决定是否启用 hdcd 服务。
**hdc shell 调试注意(详见 §1.3.1):**
bash
hdc shell id
# 调试产品(const.debuggable=1)常见 uid=0(root);非调试产品多为 shell(2000)
# 无论 uid 为何,均不进 HAP/SA 沙箱
hdc shell ls /mnt/hmdfs/100/
# 若 dfs 已挂载且权限允许,shell 通常可见;不代表 HAP 内也能访问
开发者常见误区:在 shell 里能访问的路径,不代表 HAP 应用也能访问。
nativespawn(原生应用孵化,来源 //base/startup/appspawn/nativespawn.cfg):
json
// 源码://base/startup/appspawn/nativespawn.cfg
{
"name": "nativespawn",
"path": ["/system/bin/nativespawn", "--sandbox-switch on", "..."],
"sandbox": 0
}
nativespawn自身sandbox: 0(需管理子进程沙箱)- 它拉起的 native 应用子进程,由
--sandbox-switch on决定是否进应用沙箱
5.5 ArkUI / HAP 应用
ArkUI 应用(Stage 模型 HAP)由 appspawn 孵化,默认进入应用沙箱 ,开发者通常无需、也无法在 HAP 包里写 sandbox: 0。
配置层级(由粗到细):
| 层级 | 配置文件 / 参数 | 开关字段 | 作用 |
|---|---|---|---|
| 系统预置 | //base/startup/appspawn/appdata-sandbox.json(设备 /etc/sandbox/appdata-sandbox.json) |
top-sandbox-switch: ON/OFF |
全局默认 |
| appspawn 启动 | //base/startup/appspawn/appspawn.cfg |
--sandbox-switch on/off |
孵化守护进程策略 |
| 应用级覆盖 | vendor overlay / 包名规则 | sandbox-switch: ON/OFF |
单包豁免或加强 |
| 隔离模式 | //base/startup/appspawn/appdata-sandbox-isolated.json |
独立策略集 | 更严格隔离 |
| 应用权限 | HAP 内 module.json5 |
requestPermissions |
沙箱内 API 访问控制 |
开发者侧配置(module.json5):
json5
{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.DISTRIBUTED_DATASYNC" }
]
}
}
文件路径使用规范:
typescript
// ✅ 正确:使用 Context API 获取沙箱内合法路径
const filesDir = context.filesDir;
const distDir = context.distributedFilesDir;
// ❌ 错误:硬编码系统路径
const path = "/mnt/hmdfs/100/account/device_view/...";
应用沙箱内 HMDFS 的映射关系 (//base/startup/appspawn/appdata-sandbox.json):
| 应用看到的路径 | 实际宿主机映射 |
|---|---|
/data/storage/el2/distributedfiles |
/mnt/hmdfs/<userId>/account/merge_view/data/<PackageName> |
/mnt/hmdfs/<userId> |
同路径 bind mount(有限可见性) |
/data/storage/el2/base |
应用私有数据目录 |
应用不能 直接访问 device_view/{networkId}/services/.../.share/ 这类系统级 HMDFS 路径,需通过 remotefileshare 等框架 API。
如何「关闭」某个应用沙箱(仅系统集成,非应用开发者):
- 在 vendor overlay 的
appdata-sandbox.json(如//vendor/<产品名>/appspawn/appdata-sandbox.json)中为该包名设sandbox-switch: OFF - 或全局
top-sandbox-switch: OFF(极不推荐,破坏安全模型)
5.6 最复杂场景:分布式文件(HMDFS)
分布式文件是 OpenHarmony 沙箱体系中最复杂的场景,因为它同时涉及:
- 服务层 (
//foundation/filemanagement/dfs_service/、需直访 HMDFS 的业务 SA)--- init 沙箱 - 应用层(分布式音视频等 HAP)--- appspawn 沙箱
- 内核层 (
//kernel/linux/.../fs/hmdfs/share_table、devsl、ioctl)--- 与沙箱无关但强相关 - 动态挂载 (
/mnt/hmdfs/运行时 mount)--- 与privatebind 传播冲突
5.6.1 三层机制与沙箱的关系
text
L1 Share 注册(ioctl SET_SHARE_PATH)
├── HAP 路径:remotefileshare.createSharePath(框架代劳)
└── SA 路径:原生 ioctl(需 sandbox:0 + dfs_share gid)
↓
L2 P2P 建链(`//foundation/filemanagement/dfs_service/` → `DistributedFileDaemonManager::OpenP2PConnection`)
└── distributedfiledaemon(`//foundation/filemanagement/dfs_service/services/distributedfile.cfg`,sandbox:0)负责
↓
L3 对端访问(open/read device_view/.share/)
├── HAP:/data/storage/el2/distributedfiles/.share/(沙箱映射路径)
└── SA:/mnt/hmdfs/.../device_view/.../.share/(系统真实路径)
5.6.2 HAP vs SA 沙箱配置对比
| 维度 | HAP(应用沙箱) | SA(服务沙箱) |
|---|---|---|
| 沙箱开关 | top-sandbox-switch(默认 ON) |
sandbox: 0/1 |
| Share 注册 API | remotefileshare.createSharePath |
ioctl(HMDFS_IOC_SET_SHARE_PATH) |
| 发布路径 | .../distributedfiles/.share/ |
.../merge_view/services/{svc}/.share/ |
| 消费路径 | 框架映射的 distributedfiles | device_view/{nid}/services/.../.share/ |
| 能否直接 ioctl | 否 | 是(需 sandbox:0) |
| P2P 触发 | 框架自动 / 远程拉起 | 显式 OpenP2PConnection |
| 关键 permission | DISTRIBUTED_DATASYNC 等 |
同左 + cfg 中声明 |
| 关键 gid | 应用 uid | dfs_share |
5.6.3 分布式文件相关服务的沙箱配置清单
| 组件 | 源码 cfg / 配置 | sandbox | 原因 |
|---|---|---|---|
distributedfiledaemon |
//foundation/filemanagement/dfs_service/services/distributedfile.cfg |
0 |
负责 HMDFS mount、P2P |
cloudfileservice |
//foundation/filemanagement/dfs_service/services/distributedfile.cfg |
0 |
云同步访问全局路径 |
| 需直访 HMDFS 的业务 SA | 对应服务 *.cfg(产品子系统或 //vendor/.../etc/init/) |
0 |
ioctl share + 读 device_view |
appspawn |
//base/startup/appspawn/appspawn.cfg |
0 |
管理应用沙箱 |
nativespawn |
//base/startup/appspawn/nativespawn.cfg |
0 |
管理 native 应用沙箱 |
| 普通 HAP | HAP 内 module.json5 |
默认进应用沙箱 | 通过框架 API 间接访问 HMDFS |
5.6.4 业务 SA 完整沙箱相关配置示例
json
{
"name": "example_business_service",
"uid": "business",
"gid": ["business", "shell", "dfs_share"],
"secon": "u:r:example_business_service:s0",
"sandbox": 0,
"permission": [
"ohos.permission.DISTRIBUTED_DATASYNC",
"ohos.permission.DISTRIBUTED_SOFTBUS_CENTER"
]
}
| 配置项 | 作用 |
|---|---|
sandbox: 0 |
不进 init 沙箱,能同步看到 dfs 动态挂载的 /mnt/hmdfs/ |
gid: dfs_share |
允许参与 HMDFS share 相关操作 |
secon: <service>_domain |
SELinux;share 后需 setxattr user.security=s0 |
DISTRIBUTED_DATASYNC |
OpenP2PConnection 权限校验 |
5.6.5 分布式文件沙箱踩坑汇总
| 现象 | 根因 | 解决 |
|---|---|---|
SA 内 /mnt/hmdfs/ 不存在 |
sandbox:1 + private mount 看不到动态挂载 |
改 sandbox: 0 |
HAP 内硬编码 /mnt/hmdfs/ 失败 |
应用在沙箱内,路径未映射或不可写 | 用 context.distributedFilesDir + 框架 API |
share 注册 ENOENT |
merge_view .share 目录未创建 |
等 dfs 初始化或手动 mkdir |
| 对端 device_view 不可见 | 仅 L1 share,未 L2 P2P | 调用 OpenP2PConnection |
| shell 能访问但 HAP 不能 | shell 不进两层沙箱,HAP 在应用沙箱内 | 按应用沙箱路径开发,勿以 shell 结果为准 |
| 插件 .so ioctl 失败 | 宿主 SA 为 sandbox:1 | 在 SA cfg 设 sandbox:0 |
5.6.6 选型建议(需直访 device_view 的场景)
当业务需要直接操作 device_view 路径、执行 ioctl(HMDFS_IOC_SET_SHARE_PATH) 时,宜选择 SA + sandbox:0 + 原生 innerkit/ioctl,而非 HAP 方案,原因:
- 需要直接操作
device_view路径,HAP 沙箱内不可达 - HAP 侧应使用
remotefileshare等框架 API,无法满足 SA 直访需求 - 与
distributedfiledaemon(同为 sandbox:0)共享 mount namespace,保证动态挂载可见 - 业务插件
.so若随 SA 加载,继承同一沙箱策略
6. 服务层 vs 应用层对比
| 对比项 | 服务层(init) | 应用层(appspawn) |
|---|---|---|
| 进程类型 | 原生 SA / daemon | HAP 应用 |
| 源码配置 | 服务 *.cfg(子系统 etc/ 或 //vendor/.../etc/init/) |
//base/startup/appspawn/appdata-sandbox.json |
| 设备配置 | /system/etc/init/xxx_service.cfg |
/etc/sandbox/appdata-sandbox.json |
| 开关字段 | "sandbox": 0/1 |
top-sandbox-switch / sandbox-switch |
| 系统总开关 | const.sandbox=enable |
appspawn --sandbox-switch(//base/startup/appspawn/appspawn.cfg) |
| 沙箱根目录 | /mnt/sandbox/system 等 |
/mnt/sandbox/<userId>/<PackageName>(默认) |
| namespace | 主要是 mount | mount + 可配 pid/net(默认配置含 net) |
| 数据目录 | /data/service/el1/... |
/data/storage/el2/base/... |
能否访问 /mnt/hmdfs/ |
sandbox: 0 时可访问全局挂载 |
可映射 /mnt/hmdfs/<userId> 及 distributedfiles,不能 直接访问 device_view 系统路径 |
| 典型配置者 | 系统/平台集成工程师 | 系统预置 + 应用权限声明 |
7. HAP vs SA 选型(分布式文件场景)
7.1 何时用 HAP、何时用 SA
需跨设备共享文件时,常见两条路线:
| 维度 | HAP(应用沙箱) | 业务 SA(sandbox: 0) |
|---|---|---|
| 运行形态 | 应用沙箱进程 | 系统 SA 进程 |
| Share 注册 | ArkTS remotefileshare.createSharePath |
原生 ioctl(HMDFS_IOC_SET_SHARE_PATH) 或 innerkit |
| HMDFS 路径 | /data/storage/el2/distributedfiles/.share/ |
/mnt/hmdfs/.../device_view/.../.share/ |
| 配置 | 应用权限 + 沙箱内合法路径 | 服务 *.cfg 中 sandbox: 0、dfs_share gid、分布式 permission |
| 适用场景 | 面向用户的应用、媒体流转、仅需框架映射路径 | 需直访 device_view、底层 ioctl、与 dfs 同 namespace 的后台业务 |
7.2 业务 SA 关键配置要点
json
{
"name": "example_business_service",
"uid": "business",
"gid": ["business", "shell", "dfs_share"],
"secon": "u:r:example_business_service:s0",
"sandbox": 0,
"permission": [
"ohos.permission.DISTRIBUTED_DATASYNC",
"ohos.permission.DISTRIBUTED_SOFTBUS_CENTER"
]
}
| 配置项 | 与沙箱/安全的关系 |
|---|---|
sandbox: 0 |
不进 init 沙箱,能同步看到 dfs 挂载的 /mnt/hmdfs/ |
gid: dfs_share |
HMDFS share 操作的组权限 |
secon: <service>_domain |
SELinux 标签,share 时需 setxattr user.security=s0 |
permission: DISTRIBUTED_DATASYNC |
OpenP2PConnection 权限校验 |
7.3 若误设 sandbox: 1 的可能后果
在 const.sandbox=enable 的产品上:
- 进程进入
/mnt/sandbox/system,文件系统视图被隔离 /mnt以private绑定,后续/mnt/hmdfs/挂载不可见ioctl SET_SHARE_PATH目标路径不存在或不可写- 读取
device_view/.share/失败
8. 配置决策指南
8.1 服务层:何时设 sandbox: 0
- 需访问运行时动态挂载的路径(HMDFS、外置存储、网络文件系统)
- 需执行
ioctl/setxattr等底层文件系统操作 - 需与多个系统服务共享同一 mount namespace(如 storage、dfs)
- 参考同类系统服务(
distributedfiledaemon、storage_daemon均为0)
8.2 服务层:何时设 sandbox: 1
- 服务仅需访问
/system、/data等静态白名单路径 - 安全审计要求更严格的文件系统隔离
- 确认
const.sandbox=enable且白名单已覆盖所有依赖路径
8.3 应用层:开发者注意事项
- 应用默认进沙箱 (
top-sandbox-switch: ON),无需也不能像 cfg 那样写sandbox: 0 - 文件操作限制在
context.filesDir、context.distributedFilesDir等 API 返回路径内 - 跨设备文件共享使用框架 API(
remotefileshare),不要硬编码/mnt/hmdfs/ - 需要额外路径时通过系统 overlay 配置或申请特殊权限,而非关闭沙箱
9. 调试与验证
9.1 实测结论速览
| 验证项 | 文档预期 | 本机实测 | 结论 |
|---|---|---|---|
const.sandbox |
enable 时服务沙箱生效 | enable |
✅ 一致 |
distributedfiledaemon sandbox:0 |
dfs 需不进沙箱 | cfg 与进程 PID 3646 均确认 | ✅ 一致 |
业务 SA sandbox:0 可见 hmdfs |
mountinfo 含 hmdfs | thingmodel_service PID 13470 可见 |
✅ 一致 |
/etc/sandbox/*.json |
设备有沙箱策略 | 4 个文件均存在 | ✅ 一致 |
top-sandbox-switch |
默认 ON | appdata-sandbox.json 为 ON |
✅ 一致 |
| 应用沙箱根 | /mnt/sandbox/<userId>/<PackageName> |
/mnt/sandbox/100/com.ohos.launcher/ 存在 |
✅ 一致(userId=100) |
HAP distributedfiles 映射 |
bind 到 merge_view | launcher mountinfo 证实(见 §9.3) | ✅ 一致 |
hdc shell 不进沙箱 |
全局 namespace | shell 85 行 mount vs launcher 94 行,且映射不同 | ✅ 一致 |
libremotefileshare.z.so |
产品可选集成 | 不存在 | ⚠️ 本镜像未集成 NAPI |
device_view/.../.share/ |
share 后才有 | 无活跃 share 时目录不存在 | ✅ 正常 |
9.2 验证命令与实测输出
9.2.1 系统沙箱开关
bash
$HDC shell "param get const.sandbox"
# 实测:enable
$HDC shell "param get const.debuggable"
# 实测:1(调试产品;影响 hdc 默认 uid,见 §1.3.1)
9.2.2 服务层:dfs 与业务 SA
bash
$HDC shell "ps -ef | grep distributedfiledaemon | grep -v grep"
# 实测:dfs 3646 ... distributedfiledaemon
$HDC shell "grep sandbox /system/etc/init/distributedfile.cfg"
# 实测:distributedfiledaemon / cloudfileservice 均为 "sandbox": 0
$HDC shell "cat /proc/3646/mountinfo | grep hmdfs | head -2"
# 实测(节选):
# ... /mnt/hmdfs/100/account ... hmdfs ...
$HDC shell "grep sandbox /system/etc/init/thingmodel_service.cfg"
# 实测:"sandbox": 0
$HDC shell "cat /proc/13470/mountinfo | grep hmdfs | head -1"
# 实测:thingmodel_service 同样可见 /mnt/hmdfs/100/account
9.2.3 应用层:沙箱目录与 HAP 映射
bash
$HDC shell "ls /etc/sandbox/"
# 实测:appdata-sandbox.json、appdata-sandbox-isolated.json、
# chipset-sandbox.json、system-sandbox.json
$HDC shell "ls /mnt/sandbox/"
# 实测:0、100、chipset、com.ohos.render、system
$HDC shell "ls /mnt/sandbox/100/ | head -5"
# 实测:com.ohos.launcher、com.ohos.settings、...
# launcher PID=2562,验证 distributedfiles 映射
$HDC shell "cat /proc/2562/mountinfo | grep distributedfiles"
# 实测(节选):
# ... /merge_view/data/com.ohos.launcher /data/storage/el2/distributedfiles ... hmdfs ...
9.2.4 分布式文件与 NAPI
bash
$HDC shell "ls /mnt/hmdfs/"
# 实测:100
$HDC shell "ls /mnt/hmdfs/100/account/"
# 实测:cloud_merge_view、device_view、merge_view
$HDC shell "ls -l /system/lib/module/libremotefileshare.z.so"
# 实测:No such file or directory(本产品线镜像未集成)
$HDC shell "find /system -name '*remotefile*' 2>/dev/null"
# 实测:无结果
9.2.5 hdc shell 身份(不进沙箱)
bash
$HDC shell "id"
# 实测:uid=0(root) ... context=u:r:su:s0
# 说明:const.debuggable=1 且 persist.hdc.root 未设置时,hdc 默认 root
$HDC shell "param get persist.hdc.root"
# 实测:Get parameter fail errNum:106(未设置)
$HDC shell "cat /proc/1253/status | grep Uid"
# 实测:hdcd 进程 Uid: 0(运行态亦为 root,与 cfg 中 uid:shell 可能因调试策略不同)
9.2.6 应用沙箱调试
bash
$HDC shell "begetctl sandbox 2>&1 | head -1"
# 实测:sandbox -s | -n [-p] | -p | -b | -h
$HDC shell "begetctl sandbox -b 2562"
# 进入 com.ohos.launcher 的应用沙箱 shell(需 root)
9.3 shell 与 HAP 视角差异(实测对照)
| 视角 | 进程 | mount 条目数(实测) | /data/storage/el2/distributedfiles |
/mnt/hmdfs/100/account/device_view/... |
|---|---|---|---|---|
hdc shell |
sh(root) | 85 | 无此映射 | shell 可直接 ls 部分 hmdfs 路径 |
| HAP(launcher) | com.ohos.launcher | 94 | ✅ bind 到 merge_view | ❌ 无 device_view 直访映射 |
开发者误区(实测印证) :shell 能
ls /mnt/hmdfs/100/...,不代表 HAP 内能访问同一路径;HAP 应使用context.distributedFilesDir及框架 API。
9.4 常见问题
| 现象 | 可能原因 | 实测相关 |
|---|---|---|
/mnt/hmdfs/ 不存在 |
dfs 未启动 | 本机 dfs 正常,存在 /mnt/hmdfs/100 |
libremotefileshare.z.so 不存在 |
产品未集成 NAPI | 本机确认未集成;HAP share-path 路线需产品补模块 |
device_view/.../.share/ 不存在 |
无活跃 share 注册 | 正常;share 注册后才会出现 |
sandbox:1 但行为像 0 |
const.sandbox 未 enable |
本机为 enable,此项不适用 |
| shell 能访问但 HAP 不能 | shell 不进沙箱 | launcher mountinfo 已证实映射差异 |
10. 参考
| 资源 | 源码路径 |
|---|---|
| init 沙箱实现 | //base/startup/init/services/sandbox/sandbox.c |
| 服务 sandbox 解析 | //base/startup/init/services/init/init_service_manager.c → GetServiceSandbox() |
| 服务 sandbox 生效 | //base/startup/init/services/init/standard/init_service.c → SetServiceEnterSandbox() |
| system 沙箱白名单 | //base/startup/init/services/sandbox/system-sandbox64.json(设备 /etc/sandbox/system-sandbox.json) |
| chipset 沙箱白名单 | //base/startup/init/services/sandbox/chipset-sandbox64.json(设备 /etc/sandbox/chipset-sandbox.json) |
| appspawn 沙箱入口 | //base/startup/appspawn/modules/sandbox/sandbox_utils.cpp → SetAppSandboxProperty |
| begetctl 沙箱调试 | //base/startup/init/services/begetctl/sandbox.cpp |
| appspawn 守护进程 cfg | //base/startup/appspawn/appspawn.cfg |
| nativespawn cfg | //base/startup/appspawn/nativespawn.cfg |
| 应用沙箱配置(源码) | //base/startup/appspawn/appdata-sandbox.json(设备 /etc/sandbox/appdata-sandbox.json) |
| 应用隔离沙箱配置 | //base/startup/appspawn/appdata-sandbox-isolated.json |
| dfs_service 配置 | //foundation/filemanagement/dfs_service/services/distributedfile.cfg |
| storage_daemon 配置 | //foundation/filemanagement/storage_service/services/storage_daemon/storage_daemon.cfg |
| hdc 调试守护进程 cfg | //developtools/hdc/src/daemon/etc/hdcd.cfg |
| HMDFS 内核 share 定义 | //kernel/linux/.../fs/hmdfs/hmdfs_share.h(具体内核树路径因产品而异) |
| RemoteFileShare innerkit | //foundation/filemanagement/app_file_service/interfaces/innerkits/native/remote_file_share/ |
| remotefileshare NAPI(产品集成) | 编译产物部署至设备 /system/lib/module/libremotefileshare.z.so;源码通常置于 app_file_service/interfaces/kits/js/ 或厂商文件管理子系统 |
11. 审校说明
本节记录对照源码审校后的已证实结论 与需注意的边界。
11.1 已由源码证实(OH 部分)
| 结论 | 源码依据 |
|---|---|
sandbox: 0 → SERVICE_ATTR_WITHOUT_SANDBOX,跳过 EnterSandbox |
//base/startup/init/services/init/init_service_manager.c → GetServiceSandbox |
const.sandbox=enable 才使服务沙箱生效 |
//base/startup/init/services/init/standard/init_service.c → SetServiceEnterSandbox |
仅 /system/bin/、/vendor/bin/ 前缀会 EnterSandbox |
同上 strncmp 判断 |
服务沙箱 pivot_root 在 PrepareSandbox,子进程仅 SetNamespace |
//base/startup/init/services/sandbox/sandbox.c |
应用默认沙箱根 /mnt/sandbox/<userId>/<PackageName> |
//base/startup/appspawn/modules/sandbox/sandbox_utils.cpp(UID_BASE=200000) |
| 应用沙箱默认开关为 ON | sandbox_load.c:GetBoolValueFromJsonObj(..., "sandbox-switch", true) |
appdata-sandbox.json 映射 distributedfiles ↔ HMDFS merge_view |
//base/startup/appspawn/appdata-sandbox.json mount-paths |
begetctl sandbox -b <pid> 进入应用沙箱 |
//base/startup/init/services/begetctl/sandbox.cpp |
板端实测 launcher distributedfiles bind |
/proc/<pid>/mountinfo:merge_view/data/<PackageName> → distributedfiles(§9.2.3) |
板端实测 sandbox:0 服务可见 hmdfs |
distributedfiledaemon、thingmodel_service 的 mountinfo 含 /mnt/hmdfs/100/account |
11.2 产品/环境相关
| 项 | 说明 |
|---|---|
| SELinux | 是否启用取决于产品编译与策略;cfg 中 secon 仅在 SELinux 开启时生效 |
| const.sandbox | 嵌入式产品可能未 enable;本机实测为 enable |
| /mnt/hmdfs/ 动态挂载不可见 | 在 sandbox:1 + /mnt 以 private bind 的前提下成立;本机 sandbox:0 服务实测可见 hmdfs |
| HAP 能否 ioctl HMDFS | 除沙箱外还受权限、SELinux、框架封装约束;本文表述为「通常不能/不应直接 ioctl」 |
| hdc shell 的 uid | cfg 默认 shell;本机实测 const.debuggable=1 时 hdc shell 与 hdcd 均为 root,但均不进沙箱 |
| remotefileshare NAPI | 产品可选;本机镜像未集成 libremotefileshare.z.so |
| §0 跨平台对比 | 概要类比,Android/iOS/Windows 等细节因版本而异 |