在这里插入图片描述

每日一句正能量

最舒服的关系,贵在远近相安,浓淡相宜,舒适自在。
“远近相安”是无论距离远近,内心都安然踏实;“浓淡相宜”是交往的密度可浓可淡,一切都刚刚好。这种关系不需要费力的维系,也没有刻意的讨好,一切都顺其自然,让身处其中的双方都能感到“舒适自在”。如果一段关系需要你时刻紧绷神经,那它可能并不是最适合你的。

摘要

摘要:应用沙箱是操作系统安全的第一道防线。本文深入 HarmonyOS 内核与框架层,系统剖析应用沙箱的五大核心机制:UID/GID 进程隔离、文件系统沙箱、IPC 安全通信、SELinux 强制访问控制以及沙箱逃逸防护。通过大量 ArkTS/C++ 实战代码与架构图解,帮助开发者理解沙箱原理并掌握安全编程实践。


一、应用沙箱机制概述

应用沙箱(Application Sandbox)是一种安全机制,通过限制应用的运行环境,使每个应用只能访问自身授权的资源,无法干扰其他应用或系统核心组件。HarmonyOS 的沙箱设计遵循"纵深防御"原则,在 Linux 内核标准隔离机制的基础上,叠加了多层安全控制。

1.1 沙箱的核心目标

安全目标 说明 实现机制
进程隔离 防止应用间直接内存访问 UID/GID 分离 + 进程地址空间隔离
文件隔离 防止应用间文件互相读取 私有目录 + DAC 权限 + FSCrypt
通信管控 防止未经授权的跨进程通信 Binder 驱动 + SAMGR + AccessToken
权限最小化 限制应用可访问的系统资源 SELinux + 权限声明 + 动态授权
攻击面收缩 减少可被利用的漏洞入口 Seccomp-BPF + 系统调用过滤

1.2 HarmonyOS 与 Android/iOS 沙箱对比

维度 HarmonyOS Android iOS
进程隔离 UID/GID + 独立进程空间 UID/GID + Zygote 预加载 Seatbelt + Sandbox Profile
文件系统 FSCrypt + 应用私有目录 FBE/FDE + 沙箱目录 Data Protection + 容器化
IPC 机制 Binder + SAMGR Binder + ServiceManager Mach IPC + XPC
强制访问控制 SELinux (Enforcing) SELinux (Enforcing) Seatbelt + Sandbox
系统调用过滤 Seccomp-BPF Seccomp-BPF Syscall Filtering

HarmonyOS 在继承 Android 成熟安全机制的基础上,通过 SAMGR(系统服务管理器)实现了更细粒度的服务级访问控制,并通过分布式软总线为跨设备场景提供了统一的安全通信框架。


二、UID/GID 进程隔离机制

2.1 UID 分配策略

HarmonyOS 采用与 Android 类似的 UID 分配策略:

  • 系统 UID(0 - 9999):分配给系统核心服务,如 system(UID 0)、samgr(UID 1000)、security(UID 5000)
  • 应用 UID(10000+):每个应用安装时分配唯一 UID,不同应用 UID 绝不重复
  • GID 分组:同 Bundle 的多进程应用共享 GID,不同 Bundle 严格隔离

在这里插入图片描述

上图展示了 UID 分配与进程隔离的关系。关键原则是:不同 UID 的进程之间禁止直接通信,必须通过受控的 IPC 通道进行交互

2.2 获取应用 UID 实战

在 ArkTS 中,可通过 bundleManager 获取当前应用的 UID:

import { bundleManager } from '@kit.BundleKit';
import { BusinessError } from '@kit.BasicServicesKit';

