2015年06月17日

Android で今後ネイティブ実行形式を扱う際に注意すべきこと

はてなブックマークに登録

2010年の年末、このブログに以下の記事を掲載しました。

Android NDK でネイティブ CUI プログラムを書く!

当時は Android 2.3 を搭載した機種がぽつぽつ出始めたころでした。2015年6月現在の最新バージョンは Android 5.1.1 であり、Android 5.0/ 5.1 (Lollipop) からは PIE (Position Independent Executable) 以外のネイティブ実行形式がサポート外となったため注意が必要です。

$ ./hello 
error: only position independent executables (PIE) are supported.

PIE にはプロセス空間上のどのアドレスに配置されてもメモリ上のアドレステーブル等を書き換えることなくそのまま実行できるという特長があり、アドレス空間レイアウトのランダム化(ASLR Address Space Layout Randomization)と組み合わせて利用すればプロセス上の所定のアドレスを想定したセキュリティ攻撃への対策として有効とされています。(ただし、PIE の生成・実行にはコンパイラと実行環境がこれに対応している必要があります)

A look at ASLR in Android Ice Cream Sandwich 4.0 - www.duosecurity.com

先年公開した Google Play ストア上のアプリ「stone for Android」には パケットリピータプログラム「stone」のネイティブ実行形式を梱入しています。ここしばらく Android から手が離れていたのですが、利用者の方からこのアプリが Android 5.0 環境で正常に動作しない旨のフィードバックを受けて上の事情を知り調査と対応を行いました。Lollipop で非 PIE を実行できないことは随所で言及されているものの、後方互換性やアプリでの対処方法に関する具体的な話題は意外と見あたらないため備忘をかねて以下に情報を控えます。

まずは動作確認

まず各プラットフォームで首実検を行いました。現時点で最新の NDK-r10 を使って stone と小さな "hello!" プログラムの PIE 版と 非 PIE 版をビルドしました。それを Android 各バージョンの ARM エミュレータ上で実行した結果を以下に示します。

NDK用 プロジェクト一式 : stone & hello

https://github.com/mkttanabe/stone-Android-NDK/stone

https://github.com/mkttanabe/stone-Android-NDK/hello
hello/jni/hello.c
#include <stdio.h>

int main()
{
    puts("hello!");
    return 0;
}
hello/jni/Android.mk
※太字 2行で PIE をビルド、外せば非 PIE をビルド
LOCAL_PATH := $(call my-dir)

TARGET_PIE := false
NDK_APP_PIE := false

include $(CLEAR_VARS)

LOCAL_CFLAGS += -fPIE
LOCAL_LDFLAGS += -fPIE -pie

LOCAL_MODULE    := hello
LOCAL_SRC_FILES := hello.c

LOCAL_LDLIBS += -ldl

include $(BUILD_EXECUTABLE)
hello/jni/Application.mk
APP_PLATFORM := android-19

# build for arm
APP_ABI := armeabi

# build for x86
#APP_ABI := x86

各プラットフォームでの実行結果

  • Android 5.0 (Lollipop) 以降は PIE のみに対応しており非 PIE の実行はブロックされる
  • Android 4.1 (Jelly Bean) 〜 Android 4.4 (KitKat) では PIE / 非 PIE のいずれも実行可
  • Android 4.0 (Ice Cream Sandwich) 以前のすべてのプラットフォームでは PIE を実行することができない
hello hello (PIE) stone stone (PIE)
5.1.1 (API 22) Lollipop "Not
supported"
OK "Not
supported"
OK
5.0.1 (API 21) "Not
supported"
OK "Not
supported"
OK
4.4W.2 (API 20) KitKat OK OK OK OK
4.4.2 (API 19) OK OK OK OK
4.3.1 (API 18) Jelly Bean OK OK OK OK
4.2.2 (API 17) OK OK OK OK
4.1.2 (API 16) OK OK OK OK
4.0.3 (API 15) Ice Cream
Sandwich
OK segfault OK segfault
4.0 (API 14) OK segfault OK segfault
3.2 (API 13) Honeycomb OK segfault OK segfault
3.1 (API 12) OK segfault OK segfault
3.0 (API 11) OK segfault OK segfault
2.3.3 (API 10) Gingerbread OK segfault OK segfault
2.2 (API 8) Froyo OK segfault OK segfault
2.1 (API 7) Eclair OK segfault OK segfault
1.6 (API 4) Donut OK segfault OK segfault
1.5 (API 3) Cupcake OK segfault OK segfault

5.1.1 (API 22) 環境で実行時の様子

root@generic:/data/local/tmp # ./hello 
error: only position independent executables (PIE) are supported.

root@generic:/data/local/tmp # ./hello_pie                                   
hello!

root@generic:/data/local/tmp # ./stone
error: only position independent executables (PIE) are supported.

