ソフトウェア工学研究の日々

ソフトウェア工学の学術研究を紹介しています。ソフトウェア開発に関する調査と実験が大好きです。

ライブラリのセキュリティ問題がアプリケーションに影響しないケースも多い

ライブラリの名前とバージョン番号を指定するだけで自動的にダウンロード等を完了してくれる依存性管理ツールのおかげで、どのライブラリを使っているかが機械的にチェックできるようになり、古いバージョンのライブラリを使っているとセキュリティ問題のあるライブラリを使わないように、と自動的に警告を出すこともできるようになりました。

一方で、セキュリティ問題があるといっても、問題があるのはライブラリの特定の機能に限られるため、すべての利用者がその影響を受けるわけではありません。たとえば画像ファイルを読み書きするライブラリに特殊な細工をしたファイルを読ませると不正な動作をすることが分かったとき、そのライブラリを画像ファイルを書き出すためだけに使っているアプリケーションは影響を受けないことになります。現在のセキュリティ警告の仕組みは、残念ながら、そこまでは考慮してくれません。

そこで、セキュリティ問題が存在することが分かっているライブラリのバージョンを使っていて、かつ、その問題の影響を受けないケースがどれだけあるか、JavaScript で npm によって依存ライブラリを管理しているプロジェクトに対して小規模なサンプル調査を行ってみました。全文は以下のようなタイトルで発表しています。

Rodrigo Elizalde, Raula Kula Gaikovina, Bodin Chinthanet, Takashi Ishio, Akinori Ihara and Kenichi Matsumoto: Towards Smoother Library Migrations: A Look at Vulnerable Dependency Migrations at Function Level for npm JavaScript Packages

 この調査では、ライブラリのバージョン更新履歴(ソースコードの差分)からセキュリティ問題がどの関数にあったかを特定し、関数の呼び出し経路を調べて、その関数を実際に呼び出す可能性があるかどうかを確認しました。

その結果、73.3% はセキュリティ問題の報告されているバージョンのライブラリを使用しているが、その問題を含む関数に対する呼び出しの経路は存在していませんでした。ライブラリのセキュリティ問題に関する話題でよく登場する「〇〇%のアプリケーションがいまだに古いライブラリを使っている」といった文言は、影響を過大に喧伝してしまっていることになります。

ただし、セキュリティ問題の影響を受けるような機能を使っていないうちにライブラリを更新しておくほうが、セキュリティ修正がアプリケーションに影響を与えないと分かっているので、更新作業自体にはコストがかからない可能性もあります。何かの拍子に開発者がその機能を使ってしまう可能性もありますので、更新を急ぐ必要はないとは言えるものの、更新がまったく必要ではないとまでは言い切れません。ライブラリの更新に伴うコストと更新しない場合のリスクをうまく見積もる(あるいは更新コストを下げる)技術の研究が今後も必要なのだと考えています。

 

Java プログラムのリアルタイムプロファイラの開発

プログラムの実行に時間がかかる、一瞬動きが止まって見えるといった、実行速度に関する問題が生じることがあります。そんなときに使えるのがプロファイラと呼ばれるツールで、Java の場合は Oracle JDK に標準で付いてくる hprof がよく知られていると思います。プロファイラを使うと、プログラムのどの部分の実行にどれだけ時間がかかっているか、計測することができます。

普通のプロファイラは、プログラムの実行が終了するまで計測を続け、最終的にどこに一番時間がかかっていたかを表示しますが、一瞬だけ動きが止まるといったような一時的な挙動は調べることができません。私たちの研究室では、 Java プログラムの実行中に起きたことをリアルタイムに可視化すると、プログラムの挙動の理解が容易になるのではと考え、2017年度の修士論文として、ツールを開発しました。

github.com

Heijo と名付けられたこのツールは、Java のパッケージおよびクラスを土台として、直近の1秒間のうちメソッドが実行されていた時間の割合を四角い棒(建物)の高さとして可視化するという可視化を採用しました。この可視化方法自体は Code City という名前で知られる既存研究に基づくものです。以下の動画は、上記プロジェクトページに動作例として掲載しているものです(音量にはご注意ください)。

