正则匹配引发的血案

引子:一家商业IT服务公司,提供给客户的服务突然中断了将近一个小时,事后排查原因,竟然是因为一个正则表达式引起的,小小的正则表达式何以引起如此严重的问题?

事情的原因是由于正则解析导致cpu资源消耗殆尽,引起连锁反应,后续的服务都无法对外提供。引起故障的正则表达式是这样的,“(?:(?:\"|‘|\]|\}|\\|\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\`|\-|\+)+[)]*;?((?:\s|-|~|!|{}|\|\||\+)*.*(?:.*=.*)))”,其中关键的部分是“.*(?:.*=.*)”,需要了解一点正则引擎工作的原理,比如“?:”是一个非捕获组表达(括号中的表达式会被分组聚合成一个单独的表达式)。

为了简化问题,可以将“.*(?:.*=.*)”,简化为“.*.*=.*”,这样的表达式简直复杂到不知所云。任何一个试图表达“匹配任何一个后面跟着任何字符的字符”这样的表达式都会引起灾难性的回撤,这个往往会导致问题。

在正则表达式中,“.”表示匹配一个字符,“*”表示匹配零个或多个字符,并且尽量多的匹配。所以,“.*.*=.*”意味着先匹配这零个或多个字符,接着再匹配零个或多个字符,然后匹配一个“=”,然后再匹配零个或多个字符。

假设有字符串“x=x”,就可以匹配“.*.*=.*”。“.*.*”可以匹配第一个“x”,比如“.*”匹配“x”,另一个“.*”匹配零个字符,最后一个“.*”可以匹配最后的“x”。

这个匹配成功的过程总发生了23次匹配。首先,第一个“.*”匹配了“x=x”中的所有的字符,当引擎尝试去匹配下一个“.*”时,已经没有可以匹配的了,所以直接匹配零个字符,然后引擎尝试去匹配“=”,同样因为没有剩余字符了,所以匹配失败。

这种情况下,引擎就会回撤,只用第一个“.*”匹配“x=”,然后第二个“.*”会匹配成功“x”,同样,当引擎尝试去匹配“=”时又发现没有剩余字符而无法匹配,匹配失败,引擎再次回撤。

这次还是会让第一个“.*”来匹配“x=”,但是第二个“.*”不匹配任何字符,引擎就会接着去匹配“=”,自然匹配失败,然后引擎再次回撤。

接着来,现在第一个“.*”只匹配一个“x”,然后第二个“.*”则匹配成功了“=x”。不用说你也明白了,下来匹配“=”的时候,自然不会成功,再次发生回撤。

再次,“.*”匹配了第一个“x”,然后第二个“.*”匹配了“=”,同样显而易见,正则中的“=”再次无法匹配成功,引擎再次回撤。

我们不去推演每一次匹配了,直接说匹配成功的过程。第一个“.*”匹配成功“x”,然后第二个“.*”匹配零个字符,然后表达式中的“=”匹配成功字符串中的“=”,第三个“.*”会匹配成功“x”,最终表达式和字符串匹配成功,这仅仅是对一个只有3个字符的字符串的匹配过程。

下图是完整的匹配23次的过程,采用的引擎是perl的引擎,你会看到执行的步骤和回撤的过程。

如果字符串从“x=x”变成“x=xx”会发生什么?显然会有更多的回撤发生,事实上,会发生33次匹配的过程,如果字符串变为“x=xxx”则会发生45次匹配,这种匹配次数的增加是非线性的,如果字符串变为“x=xxxxxxxxxxxxxxxxxxxx”(“=”后面有20个x),则匹配过程会变为555次,下图展示了匹配过程。(如果字符串没有了开头的“x=”,引擎会执行4947次,最后发现无法匹配成功)。

下面的动图同样展示了字符串“x=xxxxxxxxxxxxxxxxxxxx”匹配的过程:

这是一种糟糕的情况,当输入参数的长度只有少量增加的时候,耗费的时间却是非线性的大量的增加。如果正则表达式适当修改,情况会更糟。看看这个正则匹配表达式“ .*.*=.*;”,相比上面的表达式,其后面多了一个“;”,这样的表达式可能被用来匹配字符串“foo=bar;”。

这个表达式用来匹配“x=x”就不是发生23次匹配,而是90次,如果采用“=”后面有20个“x”的字符串,则会发生5353次匹配。下面是一张对应的图表,会看到Y轴的增长非常迅速。

同样,下面的动态展示了需要匹配的5353次的过程。

如果在匹配的过程中使用懒匹配策略而不是贪婪策略,则发生回撤的次数会减少。如果将表达式改为“.*?.*?=.*?”,则匹配字符串“x=x”会发生11次匹配(之前是23次)。因为“.*”之后的“?”告诉引擎在匹配下一个模式之前只需要匹配最小的字符就可以。

但是懒惰策略也并不能完全解决问题,比如表达式从“.*.*=.*;”变成“.*?.*?=.*?;”,匹配字符串“x=x”依然要发生555次匹配,匹配“x=xxxxxxxxxxxxxxxxxxxx”依然要发生5353次匹配。

真正的解决方案,如果不想重新匹配模式的话,是需要摆脱正则匹配规则引擎的这种回撤机制。其实类似的事情从1968年Ken Thompson的论文“Programming Techniques: Regular expression search algorithm”就有解决方案。

本文节选翻译自https://blog.cloudflare.com/details-of-the-cloudflare-outage-on-july-2-2019/#appendix-about-regular-expression-backtracking

原文地址:https://www.cnblogs.com/029zz010buct/p/11431628.html

时间: 2024-10-12 05:10:43

正则匹配引发的血案的相关文章

本地Host和域名不匹配引发的血案

本地Host和域名不匹配引发的血案 一.问题及原因分析: 1.页面无法加载,看控制台,发现这些do加载JS文件时找不到. 2.代码中确认对应Do加载时引用的JS路径(即域名或IP: hdm.oper.jd.com ): 3.在Host中找到对应的域名和IP的映射,删除,然后发现页面正常: 二.解决方法: 方法一:代码中直接将JS文件的引用路径改为本地: 方法二:更改Host,使加载js文件时的域名解析到一个可用的正常IP: 三.说明: 这个问题不难,只是自己缺少前台经验,不知道可在浏览器的控制台

版本不匹配引发的血案:javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint

发现一个API报了错: javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint 'javax.validation.constraints.NotEmpty' validating type 'java.lang.String'. Check configuration for 'cancelReason' at org.hibernate.validator.i

iOS 中的正则匹配(工具类)

正则表达式 正则表达式是对字符串操作的一种逻辑公式, 用事先定义好的一些特定字符.及这些特定字符的组合, 组成一个"规则字符串", 这个"规则字符串"用来表达对字符串的一种过滤逻辑, 正则表达式就是用于描述这些规则的工具, 或者说, 正则表达式就是记录文本规则的代码. 在开发中, 我们经常会有查找符合某些复杂规则的字符串的需要, 比如数据校验: 判断用户的输入是否合法(如:用户注册的时候,QQ号码,电话号码,邮箱是否符合要求) 下面让我们先来看看正则匹配常用的一些字

php正则匹配用户名必须包含字母和数字且大于6位

php正则匹配用户名必须包含字母和数字且大于6位 UEditor 1.4.3版本中去掉本地自动保存功能 右键菜单没有新建文本文档txt 常见HTTP错误代码大全 http常见状态码 eclipse内存溢出错误 为什么井盖是圆的?--揭开面试题的神秘面目! Linux Centos 6.6搭建SFTP服务器 密码强度检测 JS判断检测用户输入密码强度代码 对程序员来说,提高薪水最好的建议是什么? CSS3仿淘宝右侧固定导航悬浮层 jQuery仿淘宝网登录拖动滑块验证码代码 jQuery单击div更

利用Python正则匹配中文——爬取校园网公告栏中感兴趣的内容

写这个程序是因为校园网公告栏时不时会有学术报告,讲座之类的信息发布,但这类信息往往发布在讲座的前一天,以至于丢失很多重要消息.同时公告栏里也会发布一些跟学生无关的内容,比如工会主席会议啥的. 主要遇到的困难时对中文的正则匹配问题.(比如通过第一次正则可以提取到一个页面内的所有中文标题,第二次正则从这些中文标题中将能匹配上“报告”两个字的对象添加到结果list内) 学校公告页面是gb2312编码.我使用的方式是,整个工程使用utf-8编码,将需要匹配的关键字转换成utf-8编码格式,使用正则匹配u

js截取相应的域名----正则匹配法 和校验Url 正则表达式

js截取相应的域名----正则匹配法 和校验Url 正则表达式 用javascript截取相应的域名方法两种,供大家参考 1.方法1: [javascript] view plain copy function domainURI(str){ var durl=/http:\/\/([^\/]+)\//i; domain = str.match(durl); return domain[1]; } 调用:var domain=domainURI(document.location.href);

一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁

前续 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(一)——地:起因 一个无锁消息队列引发的血案:怎样做一个真正的程序员?(二)——月:自旋锁 平行时空 在复制好上面那一行我就先停下来了,算是先占了个位置,虽然我知道大概要怎么写,不过感觉还是很乱. 我突然想到,既然那么纠结,那么混乱,那么不知所措,我们不如换个视角.记得高中时看过的为数不多的长篇小说<穆斯林的葬礼>,作者是:霍达(女),故事描写了两个发生在不同时代.有着不同的内容却又交错扭结的爱情悲剧,一个是“玉”的故事,一个是“月”

如何用正则匹配后缀名不为.jpg, .css, .js, .html, .htm, .png的文件

有网友碰到过这样的问题:如何用正则匹配后缀名不为.jpg, .css, .js, .html, .htm, .png的文件,问题详细内容为: 如何用正则匹配后缀名不为.jpg, .css, .js, .html, .htm, .png的文件 ? ,我搜你通过互联网收集了相关的一些解决方案,希望对有过相同或者相似问题的网友提供帮助,具体如下: 解决方案1: /.*\.(?:(?!(jpg|css|js|html|htm|png)).)+/ --- 共有 3 条评论 --- 皮总find . -ty

常用的JavaScript正则匹配规则代码收藏,很实用

收集一些常用的JavaScript正则表达式匹配规则,比如匹配电话号码.Email.中文字符.身份证号.邮编.QQ号.过滤空白行.匹配特定数字等.觉得这玩意是很有用的,只不过自己水平菜,老是自己写不出,看了这个文档,觉得心里踏实很多,用到正则匹配的时候,看下这个,相信为让你省不少时间.注:本正则不仅限于在JS中使用,其它语言也可以借鉴. 匹配特定数字: ^[1-9]\d*$ //匹配正整数 ^-[1-9]\d*$ //匹配负整数 ^-?[1-9]\d*$ //匹配整数 ^[1-9]\d*|0$