
指纹方案移植做过多轮的工程师都有体会REE侧驱动、HAL哪怕跑通了心里还是不踏实因为真正握着指纹数据和算法的是安全世界里的TA。我这次拿到一台基于骁龙平台的新主板要把整套指纹识别方案从参考平台挪过来前面驱动、HAL、指纹服务改得七七八八剩下最硬核的一步就是把QSEE侧的木马不对是QSEE里的TA给移植过去。这篇文章就围绕这最后一步展开TA在指纹方案里到底干嘛的、移植前要准备哪些东西、怎么搭编译工程、动手改哪些文件、最后镜像怎么签名落地以及我在联调时踩过的一堆雷。适合正在做高通平台指纹识别方案、准备碰QSEE的软工和BSP工程师参考。1. 移植前必须想清楚的四件事1.1 TA在整个指纹方案里扮演什么角色先把架构讲明白。在高通的TrustZone生态里普通世界REE和高安全世界QSEE是靠ARM TrustZone硬件隔离的。REE这边有指纹内核驱动、指纹HAL、fingerprintd它们负责和传感器打交道比如初始化SPI、控制中断、读取原始图像数据但这些只是“搬运工”。真正干活的是运行在QSEE里的TATrusted Application。一颗指纹模组从按下手指到最终解锁流程大致是这样的REE驱动收到中断后通知HAL去采集指纹图像HAL把图像buffer映射到共享内存然后通过qseecom设备节点发一个命令给TATA在安全世界里拿到图像数据执行特征提取再和本地模板做比对返回结果。整个过程里指纹图像、特征点、模板明文的读写都在安全世界里完成REE侧拿到的只是“匹配成功/失败”这类结果。模板数据通常也由TA负责加密写入安全存储RPMB或SFS算法密钥不会暴露给Android系统。所以简单总结TA是这套指纹方案的安全核心也是整个方案能不能过安全评审的关键。之前我有一次图省事把模板直接存到REE侧/data分区结果安全测试一针见血直接标了高风险。后来老老实实把模板迁移做进TA才把这个问题关掉。换句话说如果你只是想把指纹功能跑起来可能REE侧做点恶就能凑合但要做成一个能商用的方案TA这一环是绕不过去的。1.2 移植前的技术材料清单除了代码移植前要准备的东西其实比想象中多。我列一下我自己常用的清单每一项都踩过坑指纹传感器型号、规格书datasheetSPI工作模式、中断触发电平、复位时序、供电要求。这块决定你在REE侧怎么配置虽然TA不直接管GPIO但TA拿到的图像质量依赖REE侧采集链路是否正确。模组厂商提供的算法库版本。同一个模组算法版本不同TA内部行为差异可能很大最好拿到和原平台完全相同的版本再动手。目标平台的TZ软件包。老平台叫tzbsp新平台一般在vendor BSP里里面包含QSEE的运行环境和已有的系统TA。参考平台可用的TA最好同时有源码和二进制。优先用源码没有源码也要保留二进制并记清楚它是哪个平台编出来的。目标板原理图和设备树源文件。SPI挂在哪组控制器中断脚、复位脚各连到哪个GPIO电源域有哪些这些要看清楚。一份能正常工作平台的完整启动log和运行时log用来对照。我习惯在动手前先把这些资料归档到一个目录避免移植过程中到处找。指纹方案移植最怕的不是代码复杂而是资料零散改到一半发现忘了某根GPIO的实际接法只能干等。1.3 确认QSEE版本和TA规范是否匹配这一条很多新手会忽略但往往是第一只拦路虎。高通的TrustZone环境不是一成不变的从MSM8953时代的QSEE 1.0/2.0到后来的QSEE 4.0再到新平台常见的“通用TEE”和带有aarch64 TA支持的环境TA的二进制格式、ABI、系统调用接口都有差异。最常见的问题就是参考平台是32位TA目标平台是64位TA环境两边ABI不对签名检测过了也照样加载失败。还有qseecom的ioctl命令号不同平台有变化REE侧HAL调用的通信协议如果和TA预期不一致也会表现成“命令有去无回”或“返回乱码”。我的做法是在正式移植前先看一眼目标平台已有的系统TA是怎么编的。比如keymaster、widevine这些TA的格式确认它们是32位还是64位加载路径在哪。再用相同工具链尝试编译一个最小TA加载成功后再把指纹TA放进来这样能把“环境问题”和“业务逻辑问题”分开。1.4 选对移植路线源码移植还是二进制移植拿到厂商的TA后首先面临路线选择。有三种常见情况第一种拿到完整源码。这是最舒服的可以自己改命令号、加log、调heap大小也能适配不同sensor型号。缺点是编译环境搭建需要点功夫而且如果算法核心也在源码里牵扯到授权问题。但总体推荐这个路线排查问题灵活。第二种只有二进制。那问题也不大但有几个硬性约束UUID不能改、TA实现的命令列表必须固定、REE侧驱动和HAL得严格配套。二进制TA在目标平台上的加载路径、依赖的SFS文件位置也必须保持兼容否则跨平台后数据读不出来。第三种混合模式。很多指纹模组厂商会把算法封装成一个算法TA提供给你的同时再给你一个适配TA的源码框架由适配TA去调用算法TA。这个模式下你负责把适配TA改到目标平台算法TA按固定接口加载两边通过内部session通信。如果条件允许我强烈建议在动手前先验证一条“可行性红线”把原平台的TA或最小TA在目标平台上成功加载起来。这一步过了后面的移植就是拼肉这一步不过先搞清楚环境差异再往下走。2. 搭好工程再动手QSEE TA的编译环境与源码结构2.1 源码目录与工程结构快速认识高通QSEE TA的源码在BSP里一般有固定的摆放位置。老平台的tzbsp包里路径通常长这样trustzone_images/core/securemsm/trustzone/qsapps/下面每一个子目录就是一个TA比如keymaster、sfs、widevine。指纹TA一般会新建一个自己的目录里面再分src、include、tools等。新平台因为Android构建系统的演进很多厂商把securemsm相关代码挪到了vendor/qcom/proprietary/securemsm下面或者以单独的安全镜像仓库方式提供。不管放哪结构上都差不多一个TA目录里至少要包含源码文件、头文件、一个编译描述文件Android.mk或者securemsm.mk以及一份标志TA的配置文件里面记录UUID、镜像名、构建类型这些信息。我第一次做指纹TA移植时犯过一个低级错误只把源码复制到了新平台的目录下忘了在镜像列表配置里声明这个TA。结果编译出来的系统固件里根本没有指纹TA镜像加载时一直报TA找不到排查大半天。后来才明白在QSEE这套体系里不是你放了源码它就会自动编进去需要显式地把TA注册到构建系统里。2.2 工具链、编译脚本与关键环境变量QSEE TA的编译不像普通Android App那样敲个gradle就能完事。它依赖BSP自带的交叉工具链以及一套指定的链接脚本。老平台常用arm-none-eabi或qcc工具链新平台则倾向于用clang配合QSEE的链接脚本和库。编译之前要确认几个环境变量TARGET_BOARD_PLATFORM对应具体芯片平台名CHIPSET用来区分同一平台的不同芯片版本还有一个指向工具链路径的变量。这些变量设置错了编译会直接报错或者编出来的镜像格式不对加载时被TZ拒绝。这里我建议直接在厂家BSP的编译环境里操作不要自己手工造轮子。我见过有同事为了图快把老平台的编译链硬拷到新平台结果源码头文件的ABI结构对不上编出来的TA加载必挂。最稳妥的办法是先试着编译一个系统自带的TA比如sfs或keymaster确认整个工具链在目标环境里能正常出镜像再动指纹TA的代码。2.3 TA入口函数与REE侧接口协议QSEE里的TA有四个入口函数是必须实现的分别是TA_CreateEntryTA、TA_CreateSessionTA、TA_InvokeCommandTA、TA_DestroySessionTA。注册TA时要调用qsee_ta_register接口把UUID、heap、数据段大小、命令处理函数表传进去。整套体系把TA生命周期管理得比较规范REE侧通过qseecom驱动发起session然后才能传命令。在移植时需要重点确认三样东西UUID是否和REE侧HAL里定义的一致不一致直接找不到TA。命令处理函数表是否完整比如指纹方案常见的enroll、authenticate、remove这些命令都在表里注册过。共享内存缓冲区的格式。TA从REE侧拿到的数据是通过共享内存映射过来的如果两边结构体定义不一致轻则特征提取不出来重则TA直接panic重启。接口协议这块我一般会在移植前把REE侧HAL的发送命令代码和TA侧的命令解析代码放在一起比对一遍。重点看命令号、参数结构体、buffer长度字段对照一遍基本上就能避免八成以上的通信问题。3. 动手移植从新建TA工程到镜像落地的全过程3.1 新建指纹TA工程并配置UUID、Heap和Stack一切准备妥当后开始正式移植。第一步是在目标平台的TA目录下新建指纹TA的工程目录把厂商提供的源码复制进来。在构建描述文件里把TA的UUID写上。UUID不是随便编的要跟REE侧HAL保持一致一般由模组厂商或方案商约定。个人改动UUID属于大忌除非你是整套方案重新设计否则别乱动。然后配置heap和stack大小。很多人忽略这两个参数等到TA在运行到特征提取时报内存不足才想起来。指纹算法在某些型号上跑起来很吃内存尤其是模板训练阶段参考值一般要64KB以上。我自己习惯先按原平台的配置给再把大图反复跑几轮压测如果稳定就维持原样。heap设太大会拖慢TA加载速度设太小直接崩这个平衡要在实测里找。还有一个容易被忽视的配置是TA是否启用线程支持、是否允许安全世界内部调用某些系统服务。指纹TA一般需要访问安全存储SFS/RPMB在配置里要把这些依赖打开。3.2 对接指纹Sensor的硬件访问逻辑这一步让很多人产生误解以为TA移植就是把代码编一下大不了改改log实际上硬件适配才是大头。老一代方案里QSEE TA可以直接访问部分外设比如SPI控制器、共享GPIO。这种情况下指纹图像可以由TA直接通过安全世界的SPI控制器获取REE侧驱动只是帮忙做电源管理和中断通知。但这种方案配置起来很麻烦要在TZ侧设置对应的XPU权限和外设映射稍有不对整个TZ都可能起不来。现在大多数主流方案尤其是新平台倾向于“REE采集、TA处理”分工REE侧负责SPI读写、采集原始图像、控制中断和复位然后通过共享内存把图像数据传进TA。TA只负责算法处理和安全存储。好处是硬件适配集中在REE侧TA移植工作量小一些。你的目标平台到底属于哪种模式直接决定你要改哪些东西。如果走“REE采集”模式那TA侧其实只需要保证共享内存协议没问题如果走“TA直读外设”模式那就要在TZ配置里新增SPI控制器的访问权限、IO引脚映射复杂程度几何级上升。3.3 指纹模板数据与安全存储的迁移指纹功能上线前最容易被忽视但也最容易出事的是模板数据。用户已经录入的指纹模板在旧平台里存的是旧TA加密过的数据直接搬过去给新TA解密基本解不出来。如果平台算法没变只是换了颗sensorTA里模板格式可能兼容如果算法版本变了或者安全存储密钥发生了变化那历史模板继续使用就是奢望。这种情况下产品上一般要做两个方案一是量产下线阶段强制用户重新录入指纹。虽然体验不佳但最干净不会出现“指纹明明还在但怎么解不了锁”的诡异问题。二是提供一个“模板迁移接口”由新TA识别旧模板版本后重新封装。这个方案听着高级实际做起来要考虑版本回退、安全密钥变化、RPMB数据越界等一堆问题除非老平台用户升级需求非常强烈否则我一般不建议接这个活。从安全角度讲模板迁移还涉及一个原则性问题新平台必须要能确认旧模板确实来自可信来源不能随便接受一个外部数据就当模板。所以很多安全团队会直接要求禁止迁移重新录入才能过审。3.4 编译、签名与镜像放置路径TA代码改完接下来就看编译和签名了。QSEE TA的编译产物不是一个简单的二进制而是一个被签名过的镜像集合常见格式是先产生一个ELF再通过工具拆分成.mdt和.b00、b01文件序列。Android系统开机时由引导加载阶段把这一组镜像加载进安全世界。签名这一步是整个流程里最容易踩坑的地方。开发阶段可以用测试key或permissive签名但产线固件必须用对应平台的生产私钥来签而且还受防回滚机制约束。签名不对TA在加载阶段直接被拒log里会明确报出安全校验失败。镜像签名好之后放置路径也有讲究。常见的位置是/vendor/firmware/或者老平台放在/system/etc/firmware/。放错地方等于没放因为引导加载器和TZ在固定路径找TA。我在实测中经常用的一个检查手段是固件刷好后先在目标板shell里ls一下/vendor/firmware确认指纹TA的.mdt和.bXX文件都在。然后在kernel log里搜TA加载相关的关键词比如“fingerprint_ta”、“TZ”这些看有没有加载成功的信息。这一步能过滤掉一大半的“镜像根本没进去”的低级问题。3.5 REE侧配套改动清单TA移植从来不是孤立事件REE侧如果不动TA跑起来也没用。我每次都会把REE侧的配套改动一起列出避免联调时来回折腾。内核侧确认/dev/qseecom节点正常创建qseecom驱动加载成功确认指纹SPI设备树配置正确中断GPIO可用指纹驱动注册成功并能为HAL提供设备节点。HAL侧确认libhardware里的fingerprint.default库编译版本与TA配套HAL里的TA命令号、UUID、buffer格式要和TA侧完全一致HAL初始化时是否能正常打开qseecom会话。安全策略侧检查SELinux的avc denied日志。新平台对qseecom节点访问管控很严供应商指纹HAL进程如果没加sepolicy规则也会发生“驱动正常但ioctl被拒”的现象。这些改动看起来和TA本身没什么关系但任何一个环节不对最终现象都会表现为“TA不工作”容易把排查方向带偏。4. 联调阶段的坑与排查思路4.1 高频错误码速查表联调阶段遇到的错误大部分都会以log错误码形式出现。我整理了一份高频错误码对照表遇到直接查错误码/现象含义常见原因TEE_ERR_TA_NOT_FOUND找不到TA镜像没编译进去或放置路径不对TEE_ERR_TA_BAD_FORMATTA格式错误编译工具链与目标环境不匹配TEE_ERR_SECURITY安全校验失败签名key不对或防回滚冲突TEE_ERR_MEMORY内存不足TA的heap/stack配置太小ioctl返回-1 ENOTTY通信通道异常qseecom版本不匹配或驱动没注册avc deniedSELinux拒绝访问指纹HAL进程缺sepolicy规则EINVAL参数错误共享内存buffer格式或命令号不对遇到错误不必慌照着这个表格定位会快很多。但要注意同一个错误码在不同平台上可能对应不同细节还是要结合日志上下文判断。4.2 分阶段定位加载失败、通信失败与功能异常我自己排查TA问题时习惯把故障分成三个阶段来定位每个阶段的排查手段完全不同。第一个阶段是加载阶段。如果TA加载失败先确认镜像是否编译进固件、路径是否放对、签名是否有效。这段时间主要看kernel log和TZ侧log。我试过一个现象镜像文件都在签名也设置了但log里就是报安全校验失败最后发现是防回滚版本号对不上刷了旧版本TZ后再刷新TA就被拒。这属于平台机制问题不是TA代码的问题。第二个阶段是通信阶段。TA加载成功但REE侧调用时没反应或者返回乱码。优先检查qseecom内核驱动版本、HAL的UUID和命令号以及共享内存buffer的长度定义。我遇到过HAL里定义的最大模板长度是2048字节TA侧定义的是4096结果数据一长就溢出整个TA崩溃。两边结构体逐一核对能省很多时间。第三个阶段是功能阶段。TA能加载通信也没问题但指纹图像采集正常、模板录不上、匹配率低。这时候就要怀疑硬件链路了比如SPI时序不对、中断抖动过大、电源纹波影响成像质量。这种情况下与其埋头看TA代码不如用示波器看一遍SPI波形和中断信号往往能更快找到问题。4.3 排查过程中必须留意的细节有几个细节在联调阶段特别重要。第一个是启动顺序。指纹TA依赖的SFS等系统TA如果没先加载指纹TA可能在运行到某一命令时才报错排查时会误以为是自身代码问题。建议在启动log里确认安全世界的加载顺序。第二个是设备状态。调试过程中如果改了安全世界分区比如写了新的TZ或TA镜像一定要确认设备真的进入了新版本。有些平台刷写后不会自动生效需要完整重启或重新在fastboot模式下刷入。第三个是双分区或多分区固件。新版Android系统的固件可能有AB分区刷写一侧分区后另一侧还是旧版本导致现象时好时坏。我在高通平台遇到过类似的“玄学问题”最后发现是OTA槽位没切换日志里全是旧版本的。第四个也顺带提一下如果调试中把TZ分区刷坏进不了系统高通平台一般会进入9008紧急下载模式系统里会看到qualcomm hs-usb qdloader 9008设备。这时候可以通过EDL工具配合原始分区备份救回来。这里要提醒一下刷TZ分区前一定要先备份原始固件否则救砖会很难受。4.4 后续还可以这样扩展移植完TA、功能稳定之后并不代表整个安全方案就结束了。如果项目要进一步过安全认证通常还要补几个东西对TA内存进行安全审计、增加防重放和防回滚策略、确认模板存储的密钥管理和销毁流程。另外新平台如果支持动态TA加载可以把指纹TA改成按需加载开机时少占内存也能减少攻击面。成熟一点的团队还会在TA外面包一层“命令白名单”或“频率限制”防止恶意应用疯狂调用指纹命令。这些都属于增强项不在基础移植范围内但我觉得真正做过商用量产的人最好在这一步就把设计考虑进去免得后面再回头改TA又要重新走一遍签名和测试流程。做指纹方案移植这几年我最大的体会是TA移植真正难的其实不是写代码而是对整个高通安全世界体系的熟悉程度。你只有把加载机制、签名机制、通信机制都摸透了才能在诡异的现象面前快速定位。上面这些经验都是我在实际项目里一砖一瓦垒出来的希望对正在做同类方案的朋友有帮助。至少以后再遇到TA加载失败你可以少走几段弯路。