きりかノート 3冊め

おあそびプログラミング

OS Xの"DYLD_*"環境変数

先日のCocoa勉強会で話してきたので整理しとく。

まとめ

dyldと"DYLD_"ではじまる環境変数

dyldはOS X/iOSのダイナミックリンカで、ライブラリやフレームワークをロードするプログラムです。man DYLD(1)にあるように、"DYLD_"ではじまる環境変数で動作を変更することができます。UnixLinuxLDでいうところのLD_LIBRARY_PATHやLD_PRELOADの類ですね。

すっごく雑に分けると、これらの環境変数

  • ライブラリの探索方法を指示
    • DYLD_*_PATH: 探す場所を指示する。
    • DYLD_IMAGE_SUFFIX: デバッグ用などに用意したライブラリのsuffixを指示する。
  • 実行中の情報を表示
    • DYLD_PRINT_*: ロードしたものや実行環境の情報などを標準エラー出力に表示する。
  • その他
    • DYLD_INSERT_LIBRARIES: LD_PRELOAD
    • DYLD_FORCE_FLAT_NAMESPACE, DYLD_BIND_AT_LAUNCH: リンカの解決方法を指定する。

という感じになります。

RubyCocoaの開発ではテストスクリプトの実行時に

  • DYLD_FRAMEWORK_PATH: 開発中のRubyCocoa.frameworkの場所を指示する。
  • DYLD_PRINT_LIBRARIES_POST_LAUNCH: テストスクリプトが正しく開発中のRubyCocoa.frameworkやrubycocoa.bundleをロードしたかを確認する。

の2つを使っています。プログラム的には次のようなrubyスクリプトです。

   ENV['DYLD_FRAMEWORK_PATH'] = path_to_devel_framework
   ENV['DYLD_PRINT_LIBRARIES_POST_LAUNCH'] = '1'
   libs = Open3.popen3("ruby -rosx/cocoa -e ''") do |stdin, stdout, stderr|
     # read stderr
   end
   # test loaded rubycocoa framework/bundle paths from stderr

DYLD_PRINT_LIBRARIESとDYLD_PRINT_LIBRARIES_POST_LAUNCHのちがいは、プロセスの起動時にロードしたものを表示するか否かです。

   % DYLD_PRINT_LIBRARIES=YES ruby2.2 -e 'require "zlib"'
   dyld: loaded: /opt/local/bin/ruby2.2
   dyld: loaded: /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
   dyld: loaded: /opt/local/lib/libruby.2.2.0.dylib
   dyld: loaded: /usr/lib/libSystem.B.dylib
     :
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/encdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/trans/transdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/thread.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/date_core.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/zlib.bundle
   dyld: loaded: /opt/local/lib/libz.1.dylib
   % DYLD_PRINT_LIBRARIES_POST_LAUNCH=YES ruby2.2 -e 'require "zlib"'
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/encdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/trans/transdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/thread.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/zlib.bundle
   dyld: loaded: /opt/local/lib/libz.1.dylib
   %

OS X 10.11での制限

そろそろ10.11がリリースされそう(当時beta 8)なのでRubyCocoaの検証をしていたところ、上記のテストスクリプトフレームワークのパスの検証が通らないことに気付きました。どうもDYLD_FRAMEWORK_PATH環境変数が機能していないようです。

試してみると、

   % uname -r
   15.0.0 # 10.11 GM
   % ruby -e "system({'DYLD_AAA' => 'AAA', 'AAA_DYLD_BBB' => 'BBB'}, \
                 '/usr/bin/printenv | grep DYLD')"
   AAA_DYLD_BBB=BBB
   %

と、"DYLD_"ではじまる環境変数が子プロセスに反映されないようになっているようです。betaの無印のときはこんなことなかったと思うんだけどなあ。もちろんOS X 10.10では"DYLD_AAA"も出力されます。

仕方ないので、次のような一時ファイルのシェルスクリプトを生成して実行することを考えています。

   # temp shell script for testing with DYLD_ environments
   DYLD_FRAMEWORK_PATH=path ${RUBY} -rosx/cocoa -e ''

さすがにこれで解消するのですが、なんかすごく不毛な気持ちになります。もうちょっとスマートに書けるような…
よい方法があれば教えてください!だいぶ切実です。

(2015-09-19追記: ついったで @n0kada さんに`env`コマンドでいけることを教えてもらいました。ありがとうございます!)

また、WWDC 2015のセッション706:"Security and Your Apps"(ASCIIwwdc)で説明されているように、保護される領域(/Systemや/usr/binなど)にあるバイナリではDYLD_*環境変数は問答無用で無視されるようになっています。

例を示すと、

   % uname -r
   15.0.0 # 10.11 GM
   % cat dyld_env.rb
   # print DYLD_* environments
   ENV.each_pair do |k, v|
       puts "#{k}=#{v}" if k =~ /DYLD/
   end
   % DYLD_AAA=AAA AAA_DYLD_BBB=BBB /usr/bin/ruby dyld_env.rb
   AAA_DYLD_BBB=BBB
   % DYLD_AAA=AAA AAA_DYLD_BBB=BBB /opt/local/bin/ruby2.2 dyld_env.rb
   dyld: warning, unknown environment variable: DYLD_AAA
   DYLD_AAA=AAA
   AAA_DYLD_BBB=BBB
   %

のように、システム付属のrubyにはDYLD_ではじまる環境変数が渡されないようになっています。

感想

セキュリティの観点としてはいままでがざるざるだったこともあり、制限は仕方ないし正しい方向だとは思います。だけど、プログラム書く人間としてはめんどくさいなあという気持ちも隠せません。

人によってはぜんぜん影響ないかもしれませんが、思わぬワナにはまるかもなのでご注意を。