タイトル改めたけど 昨日の続き。

元よりdehydratedの正規表現がマヌケで

$ sed -e 's/=*$//g'

という行末にbase64のパディングある=が存在しなくても文字列置換が発生する、つまり「zero-length matching」ちゅーコーナーケースであることは 初回に説明しました。

前回現場をprocess.cのsubstitute関数内のswitch文でn = 0(global search)の部分という事は察しがついてます。

378 if (!regexec_e(re, s, 0, 0, psl)) 379 return (0); 380 381 SS.len = 0; /* Clean substitute space. */ 382 slen = psl; 383 n = cp->u.s->n; 384 lastempty = 1; 385 386 switch (n) { 387 case 0: /* Global */ 388 do { 389 if (lastempty || match[0].rm_so != match[0].rm_eo) { 390 /* Locate start of replaced string. */ 391 re_off = match[0].rm_so; 392 /* Copy leading retained string. */ 393 cspace(&SS, s, re_off, APPEND); 394 /* Add in regular expression. */ 395 regsub(&SS, s, cp->u.s->new); 396 } 397 398 /* Move past this match. */ 399 if (match[0].rm_so != match[0].rm_eo) { 400 s += match[0].rm_eo; 401 slen -= match[0].rm_eo; 402 lastempty = 0; 403 } else { 404 if (match[0].rm_so == 0) 405 cspace(&SS, 406 s, match[0].rm_so + 1, APPEND); 407 else 408 cspace(&SS, 409 s + match[0].rm_so, 1, APPEND); 410 s += match[0].rm_so + 1; 411 slen -= match[0].rm_so + 1; 412 lastempty = 1; 413 } 414 } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen)); 415 /* Copy trailing retained string. */ 416 if (slen > 0) 417 cspace(&SS, s, slen, APPEND); 418 break;

414行目の2度目のregexec_e関数の呼出がバグの元ということまでは前回までで判明しとりますが、このdo～whileループの中にはどこにもbreakなり脱出コードが無いんですよな。 よってwhileの条件節に直前で現れる

slen > 0

の条件あるいは値自体がおかしいんじゃねという推測がまず立ちます。

実際にslenの値を確認してみましょ

$ gdb --quiet /usr/bin/sed Reading symbols from /usr/bin/sed...done. (gdb) b /usr/src/usr.bin/sed/process.c:413 Breakpoint 1 at 0x404fcf: file /usr/src/usr.bin/sed/process.c, line 413. (gdb) r -e 's/=*$//g' test.txt Starting program: /usr/bin/sed -e 's/=*$//g' test.txt Breakpoint 1, substitute (cp=0x7f7ff7b0b050) at /usr/src/usr.bin/sed/process.c:414 414 } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen)); (gdb) p slen $1 = 18446744073709551615 (gdb) p (ssize_t)slen $2 = -1 (gdb)

うーんこの、クソ古いコードにありがちな size_t underflowですなぁ。

なのでとりあえず

--- process.c.orig 2017-05-03 18:38:13.000000000 +0900 +++ process.c 2017-05-03 18:38:31.000000000 +0900 @@ -411,7 +411,7 @@ slen -= match[0].rm_so + 1; lastempty = 1; } - } while (slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen)); + } while ((ssize_t)slen > 0 && regexec_e(re, s, REG_NOTBOL, 0, slen)); /* Copy trailing retained string. */ if (slen > 0) cspace(&SS, s, slen, APPEND);

というパッチを適用してsedを作り直し、例の問題の正規表現を実行すると

$ echo -n 'A' |sed -e 's/=*$//g' | od -x 0000000 0041 0000002

はい、これで直ったっぽいね。