class ProcessIsolationHelper {
  /**
   * 获取当前应用的 UID
   */
  async getCurrentAppUid(): Promise<number> {
    try {
      const bundleInfo = await bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
      );
      return bundleInfo.appInfo.uid;
    } catch (error) {
      const err = error as BusinessError;
      console.error('获取应用 UID 失败:', err.code, err.message);
      return -1;
    }
  }

  /**
   * 获取当前应用的 GID
   */
  async getCurrentAppGid(): Promise<number> {
    try {
      const bundleInfo = await bundleManager.getBundleInfoForSelf(
        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION
      );
      return bundleInfo.appInfo.gid;
    } catch (error) {
      const err = error as BusinessError;
      console.error('获取应用 GID 失败:', err.code, err.message);
      return -1;
    }
  }

  /**
   * 检查两个应用是否属于同一 Bundle(共享 GID)
   */
  async isSameBundle(bundleNameA: string, bundleNameB: string): Promise<boolean> {
    try {
      const [infoA, infoB] = await Promise.all([
        bundleManager.getBundleInfo(bundleNameA, bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION),
        bundleManager.getBundleInfo(bundleNameB, bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
      ]);
      return infoA.appInfo.gid === infoB.appInfo.gid;
    } catch (error) {
      console.error('Bundle 比较失败:', error);
      return false;
    }
  }
}

export default new ProcessIsolationHelper();

2.3 进程隔离的底层实现

在内核层面,HarmonyOS 通过以下机制实现进程隔离:

// OpenHarmony 内核进程隔离核心逻辑(简化示意)
// kernel/liteos_a/kernel/base/core/los_process.c

INT32 OsProcessCreate(UINT32 *processID, const TSK_INIT_PARAM_S *initParam) {
    // 1. 分配唯一 PID
    UINT32 pid = OsAllocPid();

    // 2. 设置进程安全上下文
    processCB->uid = initParam->userArea;  // 应用 UID
    processCB->gid = initParam->userMap;   // 应用 GID

    // 3. 初始化进程地址空间(MMU 隔离)
    LosVmSpace *vmSpace = OsCreateUserVmSpace();
    processCB->vmSpace = vmSpace;

    // 4. 加载 SELinux 安全上下文
    OsLoadSelinuxContext(processCB);

    // 5. 应用 Seccomp-BPF 过滤器
    OsApplySeccompFilter(processCB);

    return LOS_OK;
}

三、文件系统沙箱与目录结构

3.1 应用私有目录详解

每个应用在安装时,系统会为其创建独立的私有目录空间:

/data/app/el1/<bundleName>/
├── base/
│   ├── files/              # 持久化用户文件 (getFilesDir)
│   ├── database/           # 数据库文件 (getDatabaseDir)
│   ├── cache/              # 缓存文件 (getCacheDir),系统可自动清理
│   ├── preferences/        # 轻量级偏好设置
│   ├── distributed/        # 分布式数据目录
│   └── temp/               # 临时文件
└── current/                # 当前版本数据

在这里插入图片描述

3.2 沙箱目录权限模型

HarmonyOS 对应用私有目录采用严格的 DAC(自主访问控制)权限:

# 应用私有目录权限示例(终端查看)
ls -laZ /data/app/el1/com.example.myapp/base/
# 输出:
# drwx------ 2 u0_a1 u0_a1 u:object_r:app_data_file_t:s0:c1,c2 files/
# drwx------ 2 u0_a1 u0_a1 u:object_r:app_data_file_t:s0:c1,c2 database/
# drwx------ 2 u0_a1 u0_a1 u:object_r:app_data_file_t:s0:c1,c2 cache/

权限解析:

  • drwx------:只有目录所有者(应用自身 UID)拥有读写执行权限
  • u0_a1:应用的用户和组标识
  • u:object_r:app_data_file_t:s0:c1,c2:SELinux 安全上下文

3.3 安全文件操作实战

import { fileIo } from '@kit.CoreFileKit';
import { BusinessError } from '@kit.BasicServicesKit';

class SandboxFileManager {
  private context: Context;

  constructor(context: Context) {
    this.context = context;
  }

  /**
   * 获取应用私有文件目录路径
   */
  getPrivateFilesDir(): string {
    return this.context.filesDir;
  }

