共有される可変変数へのアクセスを同期する方法には、メソッド同期とブロック同期の二通りがある。synchronized宣言したメソッドと、synchronizedブロックでthis参照に対する同期を行うコードのどちらも、オブジェクトの モニタ (すなわち固有ロック)を使用する。攻撃者は、アクセス可能なクラスの固有ロックを取得し、これを無期限に保持することで、競合状態やデッドロックを発生させ、サービス運用妨害(DoS)を行うことが可能である。
この脆弱性を防ぐ方法の一つに、Blochの「private宣言したロックオブジェクト」手法がある[Bloch 2001]。この手法は、オブジェクト自身の固有ロックの代わりに、クラス内でprivate final宣言されたjava.lang.Objectのインスタンスの固有ロックを使用する。またこの手法では、synchronizedメソッドではなく、クラスのメソッド内でsynchronizedブロックを使用する必要がある。悪意あるクラスはprivate final宣言したロックオブジェクトにアクセスできないため、クラスメソッドと他の悪意あるクラスのメソッドの間にロック競合は生じない。
静的メソッドや静的状態にも、同様の問題が潜在的に存在する。静的メソッドをsynchronized宣言すると、静的メソッド内の文(statement)が実行される前に Classオブジェクト(java.lang.Class)の固有ロックが取得され、メソッドが完了した時点でロックがリリースされる。クラスやサブクラスのオブジェクトにアクセス可能な信頼できないコードも、getClass()メソッドを使用することでClassオブジェクトにアクセスでき、結果としてClassオブジェクトの固有ロックを操作できてしまう。private static final宣言したObject クラスでロックし、static宣言されたデータを保護すること。クラスのアクセスをパッケージプライベートに制限することで、信頼できない呼出しに対する保護をさらに強化することもできる。
この「private static final宣言したロックオブジェクト」を使用する手法は、継承用に設計されたクラスにも適している。スーパークラスのスレッドが自身のモニタ(固有ロック)を要求するとき、サブクラスのスレッドはその要求を妨害することができてしまう。たとえば、サブクラスは、スーパークラスのオブジェクトの固有ロックを何か無関係な操作に使用し、深刻なロック競合やデッドロックを引き起こすかもしれない。スーパークラスのロック方式とサブクラスのロック方式を分離すれば、双方がロックオブジェクトを共有することはなくなる。また、複数のロックオブジェクトを利用することで、相互干渉を最小限にした細かい粒度のロックが可能になり、アプリケーション全般の性能改善につながる。
信頼できないコードが次に挙げる操作を行える場合、同期する必要のあるオブジェクトは、そのクラスのオブジェクト自身の固有ロックではなく、private final宣言されたロックオブジェクトを使用しなくてはならない。
- 当該クラスをサブクラス化する
- 当該クラスあるいはそのサブクラスのオブジェクトを生成する
- 当該クラスあるいはそのサブクラスのオブジェクトにアクセスするか取得する
スーパークラスがprivate宣言されたロックオブジェクトの手法を使用している場合、そのサブクラスも同じ手法を使用しなくてはならない。しかし、スーパークラスがロックポリシーを明文化せずにスーパークラス自身の固有ロックを同期に利用している場合、サブクラスは自身の固有ロックを使った同様な手法を実装してはならない。スーパークラスのロックポリシーにクライアントサイドロックをサポートすると書かれている場合、サブクラスの実装においては、オブジェクトの固有ロックかprivate宣言されたロックのいずれかを選択できる。どちらのロック手法を採用するにせよ、サブクラス自身のロックポリシーは文書化されなければならない。関連情報として「TSM00-J. スレッドセーフなメソッドを、スレッドセーフでないメソッドでオーバーライドしない」を参照。
前述の制限に一つでも違反した場合、オブジェクトの固有ロックは信頼できないものとなる。一方、これらの制限を満たしている場合、private final宣言されたロックオブジェクトを使用してもセキュリティ上のメリットはあまり得られない。それゆえ、前述の制限すべてに従うオブジェクトは、オブジェクトの固有ロックを使用して同期を行うことができる。しかし、メソッドがアトミックでない操作から構成され、それらの操作が同期を必要としない、あるいは複数のロックオブジェクトを使用してさらにきめ細かい排他制御が実現できる場合には、private final 宣言したロックオブジェクトによるブロック同期を行う方がメソッド同期を行うよりも望ましい。アトミックでない操作は、同期が要求される操作から分離し、synchronizedブロックの外部で実行することが可能である。この理由に加え、保守性の観点からも、固有ロックを使用した同期よりも、private final宣言されたロックオブジェクトによるブロック同期の方が、一般には推奨される。
違反コード (synchronizedメソッド)
以下の違反コード例では、SomeObjectクラスのインスタンスが信頼できないコードからアクセスできるようになっている。
public class SomeObject { // オブジェクトのモニタをロックする public synchronized void changeValue() { // ... } } // 信用できないコード SomeObject someObject = new SomeObject(); synchronized (someObject) { while (true) { // 無期限にsomeObjectを遅延させる Thread.sleep(Integer.MAX_VALUE); } }
信頼できないコードは、synchronized宣言されたchangeValue()メソッドの呼出しに必要となるオブジェクトの固有ロックを取得した後、そのロックをリリースせずに処理を無期限に遅延し、他からのロックの取得(changeValue()の呼出し)を妨害している。信頼できないコードは故意に「LCK09-J. 途中で待機状態になる可能性のある操作をロックを保持したまま実行しない」に違反している。
違反コード (final宣言されていないpublicロックオブジェクト)
以下の違反コード例では、SomeObjectの固有ロックではなく、final 宣言されていない public オブジェクトをロックしている。
public class SomeObject { public Object lock = new Object(); public void changeValue() { synchronized (lock) { // ... } } }
この変更では、悪意あるコードからの攻撃を防ぐことはできない。たとえば信頼できない悪意あるコードは、ロックオブジェクトの値を変更することで、同期を妨害することができる。
違反コード (final宣言されていないパブリックアクセス可能なロックオブジェクト)
以下の違反コード例では、private宣言のみでfinal宣言されていないロックオブジェクトを使って同期を行っている。lock フィールドは volatile 宣言されており、その値の変更は他のスレッドにも可視である。
public class SomeObject { private volatile Object lock = new Object(); public void changeValue() { synchronized (lock) { // ... } } public void setLock(Object lockValue) { lock = lockValue; } }
どんなスレッドでも、setLock()メソッドのようなアクセッサを介して、別のオブジェクトを参照するようにフィールドの値を変更することができる。ロックオブジェクトが変更されると、本来は2つのスレッドが同一オブジェクトをロックすべきところ、それぞれ異なるオブジェクトをロックしてしまい、結果的に2つのクリティカルセクションが安全に実行されなくなる可能性がある。たとえば、一方のスレッドがクリティカルセクションを処理している最中にロックオブジェクトが変更されると、他方のスレッドは、変更前のオブジェクトではなく変更後のオブジェクトをロックして、間違ってクリティカルセクションの処理を行ってしまうかもしれない。
ロックを変更するメソッドを実装していないクラスは、信頼できない操作を行われる危険がない。しかし、プログラマがうっかりロックを変更するメソッドを追加してしまう危険は残っている。
違反コード (public finalロックオブジェクト)
以下の違反コード例では、public final宣言したロックオブジェクトを使用している。
public class SomeObject { public final Object lock = new Object(); public void changeValue() { synchronized (lock) { // ... } } } // 信頼できないコード SomeObject someObject = new SomeObject(); someObject.lock.wait();
SomeObjectクラスのインスタンスの生成、あるいは、既に生成済のインスタンスにアクセスできる信頼できないコードは、パブリックにアクセス可能なlockのwait()メソッドを呼び出し、changeValue()メソッドのロックを直ちにリリースさせることができる。また、changeValue()メソッド中で、実行再開条件のチェックを伴わないlock.wait()メソッドの呼び出しが行われている場合、意図しないタイミングでwait()メソッドを終了させるような悪意のある通知に対して脆弱となる。詳細は「THI03-J. wait() および await() メソッドは常にループ内部で呼び出す」を参照。
このコードは「OBJ01-J. データメンバはprivate宣言し、それにアクセスするためのラッパーメソッドを提供する」にも違反している。
適合コード (private final ロックオブジェクト)
信頼できないコードとのやり取りを行うスレッドセーフなpublicクラスは、private final宣言されたロックオブジェクトを使用しなければならない。固有ロックを使用するクラスは、ロックオブジェクトを用いたブロック同期を行うように修正する必要がある。以下の適合コードでは、changeValue()メソッドの呼出しで、クラスのスコープ外からアクセスできない、private final宣言されたObjectクラスのインスタンスに対するロックを取得している。
public class SomeObject { private final Object lock = new Object(); // private final宣言されたロックオブジェクト public void changeValue() { synchronized (lock) { // private な Object である lock をロックする // ... } } }
private final宣言されたロックオブジェクトは、ブロック同期でのみ使用することができる。ブロック同期はメソッド同期よりも推奨される。なぜならば、同期を必要としない操作を同期されたブロックの外へ移動しやすく、ロック競合や処理がブロックされる機会を減らすことができるからである。final宣言されたフィールドの可視性は確保されるので、lockをvolatile宣言する必要はない点に注意。粒度の細かいロックを行うために複数のロックを使用しなくてはならない場合、ロックオブジェクトへの可変参照をセッターメソッドとともに使用するのではなく、private final宣言した複数のロックオブジェクトを使用すること。
違反コード (static private ロック)
以下の違反コードでは、SomeObjectのクラスリテラルは、信頼できないコードからアクセス可能である。
public class SomeObject { // changeValueは、クラスオブジェクトのモニタをロックする public static synchronized void changeValue() { // ... } } // 信頼できないコード synchronized (SomeObject.class) { while (true) { Thread.sleep(Integer.MAX_VALUE); // 無期限にsomeObjectを遅延させる } }
信頼できないコードは、クラスオブジェクトのモニタに対するロックを取得しようとし、これに成功すると、無期限に処理を遅延させ、synchronized宣言されたchangeValue()メソッドがロックを取得できないようにする。
適合コードは「LCK05-J. 信頼できないコードによって変更されうる static フィールドへのアクセスは同期する」にも準拠しなければならない。 上記の信頼できないコードでは、攻撃者は意図的に「LCK09-J. 途中で待機状態になる可能性のある操作をロックを保持したまま実行しない」に違反している。
適合コード (static private ロック)
オブジェクトの固有ロックを用い、かつ、信頼できないコードとやり取りを行うスレッドセーフなpublicクラスは、private static final 宣言されたロックオブジェクトを使用してブロック同期を行うよう修正しなければならない。
public class SomeObject { private static final Object lock = new Object(); public static void changeValue() { synchronized (lock) { // private宣言されたObjectをロック // ... } } }
上記の適合コードにおいて、changeValue()メソッドは、信頼できないコードからはアクセスできないstatic private宣言されたObjectクラスのロックを取得している。
例外
LCK00-EX0: 下記の条件をすべて満たすクラスはこのルールに適合しなくてもよい。
- 呼出し元(クライアント)に対し、信頼できないコードにこのクラスのオブジェクトを渡すことを禁止する旨が十分に文書化されている。
- このルールに違反した信頼できないクラスのオブジェクトが持つメソッドを、直接的にも間接的にもクラスが呼び出せない。
- クラスの同期ポリシーが適切に文書化されている。
下記の条件をすべて満たすクライアントはこのルールに適合しないクラスを使用してもよい。
- クライアントのクラスも、同一システムのいかなる他のクラスも、このルールに適合しないクラスのオブジェクトを信頼できないコードに渡さない。
- このルールに違反したクラスが、直接的にも間接的にも、このルールに適合しない信頼できないクラスのメソッドを呼び出せない。
LCK00-EX1: スーパークラスにおいて、クライアントサイドロックをサポートし、さらに、スーパークラス自身のクラスオブジェクトを使用して同期を行う旨が文書化されているならば、サブクラスは同様にクライアントロックをサポートし、そのようなロックポリシーを文書化してもよい。
LCK00-EX2: パッケージプライベートなクラスは、アクセス制限により信頼できない呼出し元から保護されるため、このルールに適合しなくてもよい。しかし、同一パッケージ内の信頼できるコードがロックオブジェクトを不注意に再利用あるいは変更することがないよう、この例外を適用することを明示的に文書化するべきである。
リスク評価
ロックオブジェクトを信頼できないコードからアクセスできるようにすると、サービス運用妨害につながる可能性がある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
LCK00-J | 低 | 中 | 中 | P4 | L3 |
関連ガイドライン
MITRE CWE | CWE-412. Unrestricted externally accessible lock |
CWE-413. Improper resource locking |
参考文献
[Bloch 2001] | Item 52. Document Thread Safety (スレッド安全性を文書化する) |
翻訳元
これは以下のページを翻訳したものです。
LCK00-J. Use private final lock objects to synchronize classes that may interact with untrusted code (revision 176)