Perl Hackers Hub

第46回Perl 5.26で変わること(1)

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーはcharsbarこと石垣憲一さんで、テーマは「Perl 5.26で変わること」です。

Perl 5.26リリース

去る2017年5月30日にPerl 5.26がリリースされました。これは1987年にPerl 1.0が生まれてから30年、モダンPerl運動のきっかけとなったPerl 5.10が2007年に生まれてから10年という節目のリリースです。2016年春に新たにパンプキン[1]に就任したSawyer X氏のリードのもとでリリースされる最初の安定版[2]でもあります。

Perl 5はかれこれ20年以上も同じメジャーバージョンを使い続けていることからも察せられるように、後方互換性が比較的よく保たれている言語です。それでも、節目節目では新機能の追加とともに多かれ少なかれ互換性が失われるような変更が行われてきました。その多くはバグと言っても差し支えないような細かな挙動の変更や実験的な機能に関するものですが、2016年12月から2017年7月にかけて札幌、大阪、福岡で開催されたYAPCYet Another Perl Conferenceなどで既報のとおり、Perl 5.26では一部の人に非常に大きな影響を及ぼしそうな非互換性がいくつか入っています。

Perl 5.26で何が変わったかは、5.26付属のperldeltaという文書にまとまっています。

本稿では、まずPerl 5.26の最大の変更点である特殊変数@INCからカレントディレクトリが削除された経緯とその対策を説明し、そのあと、細かな互換性の問題やPerl 5.26の新しい機能を紹介します。

@INCからカレントディレクトリが削除

Perlはバージョン3の時代である1990年から、ファイルやモジュールの呼び出し元を指定する特殊変数@INCの末尾に、カレントディレクトリを表す.を保持していました。20世紀末のCGIスクリプトでは歌代和正氏が作成したjcode.plというライブラリがよく利用されましたが、次のようなコードを書いて、CGIスクリプトの置かれているディレクトリにjcode.plというファイルを設置するだけで期待したとおりに動作したのは、@INCの末尾にカレントディレクトリが含まれていたからです。

# ほかの場所にインストールされていなければ
# CGIスクリプトと同じディレクトリにある
# jcode.plを読み込む
require "jcode.pl";

Perl 5.26では、セキュリティ上の理由でデフォルトの@INCから.が削除されました。上のコードはそのままではもう期待どおりの動作はしません[3]⁠。

何が問題だったのか

UNIX系OSの世界、とりわけシステム管理者の間では前世紀から、実行ファイルを検索するパスにカレントディレクトリが含まれているとセキュリティ的によろしくない、という議論がされてきました。たまたまそのディレクトリに何らかの実行ファイルが存在していた場合、タイプミスなどの意図しない理由でそのファイルが実行されてしまう恐れがあるためです。安全のためには、システム標準以外のコマンドを実行するときは、どのディレクトリにあるコマンドかを明示すべきです。

読み込んだ時点でなにがしかのコードが実行されてしまうという点では、スクリプト言語のライブラリ類も同じです。正規のインストールパスにはインストールされていないライブラリがたまたまカレントディレクトリに存在していた場合、ライブラリの検索パスにカレントディレクトリが含まれていると、場合によっては意図しない形でそのライブラリが読み込まれ、内部のコードが実行されてしまいます。これは、特に特権ユーザーでスクリプトを実行することの多いシステム管理者には歓迎できないことです。

Rubyの対応とPerlの初期の反応

そのため、たとえばRubyの世界では2010年8月にリリースされたバージョン1.9.2でロードパス$:からカレントディレクトリが削除されました

Perlの世界でも、同じく2010年8月に@INCからカレントディレクトリを削除する提案がされていたほかその2年後、Perl 5.16のリリースを間近に控えた2012年3月にも議論が再燃し、賛成反対合わせて都合40通ほどのメールが飛び交いました。ただ、20年も前から便利に使われ続けてきた仕様を変更することで、既存のモジュールやアプリケーションに大きな影響が出ることを心配する声が強かったことから、いずれも明確な結論は出ないまま議論は立ち消えになりました。

CVE-2016-1238とPerlの対応

この議論は2016年4月にまたも蒸し返されるのですが、今度は机上の空論ではなく実際に深刻な問題が発覚した─⁠─Debian GNU/Linux系ディストリビューションのパッケージ管理ツールが、実行中に誰でも読み書きできるディレクトリに移動したうえで内部的にPerlスクリプトを呼び出していたことから、共有サーバなどで悪意を持ったユーザーがそのディレクトリにしかけをすると容易にルート権限を奪える状態になっていた─⁠─ということで、これまでのような公開チャンネルではなく、セキュリティ問題を扱う非公開のメーリングリストで話が進んでいきます。