www.youtube.comこの動画では、左側で Mario Like と名付けられたゲームが動作しており、右側にその実行を可視化しています。常時 2つ、青い棒が立ち上がっているので、2つのスレッドが動いているのが分かります。動画開始から14秒あたりのところで、ゲームの動きが一瞬だけ動きが止まるという動作が起き、右側の可視化結果でも、1本の棒が縮んでなくなり、代わりに別の棒が伸びてきます。つまり、特定のメソッドに多くの時間を費やしてしまいその結果プログラムの動きが停止してしまったことが分かります。Android アプリの監視バージョンも作成し、色々な用途を探索した結果を修士論文としました。

技術的には、Java プログラムの各メソッドの先頭と末尾、メソッド呼び出し命令の前後に、現在どのメソッドの実行を開始・終了したかを報告するような処理を行うよう、プログラムをロードするタイミングでバイトコードの書き換えを行っています。これにより、実行オーバーヘッドを(デバッガのステップ実行などと比べると)小さく抑えながら情報を収集し、可視化のエンジンにデータを送信しています。ソフトウェアの詳しい紹介はソフトウェア科学会の論文誌「コンピュータソフトウェア」に掲載の予定です(本記事執筆時点ではまだ公開されていません)。

 

ブロックチェーンを用いたソフトウェアビルドプロセスの記録

ソフトウェアの実行可能ファイル(いわゆるバイナリ)は、ソースコードさえあれば自由に作成することができます。プロの分析者であればバイナリに含まれた定数値などから元のソースコードをある程度は推定することができますが、多数のソースファイルのどれを組み合わせてバイナリを作ったかは、実際にビルドを実行した人以外にはわかりません。

ソフトウェアの実行可能ファイルをエンドユーザに提供するとき、「このファイルは、これらのソースファイル(といくつかのライブラリ)だけから作りました」と説明できるようになると、悪意あるコードの混入などのリスクを抑えらえるようになりますし、著作権の侵害などのトラブルにも対応しやすくなります。このコンセプトは "Reproducible Builds" として知られています。

Reproducible Builds — a set of software development practices that create an independently-verifiable path from source to binary code

ソフトウェア工学研究室では、このような取り組みを支援する技術の1つとして、ブロックチェーンを用いたビルドプロセスの記録に取り組んでいます。任意のコマンド(たとえば gcc のようなコンパイルの実行)の実行を監視し、読み書きしたファイルを記録することで、どのファイルが、どのファイルと関係があるかを記録することで、この目的を達成しようとしています。2019年1月の時点で、ビルドを監視するツールを試作し、ブロックチェーンに登録可能であることを確認した段階というところですが、構想には思ったより反響もあり、第25回ソフトウェア工学の基礎ワークショップ(FOSE 2018)では貢献賞(ショートペーパー部門)を受賞しました。以下はそのときの発表スライドです。

www.slideshare.net

ソースファイルと実行可能ファイルを結びつけることは、「その実行可能ファイルに使われたソースファイルに対する品質検査を確かに行った」というように、エンドユーザに提供されているソフトウェアの品質を説明していくためにも使えます。ソフトウェアにはバグが避けられませんが、開発者がその時点で行った様々な努力を証拠として記録することも、ソフトウェアに起因するトラブルの解消や原因の理解、再発の防止において重要な課題であると考えています。

 

ソースコードからの類似したコード断片の検索

プログラミングをしていると、似たような、少しだけ違う処理を何度か書かなければならないことがあります。うまく関数などの単位にまとめられれば良いのですが、ループ構造などをうまくまとめられない場合もありますし、開発の担当者が異なるために編集できないという場合もあります。そんなときは、1つ記述した処理の内容をコピー&ペーストで複製し、それぞれの場所に合わせて編集するという形でプログラムを書くことになります。

このような処理のコピーが、1つのバグを、複数の場所にばらまいてしまうこともあります。以下は、ある実際の企業システムにおける C# のバグ修正の例から、システム固有の変数名などをつぶしたものです。

   for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
-      break;
+      continue;
      }
      ret.Add((string)row.Cells[i].Value);
  }

この修正では、マイナスのついている行の break 文が、プラスで示された continue 文に置き換えられました。この処理では、表のセルに格納された値が null の場所を飛ばしてデータを取り出していくことが必要だったのですが、開発者が書いたコードは、最初の null を見つけた時点でループを打ち切ってしまっていました。

このようなバグが発見されたとき、開発者は、他に同じ誤りのあるコードはないかを検査し、まとめて修正を行うことが求められます。しかし、コード片の個々の単語だけで見ると、プログラムのあちこちに出現するものばかりです。grep のようなキーワード検索ツールだけでは、システム全体から、注目すべきポイントだけに絞り込むことは簡単ではありません。

