クマは森で用を足しますか?

アウトプットは重要です。

突然のSW 6F00はメモリリークによるものなのか?

Android Secure Element CTS仕様に沿ってJava Cardアプレットを作成中ですが、ある日突然アプレットを選択することができなくなりました。最初に作ったアプレットAID 'A000000476416E64726F696443545331'に対するSELECTコマンドに、SW 6F00が返ってきてしまいます。

SW 6F00 : Command aborted – more exact diagnosis not possible

アプレット内でメモリリークが繰り返された場合に、SW 6F00が返ってくることがあったことを思い出しました。今回のこの現象も、同じ原因によるものかもしれません。Oracleの技術文書によると、カードによってはGCが機能しないことも想定するべきで、必要なメモリはアプレットの初期化時に確保しておくようにとのことです。

On a Java Card device, memory is the most valuable resource. In some Java Card implementations a garbage collector may not be available.
...
In a Java Card environment, arrays and primitive types should be declared at object declaration, and you should minimize object instantiation in favor of object reuse. Instantiate objects only once during the applet lifetime, preferably at applet initialization, in the install() method, which is invoked only once during the applet lifetime.

あやしいところはふたつ

ひとつ目は、SELECTコマンドでFCIテンプレートを返すところ。AIDクラスのインスタンスを毎回生成し、それでいていつまでもGCに回収されないという事態になっている可能性があります。サイズが大きなオブジェクトではないと思いますが、比較する一方のAIDは固定値なので、単一インスタンスが繰り返し再利用されるように書くべきでした。

...
        switch (buffer[ISO7816.OFFSET_P2] & 0x0C) {
            // Return FCI template, optional use of FCI tag and length
            case 0x00:
                AID selected = new AID(buffer, ISO7816.OFFSET_CDATA, buffer[ISO7816.OFFSET_LC]);
                if (selected.equals(AID_LONG_RESPONSE, (short) 0,
                        (byte) AID_LONG_RESPONSE.length)) {

もうひとつは、Case 2コマンドおよびCase 4コマンドに対して256バイトのデータを返すところ。この256バイトのデータは、何度も利用できるものとしてアプレットの初期化時に作るようにします。

...
        output = new byte[256];
        Util.arrayFillNonAtomic(output, (short) 0, (short) output.length, (byte) 0x00);
        apdu.setOutgoing();
        apdu.setOutgoingLength((short) output.length);
        apdu.sendBytesLong(output, (short) 0, (short) output.length);

修正したものがこちら

こちらのコミットで、上記二点を修正しました。

github.com


これまでに書いたテストを100周回しても問題なかったので、ひとまずこれで良しとします。少しずつ作りながら書くと、途中のバグや迷走具合を全部さらしてしまうのが良くないですね(今頃気付きました)。