Security · · 8 min read

pypdf: /ToUnicode bfchar 토큰 길이 검증 누락으로 인한 메모리 고갈

pypdf의 /ToUnicode bfchar 파서가 개별 토큰 크기를 제한하지 않아, 조작된 PDF의 텍스트 추출 과정에서 과도한 메모리를 사용할 수 있었던 문제를 분석하고 패치까지 확인한 기록입니다.

#Security #pypdf #Python #DoS #Responsible Disclosure

요약

pypdf/ToUnicode CMap 처리 코드에서 bfchar 항목의 개별 토큰 길이를 제한하지 않는 문제를 확인했습니다. 공격자가 조작한 PDF를 애플리케이션이 extract_text()로 처리하면, 압축된 작은 입력이 큰 문자열 디코딩과 임시 객체 할당으로 확장되어 메모리 사용량과 처리 시간이 증가할 수 있었습니다.

이 문제는 공개 GHSA로 등록되었고 pypdf 6.18.1에서 수정되었습니다.

| 항목 | 내용 | | --- | --- | | 영향 구성요소 | /ToUnicode CMap의 bfchar 파서 | | 공개 식별자 | [GHSA-fp3h-c4fm-7vvf](https://github.com/py-pdf/pypdf/security/advisories/GHSA-fp3h-c4fm-7vvf) | | 심각도 | Medium | | 분류 | CWE-400: Uncontrolled Resource Consumption | | 영향 버전 | pypdf < 6.18.1 | | 수정 버전 | pypdf 6.18.1 이상 | | CVE | 이 글 작성 시점인 2026-09-14 기준 미배정 | | 수정 PR | [py-pdf/pypdf #4071](https://github.com/py-pdf/pypdf/pull/4071) |

영향 범위

문제가 발생하는 공개 처리 경로는 다음과 같습니다.

조작된 PDF
  -> PdfReader
  -> 페이지의 폰트 리소스 로딩
  -> /ToUnicode CMap 파싱
  -> bfchar 토큰 디코딩
  -> extract_text()

페이지에 실제 텍스트가 많지 않더라도 페이지 리소스의 폰트를 읽는 과정에서 CMap이 처리될 수 있습니다. 따라서 PDF 업로드, 미리보기, 검색 색인, 문서 변환처럼 서버가 신뢰하지 않은 PDF에 자동으로 텍스트 추출을 수행하는 서비스가 주요 영향 대상입니다.

이 문제는 코드 실행이나 정보 노출 문제가 아니라, 파서가 입력 크기에 비례하지 않는 자원 사용을 하게 되는 가용성 문제입니다. 실제 영향은 프로세스의 메모리 제한, 동시 처리 수, 작업 큐 구성에 따라 달라질 수 있습니다.

기술적 원인

기존 CMap 파서에는 전체 매핑 항목 수를 제한하는 검사가 있었습니다. 그러나 이 검사는 “항목이 몇 개인가”를 제한할 뿐, 한 항목을 구성하는 소스·대상 토큰이 얼마나 큰지는 제한하지 않았습니다.

parse_bfchar()는 입력을 쌍 단위로 나눈 뒤 다음과 같은 작업을 수행합니다.

1. PDF가 제공한 소스 토큰과 대상 토큰을 읽습니다. 2. 16진수 문자열을 바이너리로 변환합니다. 3. 대상 값을 문자 인코딩에 맞춰 디코딩합니다. 4. 결과를 매핑 사전에 저장합니다.

문제 버전에서는 2번과 3번으로 넘어가기 전에 개별 토큰의 길이를 확인하지 않았습니다. 그 결과 입력 파일 자체는 작더라도, 압축 해제와 16진수 변환·UTF-16 디코딩 과정에서 훨씬 큰 임시 문자열과 객체가 생성될 수 있었습니다.

같은 계열의 bfrange 파서에는 이미 소스 코드 토큰과 대상 문자열에 대한 길이 제한이 있었지만, 형제가 되는 bfchar 경로에는 동일한 보호가 적용되지 않은 상태였습니다. 이처럼 동일한 데이터 형식을 처리하는 인접 파서 사이에서 보안 제한의 적용 범위가 달라진 것이 핵심 원인입니다.

재현 및 영향 측정

다음 측정은 Windows, CPython 3.11.9, pypdf 6.18.0 환경에서 수행했습니다. 메모리는 tracemalloc이 보고한 Python 할당 피크이며, 실제 결과는 운영체제·Python 할당자·하드웨어·pypdf 리비전에 따라 달라질 수 있습니다.

| 테스트 조건 | 입력 PDF 크기 | 처리 시간 | Python 할당 피크 | 결과 | | --- | ---: | ---: | ---: | --- | | bfchar, 큰 단일 토큰, 128개 폰트 | 24,189 bytes | 약 8.53초 | 약 319 MB | 처리 완료 | | 같은 입력, strict=True | 24,189 bytes | 약 8.55초 | 약 319 MB | 동일하게 처리 완료 | | 제한이 있는 bfrange 제어 사례 | 24,190 bytes | 약 0.08초 | 약 50 MB | 즉시 거부 | | 로컬 패치 적용 후 동일한 bfchar 사례 | 24,189 bytes | 약 0.09초 | 약 50 MB | 즉시 거부 |

핵심 확인 사항은 다음과 같습니다.

  • 공개 API인 PdfReader(...).pages[0].extract_text()에서 도달할 수 있었습니다.
  • 입력 PDF가 소스·대상 토큰을 제어할 수 있었습니다.
  • 기본 모드뿐 아니라 strict=True에서도 동작이 재현되었습니다.
  • 매핑 항목 수 제한만으로는 개별 토큰의 과도한 크기를 막지 못했습니다.
  • 압축은 파일 크기를 줄일 수 있지만, 파싱 중 생성되는 문자열과 매핑 객체의 크기를 제한하지는 못했습니다.

공개 글에서는 악용 가능한 PDF 샘플, 생성 스크립트, 페이로드 전문은 제공하지 않습니다. 위 수치는 문제의 성격과 패치 효과를 검증하는 데 필요한 범위로만 정리했습니다.

수정 내용

pypdf 6.18.1에서는 bfchar의 각 소스 토큰과 대상 토큰을 디코딩하기 전에 기존 bfrange 경로와 동일한 정책으로 길이를 검사하도록 수정되었습니다.

수정의 목적은 다음과 같습니다.

  • 소스 코드 토큰은 허용된 CMap 코드 길이를 넘지 않도록 제한
  • 대상 문자열 토큰은 허용된 디코딩 크기를 넘지 않도록 제한
  • 제한을 넘으면 디코딩·매핑 사전 저장 전에 LimitReachedError로 중단
  • 빈 매핑을 나타내는 기존 placeholder 동작은 유지
  • bfcharbfrange에 동일한 제한 정책을 적용

이 방식은 큰 값이 디코더와 문자열 객체 생성 단계에 도달하기 전에 차단한다는 점에서, 사후에 메모리를 회수하는 방식보다 효과적입니다.

검증 포인트

패치 검증에서는 정상 범위의 CMap 항목은 계속 처리되고, 소스 또는 대상 토큰이 제한을 넘으면 예외가 발생하는지 확인했습니다. 또한 제한 초과 입력이 매핑 사전에 부분적으로 반영되지 않는지도 함께 확인했습니다.

운영 환경에서 pypdf를 사용하는 경우에는 다음 방어책도 함께 고려할 수 있습니다.

  • 신뢰하지 않은 PDF의 텍스트 추출을 별도 프로세스나 컨테이너에서 실행
  • 작업별 CPU·메모리·실행 시간 제한 설정
  • 업로드 직후 동기식으로 무제한 파싱하지 않고 작업 큐로 격리
  • pypdf를 6.18.1 이상으로 업데이트
  • 예외 발생 시 원본 PDF와 처리 로그를 제한된 접근 영역에 보관

이 방어책은 라이브러리 패치를 대체하지 않으며, 여러 PDF를 동시에 처리하는 서비스에서 피해 범위를 줄이기 위한 보완책입니다.

공개 타임라인

| 시점 | 내용 | | --- | --- | | 2026-09-09 | 인접한 bfrange 보안 수정의 적용 범위를 검토하던 중 bfchar 변형 확인 | | 2026-09-09 | 영향 버전과 공개 API 경로를 확인하고 비공개 제보 절차로 전달 | | 2026-09-11 | GHSA가 공개되고 pypdf 6.18.1 패치 버전이 반영됨 | | 2026-09-14 | CMS 게시용 초안 작성 |

참고 자료

  • [GHSA-fp3h-c4fm-7vvf: Possible large memory usage for large /ToUnicode streams](https://github.com/py-pdf/pypdf/security/advisories/GHSA-fp3h-c4fm-7vvf)
  • [pypdf pull request #4071](https://github.com/py-pdf/pypdf/pull/4071)
  • [pypdf 6.18.1 release](https://github.com/py-pdf/pypdf/releases/tag/6.18.1)

공개 범위에 대한 메모

이 글은 공개된 권고문과 패치 결과를 바탕으로 원인·영향·완화책을 설명하는 기술 기록입니다. 재현용 악성 PDF와 자동화된 공격 코드는 포함하지 않았으며, 세부 검증이 필요한 경우 위 GHSA와 수정 PR을 기준으로 확인할 수 있습니다.

정민기 연구 및 기술 기록으로 돌아가기