Rust 빌림 검사기에서 11년 된 건전성 버그들 발견
요약
Rust 언어에서 공변적 필드 대입(covariant field assignment)을 통해 구조체의 수명 제약을 우회하는 심각한 건전성 버그들이 발견되었습니다. 이 문제는 컴파일러가 거부해야 할 코드가 통과하게 만들며, 지역 변수의 참조를 전역 또는 'static으로 취급할 위험이 있습니다. 해당 이슈는 1.4 버전부터 존재했던 것으로 확인되었으며, 타입 단일화 자체의 문제까지 제기되고 있습니다.
핵심 포인트
- 공변적 필드 대입을 통해 수명 제약 우회 가능성 발견
- 지역 변수의 참조를 'static으로 오인할 위험이 있음
- 문제는 1.4 버전부터 존재했으며, 심각도가 높음
- 소멸자(Drop) 내부에서 메모리 해제 접근 재현됨
공변적 필드 대입 으로 구조체의 수명 제약을 우회하는 재현 코드가 발견됨. 컴파일이 거부돼야 할 코드가 통과하고, Drop
에서 짧은 수명의 참조를 'static
참조로 취급할 수 있음
최초 재현 코드는 수명이 끝난 지역 변수의 참조를 전역 저장소에서 다시 사용하며, AddressSanitizer 실행 시 stack-use-after-scope
오류로 종료됨
최초 보고는 당시 nightly와 최소 Rust 1.85.0 까지의 stable에서 문제를 확인했으며, 후속 재현에서는 1.4부터 건전성 문제가 있었음을 확인함
재현 범위는 'static
제약에만 국한되지 않음. 수명 간 관계 를 이용하거나 소멸자에서 클로저를 호출하는 코드에서도 해제된 메모리 접근을 재현함
후속 논의에서는 타입 단일화(unification) 자체가 수명 제약을 검사하지 않을 가능성까지 제기됨. 우선순위는 P-high
로 정해졌으며, 제공된 논의에는 수정안이나 해결 결과가 없음
필드 대입으로 우회되는 수명 제약
이슈 #164250 은 공변적 필드 대입 으로 구조체의 수명 요구사항을 우회하는 문제를 다룸
최초 재현은 struct Q<T: 'static + Send>(Option<T>)
를 정의하고, Q(None)
으로 생성한 뒤 지역 String
의 참조를 q.0 = Some(&l)
로 대입함
Drop
구현은 self.0.take()
로 값을 꺼내 전역 KEPT
에 저장함
KEPT
의 타입은 Mutex<Vec<Box<dyn std::any::Any + Send>>>
이며, 이후 downcast_ref::<&'static String>()
으로 참조를 꺼내 출력함
기대 동작은 컴파일 거부 지만, 실제로는 컴파일된 뒤 원래 문자열 대신 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
을 출력함
축약된 재현에는 Send
나 전역 저장소가 필요하지 않음. Q<T: 'static>(Option<T>)
와 Drop
만으로, 소멸자 내부가 'static
이 아닌 &String
을 &'static String
으로 가정하는 상황을 만들 수 있음
확인된 버전과 관련 이슈
최초 보고에서 당시 nightly 와 최소 stable 1.85.0 까지 문제를 확인함
후속 확인에서는 축약된 코드가 1.18.0(2017년) 부터 컴파일됐으며, 다른 재현으로 1.4부터 건전성 문제가 있음 을 확인함
이슈 #128225 가 관련 있을 가능성 이 있는 이슈로 연결됐으며, 직접적인 연관성은 확정하지 않음
'static
에 국한되지 않는 재현
전역 static
에 의존하지 않는 예제는 MyType<'a, 'b: 'a, 'c>
에 &'c mut &'a str
과 &'b str
을 저장함
지역 String
의 참조를 q.1
에 대입한 뒤, Drop
에서 *self.0 = self.1
을 실행함
지역 문자열의 스코프가 끝난 뒤 외부 변수 me_out
을 출력하는 코드로, 수명 간 제약을 우회하는 상황을 재현함
또 다른 예제는 Q<T, F: FnMut(&T)>
의 소멸자에서 저장된 클로저를 호출함
지역 Vec<Vec<i32>>
의 참조를 q.0
에 넣고, 클로저가 이를 외부의 Option<&'static _>
에 저장함
지역 변수가 사라진 뒤 외부 값을 출력하면 세그멘테이션 오류 또는 해제된 메모리 출력 이 발생하며, 이 변형도 1.4부터 재현됨
스레드를 사용하는 예제는 Q<'a: 'static>(Option<&'a String>)
에 지역 문자열 "abc"
의 참조를 대입함
Drop
에서 이를 let wrong: &'static String
에 저장하고 새 스레드로 넘김
새 스레드는 1초 뒤 참조를 출력하고, 메인 스레드는 지역 문자열의 스코프가 끝난 뒤 2초간 대기함
논의가 확장되면서 이슈 제목도 T: 'static
제약 우회 에서 구조체의 수명 요구사항 우회 로 범위를 넓힘
타입 단일화로 확장된 문제 가능성
후속 댓글에서는 타입 단일화가 일반적으로 타입의 수명 제약을 검사하지 않는 것일 수 있다 는 가설과 함께, 필드 대입 없이 단일화하는 예제를 추가함
예제는 Q<T: Copy, F: FnMut(T)>
두 개를 배열에 넣어 타입을 단일화함
하나는 Box::leak
으로 만든 &'static T
와 fn(&'static T)
를 저장함
다른 하나는 지역 값의 참조와 fn(&T)
를 저장하며, 여기서 T
는 Vec<Vec<i32>>
의 타입 별칭임
댓글은 배열 원소 타입인 Q<&'local T, fn(&'static T)>
이 올바른 형식의 타입이 아니라고 지적함
이어서 std::mem::swap
으로 두 원소의 참조 필드를 교환함. 소멸자가 함수를 호출하면 지역 값의 참조가 'static
인자를 받는 함수로 전달되고, 그 함수가 참조를 출력하는 스레드를 생성함
우선순위와 해결 상태
우선순위는 Zulip 논의 를 거쳐 P-high
로 지정됨
제공된 논의에는 여러 재현 코드와 원인 가설이 있지만, 구체적인 수정안이나 해결 결과는 없음
댓글과 토론
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기