2011年5月24日火曜日

Android JNIライブラリの利用<その1>

Android JNIライブラリの作成で開発したライブラリプロジェクトを、そのままEclipseから利用する方法です。
<その1>ではJNIを対象としていますが、利用方法はJavaプロジェクトも全く同一です。<その2>はJNI専用になります。

<Android JNIライブラリ利用側>
  • ライブラリプロジェクトが完成したら、今度は利用するプロジェクト側を用意します。これは例ですが、特別なことはしていません。

  • プロジェクトプロパティの[Android]にあるLibraryの[Add...]ボタンでAndroidライブラリを選択します。ライブラリ登録したプロジェクトが選択できるようになります。設定完了した[Android]タブ

  • 同じくプロジェクトプロパティの[Java Build Path]のProjectsタブにある[Add...]ボタンを押下します。すると、Workspaceにある自分を除く全てのプロジェクトが選択できますが、JNIライブラリを選択します。この例では2つしか無いのでJNIライブラリだけ選択候補になっているように見えますが、間違えないでください。

  • Librariesタブに移動し[Add Class Folder...]ボタンを押下します。ここで、ライブラリ側のJavaソースがあるsrcフォルダを選択します。

  • Order and Exportタブに移動し先程追加したソースのクラスフォルダにチェックを入れます。一応これで準備完了です。後は好きにライブラリを利用したプログラムを開発すれば良いです。

  • ちなみに、Project Explorerで確認するとライブラリが追加されていることが分かると思います。ついでですが、説明しませんでしたがプロジェクトプロパティの[Java Build Path]にあるSourceタブは完了すると勝手にソースが追加されます。これを削除するとライブラリをコンパイルしませんので注意してください。

設定のみなので簡単でしたよね。後はJava実装を行なって実行すれば良いです。
ただ、初心者はJNIライブラリは利用側を実装するまでビルドしないことをお勧めします。というのも、ビルド結果がライブラリフォルダ内で行われてしまい、ライブラリ利用側のbinにコピーされない現象が発生しました。私が行なった対処方法は、binフォルダやgen、Libs、obj配下のディレクトリやファイルを削除することで対応できたように記憶しています。

上記のJNIライブラリ利用は、ライブラリ開発者とアプリ開発者が同一の場合に利用できます。しかしながら、他人にバイナリを配布する際はどうするのか?
これを<その2>に記載したいと思います。

2011年5月17日火曜日

Android JNI で C++ を利用する<その2>

単純なC++の場合には問題無いのだけれど、JNIフォルダの中に複数のフォルダを配置した階層構造とした場合に少々嵌ったので備忘録として記録。

この図のようなJNIディレクトリ構造を持つJNIを開発すると仮定する。
CソースとCPPソースが混在したとしても、Application.mkはJNI直下で無ければならない点に注意。次に、JNI直下では各ディレクトリ毎にAndroid.mkを配置するようにした場合(JNI直下でも良いがその場合には全て指定)には、次の一行をAndroid.mkに記述しておく。
include $(call all-subdir-makefiles)
これに気がつくまでに、時間がかなり掛かってしまったので注意してください。

2011年5月16日月曜日

Android JNIライブラリの作成

Androidの開発を行なっていく上で、避けて通れないのがライブラリ化でしょう。開発していく中で再利用ソースが増えてくるので、他プロジェクトでも利用したいって気持ちが増えます。
かといって共用部分をコピー&ペーストする方法もありますが効率的ではありませんし、なによりプロっぽくありません。ご存知だとは思いますが、アマチュアとプロフェッショナルの決定的な違いは仕事でお金を貰えるか否かになります。作業の効率化もそうですが、メンテナンスも常に意識し他者に継承出来ることが重要視されます。

#企業では同じ仕事を延々に同一担当者が行うことは出来ませんし。
#簡単なメンテナンスとかは、徐々に後輩に移行させる会社を想定しています。

