Cybozu Inside Out | サイボウズエンジニアのブログ

 

技術顧問に聞いてみた──小崎資広さん、武内覚さんインタビュー

こんにちは。コネクト支援チームの風穴(かざあな)です。

この度、あの小崎資広さん、武内覚さんに、サイボウズの技術顧問に就任していただくことになりました。

小崎さんは、日本を代表するLinuxカーネル開発者の一人です。また武内さんも、Linuxカーネル開発者として知る人ぞ知る存在で、最近では、AMD Ryzenプロセッサの問題を追究したことでも注目を集めました。

ということで、さっそくお二人にお話を伺ってきました。

サイボウズSREチームと、小崎さん、武内さんの集合写真
サイボウズSREチームのメンバーと(前列左から4人目が小崎資広さん、同5人目が武内覚さん)


──そもそも、このお話の経緯は?

小崎さん(以下、敬称略):山本さん(編注:山本泰宇、サイボウズ執行役員・運用本部長)から、「技術顧問になっていただけませんか?」とオファーをいただきまして。

──それまでは、山本とは知り合いでした?

小崎:以前、2、3回、お会いしたことがあり、お名前は認識してました。こちらはすっかり忘れていたのですが、山本さん、沼津の私のオフィスまで来てくれたことがあり、どうもその時打ち合わせで同席していたらしいというのが後から分かって。

武内さん(以下、敬称略):私もいました!

──いきなりでビックリしました?

小崎:そうですね。

武内:不思議な縁ですね。

小崎:それとは並行してサイボウズ社の内田さん(編注:内田公太、サイボウズ・SRE)からOpen Source SummitでWalB(※1)の発表をするので、その資料をレビューしてもらえないか、という相談をいただいていて、内田さんとは一緒に作業をしていました。

武内:その会話を小崎さんがTwitterでしているとき、私が巻き込みリプライをされて。

小崎:「何でこの人が@に入ってるんやろ?」と(笑)。今あらためて見直したら武内さんがWalBの話をしたところに星野さん(星野喬、サイボウズ・ラボ)と山本さんがJoinする形で会話をしてて、そこになにも考えずに返信してたんです。

武内:えー、ひどい話だ(笑)。でも僕は、風穴さんの書籍プロジェクトで内田さん、星野さんとは知り合いだったし、面白そうだからと議論の輪に入りました。

──お二人にたくさんレビューしていただいたおかげで、WalBの発表は大成功でした。ありがとうございました!

武内:そのWalBの発表が終わった頃に、山本さんから連絡があって、技術顧問としてやっていただけませんか、と。私は、春に会社を辞めたばかりで、しばらくは就職せずに、家に籠って自己鍛錬するつもりだったんです。そしたら、そんな私にピッタリの契約が提示されて。

──すごいタイミングですね!

武内:ホントに(笑)。なので、そういう形なら全然いいです、やります、と返事しました。

──その後、小崎さんも所属元とサイボウズの間での調整が終わり、サイボウズの技術顧問をやっていただけることになったわけですが、ちなみに、サイボウズにはどんな印象を持ってました?

小崎:僕は元々、風穴さんとか、サイボウズ・ラボの西尾さんとかと付き合いはありましたし、そういう印象でした。

──おー、そうなんですね。武内さんは?

武内:サイボウズの印象は2段階あって、最初は、サイボウズ・ラボの印象でした。セプキャン(編注:セキュリティ&プログラミングキャンプ、現在のセキュリティキャンプの前身)の講師をしていたので、そこで竹迫さん(元サイボウズ・ラボ)、川合さん(サイボウズ・ラボ)と知り合いました。なんかずいぶん元気な、というか、楽しそうに仕事している人がいるな、と(笑)。

小崎:わたしも同じ年にセプキャンに参加していたので、このあたりの方々とは面識がありました。

──ラボの印象だったんですね。

武内:次が、例の書籍プロジェクトで星野さん、光成さん(サイボウズ・ラボ)、内田さんと出会って、みんなゴリゴリの技術屋で、やっぱりすごく楽しそうだなと。しかも、すごく社内の風通しが良くて、なんて仕事がしやすそうなんだ、と。そういう印象です。あと最近では、働き方の話で、ずいぶん攻めたことをする会社だなと(笑)。

小崎:そうそう。働き方改革とかでもサイボウズがすごくバズってるなと。あの広告(※2)とかで。

──そうですね。ここ1年ぐらいは特にそうですね。

武内:他に例がないというか、いろんなことをされていて、すごいな、という印象はありましたね。

話題になったkintoneの広告ポスターの画像
話題になったkintoneの広告ポスター


お二人の「伝説」

──小崎さんって、いつ頃からすごい人になったんでしたっけ?

小崎:何をいきなり(笑)。

──カーネル読書会で、glibcのmallocについて発表したのがコミュニティデビュー?

小崎:ですね。それより前は、すごく昔に、RELAX(REgular LAnguage for XML)というスキーマ言語のJISテクニカルレポートに、Special Thanksとして名前が出たぐらいかな。なので、Linux系では、あれが最初。

──カーネル読書会で、mallocの発表をされたときは、まだLinuxカーネルはお仕事じゃなかったんですよね?

小崎:そうなんですよ。

──私も、そのときのカーネル読書会に参加していましたが、ホント衝撃的でしたね。

小崎:あの後、社内で仕事をしてると、何か知らんけど、代わる代わる社内の人がやって来て、柱の向こうから私を指差して帰っていくんですよ。ものすごい、感じ悪い(笑)。

武内:それは、感じ悪いですね〜(笑)。

小崎:オマエもやってたやないか!(笑)

