当前位置: 首页 > news >正文

【ZeroRange WebRTC】KVS WebRTC C SDK 崩溃分析报告

KVS WebRTC C SDK 崩溃分析报告

项目:Amazon Kinesis Video Streams WebRTC C SDK(Master 示例,使用系统 OpenSSL 与系统 libwebsockets)
项目地址:https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c

摘要

  • 现象:运行 kvsWebrtcClientMaster 示例时出现 “stack smashing detected: terminated”,在开启 AddressSanitizer(ASan)后报告为栈缓冲区溢出。
  • 根因:IceAgent.c 的定时器回调在一次执行中复制了超过固定上限数量的本地 ICE 候选到栈数组,导致越界写入。
  • 修复:
    • 为本地候选复制逻辑增加严格上限检查,确保不会写出数组边界。
    • 在信令路径中补充字符串终止与空列表处理,避免边界条件触发栈破坏。
    • 为缺失的 libwebsockets 函数提供本地替代实现,解决链接期符号不可用问题。
  • 结论:崩溃源于栈数组越界,已通过边界保护修复;建议继续在 ASan 下验证,并对 JSON 拼接做进一步长度检查以提高健壮性。

环境与构建

  • 操作系统:Linux
  • 构建选项(使用系统依赖):
    • cmake .. -DBUILD_DEPENDENCIES=OFF -DUSE_OPENSSL=ON
  • 诊断构建(建议用于验证与定位):
    • cmake .. -DCMAKE_BUILD_TYPE=Debug -DADDRESS_SANITIZER=ON -DCOMPILER_WARNINGS=ON
    • make -j$(nproc)

现象与日志

  • 非 ASan 情况:
    *** stack smashing detected ***: terminated
  • ASan(关键摘录):
    • 报错位置:src/source/Ice/IceAgent.c:1636,函数 iceAgentGatherCandidateTimerCallback
    • 越界对象:栈数组 newLocalCandidates[...] 被写出边界
    • 写入大小与结构体 IceCandidate 大小一致

分析过程

  • 初次构建依赖拉取失败(网络超时),切换为系统依赖构建(OpenSSL、libwebsockets)。
  • 链接期出现 lws_http_date_parse_unix 未定义;系统 libwebsockets 不提供该符号:
    • LwsApiCalls.c 添加本地 parse_http_date_unix(解析 RFC 7231 IMF‑fixdate),替换原调用点。
  • 运行示例进入 ICE 交换阶段时触发 “stack smashing detected”。
  • 启用 ASan 后精确定位为 IceAgent.c:1636 的栈数组越界。
  • 分析代码逻辑并确认:一次定时器回调中可能有超过固定上限的 “VALID 且未报告” 的候选被复制至栈数组,导致越界。
  • 实施修复并验证。

根因与原理

问题代码(摘录)

文件:src/source/Ice/IceAgent.c
函数:iceAgentGatherCandidateTimerCallback

  • 关键栈数组:
    • IceCandidate newLocalCandidates[KVS_ICE_MAX_NEW_LOCAL_CANDIDATES_TO_REPORT_AT_ONCE];(默认上限 10)
  • 原始复制逻辑:
    • 无上限判断地执行:
      • newLocalCandidates[newLocalCandidateCount++] = *pIceCandidate;
      • pIceCandidate->reported = TRUE;
    • 若当前回调中有超过 10 个候选满足条件(VALID && !reported),则发生越界写。

为什么看起来在 “Client is writable” 附近崩溃

  • 栈保护(Stack Protector)的 “金丝雀” 校验通常在函数返回或随后调用过程中触发。实际越界写发生在更早的定时器回调;当执行到可写回调或其他路径时,才检测到栈被破坏并抛错。

栈保护与 ASan 简述

  • 栈保护器在局部栈帧中放置“金丝雀”值以检测越界写;返回时校验金丝雀是否被破坏。
  • ASan利用影子内存监控访问合法性,对栈/堆红区的越界写会立即报错,并提供精确的源码行与调用栈。

ASan 工作原理详解与使用建议

