特権ブロックのためのAPI

このドキュメントでは、特権コードの意味とその使用目的について説明した後、APIの使用方法を示します。次のトピックについて説明します。

特権コードを使う意味

JDKのインストール・ポリシーにより、特定のコード・ソースに基づくコードに対して許可されるアクセス権、つまりシステム・リソースに対するアクセスの種類が決まります。CodeSource型のコード・ソースは、基本的に、コードの場所(URL)と、コードへの署名に使われている非公開鍵に対応する公開鍵を含む証明書の参照(コードが署名されている場合)で、構成されています。

ポリシーは、Policyオブジェクトによって表されます。具体的には、Policyクラス(java.securityパッケージ内)で定義されているabstractメソッドを実装したPolicyサブクラスによって表されます。

Policyオブジェクトが使用するポリシー情報がどこに置かれるかは、Policyの実装によります。Policyのリファレンス実装では、ポリシー情報をポリシー構成ファイルから得ます。デフォルトのPolicyのリファレンス実装とそこで読み取られるポリシー・ファイルで使用する構文については、「Policyのデフォルト実装とポリシー・ファイルの構文」を参照してください。Policy Toolを使った(必須の構文を知る必要のない)ポリシー・ファイルの作成方法の詳細は、Policy Toolのドキュメント(Unix用Windows用)を参照してください。

そのとき有効になっているセキュリティ・ポリシーの決定に従って、CodeSourceインスタンスと、そのCodeSourceに基づくコードに対して付与されているアクセス権は、保護ドメインに包含されます。したがって、同じ鍵で署名された同じURLにあるクラスは一般に同じドメインに置かれ、クラスは1つの保護ドメインのみに所属します。(ただし、同じ鍵で署名され、同じURLにあっても、別のクラス・ローダー・インスタンスでロードされたクラスは、一般に別のドメインに置かれます。)同じアクセス権を与えられていても、別のコード・ソースが基になっているクラスは、別のドメインに属します。

現在、JDKとともに提供されるすべてのクラスには、ロード時にすべてのアクセス権が与えられます(これは将来のリリースで変更される可能性があります)。これらのクラスのほとんどは、固有のシステム・ドメインに置かれます。さらに、拡張クラス・ローダーは$JAVA_HOME/jre/lib/extディレクトリに含まれているJARファイルのコードを(これらのJARファイルのコードに固有のURLがあるため)別々のドメインにロードしますが、これらのドメインはJDKとともに提供されるクラスのために予約された固有のシステム・ドメインとは独立しています。

アプレットまたはアプリケーションは、それぞれのコード・ソースによって決められるドメイン内で動作します。アプレット(またはセキュリティ・マネージャの下で動作しているアプリケーション)がセキュリティ保護された操作(ファイルの読み書きなど)を行うためには、その操作を行うためのアクセス権がアプレットまたはアプリケーションに与えられている必要があります。

より具体的には、リソースにアクセスしようとする場合は常に、その地点に至るまでに実行スレッドがたどってきたすべてのコードが、そのリソース・アクセスのためのアクセス権を持っている必要があります。ただし、スレッドの中に特権付きのマークが付けられたコードがある場合は除きます。つまり、複数の呼出し側のチェーンが存在する実行のスレッドで、アクセス制御のチェックが行われる場合を考えます。(保護ドメインの境界を横断する可能性のある複数のメソッドの呼出しのような場合です。)最後の呼出し側がAccessControllercheckPermissionメソッドを呼び出すとき、要求されたアクセスの許可または拒否を決定する基本的なアルゴリズムは、次のようになります。呼出しチェーンのいずれかの呼出し側のコードに、要求されたアクセス権がない場合、AccessControlExceptionがスローされます。ただし、このアクセス権を与えられているコードの呼出し側に特権付きのマークが付けられていて、この呼出し側からその後(直接または間接的に)呼び出されるすべての部分がこのアクセス権を持っている場合、この例外はスローされません。

AccessController.checkPermissionメソッドは、通常、checkという語で始まる特定のSecurityManagerメソッド(checkConnectなど)の呼出しによって、またはSecurityManager.checkPermissionメソッドによって間接的に呼び出されます。通常、これらのチェックはSecurityManagerがインストールされている場合にのみ行われます。AccessController.checkPermissionメソッドによってチェックされるコードは、最初にSystem.getSecurityManagerメソッドがnullを返すかどうかをチェックします。