武内:私も4人ぐらい連れて行きましたよ。あれが小崎さんだ、って。当時、「Linuxカーネルやglibcが専門じゃない部署の人が何でこんなにglibcのmallocに詳しいんだ?」って、社内で話題になって、見に行きました(笑)。それからが縁ですね。それまでは、同じ社内にいても、小崎さんの存在すら、僕らは認識してなかったですから。

──カーネル読書会の発表が、そんなことになってたとは。

武内:でも、あのときはまだ「普通のすごい人」という感じでしたよね。

小崎:それ、形容詞が矛盾してるような(笑)。

なお、もはや伝説となっている、カーネル読書会での小崎さんの発表「mallocの旅(glibc編)」は、以下で見ることができます。


──武内さんは、まずインターンで富士通に行ったんですよね。

武内:そうです。どうしてもLinuxカーネルがやりたいと思って探したら、富士通がインターンを募集していたので、そこでちょっと頑張ったら、そのままLinuxの開発をやってる部署に採用されて、12年かな、勤めました。

小崎:この人は、インターンの頃から伝説を持っていて。

武内:僕は、大学の学部が情報系じゃなかったので、何か印象を残さないとダメだなと思って、当時触ったことのなかったIA-64 Linux上でハードウェアデバッガを使ってファームウェアがOSに渡したテーブルを書き換えたり、色々やりましたね(笑)。

小崎:何学部でしたっけ?

武内:材料工学。

小崎:それでよくインターンの書類選考が通ったよね。

武内:そうそう(笑)。何か、いろいろ書いた気がします。無茶なことを。

──大きな会社だと、やりたいと言っても、なかなかその通りには配属されないことも多いですからね。

武内:いやぁ、奇跡ですよ。すごく良い巡り合わせでしたね。入社してからは、Linuxカーネルのプロセススケジューラの改善というのをやってました。さらに、社内のOS教育もやることになり、その流れで、セプキャンの講師に呼ばれて……。それが社外への認知の第一歩ですね。

──小崎さんも、大学は情報系じゃないんですよね?

小崎:物理屋さんです。大学を卒業したときが、ちょうど就職氷河期だったので、あまり選べずに家電メーカーの子会社に進みました。ちょうど家電にOSが入り始めた頃で、最初はまだITRONだったけど、その後、Linuxになって。

──スタートは組み込み系だったんですね。

小崎:そう。組み込みでソフトを書いていると、何でこう動くんだろうと、OSのカーネルのコードを読まないといけないことが多々あって……。さらに、周りにそれを説明する必要もあったので、カーネルやらglibcやらの資料を山ほど書いて、説明するということに慣れました。なので、その後エンタープライズに移ったら、そこでは激しく分業が進んでいて、みんな自分の専門以外は知らないということに、ものすごく驚きました。

武内:あるある(笑)。

小崎:結局、最初に就職したところで、たまたま、大きなプロジェクトに入って、大量のソースコードに触れたっていうのが転機になった気がしますね。家電って、ユーザがリモコンを操作して動かない、となったら「リモコンを押したけど動きませんでした」というバグチケットが切られるんですけど、その犯人捜しが困難を極めるわけですよ(笑)。するといつも貧乏くじを引く人がいて、横断的にコードレビューをして突き止めたりする必要があるわけ。そういうのを繰り返して、すごく大きいソフトウェアに対して、何とかしてコントローラブルにするっていうのが、だんだん得意になってきて、それが後々、だいぶ役に立ちましたね。

小崎資広さんの写真
小崎資広さん


技術顧問としての抱負

──今後、どうする、こんなふうにしたい、みたいなのはありますか? 個人的なことと、それから技術顧問としての2つの視点で。

武内:個人的なことで言うと、私、退社の寸前までBtrfsの開発をやっていました。それを、今でいうext4みたいに、あらゆるユーザが使うファイルシステムにしたいです。なので、またその開発に戻ったり、資料を書いて広めたりして、何とか流行らせたいなと。これはすごく良いものだと、自分では思っているので。

──Btrfs! 私も、個人的にBtrfsには期待してるので、ぜひ頑張ってください!

武内:技術顧問として何ができるかというと、何だろなぁ、やっぱり、アプリケーションより下のレイヤ、特にglibcとかカーネルとか、その辺の低レイヤのことをもっと知ってほしいかなと。当たり前のように話せるようになってほしいので、そのお手伝いができればと。

──なるほど。それもぜひ!

武内:あと、わたしは前職では障害が発生したら社会インフラが止まるし、すぐに原因究明をして対策を考えないといけないシステムを扱っていました。その経験を共有し、サイボウズのサービスをより良くしていくことに貢献できたらなと思ってます。

──ありがとうございます! では、小崎さんは?

小崎:なんか、美味しいところ全部取られちゃって言いにくいんですけど(笑)。私はLinuxカーネルのdevelopmentのほうにいたというのが、特色としてあると思ってます。ネットで検索できるLinux記事は、特にトラブルシューティングの記事が結構間違ってたりするんです。動作原理を知らないからそうなってしまうんですけど。ですからLinuxの設計や、なぜそう動作するのかといったところも含めて共有しつつ、一緒に最高のサービスを作っていきたいです。

──小崎さんご自身は、いま、技術的にはどの辺に興味あります? Linuxカーネルだとメモリ管理ですよね?

小崎:そうですね。私はメモリ管理周りが専門です。ただ、カーネルの中の活動をする前はHPC屋さんで、パフォーマンスチューニングをやってました。そのあたりはそれなりに知識があるので、そういうところもSREの領域に生かせると思っています。

働き方は変わった?

──ちなみに、生活というか、働き方みたいなものは、技術顧問就任で変化はありますか?

武内:僕は、しばらくは仕事をしないつもりだったので、適度に仕事が入るかな、というぐらいですね。どっちかというと、小崎さんのほうが変化はあるんじゃないですか?

小崎:忙しいは忙しいんですけどね。だいぶ配慮していただけているので、それで回せてますね。サイボウズさん以外だったら、こういうのは無理だったかもしれないですね。

