Ghostscript脆弱性とImageMagick/GraphicsMagick、そしてGoogle Project Zero

Ghostscriptの脆弱性が、Google Project ZeroのTavis Ormandy氏により公開されました(CVE番号はまだ無し)。openwallのoss-securityメーリングリストにもクロスポストされたので、こちらを見た方も多いでしょう。

基本的な情報は以下のJPCERTアナウンスから出ているので、ここではもうちっと細かいTopicsをいくつかまとめます。

概要

PostScriptは、単なるドキュメントファイルの一型式ではなく、描画コマンドなども利用できるプログラミング言語です。そのためGhostscriptには、-dSAFERという「キケンなこと」ができないようにするモードがあります。ところが、この-dSAFERを付けていても任意コード実行が可能な穴があった……というのが今回の脆弱性のキモです。

脆弱性のパッチは、開発元のArtifex Softwareから既に提供されています。正式な次バージョンのリリースは9月になるようです。

ただ、上記Project ZeroのTavis氏の発言によると、この修正は不十分であり、また現在Fuzzerを回しておりまだまだ見つかりそうだとのことから、さらなる追加修正が入りそうです。

ImageMagickも対象である

今回の脆弱性はGhostscriptの脆弱性ですが、ImageMagickもGhostscriptに依存しているため影響を受けます。そのためWebアプリでユーザに画像をアップロードしてもらう等、画像処理をImageMagickで行うプログラムではすべて対応が必要です。

実際、以下のようにImageMagickで細工したPSファイル(VU-332928.ps)を扱うとLinuxコマンドが実行されており、脆弱性の影響を受けることが分かります。
なお以下の例では、idコマンドを実行しているので、uid等が表示されています。もちろん、もっとヒドいこともできます。

