ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PRODUCT_STATE切换后Unable to Get Core ID:设备身份权限模型与量产流程排查指南

PRODUCT_STATE切换后Unable to Get Core ID:设备身份权限模型与量产流程排查指南 第一次看到Unable to Get Core ID after Changing PRODUCT_STATE这行日志时我以为是软件版本刷漏了。固件启动正常业务线程也在跑但设备身份子系统就是拿不到 Core ID整个产测鉴权卡在第一步。后来翻完安全启动、生命周期状态、驱动初始化三块代码才明白这行日志不是一个偶发错误而是量产品切换产品状态后安全策略把 Core ID 读取接口主动关闭了。这是一次典型的“设备身份权限模型”与“生产流程”错位的坑。这篇文章把我这次完整的排查过程、背后机制和最终落地的解决办法写清楚如果你也做 IoT 模组、车规级 MCU、带安全处理器平台的量产应该能少走不少弯路。1. 现场还原Unable to Get Core ID 是在什么环节爆出来的1.1 日志出现的真实位置我第一次复现这个报错是在产测软件做设备身份校验的时候。设备固件已经切到PRODUCTION状态开机流程正常走完Wi-Fi 和蓝牙扫描也都能跑起来但上位机发送“读取设备唯一识别码”的指令后设备侧一直没有正常应答。抓串口日志先看到几条 devid 服务相关的调试信息然后就是这行[ 4.314150] devid_core: Start to read core id... [ 4.318902] devid_core: PRODUCT_STATEPRODUCTION, read access denied [ 4.325110] devid_core: Unable to Get Core ID after Changing PRODUCT_STATE [ 4.332020] devid_core: fallback to EID(deadbeef-0001-0002-0003-0004)关键信息有两层。第一层devid_core是设备身份服务模块负责在启动早期读取核心 ID并把 ID 提供给上层证书绑定、产测鉴权、日志溯源等模块使用。第二层日志本身已经把原因写得比较直白它检测到当前PRODUCT_STATE是PRODUCTION然后主动拒绝了这次读取后续逻辑只能走 fallback 通道尝试退回 EID。如果你的产品没做 EID 兜底这里会直接返回失败上层初始化跟着挂掉现象会比我的更难看。我当时把完整日志拉出来之后最迷惑的一点是设备并不是完全故障网络接口、文件系统、业务进程都正常只有身份子系统这一路报错。如果只看设备业务层的健康检查你甚至可能认为设备一切正常直到联调设备认证时才发现身份校验过不去。1.2 我当时踩的几个错误排查方向遇到这个报错时团队里至少有三种猜测驱动加载顺序问题、固件分区损坏、编译宏配置错误。我当时也按这个思路查了一轮。调整设备身份服务的初始化顺序从late_initcall改成subsys_initcall还尝试在根文件系统挂载后再启动 devid 服务问题依旧。全量校验 bootloader、trustzone、devid 所在分区哈希全部通过排除 flash 刷写异常。对比现象版本和能正常读取的版本发现唯一区别就是烧录流程最后多了一步“写入生产状态”。到这里基本能确定不是软件刷坏了而是行为和数据都正常只是读取入口被有意关掉。真正的问题在于为什么一个可读的接口会在状态切换后变成不可读这需要回到产品状态机的设计里去理解。1.3 伴随现象里藏着的重要线索在报错日志出现的同时还有几个不太起眼的现象后来回头看都指向同一件事情设备身份服务虽然读不到 Core ID但通过另一个接口能读到 EIDEID 值固定不会为空产测上位机发送 AT 指令或私有协议指令时设备响应时间比工程态慢了几百毫秒设备安全日志里有一条sec-policy: enabled core_id_ro_after_prod可惜当时没在意。这个core_id_ro_after_prod其实已经把策略名字写明白了Core ID 在生产态之后变成只读保护对象甚至对普通用户态调用方彻底不可读。当时我没看到这一行导致后面多走了几个小时弯路。你要是现在遇到类似日志先去安全策略日志里搜关键字能省很多事。2. PRODUCT_STATE 改变的不只是一个标志位2.1 设备生命周期状态机在多数带安全能力的 SoC/IoT 平台上PRODUCT_STATE 并不是普通全局变量而是一个生命周期状态机。通常由这几个状态组成状态名常见数值典型用途Core ID 用户态读取ENGINEERING0x01开发调试、跑测试用例允许TEST_LOCKED0x02产测、校准、老化允许或受限PRODUCTION0x03正式出货拒绝SECURED0x04启用更严格的防回滚策略拒绝状态迁移基本是单向的。开发态可以自由切回可以从ENGINEERING前往TEST_LOCKED但一旦走到PRODUCTION以后想再回到前面状态就会受到熔丝、证书、版本号等机制的限制。这个设计很像我们平常接触的门禁权限普通员工卡可以自由进出开发机房但正式商用的核心机房员工卡默认没有权限必须单独申请。我在这类设备上还遇到过一种“半生产状态”产品文档里叫TEST_LOCKED此时安全策略已经开始收口但还会给产测工具保留几个特定接口。如果平台支持这个状态尽量把“读 Core ID”的动作安排在这个阶段完成会比切到PRODUCTION后从容很多。2.2 状态下发链路与读取权限判断状态不是固件里写死一个枚举就完事。生产时烧录工具或工厂脚本会把状态写进安全存储区域有些平台是 OTP Fuse有些是安全 NVRAM还有的平台由带签名的启动配置数据下发给 TEE。设备每次启动时BootROM 和 TEE 都会验证这段配置的签名然后才把可信状态暴露给内核态和用户态服务。enum product_state { STATE_ENGINEERING 1, STATE_TEST_LOCKED 2, STATE_PRODUCTION 3, }; int read_core_id(char *buf) { if (current_state STATE_PRODUCTION !is_whitelisted(current_credential)) { return -EACCES; } return secure_hw_read_core_id(buf); }看这段简化代码PRODUCT_STATE改变后影响的是is_whitelisted的判定结果。开发态下几乎所有内核调用都被放行生产态下只有持有特定安全凭证的调用方才允许访问。权限模型不是“能不能读”的问题而是“谁有资格读”的问题。实际平台上这个调用链通常会长一些用户态应用 - ioctl /sysfs 节点 - 内核 devid driver - TEE Client API - Trusted Application (TA) - 安全硬件寄存器每一层都会做状态检查和权限校验任何一个环节收到PRODUCT_STATE是生产态都可能返回访问拒绝。这也解释了为什么单纯改驱动加载顺序无效——它根本不是执行顺序问题而是安全上下文问题。2.3 为什么故意把 Core ID 锁起来产品方把 Core ID 锁起来并不是为了给产线添堵。Core ID 是芯片制造商烧录或测试时写入的唯一标识很多产品的设备证书、安全密钥、账号绑定都拿它当种子。如果应用层随便一个进程都能读攻击者只要拿到一台设备甚至从日志里提取到一次输出就可以克隆“设备身份”让伪造设备接入服务器时看起来像正品。我在做设备接入平台时也确认过token 换签名、设备认证、固件授权全都挂在 Core ID 或由它派生的密钥上。如果 Core ID 可被普通进程随意读整个信任链都是裸奔的。生产态下锁掉读取接口等于把设备唯一身份的保护等级提到了和私钥差不多。从产品设计角度想这是很合理的取舍。多亏这次踩坑我再看到类似策略不会觉得是“厂商故意限制”而是会先问一句这个信息当前应该被谁读读到了用于什么有没有不可替代的时机3. 根因定位不是硬件坏是权限模型收口了3.1 向安全策略表要答案要确认问题在权限收口而不是硬件故障最直接的办法是看安全策略表。常见平台里设备身份服务通常运行在内核态或 TEE 安全世界中向安全世界发起调用的进程需要经过 CA 到 TA 的 session 鉴权。你可以做三件事打开 TEE 层日志确认调用被拒的具体返回码。通常是TEEC_ERROR_ACCESS_DENIED或类似的权限码而不是TEEC_ERROR_NOT_FOUND。查看设备身份服务所在进程的 UID 或安全上下文对比当前策略文件里的白名单确认它是否被允许访问core_idTA。用平台提供的调试接口查一下当前PRODUCT_STATE的实际值确认它确实到达了拒绝阈值。如果第二、三条都符合那基本可以断定设备硬件本身和固件没坏只是调用方现在不在白名单里。我当时在设备终端上做了这样一组快速检查# 查看当前产品状态节点 cat /sys/fuse/*/product_state # 查看设备身份服务进程的安全上下文 ps -A | grep devid ls -lZ /system/bin/devid_serv # 尝试直接读取身份节点观察返回码 cat /proc/device_identity/core_id在开发态下cat /proc/device_identity/core_id能打印一串 128 位十六进制 ID切到生产态后这个命令返回Permission denieddmesg里正好对应read access denied。这就把问题定位到了“用户态调用被权限层拒绝”而不是硬件读取失败。3.2 回退实验把同一块板子切回开发态再看光靠静态分析还不够我当时做了一个很有说服力的回退实验。在一块尚未烧断熔丝的样机上先切到ENGINEERING状态正常读取到 Core ID然后把同一块板子切到PRODUCTION状态再读取马上复现Unable to Get Core ID after Changing PRODUCT_STATE最后按照平台规范启动安全回退流程回到开发态再读取又正常拿到完整 ID。对比实验结果实验条件读取结果说明ENGINEERING 状态Core ID 完整读取读取通道正常PRODUCTION 状态拒绝 报错日志权限收口生效回退到 ENGINEERINGCore ID 完整读取硬件与固件链路正常这个实验一做完基本排除了所有“硬件损坏”或“固件分区丢失”的猜测问题范围收敛到状态机与权限模型这一层。后续排查就是围绕能否在保持生产态的前提下合法地恢复读取能力。要注意回退实验只能在尚未彻底烧断生产熔丝的工程样机上做。如果设备已经走到不可逆熔断强行回退只会触发更高层级的安全策略甚至把设备锁死。所以动手之前先确认当前平台的回退条件。3.3 真正的熔断点不在驱动在授权入口很多工程师遇到这种问题会在驱动代码里翻个底朝天实际上驱动只是执行者真正的判断在安全世界。你可以在 driver 返回的errno、TEE 的 syscall 日志、安全世界预留的 trace 节点三个位置看到同一个拒绝结论。有些平台在用户态还留了一个可观察的信号固件启动阶段会打印策略版本号比如[ 3.880123] sec-policy: enabled core_id_ro_after_prod v2.1看到这行基本就明白了core_id_ro_after_prod本身就是平台在安全态下启用的一项保护策略名字已经把规则写清楚了。后面要做的是找到符合平台规范且不影响安全模型的读取通道而不是去挑战安全设计。4. 三条能落地的恢复与规避方案4.1 生产前批量采集治本方案最省心的解决方案从来不是在出事后去解锁而是在生产态切换之前把需要的数据全部导出。具体做法是在设备处于ENGINEERING或TEST_LOCKED状态时通过产测软件批量读取 Core ID并附带上设备序列号、MAC、校准数据、测试项结果形成一条包含设备唯一身份信息的档案记录。我当时给产线写过一个采集流程大致是这样# 产测阶段读取并登记 Core ID import serial for sn in serial_devices: with serial.Serial(sn, 115200, timeout5) as port: port.write(bread_core_id\n) resp port.read_until(bCORE_ID) core_id resp.decode().strip() database.insert(serial_numbersn, core_idcore_id, stateengineering)数据库里存下来的数据不光是 Core ID 本身还建议把“设备型号”“芯片批次”“产测软件版本”“状态切换时间”一起存。后面如果遇到批量返修或者要追溯某台设备用的是哪个版本的固件这些字段都能派上用场。这个方案有两个天然优势。第一不需要给生产态开任何读取后门设备安全边界不会减弱第二Core ID 采集和后续证书签发可以在同一阶段完成证书文件直接写入设备安全分区后续产线只做校验不再做读取。4.2 用授权接口做受控读取治标方案如果你的产测流程确实漏了一步设备已经切到生产态但此时必须读回 Core ID那就得看平台是否支持“受控读取”接口。这类接口一般有一个共同特征调用方必须提供一次性令牌、签名包或者由工厂产测工具向安全后端申请临时授权。典型流程如下工厂上位机向工厂管理系统申请一次设备身份读取授权后端系统用自己的私钥签发一次性授权包写入设备侧并附带有效期设备身份服务校验签名、有效期、设备状态通过后才允许本次读取读取结束后授权包立即失效。授权包本身不需要很复杂核心是要做到“一次一包、可失效、带时限”。我见过一些团队图省事直接用一个固定开发态固件顶上去这样虽然暂时能把 Core ID 读回来但安全策略形同虚设。设备一旦在市场上流通调试接口就变成了攻击面。实际操作中我发现越是低成本的 IoT 芯片生产态下的受控读取接口越少甚至完全没有。所以不要把这种能力当成默认项选型阶段就要确认量产阶段能不能读、走什么流程读否则等上了产线才发现很被动。4.3 回退开发态有条件有代价第三种思路是回退到开发态。这里最大的坑在于回退不是无条件的也不是所有板子都能成功。可回退通常要求设备的安全熔丝没有走到不可逆烧断那一步而且平台提供双状态位以便在工厂模式下切换状态。如果满足条件回退步骤一般包括通过工厂工具或串口指令进入安全恢复模式重新烧录包含开发态标记的启动配置TEE 在启动时验证该标记与当前版本号的兼容性重启后再次读取 Core ID 验证回退是否成功。回退的风险在于设备可能因此暴露在更宽松的安全环境中。如果回退后流到工厂之外很容易被当作调试机可能被用来逆向、提取密钥、绕过校验。所以业内更推荐只把回退用于研发阶段的返修板不用于量产设备。5. 量产流程设计上的规避措施5.1 正确的状态切换顺序综合上面的分析我想把最推荐的量产顺序写出来。这个顺序已经在多个项目上验证过能避开绝大多数“状态切换后拿不到身份标识”的坑。设备通电确认固件加载到 ENGINEERING 或测试态批量读取 Core ID、EID、MAC 等身份标识写入工厂管理系统按算法生成设备密钥对将公钥或设备证书写入安全存储写入产测数据、校准参数、产品配置验证设备能正常完成业务功能自检最后一步切换 PRODUCT_STATE 到 PRODUCTION切换后只做黑盒验证比如整机状态、网络连接、日志检查不再做身份字段的读取比对。这个顺序的关键点是所有需要读取身份明文的步骤都在状态切换之前完成切换之后产测只需要验证身份存在和签名有效不碰明文。5.2 给设备身份管理的一点上游建议除了流程顺序还有几件小事值得提前规划。一是把 Core ID 和真正面向业务的设备标识SN、IMEI、MAC在系统设计阶段做映射关系不要让上层业务直接依赖 Core ID 的实时读取二是设备证书或设备密钥的签发应该在产测阶段就完成并写入而不是等到设备联网后再向服务器申请三是给安全日志留一个独立分区记录状态切换时间、策略版本号、调用结果方便后续出问题回溯。我在这点上吃过亏最开始把 Core ID 直接当成数据库里的主键结果状态切换后无法读取整条生产链路卡死。后来改成“Core ID 只在工厂阶段读取入库业务阶段全部用 SN 加设备证书”问题彻底消失。设备到了用户手里Core ID 连影子都不出现反而更安全。5.3 千万别做绕过后门最后必须泼一盆冷水。出问题的时候团队里最容易出现的冲动是直接在用户态驱动里把权限判断注释掉或者专门出个“开发版固件”拿上产线应付检查。这是高危操作。一旦生产态固件能自由读取 Core ID等于把设备唯一身份的保护等级降到零后续设备认证、防克隆、数字版权都会受影响。万一这批固件外泄产线同事和客服热线都会被假冒设备投诉淹没。技术上不是不能绕过而是代价远大于收益。安全设计这件事有时候守得住才做得长久。遇到问题先判断能不能通过流程上移解决实在不行再找受控方案这条原则比“这个接口怎么打开”重要得多。6. 处理这个报错的完整动作清单6.1 从日志到结论的快速判断路径如果你现在也在排查同款报错可以直接按照下面的动作顺序来省得像我一开始那样乱翻代码。抓完整开机日志确认 PRODUCT_STATE 的具体值确认设备身份调用方是谁内核驱动、TEE 客户端还是用户态守护进程从 TEE 或安全世界日志里找到返回码看是访问拒绝还是设备不存在用一块开发态样机做对照实验确认同一固件在开发态可读确认当前状态的切换是写入 OTP Fuse不可逆还是仅 NVRAM 标记可逆根据可逆性选择方案若能回退走回退流程若不能考虑受控读取或流程再造。这六步走完通常半小时内能定位。最怕的是陷入“重编译、换版本、试驱动顺序”的循环那些行为只能不断排除无关因素不解决真正的问题。6.2 与原厂或供应商确认信息时的清单如果平台保密性较强需要向原厂提工单时建议带上这些信息对方才能快速判断SoC 或模组型号、固件版本、PRODUCT_STATE 写入工具版本、失败时的完整日志、TEE 返回码、读取调用的线程或进程 UID。少了任何一项原厂都可能让你继续补日志来来回回拖一周。我自己的经验是把开发态能读取和现网生产态不能读取的日志一并打包原厂看到开发态能读、生产态不能读基本就会告诉你这是安全策略接下来的沟通效率会高很多。如果只给一条生产态报错日志对方第一反应可能还是让你排查驱动问题沟通成本上去了问题还没推进。7. 换个思路看这类报错现在再看到Unable to Get Core ID after Changing PRODUCT_STATE这行日志我第一反应已经不会去翻驱动加载顺序了而是先问三个问题产品状态切了没有当前策略表允许谁读回退是否还被熔丝放过。如果能重来我会在项目立项时就把“身份信息读取节点”画进量产流程图里让 Core ID 的采集、入库、证书签发全部发生在状态切换之前。状态切换之后Core ID 越难读反而说明设备的安全边界越清晰。这个认知是我在这次踩坑里得到的最大收获。
返回列表