Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
948d07d
fix(kpm): validate ELF section bounds and guard module query callbacks
Zhanfg Oct 13, 2026
01223df
feat(runtime): define shared versioned management ABI for both backends
Zhanfg Oct 13, 2026
e6acf62
test(runtime): assert stable v1 ABI layout and backend capability sem…
Zhanfg Oct 13, 2026
35e756f
docs(runtime): specify dual-backend architecture, safety invariants a…
Zhanfg Oct 13, 2026
94362b3
ci(runtime): add fast host ABI smoke checks for dual-backend protocol
Zhanfg Oct 13, 2026
d1aa176
feat(runtime): assign native status ioctl for ABI negotiation
Zhanfg Oct 13, 2026
4588ec0
feat(runtime): add kernel-native status-only adapter (Kconfig)
Zhanfg Oct 13, 2026
fee5418
feat(runtime): add kernel-native status-only adapter (Makefile)
Zhanfg Oct 13, 2026
f608102
feat(runtime): add kernel-native status-only adapter (patchnest_nativ…
Zhanfg Oct 13, 2026
13de7ac
fix(native): include kernel module and memory interfaces explicitly
Zhanfg Oct 13, 2026
2c0ebcc
fix(kpm): reject missing init before recording a load transaction
Zhanfg Oct 13, 2026
1f6f565
docs(runtime): record status-only native port and truthful capability…
Zhanfg Oct 13, 2026
8f9b043
fix(kpm): serialize publication and reject unsafe concurrent unloads
Zhanfg Oct 13, 2026
7825d5d
fix(kpm): record in-flight module callbacks for unload guard
Zhanfg Oct 13, 2026
50f3d75
fix(kpm): pin control/event callbacks and protect module list with sp…
Zhanfg Oct 13, 2026
f131d01
feat(kpm): add reusable lock-protected callback lifetime primitive
Zhanfg Oct 13, 2026
512613b
fix(kpm): validate ELF before parsing, bound metadata and close file …
Zhanfg Oct 13, 2026
c6b11f0
refactor(kpm): store shared lifetime primitive per module
Zhanfg Oct 13, 2026
b1cca86
refactor(kpm): use single verified reference-lifetime primitive
Zhanfg Oct 13, 2026
8f87461
test(kpm): exercise real lifetime primitive with concurrent pinned ca…
Zhanfg Oct 13, 2026
0ddf90d
ci(kpm): run GCC/Clang pthread concurrency test on actual lifetime pr…
Zhanfg Oct 13, 2026
a714889
feat(runtime): add read-only native backend status probe CLI
Zhanfg Oct 13, 2026
fb55a64
fix(runtime): use POSIX feature macros for portable status probe
Zhanfg Oct 13, 2026
7509cb2
ci(runtime): build native status client and verify safe no-device beh…
Zhanfg Oct 13, 2026
f49f277
fix(kpm): restore error exit label after bounded file loader refactor
Zhanfg Oct 13, 2026
f15cf38
fix(kpm): cap ELF memory layout and verify symbol and relocation bounds
Zhanfg Oct 13, 2026
a41bae8
fix(kpm): validate linked string-table bounds before dereference
Zhanfg Oct 13, 2026
1a93bae
fix(kpm): enforce architecture relocation write width within target s…
Zhanfg Oct 13, 2026
e1dcefd
docs(runtime): record protected callbacks, ELF bounds and remaining h…
Zhanfg Oct 13, 2026
cbaf84c
feat(native): add idempotent opt-in Linux Kbuild integration script
Zhanfg Oct 14, 2026
9e2c63a
test(native): stage driver twice and reject conflicting existing kern…
Zhanfg Oct 14, 2026
3db3f5b
ci(native): verify idempotent kernel-tree installation and conflict g…
Zhanfg Oct 14, 2026
f278faf
fix(native): reject ABI/Kbuild conflicts before modifying target kern…
Zhanfg Oct 14, 2026
256349e
test(native): reject mismatched ABI headers before touching kernel tree
Zhanfg Oct 14, 2026
71885de
docs(native): document fail-closed idempotent Kbuild staging and acce…
Zhanfg Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/patchnest-abi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: PatchNest dual-backend ABI smoke

on:
push:
branches:
- 'refactor/dual-backend-runtime'
pull_request:
paths:
- 'include/patchnest/**'
- 'tests/patchnest_abi_smoke.c'
- 'tests/kpm_lifetime_concurrency.c'
- 'kernel/patch/include/kpm_lifetime.h'
- 'kernel/patch/include/module.h'
- 'kernel/patch/module/module.c'
- 'tools/patchnest_status.c'
- 'ports/linux-native/**'
- 'tests/patchnest_native_integration.sh'
- '.github/workflows/patchnest-abi.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
abi-smoke:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
- name: Compile and test ABI contract with GCC and Clang
shell: bash
run: |
set -euo pipefail
for cc in gcc clang; do
"$cc" -std=c11 -O2 -Wall -Wextra -Werror -pedantic \
tests/patchnest_abi_smoke.c -o /tmp/patchnest-abi-"$cc"
/tmp/patchnest-abi-"$cc"
done
- name: Exercise shared KPM lifetime primitive with concurrent callbacks
shell: bash
run: |
set -euo pipefail
for cc in gcc clang; do
"$cc" -std=c11 -O1 -g -pthread -Wall -Wextra -Werror \
-fsanitize=undefined -fno-sanitize-recover=all \
tests/kpm_lifetime_concurrency.c -o /tmp/patchnest-lifetime-"$cc"
/tmp/patchnest-lifetime-"$cc"
done
- name: Verify non-destructive in-tree Kbuild integration
shell: bash
run: |
set -euo pipefail
dash -n ports/linux-native/integrate.sh
dash tests/patchnest_native_integration.sh
bash tests/patchnest_native_integration.sh
- name: Compile status-only native client and verify missing-device behavior
shell: bash
run: |
set -euo pipefail
for cc in gcc clang; do
"$cc" -std=c11 -O2 -Wall -Wextra -Werror \
tools/patchnest_status.c -o /tmp/patchnest-status-"$cc"
rc=0
/tmp/patchnest-status-"$cc" >/tmp/pn-output 2>/tmp/pn-error || rc=$?
test "$rc" -eq 4
test ! -s /tmp/pn-output
done
115 changes: 115 additions & 0 deletions docs/PATCHNEST_DUAL_BACKEND.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# PatchNest Core:双后端底层重构(RFC v1)

状态:**研发中 / 不可刷入 / 未完成真机验收**
基线:`Zhanfg/KernelPatch-Public@0bdf0094`
工作分支:`refactor/dual-backend-runtime`
核心独立仓:`Zhanfg/KernelPatch-Public`
管理端仓:`Zhanfg/PatchNest`(`cli/` + `module/` + `kpms/`)

## 1. 必须实现的两个版本

| 项目 | A:Kernel Integrated | B:Image Patched |
|---|---|---|
| 核心来源 | PatchNest Core | 同一 PatchNest Core |
| 安装过程 | 将内核源码实现以 `CONFIG_PATCHNEST_CORE=y` 编译进内核,AK3 只分发该内核 | `kptools` 对受支持的内核镜像进行离线修补,把 freestanding runtime 的 `kpimg` 注入镜像 |
| 内核编译要求 | 必须有完整可编译源码与适配补丁 | 不要求重新编译目标内核,但需要兼容的镜像结构、ARM64 指令/KCFI 约束及符号解析 |
| 运行时承载 | 内核内建组件;不依赖 WebUI 或用户态守护进程存活 | 注入后的镜像内 runtime;不依赖 WebUI 或用户态守护进程存活 |
| KPM 管理 | 轻量 CLI / WebUI 调控制接口 | 同一 CLI / WebUI,动态选择 patched adapter |
| 更新内核 | 重新编译并打包 AK3 | 对新基线重测、重新修补 |
| 无法兼容 | 构建失败,不得伪装成成功 | 直接拒绝修补,不得强行写入 |
| 与其他 root 系统 | 显式能力协商,避免双重 su/hook | 显式能力协商,避免双重 su/hook |

**澄清**:AK3 是镜像打包与安装工具,不能替代内核编译阶段的链接。把现有 `kpimg` 复制进 AK3 ZIP 不等于方案 A。

## 2. 分层设计

```text
PatchNest WebUI / CLI <- 非常薄的控制面,无需常驻
|
protocol v1 <- include/patchnest/runtime_abi.h
|
authenticated adapter
/ \
kernel-native image-patched <- 后端特有初始化 / memory / hooks
\ /
KPM runtime <- ELF/relocations, lifecycle, events, safety
|
loaded KPMs
```

管理端先查询 `STATUS`,验证 ABI 主版本、`backend` 和真实能力位,**绝不通过「检测是否 root」推断内核支持 KPM**。后续管理操作:LIST、LOAD、UNLOAD、EVENT、READ_AUDIT。v1 禁止未定义 `flags`,请求必须校验大小上限、操作权限和字段边界。

### 核心内部接口(迁移目标)

1. `pn_elf_validate()`:严格长度/节区/符号/重定位校验,验证在任何解引用之前执行。
2. `pn_module_prepare()`:核验元数据与签名、准备内存权限、resolve 符号,但不发布模块。
3. `pn_module_activate()`:受控 init,成功后才进入公开模块表。
4. `pn_module_deactivate()`:先标记卸载并阻止新引用,等待进行中的控制/事件回调退出,再执行 exit、回收代码和数据。
5. `pn_event_dispatch()`:可拒绝已经卸载/隔离的模块;回调不持有不允许睡眠的锁。
6. `pn_safety_boot_guard()`:崩溃计数、安全模式、最后一次加载事务持久化;嵌入模块必须可隔离。
7. `pn_audit()`:记录启动来源、验证结果、模块生命周期与失败原因,必要时脱敏。

本阶段先修复现有 loader 的独立可确认错误;**上述新函数名是目标接口,不代表都已实现**。

### Kernel Integrated(A)的实现路径

- 引入 `kernel/patchnest/Kconfig` 与 `kernel/patchnest/Makefile`,支持 arm64 的 `bool` 型 built-in 构建;**不把当前 freestanding `kernel/base/*.S` 直接作为内核 `obj-y`**。它们预设了不同的链接、地址映射、符号解析与初始化时机。
- 创建 Linux native port:使用内核分配器、workqueue/mutex/RCU、内核已支持的安全内存权限与适用的 symbol API;必要的 hook 技术要按 GKI/CFI 版本分级,不允许全局关掉 KCFI 来凑兼容。
- 将 KPM 装载器从 root/hiding 逻辑里分离:KPM 能力默认独立于 SU;复用现有 ReSukiSU / KernelSU 时不得安装第二个 superuser/hook 栈。
- 模块嵌入采用 **构建期只读清单**:每个 KPM 有名称、大小、哈希、签名、加载阶段和隔离策略。嵌入更新需要重新编译或提供受信的后续部署路径;WebUI 不得声称能「运行时改写已编译内核中的只读 KPM」。
- 给 AK3 生成设备/构建身份清单,校验对应的 `Image`、DTB/DTBO、AVB、槽位和内核 ABI。出现不匹配直接停止安装。

### Image Patched(B)的实现路径

- 保持原有 `kpimg`、`kptools` 为兼容基础;在既有能力上引入 `patched adapter`,而非立即重写所有 ARM64 重定位/指令注入逻辑。
- 支持离线预检、原镜像备份、原子生成目标镜像、写入后比对、可回滚恢复。拒绝未知 ELF/boot header/KCFI 组合。
- 加载 KPM 时调用相同的安全策略和生命周期算法;静态嵌入与在线加载共享同一判定。
- 独立修补版应输出镜像或待刷入 ZIP;不直接对用户当前 boot 分区做隐式写入。

## 3. 统一安全规则

- 默认不启用未知的 KPM,嵌入包的信任链必须在安装前验证;签名策略不能仅依赖 WebUI。
- 所有修改操作都需真实调用者授权检查;**已授权普通 UID 不等于拥有全局策略/授予其他 UID 的权限**。
- 严禁在卸载 KPM 时释放仍被回调执行的代码;正确的引用保护/睡眠同步完成前,动态卸载不可被标记为稳定。
- 内核安全模式能禁止所有第三方 KPM,不得与系统其他 root 管理器相互打架。
- 不允许把 `.ko` 当作 `.kpm` 无条件兼容:必须分别验证重定位、导出符号、KCFI、许可证和生命周期。
- 不允许修补 `init_boot` 与 `boot` 的规则互换;A/B slots、AVB 与设备差异需单独处理。
- 运行时 idle 路径必须事件驱动,不增设每秒轮询的常驻用户态 daemon。

## 4. 阻塞与真实验收

已有真实负面测试:OnePlus 6 的 BakaSU v0.2 支持加载/嵌入指定 KPM,而 v0.3 加载/嵌入后会重启或掉入 recovery。**这不能简单归因于 PatchNest,也不能跳过回归测试。**

现有待修 P0: #6(授权升级)、#7(superkey 长度)、#8(列表信息泄漏)、#9(TOCTOU)、#10(KPM UAF)、#11(cred/ seccomp)、#12(回调 NULL)、#13(ELF)、#14(启动计数)、#15(RCU)。这些应在正式刷机前全部审计闭环。

验收门槛:

1. 代码级单元测试 + 负样本测试、UBSAN/KASAN(支持的测试内核)、静态检查。
2. A:至少一种实际 arm64 内核 Kbuild 编译和 AK3 打包成功,验证 KPM 加载/卸载、嵌入与重启。
3. B:原始镜像→修补→启动→KPM→重启→卸载/撤回→恢复原镜像,验证 A/B 两个槽位。
4. 两种后端测试一致的 ABI、错误码、安全模式和事件生命周期。
5. 真机分别执行正常场景、空指针/畸形 ELF、并发 unload/control、失败注入和已知 OP6 v0.3 回归。

## 5. 当前分支完成范围

- 已建立双后端的 **ABI v1 数据结构/能力标识**;新增 `ports/linux-native/` 的 Kconfig/Kbuild 开发入口与 `/dev/patchnest` **只读状态握手**。该入口固定报告 `NOT_READY`、`capabilities=0`,不代表已支持 KPM 加载,也尚未接入目标内核编译。
- 已实现 KPM 回调引用保护的第一阶段:列表自旋锁、单实例加载预留、控制回调独立参数、最多 64 个模块的事件快照,以及忙时 `-EBUSY` 卸载保护。共用引用计数基础逻辑使用 GCC/Clang + pthread 进行并发测试。
- 已扩展 ELF 防御:大小、总内存映射预算、节对齐、符号表字符串终止、RELA 目标及索引/写入宽度边界。尚需变异样本 fuzz 与完整重定位语义审计。
- 已添加 `tools/patchnest_status.c`(native `ioctl` 只读状态客户端),管理端 `Zhanfg/PatchNest` 的 Draft PR #12 提供只读运行时探测和展示。
- **重要限制**:现有 callback pin 只覆盖 `control0/control1/event`,未覆盖 KPM 注册的所有外部 inline hook、异步工作任务与其他任意函数入口。因此动态卸载的代码存活期仍不能宣称彻底安全,Issue #10 仍是 P0 阻塞项。
- 后续必须补齐:KPM hook/工作任务全链路注销与同步、Native KPM loader、Patched adapter、统一认证 API、真正的 Kbuild/AK3 编译和真机验证。
- **禁止发布可刷入稳定包或自动合并 `main`,直到这些条件完成。**

### A 版 Linux 内核源码接入(本轮新增)

`ports/linux-native/integrate.sh` 可以将 status-only native adapter 安装到一个完整 Linux ARM64 内核源码树。脚本默认 **dry run**,且在任何复制发生前执行既有实现冲突检查;需显式设置 `PATCHNEST_KERNEL_SOURCE` 与 `PATCHNEST_APPLY=1` 才会修改目标源码。可重复运行,不会重复附加 Kconfig / Makefile 条目。脚本在交互终端也支持直接 `sh` 无参数执行。

集成后,`CONFIG_PATCHNEST_CORE` 默认为 `n`。可显式改为 `y` 做目标内核编译验证;当前代码仍只提供 `/dev/patchnest` 状态握手,**绝不应该宣称可加载或嵌入 KPM**。内核源码树的构建、OnePlus 6/13 对应 AK3 打包、实机稳定性尚未完成。

CI 的 `tests/patchnest_native_integration.sh` 覆盖 dry-run 无副作用、重复接入幂等性、原始文件保持不变以及 ABI 和 driver 冲突拒绝。其通过不等于真正 Kbuild/GKI 链接完成。

## 6. 版本与仓库治理

- Core 改动仅在本仓库 feature 分支;WebUI/CLI 改动将进入 `Zhanfg/PatchNest` 的对应 feature 分支。
- 不物理迁仓、不重命名、不改变默认分支、不删除历史 Release。
- 功能分支通过 Draft PR 审查;稳定版等真正 device gate 通过再考虑合并。
100 changes: 100 additions & 0 deletions include/patchnest/runtime_abi.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* PatchNest dual-backend management ABI, version 1.
*
* This is a wire contract shared by an in-tree kernel implementation,
* an image-patched kpimg implementation, and their management clients.
* Declaring the ABI does NOT mean an implementation is already connected.
*
* Never pass raw user pointers, kernel addresses, or function pointers
* through this structure. Every implementation must authenticate each
* mutating operation, validate size/version, and copy bounded payloads.
*/
#ifndef PATCHNEST_RUNTIME_ABI_H
#define PATCHNEST_RUNTIME_ABI_H

#ifdef __KERNEL__
#include <linux/types.h>
typedef __u32 pn_u32;
typedef __u64 pn_u64;
#else
#include <stdint.h>
typedef uint32_t pn_u32;
typedef uint64_t pn_u64;
#endif

#define PATCHNEST_ABI_MAJOR 1u
#define PATCHNEST_ABI_MINOR 0u
#define PATCHNEST_ABI_VERSION ((PATCHNEST_ABI_MAJOR << 16) | PATCHNEST_ABI_MINOR)
#define PATCHNEST_KPM_NAME_MAX 64u

/* Backends are mutually exclusive for an active kernel session. */
enum patchnest_backend {
PATCHNEST_BACKEND_NONE = 0,
PATCHNEST_BACKEND_BUILTIN = 1, /* compiled into kernel, distributed in AK3 */
PATCHNEST_BACKEND_PATCHED = 2, /* freestanding kpimg, offline image patch */
};

/* Advertised capabilities, never infer support from backend type alone. */
#define PATCHNEST_CAP_KPM_LIST (1u << 0)
#define PATCHNEST_CAP_KPM_LOAD (1u << 1)
#define PATCHNEST_CAP_KPM_UNLOAD (1u << 2)
#define PATCHNEST_CAP_KPM_EVENT (1u << 3)
#define PATCHNEST_CAP_EMBEDDED (1u << 4)
#define PATCHNEST_CAP_SAFE_MODE (1u << 5)
#define PATCHNEST_CAP_AUDIT_LOG (1u << 6)

enum patchnest_safety_state {
PATCHNEST_SAFETY_NOT_READY = 0,
PATCHNEST_SAFETY_READY = 1,
PATCHNEST_SAFETY_QUARANTINED = 2,
PATCHNEST_SAFETY_SAFE_MODE = 3
};

/*
* First-stage readonly handshake. Client initializes struct_size to
* sizeof(struct patchnest_status_v1). Providers must reject smaller
* structures and zero fields they do not support. Integers are native
* endian, little-endian on supported Android arm64 targets.
*/
struct patchnest_status_v1 {
pn_u32 struct_size;
pn_u32 abi_version;
pn_u32 backend;
pn_u32 capabilities;
pn_u32 safety_state;
pn_u32 loaded_kpm_count;
pn_u64 boot_session_id;
};

/*
* Kernel-native transport: STATUS is read/write so the caller supplies
* struct_size and abi_version for validation before any data is copied out.
* The patched backend is free to use the legacy supercall transport while
* preserving the same payload layout.
*/
#include <linux/ioctl.h>
#define PATCHNEST_IOCTL_STATUS _IOWR('P', 0x01, struct patchnest_status_v1)

enum patchnest_operation {
PATCHNEST_OP_STATUS = 1,
PATCHNEST_OP_LIST_KPM = 2,
PATCHNEST_OP_LOAD_KPM = 3,
PATCHNEST_OP_UNLOAD_KPM = 4,
PATCHNEST_OP_DISPATCH_EVENT = 5,
PATCHNEST_OP_READ_AUDIT = 6,
};

/*
* All requests have this prefix. size includes the complete payload.
* No operation may bypass caller authentication or safe-mode gating.
* Runtime implementations set strict maximum payload lengths per op.
*/
struct patchnest_request_v1 {
pn_u32 size;
pn_u32 abi_version;
pn_u32 operation;
pn_u32 flags; /* MUST be zero in v1 */
};

#endif /* PATCHNEST_RUNTIME_ABI_H */
38 changes: 38 additions & 0 deletions kernel/patch/include/kpm_lifetime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef PATCHNEST_KPM_LIFETIME_H
#define PATCHNEST_KPM_LIFETIME_H

/*
* The caller MUST hold the registry lock for every operation below.
* Callbacks themselves execute without the registry lock.
*
* The unload operation is nonblocking: it returns -EBUSY if the module
* has active callback holders. No callback is allowed to execute after the
* module is detached from the registry.
*/
struct pn_kpm_lifetime {
unsigned int callback_users;
};

static inline int pn_kpm_lifetime_get(struct pn_kpm_lifetime *state)
{
if (!state || state->callback_users == ~0u)
return 0;
++state->callback_users;
return 1;
}

static inline int pn_kpm_lifetime_put(struct pn_kpm_lifetime *state)
{
if (!state || !state->callback_users)
return 0;
--state->callback_users;
return 1;
}

static inline int pn_kpm_lifetime_may_unload(const struct pn_kpm_lifetime *state)
{
return state && state->callback_users == 0;
}

#endif /* PATCHNEST_KPM_LIFETIME_H */
7 changes: 7 additions & 0 deletions kernel/patch/include/module.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <asm-generic/module.h>
#include <kpmodule.h>
#include <kpm_lifetime.h>
#include <uapi/kpm_event.h>

struct load_info
Expand Down Expand Up @@ -50,6 +51,12 @@ struct module

void *start;

/*
* Protected by module_lock in patch/module/module.c. Unload refuses
* with -EBUSY until all in-flight event/control callbacks finish.
* This field is NOT an atomic/free-standing reference count.
*/
struct pn_kpm_lifetime lifetime;
struct list_head list;
};

Expand Down
Loading
Loading