root@generic:/data/local/tmp # ./stone_pie                                   
May  1 10:40:28.556080 start (2.3e) [375]
May  1 10:40:28.570865 stone 2.3e  http://www.gcd.org/sengoku/stone/
May  1 10:40:28.572752 Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>
May  1 10:40:28.573123 using OpenSSL 1.0.1j 15 Oct 2014 http://www.openssl.org/
Usage: ./stone_pie <opt>... <stone> [-- <stone>]...
opt:  -h opt            ; help for <opt> more
      -h stone          ; help for <stone>
      -h ssl            ; help for <SSL>, see -q/-z opt

4.1.2 (API 16) 環境で実行時の様子

root@android:/data/local/tmp # ./hello                                       
hello!

root@android:/data/local/tmp # ./hello_pie                                 
hello!

root@android:/data/local/tmp # ./stone
May  1 19:50:32.740789 start (2.3e) [647]
May  1 19:50:32.756406 stone 2.3e  http://www.gcd.org/sengoku/stone/
May  1 19:50:32.759034 Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>
May  1 19:50:32.760995 using OpenSSL 1.0.1c 10 May 2012 http://www.openssl.org/
Usage: ./stone <opt>... <stone> [-- <stone>]...
opt:  -h opt            ; help for <opt> more
      -h stone          ; help for <stone>
      -h ssl            ; help for <SSL>, see -q/-z opt

root@android:/data/local/tmp # ./stone_pie                                   
May  1 19:50:40.402476 start (2.3e) [651]
May  1 19:50:40.412950 stone 2.3e  http://www.gcd.org/sengoku/stone/
May  1 19:50:40.413214 Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>
May  1 19:50:40.415012 using OpenSSL 1.0.1c 10 May 2012 http://www.openssl.org/
Usage: ./stone_pie <opt>... <stone> [-- <stone>]...
opt:  -h opt            ; help for <opt> more
      -h stone          ; help for <stone>
      -h ssl            ; help for <SSL>, see -q/-z opt

4.0.3 (API 15) 環境で実行時の様子

# ./hello
hello!

# ./hello_pie
[1] + Stopped (signal)        ./hello_pie
[1]   Segmentation fault      ./hello_pie

# ./stone
May  1 10:53:22.265410 start (2.3e) [112]
May  1 10:53:22.423641 stone 2.3e  http://www.gcd.org/sengoku/stone/
May  1 10:53:22.425563 Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>
May  1 10:53:22.425869 using OpenSSL 1.0.0e 6 Sep 2011 http://www.openssl.org/
Usage: ./stone <opt>... <stone> [-- <stone>]...
opt:  -h opt            ; help for <opt> more
      -h stone          ; help for <stone>
      -h ssl            ; help for <SSL>, see -q/-z opt

# ./stone_pie
[2] + Stopped (signal)        ./stone_pie
[2]   Segmentation fault      ./stone_pie

アプリでの対処のしかた

上の結果の通り、Android 4.0 以前の環境では PIE を実行することはできません。そのため、ネイティブ実行形式を含むアプリケーションを Lollipop 以降の環境と Ice Cream Sandwich 以前の環境の両方に対応させるためには相応の対処が必要となります。

方法 1 : PIE と 非 PIE のふたつを使い分ける

stone for Android の場合、アプリに内蔵しているネイティブの実行形式は stone 本体のみで、そのファイルサイズは 140KB 程度です。そのため アプリに PIE 版と非 PIE 版の両方を持たせ、起動時に当該プラットフォーム用の実行形式を切り出して使用するというもっとも素朴な対応を選びました。
※このアプリは現時点では ARM 専用です

stone-for-Android/src/jp/klab/stone/stone.java#L103

                         :

    String MyDir = mUtil.GetMyResFileDirectory();
    if(Build.VERSION.SDK_INT >= 21) { // LOLLIPOP = android 5.0
        // only Position Independent Executables (PIE) are supported
        mUtil.ExtractMyResFile(R.raw.stone_pie, "stone", MyDir, "744");
    } else {
        // for backward compatibility
        mUtil.ExtractMyResFile(R.raw.stone, "stone", MyDir, "744");
    }
                         :

方法 2 : "run_pie" ユーティリティを利用する

run_pie は Android 4.0 (Ice Cream Sandwich) 環境で PIE を実行することを目的に Chromium プロジェクトの primiano@chromium.org 氏が開発したラッパプログラムです。手元にあるもっとも旧い Android 1.6 環境で試したところ、このツールを利用することで所定の PIE を正常に実行することができました。これをアプリに同梱し ICS 以前の環境で PIE を実行する際には そのフルパスと引数を run_pie へ渡してラップしてやれば互換性のジレンマをスマートに解決できそうです。

Android 1.6 環境で実行した様子
(直接 PIE を実行すると異常終了、run_pie 経由なら OK)

# /data/local/tmp/hello_pie2
[1] + Stopped (signal)        /data/local/tmp/hello_pie2
[1]   Segmentation fault      /data/local/tmp/hello_pie2