原理概述

  • AddressSanitizer(ASan)在编译期对内存访问插桩,并在运行时维护一段“影子内存(shadow memory)”。程序内存区域周边会被“红区”保护(例如栈红区、堆红区),任何对红区的读/写都被视为越界并立即上报。
  • ASan 报告中的影子字节图示(例如 f3 f3 f3 ...)代表不同类型的“红区/毒化”状态:
    • f1/f2/f3 等常见值表示栈的左/中/右红区;对这些区域的访问就是典型栈越界。
    • fa 表示堆左红区;fb/fd 等用于区分不同堆状态(释放后区域、内部使用等)。
  • 报告会包含:违规访问类型(READ/WRITE)、大小、精确源文件/行号、调用栈,以及“该栈帧的对象布局”,帮助定位具体变量越界。

示例 ASan 报告(节选)与字段解读

WRITE of size 96 at 0x... thread T6#0  iceAgentGatherCandidateTimerCallback .../Ice/IceAgent.c:1636#1  timerQueueExecutor .../kvspic-src/src/utils/src/TimerQueue.c:587#2  start_thread nptl/pthread_create.c:442#3  __libc ...Address ... is located in stack of thread T6 at offset 1120 in frame#0  iceAgentGatherCandidateTimerCallback .../Ice/IceAgent.c:1595This frame has 4 object(s):[160, 1120) 'newLocalCandidates' <== Memory access at offset 1120 overflows this variableShadow bytes around the buggy address:... f3 f3 f3 f3 f3 f3 f3 f3 ...
Shadow byte legend:Stack right redzone:     f3Stack left redzone:      f1Heap left redzone:       faFreed heap region:       fd
  • 关键字段说明:
    • WRITE of size 96:写入了 96 字节(与 IceCandidate 结构体大小一致)。
    • IceAgent.c:1636:精确定位到源码行,便于在 IDE 中快速跳转修复。
    • This frame has N object(s):罗列当前栈帧内的局部变量布局,标注越界变量(本案为 newLocalCandidates)。
    • Shadow bytes ... f3f3 代表“栈右红区”,即典型栈越界写;结合 legend 可快速判断越界类型。

如何阅读 ASan 报告(结合本案)

  • WRITE of size 96 ... in IceAgent.c:1636:表示写入 96 字节(等于 IceCandidate 结构体大小),定位到源代码第 1636 行。
  • “This frame has N object(s)” 与后续 <== Memory access at offset ... overflows this variable 指明哪个局部变量越界(本案为 newLocalCandidates)。
  • “Shadow bytes around the buggy address” 用于确认内存区域性质(f3 为栈右红区,典型的栈越界写)。

启用与配置建议

  • 构建:
    • cmake .. -DCMAKE_BUILD_TYPE=Debug -DADDRESS_SANITIZER=ON -DCOMPILER_WARNINGS=ON
    • make -j$(nproc)
  • 运行时推荐环境:
    • ASAN_OPTIONS="detect_stack_use_after_return=1:strict_string_checks=1:abort_on_error=1"
    • 如需更详细符号化:确保安装符号文件并在 CMAKE_BUILD_TYPE=Debug 下编译。
  • 与 GDB 联用(必要时):可用 gdb --args ./samples/kvsWebrtcClientMaster <ChannelName> 运行,在崩溃点查看线程与栈帧现场。

常见问题与排查策略

  • 报告定位在某函数,但越界写可能更早发生:这通常由于栈红区在函数返回或后续栈访问时才触发检查,因此需结合调用栈前后函数来回溯真实写入点。
  • 字符串操作高危:strcat/strcpy/sprintf 等非安全函数容易踩红区。建议统一使用有界版本(strn*snprintf)并进行长度预检与手动终止符处理。

栈保护(Stack Protector)与“金丝雀”详解

机制简介

  • 编译器在函数栈帧中插入一个随机值(“金丝雀”),位于局部缓冲区与返回地址之间。
  • 函数返回前对金丝雀做校验:若被改写(例如栈缓冲越界覆盖),则判定为“栈破坏”,立即报错并终止进程(常见信息为 *** stack smashing detected ***: terminated)。

