RoLingG | 博客
type: channel
link: https://rolingg.top/
items: [Golang] Fedora上运行Wails构建出现libwebkit缺失问题
items: [Golang] 通过errors.AsType的改动反映反射的优化方向
items: [Golang] 一次errors的benchmark记录
items: [Golang] 1.26版本新GC回收——Green Tea 🍵 Garbage Collector
items: [Golang] net.conn.Read、io.ReadAll和io.ReadFull三者区别
items: [Golang] 从字节Netpoll中学习相关Read问题处理
items: [Golang] 纯粹的优化神经if-else
items: [Golang] Map注册表优化多个if-else
items: [MongoDB] MongoDB-CVE-2025-14847漏洞
items: [还债] 高精度加法和高精度乘法

[Golang] Fedora上运行Wails构建出现libwebkit缺失问题
type: item
date: 2026-04-04T00:05:53+08:00
description: Fedora上运行Wails构建出现libwebkit缺失问题现在 Wails 2.x 版本在 Fedora 上 build 构建可能会有 libwebkit 库缺失的问题：→ wails build # Building target: linux/amd64 • Generating bindings: Done. • Installing frontend dependencies: Done. • Compiling frontend: Done. • Compiling application: # github.com/wailsapp/wails/v2/internal/frontend/desktop/linux # [pkg-config --cflags -- gtk+-3.0 webkit2gtk-4.0 gtk+-3.0 webkit2gtk-4.0 gtk+-3.0 webkit2gtk-4.0 gtk+-3.0 gtk+-3.0 webkit2gtk-4.0 gtk+-3.0 webkit2gtk-4.0 gtk+-3.0 webkit2gtk-4.0 webkit2gtk-4.0 gtk+-3.0 webkit2gtk-4.0] Package webkit2gtk-4.0 was not found in the pkg-config search path. Perhaps you should add the directory containing `webkit2gtk-4.0.pc' to the PKG_CONFIG_PATH environment variable Package 'webkit2gtk-4.0' not found Package 'webkit2gtk-4.0' not found Package 'webkit2gtk-4.0' not found Package 'webkit2gtk-4.0' not found Package 'webkit2gtk-4.0' not found Package 'webkit2gtk-4.0' not found Package 'webkit2gtk-4.0' not found Package 'webkit2gtk-4.0' not found ERROR exit status 1 ERROR exit status 1 ♥ If Wails is useful to you or your company, please consider sponsoring the project: https://github.com/sponsors/leaanthony一般出现这种情况有两种：① 真的缺失 libwebkit 依赖，无法根据对依赖执行构建。② Wails build 依赖识别出问题。缺失 libwebkit 依赖如果是缺失依赖的话，我们只需要执行相应的 dnf 指令安装对应依赖就好了：# 安装依赖（包括Wails相关构建依赖） sudo dnf install gcc pkg-config gtk3-devel webkit2gtk4.0-devel # 验证依赖版本 pkg-config --modversion webkit2gtk-4.0部分 Fedora 版本可能会有所不同，比如相关魔改版或者像 Fedora 43 这样的新版本，webkit2gtk4.0 可能已经从 dnf 中移除，需要使用 webkit2gtk4.1 代替：# 安装依赖（包括Wails相关构建依赖） sudo dnf install gcc pkg-config gtk3-devel webkit2gtk4.1-devel # 验证依赖版本 pkg-config --modversion webkit2gtk-4.1如果系统版本没有 webkit2gtk-4.0 的话，那大概率即便你装了依赖也会遇到问题②或者额外的问题，请继续往下看。Wails build 依赖识别问题在 build 前我们可以用 wails doctor 看一下依赖是否齐全，如果像我（Fedora 43）这样即便安装了 webkit2gtk-4.1 也提示查找不到 libwebkit，就说明 Wails 2.x 版本的依赖识别也发生在了你的机器上。→ wails doctor Wails Doctor # Wails Version | v2.12.0 Package Manager | dnf # System ┌──────────────────────────────────────────────────────────┐ | OS | Fe…