──そういう面はあるんですね。

武内:私もそうですね。さっきも言いましたが、こっちの都合に会社のほうが合わせてくれるっていう……なんて会社だとビックリしましたよ(笑)。すごく助かりましたね。

武内覚さんの写真
武内覚さん


最後にひと言

──最後にひと言、お願いします。

武内:サイボウズのエンジニアの方々って、みんなすごい優秀なんですけど、「サイボウズがオープンソースソフトウェアに、こういうコントリビュートをしました」みたいなのが、外にはまだそれほど見えてきていないので、それは何とかしたいですね。サイボウズは、働き方はすごい、というのに加えて、こんなにすごいエンジニアがいるっていうのを、ラボの面々だけでなく、サイボウズ本社のほうからも、どんどん出ていくといいのかなと思ってます。

小崎:その辺は、我々もだいぶ、何と言うか、empowerするのは得意なので、ぜひやりたいですね。

武内:例えば、WalBをアップストリームに持っていくとか、その他の機能改善とか開発とか、何でもいいですけど、それをOSSに持っていくというときに、どうやればいいのかといった点は、我々はまさに得意技なので、外に出るお手伝いはできると思いますね。

小崎:我々、文化が違うところから来てるので、お互いに学びあっていけるといいな、と。それを通じて、ぜひとも新しい価値をお互いに作っていくことができたらなと思っています。よろしくお願いします!(了)

kintoneゲーストスペースの画面
お二人とのやり取りは、kintoneのゲストスペース(kintoneの利用ユーザー以外の人が、ゲストとして参加できるスペース)で行っています。


文責・写真:風穴 江


※1:「WalB」(ワルビー)は、サイボウズ・ラボの星野を中心に開発されている、Linux用のバックアップおよび非同期レプリケーションのためのシステムです。ブロックデバイス層でWrite-Ahead Logging(ログ先行書き込み)を行っているのが大きな特徴で、オープンソースソフトウェアとして公開されています。

WalB v1.0 リリース

※2:鉄道駅などで展開された、kintoneの広告。皮肉な「煽り」広告として、BuzzFeed(「ノー残業、楽勝!予算達成しなくていいならね」 サイボウズの煽り広告がよくぞ言ってくれた!──上司の皆さんは耳が痛いのでは)などでも話題に。

 

遅いクエリと向き合ったり、ログ基盤を刷新したり──Cybozu Meetup #6レポート

まいど! コネクト支援チームの風穴(かざあな)です。

今回は、7月25日に開催した「Cybozu Meetup #6 大規模サービスを支える名脇役たち」についてレポートします。

f:id:cybozuinsideout:20170821122829j:plain

Cybozu Meetupとは?

「Cybozu Meetup」は、サイボウズのエンジニアとカジュアルに交流する場として企画している、ミートアップイベントです。会場はサイボウズのオフィス(今のところ東京と大阪)なので、社内の雰囲気や社員の様子を、実際に肌で感じて頂ける機会でもあります。

開催ペースは、東京オフィスは毎月1回、大阪オフィスは3カ月に1回となっています(今のところ)。これまでに、以下のようなテーマで計6回開催してきました。

アプリケーション基盤チーム

東京開催、第6回目のテーマは「大規模サービスを支える名脇役たち」。

「名脇役って何?」という声が聞こえてきそうですが(実際、社内でも、そういう声が……)、要するに、サイボウズが提供しているクラウドサービスやソフトウェアを支える基盤システムのことです。製品やサービスの「顔」として語られることはないけど、重要な役割を果たしている……という思いが、このタイトルに込められているのでした。

その「名脇役」を支えているのが、「アプリケーション基盤チーム」です。一般にミドルウェアとかフレームワークと呼ばれるもの等を守備範囲として活動しています。あるいは、ログ基盤、メッセージキュー、全文検索、ユーザ管理機能といった「サイボウズの複数製品で使うプログラムを作るチーム」(アプリケーション基盤チーム・赤井駿平)でもあります。

トークタイムでは、そのアプリケーション基盤チームから、以下の2名がそれぞれのテーマで発表しました。

f:id:cybozuinsideout:20170821122902j:plain

f:id:cybozuinsideout:20170821122936j:plain

質問&交流タイム

発表の後の質問タイムでは、「(ログ基盤システムで)Fluentdを採用しなかった理由は?」「ログは、どれぐらいの期間の分を残してますか?」「kintoneでは、MySQLのindexはどのようにしてますか?」など、いつになくガチな質疑応答となっていました。

特に、Fluentdを採用しなかった理由については、Fluentdガチ勢の皆さんからツッコミが。詳しくは、以下のまとめ(Togetter)をご覧ください。

Cybozu Meetup #6 サイボウズのサービスを支えるログ基盤(Togetter) https://togetter.com/li/1142720

その後は、立食形式で楽しい懇親会へ。

f:id:cybozuinsideout:20170821123001j:plain f:id:cybozuinsideout:20170821123042j:plain

今後も、東京オフィスでは、毎月開催を予定しています。ご興味ある方は、connpassグループ「Cybozu Inside Out」をフォローして、情報をチェック頂ければ。

「サイボウズのこんな話を聞いてみたい!」というご要望も、随時、受け付けています。お気軽にお寄せください。

f:id:cybozuinsideout:20170821123100j:plain

ではまた。

 

サイボウズ・ラボユース合宿2017開催

こんにちはサイボウズ・ラボの光成です。

先日(8/23~8/25)、サイボウズ・ラボユース合宿をしたのでその紹介をします。

合宿の概要

サイボウズ・ラボユースは学生のソフトウェア開発を支援する制度で、詳細は「第6期サイボウズ・ラボユース成果発表会」開催などをご参考ください。