触发时机与表现

  • 越界写可能发生在 A 函数,但错误信息常在 A 返回或后续 B 函数执行时出现;这是因为金丝雀在返回点或下一次栈解构时才进行校验。
  • 与 ASan 不同:ASan 倾向于在越界写当下就报告(依赖于插桩覆盖),栈保护更像“退出时校验”的最后防线;两者结合使用可最大化发现问题。

与本案关系

  • 本案中,多条日志显示在 “Client is writable” 附近崩溃;实际越界写发生在更早的 iceAgentGatherCandidateTimerCallback 中,栈保护在后续路径才检测到金丝雀被改写,从而统一报错并终止。

项目集成:ASan 与栈保护如何启用、两者关系

ASan 集成(CMake)

  • 顶层 CMakeLists.txt 提供多个 Sanitizer 开关:
option(ADDRESS_SANITIZER "Build with AddressSanitizer." OFF)
option(MEMORY_SANITIZER  "Build with MemorySanitizer."  OFF)
option(THREAD_SANITIZER  "Build with ThreadSanitizer."  OFF)
option(UNDEFINED_BEHAVIOR_SANITIZER "Build with UndefinedBehaviorSanitizer." OFF)if("${CMAKE_C_COMPILER_ID}" MATCHES "GNU|Clang")if(ADDRESS_SANITIZER)enableSanitizer("address")endif()...
endif()
  • CMake/Utilities.cmake 中的 enableSanitizer 会追加编译与链接标志:
function(enableSanitizer SANITIZER)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g -fsanitize=${SANITIZER} -fno-omit-frame-pointer" PARENT_SCOPE)set(CMAKE_C_FLAGS   "${CMAKE_C_FLAGS}   -O0 -g -fsanitize=${SANITIZER} -fno-omit-frame-pointer -fno-optimize-sibling-calls" PARENT_SCOPE)set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=${SANITIZER}" PARENT_SCOPE)
endfunction()
  • 使用方式:配置时开启相应选项,例如:
cmake .. -DCMAKE_BUILD_TYPE=Debug -DADDRESS_SANITIZER=ON -DCOMPILER_WARNINGS=ON
make -j$(nproc)
ASAN_OPTIONS="detect_stack_use_after_return=1:strict_string_checks=1:abort_on_error=1" ./samples/kvsWebrtcClientMaster <ChannelName>

栈保护(Stack Protector)集成

  • 本工程未在 CMake 中显式设置 -fstack-protector*,栈保护通常由系统编译器默认启用(例如许多 Linux 发行版默认启用 -fstack-protector-strong)。
  • 如果需要在工程层面强制启用或增强栈保护,可在配置时追加编译旗标:
cmake .. -DCMAKE_BUILD_TYPE=Debug -DADDRESS_SANITIZER=ON \-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -fstack-protector-strong" \-DCOMPILER_WARNINGS=ON

两者关系与建议

  • ASan 与栈保护互为补充:
    • ASan:在越界读/写发生时即刻通过影子内存检测,提供更丰富的上下文与调用栈;适用于开发/调试阶段。
    • 栈保护:在函数返回或后续栈操作时校验“金丝雀”,作为运行时最后防线;一般在生产构建也可保留。
  • 性能与内存开销:
    • ASan 会显著增加内存占用与运行时开销(常见 1.5×~2×),建议在 Debug 或专门的检测构建中启用;
    • 栈保护开销较小,建议长期启用 -fstack-protector-strong

