
GNU coreutils split 프로그램의 힙 오버플로 취약점 분석. CVE-2024-0684
GNU coreutils "split" 프로그램의 취약점으로 인해 사용자 제어 데이터로 힙 버퍼 오버플로우가 발생할 수 있습니다.
해당 취약점은 40bf1591bb4362fa91e501bcec7c2029c5f65a43 커밋에서 2023년 3월 4일에 도입되었습니다. 수정 사항은 c4c5ed8f4e9cd55a12966d4f520e3a13101637d9 커밋에서 2024년 1월 17일에 릴리스되었습니다.
영향을 받는 버전: GNU coreutils v9.4; v9.3; v9.2
개념 증명:
이 저장소의 split_me 예제 파일을 사용하여 영향을 받는 버전에서 충돌을 발생시킬 수 있습니다.
split -C 1024 ./split_me
이렇게 하면 split이 세그멘테이션 오류(SIGABRT)로 충돌합니다.
저는 QR 코드를 사용하여 에어 갭(air-gapped) 시스템에서 데이터 추출을 자동화하는 과정에서 이 취약점을 발견했습니다. qrencode로 생성된 QR 코드는 약 4000자 용량을 가지므로 split을 많이 사용해야 했습니다. 특정 테스트 케이스에서 split이 세그멘테이션 오류로 충돌했습니다.
GNU coreutils가 오픈 소스이므로 바이너리를 리버스 엔지니어링하는 대신 소스 코드를 사용하여 버그를 식별할 수 있습니다. 오픈 소스 프로젝트에서는 버그 보고서를 가능한 한 구체적으로 작성하는 것이 좋으며, 버그를 도입한 정확한 커밋과 라인, 그리고 제안된 수정 사항을 제공하는 것이 이상적입니다. 이렇게 하면 유지보수자가 보고서를 신속하게 확인하고 응답 시간을 단축할 수 있습니다.
여러 시스템에서 버그를 확인하는 동안 충돌이 비교적 최신 버전의 split에서만 발생한다는 것을 알게 되었습니다. 정상 커밋과 비정상 커밋을 알고 있다면 커밋 기록에서 이진 탐색을 수행하여 실제로 버그를 도입한 커밋을 찾을 수 있습니다.
Git은 이러한 사용 사례를 위한 특정 도구인 git bisect를 제공합니다. 이 도구는 테스트할 커밋을 자동으로 제안하고 정상 또는 비정상으로 표시할 수 있게 합니다. 결국 버그를 도입한 커밋에 도달하게 됩니다. 이 경우:
commit 40bf1591bb4362fa91e501bcec7c2029c5f65a43
Author: Paul Eggert <[email protected]>
Date: Sat Mar 4 11:42:16 2023 -0800
split: prefer signed integers to size_t
This allows for better runtime checking with gcc
-fsanitize=undefined.
* src/split.c: Include idx.h.
(open_pipes_alloc, n_open_pipes, suffix_length)
(set_suffix_length, input_file_size, sufindex, outbase_length)
(outfile_length, addsuf_length, create, cwrite, bytes_split)
(lines_split, line_bytes_split, lines_chunk_split)
(bytes_chunk_extract, ofile_open, lines_rr, main):
Prefer signed integers (typically idx_t) to size_t.
src/split.c | 105 ++++++++++++++++++++++++++++++------------------------------
1 file changed, 52 insertions(+), 53 deletions(-)
그런 다음 프로그램을 컴파일하여(가급적 주소 산출기(address sanitizer)와 함께) 충돌이 발생하는 정확한 라인을 찾을 수 있습니다. 약 50줄만 살펴보면 버그를 쉽게 식별할 수 있습니다. 이 경우 충돌은 잘못된 인덱스가 있는 memcpy() 호출에서 발생했습니다. 실제로 memcpy() 호출 주변을 확인해 보면 바로 전에 인덱스 계산을 변경하는 diff가 있습니다:
@@ -816,15 +820,10 @@
/* Update hold if needed. */
if ((eoc && split_rest) || (!eoc && n_left))
{
- size_t n_buf = eoc ? split_rest : n_left;
+ idx_t n_buf = eoc ? split_rest : n_left;
if (hold_size - n_hold < n_buf)
- {
- if (hold_size <= SIZE_MAX - bufsize)
- hold_size += bufsize;
- else
- xalloc_die ();
- hold = xrealloc (hold, hold_size);
- }
+ hold = xpalloc (hold, &hold_size, n_buf - (hold_size - n_hold),
+ -1, sizeof *hold);
memcpy (hold + n_hold, sob, n_buf);
n_hold += n_buf;
n_left -= n_buf;
이러한 변경 사항을 되돌리고 다시 컴파일하면 split이 모든 테스트 케이스를 오류 없이 처리합니다.
남은 것은 버그를 확인하고 수정 사항을 개발하기 위해 로직을 검토하는 것뿐입니다.