対策を取れば既存のモジュールやアプリケーションが壊れることはこれまでの議論からもわかっていたので、まずはそもそもPerl側が対策すべきか、またどこまで対策すべきかをすり合わせ、それからどう対応すれば影響を最小限にとどめられるか、関係者の間で議論されていきました。

公式にはCVE-2016-1238というIDで識別されるこの脆弱性に関する議論は1年近く続きました。その詳細は割愛しますが、最終的にはシステム管理用のスクリプトの安全性を保つのはスクリプトの作者の責任としたうえで、深刻な問題が起こる可能性を減らすため、Perl側でもデフォルトの@INCからカレントディレクトリが削られました。コアモジュールのうち特によく使われるものについては、モジュール側にも対策を施して古いPerlが入っている環境への対策としたうえで、CPANモジュールのインストールに支障が出ないようツールチェイン側にしかけが加えられました。また、この問題の影響を受けていることに気付かれづらいdo関数については、警告を出すことになりました。

どのように対応すればよいのか

この変更によって、CPANで公開されているものに限っても数千件のディストリビューションが影響を受けることがわかっています。その中にはCatalystのように多くの利用実績があるWebアプリケーションフレームワークなども含まれているため、実際に影響を受ける人やアプリケーションの数はさらに増えることでしょう。

どのように対応すればよいかは立場によって変わりますが、典型的な対策は次のとおりです。

ツールチェインを更新する

一般的なCPANモジュールについては、CPANクライアントを最新のものに更新しておけば、この変更に由来するインストール時の問題はほぼ解決します。

最近新たにPerl 5.26をインストールした人ならすでに最新のツールを使っていると思いますが、環境によっては5.26より前のPerlにも今回の変更の一部が適用されていることがあります。モジュールのインストール時にファイルが見つからないなどのエラーを見かけたら、CPANクライアントとTest::Harnessモジュールを最新のものにしてください。

CPANクライアントとしてcpanmコマンドを使っている人は、コマンドラインから以下を実行することで最新版に更新できます。

$ cpanm App::cpanminus Test::Harness

また、Minillaなどのモジュールオーサリングツールを使っている場合も、最新にしておきましょう。

環境変数を設定する

CPANクライアントを最新のものに更新するだけでインストール時の問題が解決するのは、内部で一時的にPERL_USE_UNSAFE_INCという環境変数を設定して、Perlに従来どおり@INCの末尾にカレントディレクトリを追加させているためです。

この環境変数はあくまでも一時しのぎで、数年後にはサポートされなくなる可能性も高いのですが、ローカルの開発環境で自分以外のユーザーがいないとか、CIContinuous Integration継続的インテグレーション)環境のように都度環境を使い捨てるから大丈夫という場合は、ログイン時などに適宜この環境変数をセットしておくことで、問題を先送りできます。

$ export PERL_USE_UNSAFE_INC=1

適切な検索パスを指定する

CPANモジュールのようにほかのユーザーにインストールして使ってもらうモジュールやアプリケーションを開発している人は、検索パスにカレントディレクトリがなくてもモジュールやアプリケーションが正常動作するようにしておいたほうがよいでしょう。

もっとも、libディレクトリの下にまとめられているモジュールやアプリケーション自体がこの問題の影響で挙動を変えることはめったにありません。問題となるのはたいてい、Makefile.PLBuild.PL各種のテストファイルといった補助的なスクリプト類です。

これらの中でinc::Module::Installt::Testのようなディレクトリ名を含むモジュールを利用している場合は、暗黙のうちに@INCの中にカレントディレクトリが含まれていることが前提となっているため、適切な検索パスを追加する必要があります。

従来どおりの挙動にしたい場合は、Makefile.PLやテストファイルの先頭で、@INCの末尾にカレントディレクトリを追加してください。

# 末尾にデフォルトのカレントディレクトリがなければ追加
BEGIN { push @INC, "." if $INC[-1] ne "."; }

上記で条件節を追加しているのは、古いPerlや、前述した環境変数が設定された場合に何度もカレントディレクトリを追加させないためです。

CPANディストリビューションの場合、普通はカレントディレクトリが安全である(アーカイブを展開したあと、悪意を持った第三者のファイルがカレントディレクトリ以下に紛れ込むことはない)ことが前提になっているので、簡潔にlibモジュールを使ってカレントディレクトリを追加してもよいでしょう。

use lib ".";