详细分析过程(分步)

  1. 初始构建阶段:
    • 外部依赖(OpenSSL/boringssl 子模块)拉取失败 → 采用系统依赖(BUILD_DEPENDENCIES=OFFUSE_OPENSSL=ON),成功生成工程。
  2. 链接期修复:
    • 系统 libwebsockets 不提供 lws_http_date_parse_unix → 在 LwsApiCalls.c 添加 parse_http_date_unix(RFC 7231 IMF‑fixdate 解析),并替换原调用,解决链接未定义符号。
  3. 运行与初步怀疑:
    • 在发送 ICE_CANDIDATE 后出现 “stack smashing detected”。
    • 初步检查信令路径的 JSON 构造、payload 终止符与 URIs 尾逗号处理,修复潜在边界问题(空列表与手动 \0 终止)。
  4. 启用 ASan 精确定位:
    • 开启 ASan 并重现问题,报告显示 IceAgent.c:1636WRITE of size 96,越界对象为栈数组 newLocalCandidates
    • 结合常量 KVS_ICE_MAX_NEW_LOCAL_CANDIDATES_TO_REPORT_AT_ONCE=10,判断在一次回调中复制超过 10 个“VALID 且未报告”的候选导致溢出。
  5. 根因确认与修复:
    • 增加上限保护:仅在 newLocalCandidateCount < 上限 时复制并标记 reported=TRUE;超过上限的候选留待下一轮定时器回调。
  6. 回归与验证:
    • 在 ASan 构建下运行,确认不再出现 stack-buffer-overflow
    • 观察候选报告是否分批进行,符合原注释“每次最多报告 N 个”的设计意图。
  7. 加固与建议:
    • 在信令 JSON 拼接处加入长度预检(encodedUrisencodedIceConfig),保持所有有界拷贝后显式 \0 终止;将 ASan 与编译器警告纳入常态化验证。

修复方案(已应用)

1)为栈数组复制增加上限保护

只在未达到上限时复制并标记为已报告;超出上限的候选保留为未报告,交由下次回调继续处理(符合原注释“每次最多报告 N 个”的设计意图)。

示例修复代码:

else if (pIceCandidate->state == ICE_CANDIDATE_STATE_VALID && !pIceCandidate->reported) {if (newLocalCandidateCount < KVS_ICE_MAX_NEW_LOCAL_CANDIDATES_TO_REPORT_AT_ONCE) {newLocalCandidates[newLocalCandidateCount++] = *pIceCandidate;pIceCandidate->reported = TRUE;}if (pIceCandidate->iceCandidateType == ICE_CANDIDATE_TYPE_SERVER_REFLEXIVE) {CHK_STATUS(createIceCandidatePairs(pIceAgent, pIceCandidate, FALSE));}
}

2)信令路径的安全性增强

  • LwsApiCalls.c
    • 构造 ICE URIs 时删除末尾逗号前先判断长度(空列表不做递减),避免下标越界:
      • urisLen > 0,再执行 encodedUris[--urisLen] = '\0';
  • samples/Common.c
    • candidateJson 拷贝到 message.payload 后显式添加字符串结束符,避免后续日志或编码读取越界:
      • message.payload[message.payloadLen] = '\0';

3)libwebsockets 缺失符号的兼容处理

系统 libwebsockets 不提供 lws_http_date_parse_unix;在 LwsApiCalls.c 中添加本地 parse_http_date_unix(const char* buf, size_t len, time_t* out) 并替换调用,以兼容系统库。


验证步骤

  • 建议在开启 ASan 的构建下验证:
    • cmake .. -DCMAKE_BUILD_TYPE=Debug -DADDRESS_SANITIZER=ON -DCOMPILER_WARNINGS=ON
    • make -j$(nproc)
    • 运行示例:./samples/kvsWebrtcClientMaster <ChannelName>
  • 观察是否仍有 stack-buffer-overflow 报错;同时确认 ICE 候选报告行为符合“分批上报”的预期。

进一步加固建议

为了提升字符串处理的健壮性,建议增加长度预检:

  • encodedUris 拼接前检查:
    • 每次追加前判断:STRLEN(encodedUris) + 3 + STRLEN(uri) <= MAX_ICE_SERVER_URI_STR_LEN(3 为引号和逗号额外字符)
    • 超出则停止追加或截断
  • encodedIceConfig 累计长度检查:
    • 判断:iceConfigLen + MAX_ICE_SERVER_INFO_STR_LEN <= MAX_ENCODED_ICE_SERVER_INFOS_STR_LEN
    • 超出则不打包 IceServerList(Offer 不带该字段仍可工作)
  • 保持在有界拷贝后显式添加 '\0'(已在样例中应用)

