クラスを可変にすると、クラス外部のコードがインスタンスフィールドやクラスフィールドを変更することができる。信頼できないコードには破棄されても構わないコピーを渡せるよう、可変クラスにはコピーを作成する手段を実装すること。このような機能は、他のクラスのメソッドが特定のクラスインスタンスのコピーを作成しなくてはならない場合に便利である。詳細は「OBJ05-J. privateかつ可変なクラスメンバへの参照を返す前にそのディフェンシブコピーを作成する」および 「OBJ06-J. 可変入力や可変な内部コンポーネントはディフェンシブコピーを作成する」を参照。
可変クラスは、コピーコンストラクタか、インスタンスのコピーを返すpublicな静的ファクトリメソッドのいずれかを実装しなくてはならない。あるいは、java.lang.Objectのcloneメソッドをオーバーライドし、コピー機能を実装してもよい。clone()メソッドを安全に使用できるのは、finalクラスに対してのみであり、finalでないクラスに対してこれを行ってはならない。
信頼できる呼出し元は、オブジェクトのインスタンスを信頼できないコードへ渡す前に、提供されたコピー機能を使ってディフェンシブコピーを作成することができる。信頼できない呼出し元はそのようにディフェンシブコピーを作成できない。したがって、コピー機能を用意したとしても、信頼できないコードから受け取った入力や、信頼できないコードに返される出力のディフェンシブコピーを作成する必要がなくなるわけではない。
違反コード
以下の違反コード例では、MutableClassはDate型の可変フィールドdateを使用している。Dateクラスもまた可変クラスである。MutableClassオブジェクトにはコピー機能がないため、本ルールに違反している。
public final class MutableClass {
private Date date;
public MutableClass(Date d) {
this.date = d;
}
public void setDate(Date d) {
this.date = d;
}
public Date getDate() {
return date;
}
}
信頼された呼出し元が信頼できないコードにMutableClassのインスタンスを渡し、信頼できないコードがそのインスタンスを変更した場合(月を先に進める、タイムゾーンを変更するなど)、オブジェクトは変更前と比べて矛盾した状態になるであろう。同様の問題は、信頼できないコードが存在しなくても、マルチスレッド環境で発生しうる。
適合コード (コピーコンストラクタ)
以下の適合コードでは、引数に同じ型(あるいはサブタイプ)が渡されるとMutableClassのインスタンスを初期化するコピーコンストラクタを使用している。
public final class MutableClass { // コピーコンストラクタ
private final Date date;
public MutableClass(MutableClass mc) {
this.date = new Date(mc.date.getTime());
}
public MutableClass(Date d) {
this.date = new Date(d.getTime()); // ディフェンシブコピーを作成
}
public Date getDate() {
return (Date) date.clone(); // コピーを作成して返す
}
}
このアプローチは、クラスのインスタンスフィールドが final 宣言されている場合に有効である。呼出し元は、既存のMutableClassのインスタンスを引数としてコピーコンストラクタを呼び出すことで、コピーをリクエストする。
適合コード (public staticファクトリーメソッド)
以下の適合コードは、与えられたMutableClassオブジェクトのインスタンスのコピーを作成して返す、public staticファクトリーメソッドgetInstance()を実装している。
class MutableClass {
private final Date date;
private MutableClass(Date d) { // インスタンスやサブクラスは作れない
this.date = new Date(d.getTime()); // ディフェンシブコピーの作成
}
public Date getDate() {
return (Date) date.clone(); // コピーして返す
}
public static MutableClass getInstance(MutableClass mc) {
return new MutableClass(mc.getDate());
}
}
このアプローチは、インスタンスフィールドが final 宣言されている場合に有効である。
適合コード (clone())
以下の適合コードは、MutableClassをfinal宣言し、Cloneableインタフェースを実装し、オブジェクトの深い(deep)コピーを行うメソッドObject.clone()を実装することで、必要とされるコピー機能を実現している。
public final class MutableClass implements Cloneable {
private Date date;
public MutableClass(Date d) {
this.date = new Date(d.getTime());
}
public Date getDate() {
return (Date) date.clone();
}
public void setDate(Date d) {
this.date = (Date) d.clone();
}
public Object clone() throws CloneNotSupportedException {
final MutableClass cloned = (MutableClass) super.clone();
cloned.date = (Date) date.clone(); // 可変Dateオブジェクトのコピーを作成
return cloned;
}
}
clone()メソッドはDateオブジェクトのコピーを作成しなくてはならない点に注意。オブジェクトがプリミティブフィールドや不変オブジェクトを参照するフィールドのみを持つ場合、このステップは必要ない。しかし、フィールドに一意の識別子やオブジェクトの構築回数などの情報を保存する場合、clone()メソッドは値を計算し、適切な値をフィールドに代入しなくてはならない[Bloch 2008]。
clone()メソッドを定義するクラスはfinal宣言しなくてはならない。final宣言することで、信頼できないコードがclone()メソッドをオーバーライドするサブクラスを定義し、偽のインスタンスを作成することを不可能にする。clone()メソッドは、すべての可変な内部状態(上記のコード例ではDateオブジェクト)をコピーすべきである。
信頼できないコードが、可変な引数を使ってアクセッサメソッドを呼び出すことが可能な場合、引数がインスタンスフィールドに保存される前にそのディフェンシブコピーを作成せよ。詳しくは「OBJ06-J. 可変入力や可変な内部コンポーネントはディフェンシブコピーを作成する」を参照のこと。また、信頼できないコードに可変な内部状態を返す場合、そのディフェンシブコピーを作成して返すこと。 詳しくは「OBJ05-J. privateかつ可変なクラスメンバへの参照を返す前にそのディフェンシブコピーを作成する」を参照。
信頼できないコードが常に、可変クラスから取得した可変な内部状態に対してのみclone()メソッドを呼び出し、作成されたコピーに対してのみ処理を行うのであれば、ディフェンシブコピーを作成する必要はないであろう。残念ながら、信頼できないコードがこれに従ういわれはない。むしろ、悪意をもったコードはあらん限りの不正な操作を行おうとするだろう。前述の適合コードでは、信頼されるコードに対してclone()メソッドを用意し、かつ、信頼できないコードからアクセッサメソッドが直接呼び出された場合にオブジェクトの内部状態が不正に改変されないことを保証している。
適合コード (final メンバを持つ clone())
可変クラスのインスタンスフィールドがfinal宣言されており、コピーメソッドがない場合、以下の解決法に示すようにclone()メソッドを用意しなくてはならない。
public final class MutableClass implements Cloneable {
private final Date date; // final フィールド
public MutableClass(Date d) {
this.date = new Date(d.getTime()); // privateフィールドにコピー
}
public Date getDate() {
return (Date) date.clone(); // コピーして返す
}
public Object clone() {
Date d = (Date) date.clone();
MutableClass cloned = new MutableClass(d);
return cloned;
}
}
呼出し元はclone()メソッドを使うことで、そのような可変クラスのインスタンスを取得することができる。clone()メソッドは、finalメンバのクラスの新規インスタンスを作成し、そこへ元の状態をコピーしなくてはならない。新規インスタンスが必要な理由は、メンバクラスには利用可能なコピーメソッドが存在しないかもしれないからである。メンバクラスが将来更新されるようであれば、手元のコピーに状態を保存することが重要になる。最後に、clone()メソッドは、新規作成したメンバインスタンス(d)を使ってそれを内包するクラス(MutableClass)の新規インスタンスを作成し、これを呼出し元に返す。
clone()メソッドを定義する可変クラスはfinal宣言しなくてはならない。
適合コード (変更不可能な Date のラッパー)
可変オブジェクトのコピーの作成が不可能であるもしくはコストがかかる場合、変更不可能なビュークラスを作成するという方法もある。このクラスは例外をスローするために可変メソッドをオーバーライドし、可変クラスを保護する。
class UnmodifiableDateView extends Date {
private Date date;
public UnmodifiableDateView(Date date) {
this.date = date;
}
public void setTime(long date) {
throw new UnsupportedOperationException();
}
// その他すべてのmutatorメソッドをオーバーライドし、
// UnsupportedOperationException をスローするようにする
}
public final class MutableClass {
private Date date;
public MutableClass(Date d) {
this.date = d;
}
public void setDate(Date d) {
this.date = (Date) d.clone();
}
public UnmodifiableDateView getDate() {
return new UnmodifiableDateView(date);
}
}
例外
OBJ04-EX0: 「OBJ07-J. センシティブなクラスはコピーさせない」にあるように、センシティブなクラスはクローンを作成できないようにすべきであり、このルールの例外である。
リスク評価
コピー機能を持たない可変クラスを作成すると、そのインスタンスが信頼できないコードに渡された場合にデータが破壊される恐れがある。
| ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
|---|---|---|---|---|---|
| OBJ04-J | 低 | 高 | 中 | P6 | L2 |
自動検出
一般に自動検出を行うのは困難である。ヒューリスティックなアプローチは有効であろう。
関連ガイドライン
| MITRE CWE | CWE-374. Passing Mutable Objects to an Untrusted Method |
| CWE-375. Returning a Mutable Object to an Untrusted Caller | |
| Secure Coding Guidelines for the Java Programming Language, Version 3.0 | Guideline 2-3. Support copy functionality for a mutable class |
参考文献
| [API 2006] | Method clone() |
| [Bloch 2008] | Item 39. Make defensive copies when needed, Item 11. Override clone judiciously |
| [Security 2006] |
翻訳元
これは以下のページを翻訳したものです。
OBJ04-J. Provide mutable classes with copy functionality to allow passing instances to untrusted code safely (revision 116)



