Skip to content

KeyLens v3.0.3

Latest

Choose a tag to compare

@IamCanincan IamCanincan released this 02 Nov 16:21
· 3 commits to main since this release

这一版你会看到什么

  • 启动哈希默认「自动」:重装/新装的机器一个字都不用填,模块从当前后端自己的配置里取
    verifiedBootHash 写进 ro.boot.vbmeta.digest;取不到(比如 vb_hash=random)就一个字节都不写。
    升级前手填过值的机器行为完全不变(那一档仍是手动)。
  • 属性伪装补到 43 个属性名:userdebug / test-keys 在每个分区的 *.build.* 与指纹里
    就地改掉,不再出现"顶层说 user、分区说 userdebug"的自相矛盾;本机没有的分区报「本机无」,
    不凭空写。
  • 属性页修掉一处画错的颜色:本机没有的分区原来被画成红色「不符」,现在走中性。
  • 包小了一半:533KB(上版 729KB),二进制 326KB(上版 653KB)——启动路径与界面行为不变。

安装:从管理器刷入 zip 即可;已装旧版的直接覆盖。


变更

  • 打包不再把运行时缓存带出去:tools/build-zip.mjs 的 walk 会把 module/webroot/icons/
    (设备上 webui.sh icons 写的应用图标缓存,.gitignore 里本来就是运行期目录)打进发布包 ——
    v3.0.2 实测带了 3 张、33KB。现在 walk 跳过 webroot/icons 与 bin(后者由脚本显式追加,
    磁盘上有残留就会变成两条同名条目),并在 --check 里加两条断言:
    「包里没有 webroot/icons/」「bin/keylens 只有一条」;落盘前也自证一次,跳过表失效时当场退出。
    第一步 -20,924 字节(728,684 → 707,760),条目 34 → 31;加上后面的二进制优化,
    最终 728,684 → 533,142(-26.8%)。

  • 交付体积的构成(优化前量的):压缩后 keylens 368KB(52%)、index.html 241KB(34%)、
    shell 层 91KB(13%)、其余 3KB —— 大头是二进制,不是界面,所以下一刀落在二进制上。

  • 二进制 -50%(652,960 → 326,416 字节):新增 rust/build-release.sh 固定发布构建 ——
    nightly + -Z build-std=std,panic_abort + -Zunstable-options -Cpanic=immediate-abort
    (std 跟着 opt-level=z 重编、panic 的格式化与展开整块去掉)。CI 改成装 nightly +
    rust-src + 两个 musl target 后调这个脚本。发布包因此 728,684 → 533,142 字节(-26.8%)。
    代价与前提写进脚本抬头:panic 时不打印消息、退出码由 101 变 SIGABRT;命令的错误路径
    全走 Result + 自己的退出码,247 项对拍、cargo test 72、真机 device-boot-hash 20 条与
    device-props-mask --write 17 条(跑完现场还原)都在这份产物上验过。

  • 量过但决定不做的三刀(都留了数):去 unwind 表(产物一字未变,.eh_frame 来自预编译
    std/crt)、tokens 瘦身(154/382 个变量没人用,只值 8.8KB)、color-spaces 换小实现
    (133KB 覆盖 20 个色彩空间且被泛型遍历,收益约 40KB 压缩而风险是色值静默偏移)。

  • WebUI 台架加性能基线(verify.mjs 末尾,3 次导航取中位):首屏可见 45ms · FCP 48ms ·
    脚本 15ms · 布局 3ms(本机 Chrome 417×975@2x)。第一版量测写错了并当场被自检抓住:
    Performance.getMetrics 跨文档导航会清零,做差得负值;改成导航完读一次累积值。

  • 「启动哈希」改成三态,默认是「自动」——新用户一个字都不用填。以前这一格只有
    「填了 / 没填」两态,而多数 ROM(本机是 LineageOS)根本不给 ro.boot.vbmeta.digest,
    于是"钥匙盒在证明一个启动哈希、系统属性里那格却是空的"正好踩中检测器的
    vbmetaDigestMissingWhileAttestedHashPresent(这台机实测 Danger 4 → 2 的差就是它)。
    现在:文件不存在 = auto,开机时从当前后端自己的配置取(OMK 的 [trust] vb_hash、
    TEESim 的 overrides.json verifiedBootHash),拿到合法 hex 才写;文件里是 hex =
    manual(优先,升级前填过的机器行为不变);文件里是 off = 明确不写(旧版
    「清除」的语义搬到这里)。界面那一格跟着改成三态,并给出「保存并应用 / 改为自动 /
    不写 digest」三个动作。
    为什么 random 必须熄火:OMK 允许 vb_hash = "random"(每次 keymint 启动重新生成),
    镜像过来的值下一开机就过期,而检测器对"过期值"和"空值"给的是同一级 Danger——
    写了反而更糟。所以派生只认 64/128 位十六进制,random/null/缺文件/注释行一律当
    取不到,且绝不缓存:每次开机、每次应用都重读后端配置。只问启用的那一家后端,
    不读卸载后残留的配置目录(那会把一个不再被证明的 hash 写进 prop)。
    真机正对照:夹具里只放真实的 config.toml、不放 boot_hash 文件,auto 算出的值与
    手抄那串 0561fff0… 逐字节相同。

  • 属性伪装补齐「构建身份」这一族:期望表从 17 项涨到 43 个属性名。以前只钉顶层
    ro.build.type / ro.build.tags,而每个分区还有一套自己的
    *.build.type(本机现量 8 个还是 userdebug)、*.build.tags、*.build.fingerprint
    (ro.system.build.fingerprint 尾巴就写着 userdebug),再加上 ro.build.flavor
    (lineage_pdx234-userdebug)。结果是「顶层说 user、分区说 userdebug」这种自相矛盾——
    检测器今天只把它印在 [Info] 不判级,但任何应用逐键都读得到,而矛盾比统一的
    userdebug 更难看。特征表从 2 行补到 38 行。

  • 特征行从「整值替换」改成「就地替换每一处命中」。旧语义把
    ro.bootloader=engineering-x 直接压成 release、把整条 ro.build.description
    压成 release-keys —— 那比不改更像被动过。同名多条规则(description、每个指纹)
    现在链式串完只落一次盘。

  • 本机没有的分区报 absent:不计入「写入并复核」的不符项,也绝不凭空写出来。
    凭空多一个 ro.system_dlkm.build.type 本身就是破绽——stock 不会给不存在的分区留属性。
    固定表那 15 项保留「缺失就补」的原语义,没动。

  • 判定改成一次 getprop + 一次 awk 出计划,这是上面两条能落地的前提。
    两条看着最自然的方案都被真机读数否掉了:逐键读 43 行 = 1484ms;
    把 dump 读进 shell 变量再查表更糟——mksh 的字面匹配随字符串长度平方级增长
    (66897 字节上 15 次匹配实测 160307ms,先 grep -F 裁到 2.7KB 后 53 次仍要 1841ms)。
    还有一堵是 设备上 printf 不是内置:在 shell 里逐行拼数据,53 行 ≈ 850ms,
    比 awk 本体贵一个数量级。现在表原样交给 awk,shell 只执行计划:
    props_plan 942ms→208ms,同一张表 awk 路 485ms vs 逐键回落 1484ms。