经验总结

  • 固定大小的栈数组必须在运行时对聚合数量做上限保护,尤其是当数据可能在某个时刻集中变为有效状态时(如 ICE 候选)。
  • 代码注释的设计约束(“每次最多报告 N 个”)必须由实际代码逻辑保证,而非仅停留在说明。
  • 使用系统库时可能出现符号差异(libwebsockets 缺函数),需添加兼容实现以保证可构建与可运行。
  • ASan 是定位内存越界的高效工具,建议作为本地调试与 CI 的常设选项之一。

关键常量与尺寸(摘录)

  • KVS_ICE_MAX_NEW_LOCAL_CANDIDATES_TO_REPORT_AT_ONCE = 10(每次回调最多报告的本地候选数量)
  • MAX_SDP_ATTRIBUTE_VALUE_LENGTH = 512(候选 SDP 字符串长度上限)
  • MAX_ICE_CONFIG_URI_LEN = 127MAX_ICE_CONFIG_URI_COUNT = 4
  • MAX_SIGNALING_MESSAGE_LEN = 18750
  • LWS_MESSAGE_BUFFER_SIZE = SIZEOF(CHAR) * (MAX_SIGNALING_MESSAGE_LEN + LWS_PRE)

构建与运行示例

  • 使用系统依赖构建:
    • rm -rf build && mkdir build && cd build
    • cmake .. -DBUILD_DEPENDENCIES=OFF -DUSE_OPENSSL=ON
    • make -j$(nproc)
  • 启用 ASan 验证:
    • cmake .. -DCMAKE_BUILD_TYPE=Debug -DADDRESS_SANITIZER=ON -DCOMPILER_WARNINGS=ON
    • make -j$(nproc)
    • ./samples/kvsWebrtcClientMaster <ChannelName>

变更摘要

  • IceAgent.c:为 newLocalCandidates 复制加上限保护,避免栈数组越界。
  • LwsApiCalls.c:修正空 URIs 列表尾逗号处理;添加本地 parse_http_date_unix 替换缺失符号。
  • samples/Common.c:拷贝候选 JSON 后显式 '\0' 终止,避免读取越界。
http://www.dtcms.com/a/596646.html

相关文章:

  • 库卡机器人编程语言 | 深入了解库卡机器人的编程方法与应用
  • 移动+协作+视觉=?复合型机器人重新定义智能产线
  • 【macOS 版】Android studio jdk 1.8 gradle 一键打包成 release 包的脚本
  • 网站关键词优化原理亳州做企业网站
  • 数据库知识整理——SQL数据定义
  • AAAI 2026|港科大等提出ReconVLA:利用视觉重构引导,刷新机器人操作精度!(含代码)
  • Java 进阶:IO 流与 Lambda 表达式深度解析
  • 集团公司网站推广方案怎么做十年网站建设
  • 张祥前统一场论视角下的托卡马克Z箍缩不稳定性解读
  • 【每天一个AI小知识】:什么是MCP协议?
  • 在 kubernetes 上使用 SMB 协议做存储的「即插即用」方案
  • 软件测试大赛Web测试赛道工程化ai提示词大全
  • 智慧团建官方网站登录入口优秀的网站建设价格
  • 静海做网站公司十一月新闻大事件摘抄
  • GJOI 11.6 题解
  • Java Excel 导出:EasyExcel 使用详解
  • 【SOMEIP】【R24-11】【需求翻译】[RS_SOMEIP_00002]-[RS_SOMEIP_00004]
  • VMware无法将网络更改为桥接状态:没有未桥接的主机网络适配器
  • maven打包问题/ClassNotFoundException异常
  • 告别局域网限制!Windows快速部署Docsify技术文档站点,搭配cpolar内网穿透实现公网随时随地访问
  • Python每日一练---第十二天:验证回文串
  • 【Docker】Dockerfile自定义镜像
  • 1.3 Spring的入门程序
  • 网站内的链接怎么做修改wordpress 表格
  • 企业网站源码是什么网站内链怎么优化
  • FPGA-zynq PS与PL的交互(一)
  • 做电影网站需要施工企业会计核算及常用会计分录
  • CONFIG_TRACEPOINTS和CONFIG_FTRACE的作用
  • LeetCode 热题 100——哈希——字母异位词分组
  • MATLAB 计算两点直线方程(叉乘)