ただし、libモジュールは@INCの先頭にカレントディレクトリを追加するため、実行中にファイル検索などの理由でカレントディレクトリを移動すると、かえって意図しないモジュールを読み込みやすくなってしまう(インストールされているモジュールよりも移動後のカレントディレクトリに存在しているモジュールを優先してしまう)危険性があります。安全性を優先するなら、FindBinモジュールや__FILE__キーワードなどを利用して、@INCには相対パスを含めないようにしたほうがよいでしょう。

use FindBin;
use lib $FindBin::Bin;

別のツールに切り替える

場合によっては、カレントディレクトリを検索パスに戻すのではなく、そもそもカレントディレクトリを追加しなくてもよいツールに切り替えるべきかもしれません。

たとえば、この問題の影響を受ける代表的なモジュールであるModule::Installについては、さまざまな理由からもう新規の開発では使うべきではないと最新版のドキュメントに警告が書いてあります。可能であればMinillaなどのオーサリングツールに移行するか、特殊な設定が不要ならもとのExtUtils::MakeMakerExtUtils::MakeMaker::CPANfileなどのモジュールを使うことも検討してください。

doやrequireでライブラリを読み込んでいる場合

古くからあるCGIスクリプトなどでrequire関数を使ってカレントディレクトリにあるライブラリを読み込ませている場合は、次のように書くと、ライブラリがカレントディレクトリにあることを明示できます。

require "./jcode.pl";

アプリケーションの設定をdo関数で読み込ませている場合も同じです。

do "./config.pl";

もちろん./の代わりに$FindBin::Binなどでパスを指定してもかまいません。

doの新しい警告

douserequireと違ってファイルが見つからなくてもエラー終了しないため、今回の@INCの変更でカレントディレクトリのファイルが見つからなくなっても気付けません。そのため、Perl 5.26ではカレントディレクトリに該当のファイルが存在している場合に限り、次のような警告を出します。

do "config.pl" failed, '.' is no longer in @INC;
did you mean do "./config.pl"?

古いPerl向けの対応

ここまではPerl 5.26で従来の挙動を取り戻したい場合にどうすればよいかを説明してきましたが、場合によっては、Perl本体のバージョンは上げられないけれど、この検索パスの保護は導入したいということもあるかもしれません。

OSによっては古いバージョンのPerlにこの変更をバックポートしているものがありますが、Perlの設定によってはPerlにパッチを当てたりしなくても簡単にこのしくみを導入できることがあります。

簡単に対応可能なPerlかどうかを調べるには、次のコマンドを実行します。大文字の-Vオプションは、そのまま使えばPerlの詳細設定を表示しますが、コロンに続いて項目名を渡すとその項目のコンパイルオプションを表示します。

$ perl -V:usesitecustomize
usesitecustomize='define';

defineの値が返ってきたら対応できます。

続いてもう一つPerlの設定を調べます。

$ perl -V:sitelib
sitelib='/usr/local/share/perl5';

表示されたディレクトリの下にsitecustomize.plというファイルがなければ作成し、次のようなコードを追加します[4]⁠。このディレクトリとファイルには忘れずに適切なパーミッションを設定しておいてください。

if (!$ENV{PERL_USE_UNSAFE_INC}) {
    pop @INC if $INC[-1] eq ".";
}

これでperl -Vを実行すると、@INCから.が消えたことが確認できるはずです[5]⁠。

独自のコマンドをインストールする場合

独自のコマンドをインストールするCPANディストリビューションについては、古い環境での安全性を高めるため、コマンドの先頭で@INCからカレントディレクトリを抜くようにしたほうがよいでしょう。

典型的にはコマンドの先頭付近に先ほどと同じような行を追加します。

BEGIN { pop @INC if $INC[-1] eq "."; }

なお、このような@INCの調整は起動に使われるスクリプトの先頭で一度行えば十分です。Perlに同梱されるコアモジュールの中には、影響の大きさを鑑みて動的にモジュールをロードする場面でモジュール内部で@INCをローカライズしてカレントディレクトリを省いているものもありますが、これは関心の分離の原則をあえて破った例外的な処置としてよいでしょう。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.130

2022年8月24日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-13000-8

  • 特集1
    イミュータブルデータモデルで始める
    実践データモデリング

    業務の複雑さをシンプルに表現!
  • 特集2
    いまはじめるFlutter
    iOS/Android両対応アプリを開発してみよう
  • 特集3
    作って学ぶWeb3
    ブロックチェーン、スマートコントラクト、NFT

おすすめ記事

記事・ニュース一覧