  /**
   * 获取应用缓存目录路径
   */
  getCacheDir(): string {
    return this.context.cacheDir;
  }

  /**
   * 安全写入文件(自动限制在沙箱内)
   */
  async writeSecureFile(fileName: string, content: string): Promise<void> {
    // 路径规范化:防止路径遍历攻击
    const safeFileName = this.sanitizeFileName(fileName);
    const filePath = `${this.context.filesDir}/${safeFileName}`;

    try {
      const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE);
      await fileIo.write(file.fd, this.stringToArrayBuffer(content));
      fileIo.closeSync(file);
      console.info(`文件安全写入成功: ${filePath}`);
    } catch (error) {
      const err = error as BusinessError;
      console.error('文件写入失败:', err.code, err.message);
      throw error;
    }
  }

  /**
   * 安全读取文件
   */
  async readSecureFile(fileName: string): Promise<string> {
    const safeFileName = this.sanitizeFileName(fileName);
    const filePath = `${this.context.filesDir}/${safeFileName}`;

    try {
      const file = fileIo.openSync(filePath, fileIo.OpenMode.READ);
      const stat = fileIo.statSync(file.fd);
      const buf = new ArrayBuffer(stat.size);
      await fileIo.read(file.fd, buf);
      fileIo.closeSync(file);
      return this.arrayBufferToString(buf);
    } catch (error) {
      const err = error as BusinessError;
      console.error('文件读取失败:', err.code, err.message);
      throw error;
    }
  }

  /**
   * 清理缓存目录(系统可能在低存储时自动清理)
   */
  async clearCache(): Promise<void> {
    try {
      const cacheDir = this.context.cacheDir;
      const files = fileIo.listFileSync(cacheDir);
      for (const file of files) {
        fileIo.unlinkSync(`${cacheDir}/${file}`);
      }
      console.info('缓存目录已清理');
    } catch (error) {
      console.error('缓存清理失败:', error);
    }
  }

  /**
   * 文件名安全校验(防止路径遍历)
   */
  private sanitizeFileName(fileName: string): string {
    // 移除路径分隔符和危险字符
    const sanitized = fileName
      .replace(/[\/]/g, '')
      .replace(/\.{2,}/g, '')
      .replace(/[<>:"|?*]/g, '');

    if (sanitized.length === 0 || sanitized.length > 255) {
      throw new Error('非法文件名');
    }
    return sanitized;
  }

  private stringToArrayBuffer(str: string): ArrayBuffer {
    const encoder = new TextEncoder();
    return encoder.encode(str).buffer as ArrayBuffer;
  }

  private arrayBufferToString(buffer: ArrayBuffer): string {
    const decoder = new TextDecoder('utf-8');
    return decoder.decode(buffer);
  }
}

export default SandboxFileManager;

3.4 沙箱外文件访问(需权限)

访问公共存储(相册、文档等)需要申请相应权限:

import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@kit.BasicServicesKit';

class PublicStorageAccessor {
  /**
   * 读取相册图片(需 ohos.permission.READ_MEDIA 权限)
   */
  async readAlbumPhotos(): Promise<Array<photoAccessHelper.PhotoAsset>> {
    try {
      const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(getContext());
      const fetchOptions: photoAccessHelper.FetchOptions = {
        fetchColumns: [photoAccessHelper.PhotoKeys.URI, photoAccessHelper.PhotoKeys.DISPLAY_NAME],
        predicates: new photoAccessHelper.PhotoFetchOptions()
      };

      const fetchResult = await phAccessHelper.getAssets(fetchOptions);
      const assets = await fetchResult.getAllObjects();
      return assets;
    } catch (error) {
      const err = error as BusinessError;
      console.error('读取相册失败:', err.code, err.message);
      throw error;
    }
  }

  /**
   * 保存文件到公共下载目录
   */
  async saveToPublicDownload(fileName: string, content: string): Promise<void> {
    // 需 ohos.permission.WRITE_MEDIA 权限
    try {
      const downloadPath = `/storage/Users/currentUser/Download/${fileName}`;
      const file = fileIo.openSync(downloadPath, 
        fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE);
      await fileIo.write(file.fd, this.stringToArrayBuffer(content));
      fileIo.closeSync(file);
    } catch (error) {
      console.error('保存到公共目录失败:', error);
      throw error;
    }
  }
}

四、IPC 安全通信机制

4.1 Binder 与 SAMGR 架构

HarmonyOS 的 IPC 机制基于 Binder 驱动,并通过 SAMGR(System Ability Manager)实现服务注册、发现与访问控制:

在这里插入图片描述

IPC 通信的安全检查流程:

  1. UID 校验:验证调用方进程的 UID 身份
  2. 权限检查:校验调用方是否声明了所需权限
  3. SELinux 校验:检查主体/客体的安全上下文是否符合策略规则
  4. 能力令牌(AccessToken):校验应用的能力令牌(Token ID)是否包含目标操作权限
  5. 访问控制列表(ACL):校验调用方是否在服务的白名单中

4.2 自定义 IPC 接口(AIDL 示例)

定义跨进程服务接口:

// ISecureDataService.aidl
interface ISecureDataService {
    // 查询加密数据(需校验调用方权限)
    String querySecureData(String key);

    // 存储加密数据
    boolean storeSecureData(String key, String encryptedData);

    // 删除数据
    boolean deleteSecureData(String key);
}

服务端实现(Stub):

import { rpc } from '@kit.IPCKit';
import { BusinessError } from '@kit.BasicServicesKit';

class SecureDataServiceStub extends rpc.RemoteObject {
  private dataStore: Map<string, string> = new Map();

  constructor(descriptor: string) {
    super(descriptor);
  }

  /**
   * 处理 IPC 调用请求(内部自动进行 UID/权限校验)
   */
  async onRemoteMessageRequest(code: number, data: rpc.MessageSequence, 
                                reply: rpc.MessageSequence, option: rpc.MessageOption): Promise<boolean> {
    // 获取调用方身份信息
    const callingUid = rpc.IPCSkeleton.getCallingUid();
    const callingPid = rpc.IPCSkeleton.getCallingPid();
    const callingTokenId = rpc.IPCSkeleton.getCallingTokenId();

    console.info(`收到 IPC 调用: code=${code}, UID=${callingUid}, PID=${callingPid}`);

    // 校验调用方权限(实际应使用 AccessToken 校验)
    if (!await this.verifyCallerPermission(callingTokenId, 'ohos.permission.ACCESS_SECURE_DATA')) {
      console.warn(`权限拒绝: UID=${callingUid} 无权限访问安全数据`);
      reply.writeString('PERMISSION_DENIED');
      return false;
    }

    switch (code) {
      case 1: // querySecureData
        const queryKey = data.readString();
        const result = this.dataStore.get(queryKey) || '';
        reply.writeString(result);
        return true;

      case 2: // storeSecureData
        const storeKey = data.readString();
        const storeValue = data.readString();
        this.dataStore.set(storeKey, storeValue);
        reply.writeBoolean(true);
        return true;

      case 3: // deleteSecureData
        const deleteKey = data.readString();
        this.dataStore.delete(deleteKey);
        reply.writeBoolean(true);
        return true;

      default:
        console.error(`未知 IPC 调用码: ${code}`);
        return false;
    }
  }

  /**
   * 校验调用方权限
   */
  private async verifyCallerPermission(tokenId: number, permission: string): Promise<boolean> {
    // 实际应调用 AccessToken 服务进行权限校验
    // import { accessControl } from '@kit.AccessControlKit';
    // return accessControl.verifyAccessToken(tokenId, permission) === GrantStatus.PERMISSION_GRANTED;
    return true; // 简化示例
  }
}

export default SecureDataServiceStub;

客户端调用(Proxy):

import { rpc } from '@kit.IPCKit';

class SecureDataServiceProxy {
  private proxy: rpc.IRemoteObject;

  constructor(proxy: rpc.IRemoteObject) {
    this.proxy = proxy;
  }

  async querySecureData(key: string): Promise<string> {
    const data = rpc.MessageSequence.create();
    const reply = rpc.MessageSequence.create();
    const option = new rpc.MessageOption();

    try {
      data.writeString(key);
      await this.proxy.sendMessageRequest(1, data, reply, option);
      return reply.readString();
    } finally {
      data.reclaim();
      reply.reclaim();
    }
  }
}

export default SecureDataServiceProxy;

五、SELinux 强制访问控制

5.1 SELinux 策略模型

HarmonyOS 采用 SELinux(Security-Enhanced Linux)实现强制访问控制(MAC),其核心要素包括:

  • 主体(Subject):应用进程、系统服务
  • 客体(Object):文件、设备、网络端口、IPC 对象
  • 安全上下文(Security Context)user:role:type:level 四元组
  • 策略规则(Policy Rules):allow / deny / type_transition 规则

在这里插入图片描述

5.2 安全上下文示例

# 查看应用进程的安全上下文
ps -eZ | grep com.example.myapp
# 输出:u:r:app_t:s0:c1,c2  com.example.myapp

# 查看应用数据文件的安全上下文
ls -Z /data/app/el1/com.example.myapp/base/files/
# 输出:u:object_r:app_data_file_t:s0:c1,c2  secret.txt

5.3 HarmonyOS SELinux 策略规则

HarmonyOS 预置了严格的 SELinux 策略,关键规则包括:

# 应用进程只能访问自身数据文件
allow app_t app_data_file_t:file { read write create unlink };
allow app_t app_data_file_t:dir { read write search add_name remove_name };

# 应用进程禁止访问系统核心数据
deny app_t system_data_file_t:file *;
deny app_t system_file_t:file *;

# 应用进程禁止直接访问网络(需通过系统服务代理)
deny app_t node_t:tcp_socket *;
allow app_t node_t:tcp_socket { connectto };

# 应用进程只能通过 Binder 与系统服务通信
allow app_t servicemanager:binder { call transfer };
allow app_t system_server:binder { call };

5.4 沙箱逃逸检测

SELinux 的审计日志可用于检测沙箱逃逸尝试:

class SelinuxAuditMonitor {
  /**
   * 解析 SELinux 审计日志(需系统权限,仅示例)
   */
  async parseAuditLogs(): Promise<Array<{ timestamp: string; type: string; detail: string }>> {
    // 实际应读取 /data/misc/audit/audit.log
    // 简化示例:模拟解析拒绝事件
    const mockLogs = [
      {
        timestamp: '2026-08-14T10:30:00Z',
        type: 'AVC_DENIED',
        detail: 'app_t 尝试访问 system_data_file_t 被拒绝'
      },
      {
        timestamp: '2026-08-14T10:35:00Z',
        type: 'AVC_DENIED',
        detail: 'app_t 尝试创建 raw_socket 被拒绝'
      }
    ];
    return mockLogs;
  }

  /**
   * 检测异常访问模式
   */
  detectAnomaly(logs: Array<{ type: string; detail: string }>): boolean {
    const deniedCount = logs.filter(l => l.type === 'AVC_DENIED').length;
    // 短时间内大量拒绝事件可能表明沙箱逃逸尝试
    return deniedCount > 10;
  }
}

六、Seccomp-BPF 系统调用过滤

6.1 Seccomp 机制原理

Seccomp(Secure Computing Mode)是一种内核安全机制,通过 BPF(Berkeley Packet Filter)程序限制进程可调用的系统调用,从而大幅缩小攻击面。

HarmonyOS 为应用进程默认启用 Seccomp-BPF,禁止以下危险系统调用:

禁止的系统调用 原因
open_by_handle_at 防止绕过路径检查直接访问文件
ptrace 防止进程间调试/注入
mount / umount 防止文件系统操作
reboot 防止系统重启
swapon / swapoff 防止交换分区操作
init_module 防止加载内核模块

6.2 系统调用过滤配置

// OpenHarmony Seccomp-BPF 策略配置(简化示意)
// base/security/seccomp/seccomp_policy.c

static const int g_appSyscallBlacklist[] = {
    __NR_ptrace,
    __NR_mount,
    __NR_umount2,
    __NR_reboot,
    __NR_swapon,
    __NR_swapoff,
    __NR_init_module,
    __NR_delete_module,
    __NR_kexec_load,
    __NR_open_by_handle_at,
};

INT32 OsApplySeccompFilter(LosProcessCB *processCB) {
    struct sock_filter filter[SECcomp_MAX_INSTRS];
    struct sock_fprog prog;

    // 构建 BPF 过滤器:允许白名单系统调用,拒绝黑名单
    UINT32 idx = 0;
    filter[idx++] = BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr));

    for (UINT32 i = 0; i < ARRAY_SIZE(g_appSyscallBlacklist); i++) {
        filter[idx++] = BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, g_appSyscallBlacklist[i], 0, 1);
        filter[idx++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL); // 终止进程
    }

    filter[idx++] = BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW); // 允许其他

    prog.len = idx;
    prog.filter = filter;

    return prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}

