一个数学计算,正在出卖你的操作系统

一个数学计算,正在出卖你的操作系统

浏览器指纹隐私安全Chromium操作系统V8

数据源:HN + web research · HN

2026 年 7 月 12 日,反爬虫技术公司 Scrapfly 的工程师发布了一篇技术博客,揭示了一个令人不安的发现:从 Chrome 148 开始,一个看起来人畜无害的数学函数 Math.tanh(),在不同操作系统上会返回略微不同的结果。也就是说,任何网站只要让你运行一行数学计算,就能判断你用的是 Windows、macOS 还是 Linux。

Scrapfly 博客截图:Math.tanh 在不同 OS 上的返回值差异 ▲ 图片来源:Scrapfly 博客文章截图

这个发现当天就登上了 Hacker News 首页,获得了 207 个推荐和 90 条讨论。开发者社区的反应可以用”惊讶”来形容——大家习惯了浏览器指纹通过 Canvas 绘图、WebGL 渲染、音频处理这些”重武器”来追踪用户,从没想到一个普通的双曲正切函数也能成为识别操作系统的线索。

同一道数学题,三个不同的答案

让我们用一个具体例子来说明。在 Chrome 150 的浏览器控制台中,输入 Math.tanh(0.8),也就是计算 0.8 的双曲正切值,三台不同操作系统的真实机器给出了三个不同的结果:

操作系统Math.tanh(0.8) 的返回值
Linux (glibc)0.6640367702678491
macOS (libsystem_m)0.664036770267849
Windows (UCRT)0.6640367702678489

注意最后几位数字。Linux 比 macOS 多一位,值最大;macOS 比 Windows 少一位,居中;Windows 的值稍小。三者的差异只在最后一位或两位,肉眼几乎无法察觉——但对于计算机来说,这些差异足以构成一个明确的操作系统签名。

有趣的是,不是所有输入都会产生差异。Scrapfly 的测试数据显示:大约四分之三的输入值在三系统上结果完全一致。比如 Math.tanh(0.5),Linux、macOS、Windows 返回的都是 0.46211715726000974。而像 tanh(0.7) 则只有 Linux 的值与另外两者不同,tanh(0.9) 则只有 Windows 独树一帜。tanh(0.8) 恰好是那个能把三者全部区分开来的”甜蜜点”。

Scrapfly 对比表格:不同输入值下三系统的 tanh 返回值 ▲ 图片来源:Scrapfly 博客对比表格截图

这意味着,一个追踪者不需要做什么复杂操作。在网页上跑几次 Math.tanh(),选取几个关键输入值,对比返回结果,就能推断出访客的操作系统。如果访客的 User-Agent 声称自己用的是 macOS,但 tanh 的返回值却是典型的 Linux 结果——那这个访客大概率在伪装身份。

是谁的锅?Bug 还是数学的宿命?

读到这里的读者可能会问:这算不算 Chrome 的一个 bug?

答案比较微妙。它不完全是 bug,但它是一次”修补”带来的意外副作用。

在 Chrome 148 之前,V8 引擎(Chrome 的 JavaScript 执行核心)对 Math.tanh 的实现使用的是自己捆绑的一套数学库,叫 fdlibm。因为所有平台上都用同一套代码,所以无论用户在 Windows、macOS 还是 Linux 上用 Chrome,Math.tanh 的结果完全一致——自然不会泄露任何操作系统信息。

但在 2025 年底,V8 团队提交了一次代码变更(commit c1486295ae5),把 Math.tanh 的实现从自带的 fdlibm 替换成了 C++ 标准库的 std::tanh。这个变更的动机很合理:减少 V8 自身的代码体积,利用操作系统底层已经高度优化的数学库,理论上还能提升性能。该变更随 V8 14.8.57 发布,对应 Chrome 148。

问题在于:不同操作系统底层的数学库(Linux 的 glibc、macOS 的 libsystem_m、Windows 的 UCRT)对于双曲正切函数的实现并不相同。

这是数学上的一个根本性约束。IEEE 754 标准规定了浮点数的存储格式和基本运算(加减乘除、开方)的精度要求,但对于三角函数、指数函数、双曲函数这类”超越函数”,标准并未强制要求”正确舍入”——即保证计算结果精确到最后一个二进制位。原因很实际:实现正确舍入的计算量极大,会严重影响性能。因此,每个操作系统的数学库都有自己的近似算法、系数表和常数,它们的目标是在保证速度的前提下,把误差控制在一个”最小精度单位”(ULP)以内。

