
flipperzero-firmware 中的 Mbed TLSPSA Crypto 共享内存安全防护架构与实现【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware本文基于 flipperzero-firmware 仓库内嵌的 Mbed TLS 架构设计文档 psa-shared-memory.md系统讲解当 PSA Crypto API 的输入/输出缓冲区位于与其他分区partition共享的内存时密码库面临的安全威胁模型、防护设计决策以及 Mbed TLS 实际落地的缓冲拷贝机制与内存投毒memory poisoning验证体系。读完后你将理解 Mbed TLS 是如何保证未信任进程在运算期间篡改共享缓冲不会破坏密码服务的安全性这一核心承诺的并能定位到本仓库中的对应源码与测试基础设施。1. 背景与适用范围为什么 PSA API 需要防共享内存文档首先给出两条明确的范围假设Core assumptions这是理解全文的前提只考虑PSA Crypto API函数含 Mbed TLS 对官方 API 规范的扩展。所有不以psa_xxx命名的接口——Legacy crypto、X.509、TLS 等——均不在讨论范围内只考虑 PSA 规范定义的输入缓冲区和输出缓冲区这两类参数。其余数据假定位于非共享内存中。1.1 目标系统架构文档讨论的系统具备分区级的内存隔离一个分区不能直接访问另一个分区的内存分区之间只能通过明确定义的接口互相影响完整性。这类系统包括Unix/POSIX 风格的进程隔离系统依赖 TrustZone 等机制的安全世界secure world与非安全世界之间的隔离安全世界内部各应用之间的隔离。更具体地说文档假设 PSA Crypto 的实现运行在某个分区内称为密码服务crypto service。该服务接收其他分区的远程过程调用RPC校验参数例如校验 key identifier 的归属权然后调用 PSA Crypto API 函数。文档关注的正是这样一类环境传给 PSA 函数的参数可能位于共享内存中——而不是输入总是先被拷贝进密码服务独占内存、输出总是在函数返回后才暴露的那类环境。1.2 核心安全承诺当数据可被另一个分区访问时该分区可能在密码实现正在处理数据的同时访问它。虽然理论上可以在密码运算期间挂起整个系统来规避但这种限制很少被需要且多数系统根本不提供该手段——即使某个系统拥有绝对线程优先级crypto 的优先级高于任何未信任分区在多核或存在外设异步数据传输的情况下仍可能遭殃。因此密码服务必须保证它在执行期间表现得仿佛全世界都被挂起了。任何一种只有在未信任实体于密码服务处理数据期间访问了缓冲区才可能发生的行为都构成安全违规。2. 威胁模型四类共享内存风险文档将安全架构建模为两个或三个实体提供共享内存 RPC 的密码服务、发起 RPC 的客户端以及某些场景下客户端的客户端——它向客户端发起 RPC而客户端又把内存重新共享re-share给密码服务。RPC 的行为按输入/输出值的语义来定义这模拟了一个理想世界输入/输出缓冲区的内容在密码服务处理 RPC 期间对外不可见。如果密码服务表现出一种不可能仅通过在 RPC 前设置输入、RPC 后读取输出来实现的行为即为安全违规。在此模型下文档归纳出四类风险2.1 读-读不一致Read-read inconsistency当输入参数位于共享内存时密码代码读取输入的一部分并做校验或将其注入计算客户端或客户端的客户端修改了该输入密码代码再次读取同一部分执行了一个如果输入始终不变就不可能发生的动作。文档给出两个漏洞示例解析类输入采用 TLV 或 length-value 编码如导入 RSA 密钥。代码先读长度字段并校验其不越界之后又读一次长度并直接使用。恶意客户端可在两次读取之间修改共享内存中的长度字段从而在第二次读取时造成缓冲越界读。双重处理类一次执行 encrypt-and-MAC 结构认证加密的 RPC。客户端把明文设为PPPP发起 RPC 后趁密码服务处理期间把缓冲区改成QQQQ。enc(PPPP)mac(PPPP)、enc(PPQQ)mac(PPQQ)、enc(QQQQ)mac(QQQQ)都是合法输出但如果实现先计算密文、再读一次输入计算 MAC输出将是enc(PPPP)mac(QQQQ)——不存在任何输入能产生该结果安全承诺被打破。2.2 写-读不一致Write-read inconsistency当输出参数位于共享内存时密码代码把中间数据写入输出缓冲客户端修改了这些中间数据密码代码把中间数据读回来继续计算得到中间数据未被篡改就不可能产生的结果。示例若 RSA 签名函数先在输出缓冲中原地格式化数据、再原地执行 RSA 私钥运算mbedtls_rsa_pkcs1_sign正是这种实现方式恶意客户端可向缓冲区写入格式错误的伪造数据使私钥运算产生一个不合法的签名例如实质上是一次解密从而违反 RSA 密钥的使用策略。文档还给出了链式调用的放大场景证明attestation应用替最终客户端签名密钥与待签数据由证明应用控制最终客户端不得获得任意签名但最终客户端与证明应用共享签名输出缓冲、证明应用又把它重新共享给密码服务时恶意最终客户端可通过篡改中间数据实现对任意数据的签名。2.3 写-写泄露Write-write disclosure同样针对共享的输出参数密码代码把必须保持机密的中间数据写入输出缓冲客户端读取了这些中间数据密码代码随后覆盖掉它们。两个示例临时暴露应用加密数据后允许其客户端存储密文但客户端不应能访问明文。为省内存应用把位于最终客户端内存中的缓冲作为输出传给密码服务若加密机制是先把输入拷入输出缓冲、再原地加密例如为了简化重叠处理或底层 API 只能原地工作明文就在加密进行期间暴露给了最终客户端。回溯backtrack一个配置provisioning应用为多个客户端提供数据加密服务使用单一共享密钥靠把客户端身份放入 AAD 来隔离客户端。若 AEAD 解密边算 tag 边增量地把明文写入输出缓冲常规 AEAD 解密的工作方式且 tag 错误时会擦除输出缓冲那么恶意客户端可以把受害者客户端的密文交给配置应用解密——由于 AAD 不匹配操作必然失败但恶意客户端已经白嫖到了受害者的明文。2.4 写-读反馈Write-read feedback当一个函数同时有位于共享内存的输入与输出参数且增量地边处理输入边产生输出时可能发生密码代码处理一部分输入写出对应的输出部分客户端读取早期输出据此计算输入的下一部分密码代码继续处理剩余输入。某些密码机制下这会破坏安全性质。文档引用了 CBC 加密的研究结论如果客户端能看到前一个密文块后再选择明文块内容就构造出了解密预言机——若密钥策略只允许加密这就是安全违规。文档此处保留了一个 TODO是否要把这种风险纳入考量因为虽然它扩展了一次性one-shot接口的可能行为但客户端本来就能用多步multipart接口合法地做同样的事。3. 通用对策拷贝与谨慎访问3.1 拷贝Copying拷贝是有效且概念简单的对策但常因额外内存与时间开销而不受欢迎。文档特别提醒虽然拷贝很容易写进程序但若编译器尤其是做全程序优化时不理解共享内存与非共享内存之间的拷贝具有语义意义可能把拷贝优化掉。作为参照文档指出 PSA Firmware Framework 1.0 曾直接禁止分区间共享内存1.1 版本因 RAM 占用考量才解除了这一限制。3.2 谨慎访问Careful accesses以下规则可以保证共享内存不会引入除 write-read feedback 之外的安全违规绝不在同一索引处两次读取同一输入绝不从输出缓冲读回数据绝不在同一索引处两次写入同一输出。这条规则在许多场景下可以放宽先写入与输入无关且不涉密的数据、再覆盖是允许的例如在处理输入前先把输出缓冲清零。文档直言这些规则非常难以强制执行并指出GlobalPlatform TEE 的可信应用运行在 Cortex-A TrustZone 安全侧的应用正是必须遵守这套规则的典型系统。4. 防护责任的归属dispatch 层还是 driver 层一次通过密码服务执行的密码运算涉及四个组件操作系统提供的 RPC 框架密码服务的代码PSA Crypto 分发层dispatch layer又称 core由 Mbed TLS 提供实现具体密码机制的驱动driver可能由 Mbed TLS内置驱动或第三方提供。PSA Crypto API 规范把防护责任放在 PSA Crypto API 的实现上即组件 (3) 或 (4)。规范原文关于参数稳定性要求在多线程或共享内存环境中实现必须谨慎访问不重叠的缓冲区参数以防止缓冲区内容在函数执行期间被修改或观察所带来的任何安全风险。文档给出一个关键的历史事实在 Mbed TLS 2.x 与 3.x 至 3.5.0含没有任何针对共享内存缓冲的防护责任事实上转移到了组件 (1) 或 (2)但这一转移从未被文档化。因此从 3.6 起防护回到了它本应在的位置——PSA Crypto API 实现内部并存在两个可选层级分发层与每个机制的实现无关或驱动层针对每个具体实现。4.1 在分发层做防护分发层无法控制驱动如何访问缓冲区因此该层唯一可行的防护就是确保驱动接触不到共享内存任何位于共享内存的缓冲都必须被拷入/拷出密码服务自有内存堆或栈中的缓冲。代价是 RAM 使用增加。文档特别说明对于已知肯定不在共享内存中的缓冲可以跳过拷贝。但缓冲区位置不受 Mbed TLS 控制因此跳过拷贝只能做成编译期或运行期选项由使用 Mbed TLS 的应用来设置——这既是额外维护成本更多代码要分析、更多测试负担也是配置者设错时的残余安全风险。结论是除非有充分理由Mbed TLS 不会提供这种可配置性这与后文MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS的定位相呼应它是一个明确的我保证独占声明而非默认行为。4.2 在驱动层做防护把防护责任放到驱动层会增大总工作量——驱动实现的数量远多于分发层实现即便在 Mbed TLS 内部几乎每个 API 函数都有多种底层实现对应各算法并增大生态风险某些第三方驱动可能保护不当。因此只有当驱动层防护有明确收益时才是好选择预期收益是有些场景下存在拷贝之外的实用防护手段。一些密码机制天然地以单遍single pass方式处理输入、几乎不可能重复读取同一字节并直接把最终结果写入输出缓冲。对这类机制要求驱动遵守谨慎访问规则是合理的。5. 各密码机制的脆弱性分析与设计决策5.1 涉及小缓冲区small buffers的操作对小缓冲区操作拷贝成本很低而不拷贝的风险通常很高任何格式化数据的解析都有高概率的读-读不一致风险内部评审表明RSA 运算的实现天然容易带出写-读不一致或写-写泄露。这里的小缓冲区有严格定义大小上限在编译期已知、且小到拷贝不昂贵的缓冲。例如 RSA 密钥是小缓冲区哈希输入不是——即使某次调用恰好只有几个字节。文档把以下缓冲归类为小缓冲区一切与非对称加密直接相关的输入/输出签名、加密/解密、密钥协商、PAKE含密钥导入/导出注意这不包括未经非对称原语处理的输入输出例如传给psa_sign_message/psa_verify_message的消息cooked key derivation派生出结构化密钥的输出哈希或 MAC 运算的输出。设计决策分发层必须拷贝所有小缓冲区。5.2 小输出的对称密码输入传给哈希、MAC、密钥派生的消息输入是未格式化数据且对规范中的所有算法逐字节处理输入是自然的实现方式因此读-读不一致风险低。设计决策要求对称密码驱动读取输入时不存在读-读不一致风险。文档在此留有一个 TODOIV/nonce 输入怎么办它们通常较小但不一定有静态大小上限例如 GCM 推荐 12 字节 nonce但也允许更长的 nonce。5.3 密钥派生输出密钥派生通常以流的形式产生输出setup 之后除操作性故障如与加速器通信失败或数据发完数据量可预先精确检查之外没有错误条件。注意这说的是原始字节输出cooked key derivation 属于小缓冲区范畴。设计决策要求密钥派生驱动在产生输出时不从输出缓冲读回。5.4 分组密码与 AEADAEAD 解密在tag 不匹配时面临写-写泄露风险明文可能已被写入输出缓冲。AEAD 加/解密在多次遍历输入时面临读-读不一致风险而这在不少情况下是自然的encrypt-and-authenticate 或 authenticate-then-encrypt 结构加密时一次读算 tag、一次读加密encrypt-then-authenticate 结构解密时一次读解密、一次读算 tagSIV 模式PSA API 尚未支持但未来很可能加入一遍完整计算 IV再来一遍核心认证加密。另外若分组密码/AEAD 实现为用memmove把输入拷进输出缓冲后原地处理其输出就同时面临写-读不一致与写-写泄露。文档解释这种实现的动机memmove能正确、可移植地处理缓冲区重叠而 C99 没有高效的可移植方法检查两个缓冲区是否重叠因此这是完全支持 overlapping 的最省事办法。设计决策分发层必须为分组密码与 AEAD 的明文/密文输入输出分配中间缓冲区。补充说明若驱动支持原地操作输入和输出可以共用一个中间缓冲驱动理应支持任意重叠尽管 Mbed TLS 中并非总是如此——文档将其标注为已知问题 #3266。做中间拷贝还有一个附带好处重叠缓冲区天然被支持了。对当前已实现的所有 AEAD 模式AAD关联数据只被处理一遍用于计算 tag 的中间值。设计决策暂要求 AEAD 驱动读取附加数据时不存在读-读不一致风险。并留了备忘一旦开始支持 SIV 模式分发层应对已知非低风险的算法改为拷贝输入。5.5 消息签名对 hash-and-sign 框架的签名算法sign/verify-message 的输入先经过哈希因此可适用对称密码输入的规则。PSA_ALG_RSA_PKCS1V15_SIGN_RAW是 Mbed TLS 3.5 中唯一非 hash-and-sign 的签名机制同样只处理输入一遍。但尚未实现的 PureEdDSA 不同——PureEdDSA 签名会两遍处理消息尽管验证只处理一遍。设计决策暂要求 sign/verify-message 驱动读取输入时不存在读-读不一致风险。同样留有备忘实现 PureEdDSA 后分发层须对 PureEdDSA 这类已知非低风险的算法拷贝输入。6. 共享内存保护策略总表文档将上述分析汇总为如下策略分发层core必须拷贝以下缓冲使驱动收到的参数绝不位于共享内存非对称加密签名、加密/解密、密钥协商、PAKE的一切输入输出含密钥导入/导出分组密码与 AEAD 的明文/密文输入输出哈希或 MAC 运算的输出cooked key derivation 的输出。另有一份文档应当说明驱动对需要受保护访问的参数的要求涉及哈希与 MAC 的输入分组密码/AEAD 的 IV/nonce待确认AEAD 的关联数据待确认密钥派生的输入密钥协商除外原始密钥派生的输出cooked 输出除外。内置实现对需要受保护访问的参数必须真正加以保护。文档还给出了分模块的实现策略表Design by module模块输入防护策略输出防护策略备注Hash 与 MAC谨慎访问谨慎访问输入输出都是原始未格式化数据多次访问风险低Cipher拷贝拷贝AEAD拷贝附加数据用谨慎访问拷贝Key derivation谨慎访问谨慎访问Asymmetric signature谨慎访问拷贝签名输入会传入哈希一旦实现 PureEdDSA 此结论不再成立Asymmetric encryption拷贝拷贝Key agreement拷贝拷贝PAKE拷贝拷贝Key import / export拷贝拷贝密钥可能以 DER 等结构化格式导入导出易受读-读不一致乃至写-读不一致影响7. Mbed TLS 的具体实现本仓库源码佐证文档声称在 Mbed TLS 2.x 与 3.x ≤ 3.5.0 中不存在上述防护而本仓库内嵌的这份 Mbed TLS 中拷贝机制已经完整落地。以下源码证据均可在仓库中直接查证。7.1 统一拷贝函数与测试钩子按设计决策创建一组统一函数来拷贝输入/输出数据Mbed TLS 实现了 psa_crypto_copy_input / psa_crypto_copy_output。统一函数的好处在文档中列得很清楚测试钩子只需加一处拷贝正确性只需在一处审查将来若允许绕过拷贝只需在一处把函数替换为空操作防止编译器优化掉拷贝的复杂手段也无需重复实现。实现细节psa_crypto.c#L9055-L9142psa_crypto_copy_input()先检查input_len input_copy_len时返回PSA_ERROR_CORRUPTION_DETECTED然后执行memcpypsa_crypto_copy_output()检查output_len output_copy_len时返回PSA_ERROR_BUFFER_TOO_SMALL再memcpy写回两者都在拷贝前后调用成对的测试钩子psa_input_pre_copy_hook/psa_input_post_copy_hook/psa_output_pre_copy_hook/psa_output_post_copy_hook仅在MBEDTLS_TEST_HOOKS下编译见 psa_crypto.c#L9055-L9064——这正是第 8 节内存投毒测试的挂载点。7.2 本地缓冲区跟踪结构为了在堆上跟踪分配出来的拷贝文档设计了两个结构体并在 psa_crypto_core.h#L933-L1001 中原样落地typedef struct psa_crypto_local_input_s { uint8_t *buffer; size_t length; } psa_crypto_local_input_t; typedef struct psa_crypto_local_output_s { uint8_t *original; uint8_t *buffer; size_t length; } psa_crypto_local_output_t;配套的四对管理函数psa_crypto.c#L9144-L9231行为与文档一致psa_crypto_local_input_alloc()用mbedtls_calloc()分配input_len字节并拷贝内容长度与指针存入结构体psa_crypto_local_input_free()释放并置空psa_crypto_local_output_alloc()分配output_len字节但不拷贝数据此时只是预留干净的中间缓冲同时把原缓冲指针存入local_output-originalpsa_crypto_local_output_free()先把中间内容拷回original再释放中间缓冲零长度输入直接返回成功避免无谓分配psa_crypto_local_output_free()遇到有中间缓冲但 original 为 NULL的非法状态时返回PSA_ERROR_CORRUPTION_DETECTED。文档同时指出部分 PSA 函数可以不使用这些便利函数因为它们有本地优化例如分组密码可以为输入和输出共用同一个中间缓冲。7.3 LOCAL_* 宏与MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS为了让 PSA 函数基本不改代码就能加上拷贝文档设计了六个宏实际实现见 psa_crypto.c#L172-L281输入侧LOCAL_INPUT_DECLARE(input, input_copy_name)声明并初始化一个psa_crypto_local_input_t及拷贝指针LOCAL_INPUT_ALLOC(input, length, input_copy)分配拷贝失败则置错误码并goto exit成功则令input_copy指向拷贝LOCAL_INPUT_FREE(input, input_copy)释放拷贝并置 NULL输出侧LOCAL_OUTPUT_DECLARE/LOCAL_OUTPUT_ALLOC/LOCAL_OUTPUT_FREE与之对偶且当psa_crypto_local_output_t处于非法状态拷贝指针有效但 original 为 NULL时设置错误状态。文档用了一个假设函数psa_foo演示改造方式把原参数改名为input_external/output_external原变量名留给本地拷贝于是函数体几乎不用动psa_status_t psa_foo(const uint8_t *input_external, size_t input_length, uint8_t *output_external, size_t output_size, size_t *output_length) { psa_status_t status; LOCAL_INPUT_DECLARE(input_external, input); LOCAL_OUTPUT_DECLARE(output_external, output); LOCAL_INPUT_ALLOC(input_external, input); LOCAL_OUTPUT_ALLOC(output_external, output); /* Do some operation on input and output */ exit: LOCAL_INPUT_FREE(input_external, input); LOCAL_OUTPUT_FREE(output_external, output); }宏的第二个关键收益是可关闭性这六个宏在配置项MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS下条件编译。该选项定义在 mbedtls_config.h#L1489-L1506语义是PSA 函数被假定独占访问其输入输出缓冲。从源码看当该选项未定义时宏展开为真实的psa_crypto_local_input_alloc()/psa_crypto_local_output_free()调用当选项已定义时psa_crypto.c#L268-L281宏退化为空操作——LOCAL_INPUT_ALLOC只是把拷贝指针直接指向原缓冲input_copy input;LOCAL_OUTPUT_FREE仅置空指针从而在不受共享内存问题影响的构建中省掉拷贝开销与 RAM 占用。这个选项还会体现在query_config、version_features等工具链输出中version_features.c#L435-L437可通过 scripts/config.py 设置。编译器会不会把拷贝优化掉文档将其列为开放问题并给出了明确的评估方法用支持的主要编译器gcc、clang 等构建一个调用带拷贝 PSA 函数的示例程序开启链接时优化/全程序优化如 gcc 的-flto并尝试-Ofast、clang 的-Oz再用objdump等工具检查生成代码中拷贝是否保留。若被优化掉则实验volatile关键字阻止访问被消除仍不够时可用内存屏障或空asm块等平台相关手段并可参照恒定时间constant-time模块的做法——为重点平台实现专用版本同时保留一份在多数平台上大概率正确的 C 回退实现。7.4 多步multipartAPI 的拷贝策略文档比较了两种输入拷贝方式每次update()调用时分配缓冲并拷贝输入简单与一次性 API 的镜像对应但多步运算中途分配内存可能伤性能——多步 API 本来就是为来不及一次算完的系统设计的。运算开始时一次性分配缓冲并把update()切分按预期update()平均调用大小在运算开始时就分配好输入输出缓冲update()传入更大缓冲时PSA API 层把输入切成临时缓冲大小的块、多次调用驱动、直到填满输出。更复杂且有一个未解难题中间某次驱动update()返回错误时驱动状态无法回滚。好处是降低某些场景的内存占用、避免运算中动态分配且固定大小的缓冲甚至可以静态分配。设计决策初始采用方案 (1)把方案 (2) 作为必要时再做的优化。8. 拷贝正确性的验证内存投毒8.1 三种候选方案对比文档比较了三类验证手段人工审查最简单但可靠性低于自动化验证内存池memory pools测试代码与库代码使用不同内存池测试驱动检查需要拷贝的参数落在库池而非测试池内——需要复杂的链接方案内存投毒memory poisoning在测试代码中投毒必须被拷贝的输入/输出内存区域投毒指使内存不可访问可通过mmapmprotect关权限或 MSan/Valgrind 一类的分析器机制实现库中执行拷贝的代码通过测试钩子临时解毒。文档给出的参考实现模式对应仓库中真实存在的钩子见 psa_crypto.c#L9086-L9139static void copy_to_user(void *copy_buffer, void *const input_buffer, size_t length) { #if defined(MBEDTLS_TEST_HOOKS) if (memory_poison_hook ! NULL) { memory_poison_hook(copy_buffer, length); } #endif memcpy(copy_buffer, input_buffer, length); #if defined(MBEDTLS_TEST_HOOKS) if (memory_unpoison_hook ! NULL) { memory_unpoison_hook(copy_buffer, length); } #endif }为什么要在调用库之前投毒而不是在拷贝入之后投毒这样忘记拷贝或拷错了东西都会直接让测试失败。如果依赖库的拷贝函数来做投毒就只能验证在拷贝确实按预期发生的前提下驱动没有越界访问——覆盖面弱了一档。8.2 投毒机制选型文档列举了五种实现路径并逐一评估Valgrind memcheckVALGRIND_MAKE_MEM_NO_ACCESS宏可做手动投毒Mbed TLS 的 constant-flow 测试已在用MSan标记未初始化内存同样用于 constant-flow 测试但只适合输入缓冲——能检测对投毒缓冲的读检测不到写ASanASAN_POISON_MEMORY_REGION标记区域不可访问独立页 mprotect()缺点是要手工保证缓冲独占页往往意味着还得再拷贝一次实现复杂随机数据填充 保留副本输入缓冲保留原始副本、函数返回后拷回输出缓冲填随机数并留副本钩子里比对。简单、无需工具、性能尚可但要为钩子维护额外副本且没有精确检验目标性质——依赖给定随机输入时测试恰好失败这一运气遇到专门校验错误码的测试用例时更不可靠。文档判定其不适用。结论(2) 不够用(4) 比另两种复杂(1) 和 (3) 最顺手——只需对自分配缓冲调用一个特殊宏其余交给 sanitizer。ASan 有一个对齐限制引自官方文档由于 ASan 对齐限制可能只投毒了 [addr, addrsize) 的子区域实现上会把缓冲大小向下取整到 8 字节可以通过在投毒函数中手工把长度向上取整到 8 的倍数来规避Valgrind 未见此限制但测试整体慢得多。设计决策同时用 Valgrind memcheck 与 ASan 手动投毒实现内存投毒测试让使用者可按需在性能与精度之间选择——两者投毒接口非常相似实现成本很低。8.3 透明集成进现有测试文档在新写测试 vs 集成进现有测试之间选择了后者设计决策把内存投毒透明地加进现有测试理由是现有测试覆盖率更高——且由于拷贝发生在分发层测试本就不依赖传给驱动的具体参数值。实现方式文档中的示例为每个 PSA 函数写一个先投毒、再调用、最后解毒的包装函数然后用宏替换函数名psa_status_t mem_poison_psa_aead_update(psa_aead_operation_t *operation, const uint8_t *input, size_t input_length, uint8_t *output, size_t output_size, size_t *output_length) { mbedtls_test_memory_poison(input, input_length); mbedtls_test_memory_poison(output, output_size); psa_status_t status psa_aead_update(operation, input, input_length, output, output_size, output_length); mbedtls_test_memory_unpoison(input, input_length); mbedtls_test_memory_unpoison(output, output_size); return status; } #define psa_aead_update(...) mem_poison_psa_aead_update(__VA_ARGS__)文档指出Mbed TLS 已有一套更通用的机制来做这种变换——PSA test wrappers围绕所有 PSA 函数的包装器允许在每次 PSA 调用的首尾插入测试代码位于tests/include/test/psa_test_wrappers.h与tests/src/psa_test_wrappers.c由脚本生成并非构建时自动生成改动后需手动运行生成脚本更新并提交。投毒代码就加在这些包装器中对相应参数做调用前投毒、调用后解毒。在本仓库中可以查证到tests/src/psa_test_wrappers.c包装器实现大量以#if !defined(MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS)条件编译——一旦声明独占缓冲投毒包装自动关闭语义自洽tests/src/psa_memory_poisoning_wrappers.c 与 tests/include/test/psa_memory_poisoning_wrappers.h内存投毒专用的包装器层tests/src/test_memory.c实现mbedtls_test_memory_poison()/mbedtls_test_memory_unpoison()分别等价于VALGRIND_MAKE_MEM_NOACCESS/ASAN_POISON_MEMORY_REGION等调用仅在MBEDTLS_TEST_MEMORY_CAN_POISON启用时生效并维护一个_Thread_local的投毒计数mbedtls_test_memory_poisoning_counttests/suites/test_suite_psa_crypto_memory.functionPSA 内存相关测试套件。配置说明投毒测试依赖 sanitizer 专有接口因此只在用 ASan 或 Valgrind 构建时启用。文档的方案是编译期自动探测 ASan 并设置MBEDTLS_TEST_MEMORY_CAN_POISON从而让 ASan 下的透明测试无需任何额外配置项Valgrind 的自动探测与投毒支持在文档中被明确标注为留给未来工作。8.4 验证验证本身为确保投毒测试真能抓出问题文档要求写一个故意作恶的测试函数来验证验证机制该函数在调用输入拷贝函数创建本地副本之后仍去读原始输入缓冲在调用输出拷贝函数做回写前后都写原始输出缓冲。然后用内存投毒测试它并确认测试如期失败。由于预期失败这个验证要独立于常规投毒测试运行。文档说明该逻辑由programs/test/metatest.c实现一个专门检查测试失败是否正确发生的程序可经tests/scripts/run-metatests.sh脚本运行。9. 谨慎访问的验证针对不被拷贝的参数对输入输出不被拷贝的 PSA 函数哈希、MAC、AEAD 的 AAD、密钥派生、非对称签名输入必须验证内置驱动确实每个内存位置只访问一次。文档当前聚焦读-读不一致因为不拷贝的参数大部分是输入。候选方法9.1 人工审查文档将其列为最不理想但兜底的手段枯燥、易错、工程时间随 PSA 函数数量线性增长且无法保证第三方驱动质量——自动化测试原则上可以移植到任何驱动实现。9.2 基于mprotect()的测试Linux核心思路用mmap分配参数内存、mprotect拒绝对其访问任何访问触发 SIGSEGV再捕获异常并放行一次mprotect ptrace父进程用ptrace响应子进程的 SIGSEGV——先设法让子进程执行mprotect重开权限文档标注了难点ptrace 能改子进程寄存器/内存、能改正在发起的系统调用参数但不能凭空让子进程发起它本不打算发起的系统调用再用PTRACE_SINGLESTEP重执行失败的那条 load/store 指令随后mprotect关闭权限、PTRACE_CONT继续。记录被访问的地址同一地址被读两次即判测试失败调试器 mprotect用 GDB 的 signal catchpoint 处理 SIGSEGV流程相同放行、单步、关权限、继续地址记录部分用 gdb 语言实现较困难可改为记录日志后离线分析或直接用 Python 驱动 gdb。9.3 动态插桩ValgrindValgrind 没有现成工具检查每个地址至多访问一次但可用 lackey 生成完整内存访问轨迹valgrind --toollackey --trace-memyes --log-filelogfile ./myprogram若测试程序(1) 建好 PSA 调用的输入输出缓冲(2) 用print()泄露每个缓冲的起止地址(3) 恰好写一次输入缓冲(4) 调用 PSA 函数(5) 恰好读一次输出缓冲——那么解析程序输出与 Valgrind 日志即可检查每个位置恰好被访问两次程序设置一次 PSA 函数一次。9.4 固定虚拟平台FVP测试可在 Corstone 310 生态 FVP 上运行测试来测量重复访问文档指出有现成的 mbedtls CMSIS-RTX 示例工程可作起点。FVP 上支持两条路线用 Iris 脚本化调试器设置内存观察点用 Tarmac Trace 的处理器内存访问 trace 源输出 FVP 上所有内存访问解析后校验输入输出缓冲只被访问一次。缓冲地址可以由程序打印泄露也可以在 FVP 链接脚本中固定。9.5 评估标准与验证验证文档要求对每种测试策略先花 1–2 天做原型再按六个标准评估实现难度、可扩展性灵活性、性能对 CI 的拖慢程度、可复现性外部用户是否容易跑起来、可理解性、可移植性能否覆盖各目标平台避免只在特定目标上才暴露的重复访问 bug。最终选定方案后需要覆盖的接口是Hash、MAC、AEAD仅 AAD、Key derivation、Asymmetric signature仅输入。关于测试组织文档明确用现有测试套件跑谨慎访问测试不可行多数方法需要泄露缓冲边界、可预测的访问模式或特殊缓冲FVP 甚至要求非 host 目标应新写测试直接驱动驱动层——好在需要测的接口只有上述五个新测试数量有限。同时要求验证验证写一个在同一位置多次读输入、多次写输出的作恶函数为其写谨慎访问测试并确认它失败。10. 实践要点与仓库源码导读关键设计决策一览均来自原文档且多数已在本仓库源码中落地分发层拷贝所有小缓冲区非对称相关、哈希/MAC 输出、cooked KDF 输出对称密码/签名/派生驱动须保证输入单遍读取AEAD 驱动须保证 AAD 单遍读取SIV、PureEdDSA 落地时重审分发层为 cipher/AEAD 明文密文分配中间缓冲顺带支持重叠统一拷贝函数 测试钩子配合MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS一键关闭拷贝multipart API 先用每次 update 拷贝方案用 Valgrind memcheck ASan 双通道的内存投毒透明集成进现有测试谨慎访问的验证方法待原型评估后定案。本仓库中可继续深入的入口设计文档本体lib/mbedtls/docs/architecture/psa-shared-memory.md拷贝与宏实现lib/mbedtls/library/psa_crypto.c宏定义在 L172-L281拷贝函数与测试钩子在 L9055-L9231本地缓冲结构与管理函数声明lib/mbedtls/library/psa_crypto_core.h配置项定义lib/mbedtls/include/mbedtls/mbedtls_config.h投毒测试设施lib/mbedtls/tests/src/test_memory.c、lib/mbedtls/tests/src/psa_test_wrappers.c、lib/mbedtls/tests/src/psa_memory_poisoning_wrappers.c测试套件lib/mbedtls/tests/suites/test_suite_psa_crypto_memory.function测试包装器生成脚本本仓库中的实际位置lib/mbedtls/framework/scripts/mbedtls_framework/code_wrapper/psa_wrapper.py——其中同样以MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS条件包裹投毒逻辑CI 组件化构建lib/mbedtls/tests/scripts/components-configuration-crypto.sh 中包含full config MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS、cmake、gcc、ASan的构建与测试组件说明该配置项是 Mbed TLS 官方 CI 常测的组合之一。适用前提与限制本文所有结论以本仓库内嵌的这份 Mbed TLS 源码及其架构文档为准。文档自身保留了若干 TODOwrite-read feedback 是否纳入风险、IV/nonce 与 AAD 的保护要求待确认、内置驱动满足谨慎访问要求的代码级分析待补、共享内存保护要求文档待另行编写说明该体系仍在演进若在构建中启用MBEDTLS_PSA_ASSUME_EXCLUSIVE_BUFFERS即明确声明PSA 函数独占访问其缓冲所有拷贝与透明投毒验证都会随之关闭这一选择应由清楚自己系统内存模型的一方做出。【免费下载链接】flipperzero-firmwareFlipper Zero firmware source code项目地址: https://gitcode.com/GitHub_Trending/fl/flipperzero-firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考