コードを特権付きとしてマークすると、信頼できるコードは、それを呼び出しているコードが直接利用できるものより多くのリソースに、一時的にアクセスできるようになります。これは、次のような場合に必要になります。たとえば、あるアプリケーションがフォントが入ったファイルへの直接アクセスを許可されていないときに、ドキュメントを表示するシステム・ユーティリティがユーザーに代わってそのフォントを取得する必要がある場合などです。このシステム・ユーティリティは、フォントを取得するために特権付きになる必要があります。

doPrivileged APIの使用

以降のセクションでは、特権機能の使用方法について説明します。

戻り値がなく、例外がスローされない場合

privilegedブロック内から値を返す必要がない場合、doPrivilegedの呼出しは次の例のようになります。この例では、PrivilegedActionインタフェースを実装するクラス、匿名クラスおよびラムダ式の3つの方法で特権コードを指定しています。

import java.security.*;

public class NoReturnNoException {
    
    class MyAction implements PrivilegedAction<Void> {
        public Void run() {
            // Privileged code goes here, for example:
            System.loadLibrary("awt");
            return null; // nothing to return
        }
    }
    
    public void somemethod() {
           
        MyAction mya = new MyAction();
        
        // Become privileged:
        AccessController.doPrivileged(mya);
       
        // Anonymous class
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                // Privileged code goes here, for example:
                System.loadLibrary("awt");
                return null; // nothing to return
            }
        });
        
        // Lambda expression
        AccessController.doPrivileged((PrivilegedAction<Void>)
            () -> {
                // Privileged code goes here, for example:
                System.loadLibrary("awt");
                return null; // nothing to return
            }
        );
    }
    
    public static void main(String... args) {
        NoReturnNoException myApplication = new NoReturnNoException();
        myApplication.somemethod();
    }
}

AccessController.doPrivilegedメソッドは、java.security.PrivilegedAction型のオブジェクトを受け取り、そのrunメソッドを特権モードで呼び出します。非同期の例外によりdoPrivilegedの実行が中断される場合でも、runメソッドを実行したあとの特権の解除は、実装が保証します。ラムダ式を使ってdoPrivilegedを呼び出す場合は、ラムダ式をPrivilegedAction<Void>型として明示的にキャストします。doPrivilegedメソッドには、PrivilegedExceptionAction型のオブジェクトを取るバージョンも存在します。このバージョンの詳細は、「例外の処理」を参照してください。

PrivilegedActionは、型パラメータで指定された型の値を返すrunという名前の単一抽象メソッドを持つ関数型インタフェースです。

この例では、runメソッドの戻り値は無視されています。特権コードを構成する実際の要素によっては、内部クラスの処理のために若干の変更が必要になる場合があります。たとえば、特権コードが例外をスローしたりローカル変数にアクセスしたりする場合は、後で説明する変更が必要になります。

特権の構造は十分に注意して使い、特権コードのセクションをできるかぎり小さくするよう常に心がける必要があります。つまり、runメソッドの中のコードは、特権付きで実行する必要のあるコードのみに制限し、より一般的な処理はrunメソッドの外側で行うようにします。また、doPrivilegedの呼出しは、特権を有効にする必要があるコードの中で行うようにします。それ自体がdoPrivilegedを呼び出すようなユーティリティ・クラスは、セキュリティ・ホールとなる危険があるため、作成しないでください。前の例で示したように、直接呼び出さなくても、PrivilegedActionクラスのためのユーティリティ・クラスを記述できます。詳細は、「Javaプログラミング言語のセキュア・コーディング・ガイドライン」の「ガイドライン9-3: java.security.AccessController.doPrivilegedの安全な呼出し」を参照してください。

値を返す場合

次に、値を返す必要がある場合の例を示します。

System.out.println(
    AccessController.doPrivileged((PrivilegedAction<String>)
        () -> System.getProperty("user.name")
    )
);

ローカル変数にアクセスする場合

ラムダ式または匿名の内部クラスを使う場合は、アクセスするローカル変数がすべてfinalまたは事実上のfinalである必要があります。たとえば、

String lib = "awt";
AccessController.doPrivileged((PrivilegedAction<Void>)
    () -> {
        System.loadLibrary(lib);
        return null; // nothing to return
    }
); 
   