合宿の目的は、ラボユース生同士、交流を深めてもらうことです。 今年度はリモート参加の学生も多く、なかなか他の学生と交流しにくいだろうという背景があり、久しぶりの開催となりました。

もちろん集中的に開発し、様々な疑問をまとめて聞ける場を提供するのも大きな目的の一つです。

合宿の参加者は現役のラボユース生が4名、研究生が1名、ラボユースOBが4名、ラボからは7名で、 会場は神奈川県三浦海岸のマホロバ・マインズ三浦でした。

男性参加者は3~4人一組で泊まるのですが、部屋が約100平方メートル3LDKとかなりの広さで快適でした。

f:id:cybozuinsideout:20170828154516j:plain

女性参加者は一人だったため、その広さを少々もてあましていたそうです。 今後は女性の参加者が増えるとよいですね。

初日

遠方参加者がいたため11時過ぎの自己紹介でスタートです。 最近はSECCONなどの影響もあり、CTFをやっている学生も多く、開発テーマをそれ関係にしている人もいました。 みなさん黙々と開発していました。 私はすぐ寝ましたが夜遅くまで開発していた人もいたようです。

二日目

朝からずっと開発です。気分転換に自由に使えるラウンジに移動して開発してる人もちらほらでてきました。 バイナリ(機械語)が好きな人が多かったので私は息抜きネタにあなたの知らないnopたちのLTをしました。

それからC++を勉強中の希望者に対して『Effective C++』をベースにC++11, C++14で便利になった機能の紹介をする勉強会をしました。 こういうところでは時間を気にせず質問タイムを多く取れるのがよいですね。

f:id:cybozuinsideout:20170828144333j:plain

三日目

合宿での成果発表会です。

城倉さんは去年のラボユースのOBでその後未踏IT人材発掘・育成事業:2017年度採択プロジェクト概要に採択されました。 将来そのプロジェクトで動かす予定のネットワークパケットフィルタを開発しました。

f:id:cybozuinsideout:20170828182634j:plain

渡部さんは「拡張可能なインタプリタのための、構文解析のための、DSLの設計」という発表でした。 きちんとした理論を元に考察し、OCamlで実装されていて興味深いです。

f:id:cybozuinsideout:20170828182657j:plain

高品さんはTwitter上のソーシャルグラフのクローラを作って(twicrawler)、収集したデータから日本の機械学習界隈の中で影響力の大きい人たちを表示するデモを行いました。 結構それっぽい人たちが選ばれていて面白かったです。

f:id:cybozuinsideout:20170828183854j:plain

川田さんはCTF用のカーネルエクスプロイト問題を作ってみようと調査しました(資料)。

f:id:cybozuinsideout:20170828183824j:plain

dmsgのFreeing SMP …の項目に表示されていたアドレスは安全性のため新しいkernelでは表示されなくなってるそうです(mm/page_alloc: Remove kernel address exposure in free_reserved_area())。へえ。

黒岩さんはROPチェインの自動生成ツールを開発しています。

f:id:cybozuinsideout:20170828183814j:plain

ROPとはバッファオーバーフローなどを利用する攻撃の一つで実行属性のないスタック上で意図したコードを実行する手法です。 glibcなどに含まれるコード片を組み合わせてコードを構築するのですが、その部分を自動化しようというものです。

西田さんはネタアプリとしてC言語を補助してくれるslack上で動くbotを作りました。

f:id:cybozuinsideout:20170828182645j:plain

「putsの中身を"○○"にして」と発言するとソースコードが修正されます。 「インデントくらいしてよ」と発言するとソースコードをきれいにフォーマットしてくれる機能もあります。

ちなみにフォーマットツールにはClangFormatを使っているとのことで、「indentじゃないの?」と尋ねたところ「indentって何ですか」と言われてジェネレーションギャップを感じました。

赤間さん(OB)は、プラレールのレイアウトを設計するためのツールに必要な座標系の話でした(cf. プラレールで半加算器を設計した話)。

f:id:cybozuinsideout:20170828182639j:plain

平行移動と45度だけ回転する座標をうまく簡潔に表現できないか考えているそうです。

粟本さん(OB)はLinuxのuio(ユーザ空間でデバイスドライバを作る仕組み)の紹介とxHCI用ドライバの進捗状況を発表してくれました。

f:id:cybozuinsideout:20170828144413j:plain

緑川さん(OB)はとあるマイナーな公開鍵暗号に対する攻撃方法を考えていました。

まとめ

みなさん好き勝手にやっていて(ほめ言葉)すばらしい。 今後のすぐれた成果を期待します。 なお、サイボウズ・ラボユースは通年募集しております。

f:id:cybozuinsideout:20170828144420j:plain

 

Java Security Manager でセキュアなサービスを構築しよう

こんにちは、アプリケーション基盤チームの青木(@a_o_k_i_n_g)です。

今回は Java アプリケーションをセキュアに運用する仕組みである Java Security Manager について紹介しようと思います。この仕組みは Linux の強制アクセス制御機構(SELinux や AppArmor) の Java 版に相当するもので、プログラムの挙動を制限することができます。弊社が提供するクラウドサービス cybozu.com でも有効化されています。

セキュアなサービスを提供する上では良い仕組みだと思うのですが、検索したところ Java Security Manager に関する記事があまり多くなかったため、我々が得た知見をここに記します。

Java Security Manager とは

Java Security Manager (以下 JSM) とは、Java コードを安全に実行する仕組みの一つです。Java コード上で危険な操作を行う際は、事前に権限を許可しておかないかぎり実行に失敗し、例外をスローするというものです。危険な操作とは、例えばファイルの読み書きやネットワークへの接続、外部コマンドの実行、それからクラスローダーの取得やシステムプロパティの読み書きなどを指します。