御託はこの辺で終わりにして、本題のJNIライブラリ作成です。
実はAndroidのJavaライブラリ作成方法と全く同じで作成出来ましたってことで終わりにしても良いのですが、それだとやや酷いので、私の作業過程とちょっとしたトラブルの備忘録って趣旨で記載します。

この記事ではAndroid JNIをEclipse CDTの設定方法等は説明しません。それは、以前の記事であるAndroid JNI を Eclipse CDT でプロジェクト統合する<ubuntu 10.10編 その2>を見てください。


<Android JNIライブラリ作成>
  • Androidプロジェクトを生成します。この図のように、ライブラリ専用プロジェクトにActivityが不要な場合にはチェックを外しておきます。
    ☆上図では誤ってパッケージ名をタイプミスしたため、実行エラーとなってちょっと焦りました。こういった配慮も必要ですね。

  • 不要なファイルを削除します。リソースファイルは自動的に作成されますが、特に必要無いので削除しています。

  • Android Manifestの修正を行ないます。
    図の通り、Define an・・・の箇所のチェックを外すことでアプリケーションタグが削除されます。ライブラリは外部から直接起動されない(組み込まれる)ので、必要がある場合には組み込み先側での指定になります。

  • クラスファイルの作成を行ないます。ここでは、JNIの呼出元となるJavaクラスとしてsamplelibクラスを生成しています。内容は以下の通りとしました。
    package jp.co.anaheim_eng.teslib;

    public class samplelib {

    static {
    // ライブラリロード
    System.loadLibrary("testjni");
    }

    // JNIのネイティブメソッド
    public native String GetStringJNI();

    /**
    * デフォルトコンストラクタ
    */
    public samplelib() {
    }

    /**
    * 単なる文字列をJNI経由で返却するファンクション
    * @return
    */
    public String getString() {
    return GetStringJNI();
    }
    }
    この辺りで、前述リンク先の<AndroidプロジェクトのC/C++プロジェクト化>を終了しているものとします。

  • プロジェクトプロパティの[Android]にある[Is Library]をチェックを入れます。

  • JNI側のプログラムは以下の通りとしました。
    ヘッダーファイル[jp_co_anaheim_eng_testlib_samplelib.h]
    #include <jni.h>
    #ifndef _Included_jp_co_anaheim_eng_testlib_samplelib
    #define _Included_jp_co_anaheim_eng_testlib_samplelib

    /*
    * DEBUG パラメタ 0: Releaseビルド, 1: Debugビルド
    */
    #define DEBUG 1

    #ifdef __cplusplus
    extern "C" {
    #endif

    /*
    * Class: Java_jp_co_anaheim_1eng_testlib_samplelib
    * Method: GetString
    * Signature: ([II)Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_jp_co_anaheim_1eng_teslib_samplelib_GetStringJNI
    (JNIEnv *, jobject);

    #ifdef __cplusplus
    }
    #endif

    #endif
    cppファイル[TestJni.cpp]
    #include <string.h>
    #include "jp_co_anaheim_eng_testlib_samplelib.h"
    #include <android/log.h>

    #include <iostream>
    #include <string>

    #if DEBUG
    # define DebugLogInfo(...) ((void)__android_log_print(ANDROID_LOG_INFO, "TestJni LIB", __VA_ARGS__))
    #else
    # define DebugLogInfo(...) do{}while(0)
    #endif

    JNIEXPORT jstring JNICALL Java_jp_co_anaheim_1eng_teslib_samplelib_GetStringJNI
    (JNIEnv *env, jobject thiz)
    {
    std::string strRtn = "Hello JNI Library";
    return env->NewStringUTF(strRtn.c_str());
    }
    [Android.mk]
    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE := testjni
    LOCAL_SRC_FILES := TestJni.cpp
    LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
    LOCAL_LDLIBS := -llog

    include $(BUILD_SHARED_LIBRARY)
    [Application.mk]
    APP_STL := stlport_static
    これでJNIライブラリの準備は完了となります。次からはJNIライブラリをどのように利用するかについて記述します。

2011年5月4日水曜日

Android JNI でのファイルの取扱と脆弱性

日本Androidの会 セキュリティ部で私が投稿した内容を纏めます。(^^;

事の発端はSkypeの脆弱性が指摘された件は、SQLiteのパーミッションに問題があったことから始まっています。
#パーミッションが開放されたとしても暗号化されていればまだ良かったのですがね。

「kaito」さんも指摘されていますように、一般的なAndroid APIを利用してファイルを作成する場合には、other権限で読み書きできるパーミッションが付与されることは無いからです。

「kaito」さんの解析成果によりSkype同梱されている「libpcmhost.so」が問題であるとの推測されましたので、JNI側でファイルを作成するとどうなるかを調査しました。

JNI側で一般的な入出力関数である「fopen」「open」の2つを調査しました。ファイルの中身は問題ではありませんので、単なるファイルをopenしcloseすることでファイルを生成させるというものです。ソースサンプルは以下の通り。

#include <stdio.h>
#include <fcntl.h>
#include <jni.h>
#include <android/log.h>

#define DEBUG 1
#if DEBUG
# define DebugLogInfo(...) ((void)__android_log_print(ANDROID_LOG_INFO, "TestOpen", __VA_ARGS__))
#else
# define DebugLogInfo(...) do{}while(0)
#endif

JNIEXPORT jint JNICALL Java_jp_co_anaheim_1eng_MainActivity_testOpen (JNIEnv *env, jobject thiz)
{
//fopen関数は最も一般的
FILE *fp;
fp = fopen( "/data/data/jp.co.anaheim_eng/testfile0.txt", "w+" );
if( fp == NULL ) {
DebugLogInfo("File cannot open error.");
return 1;
}
fclose(fp);

//open関数でパーミッションを指定しない状態で実行
int fd;
fd = open("/data/data/jp.co.anaheim_eng/testfile1.txt", O_CREAT | O_RDWR);
if (fd == -1) {
DebugLogInfo("testfile1.txt File cannot open error.");
return 1;
}
close(fd);

//ここは敢えてパーミッションを666に設定している
fd = open("/data/data/jp.co.anaheim_eng/testfile2.txt", O_CREAT | O_RDWR, 0666);
if (fd == -1) {
DebugLogInfo("testfile2.txt File cannot open error.");
return 1;
}
close(fd);

return 0;
}

このソースをJavaから呼び出して見た結果は以下の通り。

エミュレータはこの結果になるが、ひょっとすれば実機なら変わるかも?ってことでAndroid 2.3.3搭載している開発標準端末であるNexus Sで実行してみた。結果は以下の通り。
#ちなみに私のNexus Sはroot化していませんので。


Skypeの脆弱性はパーミッションを意識しない状態というか、AndroidのNativeで作成されるパーミッションを調査しなかっただけなのだろう。解決方法は?というと、open関数であれば適切なパーミッションを付与させれば良い。その他の場合には自分でパーミッションを設定させる命令を追加すれば良いだけ。

#include <sys/stat.h>

//パーミッションとして660に設定した
if (chmod("/data/data/jp.co.anaheim_eng/testfile0.txt", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0) {
DebugLogInfo("File cannot change chmod.");
return 1;
}

この処理を追加して実行した結果が以下の通りとなる。


<雑感>
Android NativeであるNDKを利用してファイル操作を行った場合には特にパーミッションを注意しなければならいと思う。因みにUbuntu上でfopenを利用した場合のパーミッションは644になっているので、666のAndroidはややどうかなぁって気はする。
デフォルトパーミッションはJavaと同様に660にして欲しい気もするのだが、最低でも664 or 644になっていることを望みます。

root化された端末では、パーミッションによるセキュリティ担保が出来るということは期待しない方が良いから暗号化等の配慮が必要にはなる。であるとするとJNIで666となるパーミッションの標準もこのままでも良いのかも?って気がしないでもないなぁ。(^^;