AccessController.doPrivileged(new PrivilegedAction<Void>() {
    public Object run() {
        System.loadLibrary(lib);
        return null; // nothing to return
    }        
});

変数libは、その値が変更されていないため、事実上のfinalです。たとえば、変数libの宣言の後に次の代入文を追加するとします。

lib = "swing";

コンパイラは、ラムダ式と匿名クラスの両方にあるSystem.loadLibraryの呼出しを検出したときに、次のエラーを生成します。

詳細は、「ローカル・クラス」の「包含クラスのメンバーへのアクセス」を参照してください。

繰返し設定されるなどの理由で、既存の変数を事実上のfinalとして宣言できない場合は、doPrivilegedメソッドを呼び出す直前に新しいfinal変数を作成し、その変数を他方の変数と等しいものとして設定します。たとえば、

String lib;
    
// The lib variable gets set multiple times so you can't make it
// effectively final.
  
// Create a final String that you can use inside of the run method
final String fLib = lib;
    
AccessController.doPrivileged((PrivilegedAction<Void>)
    () -> {
        System.loadLibrary(fLib);
        return null; // nothing to return
    }
);

例外の処理

runメソッド内で実行された処理によってチェック例外(メソッドのthrows節にリストする必要がある例外)がスローされた場合は、PrivilegedActionインタフェースではなく、PrivilegedExceptionActionインタフェースを使う必要があります。

public void processSomefile() throws IOException {

    try {
        Path path = FileSystems.getDefault().getPath("somefile");
        BufferedReader br = AccessController.doPrivileged(
            (PrivilegedExceptionAction<BufferedReader>)
                () -> Files.newBufferedReader(path)
        );
        // ... read from file and do something
    } catch (PrivilegedActionException e) {
    
        // e.getException() should be an instance of IOException
        // as only checked exceptions will be wrapped in a
        // PrivilegedActionException.
        throw (IOException) e.getException();
    }
}

チェック例外がrunメソッドの実行中にスローされる場合は、この例で示したようにPrivilegedActionExceptionラッパー例外に置いてからスローし、記述したコードを使ってキャッチします。

特権のサブセットのアサーション

JDK 8では、他のアクセス権をチェックするためのスタックの完全なトラバースを禁止せずにコード内でコードの特権のサブセットを表明できるAccessController.doPrivilegedのバリアントを使用できます。このdoPrivilegedのバリアントには3つのパラメータがあり、そのうちの1つはこの特権のサブセットを指定するために使用します。たとえば、次のコード例はシステム・プロパティを取得するための特権を表明しています。

// Returns the value of the specified property. All code
// is allowed to read the app.version and app.vendor
// properties.

public String getProperty(final String prop) {
    return AccessController.doPrivileged(
        (PrivilegedAction<String>) () -> System.getProperty(prop),
        null,
        new java.util.PropertyPermission("app.version", "read"),
        new java.util.PropertyPermission("app.vendor", "read")
    );
}

このバージョンのdoPrivilegedの1つ目のパラメータは、java.security.PrivilegedAction型です。この例では、1つ目のパラメータは関数型インタフェースPrivilegedActionを実装するラムダ式です。このインタフェースのrunメソッドは、パラメータpropで指定されたシステム・プロパティの値を返します。

このバージョンのdoPrivilegedの2つ目のパラメータは、AccessControlContext型です。場合によっては、異なるコンテキスト(ワーカー・スレッドなど)で追加のセキュリティ・チェックを実行する必要があります。AccessControlContext.getContextメソッドを使用して、特定の呼出しコンテキストからAccessControlContextインスタンスを取得できます。このパラメータに(この例のように)nullを指定すると、doPrivilegedの呼出し時に追加のセキュリティ・チェックが実行されません。

このバージョンのdoPrivilegedの3つ目のパラメータは、可変引数パラメータであるPermission...型です。つまり、1つ以上のPermissionパラメータを指定するか、またはPermission[]のようにしてPermissionオブジェクトの配列を指定できます。この例では、doPrivilegedを呼び出すことでapp.versionプロパティとapp.vendorプロパティを取得できます。

このdoPrivilegedの3つのパラメータ・バリアントは、最小特権のモードまたは追加特権のモードで使用できます。

最小特権

doPrivilegedメソッドの典型的な使用例は、現在のメソッドの呼出し側に必要なすべてのアクセス権がなくても、これを呼び出すことで、そのメソッドでアクセス権のチェックを必要とする1つ以上のアクションを実行できるようにする場合です。たとえば、現在のメソッドが内部実装に固有の目的でファイルを開いたり、ネットワーク要求を実行したりする必要がある場合があります。

