专栏名称: IT服务圈儿
关注互联网前沿资讯,提供最实用的学习资源。我们是有温度、有态度的IT自媒体平台。
目录
相关文章推荐
潇湘晨报  ·  长沙2025学年安排来了!暑假从7月6日开始 ·  20 小时前  
新闻株洲  ·  时尚芦淞,元宵节有好戏 ·  昨天  
新闻株洲  ·  株洲营商环境这一做法,全国推广! ·  昨天  
潇湘晨报  ·  暴跌8℃!湖南多地迎降雨 ·  2 天前  
湖南日报  ·  陈思诚,中国影史票房最高导演 ·  3 天前  
51好读  ›  专栏  ›  IT服务圈儿

还记得十几年前PHP那个0x00+2=4的Bug吗

IT服务圈儿  · 公众号  ·  · 2025-01-19 17:30

正文

来源丨经授权转自 经授权转自 计算机奇闻逸事

作者丨胡译胡说

十几年前,在还能因“ PHP 是最好的语言 ”而争论起来、还能在上海举办 PHPCon 的那个时代,记得看到过 0x00 + 2 = 4 这么一个有关十六进制加法的 Bug(https://bugs.php.net/bug.php?id=61095)。

那时,CRUD 似乎就是技术的全部,能自制 PHP MVC 框架(还不是用 C 语言写 PHP extension)就如同站在 PHP 工程师的最高峰了。正如 Redis 的作者 antirez 在一篇名为《What we lost (now that web programming is mainstream)》(当 Web 编程成为主流后我们失去了什么)的博文中的吐槽:

" It was mostly a boring task about constructing web interfaces with a DB as back end, and the actual data processing (that's the computer science part of algorithms and great code) was minimal. ... The most interesting thing remains to write a framework :) (this is why there are so many frameworks around, people like to write them more than actual applications)……

—— http://oldblog.antirez.com/post/what-we-lost.html 2008-04-20


(Web 开发)主要的任务是构建以数据库为后端的 Web 界面,相当无聊,而实际的数据处理工作很少(这才是有关算法和优秀代码的部分,才算是计算机科学)。最有趣的事情是编写框架 :)(这就是为什么有那么多框架,人们更喜欢编写框架而不是实际的应用程序)……”

在当年那种扭曲的认知下,并没有动力深入挖掘这个 Bug 的原因。今天再回过头来分析个中来由,没想到还挺有意思。



TL;DR 省流

原因如下。对于表达式 0x00 + y ,当 + 前后都没有空格时,整个表达式首先会被误识别成一个十六进制数 y ,随后 + 之后的 y 又被正常识别为一个十进制数,导致最后的结果为 y (十六进制) + y (十进制) 。于是就会有 0x00 + 2 -> 2 (十六进制)+ 2 (十进制)= 4 的错误。更多的示例如下图所示。

详细的分析过程

先来确定一下这个 Bug 都影响了哪些版本。在 https://onlinephp.io/ 上,选择

  • 5.1.6

  • 5.2.17

  • 5.3.0

  • 5.3.10

  • 5.3.11

这 5 个版本,运行代码 echo 0x00 + 2 , PHP_EOL ;

可见,受影响的版本范围是 5.3.0~5.3.10,从 2009-06-30 到 2012-04-25,一直存在了小 3 年,直到 5.3.11 才修复。不过老版本 5.1.x 和 5.2.x 倒是没有这个 Bug。那 5.3.0 发布时修改了什么呢?

由于没有下载到 PHP 5.3.0 的 tar 包,下面改为分析 5.3.4

最先想到的原因是词法分析中识别十六进制数的规则发生了改变。于是,对比 5.2.17 和 5.3.4 两个版本词法分析器的相关代码,

除了行号不同,代码竟然一模一样!

不过在 5.3.4 中,在定义 HNUM 的位置,多了这样两行代码,

  1. /*!re2c

  2. re2c:yyfill:check = 0;

这说明 5.3.4 使用了 re2c 作为词法分析器的生成器(lexer generator)。而从这段代码所在的 zend_language_scanner . l 这个文件的扩展名 . l 可以推断出,在之前的版本中,PHP 应该使用的是 lex 或 flex。

查看 5.3.0 版本的 ChangeLog,果然从这一版本开始,PHP 用 re2c 替换了 flex。

Replaced all flex based scanners with re2c based scanners. (Marcus, Nuno, Scott)

—— https://www.php.net/ChangeLog-5.php#5.3.0

八成问题就出在这里吧,先用 gdb 跟踪一下解析十六进制数的代码。

对于 5.2.17,十六进制数的字符串 hex 和其长度 len 的初始值分别为 "00" 2 ,这没错,从 "0x00" 中去掉开头的 "0x" ,剩下的确实是长度为 2 的字符串 "00"

接下来,经过 while 循环去掉所有前导 0 后, hex 变为了空字符串, len 相应变为 0,再经过 strtol () 转换为整数,结果自然为 0。看起来一切正常。

然而到了 5.3.4 中,同样的代码却得到了不同的结果。

请注意,改用 re2c 以后, hex 的初始值不再是 "00" ,而是从 "00" 到行尾的所有代码 + 2 , PHP_EOL ; \n len 的初始值倒是没错,还是 2 。问题就出在这个 hex 的初始值上,经过 while 循环去掉所有前导 0 后, hex 的值不再是空字符串,而是 "+2, PHP_EOL;\n"

尽管 "+2, PHP_EOL;\n" 不是一个仅包含数字的字符串,但 strtol () 的特性是尽量将字符串开头部分的数字转换成对应的整数,直到遇到第一个非数字字符为止。也就是说 strtol ( "+2, PHP_EOL;\n" ) = 2 。这就是为什么 0x00 + 2 = 4 会比正确答案多了 2。

知道了问题所在就很好修复了。5.3.11 中,在调用 strtol () 之前加入了 if (







请到「今天看啥」查看全文


推荐文章
新闻株洲  ·  时尚芦淞,元宵节有好戏
昨天
潇湘晨报  ·  暴跌8℃!湖南多地迎降雨
2 天前
湖南日报  ·  陈思诚,中国影史票房最高导演
3 天前
新世相  ·  爱在“探探”刷完前
7 年前
鲁柏祥博士  ·  2017鲁班大讲堂暨鲁12班开班圆满举行
7 年前