기간을 표현하는 클래스 - 불변식을 지키지 못했다!
public final class Period { private final Date start; private final Date end; public Period(Date start, Date end) { if (start.compareTo(end) > 0) { throw new IllegalArgumentException(start + "가" + end + "보다 늦다."); } this.start = start; this.end = end; } public Date start() { return start; } public Date end() { return end; } ... // 생략 }
Period 인스턴스의 내부를 공격해보자.
Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); end.setYear(78); // p의 내부를 수정함!
위와 같이 외부 공격으로부터 Period 인스턴스 내부를 보호하려면 생성자에서 받은 가변 매개변수 가각을 방어적으로 복사(defensive copy)해야 한다.
수정한 생성자 - 매개변수의 방어적 복사본을 만든다.
public final class Period { ... public Period(Date start, Date end) { this.start = new Date(start.getTime()); this.end = new Date(end.getTime()); if (this.start.compareTo(this.end) > 0) { throw new IllegalArgumentException(start + "가" + end + "보다 늦다."); } } ... }
위와 같이 매개변수 유효성 검사 전에 방어적 복사를 하면 TOCTOU 공격을 방어할 수 있다.
여기서 말하는 TOCTOU란?
멀티스레드 환경에서 매개변수로 받은 원본 객체의 유효성을 검사 후,
복사본을 만드는 순간에 다른 스레드가 원본 객체를 변경할 위험을 말한다.
유효성 검사를 했지만 여전히 아래와 같은 공격엔 취약하다.
Date start = new Date(); Date end = new Date(); Period p = new Period(start, end); end.setYear(78); // p의 내부를 수정함!
수정한 접근자 - 필드의 방어적 복사본을 반환한다.
public final class Period { ... public Date start() { return new Date(start.getTime()); } public Date end() { return new Date(end.getTime()); } ... }