지금은 2025년이다. DirtyCOW취약점이 아마.. CVE-2016으로 시작했으니 거의 10년쯤.. 된 취약점이다.
지금와서 무슨 DirtyCOW 취약점 분석이냐 라고 할 수도있지만, 여긴 내블로그니까
내맘대로 할거다 :P
이번 포스팅에서는 더티카우 취약점과 race condition 그리고 쓰레드의 특징에 대해서 한번 얘기해보려고 한다.
우선은.. race condition부터다.
1. Race Condition
레이스 컨디션은 경합 조건이라고도 한다. 이는 서로 다른 두 작업이 하나의 결과물을 공유할때 서로의 결과물에 영향을 미칠 수 있을때를 의미한다.
예를 들어보자.
- result 파일의 초기 값은 10이다.
- A는 result 라는 파일을 열어 값을 확인한 후 +1을 계산한 값을 결과에 저장한다.
- B는 result 라는 파일을 열어 값을 확인한 후 +5를 계산한 값을 결과에 저장한다.
자 이때 어떤 문제가 발생하는가?
파일을 읽고 저장하는 이 과정에서 동시성 문제가 발생할 수 있다.
가령 다음과 같은 순서로 작업이 이루어졌다 생각해보자
- A가 result 파일을 열어 값을 읽었다.
- B가 result 파일을 열어 값을 읽었다.
- B가 result 파일의 값에 +5를 계산한 값을 저장했다.
- A가 result 파일의 값에 +1을 계산한 값을 저장했다.
결과는 어떻게 될까?
잘 생각해보면 11이 될 것이라는 것을 알 수 있다. 왜냐하면 A와 B 모두 result 파일이 초기상태인 0일때 값을 읽었다. 이후 B가 15라는 값을 썼지만, A가 다시 11을 씀으로써 B의 작업 결과가 반경되지 않는 결과가 나타날 수 있는 것이다.
이런 상태를 우리는 race condition이라 부른다. 두 별개의 프로세스간에 경합(동시성 문제)이 발생하는 것이다.
(참고로 이런 race condition을 방지하기 위해 운영체제에서는 mutex, 또는 semaphore라는 매커니즘을 사용한다.)
2. Dirty CoW(CVE-2016-5195)
더티 카우는 커널과 메모리 레벨에서 이런 동시성 문제로인해 발생하는 취약점을 exploit하는 취약점이다.
CoW는 젖소가 아니라 Copy on Write의 줄임말이다. 지금부터 자세한 의미를 한번 생각해보자.
운영체제 상에서 동작하는 A와 B라는 두 프로세스가 시스템 상에 존재하는 어떤 파일에 접근하고있다.
그런데 이 와중에 B가 해당 파일을 수정하려고 한다면?
커널은 원본 파일을 수정하는것을 허용하지 않고, 메모리상에 참조 파일의 카피본을 생성한다음 참조 주소를 변경한다.
이러한 기능을 바로 Copy on Write라 한다.
2-1. Dirty CoW 발생 원리
우선 현대 운영체제에서 각 프로세스는 가상 메모리 공간이라는 영역을 갖고있다.
바로 PA -> VA -> RVA 로 이어지는 메모리 영역 매핑과 관련된 이야기인데, 이 포스팅의 범위를 벗어나므로 생략하겠다.
2-1-1. mmap CoW
아무튼, 물리 저장장치에 있는 어떤 파일을 mmap이라는 systemcall을 통해 온메모리 형태로 적재할 수 있다.
이 때 PROT_READ와 MAP_PRIVATE flag를 사용한다.
PROT_READ는 읽기 전용, MAP_PRIVATE 는 데이터의 변경 내용을 ‘공유’ 하지 않는다는 뜻이다.
즉, 원본 파일의 읽기 전용 인메모리 카피본을 만들고 파일에 변경이 발생하면 원본이 아닌 카피본을 수정한다는 말과 같다.
위 1-5번 단계가 바로 해당 내용이다.
2-1-2. madvise, wirte 를 통한 race condition 유발
madvice는 프로세스가 사용하는 메모리 영역에 대해 커널에게 힌트를 주는 함수다.
이 때 MADV_DONTNEED 플래그를 사용하면 해당 영역은 더이상 필요없으므로 버리라는 의미와 같다.
|
|
자 그러면 CoW한 프라이빗 영역의 데이터가 버려진다.
그런데 이 때 write가 발생하면..? 뭔가 구린 냄새가 나지 않는가?
다시 순서대로 정리해보자.
- mmap으로 읽기 전용 + MAP_PRIVATE 플래그를 적용해 시스템 파일 맵핑
- thread 1: madvice(mem, n, MADV_DONTNEED) 로 매핑 페이지 무효화(버림) [반복]
- thread 2: 쓰기 시도(/proc/self/mem, ptrace 등을 사용) [반복]
- 커널이 아직 CoW 카피를 만들기 전이면? 원본 파일에 쓰기 발생
좀더 자세하게 정리해보자면 thread 1은 지속적으로 페이지를 폐기하는 역할을 한다. thread 2는 지속적으로 CoW에 쓰기를 시도한다. 이 때 실제 커널함수에서는 다음과 같이 처리가 된다.
do_page_fault() → handle_mm_fault() → do_wp_page() → copy_user_highpage()
그런데
2-1-2. /proc/self/mem
자 이제 파일을 수정할 것인데, 이 때 앞서 생성한 CoW 인메모리 포인터를 사용하지 않고 /proc/self/mem 으로 쓰기를 수행한다.
/proc/self 경로는 현재 PID를 가지는 프로세스의 심볼릭 링크이며 따라서 mem 파일은 프로세스의 메모리 영역이다.
읽기 전용 파일인데 MAP_PRIVATE 플래그를 사용했으므로 대상 파일의 사본을 생성한다.
Reference
https://www.cs.toronto.edu/~arnold/427/18s/427_18S/indepth/dirty-cow/demo.html https://en.wikipedia.org/wiki/Procfs