$ convert VU-332928.ps test.png
uid=1000(ozuma) gid=1000(ozuma) groups=1000(ozuma),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
convert: `%s' (%d) "gs" -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 "-sDEVICE=pngalpha" -dTextAlphaBits=4 -dGraphicsAlphaBits=4 "-r72x72" -g612x792  "-sOutputFile=/tmp/magick-GrMdTtj4--0000001" "-f/tmp/magick-4q5WILKb" "-f/tmp/magick-th3RM7bj" -c showpage @ error/utility.c/SystemCommand/1890.
convert: Postscript delegate failed `VU-332928.ps': そのようなファイルやディレクトリはありません @ error/ps.c/ReadPSImage/832.
convert: no images defined `test.png' @ error/convert.c/ConvertImageCommand/3046.
$ 

気をつけないといけないのは、この脆弱性はPSファイルを読み込んでパースした時点で発生するということです。具体的には、ImageMagickでconvertをしていなくても、identifyでファイルタイプ判別のみに利用していても、脆弱性の対象となります。下記でも、ファイルに仕込んだidコマンドが実行されてしまっています。

$ identify VU-332928.ps 
uid=1000(ozuma) gid=1000(ozuma) groups=1000(ozuma),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
identify: `%s' (%d) "gs" -q -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 "-sDEVICE=pngalpha" -dTextAlphaBits=4 -dGraphicsAlphaBits=4 "-r72x72" -g612x792  "-sOutputFile=/tmp/magick-ItbgeaDQ--0000001" "-f/tmp/magick-59biIgLS" "-f/tmp/magick-KUgSVrTU" -c showpage @ error/utility.c/SystemCommand/1890.
identify: Postscript delegate failed `VU-332928.ps': そのようなファイルやディレクトリはありません @ error/ps.c/ReadPSImage/832.
$

また、例えばPythonのライブラリであるPythonMagickでは、Image()メソッドでファイルを読み込むだけで発動します。つまり以下のように、ファイルを読み込むだけで何もしないスクリプト脆弱性の影響を受けます。

#!/usr/bin/python

# coding=UTF-8
import PythonMagick
img = PythonMagick.Image("VU-332928.ps")
ozuma@ubuntu17:~$ ./vul.py VU-332928.ps 
uid=1000(ozuma) gid=1000(ozuma) groups=1000(ozuma),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)
Error: /ioerror in --showpage--
Operand stack:
   --nostringval--   1   true
Execution stack:
   %interp_exit   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--   --nostringval--   --nostringval--   false   1   %stopped_push   .runexec2   --nostringval--   --nostringval--   --nostringval--   2   %stopped_push   --nostringval--   1874   1   4   %oparray_pop   --nostringval--   --nostringval--
Dictionary stack:
   --dict:1215/1684(ro)(G)--   --dict:0/20(G)--   --dict:78/200(L)--   --dict:88/91(L)--
Current allocation mode is local
Last OS error: Broken pipe
GPL Ghostscript 9.21: Unrecoverable error, exit code 1
$

ImageMagickでの対策は、JPCERT等からアナウンスされている通り、policy.xmlで処理を禁止するファイルを指定することです。

具体的には、以下のようにPS/EPS/PDF/XPSを禁止することになります。ちなみにPS2PS3とは、PostScript Level 2とLevel 3です。

<policy domain="coder" rights="none" pattern="PS" />
<policy domain="coder" rights="none" pattern="PS2" />
<policy domain="coder" rights="none" pattern="PS3" />
<policy domain="coder" rights="none" pattern="EPS" />
<policy domain="coder" rights="none" pattern="PDF" />
<policy domain="coder" rights="none" pattern="XPS" />

GraphicsMagickでの対策

GraphicsMagickも内部でGhostscriptを利用しているため本脆弱性の影響を受けます。以下のように、identifyするだけでidコマンドが実行できてしまっています。convertも同様に脆弱性の影響を受けます。

$ ./gm identify VU-332928.ps 
uid=1000(ozuma) gid=1000(ozuma) groups=1000(ozuma),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
./gm identify: "gs" "-q" "-dBATCH" "-dSAFER" "-dMaxBitmap=50000000" "-dNOPAUSE" "-sDEVICE=pnmraw" "-dTextAlphaBits=4" "-dGraphicsAlphaBits=4" "-r72x72" "-g612x792" "-sOutputFile=/tmp/gm8OJn56" "--" "/tmp/gmBWNNro" "-c" "quit" (child process quit due to signal 13).
./gm identify: Postscript delegate failed (VU-332928.ps).
./gm identify: Request did not return an image.
$

さてImageMagickと対比されることで有名なGraphicsMagickですが、policy.xmlに除外ファイルを書けば済むImageMagickと違い、こちらの対策は厄介です。
なぜなら、GraphicsMagickには、ImageMagickのようなpolicy.xmlファイルによる除外機能が無く、除外ファイルを一括指定できないためです(この辺、素直にImageMagickの設計をパクればいいのに……ってみんな思ってる。たぶん)。

GraphicsMagickでは、代わりにdelegates.mgkというファイル修正で対策が行えます。これは、GraphicsMagickのメーリングリストで、メンテナのBob Friesenhahn氏も話題に挙げています。

具体的には、以下のパスにあるdelegates.mgkファイルを修正します。

lib/GraphicsMagick-X.X.XX/config/delegates.mgk
 (X.X.XXの部分はバージョン番号)

まずは現在の状態を確認するために、-listで見てみましょう。

$ ./gm convert -list delegate
Path: /home/ozuma/local/GraphicsMagick-1.3.30/lib/GraphicsMagick-1.3.30/config/delegates.mgk

Delegate             Command
-------------------------------------------------------------------------------
     cgm =>          "ralcgm" -d ps < "%i" > "%o" 2>/dev/null
   dcraw =>          "dcraw" -c -w "%i" > "%o"
     dot =>          "dot" -Tps "%i" -o "%o"
     dvi =>          "dvips" -q -o "%o" "%i"
     eps<=>pdf       "gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE
                      -sDEVICE=pdfwrite "-sOutputFile=%o" -- "%i" -c quit
     eps<=>ps        "gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE
                      -sDEVICE=ps2write "-sOutputFile=%o" -- "%i" -c quit
     fig =>          "fig2dev" -L ps "%i" "%o"
     hpg =>          "hp2xx" -q -m eps -f `basename "%o"` "%i" && /usr/bin/mv
                      -f `basename "%o"` "%o"
    hpgl =>          "hp2xx" -q -m eps -f `basename "%o"` "%i" && /usr/bin/mv
                      -f `basename "%o"` "%o"
     htm =>          "html2ps" -U -o "%o" "%i"
    html =>          "html2ps" -U -o "%o" "%i"
    ilbm =>          "ilbmtoppm" "%i" > "%o"
    mpeg =>          "mpeg2decode" -q -b "%i" -f -o3 "%u%%05d"; gm convert
                      -temporary "%u*.ppm" "miff:%o" ; rm -f "%u"*.ppm 
     pdf<=>eps       "gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE
                      -sDEVICE=epswrite "-sOutputFile=%o" -- "%i" -c quit
     pdf<=>ps        "gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE
                      -sDEVICE=ps2write "-sOutputFile=%o" -- "%i" -c quit
     pnm<= ilbm      "ppmtoilbm" -24if "%i" > "%o"
     pnm<= launch    "gimp" "%i"
     pnm<= win       "gm" display -immutable "%i"
      ps<=>eps       "gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE
                      -sDEVICE=epswrite "-sOutputFile=%o" -- "%i" -c quit
      ps<=>pdf       "gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE
                      -sDEVICE=pdfwrite "-sOutputFile=%o" -- "%i" -c quit
      ps<= print     "no -c -s" "%i"
   shtml =>          "html2ps" -U -o "%o" "%i"

このように、各形式に対して実行するコマンドテーブルが用意されています。ここから、Ghostscriptの実行コマンドである"gs"を含むものを全部消してやればいいわけです。

delegates.mgkの中から、以下のようにgsコマンドが使われている部分をすべて削除します。

...(省略)...
  <!-- Read monochrome Postscript, EPS, and PDF  -->
  <delegate decode="gs-mono" stealth="True" command='"gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE -sDEVICE=pbmraw -dTextAlphaBits=%u -dGraphicsAlphaBits=%u -r%s %s "-sOutputFile=%s" -- "%s" -c quit' />

  <!-- Read grayscale Postscript, EPS, and PDF  -->
  <delegate decode="gs-gray" stealth="True" command='"gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE -sDEVICE=pgmraw -dTextAlphaBits=%u -dGraphicsAlphaBits=%u -r%s %s "-sOutputFile=%s" -- "%s" -c quit' />

  <!-- Read colormapped Postscript, EPS, and PDF  -->
  <delegate decode="gs-palette" stealth="True" command='"gs" -q -dBATCH -dSAFER -dMaxBitmap=50000000 -dNOPAUSE -sDEVICE=pcx256 -dTextAlphaBits=%u -dGraphicsAlphaBits=%u -r%s %s "-sOutputFile=%s" -- "%s" -c quit' />
...(省略)...

修正できたら、もう一度gm convert -list delegateを実行して、gsコマンドが使われない状態であることを確認してください。
なお、これはImageMagickもそうですが、そもそもPS/EPS/PDF/XPSファイルが扱えなくなってしまう問題が発生しますので、サービスによっては多大な影響が発生する点に注意してください。

その他の対策としては、そもそもGhostscriptをアンインストールしてしまう、gsコマンドの実行権限を落としてしまう、などの荒技も一応考えられますね。

ファイル形式をチェックしてファイル入力時点でブロック

保険的対策として、画像処理をするプログラムに渡す前に、ファイル形式をチェックしてPS/EPS/PDF/XPSならばエラーとして弾く、というのも有効です。なお、別の入力経路があるかもしれないことと、ファイル形式を偽装する攻撃手法があるので、あくまで保険的対策です。

しかしこの際、どのようにファイル形式をチェックするかは注意が必要です。

まず、当然のことながら拡張子で判断してはいけません。拡張子を.jpgとした悪意のあるPSファイルをアップロードされたら、チェックをすり抜けて試合終了です。

じゃぁImageMagickのidentifyを利用して画像形式を判別して……と思いつきそうですが、冒頭に記載した通り本脆弱性はidentifyしただけで発動します。また、今後も出るであろうGhostscript+PSファイルの脆弱性も、PostScriptファイルの性質からして、おそらくidentifyするだけで発動します。

ではどうすればいいか……正攻法の一つには、WAF(Web Application Firewall)の利用が挙げられます。例えばSaaS型WAFのScutumは対応が早く、8月23日時点で対応していました。

一方、プログラマとして対応する場合は、例えばPythonには、mimetypesというモジュールがあり、このguess_type()メソッドを利用することでMIME Typeが判別できます。これで「application/postscript」等を叩き落とせばいいですね。(2018/08/27追記:mimetypesモジュールは、拡張子を見るだけなのでダメでした。ご指摘いただいた id:penult さん、ありがとうございます。)
一方、プログラマとして対応する場合は、fileコマンドを使ったり、magicファイル(ややこしいですが、ImageMagickとは関係なく、ファイルタイプ判別のmagicファイルのこと)を利用する言語ごとのファイル形式判別ライブラリを使うと良いでしょう。例えばPythonには、python-magicというモジュールがあります。

なお、このように特定のファイル形式を叩き落とす場合は、ブラックリスト型ではなくホワイトリスト型の設計とするのが基本です。例えば画像アップローダのサービスならば、はじめから受け付ける画像形式を決めて(例えばjpg/png/gifのみ等)、それ以外は全てエラーとする、とすべきです。意図しない画像形式がアップロードされた時の誤作動を防ぐことができます。

今回のゼロデイ公開について

Google Project Zeroは、いわゆる「90日ルール」を設定しています。これは、発見した脆弱性をベンダへ通知した後に90日のdeadlineを設定し、修正されない場合は強制的に脆弱性を公開するというものです。

実際にどう運用されているかは、チケットを見てみると分かりやすいかと思います。

上記ポストの最後に、以下の文があります。

This bug is subject to a 90 day disclosure deadline. After 90 days elapse or a patch has been made broadly available, the bug report will become visible to the public.

さて今回、Ghostscriptを開発しているArtifex Software社は、Google Project Zeroから報告を受けたけど90日以内に直せなかったのでしょうか? これは事実関係がどこにも書かれていないので推測でしかありませんが、「そもそも報告を受けていない」と捉えるのが自然です。

Artifex Software社は現在既にパッチをリリースしていますが、そのアナウンスの最後に以下のような文章を掲示しています。

Artifex takes security issues very seriously and strongly encourages responsible and coordinated disclosure of vulnerabilities. Developers should be given the opportunity to fix security problems in advance of public disclosure.

(意訳)Artifex Softwareは、セキュリティ情報を非常に重要なものとして取り扱っています。頼むからいきなりZero-Day公開しないで、ちゃんと猶予をくれ。

https://artifex.com/news/ghostscript-security-resolved/

さて、今回のゼロデイ公開は、Google Project Zeroの以下のポストです。

こちらの"Reported"は、2018-08-21になっています。うーん、90日ルールはどこへ行ってしまったのでしょうか。

Google Project Zeroは、マネージャーのParisa Tabriz氏が先日のBlack Hat USA 2018でKeyNoteを努めました。セキュリティに対する熱い思いを語り、Google Project Zeroの理念を語っていた姿に感動した私は、正直「むむむ?」というフクザツな気分です。

脆弱性を事前通知したのかどうか、はっきりしないため誤解があったらすみません。

(何かあれば加筆する)

個人でEV SSL証明書は取れるのか (2)

以前に、個人でEV SSL証明書は取れるのか (1) - ろば電子が詰まっているとして、個人でEV SSL証明書をなんとか取れないかという話を書きました。

結局その後、ローカル認証局を立ててFirefoxのソースをビルドしてEV SSL証明書を作る……ということをやって勉強会で発表しました。

が、スライドに書いてある通り、結局EV SSL証明書として認識させることはできず歯切れの悪い感じになっちゃいました。その続きです。

なぜダメだったのか

その後も色々いじっていたのですが、@angel_p_57さんが見事に解決してくださいました。ありがとうございます!

結論から言うと、FirefoxではOCSPレスポンダへの問い合わせを省略する設定があるのですが、これをすると全てのEV SSL証明書がEVと認識されなくなります。OCSPレスポンダを立てるのが面倒だったのでこれをオフにしてテストしており、そのせいだった模様……。

上記のangel_p57さんの記事は、FirefoxでどのようにEV SSL証明書を検証しているか、ソースを追って細かく解説しており大変参考になります。ぜひ一読をオススメします。angel_p57さん、ありがとうございました! この場を借りてお礼申し上げます。

Firefoxと認証局証明書(信頼されたルート証明機関)

Firefoxは、Chromeとは違い、デフォルトで信頼する認証局(トラストストア;Windowsで言う「信頼されたルート証明機関」)が内部にハードコードされている。一方、ChromeはWinでもMacでもOSのトラストストアを参照する。

ということで、Firefoxソースコードからこのリストを取り出そうとしたのだけど、いろいろ面倒だったのでメモ。特にOpenSSLはトラストストアを持たないため、Firefoxの(すなわちMozillaの)トラストストアを流用するケースなどを想定。

Firefoxのソースと認証局証明書

Firefoxのデフォルトの認証局証明書は、ソースツリーでは以下のcertdata.txtにある。

しかし上記ファイルは、次のように一般的な形式(PEMなど)では書かれておらず、OpenSSLなど他のアプリでそのまま利用することはできない。

#
# Certificate "GlobalSign Root CA"
#
# Issuer: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
# Serial Number:04:00:00:00:00:01:15:4b:5a:c3:94
# Subject: CN=GlobalSign Root CA,OU=Root CA,O=GlobalSign nv-sa,C=BE
# Not Valid Before: Tue Sep 01 12:00:00 1998
# Not Valid After : Fri Jan 28 12:00:00 2028
# Fingerprint (MD5): 3E:45:52:15:09:51:92:E1:B7:5D:37:9F:B1:87:29:8A
# Fingerprint (SHA1): B1:BC:96:8B:D4:F4:9D:62:2A:A8:9A:81:F2:15:01:52:A4:1D:82:9C
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "GlobalSign Root CA"
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
CKA_SUBJECT MULTILINE_OCTAL
\060\127\061\013\060\011\006\003\125\004\006\023\002\102\105\061
\031\060\027\006\003\125\004\012\023\020\107\154\157\142\141\154
\123\151\147\156\040\156\166\055\163\141\061\020\060\016\006\003
\125\004\013\023\007\122\157\157\164\040\103\101\061\033\060\031
\006\003\125\004\003\023\022\107\154\157\142\141\154\123\151\147
\156\040\122\157\157\164\040\103\101
END
CKA_ID UTF8 "0"
CKA_ISSUER MULTILINE_OCTAL
\060\127\061\013\060\011\006\003\125\004\006\023\002\102\105\061
\031\060\027\006\003\125\004\012\023\020\107\154\157\142\141\154
.....(省略).....

これは、Firefoxのビルド時にこのcertdata.txtからcertdata.cが作られてコンパイルされるため、Cで扱いやすい形式で書かれた歴史的理由による(たぶん)。ということでこれを解決するにいくつかの方法がある。

curlプロジェクトの配布物を利用

curlhttpsにも対応していることから、トラストストアと密接に関連している。

ということで、curlプロジェクトが独自にcertdata.txtをPEMに加工したものを配布している。

これは比較的定期的に更新されており、ちゃんとメンテナンスされているため、特に理由が無ければこれを使えばいい。しかし、世の中にはもちろん、「オレはcurlプロジェクトは信頼ならん」という人もいるだろうから、そういう人は自分でcertdata.txtを加工する必要がある。

変換ツールを利用(Go-lang)

Adam Langley氏の書いた以下のツールが有名で使いやすい。

READMEに書かれているとおり、certdata.txtを置いたディレクトリでgo run convert_mozilla_certdata.goするだけ。

もちろん、Adam Langley氏も信用ならんということであれば、自分でツールを書くしかない。この際にはまる罠を紹介しておく。

Distrustの罠

自前でcertdata.txtの加工スクリプトを書く場合、ハマりがちなのがこのDistrustの罠である。

certdata.txtは、やっかいなことに「信頼されない(Distrust)証明書」もまぜこぜにして格納されている。有名なモノは、侵入者により偽証明書が発行されてしまったDigiNotarのものである。

#
# Certificate "Explicitly Distrust DigiNotar Root CA"
#
# Issuer: E=info@diginotar.nl,CN=DigiNotar Root CA,O=DigiNotar,C=NL
# Serial Number:0f:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff
# Subject: E=info@diginotar.nl,CN=DigiNotar Root CA,O=DigiNotar,C=NL
# Not Valid Before: Fri Jul 27 17:19:37 2007
# Not Valid After : Mon Mar 31 18:19:22 2025
# Fingerprint (MD5): 0A:A4:D5:CC:BA:B4:FB:A3:59:E3:E6:01:DD:53:D9:4E
# Fingerprint (SHA1): C1:77:CB:4B:E0:B4:26:8E:F5:C7:CF:45:99:22:B9:B0:CE:BA:21:2F
CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE
CKA_TOKEN CK_BBOOL CK_TRUE
CKA_PRIVATE CK_BBOOL CK_FALSE
CKA_MODIFIABLE CK_BBOOL CK_FALSE
CKA_LABEL UTF8 "Explicitly Distrust DigiNotar Root CA"
CKA_CERTIFICATE_TYPE CK_CERTIFICATE_TYPE CKC_X_509
CKA_SUBJECT MULTILINE_OCTAL
\060\137\061\013\060\011\006\003\125\004\006\023\002\116\114\061
\022\060\020\006\003\125\004\012\023\011\104\151\147\151\116\157
\164\141\162\061\032\060\030\006\003\125\004\003\023\021\104\151
.....(省略).....

上記の、「CKA_LABEL」に"Distrust"があることに注意して欲しい。つまりこれは、信頼してはいけないルート証明書なのだ。そのため自作ツールでは、これらを取り除く処理をしないといけない。いやはやめんどい。

Google Chrome (Chromium)を自分でビルドする

いろいろとやる必要があり、Chromiumを手元でコンパイルしたのでその記録。

(2018-06-23) @tzik_tackさんに指摘いただき、一部修正しました。ありがとうございます。

ChromeChromium

まず、予備知識。

Chromiumオープンソースプロジェクト名であり、そのプロダクトがChromiumというWebブラウザ。そして、ChromiumをベースにGoogleが固有機能を追加したものが、我々がふだん使っているGoogle Chromeである。

もっとも、Chromiumプロジェクト自体Googleが深く関わっているし、普通にそこでの実装の意思決定もGoogleのエンジニアによるものが多いので、実質どっちもGoogleがやってるという理解でおおむね問題ない。

Google Chrome自体はGoogleの著作物なので、自分でビルドするならばChromiumをビルドする、ということになる。

ビルド準備

基本的には、ドキュメントに書いてある通りにやればいい。ここに私が書いてあることもすぐ陳腐化すると思うので、ドキュメントを読もう。

ビルドには、Ubuntuを使った方が良い。WindowsでもVisual Studio Community 2017でビルドできるけど、Core i5, RAM 8GB, SSDのマシンでsln(プロジェクトファイル)開くだけで1時間、ビルドは10時間かけても終わらなかったので、やめた方がいい。

なおドキュメントには「8GB以上のメモリ(16GB以上を推奨)、100GB以上のディスク空き容量」が必要と書かれているので頑張ろう。私はESXi上の、Ubuntu 16.04.4を使った。

ビルドに必要なパッケージはだいたいソースコード一式に同梱されているので、事前準備としてはpython, build-essential, gitくらいあればいいはず。(最近のChromeはClang使っているが、chromiumリポジトリに同梱されているためclangパッケージは不要)

ビルド手順

depot_tools

はじめに、ビルドに必要なdepot_toolsというリポジトリをcloneする。

$ git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

この中にある色々なツールを使うので、PATHを通しておくこと。たとえば.bashrcに以下のように加える。

export PATH="$PATH:/home/ozuma/local/depot_tools"

続いて適当な作業ディレクトリ内で、dep_toolsのfetchコマンドでソースコードを取得する。この際、--no-historyを付けて最新版のみ取得すると良い。

$ fetch --nohooks --no-history chromium

ドキュメントのとおり、このfetchには30分ほどかかる。するとsrcディレクトリができるので移動する。

$ cd src

ここまでで、ソースコードのcheckoutができた。続いてUbuntuの場合、依存性を解決してくれるシェルスクリプトが用意されているので実行する。

$ sudo build/install-build-deps.sh

追加パッケージ入手。

$ gclient runhooks

ここまでで、ビルドの準備が整った。

Ninjaビルド

Chromeのビルドは、depot_tools付属のninjaというコマンドで行う。ninjaについては以下。

Ninjaがビルドするためのディレクトリout/Defaultを、depot_tools付属のgnコマンドで作成する。

$ gn gen out/Default

さて、このままビルドしても良いのだが、それだと大変に時間がかかるのでここでいくつかオプションを加える。gnコマンドにargsを付けてオプションを設定する。

$ gn args out/Default

ドキュメントに書いてあるが、まずJumbo buildsという設定をすると早くなる。多くのソースコードがヘッダファイルを共有しているのだから、それらを一緒にコンパイルする……と書いてあるけど、具体的な動きはよく分からない。

また、NaCl(Native Client)をビルドしないようにする。

それから、デバッグ用に使うわけではないの、デバッグシンボルを全部OFFにする。

これらの設定は、上記gn argsコマンドを打つとvimが起動するので、そこに以下のように記述する。実際には、out/Default/args.gnファイルに書かれる。

use_jumbo_build=true
enable_nacl=false
symbol_level=0
remove_webcore_debug_symbols=true
is_debug=false
is_component_build=true

最後に、ninjaコマンドでビルドする。timeコマンドを頭に付けて、どのくらいの時間がかかるか見ておいた方がいい。

ozuma@ubuntu16:~/local/depot_tools/src$ time ninja -C out/Default chrome
ninja: Entering directory `out/Default'
[19686/19686] LINK ./chrome