# ./run_pie /data/local/tmp/hello_pie2
hello!

#  /data/local/tmp/stone_pie2
[1] + Stopped (signal)        /data/local/tmp/stone_pie2
[1]   Segmentation fault      /data/local/tmp/stone_pie2

# ./run_pie /data/local/tmp/stone_pie2
Jun 17 08:02:07.942424 start (2.3e) [212]
Jun 17 08:02:07.950579 stone 2.3e  http://www.gcd.org/sengoku/stone/
Jun 17 08:02:07.950843 Copyright(C)2007 by Hiroaki Sengoku <sengoku@gcd.org>
Jun 17 08:02:07.951066 using OpenSSL 0.9.8h 28 May 2008 http://www.openssl.org/
Usage: /data/local/tmp/stone_pie2 <opt>... <stone> [-- <stone>]...
opt:  -h opt            ; help for <opt> more
      -h stone          ; help for <stone>
      -h ssl            ; help for <SSL>, see -q/-z opt
run_pie のソースは以下の場所にあります。
Index of /trunk/src/tools/android/run_pie - src.chromium.org

run_pie でラップして実行する PIE は以下のフラグを付与してビルドする必要があります。

LOCAL_CFLAGS +=-fvisibility=default -fPIE
LOCAL_LDFLAGS += -rdynamic -pie

※ビルドした PIE は Android 5.0 以上の環境ではもちろんそのまま実行できます

ビルドずみの run_pie 本体と上記のフラグでビルドした stone, hello の PIE を以下の場所に置いています。

https://github.com/mkttanabe/stone-Android-NDK/run_pie

run_pie 開発者による書き込み:
Issue 373219: Android binaries must now be PIE (but ICS doesn't support them) - code.google.com

Project Member     Reported by primi...@chromium.org,    May 14, 2014

Recent versions of Android require our native binaries 
(forwarder, md5sum, adb_reboot, purge_ashmem, memdump) to be PIE.
However, the same binaries must be also able to run on our bots 
running previous versions of Android all the way down to ICS.
Sadly, ICS doesn't seem to support PIE (see b/6587214 and crbug.com/147832).
                       :
3) I managed to write a "run_pie" wrapper (cl coming soon) for supporting
PIE on ICS. The idea is to just wrap commands with run_pie /actual/binary args.
The (small) price to pay is two extra gyp flags (only for android tools exe
targets) to force the PIE executable to export "main", so the pie wrapper
can dlsym it (at least, as long as we'll support ICS).

run_pie 利用者からのコメント:
Issue 888: Blocking non-PIE binaries breaks the ABI - code.google.com

#9 androtu...@gmail.com   Nov 7, 2014

I've been shipping executable in my apps for about 4 years. Now I build all
binaries with PIE option and bundle a "run_pie" non-PIE binary for older
Android versions.

This are the build option for all binaries except the run_pie:

LOCAL_LDLIBS := -pie -rdynamic
LOCAL_CFLAGS := -fPIE -fvisibility=default

When PIE will be used as default build option, I'm not sure which option
will disable it so I can still build the run_pie binary.

So far it seems to be working fine for all my users. 

That's where I found the run_pie information:
https://code.google.com/p/chromium/issues/detail?id=373219

方法 3 : Multiple APK を使用する

Google Play ストアには、同一のアプリケーションについて所定の環境に対しそれぞれ実体の異なる所定の apk を配布することのできる Multiple APK という機能があります。Multiple APK を使用すると apk を開発・管理する上でのシンプルさが損なわれるため Google は可能な限り Single APK を使うことを推奨しており、下記ドキュメントの最初の注釈には「only when your APK is too large (greater than 50MB) 」とコメントを添えています。ただし、何らかの積極的な動機があればこの手法もまた PIE/ 非 PIE とプラットフォームの間の不整合を解決するための選択肢のひとつとなり得るかも知れません。

公式ドキュメント:

Multiple APK Support - developer.android.com
参考訳:
(tanabe)
klab_gijutsu2 at 20:24│Comments(3)TrackBack(0)Android | google

トラックバックURL

この記事へのコメント

1. Posted by 匿名希望   2016年02月12日 16:29
Android6.0でsslライブラリがopensslでなくなってるため動作しなくなっています。

staticリンクすることで動作することを確認してます。

対応お願いできますでしょうか?
2. Posted by tanabe   2016年02月12日 16:36
おっと、その話題がありましたね。手元が落着き次第対応したいと思います。今しばらくお待ち下さい。後日動作確認にご協力頂ければ幸いです。
3. Posted by tanabe   2016年02月21日 08:20
本日、Android 6.x への対応を加えた stone for Android 1.0.3 を公開しました。
https://play.google.com/store/apps/details?id=jp.klab.stone
テスト版の動作確認にご協力頂いた<匿名希望>様に謹んで御礼申し上げます。

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント