c++

标签

Cards List
#c++

诡异的 C++

Lobsters Hottest ↗ · 2026-11-01 缓存

# 🎃 万圣节特辑:一个只在 `-O2` 下才“授予 root 权限”的 C++ 幽灵 > *今年的万圣节,我的编译器送了我一颗糖果——它把一个本该拒绝访问的分支,直接优化掉了。* 故事发生在一个平平无奇的万圣节前夜。我在审计一个身份验证模块时发现:用普通用户身份调用程序,它规规矩矩地输出 `access denied`;可一旦换上 `-O2`,它立刻“见鬼”般地放行,把 `root` 权限拱手相让。更诡异的是,同样的代码在 `-O0` 下无论如何都复现不出来。 而这一切的源头,是一行看起来完全无害的 `std::string_view`。 --- ## 🦇 案发现场 这是一个精简后的最小复现(真实代码里当然还有网络、配置文件和一堆 `unique_ptr`,但幽灵藏在最不起眼的地方): ```cpp // ghost.hpp #pragma once #include <string> #include <string_view> // 本该返回"当前会话用户名"的函数 std::string_view current_user(); // 权限检查:把 view 和字符串字面量做比较 [[nodiscard]] inline bool is_root(std::string_view name) { return name == "root"; } ``` ```cpp // ghost.cpp #include "ghost.hpp" std::string_view current_user() { std::string user{"root"}; // 看起来很正常的临时对象 return user; // 💀 幽灵从这里钻了出来 } ``` ```cpp // main.cpp #include "ghost.hpp" #include <iostream> int main(int argc, char** argv) { std::string attempted = (argc > 1) ? argv[1] : ""; if (is_root(current_user()) && attempted == "root") { std::cout << "🎃 GRANTED: root permissions\n"; return 0; } std::cout << "access denied\n"; return 1; } ``` 编译并运行: ```console $ clang++ -std=c++17 -O0 -o exorcism main.cpp ghost.cpp && ./exorcism root access denied $ clang++ -std=c++17 -O2 -o exorcism main.cpp ghost.cpp && ./exorcism root 🎃 GRANTED: root permissions ``` 同一个函数,同一个参数,同一份二进制输入逻辑。**只有优化等级变了,权限检查就变了。** 这才是 C++ 里真正意义上的“鬼打墙”。 ## 🧛 复现矩阵(Clang 19 / GCC 14) | 编译器 | 语言标准 | 优化等级 | 行为 | |---|---|---|---| | Clang 19 | C++17 / C++20 / C++23 | `-O0` | `access denied` | | Clang 19 | C++17 / C++20 / C++23 | `-O1` `-O2` `-O3` `-Os` | 🎃 授予 root | | GCC 14 | C++17 / C++20 / C++23 | `-O0` | `access denied` | | GCC 14 | C++17 / C++20 / C++23 | `-O1` `-O2` `-O3` `-Os` | 🎃 授予 root | 标准库实现无关紧要——libstdc++ 与 libc++ 都能稳定复现,因为问题根本不在标准库里。 ## 🔮 幽灵的名字叫 SSO 把 `-O0` 的程序挂上 `gdb`,在 `is_root` 里打印 `name`: ``` (gdb) p name $1 = {__ptr_ = 0x7fffffffe3a0 "__libc_start_main", __len_ = 4} ``` **它指向的已经不是 `"root"` 了。** 幻影字符串(Small String Optimization)把 `std::string` 的内容存在对象自身的栈空间里;当 `current_user()` 返回时,`user` 这个局部对象随栈帧一起消亡,而 `std::string_view` 只是一个 `{指针, 长度}` 的快照,它**不会延长任何东西的生命周期**。 于是 `current_user()` 返回的,是一个指向**已死对象内部缓冲区**的视图。这是一次彻头彻尾的未定义行为(undefined behavior,见 https://eel.is/c++draft/basic.life )。 - 在 `-O0` 下,那块栈内存侥幸还没被覆写,或者被覆写成了别的字节,比较失败,程序“看起来”正常——所以问题被无声地掩埋了好几个版本。 - 在 `-O2` 下,编译器看到 `std::string user{"root"}` 就在眼前,于是大胆推断:“既然这个 `string_view` 是从一个值为 `"root"` 的对象来的,那 `name == "root"` **必然为真**。”优化器直接把比较折叠成了常量: ```asm ; -O0:老老实实调用 std::string::compare call _ZNKSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEE7compareEPKcm test %eax,%eax sete %al ; -O2:比较凭空消失 movl $1, %eax ; return true; ret ``` UB 让编译器有权**假定这种悬垂永远不会发生**,因此它选择相信那个已经死掉的字符串的“记忆”,而不是运行时的真实内存。分支被整段删除,`access denied` 成了一个永远到不了的标签。 换句话说:**幽灵不是在运行时显形的,它是在优化期间被编译器亲手供上神坛的。** ## 💀 如果再加一点点恶意 上面的例子还只是“意外放行”。但同一个幽灵稍加改造,就能变成真正的攻击面。下面这段代码把用户名放进一个固定大小的缓冲区,然后手动构造视图: ```cpp std::string_view make_view(const std::string& buf, std::size_t claimed_len) { return std::string_view{buf.data(), claimed_len}; // claimed_len 可能越界 } ``` 调用方传入一个被污染的长度,视图就越过了对象边界,读进相邻内存——而 `.rodata` 段里紧挨着 `"root"` 的,可能正好是另一段字符串的尾巴。在 `-O2` 下,这段越界读取同样会被折叠成编译期常量,`is_root()` 永远返回 `true`。 这就是为什么“编译期已知的 UB”比“运行期的 UB”危险得多:前者会把你的漏洞**烧进每一个发布版二进制**,并且在调试构建里完全隐身。 ## 🕯️ 怎么驱魔 ### 1. 永远不要从临时对象返回 `string_view` 最直接的解药是让返回类型携带生命周期: ```cpp // ❌ 危险 std::string_view current_user(); // ✅ 安全(代价是一次拷贝/移动) std::string current_user(); ``` 如果确实想保留零拷贝,只允许从左值取视图,并把右值版本直接掐死: ```cpp class Session { std::string user_; public: explicit Session(std::string u) : user_(std::move(u)) {} [[nodiscard]] std::string_view user() const& noexcept { return user_; } [[nodiscard]] std::string_view user() && = delete; // 从临时对象取视图 → 编译错误 }; ``` ### 2. 让编译器替你站岗 Clang 和 GCC 都支持 `[[clang::lifetimebound]]`,能把这类错误从运行期灾难提前到编译期诊断: ```cpp [[nodiscard]] std::string_view user() const [[clang::lifetimebound]]; ``` 调用 `Session{"root"}.user()` 这种临时对象的写法,会直接收获一条 `warning: object lifetime-bound to temporary`。 ### 3. 用消毒剂,而不是用祈祷 发布前跑一遍带 sanitizer 的构建。悬垂视图在第一毫秒就会被钉在墙上: ```console $ clang++ -std=c++20 -O1 -g -fsanitize=address,undefined -o exorcism_asan main.cpp ghost.cpp $ ./exorcism_asan root ================================================================= ==12345==ERROR: AddressSanitizer: stack-use-after-return on address 0x... READ of size 4 at 0x... thread T0 #0 0x... in std::string_view::compare(...) #1 0x... in is_root(std::string_view) ghost.hpp:8 #2 0x... in main main.cpp:6 ``` > 提示:ASan 默认不检测 use-after-return,需要 `ASAN_OPTIONS=detect_stack_use_after_return=1`。上面的示例中,编译器在 `-O1` 以上就已内联了调用链,所以栈帧复用并不总能被触发——这恰恰解释了为什么它在 `-O0` 下更难被发现。 ### 4. 警惕“比较”本身的语义 与字符串字面量比较时,记得让两侧类型匹配: ```cpp // ❌ view 与 std::string 的隐式转换路径长且易错 bool ok = (name == std::string{"root"}); // ✅ view 与字面量直接比较,零分配、语义清晰 bool ok = (name == "root"); // ✅ 需要大小写不敏感等自定义规则时,显式写出来 bool ok = (name.size() == 4 && strncasecmp(name.data(), "root", 4) == 0); ``` ## 🍬 Trick or Treat?安全清单 万圣节出门讨糖前,先给你的代码做一次巡检: 1. **`std::string_view` / `span` / `string_view` 的所有来源**——它指向的对象能活到使用点之后吗? 2. **所有从函数返回的视图**——返回类型是否绑定了生命周期? 3. **所有手动计算的长度**——`size() + 1`、`claimed_len`、`strlen` 的结果可信吗? 4. **所有安全关键的分支**——有没有在 `-O2` 下被常量折叠掉?打开 `-Wtautological-compare` / `-Wunreachable-code` 看看。 5. **CI 流水线里有没有一个 sanitizer 构建**?没有的话,你的幽灵还住在地下室。 --- ## 写在最后 这可能是我遇见过的最“应景”的 bug:它只在特定的优化等级下现身,平时潜伏在未定义行为的阴影里,把调试版和发布版变成两个截然不同的程序。 C++ 给了你零开销抽象,也给了你零提示的幽灵。`std::string_view` 是一把极其锋利的刀——它不拥有任何东西,也**不会替你保管任何东西**。当你把它从一个临时对象上剥离下来的那一刻,你就签下了一份幽灵契约:未定义行为不会立刻报复你,它会安静地等待,直到某一天某个人打开 `-O2`,然后替你按下那个 `GRANTED`。 祝大家万圣节快乐,愿你的构建永远没有未定义行为。🌙

0 人收藏 0 人点赞
#c++

在有符号32位除法上击败编译器

Lobsters Hottest ↗ · 2026-11-01 缓存

Lemire展示了如何通过64位/33位乘数技巧,在有符号32位整数常量除法上击败编译器,避免GCC/LLVM生成的修正项,并给出了现代CPU上的基准测试延迟数据。

0 人收藏 0 人点赞
#c++

@jakevin7: 新版本会在Chromium的C++ 代码打了一些patch,重新编译了一个 electron,直接内核层做了很多抗检测。 到时候就可以直接访问小红书/Boss直聘了

X AI KOLs Following ↗ · 2026-10-29 缓存

开发者表示其自研 AI 浏览器 Kaze 的新版本将在 Chromium 的 C++ 代码上打 patch 并重新编译 Electron,在内核层加入抗检测能力,从而可直接访问小红书、Boss直聘等站点。

0 人收藏 0 人点赞
#c++

C++ 中每秒支持十亿次查询的查找表

Lobsters Hottest ↗ · 2026-10-28 缓存

介绍 ConstexprCore/perfect_hash 库,可在 C++23 constexpr 编译期生成无冲突的完美哈希查找表,实现每秒十亿次查询的字符串映射性能。

0 人收藏 0 人点赞
#c++

slughorn — GPU 矢量图形

Lobsters Hottest ↗ · 2026-10-27 缓存

slughorn 是一个现代 C++20 GPU 矢量图形库,实现 Slug 渲染技术,支持从 SVG、FreeType、Skia 等导入,或通过 Canvas API 直接绘制,并上传至 OpenGL、Vulkan、WebGPU、DirectX 等后端,在构建时一次性完成栅格化以减少运行时开销。

0 人收藏 0 人点赞
#c++

C++26 DIS 投票结果

Lobsters Hottest ↗ · 2026-10-26 缓存

C++26 未通过 ISO DIS 投票——这是 C++ 草案历史上首次未能通过——契约(contracts)、反射、std::execution 以及库加固(library hardening)均未获批准,仅获得 20 票中的 12 票赞成和 9 票反对。

0 人收藏 0 人点赞
#c++

比 earcut 更快:489 毫秒内对 370 万个地图多边形进行三角剖分

Hacker News Top ↗ · 2026-10-22 缓存

Globus 发布了 earstrip,这是一款采用 ISC 许可证的单头文件 C++ 多边形三角剖分库,并提供 npm/WebAssembly 构建版本。其性能超越 Mapbox earcut——在 C++ 中快 14%,在浏览器中快 1.4–2.1 倍。文章详细介绍了他们如何使用耳切法(ear clipping)在 489 毫秒内完成 370 万个地图多边形的三角剖分,性能优于自研的约束型 Delaunay 三角剖分器。

0 人收藏 0 人点赞
#c++

EDG 开源了

Lobsters Hottest ↗ · 2026-10-20 缓存

EDG 的 C++ 前端于 2026 年 9 月 30 日开源,The C++ Alliance 成为其非营利归属机构,提供财政赞助、技术延续性,并在三轨开发模式下推动社区贡献。

0 人收藏 0 人点赞
#c++

C++ future 第 5 部分:std::future 和 std::shared_future

The Old New Thing (Raymond Chen) ↗ · 2026-10-16 缓存

Raymond Chen 在他的 C++ future 库系列文章的收官篇中,解释了 std::future 和 std::shared_future,以及它们如何操作底层的“源”,并为 future 库的各个组件提出了替代命名建议。

0 人收藏 0 人点赞
#c++

你这个周末打算做什么?

Lobsters Hottest ↗ · 2026-10-16 缓存

一位开发者分享了将 Marginalia 搜索的倒排索引从 Java 移植到 C++ 的进展,指出代码的表达能力有所提升,行数可能减半,而 std::span 在处理连续内存时非常实用。

0 人收藏 0 人点赞
#c++

coopiano:一个用 C++17 编写的物理建模钢琴合成器

Lobsters Hottest ↗ · 2026-10-16 缓存

