カーネルのゼロページバグ


さて、SL-A300 の ROM ver1.1 で確認した BIOS or カーネルの ZERO_PAGE バグの件。 Nov. 29 にML になげているので、 Dec. 7 に make されている B, C にはないかもしれないし、あるかもしれない。 SL5000系にないことはわかっているが、A300 の 1.2 や B500, C700 でどうだかは知らない。 各々で確認されたし。

テストプログラム

内容はこんなの:
#include <stdio.h>
#define COUNT 10000
struct big {
  int dummy[COUNT];
} data;

int main(){
  int i;
  for(i = 0;i < (COUNT - 1); i++){
    int tmp;
    if(data.dummy[i] != 0) {
      tmp = data.dummy[i];  // dummy page から読み込み。
      data.dummy[i+1] = 0;  // copy on write 起動。
      printf("%06x %p %08x %d\n", i, &data.dummy[i], tmp, data.dummy[i]);
    }
  }
  return 0;
}
実行すると、SL5x00 では何もかえってこない(正しい挙動だ)が、 ノーマルカーネルの A300 で実行すると:
#./zeropage_test
000288 0x11000 a0000000 0
000688 0x12000 a0000000 0
000a88 0x13000 a0000000 0
000e88 0x14000 a0000000 0
001288 0x15000 a0000000 0
001688 0x16000 a0000000 0
001a88 0x17000 a0000000 0
001e88 0x18000 a0000000 0
002288 0x19000 a0000000 0
002688 0x1a000 a0000000 0
暗黙のうちにゼロ初期化されているはずの領域がゼロでなく、その非ゼロになってるところの近所に 何か書きこむと非ゼロになっていた領域がなぜかゼロに戻る。そしてその非ゼロは 4kB 毎に綺麗に並ぶ。

なお、後述する ZERO_PAGE が非ゼロに汚染されるのは suspend/resume の間なので、 ブート後、一度も suspend してない A300 では上のテストではやはり何もかえってこない。

ZERO_PAGE マップの挙動

プログラムを起動すると、BSS 領域 (明示的に初期化されていないデータ域) には ZERO_PAGE がとりあえずマップされる。

ZERO_PAGE は、本来は中身がすべてゼロのページだ。 BSS 内の read は確かに 0 を返すようになる。これによって 「明示的に初期化されていない大域変数はゼロでなければならない」という規定を満たす。

... が、一度以上 suspend した A300 では、この ZERO_PAGE が 0 でなくなっている (具体的には先頭ワードが 0xa0000000 になっている; とっても見覚えのある magic number なんだが、 その考察は省略する) ため、 BSS 内の配列を順繰りに読むと上で見たようにページ長毎 (= 4kB 毎) に非ゼロが現れる。

この状況で write が発生するとどうなるか ──

プログラム側から ZERO_PAGE への書き込みは禁じられている。 重複してページマップしているんだから当然だ。 書き込みと同時に旧ページの内容が新しいページにマップされ、そちらに書き込まれる。 ... というのが教科書的なコードだが、 「ZERO_PAGE は内容がゼロ」のはずなので、 新しいページは旧ページ(つまり ZERO_PAGE) からの memcpy でなく memset で一気にゼロクリアされてしまっている。 このため、ZERO_PAGE を指さなくなった領域は、 もともとが正しい ZERO_PAGE をさしていたがごとくの挙動に戻ることになる。

バグの発現条件

ページのどこかへの write でページ全体がゼロクリアされてしまうので、 バグが発現するのはアプリが起動してすぐのことのはずだ。 起動後ただちに segmentation fault をおこして落ちるアプリが、マシンのブート直後に 動かした場合だけ落ちないのなら、このバグにかかっている。
それを確認するには gdb なり strace なりで segmentation fault を起こした理由が 0xa0000000 への書き込みにあることをみればいい。

いまのところ w3m, dpkg, mule で起きることが分かっている。

対策カーネル

根本的な対策には BIOS ソースが必要だと思うので、てきとーに。 resume 時に ZERO_PAGE が非ゼロになるというなら、この時点で ZERO_PAGE をクリアしてしまえばいい ... てなパッチはすでに書いたが、パッチ済みバイナリも。 どーせ ROM version 1.2 が一週間以内に出るはずだが、reference として。

パッチ k1 時点で指摘のあった CFE-01 が動かん (11/13) という件は、k2 でそれらしいところは戻したけど、なにぶん CFE-01 もってないので確認できない。 あしからず mOm

追記

だそーだ。 後者に関しては自分でも確認したが ... たしかになおってねーよーだな。

さらに追記〜

Ver 1.30J で直ったことを確認。

ついでに言えば、やっぱり BIOS のバグだったらしい。 つまり、ver 1.30J の all.nb0 を入れたあとに 1.20J の無対策カーネルを入れても正常動作した。


[日記へ] [目次へ]