修复

  • 属性页会把「本机没有这个分区」画成红色「不符」。界面那句状态分派只有
    ok / missing / 其余算不符,命令层新增的第四种状态 absent 掉进 else:行上标红、
    标题写「13 项不符」,而命令层同一刻报的是 mismatch=0。现在 absent 走中性
    (灰点 + 「本机无」+ 一句"这台机没有这项属性,模块不写它"),标题只数
    diff/missing。
    为什么对拍 216 项、真机 15 条都没抓到:缺陷不在命令层,而在"这一屏从没按真形状
    画过一次"——台架的 props 假数据是手写 5 行,其中 ro.product.brand、ro.serialno
    根本不在期望表里。已把假数据换成真机现量的 53 行(vendor_dlkm 那一族 4 行除外,
    这台机有那个分区、给不出 absent,只能在台架里补),并给行数/配色/标题三条断言
    各配一个变异。
  • device-ab --write 跑完没把运行期属性还原。它原来只快照伪装开关与 boot hash,
    表一补齐,props-apply 就会把分区属性掩上并留在那里——是它自己那条「差项数与跑前相同」
    的不变量报出来的(跑前 10、跑后 0)。现在逐个键快照 + 还原 + 逐字节比对。
    这是 2026-11-01 那次「台子把主人设备弄坏」的同类项,只是这回闸先响了。
  • 两侧读法对齐:Rust 的复核不再拿 resetprop 给失效的 getprop 兜底(留着就会出现
    「shell 读不到、二进制读得到」的假差异),对拍的桩也统一成一份存储
    (以前两个桩各拿一份来源,改完判定层后两侧决策不同,mismatch 差 16 项)。