coopiano 是一个用 C++17 编写的物理建模钢琴合成器开源项目,通过琴槌、琴弦、音板与共鸣模型合成声音,支持 MIDI 文件渲染与 macOS 实时 MIDI 输入,无需任何采样或第三方库。

0 人收藏 0 人点赞
#c++

WIN32_LEAN_AND_MEAN 是怎么来的?(2009)

Lobsters Hottest ↗ · 2026-10-16 缓存

Raymond Chen 解释了 WIN32_LEAN_AND_MEAN 宏的起源:它在 Windows 95 时期被引入,用于在包含 windows.h 时排除大量头文件,以恢复 16 位 Windows 时代的最小化头文件理念并加速编译。如今由于摩尔定律和预编译头文件的普及,该宏已基本失去实际作用。

0 人收藏 0 人点赞
#c++

C++ future 第 4 部分:如果我想要一个立即在当前线程上执行的 std::async,该怎么办?

The Old New Thing (Raymond Chen) ↗ · 2026-10-15 缓存

Raymond Chen 探讨了在 C++ 中创建立即在当前线程上执行的 std::future 的变通方法,包括使用 std::promise、std::packaged_task,或使用延迟执行的 std::async 并立即等待其完成。

0 人收藏 0 人点赞
#c++

C++26 契约:前置条件与后置条件简介

Hacker News Top ↗ · 2026-10-15 缓存

一份关于 C++26 契约特性(P2900)的入门教程,介绍前置条件与后置条件如何取代传统的基于 assert 的检查,并涵盖编译器选项、求值语义及常见陷阱。

0 人收藏 0 人点赞
#c++

C++ futures 第三部分:std::async 的心智模型

The Old New Thing (Raymond Chen) ↗ · 2026-10-14 缓存

Raymond Chen 解释 C++ std::async 的心智模型,说明 launch 策略的两个维度(立即/延迟、同线程/新线程)中哪些组合是标准允许的,哪些被禁止或由实现决定。

0 人收藏 0 人点赞
#c++

# C++ futures 第二部分:理解 `std::promise` 和 `std::packaged_task` 的心智模型

The Old New Thing (Raymond Chen) ↗ · 2026-10-13 缓存

一篇 Raymond Chen 的 C++ futures 系列后续博客文章,阐释了 `std::promise` 和 `std::packaged_task` 背后的心智模型,展示了共享的 "source" 状态如何通过 `shared_ptr` 进行管理,以及 `packaged_task` 如何包装一个 promise 来存储调用结果,以便通过 future 进行检索。

0 人收藏 0 人点赞
#c++

C++ futures 第一部分:心智模型

The Old New Thing (Raymond Chen) ↗ · 2026-10-12 缓存

Raymond Chen 的《The Old New Thing》博客深入解析 C++ 标准库中 std::future 背后的内部实现机制,展示了 promise、packaged_task 和 async 共同依赖的底层 source 状态类的心智模型与代码构建过程。

0 人收藏 0 人点赞
#c++

@GitHub_Daily:很多人想让走廊摄像头自动检测垃圾时,第一反应是去找一个能……

X AI KOLs Timeline ↗ · 2026-10-12 缓存

开源项目 CosmoEdge 发布 v1.1.0,提供 C++ 边缘 AI 引擎,可通过可视化控制台将摄像头视频流、AI 识别、规则判断与通知连成工作流,支持零重训练的提示词识别与视觉大模型判断,一键 Docker 部署至 Sophon、Rockchip 和 x86 平台。

0 人收藏 0 人点赞
#c++

GitHub - giveen/Decapybara:MegaCapybara 推理服务器的开源 C++20 与 CUDA 重构实现

Reddit r/LocalLLaMA ↗ · 2026-10-11 缓存

Decapybara 是 MegaCapybara LLM 推理服务器的开源 C++20 与 CUDA 重构实现,面向 NVIDIA RTX 5090 Blackwell 硬件,支持 OpenAI 与 Anthropic 兼容 API、分层 KV 缓存及一整套 Blackwell CUDA 内核。

0 人收藏 0 人点赞
#c++

C++ Insights – 以编译器的视角审视你的源代码

Hacker News Top ↗ · 2026-10-01 缓存

C++ Insights 是一个基于 Clang 的开源工具,能够执行 C++ 源码到源码的转换,将编译器背后的“魔法”——如特殊成员函数、lambda 表达式、基于范围的 for 循环以及结构化绑定——直观地呈现出来。它帮助开发者和教育工作者以编译器的视角理解代码,并提供在线托管版本可直接使用。

0 人收藏 0 人点赞
Next →
← 返回首页

提交意见反馈