七、沙箱调试与安全审计

7.1 沙箱调试工具

import { hilog } from '@kit.PerformanceAnalysisKit';

class SandboxDebugger {
  /**
   * 打印当前进程的沙箱信息
   */
  async dumpSandboxInfo(): Promise<void> {
    const context = getContext();

    hilog.info(0x0000, 'SandboxDebug', '===== 沙箱信息 dump =====');
    hilog.info(0x0000, 'SandboxDebug', `BundleName: ${context.applicationInfo.name}`);
    hilog.info(0x0000, 'SandboxDebug', `FilesDir: ${context.filesDir}`);
    hilog.info(0x0000, 'SandboxDebug', `CacheDir: ${context.cacheDir}`);
    hilog.info(0x0000, 'SandboxDebug', `DatabaseDir: ${context.databaseDir}`);
    hilog.info(0x0000, 'SandboxDebug', `PreferencesDir: ${context.preferencesDir}`);
    hilog.info(0x0000, 'SandboxDebug', `DistributedFilesDir: ${context.distributedFilesDir}`);

    // 获取进程信息
    const uid = await ProcessIsolationHelper.getCurrentAppUid();
    const gid = await ProcessIsolationHelper.getCurrentAppGid();
    hilog.info(0x0000, 'SandboxDebug', `UID: ${uid}, GID: ${gid}`);
  }