闸

  • 新增 tests/props-plan.sh(30 条):表形状、四列与状态、坑值(值里有 ]、以 [ 开头、
    带空格、中文、空值)、链式改只落一次盘、absent 不进计划、awk 路与逐键回落路落到同一个
    属性区
    ;三处变异各自自证。
  • 新增 tests/device-props-mask.sh --write(真机 17 条,按现跑的 ok 行数):台子自己种出待改状态再测
    (不能指望现场正好在泄漏,否则"改对了"这条正对照其实是"没测到"),臂 A 实测改写
    11 个属性、shell 与二进制两臂从同一起点跑完 43 个键逐字节相同、属性区无新增键、
    applied=1 / mismatch=0、
    toybox awk 与 busybox awk 结果逐字节相同(本轮在 mawk 上撞到一个方言差:
    赋值语句里的 (k in goal) 会被 mawk 提前创建元素,链式改写整条变成写空值,gawk 却不这样)、
    跑完还原到原始现场。不加 --write 直接 skip,不在 CI 上改现场。
  • 对拍 205 → 216 项:两侧写完的属性区逐字节相同(状态 A/B,不只比 stdout)、
    链式改只落一次盘、absent 不凭空创建、getprop 全废的降级三条。
  • 新增 tests/device-boot-hash.sh(真机 20 条,全程只读):夹具里只放真实
    config.toml、不放 boot_hash 文件,断言 auto 算出的 effective 与参照值逐字节相同
    (现场是手动档取手抄那串;默认的 auto 档没有文件,取已在 prop 里生效的那串 —— 台子按
    档位选,不把"没测"报成失败);另有 4 条钉 TEESim 那一支(夹具里放诱饵 override,
    必须采用属性里那串而不是诱饵);random/off/invalid/无后端四种形状各自熄火;
    跑完核对现场档位没变。
  • tests/props-boot-hash.sh:20 → 52 条(上一版文档写的「22 条」早就过期了,本轮按
    现跑的 ok 行数改口):三态 + 派生 + random/注释行/不带引号/不缓存 + 变异(摘掉「只认 hex」
    的守卫 ⇒ random 被当成值)。对拍 216 → 239 项(启动哈希三态 23 条)。
    mutate-verify.mjs 从 5 个变异加到 6 个(第 ⑥ 个把界面 auto 档说回「未设置」,必须响)。
  • 修掉一处读数口径分叉(真机现场切 auto 时抓出来的):props-boot-hash 的 current=
    原先两侧都用 resetprop 读,而生产的 shell 侧 prop_now(module/webui.sh)用的是
    getprop。裸 shell 里 resetprop 不在 PATH,命令于是报 current=(空),属性其实有值。
    对拍当时抓不到,因为夹具 pb.sh/pba.sh 自己手抄了一份 resetprop 版 prop_now ——
    台子和被测物共用同一把坏仪器。现在夹具改成从 module/webui.sh 抽那一份,current= 两侧
    统一走 getprop(写完的复核仍走 resetprop,两边同),并补 RP_READ_DEAD=1 这条控制与
    「那种情况下 current= 必须等于真值」的绝对判据:只加"两侧相同"不够,那版两侧一起报空照样过。
    两个变异各自自证(夹具退回手抄版 → 两侧相同那条响;Rust 退回 resetprop → 绝对判据那条响)。
    对拍 239 → 242 项(下限同步抬)。
  • TEESim 那一侧的 auto 不再覆盖守护进程(读上游源码查出,docs/HANDOFF.md §5.15 记了行号):
    TEESim 每次 push 都把 ro.boot.vbmeta.digest 强制成它自己正在证明的值
    (App.kt:510-523「the value we attest wins unconditionally」,含 bootloader 根本不填这格的
    机器,上游 #228),而 overrides.json 里那串 hex 只有当它当前是合成兜底值时才被采纳
    (Harvester.kt:183-189 + applyUserOverrides:399-415 对不可编辑字段直接 continue)。
    所以 auto 在 teesim 下改成:属性已有合法值 → 采用它(source=daemon,一个字都不写);
    属性为空 → 才按 override 补
    (老版本 TEESim 没有那条强制)。拿 override 去覆盖已有值,
    恰好会在上游不采纳时亲手造出 vbmetaDigestMismatch(与空值同级的 Danger)。
    OMK 那一家相反:vb_hash 是显式 hex 时上游不写这条 prop(vbmeta.rs:452-462 的
    needs_sysprop_write 不含 ExplicitHex/Property),所以 omk 分支保持"照配置写、可覆盖"。
    对拍 242 → 247 项(新增 5 条:属性空时两侧逐字节 + 取 override、计数器自证「该写时数得到 1」、
    属性已有值时两侧逐字节 + daemon/那串 + 写次数 0 + 跑完 store 仍是守护进程那串);
    单元 52 → 64 条(新增 source=daemon 一支与变异「摘掉那一行 ⇒ override 覆盖」);
    界面多认一个来源(?bh=daemon 画出来是「TEE Simulator 正在证明的值(属性里已有,模块不覆盖)」),
    verify.mjs 加 1 条断言、mutate-verify.mjs 6 → 7 个变异(⑦ 把 daemon 说回 override;
    实测 7 个全响,还原后重新构建复跑退码 0)。
  • 顺带查清并写下:TEESim 的多 profile 与这一格无关 —— 一份 profile 装的是设备身份 /
    keybox / mode / 三档补丁级别 / apps[] / autoIncludeNewApps(ConfigStore.kt:23-46),
    里面没有 root of trust;证明里的 verifiedBootHash 在 profile 循环之外一次性写进
    bootInfo(Resolver.kt:41-43),而 overrides.json 上游明写是扁平一张表
    (OverrideStore.kt:6-9)⇒ 切 profile 不会让 digest 过期,这一格不需要"选哪一份 profile"。
    模块的多 profile 支持本来就有:teesim-profiles 列全部(名字/keybox/mode/apps/是否默认)、
    teesim-profile-new 复制 default、teesim-profile-del 删(default 是兜底不许删)、
    写路径按 payload 里的 profile=<名字> 决定"改哪一份"(省略仍是 default,旧界面行为不变),
    界面先选份再编辑。
  • 新增 tools/wsl-parity.sh:在 WSL 侧跑对拍并把结论和自己的退码写在同一行
    (parity exit=0|全部通过(chk 用例 242 项))。理由是本坑:从 Git Bash 内联
    wsl bash -c '…; echo $?' 里的 $? 会被本地 shell 提前展开,量到的永远是 0 —— 本轮
    差点据此误报"对拍的退码逻辑坏了、CI 那道闸形同虚设"。这条 runner 自带反面对照
    (指一个不存在的二进制 → parity exit=1),也顺带被对拍的陈旧守卫救了一次。
  • 台子覆盖:真机 A/B 38 条全过,shell 测试套 53 套在 WSL(mawk) 与 Git Bash(gawk) 两边全绿。
  • 界面这一层:tools/md3e-build/verify.mjs 加 4 条属性页断言(53 行逐行上屏、absent 画
    「本机无」、标题只数 9 项、absent 不挂 warn 色),mutate-verify.mjs 从 2 个变异加到
    5 个且全部会响;尾部截图 new-props-tail.png 进台架产物。