初期状態では JSM は無効になっており、Java コードはどんな処理でも行えます。自由ではありますが、一方でこれは危険な状態でもあります。サードパーティのライブラリが勝手にファイルを読み書きしてしまったり、またはリモートコード実行という脆弱性が見つかった場合どんな操作でもされたりということが起こりえます。JSM で適切なポリシーを構築すればこれらの被害を抑えることが出来るでしょう。

ポリシーの作り方

許可する権限をまとめたものをポリシーと呼びます。JSM を有効に機能させられるかどうかはこのポリシーを正しく構築できるかどうかにかかってるといっても過言ではありません。

どのような処理がどのような権限の許可を必要とするのか、こちらの仕様書に一覧があるのでわかりやすいです。
Java Development Kit (JDK)でのアクセス権

既存のアプリケーションでどのような権限を要求されているかについて知りたい時は、まず最初に全権限を許可するポリシーファイルを作ります。

grant {
  permission java.security.AllPermission;
};

上記を all.policy のようなファイル名で保存し、対象の Java プログラムの起動オプションに -Djava.security.manager -Djava.security.policy=all.policy -Djava.security.debug=access を指定して実行すると、下記のようなログが標準エラー出力に大量に書き出されます。これらのログは権限が必要になった時点でログに出力されるので、アプリケーションのすべての権限を集めたい場合は全機能を触る必要があります。こうして得たログを元にポリシーを構築しましょう。

access: access allowed ("java.lang.reflect.ReflectPermission" "suppressAccessChecks")
access: access allowed ("java.util.PropertyPermission" "java.security.egd" "read")
access: access allowed ("java.security.SecurityPermission" "getProperty.securerandom.source")
access: access allowed ("java.io.FilePermission" "/dev/random" "read")

ポリシーの記述方法

ポリシーを記述する方法はファイルに記述する方式と Java コードで記述する方式のふたつがあります。ファイルに記述する方式は公式ドキュメントでも解説されていますが、後述する性能問題やシンボリックリンクに関する問題があり、あまりお勧めしません。よってここではファイルに記述する方式の解説は省略し Java コードで記述する方式について解説します。

ポリシーをファイルに記述する方式に比べて、Java コードで記述する利点はいくつかあります。

  • パフォーマンスチューニングする余地がある
  • 複雑な権限評価を実装できる
  • ログ出力等も自由に可能

最小の記述で動かす例を示します。まず、下記のように java.lang.SecurityManagerjava.security.Policy を継承したクラスをそれぞれ作ります。SecurityManager を継承したほうは現在は空ですが後述する性能問題でオーバーライドするのでここで定義しておきます。

public class CybozuSecurityManager extends SecurityManager {
}

public class CybozuPolicy extends Policy {
    private final PermissionCollection permissions;

    public CybozuPolicy(PermissionCollection permissions) {
        this.permissions = permissions;
    }

    @Override
    public boolean implies(ProtectionDomain domain, Permission permission) {
        return permissions.implies(permission); // 権限評価部分
    }
}

実際に権限評価を行う部分は CybozuPolicy#implies メソッドです。今回のコード例では domain オブジェクトを無視していますが、domain オブジェクトを利用すればライブラリごとに評価する権限を切り替えるようなことができます。

上記のクラスを用いて、アプリケーションが起動した直後の処理に下記のようなコードを入れて JSM を有効化します。許可する権限は FilePermission クラス等、Permission クラスの子クラスのインスタンスで表現します。それらを先ほど作った CybozuPolicy クラスに渡し、Policy.setPolicy(Policy policy) で指定すれば権限が反映されます。

public static void main(String[] args) throws Exception {
    // ここで許可する権限を記述。
    Permissions permissions = new Permissions();
    permissions.add(new FilePermission("/tmp/-", "read,write"));  // /tmp/下のファイルを読み書きを許可
    permissions.add(new ReflectPermission("suppressAccessChecks")); // リフレクションを許可
    permissions.add(new PropertyPermission("line.separator", "read")); // line.separator プロパティを読み込みを許可
    permissions.add(new RuntimePermission("getenv.PATH")); // 環境変数 PATH の読み込みを許可

    Policy.setPolicy(new CybozuPolicy(permissions)); // ポリシーの反映
    System.setSecurityManager(new CybozuSecurityManager()); // SecurityManager をセット(JSM 有効化)

    ...
}

以上がほぼ最小の Java コードによる JSM を有効化する方法です。上記例で JSM を有効化した場合、例えば /proc 下のファイルを読む、というようなことはできません。リモートコード実行の脆弱性があったとして、System.setSecurityManager(new EvilSecurityManager()); のようなコードが実行されてしまうのではないか?という懸念があるかと思いますが、一度セキュリティマネージャーを設定した後再設定するには専用の権限を許可する必要があり、通常は行えません。

今後 SecurityManager を実装する方は、分散検索サーバーで有名な Elasticsearch のコードが参考になるかも知れません。

性能について

JSM を有効にする場合、既存の処理に加えてコードの各所で権限チェックが行われるので性能劣化を引き起こします。弊社のとある Web アプリケーションでは、特に性能について考慮せずナイーブにポリシーを構築した場合、応答時間が 3 倍程度まで伸びてしまいました。

これを改善するため、評価される権限のうちの一部頻繁に評価される権限について短絡評価を行うことにしました。短絡評価を行うには先ほどの CybozuSecurityManager クラスで checkPermission(Permission perm) メソッドをオーバーライドし、その中にロジックを記述します。

コード例を示します。こちらはファイルを読み書きする際の権限が要求された際に /tmp/ から始まるパスなら実行を許可するという例です。

public class CybozuSecurityManager extends SecurityManager {

    @Override
    public void checkPermission(Permission perm) {
        String name = perm.getName();
        String actions = perm.getActions();

        // ここで短絡評価を行い、実行可能な操作なら super.checkPermission(perm) を呼び出す前に return する
        if (perm instanceof FilePermission) {
            if (name.startsWith("/tmp/")) {
                return;
            }
        }

        ...
        super.checkPermission(perm);
    }
}