所以,Chrome 148 之后 Math.tanh 在不同 OS 上的微小差异,本质上是数学近似算法多样性的体现。它不是一个可以简单”修复”的 bug——这其实是浮点运算领域一个存在了几十年的权衡:速度与精度。只是当这个权衡被暴露在浏览器这个用户界面层时,它意外地变成了一个隐私泄露的通道。

不止是 tanh——一个贯穿整个浏览器的泄露面

更值得警惕的是,Math.tanh 只是冰山一角。

Scrapfly 的博客指出,任何通过宿主操作系统数学库(libm)来计算的浏览器 API,理论上都存在同样的泄露风险。这包括 CSS 中的三角函数(sin()cos()tan() 等),以及 Web Audio API 中的动态压缩器。这些功能都依赖底层操作系统的数学库来进行浮点计算。

换句话说,即使 Chrome 团队修复了 Math.tanh,只要浏览器还有任何一个 API 调用了宿主操作系统的数学函数而没有做统一处理,指纹识别的窗口就依然存在。

这是一场经典的”打地鼠”式军备竞赛。浏览器开发者在努力堵住每一个可能泄露用户身份的缝隙,而追踪者和反爬虫系统在不断寻找新的信号。指纹识别的历史就是一部双方不断发现新战场的历史:从 Canvas 到 WebGL,从字体列表到音频波形,再到如今的数学函数结果差异。每一次开发者堵上一个漏洞,追踪者就会找到下一个看起来完全不可能成为线索的指标。

HN 社区的两极反应

HN 上的讨论呈现了两个截然不同的视角。

一部分开发者认为,这个发现对普通用户的实际影响有限。用户 “Aurornis” 指出,大多数用户并不会伪造自己的 User-Agent,所以通过 tanh 识别操作系统并没有给追踪者带来额外信息——User-Agent 本身就已经告诉网站你用的是哪个操作系统了。他认为,这个漏洞对浏览器版本范围的指纹识别更有意义,但即便如此,它也只是众多指纹信号中的一小块拼图。

另一部分人的视角则完全不同。用户 “jeroenhd” 指出,这个发现对 Scrapfly 这样的反爬虫公司之所以重要,正是因为他们需要让爬虫程序伪装成真实的浏览器。一个运行在 Linux 虚拟机上的爬虫,如果声称自己是 macOS 上的 Chrome,而 tanh 的返回值出卖了它真正的操作系统,反爬虫系统就能轻易识别出这是一个机器人。

笔者倾向于认为,双方都有道理。对于普通的、诚实的浏览器用户,Math.tanh 的泄露确实是多此一举——你的 User-Agent 已经在主动告诉网站你用的什么系统了。但对于试图隐藏身份的用户(无论是出于隐私保护目的,还是出于数据爬取目的),这个新发现的信号意味着:你不仅要伪装 User-Agent,还得伪装数学函数的返回值。

这引出了一个更深层的问题:在一个互联网架构中,有多少我们以为”中性""标准化”的基础设施,实际上在无声地传递着关于我们设备的独特信号?一个数学函数,一行 CSS,一段音频处理——它们本不该成为身份的线索,却因为底层实现的多样性而变成了事实上的追踪标识。

接下来会发生什么?

目前,这个泄露通道影响 Chrome 148、149 和 150。Chrome 团队尚未就此问题公开发表回应。Scrapfly 团队表示,要彻底关闭这个泄露通道,需要让浏览器在每一层(V8、Blink、Web Audio)都使用统一的数学库,或者至少对输出做”抹平”处理。但这样做可能带来性能损失,并且在兼容性和维护层面上有不小的挑战。

对于普通用户,笔者想说:不需要恐慌。这个发现更多是隐私研究领域一个有趣但非紧急的新信号,并非一个会导致你账户被盗的严重安全漏洞。它值得关注,因为它代表了一种趋势——用户的数字足迹正变得越来越难以完全隐藏。

这个故事的真正意义或许在于它揭示了一个更普遍的观察:在软件系统的复杂依赖链中,任何一个看似无关紧要的底层选择,都可能在上层产生意想不到的隐私后果。Chrome 团队的一次代码清理,本来是为了减少冗余、提升性能,却意外地为操作系统识别打开了一扇新窗。在这个意义上,Math.tanh 的故事是一个关于”意图与副作用”的经典案例。

参考链接:

  • Scrapfly: Browser Math OS Fingerprint
  • HN 讨论 (item?id=48884853)