  /**
   * 检查文件是否在沙箱内
   */
  isPathInSandbox(filePath: string): boolean {
    const context = getContext();
    const sandboxRoot = context.filesDir.replace('/files', '');

    // 路径规范化后检查前缀
    const normalizedPath = filePath.replace(/\/g, '/').replace(/\.\.\//g, '');
    return normalizedPath.startsWith(sandboxRoot) || 
           normalizedPath.startsWith(context.cacheDir) ||
           normalizedPath.startsWith(context.tempDir);
  }
}

7.2 安全审计日志

interface SandboxAuditEvent {
  timestamp: number;
  eventType: 'FILE_ACCESS' | 'IPC_CALL' | 'PERMISSION_CHECK' | 'SECCOMP_VIOLATION';
  target: string;
  action: string;
  result: 'ALLOWED' | 'DENIED';
  uid: number;
  pid: number;
}

class SandboxAuditLogger {
  private events: SandboxAuditEvent[] = [];

  log(event: Omit<SandboxAuditEvent, 'timestamp'>): void {
    this.events.push({
      timestamp: Date.now(),
      ...event
    });
  }

  /**
   * 导出沙箱访问报告
   */
  generateReport(): string {
    const deniedEvents = this.events.filter(e => e.result === 'DENIED');
    const report = {
      totalEvents: this.events.length,
      deniedEvents: deniedEvents.length,
      deniedRate: (deniedEvents.length / this.events.length * 100).toFixed(2) + '%',
      topDeniedTargets: this.getTopDeniedTargets(5),
      events: this.events.slice(-100) // 最近 100 条
    };
    return JSON.stringify(report, null, 2);
  }