[Golang] 通过errors.AsType的改动反映反射的优化方向
type: item
date: 2026-03-28T14:45:00+08:00
description: 通过errors.AsType的改动反映反射的优化方向在 Go 1.26 版本更新中，errors 包的更新只有一句话：The new AsType function is a generic version of As. It is type-safe, faster, and, in most cases, easier to use.译文：新的 AsType 函数是 As 的泛型版本。它类型安全、速度更快，而且在大多数情况下更容易使用。从 errors.As 到 errors.AsType，表面是 API 简化，实质是 Go 核心团队对 反射 + interface{} 这套历史方案的系统性替换。旧 API 的类型不安全陷阱errors.As 的设计带着 Go 早期的设计思路：灵活优先，安全靠自觉。var pathErr *os.PathError if errors.As(err, &pathErr) { fmt.Println(pathErr.Path) }这个 API 要求你传指针的指针，原因很直白：As 需要把匹配到的错误值写回你的变量。但这也埋下了三个类型不安全的地雷：错误一：变量声明与指针解耦var pathErr *os.PathError // ...... errors.As(err, &pathErr) // &pathErr 还是 pathErr？手滑就错错误二：类型错配编译不报错var netErr *net.Error // 接口类型 errors.As(err, &netErr) // 编译通过，但可能运行时失败错误三：完全错误的类型也能传var x int errors.As(err, &x) // 编译通过！运行时才返回 false第三个例子最致命——这是一个绝对不可能成功的类型断言，但编译器一声不吭。显然比起运行时报错，我们更希望编译时报错提早进行错误修复。这也是官方会更新这个新 API 的原因之一。新 API 的编译期契约AsType 把类型参数从函数参数挪到了泛型约束：pathErr, ok := errors.AsType[*os.PathError](err)检查项errors.Aserrors.AsType是否为指针类型运行时 reflect 判断编译期泛型约束是否实现 error运行时遍历接口编译期类型检查类型是否合理运行时发现报错编译期报错实战对比：// 错误写法 1：非指针类型 val, ok := errors.AsType[os.PathError](err) // 编译错误：os.PathError 不实现 error // 错误写法 2：完全不相关类型 val, ok := errors.AsType[int](err) // 编译错误：int 不实现 error // 错误写法 3：类型参数与返回值混淆 val, ok := errors.AsType[*os.PathError](err) // 正确，*os.PathError 实现了 error编译器现在成了第一道防线。类型错误在 IDE 里就标红，而不是隐藏在 CI 的日志里。为什么早期 Go 接受这种不安全？理解历史才能理解改进。Go 1.13 引入 errors.As 时（2019年），泛型还在草案阶段。当时的选项只有：方案 A：用 interface{} + 反射，灵活但不安全方案 B：为每种错误类型写专用函数，安全但爆炸官方选了 A，这是务实的妥协。但代价是：错误处理这种高频操作，承担了反射的全部开销。显然官方解决燃眉之急之后的代价就是在之后的日子慢慢优化这些方案。细节设计注意 AsType 的签名：func AsType[T error](err error) (T, bool)返回值是 (T, bool) 而非 bool，这是刻意设计：// 旧：先声明，再判断，再使用（三步，变量作用域外泄） var e *MyError if errors.As(err, &e) { handle(e) } // 新：判断即获取，if 内部限定作用域（两步，变量不外泄） if e, ok := errors.AsType[*MyError](err); ok { handle(e) }维度errors.Aserrors.AsType变量声明位置提前声明（作用域外泄）if 内部（作用域限定）语法噪音& 符号 + 指针理解类型参数 + 返回值解构空值风险可能误用未初始化的变量变量只在成功分支存在代码行数3 行（声明+判断+使用）1 行（判断并获取）类型安全 + 作用域安全，这是 Go 惯用法的回归。干净整洁，能一条语句写完就不分开写，下面例子就能很直观的看出来两者直接编写的可读性差异：// errors.As v…

