SubmittedCWE-476 — NULL Pointer Dereference
GetDataBusName 中 ReadString 返回值未检查导致空指针崩溃
View Upstream Issuegitcode.com/openharmony/communication_ipc/issues/1917漏洞概述
在 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
自然触发场景
即使没有攻击者,以下情况也可能触发:
- 内存不足:IPC 缓冲区分配不足导致回复消息截断
- 竞态条件:服务端在写入回复过程中被中断
- 跨版本兼容:协议格式变更导致数据长度不匹配
触发条件
- 目标进程调用
GetDataBusName()获取数据总线名称 - IPC 回复中的
proto字段等于IF_PROT_DATABUS(通过前置检查) - 回复中的字符串长度前缀有效,但实际字符串数据不足
调用链
分布式 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实现)