Dashboard/Issues/OH-2026-IPC-003
SubmittedCWE-476 — NULL Pointer Dereference

GetDataBusName 中 ReadString 返回值未检查导致空指针崩溃

View Upstream Issuegitcode.com/openharmony/communication_ipc/issues/1917
CWE:CWE-476 — NULL Pointer Dereference
Date:2026-05-07
Reporter:Zirui

漏洞概述

在 OpenHarmony 的 Small 系统 RPC 实现中,GetDataBusName 函数调用 ReadString 从 IPC 回复消息中读取会话名称,但未检查返回值是否为 NULL。当 IPC 回复消息中的字符串数据被截断时(长度前缀有效但实际字符串数据不足),ReadString 返回 NULL,随后该 NULL 指针被传入 strcpy_s 导致进程崩溃。

漏洞详情

问题代码

文件: ipc/native/c/rpc/ipc_adapter/small/ipc_proxy_inner.c

// Lines 187-237
char *GetDataBusName(void)
{
    IpcIo data;
    IpcIo reply;
    uint8_t dataAlloc[RPC_IPC_LENGTH];
    IpcIoInit(&data, dataAlloc, RPC_IPC_LENGTH, 0);
    MessageOption option = {
        .flags = TF_OP_SYNC
    };
    uintptr_t ptr;
    SvcIdentity *identity = (SvcIdentity *)GetContextObject();
    if (identity == NULL) {
        return NULL;
    }
    int32_t ret = ProcessSendRequest(*identity, GRANT_DATABUS_NAME, &data, &reply, option, &ptr);
    if (ret != ERR_NONE) {
        RPC_LOG_ERROR("sendrequest GRANT_DATABUS_NAME failed, error %d", ret);
        FreeBuffer((void *)ptr);
        return NULL;
    }

    int32_t proto;
    if (!ReadInt32(&reply, &proto)) {
        FreeBuffer((void *)ptr);
        return NULL;
    }

    if (proto != IF_PROT_DATABUS) {
        RPC_LOG_INFO("GetDataBusName normal binder");
        FreeBuffer((void *)ptr);
        return NULL;
    }
    
    size_t len;
    const char *name = (const char *)ReadString(&reply, &len);
    // ⚠️ 此处未检查 name 是否为 NULL
    
    RPC_LOG_INFO("GetDataBusName name %s, len %lu", name, len);  // ← name=NULL 时格式化崩溃
    
    char *sessionName = (char *)malloc(len + 1);
    if (sessionName == NULL) {
        RPC_LOG_ERROR("GetDataBusName sessionName malloc failed");
        FreeBuffer((void *)ptr);
        return NULL;
    }
    
    if (strcpy_s(sessionName, len + 1, name) != EOK) {  // ← name=NULL 时 SIGSEGV
        RPC_LOG_ERROR("GetDataBusName sessionName copy failed");
        free(sessionName);
        FreeBuffer((void *)ptr);
        return NULL;
    }

    FreeBuffer((void *)ptr);
    return sessionName;
}

ReadString 的内部实现

// serializer.c
const char *ReadString(IpcIo *io, size_t *len)
{
    // 1. 先读取长度前缀
    bool ret = ReadUint32(io, (uint32_t *)len);  // ← 读取成功,len 被设为有效值
    if (!ret) {
        return NULL;
    }
    // 2. 尝试从缓冲区弹出 *len+1 字节的字符串数据
    return (const char *)IoPop(io, *len + 1);  // ← 如果缓冲区不足,返回 NULL
    //                                                 但 len 已经被设为了有效值!
}

关键问题ReadString 先读取长度前缀(第 1 步成功),再读取字符串内容(第 2 步可能失败返回 NULL)。当第 2 步失败时,len 已被设为非零值(例如 32),导致调用方认为有有效的字符串但实际上 name 是 NULL。

攻击场景

场景:恶意 IPC 回复消息

攻击者(伪装的 SAMGR / 中间人)           目标进程(RPC 客户端)
    |                                         |
    | ← GRANT_DATABUS_NAME 请求 ------------- |
    |                                         |
    | 1. 构造恶意回复                          |
    |    proto = IF_PROT_DATABUS (合法)        |
    |    string_len = 32 (有效长度前缀)        |
    |    string_data = <截断/不足>             |
    |                                         |
    | 2. 返回回复                              |
    |---------------------------------------> |
    |                                         | 3. ReadInt32 → proto = IF_PROT_DATABUS ✓
    |                                         | 4. ReadString(&reply, &len)
    |                                         |    → ReadUint32 → len = 32 ✓
    |                                         |    → IoPop(io, 33) → NULL ✗
    |                                         |    返回 NULL, 但 len = 32
    |                                         | 5. RPC_LOG_INFO("name %s", NULL) → 崩溃
    |                                         |    或
    |                                         |    strcpy_s(sessionName, 33, NULL) → SIGSEGV

自然触发场景

即使没有攻击者,以下情况也可能触发:

  1. 内存不足:IPC 缓冲区分配不足导致回复消息截断
  2. 竞态条件:服务端在写入回复过程中被中断
  3. 跨版本兼容:协议格式变更导致数据长度不匹配

触发条件

  1. 目标进程调用 GetDataBusName() 获取数据总线名称
  2. IPC 回复中的 proto 字段等于 IF_PROT_DATABUS(通过前置检查)
  3. 回复中的字符串长度前缀有效,但实际字符串数据不足

调用链

分布式 Binder 客户端初始化
  → GetPidAndUidInfoStub  (ipc_stub_inner.c:159)
    → GetDataBusName()  ← 漏洞触发点
      → ProcessSendRequest(GRANT_DATABUS_NAME)
      → ReadString(&reply, &len)  ← 返回 NULL
      → strcpy_s(sessionName, len+1, NULL)  ← SIGSEGV

影响分析

  • 进程崩溃 (SIGSEGV)strcpy_s 的源参数为 NULL 时触发段错误
  • 影响范围:所有使用 Small 内核分布式 Binder 的 RPC 客户端进程
  • 可靠性降级:在网络不稳定的环境中(分布式场景),IPC 消息截断的概率更高

修复建议

    size_t len;
    const char *name = (const char *)ReadString(&reply, &len);
+   if (name == NULL) {
+       RPC_LOG_ERROR("GetDataBusName ReadString failed");
+       FreeBuffer((void *)ptr);
+       return NULL;
+   }
    RPC_LOG_INFO("GetDataBusName name %s, len %lu", name, len);
    char *sessionName = (char *)malloc(len + 1);

同时建议:审查所有调用 ReadString 的代码路径,确保都有 NULL 检查。搜索 ReadString( 在以下文件中的使用:

  • ipc_proxy_inner.c (small 和 mini 两个变体)
  • ipc_stub_inner.c
  • 其他 rpc/ipc_adapter/ 下的文件

涉及文件

  • ipc/native/c/rpc/ipc_adapter/small/ipc_proxy_inner.c (line 220-228)
  • ipc/native/c/rpc/ipc_adapter/mini/ipc_proxy_inner.c (line 146, 同族问题)
  • ipc/native/c/manager/src/serializer.c (ReadString 实现)