そこで、コード片を入力として、ソースコードを検索するツールを開発しました。

github.com

ツールはコード片と検索対象ディレクトリ名を受け取り、ソースコード内から類似した内容のソースコードが出現する場所をすべて探し、報告します。この検索基準として、私たちは「正規圧縮距離(Normalized Compression Distance)」を採用しています。これはデータ圧縮アルゴリズムの特性を使用した技術の1つで、「テキスト X の内容をデータ圧縮した結果と、テキスト X の内容を2つ繰り返し並べたものをデータ圧縮した結果のデータの大きさが、ほとんど変わらない」という性質を活用したものです。上記のコード例を検索する場合、以下のようなテキストが入力となります。

   for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
      break;
      }
      ret.Add((string)row.Cells[i].Value);
  }

この内容を持ったテキストファイルを Windows 10 の「送る - 圧縮 (zip形式) フォルダー」 機能で圧縮すると、空白や改行込みで159バイトのデータが108バイトに圧縮されます。これを二度繰り返した以下のようなテキストを用意して圧縮すると、実際のデータ量は約2倍になったにも関わらず、圧縮結果は111バイトに収まります。

  for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
      break;
      }
      ret.Add((string)row.Cells[i].Value);
  }

  for (var i=0; i < row.Cells.Count; i++) {
      if (row.Cells[i].Value == null) {
      break;
      }
      ret.Add((string)row.Cells[i].Value);
  }

気になる方は、実際に手元の環境で試してみてください。zip 圧縮ではファイル名などの情報が一定量入るので、WindowsExplorer から見えるファイルサイズでは 220バイト前後になるかと思いますが、内容が2倍に増えても圧縮結果のファイルの大きさがほとんど変わらないことを確認できると思います。2回目のテキストの内容を書き換えて、情報の繰り返しを減らすと、圧縮結果のファイルが大きくなります。

この性質を使うと、入力として与えられたコード片と、ソースコードから適当に切り出してきたコード片をつないで圧縮をかけ、その大きさを確認することで、似ているソースコードの範囲を抽出することができます。上記のバグの事例では、システム全体から9個のコード片を報告し、うち2件が入力となったコード片とそのコピー(修正が必要な場所)、2件が同じ処理を既に正しく実装していた場所、残る5件は類似したループ構造だが今回のバグには無関係な場所となっていました。この検索方法は、かなり重たい計算のようにも見えますが、zip 圧縮のようなアルゴリズムは大容量のデータを高速に処理することに適しており、開発に使っているワークステーション上では Linux の600万行程度のソースコードを 20分で検索できるなど、実用性も意外と高いものとなっています(詳しくは論文PDFをご覧ください)。研究としては、「企業の開発者が日常的に使えるツール」を目指して、さらに高速な検索のための工夫に取り組んでいます。

なお、このような「類似したソースコード」を見つける技術については、コードクローン検出と呼ばれ、ソフトウェア工学でも息の長い研究分野の1つです。ソフトウェア工学研究室では、コードクローンの有無や量もソフトウェア品質の一指標として考えています。

門田 暁人, 佐藤 慎一, 神谷 年洋, 松本 健一: コードクローンに基づくレガシーソフトウェア品質の分析, 情報処理学会論文誌, vol.44, no.8, 2003. (論文PDF)

上記論文では、コードクローンを含むソースコードのほうが(おそらくは既にテストされたようなコードを流用しているために)バグが少ない、しかしあまりに多くのコードクローンを持つソフトウェアは品質が低いなど、いくつかの性質を報告しています。コピー&ペーストは非常に強力なツールの1つですが、活用しつつも度を超さないようにするというのも、開発者にとってはセンスの問われるところです。

 

ライブラリ利用例カタログの自動生成

ソフトウェアの色々な機能を作るための部品となるライブラリ。それらを初めてを使うときに頼りになるのが利用例、サンプルコードと呼ばれるものです。StackOverflow などの Q&A サイトでも、「〇〇を使って××するにはどうしたらいいか」という質問がよく行われています。

最近はオープンソースソフトウェアとして多数のプロジェクトのソースコードが公開されているのだから、そこからうまくクラスごとの利用例を収集できるのでは?と考えて、NAIST ソフトウェア工学研究室とオーストラリア・ウロンゴン大学の研究チームで協力してツールを作ってみました。

