何もしなくても運が悪いとApacheは落ちる

# TL;DR

Apache2.4.26より前のauth_digest_moduleにはバグがあり、低確率でApacheが死ぬことがある。


# 経緯

ほぼ自分専用で他所からは誰も来ないWebサーバを数年運用しています。

運用というか放置ですが。
開設当初にある程度の設定を行いましたが、その後は何年も更新せずに、それどころかSSH接続することすらほとんどなくほったらかしでした。
私は環境構築マニアではないので、現在動いているサーバには何もしたくないのです。
セキュリティ？知らんな。

そんなでもこれまで数年間何の問題もなく動いていたのですが、なにやら先日突然Apacheが止まりました。


# 調査

とりあえずApacheのステータスを確認。

```bash
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: failed (Result: exit-code) 
     Docs: man:httpd(8)
           man:apachectl(8)
  Process: 123708 ExecStop=/bin/kill -WINCH ${MAINPID} (code=exited, status=1/FAILURE)
  Process: 123702 ExecReload=/usr/sbin/httpd $OPTIONS -k graceful (code=exited, status=0/SUCCESS)
  Process: 32974 ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND (code=exited, status=1/FAILURE)
 Main PID: 32974 (code=exited, status=1/FAILURE)
   Status: "Total requests: 0; Current requests/sec: 0; Current traffic:   0 B/sec" 
 systemd[1]: Reloaded The Apache HTTP Server.
 systemd[1]: Reloaded The Apache HTTP Server.
 systemd[1]: Reloaded The Apache HTTP Server.
 systemd[1]: Reloaded The Apache HTTP Server.
 systemd[1]: Reloaded The Apache HTTP Server.
 systemd[1]: httpd.service: main process exited, code=exited, status=1/FAILURE
 kill[123456]: kill: cannot find process "" 
 systemd[1]: httpd.service: control process exited, code=exited status=1
 systemd[1]: Unit httpd.service entered failed state.
 systemd[1]: httpd.service failed.
```

止まってますね。
つぎにエラーログを確認。

```bash
# tail /var/log/httpd/error_log
[auth_digest:notice] [pid 32768] AH01757: generating secret for digest authentication ...
[auth_digest:error] [pid 32768] (17)File exists: AH01762: Failed to create shared memory segment on file /run/httpd/authdigest_shm.32768
[auth_digest:error] [pid 32768] (17)File exists: AH01760: failed to initialize shm - all nonce-count checking, one-time nonces, and MD5-sess algorithm disabled
[:emerg] [pid 32974] AH00020: Configuration Failed, exiting
```

なんだこれは？
調べてみると、どうやらmod_auth_digestが共有メモリを作りに行ったけど、ファイルが既に存在していたのでエラーになりました、ということらしい。
そもそもダイジェスト認証なんて使ってないんだけど、LoadModuleで呼ばれているせいかな。
わざわざコンフィグファイルのシェイプアップなんてしてなかったと思うから、そのせいでしょう。

ipcsコマンドで共有メモリファイルを確認できます。

```bash
# ipcs

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x00000000 0          root       600        327680     6          dest
0x01234567 16777214   root       600        1000       0
…
```

ファイルが100個くらい並んでいました。
逆に言うと、たった100個程度でファイル名が被ってApacheが落ちるなんてのは明らかにおかしいでしょう。

というわけで調べてみたら[8153365579e603ec0c50c7f2c27f4a06f927eddd](https://github.com/apache/httpd/commit/8153365579e603ec0c50c7f2c27f4a06f927eddd)で修正されていました。
修正自体は[2015/06/10に行われた](https://svn.apache.org/viewvc?view=revision&revision=1684636)みたいですが、それがマージされたのが何故か2年も経った[2017/02/08](https://svn.apache.org/viewvc?view=revision&revision=1782175)で、そしてリリースされたのは[2017/06/19のApache2.4.26](https://forest.watch.impress.co.jp/docs/news/1066320.html)のようです。

すなわち、それ以前のApacheには、低確率で脈絡なく突如Apacheが落ちる潜在的バグが潜んでいるということです。


## 原因

auth_digest_moduleは、[apr_shm_create](https://apr.apache.org/docs/apr/trunk/group__apr__shm.html#gac370c4943c22505ce2b0d57c51805480)という関数を使って共有メモリファイルを生成しています。
このときに第三引数で対象のファイル名を与えているのですが、そのファイル名の生成方法がこれ。

```c
    client_shm_filename = ap_runtime_dir_relative(ctx, "authdigest_shm");
    client_shm_filename = ap_append_pid(ctx, client_shm_filename, ".");
```

[ap_append_pid](https://ci.apache.org/projects/httpd/trunk/doxygen/group__APACHE__CORE__DAEMON.html#gaad903051d63a6821f55be9bbbe5e29b0)は、テキストの後ろにpidをくっつけるという関数です。
pidは`ps -ef`とかで出てくるやつで、ランダム性など微塵もない概ね連番になってる数桁の数字です。
たとえば、PIDが`32768`であれば共有メモリファイル名は`authdigest_shm.32768`になります。

まとめると、たまたま前回と同じPIDでmod_auth_digestが起動するとApacheが死にます。

おそらくはauth_digest_moduleの終了時に共有メモリファイルも削除するみたいな処理が入っていると思うのですが(確認してない)、[何かの拍子にファイルが残ったままになってしまう](https://github.com/openstreetmap/mod_tile/issues/166)ことがあり、そのときに次のauth_digest_moduleが同じPIDで起動すると死ぬということのようです。


## バグ修正

ファイル名にランダム要素を加えたりするのかと思いきやそうでもなく、単に共有メモリファイルを削除してから同じファイルを作り直すという原始的対応をやってました。
そんなんでいいのかよと思ったのですが、[mod_lua](https://github.com/apache/httpd/blob/2.4.46/modules/lua/mod_lua.c#L2019)やら[util_ldap_cache](https://github.com/apache/httpd/blob/2.4.26/modules/ldap/util_ldap_cache.c#L423)やらも概ね同じ作りになっていて、どうやれこれで正しいみたいです。


# 対応

設定ファイルから`LoadModule auth_digest_module`を削除した。

アプデしろ？
知らんな。


# 感想

世界でもっとも有名なソフトウェアのひとつに、わりと最近までこんな致命的なバグが潜んでいたことにびっくりだよ。
しかもそのこのバグの存在自体がネット上にほぼ存在せず、それが修正されたという情報も全く見当たらないことに再度びっくりだよ。

日本語情報はほぼ見当たらず、わずかに存在する情報もせいぜい共有ファイルを削除したまでしか実施しておらず、根本的な解決まではしていないものばかりでした。
まあ英語でも質問に回答が全くついてないとか解決策が見当違いとかばっかりで、きちんと原因まで辿り着いてる資料はほとんどなかったのですが。

ところでApache2.5αに入ってる[mod_noloris](https://github.com/apache/httpd/blob/2.5.0-alpha/modules/experimental/mod_noloris.c)にも[今回とほぼ同じバグ](https://github.com/apache/httpd/blob/2.5.0-alpha/modules/experimental/mod_noloris.c#L169)が入っているように見えるのですが、これは大丈夫なのだろうか？

# その他

`ipcrm -a`でApacheを即座に落とせるぞ。

特に意味はないが。