回 6-1 道 6 言語と処理系
ReDoS は、(a+)+$ に
読み終えたら遅くなる
この頁の流れ
正規表現が
遅い 正規表現で、 サービスは 止まるのか
止まります。
ReDoS は
道 6 では 4 回かけて
正規表現は どう動くのか
正規表現は、字を 1 つ読むたびに
ちび電卓の 字句の決まり
ちび電卓の式 x + 12 * 3 は、
| 種類 | 正規表現 | 例 |
|---|---|---|
| num (数) | [0-9]+ | 12、3 |
| name (名前) | [a-z]+ | x、total |
| op (記号) | [-+*/()] | +、* |
[0-9] は+ は[0-9]+ は数字が 1 つ以上並んだ
状態の 機械 (有限オートマトン)
[0-9]+ は、2 つの状態と
この機械は、1 字につき矢印を 1 本たどるだけです。
後戻り型のエンジン
Python の re、PCRE、JavaScript の+ や * で
後戻り型は、\1) のような、
| 動き方 | 1 字あたり | 最悪の手間 | 代表 |
|---|---|---|---|
| 状態の機械 | 矢印を | 入力の | RE2、 |
| 後戻り型 | 分かれ道を順に試す | 指数の | Python の |
動かして確かめる
1 本目は、(?P<num>...) は、m.lastgroup で
動かす ちび電卓の字句を切る
▶ 実行ボタンを押すと結果がここに表示されます
実行すると、次の通りに出ます
('name', 'x')
('op', '+')
('num', 12)
('op', '*')
('num', 3)
('end', None)
止まった位置 212 は 2 字で 1 つの num に("end", None) を# は、
2 本目は、
動かす 数と名前を読む状態の機械
▶ 実行ボタンを押すと結果がここに表示されます
実行すると、次の通りに出ます
123 num
S→NUM→NUM→NUM
abc name
S→NAME→NAME→NAME
12a ×
S→NUM→NUM→DEAD123 は12a は
後戻りは、 なぜ爆発するのか
(a+)+$ のように、
次の(a+)+$ をre と
動かす 後戻りの回数を数える
▶ 実行ボタンを押すと結果がここに表示されます
実行すると、次の通りに出ます
4 False 31 31
8 False 511 511
12 False 8191 8191
13 False 16383 16383
16 False 131071 131071
合う (True, 16)a が 4 つで 31 回、16 個で 131071 回で、
同じ(a+)+$ が
動かす 同じ入力を状態の機械で読む
▶ 実行ボタンを押すと結果がここに表示されます
実行すると、次の通りに出ます
4 False 5
8 False 9
12 False 13
13 False 14
16 False 17
合う (True, 16)状態の
本物の re でも
手元の Python 3.14 で測った秒数
re.match(r"(a+)+$", "a"*n + "b")
n=18 0.008 秒
n=20 0.035 秒
n=22 0.215 秒
n=24 0.742 秒a が 2 つ増える^(([a-zA-Z0-9])+)+$ で 1 字増える
危ない
- くり返しの入れ子
(a+)+、(a+)*、(x\w{1,10})+y。内と外の どちらで 字を 取るかを選べる - 重なる
選択肢のくり返し (.|\s)*。空白はどちらの 枝でも取れる
直し方
直し方の(a+)+$ がa+$ で
書き直せない(?>...) は、a++ は、+ です。re では、
動かす 直した式を確かめる
▶ 実行ボタンを押すと結果がここに表示されます
実行すると、次の通りに出ます
aaaa oooo
aaab xxxx
b xxxx
a+$ None
(?>a+)+$ None
(a++)+$ None短い
式の
| 守り | 効くこと | 限り |
|---|---|---|
| 入力の | 爆発の | 指数の |
先頭を ^ で縛る | 探し始める | 入れ子のくり返しは直らない |
| 後戻りしない | 入力の | 後方 |
| 式を書き直す | 爆発の元を消す | 式ごとに |
RE2 は、
正規表現は 仕事で どう 確かめるのか
仕事では、
- 外から
届く 字 (フォーム、URL、ヘッダー、 ログ) に 当てる式を洗い出す - くり返しの
入れ子と、 重なる 選択肢のくり返しが 無いかを見る - 「合いそうで
最後だけ合わない」 長い 入力 (a を 数十字と b など ) で 試験を 足し、 時間の 上限を置く - 入力の
長さに 上限を 置き、 式に 渡す前に切る - 使う
ライブラリの ReDoS の 告知を 追う。 yamory の ブログは、 Lodash の CVE-2019-1010266 を 例に 挙げています
safe-regex や
次の
関わる実例
1 本の 正規表現が 配信網の CPU を 使い 切った (2019年7月2日)
2019年7月2日、Cloudflare は
事後報告に.*(?:.*=.*) で、.*.*=.* と.* が 2 つ並ぶと、
報告は、
崩れた
出典は Details of the Cloudflare outage on July 2, 2019 (Cloudflare Blog) です。
空白 2 万字の 投稿で Stack Overflow が 止まった (2016年7月20日)
2016年7月20日、Stack Overflow は 34 分止まりました。^[\s\u200c]+|[\s\u200c]+$ に、約 20,000 字の空白が
事後\s+$ を
崩れた
よくある問い
合う 入力でも ReDoS は 起きますか
この頁の (a+)+$ では
Node.js では ReDoS が なぜ特に 問題に なるのですか
Node.js は JavaScript を 1 本のイベントループ (順番に(a+)* や
RE2 に すれば 正規表現は 全部 そのまま 動きますか
動かない
確認クイズ
答えだと
1 問目
後戻り型の(a+)+$ が
2 問目
この
ちび電卓の字句を正規表現で拾う
import re
p = r"[0-9]+|[a-z]+|[-+*/]"
print(re.findall(p, "x+12*y3"))3 問目
状態の
4 問目
(a+)+$ をre で
説明課題
遅くなる
自分の
この回の 先の一歩
入力の
一次資料
どれも 2026年10月27日に開いて、
- Details of the Cloudflare outage on July 2, 2019 (Cloudflare Blog、英語)。日と時刻、27 分、502、
問題の式と .*.*=.*、外れていた守り、3,868 個の規則、re2 か Rust の regex、1968 年の Ken Thompson の論文 - Outage Postmortem - July 20, 2016 (Stack Exchange、
英語、 。34 分の停止、Internet Archive の保存版) 問題の 正規表現、約 20,000 字の空白、 O(n²) で 指数ではない こと、文字列の関数への置き換え - re, Regular expression operations (Python 公式文書、英語)。所有の
量指定子と 原子グループ (?>...)が3.11 で 加わったこと - RE2 README と RE2 Syntax (Google、
英語)。 時間が 入力の 長さに 比例する 約束、 後方 参照、 先読み、 所有の 量指定子、 (?>re)を持たないこと - Don't Block the Event Loop (Node.js 公式、英語)。イベントループ、
避ける 形、 合わない ときに 指数に なる こと、 node-re2 と 互換の注意 - CWE-1333 Inefficient Regular Expression Complexity (MITRE、英語)。分類の
名前と、 後戻りの 試行が 指数に なり得ること - その
正規表現の 。書き方で 大丈夫? ReDoS 攻撃の 怖さと 対策方 法 (yamory、日本語) ^(([a-zA-Z0-9])+)+$で 1 字ごとに約 2 倍、 Lodash の CVE-2019-1010266、 道具の 確かめは 簡易な物であること
次へ
道 6 言語と処理系の回
- 6-1正規表現と
オートマトン後戻りが増える式この回 - 6-2字句解析と構文解析電卓の
式を木にする頁で読む - 6-3小さな
インタプリタ変数と if を持つ言語頁で読む - 6-4型と型検査型が
間違いを 先に止める頁で読む