開発したツール Catalogen は、Java を対象として、クラス名を引数に、利用例のリストを HTML 形式で出力します。Apache POI という Microsoft Office のデータファイルを読み書きするライブラリのうち、XSSFWorkbook という Excel ファイルを扱うためのクラスの利用例を対象とした実行結果の例が こちら(XSSFWorkbook | Catalogen) です。

このツールは、以下のような仕組みで動作しています(著者最終版原稿PDFはこちら)。

  1. オープンソースソフトウェアのソースコードを検索するサービス Searchcode.com に接続し、クラス名をクエリとして渡し、そのクラス名を使っているソースファイルを集めます。
  2. 集めたソースファイルの中から、クラス名と変数名が連続しているところを見つけます。これが利用例の開始位置となります。
  3. 開始位置から、その変数名が出現する行だけを拾い集めます。これが利用例の本体となります。
  4. 同じ変数名が他の用途にも何度も使われる可能性があるので、"}" で変数のスコープが無効になったところで探索を終了し、見つかった行の範囲を利用例とします。
  5. 利用例それぞれに対して、Natural Machine Translation 技術で Java のメソッド → Javadoc コメントの対応関係を学習したモデルを適用し、内容を表現しそうなコメントを割り当てます。
  6. 利用例のうち、似ているもの同士をクラスタリングして、各クラスタから一番短いもの(最も単純そうなもの)を代表として、HTML に出力します。その他の利用例は、クラスタ名をクリックしたときだけ表示されるものとして扱います。

Apache POI の各クラスに対する実行結果はこちらにああります。コメントの内容等にはまだまだ改善の余地はありますが、全自動でこれだけ情報を整理できることに可能性を感じていただければ幸いです。本ツールは、ドキュメンテーション生成技術に関するワークショップ Dysdoc3 の一部として開催された DOCGEN CHALLENGE 2018 において、Usefulness 部門第2位に選ばれました。

このツールのようなドキュメント生成技術は、「ドキュメントを書いても誰も読まない、すぐに古くなってしまう」という問題に対して、「ドキュメントは必要なときに、その人の知識に合わせて作ればよい」という考えから、ここ数年、研究者が活発に議論を交わしているトピックの1つです。今後も、新しいアイディアや技術に基づいたツールが登場することになると思います。

 

開発者は使用するライブラリを更新しない

ソフトウェア開発では、画像の読み書きやネットワーク接続など、基本的な処理を提供するライブラリを土台にして、開発したいソフトウェアに固有の機能を作っていきます。

ほとんどの人は、開発を始めるとき、あるいはライブラリを必要とするような機能を実装するときに、ライブラリの名前やバージョンを調べて、その時点での最新版を開発に使います。それで何の問題なさそうにも思えますが、ライブラリの中にはセキュリティ問題などの深刻なバグが発見されることもあり、そういう場合には新しいバージョンに素早く移行することが求められます。ただ、セキュリティに関心のある一部の人々を除くと、ライブラリに新しいバージョンが出たといっても、大して気に留められることもありません。

そんな実態を、少しまじめに調べたのがこの論文です。

arxiv.orgいくつかのセキュリティ問題の報告に対して、横軸に時間、ライブラリの利用者数 (Library Usage, LU) を縦軸に取ったグラフを取ってプロットしてみました。以下は Apache Commons-beanUtils のセキュリティ問題発生時のものです(論文内 Fig.9 を引用)。

f:id:ishiotakashi:20190202221200p:plain

セキュリティ問題の報告に伴うライブラリ利用者数の変化

セキュリティ問題が2014年4月に報告され(図中の黒い縦の破線)、新しいバージョンがリリースされた後も、一定数のユーザが残っていることが分かります。利用者数のカウントは、リポジトリで更新作業をしていることを条件にしているので、「古いバージョンのライブラリを採用したまま、プロジェクトの更新が終わっている」ようなプロジェクトの数は除外しています。

調査対象全体では、 81.5%という多数のプロジェクトが古いバージョンのまま他の作業をしていました。この原因の1つは、個々のライブラリのバージョン更新内容をまじめに追いかけるのは大変である、ということにあります。幸い、2018年になって、GitHub など複数のサービスがセキュリティ警告機能(セキュリティ問題のあるバージョンのライブラリ利用者にメール等で連絡する機能)を提供するようになり、状況はだいぶ改善されました。それでも、ライブラリはバージョンごとに完全に互換性があるわけではなく、新バージョンに更新しようとして失敗する事例があることも報告されています。プロジェクト全体の他の作業で忙しい開発者にとっては、ライブラリの更新を迅速に実行するのは難しいかもしれません。