JDK 8より前のバージョンでは、doPrivilegedメソッドの呼出しには2つのパラメータしかありませんでした。これらは、呼出し側のメソッドに一時的な特権を付与し、アクセスをチェックするための通常の完全なスタックのトラバーサルを、必要なアクセス権がないクラスで定義されたメソッドに到達する可能性がある呼出しスタックまで継続せずに、そのクラスに到達したときに停止することで機能しました。通常、doPrivilegedを呼び出しているクラスは、そのコード・パスでは不要な追加のアクセス権を持っている可能性があり、それらは一部の呼出し側クラスにもない可能性があります。

通常、これらの追加権限は実行時に使用されません。doPrivilegedを使ってこれらを昇格させないことで、意図しないアクションを実行する可能性がある不正なコードの利用を防ぐことができます。これは特に、PrivilegedActionが通常より複雑な場合や、時間の経過とともに独立して発展する可能性があるクラスやパッケージの境界の外側にあるコードを呼び出す場合に当てはまります。

3つのパラメータを持つdoPrivilegedのバリアントは、必要とされる予定がないアクセス権を不必要に昇格しないため、一般により安全に使用できます。ただし、実行時の効率が低いため、単純なコード・パスやパフォーマンス重視のコード・パスでは使用されない場合があります。

追加特権

現在のメソッドをコーディングするときに、アクションを実行するために呼出し側のメソッドのアクセス権を一時的に拡張する必要がある場合があります。たとえば、フレームワークのI/O APIに、特定のデータ形式のファイルを開くための汎用メソッドが含まれる場合があります。このAPIは、通常のファイル・パス・パラメータを取り、それを使って基盤となるFileInputStreamを開くときに、呼出し側のコードのアクセス権を使用します。ただし、これによってすべての呼出し側が標準のデモ用サンプルを含む特別なディレクトリ内のデータ・ファイルを開くこともできるようになる可能性があります。

このAPIの呼出し側には、読取りアクセス用のFilePermissionを直接付与できます。ただし、呼出し側のコードのセキュリティ・ポリシーの更新が不便または不可能になる可能性があります。たとえば、呼出し側のコードがサンドボックス内で実行されるアプレットである可能性があります。

これを実装する1つの方法は、コード内で受け取ったパスをチェックし、それが特別なディレクトリ内のファイルを参照しているかどうかを判定することです。参照している場合は、doPrivilegedを呼び出してすべてのアクセス権を有効にしてから、PrivilegedActionの内部でファイルを開きます。ファイルが特別なディレクトリに含まれていない場合は、コード内でdoPrivilegedを使用せずにファイルを開きます。

この手法では、実装は要求されたファイル・パスを慎重に扱い、それが特別な共有ディレクトリを参照しているかどうかを判定する必要があります。doPrivilegedを呼び出す前にファイル・パスを正規化して、パスが特別なディレクトリ内のファイルを参照しているかどうかを判定する前に、すべての相対パスが処理されるように(およびuser.dirシステム・プロパティを読み取るためのアクセス権がチェックされるように)する必要があります。また、特別なディレクトリの外側に抜ける意図を持つ悪質な"../"パス要素を防止する必要もあります。

より簡単かつ適切な実装では、3つ目のパラメータを持つdoPrivilegedのバリアントを使用します。特別なディレクトリへの読取りアクセスを含むFilePermissionを3つ目のパラメータとして渡します。その後、PrivilegedActionの内部でファイルの操作を行います。この実装はより簡単で、セキュリティ上の欠陥を含む可能性がかなり低くなります。

リフレクション

このAPIとリフレクションの相互作用は、注意の必要な微妙な点です。doPrivilegedメソッドは、java.lang.reflect.Method.invokeメソッドを使ってリフレクションとして呼び出すことができます。この場合、特権モードで与えられる特権は、Method.invokeの特権ではなく、それによって呼び出されるリフレクションでないコードの特権です。このようにしないと、システム特権が誤って(または悪意を持って)ユーザー・コードに与えられてしまう危険があります。既存のAPIでリフレクションを使う場合にも、同様の要件が適用されます。


Copyright © 1993, 2020, Oracle and/or its affiliates. All rights reserved.