real    331m41.029s
user    320m3.768s
sys    8m46.260s
ozuma@ubuntu16:~/local/depot_tools/src$

私の手元では、全ビルドに5時間半ほどかかった。Windowsに比べればメチャクチャ早い。

Chromiumの起動

できあがったバイナリはout/Default/chrome。これをたたくと、ちゃんと起動した。

おしまい。

特定のバージョンのビルド

上記の手順では最新のビルド(Canary相当)が入るので、Stableなど古いバージョンを指定してビルドする場合。

上記にやり方が書いてあるが、この通りだとエラーを吐く。

で言われているように、

$ gclient sync --with_branch_heads --with_tags -Rv --disable-syntax-validation

と-Rv --disable-syntax-validationを指定する必要がある。(syntax validation無しでいいのだろうか)

これでタグが持ってこれたので、タグを指定してチェックアウトする。以下のようになる。

$ git checkout -b branch_67.0.3396.87 67.0.3396.87

(Gitでは、コミットIDの代わりにタグ名を指定してcheckoutできる)

なおgclient syncは、checkoutしたあとに依存性の更新をするためにもう一度実行する必要がある。

あとは同じようにビルドする。

$ gclient runhooks
$ gn gen out/67.0.3396.87
$ gn args out/67.0.3396.87
$ time ninja -C out/67.0.3396.87 chrome