主に遅くなるのは SecurityManager#checkPermission の処理のようでした。上記のような短絡評価を複数取り入れた結果、性能劣化は 3% 程度に抑えられました。潤沢なリソースがある本番環境ではほぼユーザーエクスペリエンスに影響がない範疇に収められたかと思います。ただし、公式ドキュメントでも指摘されているように、SecurityManager クラスをサブクラス化する際は権限の評価漏れ等が起きないよう最新の注意を払ってください。

JSM をうまく扱うポイント

シンボリックリンクについて

ファイルやディレクトリを読み書きする権限を与えても、そのパスがシンボリックリンクだと正常に許可されないケースがあります。

具体的には、プログラム実行中にシンボリックリンクの指し先が変わるケースです。例えばシンボリックリンクのあるファイル /path/to/symlink への読み込みを許可するアプリケーションがあるとします。そのシンボリックリンクの指し先がアプリケーション実行中に変わると、同じ /path/to/symlink の読み込みをしているにも関わらず権限は許可されません(内部でパス解決のキャッシュが効いているのか、指し先が変わっても最大 30 秒程度は許可されます)。

この現象は前述した短絡評価と同じようなコードを挿入することで解決できます。

外部コマンドの実行について

アプリケーション内から外部コマンドを実行する系がある場合、そのコマンドのパスは絶対パスで記述しましょう。というのも、JSM の実装の都合上、絶対パスでないコマンドを実行する時は 全ファイル の実行権限を許可する必要があるからです。

コマンド実行時に呼び出される java.lang.SecurityManager の一部を転載します(整形済)。

public void checkExec(String cmd) {
    File f = new File(cmd);
    if (f.isAbsolute()) {
        checkPermission(new FilePermission(cmd, SecurityConstants.FILE_EXECUTE_ACTION));
    } else {
        checkPermission(new FilePermission("<<ALL FILES>>", SecurityConstants.FILE_EXECUTE_ACTION));
    }
}

ここからわかるように絶対パスでない場合は常に <<ALL FILES>>、すなわち全ファイルの実行権限を要求されます。checkExec メソッドをオーバーライドして回避する方法もありますが、あまりハックらしきことはせず郷に従うほうが得策ではないかと思います。

ForkJoinPool について

通常、JSM を有効にするとどのスレッドでも同じポリシーが適用されるのですが、デフォルトの ForkJoinPool で生成されたスレッドは例外です。

SecurityManagerが存在し、ファクトリが指定されていない場合、デフォルト・プールではファクトリが提供する有効なPermissionsを持たないスレッドが使用されます。 https://docs.oracle.com/javase/jp/8/docs/api/java/util/concurrent/ForkJoinPool.html

ForkJoinPool のスレッドを使う時とは、ForkJoinPool.commonPool() を用いてタスクを処理したり、Stream API で parallelStream() を用いて並列にリスト処理を行う時などです。

これを解決するには下記のように自前で ForkJoinWorkerThreadFactoryForkJoinWorkerThread を定義する必要があります。

public class CybozuForkJoinThreadFactory implements ForkJoinWorkerThreadFactory {
    @Override
    public ForkJoinWorkerThread newThread(ForkJoinPool pool) {
        return new CybozuWorkerThread(pool);
    }
}

public class CybozuWorkerThread extends ForkJoinWorkerThread {
    protected CybozuWorkerThread(ForkJoinPool pool) {
        super(pool);
    }
}

上記クラスを定義した上でシステムプロパティで ThreadFactory を指定すると、意図したとおりのポリシーが用いられるようになります。

System.setProperty("java.util.concurrent.ForkJoinPool.common.threadFactory",
                      "com.cybozu.common.concurrent.CybozuForkJoinThreadFactory");

終わりに

JSM を用いるとアプリケーションの意図しない挙動を防ぐことができるので、よりセキュアにアプリケーションを運用することができます。万が一リモートコード実行のような脆弱性が見つかっても、 JSM が有効な範疇でコードが実行されるなら被害を最小限に留めることが出来るかも知れません。

とは言え、JSM を有効にしたからといって安心安全というわけでは決してありません。人間が作るものなので完全なポリシーを作ることなどきっと不可能でしょう。そうでなくとも、サーバーサイドで JSM を有効にしたとしても、 例えばエスケープ漏れによる SQL Injection や XSS などの脆弱性を防げるようになるわけではありません。JSM は数ある脅威のうちのほんのごく一部を防げるだけに過ぎず、過信してはならないことを肝に命じた上で JSM を扱いましょう。

参考

Javaセキュリティ・アーキテクチャ

 

Migrating Garoon codebase to PHP 7

(※注 冒頭のみ日本語訳を行いました。本編はベトナムメンバーによる英語の記事になります。)
Xin chào! Cybozu Vietnam で Garoon を開発している Huy Nguyen, Vien Tran, Long Huynh, Dieu Ho, Tai Vu, Anh Nguyen です。ベトナムメンバー初のCybozu Inside Outの投稿です。Garoon で PHP 7 への移行をした経験を記事にしました。

One year ago, Garoon developement team started to migrate the codebase of Cybozu Garoon to PHP 7. Garoon is a product with large codebase, formerly written in PHP 4 and then later migrated to PHP 5. The migration really was a challenge to the team, but it was worth doing because PHP 7 brought a host of improvements. One of the benefits is performance improvements. Benchmark of Garoon using PHP 7 showed a 33 percent of speed improvement compared to PHP 5.6.

And also, active support of PHP 5.6 ended on 19 Jan 2017 according to php.net. Source: http://php.net/supported-versions.php