[Golang] 一次errors的benchmark记录
type: item
date: 2026-03-28T14:42:30+08:00
description: 一次errors的benchmark记录本文代码由我和AI携手编写，如有问题全是我菜逼导致的（package errorsbench import ( "errors" "fmt" "os" "testing" ) // makeErrorChain 构造指定深度的错误链 // depth=1: 只有底层错误 // depth=3: 底层错误 + 2层包装 // depth=5: 底层错误 + 4层包装 func makeErrorChain(depth int) error { base := &os.PathError{ Op: "open", Path: "/tmp/test.txt", Err: os.ErrNotExist, } err := error(base) for i := 0; i < depth-1; i++ { err = fmt.Errorf("layer %d: %w", i, err) } return err } // benchmarkOldAs 测试标准库 errors.As（反射版） func benchmarkOldAs(b *testing.B, chainDepth int) { err := makeErrorChain(chainDepth) b.ResetTimer() for i := 0; i < b.N; i++ { var target *os.PathError if errors.As(err, &target) { _ = target.Path // 防止编译器优化掉 } } } // benchmarkNewAsType 测试 errors.AsType（泛型版） func benchmarkNewAsType(b *testing.B, chainDepth int) { err := makeErrorChain(chainDepth) b.ResetTimer() for i := 0; i < b.N; i++ { if target, ok := errors.AsType[*os.PathError](err); ok { _ = target.Path // 防止编译器优化掉 } } } // 不同深度的对比测试 func BenchmarkAs_Depth1(b *testing.B) { benchmarkOldAs(b, 1) } func BenchmarkAsType_Depth1(b *testing.B) { benchmarkNewAsType(b, 1) } func BenchmarkAs_Depth3(b *testing.B) { benchmarkOldAs(b, 3) } func BenchmarkAsType_Depth3(b *testing.B) { benchmarkNewAsType(b, 3) } func BenchmarkAs_Depth5(b *testing.B) { benchmarkOldAs(b, 5) } func BenchmarkAsType_Depth5(b *testing.B) { benchmarkNewAsType(b, 5) } // 测试内联效果：小函数包装 func checkWithAs(err error) bool { var target *os.PathError return errors.As(err, &target) } func checkWithAsType(err error) bool { _, ok := errors.AsType[*os.PathError](err) return ok } func BenchmarkAs_Inlined(b *testing.B) { err := makeErrorChain(3) b.ResetTimer() for i := 0; i < b.N; i++ { _ = checkWithAs(err) } } func BenchmarkAsType_Inlined(b *testing.B) { err := makeErrorChain(3) b.ResetTimer() for i := 0; i < b.N; i++ { _ = checkWithAsType(err) } } // 正确性验证 func TestCorrectness(t *testing.T) { err := makeErrorChain(3) // 旧 API var oldTarget *os.PathError oldOk := errors.As(err, &oldTarget) // 新 API new…

