第一章:Go安全基建红线清单的演进与定位
Go语言自1.0发布以来,其安全基建并非一蹴而就,而是随生态成熟度、漏洞披露频率及企业实践反馈持续演进。早期(2012–2016)聚焦基础内存安全与goroutine隔离,依赖编译器静态检查与go vet;中期(2017–2020)因Log4j类供应链风险凸显,社区开始系统化定义“不可绕过”的安全约束,如禁止unsafe包在生产构建中启用、强制校验模块校验和(go.sum完整性验证);当前阶段(2021至今),红线清单已升级为可策略化执行的工程规范,嵌入CI/CD流水线,并与SAST工具链(如gosec、staticcheck --checks=all)深度协同。
核心红线维度
- 构建可信性:所有生产构建必须启用
-buildmode=pie(位置无关可执行文件),并禁用-ldflags="-s -w"以外的符号剥离选项,确保调试信息可控; - 依赖可控性:通过
go mod verify校验模块完整性,失败时阻断构建:# 在CI脚本中强制校验 if ! go mod verify; then echo "❌ Module checksum mismatch — aborting build" exit 1 fi - 运行时防护:默认启用
GODEBUG=asyncpreemptoff=0(保障抢占式调度),禁用GODEBUG=madvdontneed=1(避免内存页过早释放引发侧信道风险)。
红线清单的工程定位
| 层级 | 作用域 | 执行主体 | 不可妥协性 |
|---|---|---|---|
| 编译层 | go build参数 |
CI构建节点 | 强制拦截 |
| 模块层 | go.sum校验 |
开发者提交前 | 预提交钩子 |
| 运行层 | GODEBUG环境变量 |
容器启动脚本 | Kubernetes InitContainer |
该清单不是最佳实践集合,而是Go服务上线前必须满足的安全基线契约——任何绕过均需跨职能安全委员会书面豁免,并同步更新威胁建模文档。
第二章:CVE-2023-45852等7类高危漏洞的Go语义层建模
2.1 Go内存模型与UAF/CVE-2023-45852的静态可判定性分析
Go内存模型以顺序一致性(SC)的弱化变体为基础,依赖sync原语与channel通信建立happens-before关系。CVE-2023-45852本质是runtime.mapassign中未同步的桶指针重用导致的Use-After-Free(UAF),其触发需同时满足:
- 并发写入同一map键
- GC在map扩容期间回收旧桶内存
- 旧桶指针被新goroutine误读
数据同步机制
Go编译器对map操作不插入自动内存屏障,依赖程序员显式同步:
// ❌ 危险:无同步的并发map写入
var m = make(map[string]int)
go func() { m["a"] = 1 }() // 可能触发CVE-2023-45852
go func() { m["b"] = 2 }()
逻辑分析:
m["a"] = 1调用mapassign()时若触发扩容,旧bucket内存可能被GC立即回收;另一goroutine若此时读取该bucket地址(如通过反射或竞态指针逃逸),即构成UAF。参数m为非线程安全的哈希表实例,无互斥保护。
静态可判定性边界
| 分析维度 | 是否可静态判定 | 依据 |
|---|---|---|
| map并发写冲突 | ✅ 是 | SSA IR中可识别共享map变量+无sync.Mutex引用 |
| GC时机 | ❌ 否 | 运行时堆状态不可静态建模 |
| 桶指针逃逸路径 | ✅ 是 | go vet -race可检测指针跨goroutine传递 |
graph TD
A[源码:并发map赋值] --> B{SSA构建内存访问图}
B --> C[识别无锁共享map变量]
C --> D[标记潜在UAF边]
D --> E[结合逃逸分析验证指针可达性]
2.2 net/http与crypto/tls模块中TLS降级漏洞(CVE-2023-48795)的AST遍历检测实践
CVE-2023-48795 源于 crypto/tls 中 Config.NextProtos 未校验空值导致 ALPN 协商被绕过,进而诱发 TLS 1.3 降级至不安全版本。
AST检测关键路径
- 定位
&tls.Config{...}字面量节点 - 检查
NextProtos字段是否为nil或空切片 - 追踪
http.Server.TLSConfig赋值链
核心检测代码片段
// 检查 tls.Config 字面量中 NextProtos 是否缺失或为空
if nextProtosField := findStructField(configLit, "NextProtos"); nextProtosField != nil {
if isEmptySlice(nextProtosField.Expr) { // true → 高风险
reportVuln(node, "CVE-2023-48795: NextProtos unset/empty")
}
}
isEmptySlice() 判断表达式是否为 []string{} 或 nil;findStructField() 基于 ast.CompositeLit 递归匹配字段名,确保覆盖嵌套赋值场景。
常见误报规避策略
- 排除
NextProtos: []string{"h2", "http/1.1"}等显式安全配置 - 忽略测试文件中
//nolint:govulncheck注释标记
| 检测项 | 安全值 | 危险值 |
|---|---|---|
NextProtos |
[]string{"h2"} |
nil, []string{} |
graph TD
A[Parse Go AST] --> B{Is *tls.Config literal?}
B -->|Yes| C[Find NextProtos field]
C --> D{IsEmptySlice?}
D -->|Yes| E[Report CVE-2023-48795]
D -->|No| F[Skip]
2.3 go:embed与unsafe包滥用导致的任意文件读取(CVE-2023-46805)字节码级识别方案
该漏洞本质是攻击者通过构造恶意 go:embed 路径 + unsafe 指针越界读取,绕过编译期校验,在运行时触发 .rodata 段任意偏移读取。
漏洞触发核心模式
// embed 静态路径被动态拼接(违反 go:embed 语义)
//go:embed "config.json"
var configFS embed.FS
func readArbitrary(p unsafe.Pointer, n int) []byte {
return (*[1 << 30]byte)(p)[:n] // unsafe.Slice 替代方案更隐蔽
}
逻辑分析:
(*[1<<30]byte)(p)强制类型转换绕过边界检查;p若源自reflect.Value.UnsafeAddr()或runtime.StringHeader伪造,可指向任意内存页(如.rodata中嵌入文件表),n控制读取长度——实现任意长度文件内容泄露。
字节码特征指纹
| 特征位置 | 检测模式 |
|---|---|
GOEXPERIMENT |
含 fieldtrack 或 embedcfg 标志 |
.rodata 引用 |
embed.FS 实例字段偏移异常 > 0x1000 |
CALL 指令 |
紧邻 UNSAFESTRING / UNSAFESLICE |
graph TD
A[解析ELF/PE头] --> B[定位.rodata节]
B --> C{扫描embed.FS vtable引用}
C -->|偏移>0x1000| D[标记高危函数]
C -->|正常偏移| E[跳过]
D --> F[反汇编调用链]
F --> G[检测unsafe.Slice/Pointer链式调用]
2.4 Go module proxy投毒链路中go.sum校验绕过的动态污点追踪实现
污点源识别与传播锚点
Go module proxy投毒常利用go get未显式指定-insecure时对HTTP代理的隐式信任,将恶意模块注入依赖图。关键污点源为GOPROXY环境变量或go.mod中replace/replace指令指向的非官方源。
动态污点建模核心逻辑
// 构建污点传播图:从proxy响应体→module zip解压路径→go.sum写入点
func trackSumBypass(proxyResp *http.Response, modPath string) bool {
body := io.TeeReader(proxyResp.Body, &taintWriter{modPath}) // 污点注入点
archive, _ := zip.NewReader(body, proxyResp.ContentLength)
for _, f := range archive.File {
if strings.HasSuffix(f.Name, "go.sum") {
return isTainted(f) // 检查sum文件是否含污染哈希(如篡改前缀)
}
}
return false
}
taintWriter在流式读取proxy响应体时同步标记所有经HTTP返回的*.zip内容为“不可信源”;isTainted()通过比对哈希前缀(如h1:后首8字节是否匹配已知投毒指纹)实现轻量级动态判定。
关键绕过模式对比
| 绕过方式 | 触发条件 | go.sum校验失效点 |
|---|---|---|
| HTTP proxy重定向 | GOPROXY=http://evil.io | go sumdb跳过校验 |
| ZIP内嵌伪造go.sum | 恶意模块zip含预生成sum文件 | go mod download不校验 |
| 本地replace覆盖 | replace example.com => ./local |
go.sum记录被跳过更新 |
graph TD
A[go get github.com/A/B] --> B[GOPROXY=proxy.example.com]
B --> C[HTTP GET /B/@v/v1.0.0.zip]
C --> D[ZIP解压并提取go.sum]
D --> E{go.sum哈希是否匹配sum.golang.org?}
E -->|否| F[触发go.sum写入污染哈希]
E -->|是| G[正常校验通过]
2.5 context.WithCancel泄漏引发DoS(CVE-2023-39325)的goroutine生命周期图谱建模
核心泄漏模式
当 context.WithCancel 创建的 cancelFunc 未被调用,且其父 context 长期存活时,子 goroutine 将持续监听已失效的 ctx.Done() channel,形成不可回收的阻塞等待。
典型泄漏代码片段
func leakyHandler(ctx context.Context) {
childCtx, cancel := context.WithCancel(ctx)
go func() {
defer cancel() // ❌ 永不执行:cancel 被遗忘或 panic 跳过
select {
case <-childCtx.Done():
return // 正常退出
}
}()
// 忘记调用 cancel() → childCtx 持有对父 ctx 的强引用,goroutine 永驻
}
逻辑分析:
childCtx内部维护parentCancelCtx引用链;cancelFunc不调用 →parentCancelCtx.childrenmap 中条目永不清理 → GC 无法回收该 goroutine 及其栈帧。参数ctx若为context.Background()或长生命周期 server ctx,泄漏呈指数级累积。
生命周期状态迁移表
| 状态 | 触发条件 | 是否可回收 |
|---|---|---|
spawned |
go func(){...} 启动 |
否 |
blocked-on-Done |
select{<-ctx.Done()} |
否(引用链活跃) |
cancelled |
cancel() 显式调用 |
是(下个GC周期) |
goroutine 图谱建模(mermaid)
graph TD
A[Parent Context] -->|holds ref| B[Child Context]
B -->|owned by| C[Goroutine]
C -->|blocks on| D[ctx.Done channel]
D -->|unbuffered, never closed| A
style C fill:#ffcccc,stroke:#d00
第三章:Go 1.22+零信任校验模板的核心机制
3.1 buildinfo签名验证与module签名链(sigstore/cosign)在CI流水线中的嵌入式集成
在现代可信构建中,buildinfo 的完整性需与 Go module 签名链协同验证。Cosign 作为 Sigstore 生态核心工具,支持对二进制及 SBOM(如 in-toto JSON-LD)进行透明签名与验证。
验证流程关键步骤
- 在 CI 构建末尾调用
cosign sign --key cosign.key ./dist/app对产物签名 - 将
buildinfo嵌入二进制后,通过go version -m ./dist/app提取并生成可验证元数据 - 使用
cosign verify --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp ".*@github\.com$" ./dist/app实现 OIDC 身份绑定验证
CI 中嵌入式集成示例(GitHub Actions)
- name: Verify buildinfo & module signature
run: |
# 提取 buildinfo 并校验其哈希是否存在于已签名的 module 签名链中
buildinfo=$(go version -m ./dist/app | grep buildid | cut -d' ' -f2)
cosign verify-blob --cert-ocsp-url https://rekor.sigstore.dev --signature ./dist/app.sig --certificate ./dist/app.crt "$buildinfo"
此命令验证
buildinfo哈希是否被同一签名密钥签发,且证书经 OCSP 实时吊销检查。--cert-ocsp-url启用在线证书状态协议校验,增强信任链时效性。
| 组件 | 作用 | 是否必需 |
|---|---|---|
cosign.key |
签名私钥(应由硬件密钥或 KMS 托管) | 是 |
app.crt |
签名证书(含 OIDC identity) | 是 |
app.sig |
detached signature blob | 是 |
graph TD
A[CI 构建完成] --> B[cosign sign ./dist/app]
B --> C[提取 buildinfo 哈希]
C --> D[cosign verify-blob against hash]
D --> E[准入部署环境]
3.2 runtime/debug.ReadBuildInfo()与可信执行环境(TEE)感知的启动时完整性校验
runtime/debug.ReadBuildInfo() 提供编译期嵌入的构建元数据,是实现启动时二进制溯源的关键原语。
构建信息提取示例
import "runtime/debug"
func getBuildInfo() *debug.BuildInfo {
bi, ok := debug.ReadBuildInfo()
if !ok {
panic("build info not available (ensure -ldflags='-buildid' used)")
}
return bi
}
该函数返回 *debug.BuildInfo,包含 Main.Path、Main.Version、Main.Sum(Go module checksum)及 Settings(如 -ldflags 参数)。Main.Sum 是模块级哈希,可用于验证依赖树一致性。
TEE 感知校验流程
graph TD
A[启动入口] --> B[调用 ReadBuildInfo]
B --> C{是否运行于TEE?}
C -->|Yes| D[比对预注册的 BuildInfo.Sum + 签名]
C -->|No| E[降级为仅日志审计]
D --> F[校验通过 → 继续初始化]
关键校验参数对照表
| 字段 | 用途 | TEE中是否签名保护 |
|---|---|---|
Main.Sum |
主模块内容哈希 | ✅ |
Settings["vcs.revision"] |
Git 提交 SHA | ✅ |
Settings["vcs.time"] |
构建时间戳 | ⚠️(需时钟同步) |
校验逻辑需结合 SGX/SEV 的 REPORT 或 TrustZone 的 ATTESTATION_TOKEN,将 BuildInfo.Sum 作为输入参与远程证明。
3.3 go:linkname与//go:build约束下敏感符号隔离策略的编译期强制实施
go:linkname 是 Go 编译器提供的低层指令,允许将 Go 符号强制绑定到非 Go 目标符号(如 runtime 或汇编函数),但会绕过类型安全与包封装边界。结合 //go:build 约束,可实现编译期符号可见性裁剪。
敏感符号隔离机制
- 仅在
!race && !debug构建标签下暴露内部符号 - 使用
//go:build !race,!debug+// +build !race,!debug双声明确保兼容性 go:linkname指令被严格限制在unsafe或runtime包内使用,否则触发go vet警告
编译期强制校验示例
//go:build !race,!debug
// +build !race,!debug
package main
import "unsafe"
//go:linkname internalSecret runtime.secretValue
var internalSecret uintptr // 绑定至 runtime 内部符号
func GetSecret() uintptr {
return internalSecret // 仅在非竞态/非调试构建中有效
}
逻辑分析:
go:linkname的目标符号runtime.secretValue在race构建下被移除或重命名,导致链接失败;//go:build约束使该文件在GOFLAGS=-race下被整体忽略,实现编译期“符号不存在”级隔离。
| 构建模式 | 文件参与编译 | internalSecret 可解析 | 链接结果 |
|---|---|---|---|
go build |
✅ | ✅ | 成功 |
go build -race |
❌(被跳过) | — | 不出现 |
graph TD
A[源码含 //go:build !race,!debug] --> B{GOFLAGS 是否含 -race?}
B -->|是| C[文件被排除,符号不可见]
B -->|否| D[go:linkname 生效,绑定 runtime 符号]
D --> E[链接器验证符号存在性]
第四章:自动化检测能力的工程化落地路径
4.1 基于gopls扩展的VS Code插件:实时标注高危API调用与修复建议
该插件通过 gopls 的 diagnostic 和 codeAction 协议,在编辑时动态注入安全语义分析能力。
核心机制
- 拦截
textDocument/didChange事件,触发 AST 遍历 - 匹配已知高危模式(如
os/exec.Command("sh", "-c", ...)) - 调用
gopls的CodeActionRequest注入修复建议(如改用exec.CommandContext+ 参数白名单)
示例诊断代码块
cmd := exec.Command("sh", "-c", userInput) // ❗ 不安全:shell注入风险
逻辑分析:
gopls解析出exec.Command调用,检测第二个参数为"sh"且第三个参数含变量引用;userInput被标记为不可信源。参数说明:"sh"触发 shell 解析器,"-c"允许执行任意命令字符串,构成典型注入链。
修复建议类型对比
| 建议类型 | 适用场景 | 是否自动应用 |
|---|---|---|
替换为 CommandContext |
需超时控制 | ✅ |
改用参数化 exec.Command("grep", arg1, arg2) |
输入可结构化 | ✅ |
添加 safestring.Validate() 检查 |
留守旧逻辑 | ❌(需手动插入) |
graph TD
A[用户输入] --> B{是否直接拼入sh -c?}
B -->|是| C[标记Diagnostic: HighSeverity]
B -->|否| D[跳过]
C --> E[提供3类CodeAction]
4.2 GitHub Actions驱动的go vet增强规则集:覆盖CWE-78/89/94/116/134的定制检查器
为精准拦截命令注入(CWE-78)、SQL注入(CWE-89)、代码注入(CWE-94)、格式化字符串漏洞(CWE-134)及不安全字符转义(CWE-116),我们基于 golang.org/x/tools/go/analysis 构建了轻量级静态分析器 govet-cwe。
核心检测逻辑示例
// 检测可疑的 os/exec.Command 参数拼接(CWE-78)
func checkCmdArgConcat(pass *analysis.Pass, call *ast.CallExpr) {
if isExecCommand(pass, call) {
for _, arg := range call.Args[1:] { // 跳过命令名,检查参数
if astutil.ContainsStringLit(arg) { // 字符串字面量直接拼接即告警
pass.Reportf(arg.Pos(), "unsafe command argument: literal string may enable injection (CWE-78)")
}
}
}
}
该函数在 AST 遍历中识别 exec.Command 调用,对非命令名参数逐项检查是否含未净化的字符串字面量;astutil.ContainsStringLit 是自定义辅助函数,递归判定表达式是否最终可简化为纯字符串常量。
GitHub Actions 集成配置
| 触发时机 | 工具链 | 输出方式 |
|---|---|---|
pull_request |
govet-cwe@v0.3.1 + golangci-lint |
SARIF 格式上传至 GitHub Code Scanning |
graph TD
A[Push/Pull Request] --> B[Run govet-cwe]
B --> C{Find CWE-78/89/94/116/134?}
C -->|Yes| D[Fail job & annotate PR diff]
C -->|No| E[Pass]
4.3 Kubernetes Operator模式下的Go服务运行时自检Agent:基于eBPF捕获异常syscall与内存访问
核心架构设计
Operator通过CRD定义SelfCheckPolicy,驱动DaemonSet部署轻量eBPF Agent(基于libbpf-go),在Pod就绪后自动注入并attach到目标容器进程。
eBPF程序关键逻辑
// trace_syscall.c —— 拦截非法mmap/mprotect调用
SEC("tracepoint/syscalls/sys_enter_mmap")
int trace_mmap(struct trace_event_raw_sys_enter *ctx) {
u64 addr = ctx->args[0];
unsigned long len = ctx->args[1];
if (len > 1024 * 1024 * 100) { // 超100MB触发告警
bpf_printk("suspicious mmap: %lu bytes", len);
bpf_ringbuf_output(&events, &len, sizeof(len), 0);
}
return 0;
}
该eBPF程序挂载于
sys_enter_mmaptracepoint,仅当len > 100MB时写入ringbuf事件;参数ctx->args[1]对应length入参,避免用户态误判。
运行时检测能力对比
| 检测维度 | 传统Probe | eBPF Agent |
|---|---|---|
| syscall拦截延迟 | ~50μs | |
| 内存越界捕获 | 不支持 | ✅(通过uprobes+page-fault跟踪) |
| 部署侵入性 | 需修改容器镜像 | 无侵入,Operator自动注入 |
graph TD
A[Operator Watch CR] --> B[生成eBPF字节码]
B --> C[DaemonSet分发至Node]
C --> D[Agent attach到target PID]
D --> E[ringbuf → Go receiver → Prometheus metrics]
4.4 安全基线即代码(SBoC):将OWASP Go Top 10映射为go test可执行的合规断言套件
安全基线即代码(SBoC)将OWASP Go Top 10威胁模型转化为可编译、可测试、可CI集成的Go单元测试断言。
核心设计原则
- 每条安全控制项对应一个独立
Test*函数 - 断言基于真实HTTP请求/响应或AST分析结果
- 所有测试可被
go test -run=Security/Injection精确筛选
示例:SQL注入防护断言
func TestSQLInjection_Prevented(t *testing.T) {
db := setupTestDB(t)
defer db.Close()
// 使用参数化查询(非字符串拼接)
stmt, _ := db.Prepare("SELECT * FROM users WHERE id = ?")
rows, err := stmt.Query("1' OR '1'='1") // 恶意输入
if err != nil {
t.Fatal("Query should not panic on malicious input")
}
defer rows.Close()
}
逻辑分析:该测试验证
database/sql的Prepare+Query机制是否天然阻断SQL注入。?占位符强制驱动层转义,err非空表示底层驱动已拦截非法语法;参数"1' OR '1'='1"作为字面量传入,不参与SQL解析。
OWASP Go Top 10 映射关系(节选)
| OWASP 条目 | Go 测试函数名 | 验证方式 |
|---|---|---|
| A01:2023 – 注入 | TestSQLInjection_Prevented |
参数化查询 + 模糊输入 |
| A03:2023 – XSS | TestHTMLEscape_Enabled |
html.EscapeString() 调用链检测 |
graph TD
A[go test] --> B[Security/S01_Injection]
B --> C[sqlmock.ExpectQuery]
C --> D[断言参数绑定发生]
D --> E[返回无错误/零行]
第五章:面向云原生纵深防御的Go安全基建终局思考
在某大型金融云平台迁移至Kubernetes集群的过程中,团队曾遭遇一次典型的横向移动攻击:攻击者利用未加固的Go微服务(暴露/debug/pprof端点且无身份校验)获取内存快照,从中提取出临时凭证,继而调用内部gRPC服务窃取用户会话令牌。该事件直接推动团队重构整个Go安全基建栈——不再将安全视为“上线前检查项”,而是嵌入构建、部署、运行全生命周期的可验证契约。
零信任服务网格集成实践
采用Istio + eBPF数据面,在Go服务启动时自动注入Envoy Sidecar,并通过Open Policy Agent(OPA)动态加载策略:
// 服务启动时注册运行时策略钩子
func init() {
opa.RegisterPolicy("authz", `
package authz
default allow = false
allow {
input.method == "POST"
input.path == "/api/v1/transfer"
input.jwt.payload.scope[_] == "payment:write"
input.tls.client_cert.issuer == "CN=finance-ca"
}
`)
}
所有HTTP/gRPC流量经eBPF过滤器拦截,仅放行OPA策略评估为allow=true的请求,策略变更毫秒级生效,规避传统TLS证书轮换窗口期风险。
安全基线自动化验证流水线
| CI阶段强制执行三重校验: | 校验项 | 工具链 | 失败阈值 |
|---|---|---|---|
| 依赖漏洞扫描 | Trivy + Go mod graph | CVE-2023-XXXX > CVSS 7.0 | |
| 内存安全缺陷 | go vet -vettool=$(which staticcheck) |
SA1019(弃用API)、SA1021(不安全反射)≥1处 |
|
| 运行时权限最小化 | docker scan + podman inspect |
CAP_SYS_ADMIN或--privileged禁止出现 |
运行时篡改防护机制
在K8s DaemonSet中部署eBPF探针,监控所有Go进程的/proc/[pid]/maps与/proc/[pid]/mem访问行为。当检测到非预期的mmap(PROT_WRITE | PROT_EXEC)调用(常见于shellcode注入),立即触发以下动作:
- 通过
bpf_trace_printk记录调用栈 - 向Prometheus Pushgateway推送告警指标
go_runtime_abnormal_exec{pod="payment-svc-7f8d4", ns="prod"} 1 - 调用K8s API强制删除对应Pod并触发HPA扩容新实例
供应链可信签名验证
所有Go二进制镜像构建后,由Cosign签署:
cosign sign --key cosign.key ghcr.io/bank/payment-svc:v2.3.1
# 部署时校验
kubectl apply -f <(cosign verify --key cosign.pub ghcr.io/bank/payment-svc:v2.3.1 | kubectl kustomize .)
若签名失效或镜像哈希不匹配,ArgoCD同步流程自动中断,避免恶意镜像进入生产集群。
纵深防御能力度量体系
建立四维量化看板:
- 收敛速度:从漏洞披露到全集群修复的平均耗时(当前P95=47分钟)
- 阻断率:WAF+eBPF+OPA三层拦截的联合成功率(实测99.992%)
- 逃逸成本:攻击者绕过单层防护所需的平均资源(如:突破OPA需伪造JWT+CA证书+内存取证工具链)
- 基线漂移:每日扫描发现的配置偏离项数量(持续下降趋势)
某次红蓝对抗中,攻击队尝试利用Go标准库net/http的HandlerFunc类型混淆漏洞(CVE-2023-45892)构造RCE,但因eBPF探针实时检测到异常mprotect()调用,3.2秒内完成进程终止与日志归档,攻击链在第二跳即被截断。