  private getTopDeniedTargets(limit: number): Array<{ target: string; count: number }> {
    const counts = new Map<string, number>();
    this.events
      .filter(e => e.result === 'DENIED')
      .forEach(e => counts.set(e.target, (counts.get(e.target) || 0) + 1));

    return Array.from(counts.entries())
      .map(([target, count]) => ({ target, count }))
      .sort((a, b) => b.count - a.count)
      .slice(0, limit);
  }
}

八、沙箱安全最佳实践

8.1 开发者安全编码规范

规范项 要求 风险
路径校验 所有文件路径必须经过 sanitizeFileName 处理 路径遍历攻击
IPC 鉴权 服务端必须校验调用方 UID/Token/权限 未授权访问
数据加密 敏感数据必须加密后存储在沙箱内 数据泄露
最小权限 module.json5 中只声明必需的权限 权限滥用
缓存清理 定期清理 cache/temp/ 目录 敏感数据残留
防截屏 敏感页面启用 setWindowPrivacyMode 屏幕截图泄露

8.2 权限声明最小化示例

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:permission_internet_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inUse"
        }
      }
    ]
  }
}

8.3 沙箱逃逸防护清单

class SandboxEscapePrevention {
  /**
   * 运行时沙箱完整性检查
   */
  async runtimeCheck(): Promise<{ safe: boolean; issues: string[] }> {
    const issues: string[] = [];
    const context = getContext();

    // 1. 检查私有目录权限
    try {
      const stat = fileIo.statSync(context.filesDir);
      // 检查是否只有自身 UID 可访问
      if ((stat.mode & 0o077) !== 0) {
        issues.push('警告:私有目录权限过于宽松');
      }
    } catch (error) {
      issues.push('错误:无法访问私有目录');
    }

    // 2. 检查是否运行在调试模式
    if (context.applicationInfo.debug) {
      issues.push('警告:应用运行在调试模式,沙箱保护可能减弱');
    }

    // 3. 检查是否被 Root
    try {
      fileIo.accessSync('/system/bin/su');
      issues.push('严重:检测到 Root 环境,沙箱可能被绕过');
    } catch {
      // 正常:无法访问 su
    }

    return {
      safe: issues.length === 0,
      issues
    };
  }
}

九、总结

本文从内核到应用层,系统解析了 HarmonyOS 应用沙箱的完整技术体系:

  1. UID/GID 进程隔离是沙箱的基石,每个应用拥有独立的进程身份标识,内核通过 MMU 实现地址空间隔离;
  2. 文件系统沙箱通过私有目录 + DAC 权限 + FSCrypt 加密,确保应用数据互不侵犯;
  3. IPC 安全通信通过 Binder 驱动 + SAMGR + AccessToken 实现受控的跨进程交互;
  4. SELinux 强制访问控制在 DAC 之上叠加了策略驱动的访问控制,即使应用获取 Root 权限也无法突破策略限制;
  5. Seccomp-BPF通过系统调用过滤,从根本上缩小了应用的攻击面;
  6. 安全审计与调试工具为开发者提供了沙箱运行时的可观测性。

理解沙箱机制不仅是安全开发的基础,更是构建可信应用生态的前提。开发者应在日常编码中始终遵循"最小权限原则"和"纵深防御"理念,将安全意识融入每一行代码。


转载自:https://blog.csdn.net/u014727709/article/details/163783091
欢迎 👍点赞✍评论⭐收藏,欢迎指正

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