[Golang] 1.26版本新GC回收——Green Tea 🍵 Garbage Collector
type: item
date: 2026-03-27T16:23:00+08:00
description: Golang 1.26版本新GC回收——Green Tea 🍵 Garbage Collector官方改动文档：green tea garbage collector宏观设计目标差异设计哲学差异老 GC 原文描述：Go’s garbage collector implements a classic tri-color parallel marking algorithm. This is, at its core, just a graph flood, where heap objects are nodes in the graph, and pointers are edges. However, this graph flood affords no consideration to the memory location of the objects that are being processed. As a result, it exhibits extremely poor spatial locality—jumping between completely different parts of memory—poor temporal locality—blithely spreading repeated accesses to the same memory across the GC cycle—and no concern for topology.老 GC 原文问题描述：As a result, it exhibits extremely poor spatial locality—jumping between completely different parts of memory—poor temporal locality—blithely spreading repeated accesses to the same memory across the GC cycle—and no concern for topology.译文：它表现出极差的空间局域性——在内存中完全不同的部分间跳跃——时间局域性差——在同一记忆循环中反复访问，且对拓扑毫无关心。新 GC 原文描述：Green Tea: a parallel marking algorithm that, if not memory-centric, is at least memory-aware, in that it endeavors to process objects close to one another together.分析：【老 GC】把堆当成纯图结构，只做“洪水漫灌”式并发标记，完全不顾对象在物理内存上的位置。【新 GC】首要目标改为“内存感知”，优先把物理上相邻的对象一起处理，以改善 cache/NUMA 表现。核心算法流程差异最小工作单元原文：The core idea behind the new parallel marking algorithm is simple. Instead of scanning individual objects, the garbage collector scans memory in much larger, contiguous blocks. The shared work queue tracks these coarse blocks instead of individual objects, and the individual objects waiting to be scanned in a block are tracked in that block itself.译文：新并行标记算法的核心思想很简单。垃圾回收器不是扫描单个对象，而是扫描更大、相连的内存块。共享工作队列跟踪的是这些粗数据块，而非单个对象，等待扫描的单个物体则在该数据块内被跟踪。分析：【老 GC】以 “单个对象” 为最小单位；【新 GC】以“span（8 KiB 连续块）”为最小单位。入队/出队动作原文：When scanning finds a pointer to a small object, it sets that object’s gray bit … If the gray bit was not already set and the object’s span is not already enqueued … it enqueues the span.”“When the scan loop dequeues a span, it computes the…