In this post, I will share things that the Garoon development team met and show you the way we migrated the codebase to PHP 7.

Tools to check incompatible changes

When we upgraded our code from PHP 5 to PHP 7, we had to check our codebase for all the “backward incompatible changes” to make sure our codebase could be successfully interpreted in PHP 7. There are some tools that help you scan your code through and point you out which code needs to be fixed in order to be able to run in PHP 7, among which are Phan and Lint.

Phan

Phan is a static analyzer for PHP. It’s licensed under the MIT License.

Prepare the environment

You have to prepare a CentOS machine, with PHP 7 installed. Then install composer running the code below:

curl \-sS https://getcomposer.org/installer \| php \-\- \--install-dir=/usr/local/bin \--filename=composer

Install Phan and the php-ast extension

First, clone the source code of php-ast:

git clone https://github.com/nikic/php-ast.git

Next, build the php-ast extension and then add it to php. Run each line of the code below in turn:

cd php-ast
phpize
./configure
make install
echo 'extension=ast.so' > /etc/php.ini

Then, install Phan, and run through the code below:

git clone https://github.com/etsy/phan.git
cd phan
composer install
./test
ln \-s /phan/phan /usr/local/bin/phan

Run Phan to check backward incompatible changes

We created a bash file (e.g., testPhan.sh) with the content like the following:

COMMAND="phan \--backward-compatibility-checks \--ignore-undeclared \--quick"
find /repo/source \-type f \-regex '.*\.\(php\)' \-print0 \| xargs \-0 \-n1 \-P 1 \-I % bash \-c "$COMMAND % "

You can change the path to the soure code by changing “/repo/source”. Then run:

sh testPhan.sh

Lint

Lint is a command line option provided by PHP, which is used to check PHP syntax.

On the above CentOS machine, we ran the following command to check our PHP source code with Lint:

find . -name "*.php" -print0 | xargs -0 -n1 -P8 php -l

E_STRICT notices severity changes

E_STRICT notices severity was changed in the PHP 7, and all of the E_STRICT notices were reclassified to other levels.

Situation New level/behaviour
Indexing by a resource E_NOTICE
Abstract static methods Notice removed, triggers no error
“Redefining” a constructor Notice removed, triggers no error
Signature mismatch during inheritance E_WARNING
Same (compatible) property in two used traits Notice removed, triggers no error
Accessing static property non-statically E_NOTICE
Only variables should be assigned by reference E_NOTICE
Only variables should be passed by reference E_NOTICE
Calling non-static methods statically E_DEPRECATED

Some of these changes affected our source code.

Signature mismatch during inheritance

The “Signature mismatch during inheritance” notice usually occurs in the following cases: The first case is a case where the number of the parameters of a function/method is different during inheritance. For example:

<?php
class Foo{
    function method($a){

    }
}
class Bar extends Foo{
    function method(){

    }
}

The above example outputs:

Warning: Declaration of Bar::method() should be compatible with Foo::method($a) in /in/YoDHq on line 11

You can click here to run this example and see the output.

The second is a case where the type of a parameter of a function/method is different during inheritance. For example:

<?php
class Foo{
    function method($a){

    }
}
class Bar extends Foo{
    function method(array $a){

    }
}

The above example outputs:

Warning: Declaration of Bar::method(array $a) should be compatible with Foo::method($a) in /in/fSIbS on line 11

You can click here to run this example and see the output.

We used Phan to check our code and fixed the problems that it reported as PhanSignatureMismatch and PhanSignatureMismatchInternal.

How to fix such issues depends on the logic that we modified in the base class or subclass.

Only variables should be assigned by reference

Example:

<?php
error_reporting(E_ALL);
function foo(){
    return 'string';
}
$temp =& foo();

The above example will output:

Notice: Only variables should be assigned by reference in /in/vXUIZ on line 6

You can click here to run the example and see the output.

In the above example, the ‘&’ operator causes an E_NOTICE saying “Only variables should be assigned by reference”. In case your code is compiled in a PHP 7 environment, an error will be detected, and so you must fix it by removing ‘&’ as follows:

<?php
error_reporting(E_ALL);
function foo(){
    return 'string';
}
$temp = foo();

However, since in our code ‘&’ was used in so many places and it would take us so much time to fix them all, I would like to introduce a tool which runs on CentOS to solve this problem. First of all, in order to write E_NOTICE messages to a php_error.log, you can do the following steps:

1. Add the following script to the code of your web application and then run it:

set_error_handler(function($errno, $errstr, $errfile, $errline){
  file_put_contents('/tmp/php_error.log', implode(',', [$errno, $errstr, $errfile, $errline]) . PHP_EOL, FILE_APPEND);
});

2. The “php_error.log” will look like the following:

2048,Only variables should be assigned by reference,/var/www/project/code/xxx1.php,12
2048,Only variables should be assigned by reference,/var/www/project/code/xxx2.php,121
2048,Only variables should be assigned by reference,/var/www/project/code/xxx3.php,26
2048,Only variables should be assigned by reference,/var/www/project/code/xxx.php,194

The tool will run the following steps:

  1. Find the latest folder created with the prefix “ system-private-* ” under tmp.
  2. Grep the string “assigned by reference” in the php_error.log file, which exists inside tmp, and then output the file name and the line number to a temporary result.
  3. Find “=&”, “= &” and “= & ” and replace them with “=” following the result from the step 2.
#!/bin/bash

systemd_private_name=$(ls -t1F /tmp/ | grep systemd-private-* | head -n1)
php_error_path="/tmp/${systemd_private_name}tmp/php_error.log"

if [ ! -f $php_error_path ]; then
    echo "File is not found"
    exit;
fi


grep "assigned by reference"  $php_error_path  | sort | uniq -c | while IFS=',' read -ra line ; do
    sed -ri ${line[3]}'s/(\$.+)(\s+=\s*&\s*)(.+())/\1 = \3/g'  ${line[2]}
