做嵌入式设备开发的人,对“安全”这两个字的理解,大多都是从翻车开始的。我在实验室里调板子的时候,Secure Boot 默认关着、串口全开、root 随便进,一切岁月静好。等设备真正进了现场——也就是标题里说的 Live 状态——网络安全策略、USB 管控、只读文件系统、证书信任链、启动校验这些原本“存在感很低”的东西,突然全变成拦路虎。一个权限位设错,设备就启动不了;一条 USB 策略没配好,现场技术员插个 U 盘都报 blocked。这篇文章就整理一下这些年我在嵌入式系统安全上踩过的坑和沉淀下来的做法,从开机到运行,从芯片信任根到应用层权限,一次讲透。适合做嵌入式 Linux、物联网网关、Android 定制系统、以及工业设备准入控制的工程师参考,尤其是那些即将把设备从开发板推向量产的朋友。
1. 别等真机翻车才想起安全:嵌入式系统的“Live”问题
很多人把嵌入式安全理解成“给固件加个密”或者“开机设个密码”,这其实把问题想窄了。Security 在 Live 系统里是一个贯穿全生命周期的状态,不是某一个功能模块。设备从上电那一刻起,就要经历 BootROM 校验、Bootloader 验签、内核加载、文件系统挂载、应用启动、外设接入、网络通信,任何一个环节没有约束,都可能成为被攻击的突破口。我在一个车机项目里见过这样的场景:量产固件的 Secure Boot 是开着的,但文件系统里一个调试后门服务没删干净,结果攻击者根本不需要拆机,直接通过网络把后门激活了。这个案例给我的教训很深——安全是木桶效应,短板在哪,风险就在哪。
嵌入式设备的安全目标和 PC 或云服务器差别很大。首先是性能约束,很多 MCU 或者低端 SoC 根本跑不动高强度加解密,比如完整 TLS 握手在某些单核芯片上能把 CPU 占满;其次是存储约束,安全启动需要的密钥、证书、安全策略文件都要抢占宝贵的 flash 空间;再次是网络环境,大量设备部署在离线内网,根本无法实时拉取安全补丁,漏洞一旦被利用,修复周期长得可怕;最后是长生命周期,一台工业设备可能要用十年以上,而密钥算法和证书体系在这期间可能已经换代了。这些约束决定了你不能照着互联网安全方案往嵌入式设备上硬套。
所以我在规划系统安全时,习惯把目标拆成四层:启动安全(确保只跑我们签名的代码)、系统完整性(确保运行时文件和数据没被篡改)、外设管控(确保物理接口不被滥用)、应用与数据安全(确保业务层面权限和加密到位)。四层之间相互独立又彼此支撑,哪一层被突破了,其他层还能挡住大部分攻击。比如 Secure Boot 被绕过,还有 dm-verity 校验文件系统;文件系统被挂载成可写,还有 SELinux 限制进程权限。这种纵深防御的思路,是 Live 系统安全的基石。下面几节我就按这条链路逐步展开。
2. 从信任根到应用层:嵌入式安全的总体布局
提到“信任根”这个词,很多做应用层开发的同事会觉得抽象,我换个说法:整个系统的安全,都要建立在“第一个不可伪造的信任点”上。在 PC 上,这个信任点是 UEFI 固件里的密钥;在手机 SoC 上,是 BootROM 里固化的公钥和 eFuse 里的密钥;在工业设备上,常见的是独立的安全芯片或 HSM。为什么需要一个物理信任根?因为软件层面的校验总会被软件攻击,比如替换引导程序、注入恶意代码,只有把根密钥放在芯片内部、物理上无法轻易读取的区域,攻击者才没有捷径可走。这也是为什么华硕主板 BIOS 里开了 Secure Boot 后、很多系统起不来时,问题往往出在“信任根里的密钥和系统不匹配”,而不是主板坏了。
从信任根往外,是一条完整的“链式验证”路径。以典型的高通/MTK Android 设备为例,链条是:BootROM -> SPL/ABL -> U-Boot -> boot.img -> system/vendor -> 应用签名。每一级加载下一级之前,都要用上一级内置的公钥验证下一级的签名。任何一个环节验签失败,系统就拒绝启动。这个设计思路和你去机场过安检是一样的:每个登机口都要查一次登机牌,而不是只在机场大门查一次。但这也带来一个实际问题——密钥管理极其重要。我见过有团队把签名私钥放在一个共享网盘里,结果某次内网被入侵,固件签名私钥直接泄露,这意味着攻击者可以伪造任意固件。所以做启动安全,第一件事不是写代码,而是设计密钥的生命周期:谁生成、谁存储、谁使用、谁销毁。
然后是运行时的系统完整性。签名解决了“启动时加载的镜像是官方的”,但解决不了“运行后文件被篡改”。Android 的做法是 dm-verity,它把整个系统分区做成一个默克尔树,块设备读取时动态校验哈希,一旦发现某个块被改动,立刻返回 I/O 错误。Linux 下的做法还有 IMA(Integrity Measurement Architecture),它对文件进行哈希度量并和预期值比对。这些机制听着复杂,但实际效果很直接:设备被 root、系统分区被挂载为可写,这些尝试都会被拦截或者立即暴露。不过要注意,这类机制在开发调试阶段非常碍事,我一般建议开发板关闭、量产机强制开启,并且用构建系统把开关固化成配置,避免人为忘记。
最后是应用层的权限模型。嵌入式设备上的应用五花八门,有的是开机自启的守护进程,有的是和硬件打交道的底层服务,还有的是给用户用的业务 App。Android 里靠 SELinux 加 APK 签名权限,Linux 里靠 Capabilities 加 cgroup 隔离。原则只有一条——最小权限,每个进程只给它完成工作所需的最小权限集。这个说起来容易做起来难,我见过很多设备把平台签名密钥直接发给第三方 App 开发商,就为了让他们能调用一个系统接口,结果等于把整个系统安全拱手让了出去。正确的做法是自定义受保护的权限,让第三方申请,由系统服务统一校验。后面我会详细讲如何配置和排查这类问题。
3. 启动链路怎么做才靠谱:Secure Boot 与固件校验实战
启动安全这部分是最“硬核”也最不好调试的,因为一旦配置错误,设备直接变砖,而且不像软件问题可以打日志,Bootloader 阶段连串口都可能没起来。我建议所有要量产的朋友,提前在开发板上把 Secure Boot 的整体流程摸透,熟悉每个阶段的报错表现,否则等产线上批量刷机时才发现某台机器的密钥没烧对,那真的是灾难。
3.1 Secure Boot 的信任链与密钥体系
Secure Boot 的密钥体系通常采用 PKI 结构,以 UEFI 为例,有三类关键密钥:平台密钥(PK)、密钥交换密钥(KEK)、签名数据库(db/dbx)。PK 是整个体系的根,只有持有 PK 私钥才能修改 KEK 和 db;KEK 用于授权更新签名数据库的实体;db 里存放的是被信任的签名或证书,dbx 存放被吊销的名单。系统启动时,固件会校验引导加载程序的签名是否在 db 里、是否被 dbx 吊销,通过才继续执行。
这里我要强调一个容易被忽略的点:开启 Secure Boot 不等于万事大吉,密钥的生成和管理才是重点。很多主板厂商默认只预置了微软的签名密钥,你自己编译的 Linux 内核如果没有做签名,是过不了校验的。我在华硕主板上调试过一台双系统设备,开启 Secure Boot 后自编译内核直接拒载,排查了半天发现是没把自建证书导入 db。解决方法是进 BIOS 的 Secure Boot 管理界面,把自定义证书或签名的 .efi 引导文件加入白名单,或者干脆在构建内核时用本地私钥签一次 boot 镜像。另外,Secure Boot 开启后,部分老显卡的 Option ROM 因为不带签名会被拦截,表现就是开机黑屏——这时候要检查 PCIe 设备的固件是否支持,而不是怀疑主板坏了。
3.2 固件升级包的签名与防回滚
设备出厂之后,固件升级是安全链条里最容易出问题的环节。攻击者最常见的攻击手段有两种:一是伪造一个包含恶意代码的“升级包”骗设备刷入,二是把一个存在已知漏洞的旧版本固件“回滚”刷进设备,这就是降级攻击。针对第一种,需要在升级包层面做数字签名校验,用升级公钥验证固件包的摘要和签名;针对第二种,需要在设备里维护一个单调递增的版本计数器,Bootloader 每次刷完新固件就更新计数器,并且拒绝任何版本号低于当前值的镜像。
实际项目中,我建议把“签名验证”放在两个地方:一是设备侧的升级服务(比如 Android 的 recovery 模式),二是升级服务器侧。设备侧验证保证恶意包进不了系统;服务器侧验证保证即使有人拖走了升级包文件,也无法伪造新的恶意包。如果你用的是 OTA 方案,最好在服务端给每个设备生成唯一的升级令牌,防止合法的升级包被离线重放。此外,所有升级过程都要有断电保护——写入新固件前先写一个 flag 分区标记状态,bootloader 启动时检查这个 flag,如果发现升级中断要自动回到上一个可用版本。这个机制我当时在设计时多花了两天,后来在真机上模拟断电场景,救回了至少三块开发板。
3.3 开发阶段如何验证 Secure Boot 是否真的生效
很多团队做到最后,Secure Boot 是“开了”但没验证过,这不叫安全,叫心理安慰。我的习惯是,在测试阶段做两个验证动作。第一个是破坏性验证:把一张未签名的内核镜像或错误签名的 boot.img 刷进设备,确认系统拒绝启动。第二个是完整性验证:启动后用cat /sys/firmware/efi/efivars/SecureBoot-*或在高通平台查fastboot getvar secure,确认 Secure Boot 状态是 enabled。针对 Android 设备,还可以通过adb shell getprop ro.boot.verifiedbootstate查看 verified boot 状态,正常量产的设备应该是 green,如果是 orange 或 yellow 就说明验证链有问题。这个步骤一定要写进产测流程,不能省。
4. 文件系统与证书权限:最常见的“现场翻车点”
如果说 Secure Boot 是启动阶段的守门员,那文件系统和证书权限就是运行阶段最容易出乱子的地方。我在项目现场见到的报错,十有八九和这一层有关。有些问题是设计缺陷,有些纯粹是运维踩坑,但结果都一样:服务起不来,设备挂掉,技术员抓瞎。
4.1 为什么 /system 必须只读:一个模拟器报错引发的思考
先从一个很典型的报错说起。很多做 Android 模拟器开发的朋友应该都有过这个经历:用 adb 往/system/etc/security/cacerts里推 CA 证书,结果 shell 提示 "remote couldn't create file: read-only file system"。这个报错的直接原因就是/system分区是只读挂载的,但这背后其实是好几层安全设计在起作用。
从 Android 4.4 开始,系统分区默认只读,后续版本又加入了 dm-verity 和 SELinux,三重保护叠加:挂载标志是 ro、块设备有完整性校验、文件上下文受 SELinux 策略限制。哪怕你用adb root拿到了 root 权限,想往/system里写文件,还得先adb disable-verity、adb remount,而且需要解锁 bootloader——这在量产设备上通常是不允许的。所以这个报错不是 bug,是系统在保护自己。
那正确做法是什么?在 Android 设备上,用户级 CA 证书应该安装到/data/misc/user/0/cacerts-added/,或者通过系统的“安装证书”入口导入,系统会为每个证书生成一个 hash 命名的文件并放到用户 CA 存储区。真机上如果确实需要替换系统 CA(比如企业内网要装私有 CA),正规路径是修改系统镜像并重新签名、刷机,而不是在运行的时候用 adb 硬来。模拟器上我见过有人用-writable-system参数启动,再adb remount,这只能在开发环境用,而且每次冷启动都会恢复只读状态。要时刻记住:你越方便地改系统分区,攻击者就越方便地改你的设备。
4.2 文件安全属性设置失败的排查思路
热搜词里有一条 "could not set file security for file",这个报错我在交叉编译环境和 Windows 共享目录上见过很多次。它的本质是:你尝试设置文件的 ACL、属主或扩展安全属性(比如 SELinux 上下文、POSIX capability),但文件系统或当前权限不允许这么做。
举几个实际场景。场景一:在 Windows 共享的 FAT/exFAT 格式盘上交叉编译 Linux 内核,然后试图用setcap cap_net_raw=ep ./app,会直接报 “setting capabilities on file failed: Operation not supported”,因为 FAT 不支持扩展属性。解决方法是把文件放到 ext4 格式的分区上再设置。场景二:在多用户 Linux 服务器上,用普通用户执行chown root:root file会报 “Operation not permitted”,这是内核强制用户权限,不是 bug,应该用 sudo。场景三:在 Windows 的 NTFS 文件夹上,程序试图修改文件的安全描述符(Security Descriptor)但当前用户没有该文件的“更改权限”权限,会弹 “Unable to set file security” 之类的提示,这在共享目录里特别常见,根因往往是共享权限和 NTFS 权限叠加,取的是交集,任何一个权限位不够都会失败。
遇到这类问题,先别急着查业务代码,用ls -lZ看 SELinux 上下文、用getfacl看 ACL、用mount看文件系统类型和挂载参数,基本能定位到 80% 的原因。Windows 上则用icacls查看有效权限,确认当前用户对目标路径有“修改”和“写入”权限,而不是只看共享权限是否全开。
4.3 SELinux 策略不是摆设:从 avc denied 到放行
SELinux 是嵌入式 Linux/Android 系统里最劝退新手的组件之一,也恰恰是 Live 系统安全最不能省的防线。它的核心机制是强制访问控制(MAC),不管进程是 root 还是普通用户,所有访问都要经过策略检查。没有 SELinux 时,拿 root 等于无敌;有了 SELinux,root 打不开/data里的敏感文件也很正常。
我强烈建议在使用 SELinux 的系统上,始终开启 enforcing 模式,并且通过dmesg | grep avc或者adb logcat -b events | grep avc来观察被拒绝的访问记录。一条典型的 avc denied 日志长这样:
avc: denied { read write } for pid=1234 comm="my_service" name="config.db" dev="mmcblk0p25" ino=2457 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:vendor_data_file:s0 tclass=file这段日志的含义是:untrusted_app域里的进程尝试读写vendor_data_file类型的文件,被策略拦住了。要放行,需要在你自己的 te 策略文件里加一条 allow 规则:
allow untrusted_app vendor_data_file:file { read write open };但我要提醒,看到 denied 不要本能地第一时间放行,先想想这个访问是不是真的合理。如果某个系统服务尝试读/data/user/0下的其他 App 私有目录,那这本身就是高风险行为,正确做法是改造应用架构,而不是无脑加 allow。在 Live 环境里,安全策略的每一分宽松,都是给攻击者留的一分空间。
4.4 数据库权限也多留个心眼
在物联网后台或者设备端数据库上,热搜词里那个create algorithm=undefined definer=... sql security definer ... view information_schema.views的报错也值得聊两句。它的核心不是语法问题,而是权限定义问题:创建 View 时用了SQL SECURITY DEFINER,意味着任何用户查询这个视图时,都按定义者的权限去执行。如果定义者是mysql.infoschema@localhost这种高权限账号,而视图内容又被篡改,就可能出现越权读数据的情况。
在嵌入式设备的本地数据库上,我建议所有视图和存储过程都遵循两个原则:一是用最小权限账号创建,二是明确指定SQL SECURITY INVOKER,即按调用者的权限执行,而不是按定义者的权限执行。这跟前面讲的最小权限原则是同一个道理——权限不要悄悄放大。
5. USB 与外设策略:运行态设备管控的攻防细节
设备一旦进入 Live 状态,物理接口的管理往往比远程攻击更棘手。因为攻击者可能直接走到设备跟前,插一个 U 盘、接一个键盘、或者通过 USB 线把 PC 和设备的调试口连起来。这类攻击在工业现场尤其常见,所以外设管控是嵌入式系统安全里不能省的一环。
5.1 USB 设备管控:从策略配置到报错排查
热搜词里那条 "USB device has been blocked by the current security policy" 是 Windows 端点管控里很典型的报错。这类策略通常由企业的 MDM(移动设备管理)或 DLP(数据防泄漏)软件统一下发,通过组策略或注册表设置HKLM\SOFTWARE\Policies\Microsoft\Windows\RemovableStorageDevices下的权限项,把可移动存储类设备的读写权限全部 deny。一旦策略生效,用户插入 U 盘会看到设备被策略拦截的提示,完全没法用。
在 Android 设备上,外设管控通常走 DevicePolicyManager。比如设备所有者(Device Owner)可以通过setUsbDataSignalingEnabled(false)禁止 USB 数据连接,只允许充电;也可以使用addUserRestriction里的DISALLOW_USB_FILE_TRANSFER限制文件传输。如果设备是 kiosk 模式(比如自助终端、工控平板),我建议在系统层面把 USB 默认切到充电模式,同时用 SELinux 策略限制 vold 挂载 USB 存储的行为。这么做的好处是,即使攻击者物理接触设备,插上 U 盘也无法挂载读取数据。
还有一条常见的现场报错 "this action is not allowed with this security level configuration",这个在 Android 上通常意味着你调用的 API 需要更高的角色权限。比如某个管理应用想设置 USB 规则,但它的设备管理角色不是 Device Owner,系统就会拒绝。排查思路很简单:确认当前应用是否已经被设置为设备所有者,用dpm set-device-owner命令行进行配置;如果已经设置了还报错,检查应用是否正确处理了onDisableReasonChanged回调,或者是否因为安全补丁升级导致 API 行为变化。这事没有太多捷径,就是要一板一眼地核对 Device Admin/Device Owner 的状态。
5.2 安全策略配置的统一管理
设备现场的 USB 拦截、外设白名单、应用安装限制,这些策略如果只靠人工一台一台配,迟早出大乱子。我见过一个项目,因为运营同事在几十台设备上手工配错了策略版本,结果部分设备能插 U 盘,部分不能,排查花了一整天。后来我们把策略做成了签名配置文件,放在系统分区里,每次开机由安全服务读取并应用,同时服务器侧记录各设备的策略版本号,发现版本不匹配自动告警。
这个经验可以直接复用到 Windows 场景。比如安全中心的注册表项被人改了,或者杀毒状态在系统里显示异常,排查时先看组策略有没有被本地管理员覆盖,再检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Security Center\Provider\Av下的 Provider 注册项是否被篡改或缺失。很多情况下,不是杀毒软件本身不行,而是注册表里的安全中心状态和实际服务状态对不上,导致系统误报“未启用安全防护”。Linux 侧同理,不要逐个文件散落配置,把/etc/security下的所有策略文件纳入统一的版本管理,和固件一起发布,才能确保所有设备策略一致。
5.3 操作系统自身的安全组件别乱动
有些朋友一遇到 Windows 安全中心变英文、或者某天突然打不开安全中心,就急着去改注册表或者关掉 Defender。我劝你先想清楚:这真的是“软件问题”,还是你触碰了安全基线?Windows 安全中心界面语言跟随系统区域设置,只要安装了对应语言包并把区域切到中文,界面一般会跟着变。如果只改系统区域但界面仍然是英文,大概率是系统镜像没有包含中文语言包,而不是设置项藏在哪里。
另外,企业环境里安全中心的检测状态经常被第三方杀毒软件接管,此时 Windows 安全中心会显示“由 XXX 管理”,这是正常的多供应商协作状态,不代表系统不安全。真正需要警惕的是安全中心被注册表策略或恶意软件静默禁用——如果你发现安全服务都被关停且无法重新打开,那就得走完整的系统排查流程了。对于嵌入式设备,尤其是 Windows IoT 设备,我建议出厂时就锁定安全管理策略,避免现场操作人员随意改动。
6. 现场报错速查:那些最磨人的错误信息
把实际项目中遇到的高频报错整理成一张速查表,方便大家遇到问题先对号入座。这些信息都是我从真实现场记录下来的,有些看起来简单,但在紧张排障时非常有用。
| 报错信息 | 典型场景 | 根因分析 | 解决思路 |
|---|---|---|---|
could not set file security for file | 编译/部署把文件写到共享盘或 FAT 分区 | 文件系统不支持 ACL/扩展属性,或当前用户权限不足 | 改用 ext4/NTFS,用icacls或chown检查并修正权限 |
/system/etc/security/cacerts ... read-only | adb 往 Android 系统证书目录推证书 | /system 只读挂载 + dm-verity + SELinux | 用户证书导入/data/misc/user/0/cacerts-added;系统证书需重新制作镜像并签名 |
this action is not allowed with this security level configuration. | Android 管理 App 调用受控 API | 应用不是 Device Owner 或 Device Admin,或策略等级不够 | 用dpm set-device-owner配置角色,核对回调和权限声明 |
USB device has been blocked by the current security policy | Windows 系统插入 U 盘被拦截 | 组策略或 MDM 禁用了可移动存储 | 找到下发策略的管理端,按需调整;切勿本地硬改注册表绕过 |
hkey_local_machine\software\microsoft\security center\provider\av\相关注册表缺失 | Windows 安全中心显示异常 | 第三方杀软接管或注册表项损坏 | 检查杀软安装状态,修复安全中心 Provider 注册项 |
create algorithm=undefined definer=mysql.infoschema... | 数据库视图/存储过程权限报错 | SQL SECURITY DEFINER 与高权限定义者绑定 | 改用最小权限账号,指定 SQL SECURITY INVOKER |
| 华硕主板开启 Secure Boot 后黑屏/无法引导 | 自编译系统或老设备开机失败 | 引导加载程序未签名,或 Option ROM 不兼容 | 在 db 中导入自建证书/签名引导文件,关闭 CMS,确认显卡固件兼容 |
avc: denied日志刷屏 | SELinux enforcing 模式下服务被拒绝 | 进程域与目标文件上下文不匹配 | 根据日志写最小化 allow 规则,审慎放行 |
这个表里的问题,多数在开发环境根本不会暴露,因为开发板通常把 SELinux 关在 permissive、文件系统可写、Secure Boot 不开,所有权限都宽松得很。等设备走上产线、进入 Live 状态,这些约束一个接一个回来,问题就接踵而至。所以我的建议是:越早让开发环境模拟量产的“收紧”状态,越能提前发现这些问题,而不是等到现场才排查。具体可以每周做一次全量构建,在真机上开启所有安全项跑一遍冒烟测试,把报错提前消化在实验室里。
7. 几条实践心得,写给打算做安全的你
最后聊几句比较私人的体会。做嵌入式系统安全这几年,我最大的感受是:安全不全是技术问题,更是管理问题。比如密钥管理,技术方案再完美,如果私钥在共享网盘里躺着,一切归零。很多攻击者根本不需要破解芯片,只需要买通一个能接触到密钥仓库的运维即可。所以我现在做每个项目,都会把“密钥生命周期管理”作为一级需求来设计,明确每个环节的责任人和审计日志,哪怕这意味着流程比以前繁琐。
另一个体会是:安全配置必须版本化。分区表、SELinux 策略、证书白名单、USB 管控规则,这些都要进版本库,和固件版本一一对应。我见过太多现场问题,最后定位到“某台设备的策略文件是三个月前的手工修改版”,这种事故一旦发生,几乎没法快速恢复。把策略做成镜像的一部分,每次发布都生成唯一的策略指纹,设备端和服务端都记录在案,才是根治之道。
最后一条:真机验证不可替代。模拟器上跑得通的安全配置,放到真机很可能因为 Bootloader 版本、eFuse 熔丝、外设控制器差异而表现完全不同。尤其是 Secure Boot、dm-verity、SELinux 这几条链路,一定要在真实量产固件上做破坏性测试。我在测试 Secure Boot 时遇到过好多次烧录失败,还专门做过“假升级真掉电”的模拟实验,就为了确认设备在异常路径下不会变砖。这类测试虽然费时,但它带来的信心,是多少份安全报告都给不了的。
如果你正准备给设备做安全加固,我建议从这三步开始:先把 Secure Boot 和系统完整性校验打开,再把文件系统权限和 SELinux 收紧,最后补上外设与 USB 管控。每一步做完都跑一轮完整回归测试,别急着一步到位。安全是场持久战,设备在 Live 状态下的每一分钟,都是检验你设计是否扎实的时刻。