[Golang] net.conn.Read、io.ReadAll和io.ReadFull三者区别
type: item
date: 2026-02-25T16:07:00+08:00
description: net.conn.Read、io.ReadAll和io.ReadFull三者区别net.conn.Readtype Conn interface { // Read reads data from the connection. // Read can be made to time out and return an error after a fixed // time limit; see SetDeadline and SetReadDeadline. Read(b []byte) (n int, err error) ...... ...... }注释："从连接读取数据。可以通过 SetDeadline 或 SetReadDeadline 设置超时，超时后返回错误。"潜台词：我是最底层的阻塞 IO，不保证读满，可能超时，你自己处理。io.ReadAll// ReadAll reads from r until an error or EOF and returns the data it read. // A successful call returns err == nil, not err == EOF. Because ReadAll is // defined to read from src until EOF, it does not treat an EOF from Read // as an error to be reported. func ReadAll(r Reader) ([]byte, error) { b := make([]byte, 0, 512) for { // 利用剩余 cap 读取,第一次就是 r.Read(b[0:512]),即全部 n, err := r.Read(b[len(b):cap(b)]) b = b[:len(b)+n] // 扩展 len if err != nil { if err == EOF { err = nil } return b, err } if len(b) == cap(b) { // Add more capacity (let append pick how much). b = append(b, 0)[:len(b)] // 扩容，指数增长 } } }注释："从 r 读取直到遇到错误或 EOF，返回读到的所有数据。成功调用返回 err == nil（不是 err == EOF）。因为 ReadAll 定义为读到 EOF 才停，所以它不把 Read 返回的 EOF 当作错误报告。"潜台词：我是动态扩容的循环读取，方便但内存不可控，适合小数据。io.ReadFull// ReadFull reads exactly len(buf) bytes from r into buf. // It returns the number of bytes copied and an error if fewer bytes were read. // The error is EOF only if no bytes were read. // If an EOF happens after reading some but not all the bytes, // ReadFull returns [ErrUnexpectedEOF]. // On return, n == len(buf) if and only if err == nil. // If r returns an error having read at least len(buf) bytes, the error is dropped. func ReadFull(r Reader, buf []byte) (n int, err error) { return ReadAtLeast(r, buf, len(buf)) }注释："从 r 精确读取 len(buf) 字节到 buf。返回复制的字节数，如果读得更少则返回错误。仅当 0 字节被读时错误才是 EOF。如果读了一部分后遇到 EOF，返回 ErrUnexpectedEOF。仅当 err == nil 时 n == len(buf)。如果 r 在读了至少 len(buf) 字节后返回错误，该错误被丢弃。"潜台词：我是契约式读取，必须读满，否则报错，适合协议解析。需求官方意图选择"读到多少算多少，我继续处理"Read 的基础语义conn.Read"方便地读完整个流"ReadAll 的存在理由io.ReadAll"必须拿到固定长度，否则失败"ReadFull 的强契约io.ReadFull场景推荐选择原因网络协议…

[Golang] 从字节Netpoll中学习相关Read问题处理
type: item
date: 2026-02-25T16:04:13+08:00
description: 从字节Netpoll中学习相关Read问题处理Append[:0]细节错误我们先来看一段代码：package main import ( "net" ) func main() { var conn net.Conn var buf = make([]byte, 8192) // reading for { n, _ := conn.Read(buf) ... unpacking & handling ... var i int for i = 0; i <= n-pkgsize; i += pkgsize { pkg := append([]byte{}, buf[i:i+pkgsize]...) go func() { ... handling pkg ... } } buf = append(buf[:0], buf[i:n]...) } // writing var write_datas <-chan []byte ... packing write ... for { pkg := <-write_datas conn.Write(pkg) } }这里假设我们有一个 8192 长度和大小的 []byte。buf := make([]byte, 8192) // len=8192, cap=8192 // 切片表达式：buf[low:high] // 新切片 = (相同底层数组, len=high-low, cap=cap-low)表达式lencap底层数组buf81928192原数组buf[:0]08192原数组buf[100:]80928092原数组buf[:100]1008192原数组buf = append(buf[:0], buf[i:n]...) // ↑ 第一步：buf[:0] 创建 len=0 的切片 // ↑ 第二步：把 buf[i:n] 追加到这个 len=0 的切片// 1. 创建头切片，len=0，但 cap 还是 8192 head := buf[:0] // head = (ptr=原数组, len=0, cap=8192) // 2. 追加残留数据（假设 3 字节） residual := buf[i:n] // len=3 // 3. append 逻辑：如果 cap 够，直接写到原数组 newBuf := append(head, residual...) // 结果：len=3, cap=8192，底层数组前 3 字节被改成 residual重要的是这个准则： 新切片 = (相同底层数组, len=high-low, cap=cap-low)这意味着如果像上面这要操作切片，能够修改原切片的各项数据：// 示例：通过append修改切片 original := make([]byte, 8192) original[0] = 'A' slice := original[:0] // len=0，但指向同一数组 newSlice := append(slice, 'X') // append数据到original[0]，相当于覆盖掉original[0] fmt.Println(original[0]) // 'X'而不是'A'，原数组被改了正确的做法应该是：// 正确：保留 len=8192，只移动内容 copy(buf, buf[i:n]) // 把残留拷贝到头部 // buf 的 len 还是 8192，前 (n-i) 字节是有效数据，后面是垃圾总结操作len 变化底层数组变化下次 Read 能力buf[:0] + append变成残留长度头部被覆盖只能读 lencopy + 调整范围不变头部被覆盖可指定范围读为什么要聊这个？因为字节 netpoll 官方文档里给出了这个示例：package main import ( "net" ) func main() { var conn net.Conn var buf = make([]byte, 8192) // reading for { n, _ := conn.Read(buf) ... unpacking & handling ... var i int for i = 0; i <= n-pkgsize; i += pkgsize { pkg := append([]byte{}, buf[i:i+pkgsize]...) go func() { ... handling pkg ... } } buf = append(buf[:0], buf[i:n]...) } // writing var write_datas <-chan []byte ... packing write ... for { pkg := <-…

[Golang] 纯粹的优化神经if-else
type: item
date: 2026-01-05T18:35:00+08:00
description: 纯粹的优化神经if-else之前说写过一个 “注册表” 式优化，那种情况如下：// —————*** 优化前 ***—————— // exportData 根据格式导出数据 func exportData(data interface{}, format string) error { if format == "pdf" { exportPDF(data) } else if format == "csv" { exportCSV(data) } else if format == "json" { exportJSON(data) } else { return errors.New("no exporter found for format: " + format) } return nil } // —————*** 优化后 ***—————— // exportData 根据格式导出数据 func exportData(data interface{}, format string) error { exporter, exists := exporters[format] // 不同格式获得对应格式的闭包调用 if !exists { return errors.New("no exporter found for format: " + format) } exporter(data) // 闭包调用传输数据获得想要结果 return nil } // exportPDF 导出为PDF格式 func exportPDF(data interface{}) { fmt.Println("Exporting data as PDF...") } // exportCSV 导出为CSV格式 func exportCSV(data interface{}) { fmt.Println("Exporting data as CSV...") } // exportJSON 导出为JSON格式 func exportJSON(data interface{}) { fmt.Println("Exporting data as JSON...") }这种你要硬说，也可以用 swich 去进行优化，毕竟本质上就是单一对象的不同情况区分。但如果是多对象不同情况区分的话，就会实现的比较难受。特别还是下面这种策略模式昏厥的写法，一眼昏头：func approveOrderEvilNested(o Order, u User) string { if isEligibleAmount(o) { if !o.HasDiscount { if hasValidCurrency(o, u) { for _, it := range o.Items { if it.Price < 0 { return "rejected" } } return "approved" } else { return "rejected" } } else { return "rejected" } } else { return "rejected" } }下面是整个优化过程的源码：package main import ( "fmt" ) /* ---------- 领域对象 ---------- */ type Item struct { Price int } type Order struct { Amount int HasDiscount bool Currency string Items []Item } type User struct { Region string Currency string } type ValidCurrency struct { UserRegion string OrderCurrency string } /* ---------- 工具函数 ---------- */ func isEligibleAmount(o Order) bool { return o.Amount >= 100 } // 简单校验货币 func hasValidCurrency(o Order, u User) bool { return o.Currency == u.Currency } // 助于优化区域检验，每次增加新情况只需要在这里增添就好了 var strictlyValidCurrencyMap = map[ValidCurrency]bool{ {UserRegion: "US", OrderCurrency: "USD"}: true, {UserRegion: "EU", OrderCurrency: "EUR…

[Golang] Map注册表优化多个if-else
type: item
date: 2026-01-05T18:35:00+08:00
description: Map注册表优化多个if-else我们通过一个简单的数据转成各项格式的demo进行优化，我们先来看优化后的代码：// 优化后 package main import ( "errors" "fmt" ) // ExportFn 是导出函数的类型 type ExportFn func(data interface{}) // exporters 映射格式到对应的导出函数 var exporters = map[string]ExportFn{ "pdf": exportPDF, "csv": exportCSV, "json": exportJSON, } // exportData 根据格式导出数据 func exportData(data interface{}, format string) error { exporter, exists := exporters[format] // 不同格式获得对应格式的闭包调用 if !exists { return errors.New("no exporter found for format: " + format) } exporter(data) // 闭包调用传输数据获得想要结果 return nil } // exportPDF 导出为PDF格式 func exportPDF(data interface{}) { fmt.Println("Exporting data as PDF...") } // exportCSV 导出为CSV格式 func exportCSV(data interface{}) { fmt.Println("Exporting data as CSV...") } // exportJSON 导出为JSON格式 func exportJSON(data interface{}) { fmt.Println("Exporting data as JSON...") } func main() { data := map[string]string{"key": "value"} err := exportData(data, "pdf") if err != nil { fmt.Println(err) } err = exportData(data, "csv") if err != nil { fmt.Println(err) } err = exportData(data, "json") if err != nil { fmt.Println(err) } }如果不用的话，就是这样的：// 优化前 package main import ( "errors" "fmt" ) // exportData 根据格式导出数据 func exportData(data interface{}, format string) error { if format == "pdf" { exportPDF(data) } else if format == "csv" { exportCSV(data) } else if format == "json" { exportJSON(data) } else { return errors.New("no exporter found for format: " + format) } return nil } // exportPDF 导出为PDF格式 func exportPDF(data interface{}) { fmt.Println("Exporting data as PDF...") } // exportCSV 导出为CSV格式 func exportCSV(data interface{}) { fmt.Println("Exporting data as CSV...") } // exportJSON 导出为JSON格式 func exportJSON(data interface{}) { fmt.Println("Exporting data as JSON...") } func main() { data := map[string]string{"key": "value"} err := exportData(data, "pdf") if err != nil { fmt.Println(err) } err = exportData(data, "csv") if err != nil { fmt.Println(err) } err = exportData(data, "json") if err != nil { fmt.Println(err) } …

[MongoDB] MongoDB-CVE-2025-14847漏洞
type: item
date: 2026-01-01T22:20:07+08:00
description: MongoDB-CVE-2025-14847漏洞出自：mongobleed。这个问题也是彩虹六号 · 围攻 2025 年 12 月 27 号服务器被黑的原因之一。本质MongoDB 在 3.6–8.2 的 zlib 解压路径里，把「分配的缓冲区长度」当成「实际解压后的数据长度」回传给上层。攻击者只要发一个压缩 BSON 报文，把 uncompressedSize 故意填得很大，就能让服务端返回一大片未初始化内存。影响面所有默认开启 compression=zlib 的实例（含 Atlas 部分旧集群、Docker 官方镜像、自建副本集/分片）。无需认证，只要 TCP 能连 27017 就能打；若开了 --bindIp 限制或前端有代理，可缓解但难根除。泄露内容取决于内存布局：WT 缓存页、connection 对象、/proc 文件系统缓存、日志缓冲、甚至前任用户的查询结果。修复方案最干脆：升级到对应补丁版（或 8.3/9.0 最新稳定版）。（首选）若暂时无法重启，可热关闭 zlib：db.adminCommand({setParameter:1, "net.compression.compressors":"snappy,zstd"})然后重启节点去掉 zlib。前端加网络层 ACL，禁止非业务 IP 访问 27017；Atlas 用户可在 Network Access 里收紧源地址。容器场景用 127.0.0.1-only 或 sidecar mesh，避免把 27017 映射到宿主机。简单来说服务端把「我有多大杯子」当成「我有多少水」告诉了客户端，于是攻击者只要递一只「大杯子」，就能让服务端把杯里残留的「别人的水」倒出来。正常发送报文都是：客户端→服务器：OP_COMPRESSED{ … uncompressedSize: 真实长度 187, compressor: zlib, compressedPayload: … }服务端收到后：按 uncompressedSize 分配 187 字节 buffer；调 zlib 解压，返回 实际 长度 187；把 187 字节交给 BSON 解码。正常发送，正常解压解码，一切正常。但利用该漏洞，攻击者改字段：把 uncompressedSize 写成 8192 字节大小，实际只压缩了 20 字节垃圾数据。服务端仍按 8192 字节大小 分配，但 zlib 只往开头写了 20 字节就返回；剩下的 8 172 字节完全是 malloc 时残留的内存（WT 缓存、日志、别的连接的请求……）。关键 bug 代码示例：// mongo/util/net/compression_utils.cpp outMsg.setData(buffer, buffer.capacity()); // ❌ 把容量当长度正确应该是：outMsg.setData(buffer, uncompressedSize); // ✔️ zlib 返回的真实长度BSON 解码阶段消息层把 8192 字节交给 next 阶段，BSON 迭代器开始扫「field name」。前 20 字节是攻击者构造的合法 BSON，很快遇到 \0 结束。后面 8172 字节是随机内存，只要某处出现 \0，BSON 就把它当成「字段结束符」继续往下解析；于是攻击者就能通过返回包里的「字段名」把内存内容带出来。网络层回包MongoDB 居然把这段「超长子消息」原样发回给攻击者（因为协议头里长度就是 8192）。攻击者收包后，把「字段名」字段打印出来，从而实现了零认证内存泄露。这问题之所以这么严重也是因为 MongoDB 各项安全机制缺漏的问题：无需登录：zlib 解压发生在认证之前。跨平台：bug 在 C++ 层，和操作系统、存储引擎无关。总结服务端误把 buffer.capacity() 当解压后长度写回消息头，于是把未初始化的尾部内存随回复一起发出。其实看着之后也会发现，这个问题就是开发 “粗心大意” 导致的，但也因为这样子，才导致了 MongoDB 会有如此稳定可轻易复现，并能够大面积使用的高危漏洞。所以世界就是个草台班子。

[还债] 高精度加法和高精度乘法
type: item
date: 2025-12-27T13:48:00+08:00
description: 高精度加法和高精度乘法高精度加法：package main import ( "bufio" "fmt" "os" "strings" ) // 高精度加法：a + b，返回字符串（非负整数） func bigAdd(a, b string) string { // 去掉前导 0 a = strings.TrimLeft(a, "0") b = strings.TrimLeft(b, "0") if a == "" { a = "0" } if b == "" { b = "0" } // 倒序 aa := make([]int, len(a)) bb := make([]int, len(b)) for i := 0; i < len(a); i++ { aa[i] = int(a[len(a)-1-i] - '0') } for i := 0; i < len(b); i++ { bb[i] = int(b[len(b)-1-i] - '0') } maxLen := max(len(aa), len(bb)) res := make([]int, maxLen+1) // 逐位相加 carry := 0 for i := 0; i < maxLen || carry > 0; i++ { da := 0 if i < len(aa) { da = aa[i] } db := 0 if i < len(bb) { db = bb[i] } sum := da + db + carry res[i] = sum % 10 carry = sum / 10 } // 去高位 0 k := len(res) - 1 for k > 0 && res[k] == 0 { k-- } res = res[:k+1] // 倒序拼字符串 ans := make([]byte, len(res)) for i := 0; i < len(res); i++ { ans[len(res)-1-i] = byte(res[i] + '0') } return string(ans) } func main() { in := bufio.NewReader(os.Stdin) var a, b string fmt.Fscan(in, &a, &b) fmt.Println(bigAdd(a, b)) }高精度乘法：package main import ( "bufio" "fmt" "os" "strings" ) // 高精度乘法：a*b，返回字符串形式的结果 func bigMul(a, b string) string { a = strings.TrimLeft(a, "0 ") b = strings.TrimLeft(b, "0 ") if a == "0" || a == "" || b == "0" || b == "" { return "0" } // 倒序转成 byte 数组，方便从低位开始算 aa := []byte(a) bb := []byte(b) // 反转aa和bb，便于模拟人手算乘法 for i := 0; i < len(aa)/2; i++ { aa[i], aa[len(aa)-1-i] = aa[len(aa)-1-i], aa[i] } for i := 0; i < len(bb)/2; i++ { bb[i], bb[len(bb)-1-i] = bb[len(bb)-1-i], bb[i] } // 结果最长 len(a)+len(b) 位 res := make([]int, len(aa)+len(bb)) // 竖式乘法 for i := 0; i < len(aa); i++ { da := int(aa[i] - '0') // 将string转换成int carry := 0 for j := 0; j < len(bb); j++ { db := int(bb[j] - '0') temp := da*db + res[i+j] + carry res[i+j] = temp % 10 carry = temp / 10 } res[i+len(bb)] += carry // 残余进位保留 } fmt.Println(res) // 去除最高位剩余0 for len(res) > 0 && res[len(res)-1] == 0 { res = res[:len(res)-1] } fmt.Println(res) // 倒序拼成字符串 ans := make([]byte, len(res)) for i := 0; i < len(res); i++ { ans[…