ライブラリ開発者が互換性を保ってくれれば自動更新ができるのに、という期待もあるかもしれませんが、非互換性の導入にもエラー報告の詳細化や危険な機能の削除など、相応の理由があることが知られています。そのため、ソフトウェアを新しいバージョンのライブラリでテスト実行して安全に更新できそうなときだけ自動で通知する、といった手法も提案されています。 また、本当に今すぐ更新する必要があるのか、セキュリティ問題のあるコードを実際に呼び出しうるか・実行しているかを自動的に判定するソフトウェアというのも研究レベルでは出てきています。以下がそういうプロジェクトの1つです。

github.comこの記事を執筆した時点では、すぐに実用に供するというには難しい開発段階のようですが、いずれは、このようなツールでソフトウェアの状態をチェックし、セキュリティ問題の報告に素早く応答することがソフトウェア開発の標準となるかもしれません。

 

ソースコードのコメントに登場する URL の役割

プログラミングの最中に、ライブラリの使い方やアルゴリズムの書き方など、様々な情報をインターネット上から集めてくることが一般的になっています。プログラムを書いたときに参考にした情報の URL をコメントとして一緒に記述しておけば、あとで参照できるので便利です。

そのような URL が実際にコメント中にどれぐらい存在し、どんな情報に対するリンクを記録しているのか。また、Web でも時折見かける「リンク切れ」のような問題が起きていないか、リンクはきちんと更新されているのか、という開発者の URL 利用のスタイルを調査しました。ソフトウェア工学の国際会議(ICSE 2019)で発表予定の著者最終版を既に公開しています。

arxiv.org
調査対象は GitHub プロジェクトに存在する 25,925個のリポジトリです。プログラミング言語として、ここ10年間で継続的に多くの開発者に使用されている(=人気の高い)プログラミング言語である C, C++, Java, JavaScript, Python, PHP, Ruby の7つのどれかを使用しているプロジェクトのうち、少なくとも2年間は活発に活動していたものを選定しています。各言語の文法に従ってソースファイルからコメントを抽出し、正規表現を使って URL を収集して、トータルで 9,654,702個のリンクを集めました。

これに対して色々な分析を行っていますが、主な結果は以下の通りです。

  • 80%以上のリポジトリで少なくとも1つは URL がコメントに出現する。リンク先として最も多く出現するドメインgithub.com, stackoverflow.com, en.wikipedia.com である。
  • リンク先のコンテンツとしては、ソフトウェアライセンスや、ソフトウェアプロジェクトや開発組織の情報、何らかの要求仕様・技術標準、チュートリアルAPI の利用法などがある。コメントの内容から、リンクはソースコードに関する付加的な情報を参照できるようにするために書かれている。
  • 全 URL のうち 9%がリンク切れになっている。ライセンスのような頻出するものはリンク切れが少なく、それ以外の用途のものが相対的にリンク切れが多い。GitHub 上のデータに対するリンクも、ソースコードの構造の変化などが原因でリンク切れが生じていることが多い。
  • リンクは作られたあと、ほとんど更新されない(9%未満)。更新されているもののほとんどは、ライセンス更新や開発組織名の変更に伴う修正だった。
  • リンク切れのうち、新しい URL が分かったものを14件 Pull Request として送ったところ、ファイルを変更しないでおきたい理由のある 1件を除いて受理されたので、開発者もリンク切れが望ましくないものであると考えていると思われる。
  • StackOverflow の質問を指す URL の場合、その URL がソースコード中に初めて出現した日よりも後に、StackOverflow 側でコメントが増えるなどの変化が生じているケースが多い。つまり、そのソースコードを書いた時点で参考にした情報と、あとから見に行ったときに得られる情報は一致しない場合がある。

結果を踏まえると、何か外部ドキュメントを参考にプログラムを書いたときは、無効になりにくい URL を使う(たとえば文献ならば DOI を使う)こと、無効になっても再検索できるようにコメント側にもリンク先に関する説明を書いておくこと、そして定期的にリンク先の情報が新しくなっていないか確認することが重要であるといえそうです。ソースコードの更新にともなってコメント自体が古くなることもあるので、コメントやリンクを適切に保守していく方法を考えるのが、今後の課題といえるでしょう。