done

Only variables should be passed by reference

In PHP 7, using a function argument could return an error when the function argument was passed by reference. This issue can occur if you use a function of a PHP framework or a method you added.

To fix this issue we declared a variable to store the value of a function, which had been directly passed at a position called. What follows is an example:

Example: Strict Standards will be thrown out if you put exploded array in array_pop:

<?php
   $fruit = array_pop(explode(",", "Orange,Apple,strawberry"));
   echo $fruit;

Fixed code:

<?php
   $fruits = explode(",", "Orange,Apple,strawberry");
   $fruit = array_pop($fruits);
   echo $fruit;

Note that in this example you must assign a variable to the function explode and then pass the reference to the variable to array_pop to avoid a Strict Standard warning.

The methods in PHP framework

Example:

<?php
   $file_name = "abc.txt";
   $file_extension = end(explode('.', $file_name));

The above example will output:

Notice: Only variables should be passed by reference in /in/Cv65t on line 3

You can click here to run the example and see the output.

Fixed code:

<?php
   $tmp = explode('.', $file_name);
   $file_extension = end($tmp);

We used Phan to check our code and fixed the problems which were reported as PhanTypeNonVarPassByRef. However, Phan only retrieved methods in the PHP framework: as for the methods we added, Phan could not detect them as PhanTypeNonVarPassByRef. The example below is to explain this point.

The method you added

Example:

<?php
function getArray() {
    return [1, 2, 3];
}

function squareArray(array &$a) {
    foreach ($a as &$v) {
        $v **= 2;
    }
}

// Generates a warning in PHP 7.
squareArray((getArray()));

The above example will output:

Notice: Only variables should be passed by reference in in /in/HYONN on line 14

You can click here to run the example and see the output.

To detect a method you define in your project, you can search for it with regular expression using an editor (e.g, notepad++).

function.*\&\s+\$.*\)$

The above example will output:

E:\yourproject\comment_util.php (3 hits)
    Line 30:     function getFollowByUser( & $user, & $article )
    Line 61:     function _setNameRowSet( & $rowset, $inverse = FALSE )
    Line 119:     function _row2object( & $row )

After you list your reference methods from the search result, you must manually modify function calls.

Calling non-static methods statically

Static calls to methods that are not declared static are deprecated in PHP 7 and may be removed in the future.

We have an example:

<?php
class foo {
    function bar() {
        echo 'I am not static!';
    }
}

foo::bar();

The above example will output:

Deprecated: Non-static method foo::bar() should not be called statically in - on line 8
I am not static!

We used Phan to check our code and fixed the problems that were reported as PhanStaticCallToNonStatic. The easiest way to fix such a problem is change all non-static functions to static functions.

PHP 4 constructor

PHP 4 constructors are methods that have the same name as the class they are defined in.

PHP 7 will emit E_DEPRECATED whenever a PHP 4 constructor is defined. When the method name matches the class name, the class is not in a namespace, and a PHP 5 constructor (__construct) is not present then an E_DEPRECATED will be emitted.

Example:

<?php

class Filter {
    function Filter() {

    }
}

new Filter();

Output:

Deprecated: Methods with the same name as their class will not be constructors in a future version of PHP; Filter has a deprecated constructor in /in/IDpi7 on line 3

You can click here to run the above example and see the output.

You should use PHP 5 constructor like the below

<?php

class Filter {
    function __construct() {

    }
}

new Filter();

If your project is large, manually fixing the constructors will take a lot of time. There’s a useful tool called PHP-CS-Fixer that can help you automatically fix them. The PHP-CS-Fixer tool helped us fix over 500 files in our Garoon project.

I will show you how to use the tool to fix PHP 4 constructors. The below code runs with PHP-CS-Fixer 2.2.1.

1. First we need to create a configuration for PHP-CS-Fixer

.php_cs
<?php

$source_dir = 'c:\repo\projectX\source';
$finder = PhpCsFixer\Finder::create()
    ->in($source_dir)
    ->files()->name('*.csp')->name('*.php')
;

return PhpCsFixer\Config::create()
    ->setRiskyAllowed(true)
    ->setRules(array(
        'no_php4_constructor' => true
    ))
    ->setFinder($finder)
;

Note: Our Garoon project uses “.csp” as the file extension of PHP source files, which is why we add “.csp” to the configuration of PHP-CS-Fixer.

2. Run PHP-CS-Fixer

php php-cs-fixer.phar fix --config=.php_cs --verbose

For details about this tool, see: https://github.com/FriendsOfPHP/PHP-CS-Fixer

Internal functions Changes

There are some internal functions that were changed in PHP 7, and the substr() function is one of them.

For example:

<?php
$foo = substr("foo",3);
if($foo !== FALSE){
    echo "PHP 7\n";
    echo gettype($foo); //string
}
else{
    echo "PHP 5\n";
    echo gettype($foo); //boolean
}

The type of the variable $foo in the above example will be a boolean in PHP 5 and a string in PHP 7.

You can click here to run the above example and see the output.

To fix this issue, we checked the length of a string using the strlen function.

<?php
$foo = substr("foo",3);
if(strlen($foo) == 0){
    echo "Run on both PHP 5 and PHP 7\n";
    echo gettype($foo); //boolean on PHP 5 and string on PHP 7
}

You can click here to run the above example and see the output.

Conclusion

In the Garoon project, performance was improved and the codebase became more readable after the migration from PHP 5.6 to PHP 7: for example, the performance of the instance of Garoon we are using in Cybozu Inc. was shown to have been improved by 33 percent. We separated the migration into multiple parts and carried them out one by one. This way helped us cover possible issues while the migration was done in a short period of time.

Change log

2017-09-15

  • Modify the code and the output on “Only variables should be assigned by reference”