요약
헤일라이드가 libwebp보다 안전하고 빠른 Rust 기반 WebP 디코더 wpd를 공개했습니다.
압축 기술 회사 헤일라이드(Halide) 팀의 발표입니다. 2023년 libwebp 취약점 CVE-2023-4863이 Chrome, Firefox, Signal 등을 쓰는 기기 수십억 대를 위험에 빠뜨린 일이 계기가 됐습니다.
왜 중요한가
- Chromium 통계로는 심각한 보안 버그의 약 70%가 메모리 안전 문제이고, wpd는 이 원인을 언어 차원에서 막습니다.
- 안전한 Rust 디코더(image-webp)는 이미 있었지만, wpd는 속도에서도 libwebp를 앞선다는 점이 다릅니다.
- 제약이 아주 적은 BSD 2조항 라이선스로 공개해 누구나 무료로 가져다 쓸 수 있습니다.
핵심 내용
- 단일 스레드에서 libwebp보다 손실 압축은 1.19배, 무손실 압축은 2.74배 빠르다고 밝혔습니다.
- 멀티스레드에서는 손실 2.68배, 무손실 3.19배로 차이가 더 벌어졌습니다.
- 손실·무손실, 알파(투명도), 애니메이션 등 libwebp의 기능을 모두 갖췄습니다.
- 스레드 수 지정과 애니메이션 프레임 병렬 디코딩 기능을 새로 더했습니다.
- 손으로 짠 어셈블리를 빼고 빌드하면 안전하지 않은 코드는 검증된 zerocopy 크레이트(Rust 라이브러리)에만 남습니다.
작성자 설명
작성자는 본문에 소스 저장소 주소(https://github.com/halidecx/wpd)만 남겼습니다.
HN 반응
- 구글의 파일 형식용 안전 언어 Wuffs와 견주는 댓글이 많았고, 개발자 측은 자체 측정에서 wpd가 2~8배 빠르다고 답했습니다.
- Wuffs 작성자와 일부 이용자는 어셈블리가 안전성의 빈틈이라고 지적했고, 개발자 측은 경계를 검사하는 래퍼 뒤에 두었다고 반박했습니다.
Wuffs 기반 WebP 디코더가 지금 어떤 상태인지는 잘 모르겠습니다만, 안전성과 속도라는 같은 목표를 갖고 있으니 비교해 볼 만한 또 다른 구현체일 것 같습니다.
https://github.com/google/wuffs/tree/main/std/webp
Wuffs 작성자입니다.
Halide 팀의 새 디코더 wpd를 축하드립니다. 성능 수치가 인상적이네요. wpd(Rust 라이브러리)가 쓰시는 환경에 맞는다면 좋은 일이니 그냥 쓰시면 됩니다!
다만 Wuffs가 언급됐으니, 그래도 Wuffs를 고려해 볼 만한 이유를 몇 가지 말씀드리겠습니다.
1.a. 마찬가지로 Python 프로젝트든 Java든 뭐든, C 코드를 감쌀 수 있다면 Wuffs 라이브러리(C 형태)도 감쌀 수 있습니다. 빌드 과정에 새 툴체인을 추가하지 않고도 프로세스 안에서 메모리 안전한 이미지 디코딩을 할 수 있습니다.
SECCOMP_MODE_STRICT샌드박스 안에서 돌릴 수 있습니다.2.a. 프로세스를 분리해 극도로 메모리 안전하게 이미지를 디코딩하려는 경우를 위해, Wuffs 저장소의 example/convert-to-nia/convert-to-nia.c 프로그램은 stdin으로 이미지(JPEG, PNG, WebP 등)를 읽어 stdout으로 NIA(Farbfeld와 비슷한 아주 단순한 이미지 포맷)를 씁니다. Wuffs 언어와 툴체인 자체를 감사하고 싶지 않으시더라도, 이 convert-to-nia 프로그램의 보안 검토는 그야말로 간단합니다. main 함수가 가장 먼저 하는 일 중 하나가 스스로
SECCOMP_MODE_STRICT샌드박스를 거는 것이기 때문입니다.다 맞는 말씀이고, Wuffs 프로젝트가 특정 용도에서는 충분히 말이 된다고 생각합니다. 다만 wpd를 설명하면서 "SIMD를 어셈블리로 unsafe 블록 안에서 한다"고 하시는 건 조금 잘못 짚으신 것 같습니다. wpd는 넓은 unsafe 영역 안에서 Rust SIMD로 디코더를 구현하지 않습니다. 크레이트 전체에서 unsafe 코드는 금지되어 있고, 전용 어셈블리 바인딩 모듈에서만 허용됩니다. 어셈블리를 끄면 크레이트는 forbid(unsafe_code)를 씁니다.
손으로 짠 SIMD 커널은 안전한 Rust 래퍼 뒤에 있고, 이 래퍼가 raw 포인터를 만들어 내기 전에 정확한 슬라이스 범위를 계산하거나 검증합니다. CPU별 루틴도 런타임에 기능을 감지한 뒤에만 설치됩니다. wpd에는 어셈블리 커널이 래퍼가 정해 준 범위 밖을 읽거나 쓰지 않는지 확인하는 가드 페이지 테스트까지 있습니다. 손으로 짠 SIMD는 이 모든 수고를 들일 만큼 가치가 있다고 판단했습니다(근거는 dav1d 관련 발표를 몇 개 보시면 되는데, 저희도 같은 견해입니다).
Wuffs는 컴파일러가 SIMD 구현 내부의 로드와 스토어까지 검사한다는 점에서 더 강한 형식적 속성을 갖고 있는 게 맞습니다. 하지만 흥미로운 메모리 안전성 공격 표면(공격자가 통제하는 구조의 파싱과 디코딩) 대부분은 wpd에서도 전부 안전한 Rust에 있으니, wpd도 여전히 안전하고 libwebp보다는 분명히 크게 나아졌다고 생각합니다.
추신: 원하시면 wpd의 어셈블리를 끌 수 있고, 그러면 안전하면서 Wuffs와 비슷한 성능이 나옵니다.
알려 주셔서 감사합니다. Wuffs에 WebP 디코더가 있는 줄은 몰랐네요. 방금 벤치마크를 돌려 봤는데(b2e6da3), 제 M5 Pro에서 wpd가 손실 압축 정지 이미지는 2~3배, 알파가 있는 손실 압축은 5~7배, 무손실은 7~8배 빠릅니다(싱글 스레드인데도요).
그리고 Wuffs는 libwebp와 비트 단위로 똑같지 않고, 애니메이션 WebP는 아예 지원하지 않아서 WebP 디코딩의 현실적인 선택지인지 잘 모르겠습니다.
Wuffs 작성자입니다.
2월에 손실 압축 WebP 지원을 추가하는 풀 리퀘스트( https://github.com/google/wuffs/pull/168 )가 들어왔습니다. 무손실 WebP는 (어떤 면에서는 WebP라는 "브랜드"만 가져다 쓴 완전히 다른 포맷인데) Wuffs 구현이 나온 지 벌써 몇 년 됐고요.
아무튼 그 PR에는 SIMD 가속이 포함되어 있었고 성능은 libwebp(C 코드)와 맞먹었습니다.
제가 보기에 그 PR의 코드는 상당 부분, 혹은 대부분 AI의 도움을 받아 작성된 것이었습니다. 기능 면에서는 좋은 일이지만, 저는 여전히 사람이 직접 짠 코드를 더 믿습니다.
그래서 그 뒤로 PR을 손으로 다시 쓰는 작업을 해 왔습니다. 애니메이션 지원도 추가하고 싶고, 그래서 지난달에는 (비교 기준으로) 애니메이션 WebP를 디코딩하고 테스트할 수 있도록 애니메이션 PNG 관련 Wuffs 도구 변경분을 반영했습니다.
수작업 재작성의 상당 부분은 이미 커밋했지만 SIMD 부분은 아직 들어가지 않았습니다. 그러니 (PR이 아니라) main 브랜치에 있는 것은 아직 성능이 libwebp만 못한 게 맞습니다. 다만 SIMD 부분이 들어가면 해결될 겁니다.
처음 듣는 얘기입니다. PR 168에는 libwebp와 픽셀 단위로 똑같은 출력을 낸다고 적혀 있습니다. main 브랜치에 있는 것도 픽셀 단위로 똑같은 출력을 목표로 하고 있고요. 예를 들어 YUV를 RGB로 바꿀 때 libjpeg의 공식이 아니라 libwebp의 공식을 구현했습니다. 둘 다 BT.601을 쓰지만 libwebp는 스튜디오 레인지, libjpeg는 풀 레인지를 씁니다.
비트 단위로 똑같지 않은 .webp 이미지 예시를 링크해 주실 수 있나요?
모든 구현체가 나아지도록 기꺼이 돕고 싶습니다. Halide로 이메일을 한 통 보내 주시면 따로 이야기해 볼 수 있을까요? 도울 수 있다면 기쁘겠습니다!
관련해서 Signal Messenger의 webpsan 크레이트가 있습니다. libwebp에 넘기기 전에 webp 컨테이너 문법을 검증해 주는 크레이트입니다. 다만 실제 픽셀 데이터를 디코딩하는 데까지는 가지 않습니다. webp는 픽셀 데이터를 온전히 디코딩하려면 캔버스 전체를 할당해야 하는 방식이라서, 거기까지 하면 webpsan이 결국 완전한 디코더가 되어 버리기 때문입니다.
https://docs.rs/webpsan/latest/webpsan/
파서와 분리된 검증기는 과거에 수많은 취약점으로 이어졌습니다. 검증기가 못 잡아낸 무언가가 결국 파서를 죽이는 일이 늘 있죠.
unsafe 파서 앞에 검증기를 두는 건 unsafe 파서만 쓰는 것보다 분명히 낫습니다. 하지만 안전한 파서보다는 못합니다.
좋네요, 이건 몰랐습니다!
구글이 곧 내놓을 Gemini 4 Argon LLM 출시 홍보 자료에서, 손으로 짠 SIMD를 대체하면서 몇 가지를 Rust로 다시 썼다고 밝혔습니다. 다만 webp가 언급된 것 같지는 않습니다.
이렇게 말했습니다.
Wuffs는 어떻게 된 건가요? https://github.com/google/wuffs
안전성과 성능을 갖춘, 파일 포맷 라이브러리 전용으로 만든 프로그래밍 언어인데요.
저는 libwebp를 WebAssembly로 컴파일해서 쓸 수 있는 웹 페이지를 만들었습니다. https://qip.dev/webp-to-png
같은 wasm을 커맨드 라인에서도 돌릴 수 있습니다.
libwebp와 비교한 성능 수치가 있나요?
대부분을 wasm으로 두면 안 되나요? JS 없이 돌아가는 wasm 런타임도 있고, 구글이라면 자사 엔진을 거기에 맞춰 고칠 수 있을 텐데요. 그런 다음 반대편은 Rust와 연동하면 되지 않을까요.
wasm으로 컴파일하거나 wasm 디코더를 배포할 수는 있습니다. JS 디코더를 배포하는 것도 가능하고요. 하지만 속도와 리소스 소모가 중요해서 둘 다 선택지에서 빠집니다.
글에서 직접 언급되지는 않았지만, 이 디코더는 자유 소프트웨어로 라이선스되어 있고(그러면서도) Claude를 많이 써서 개발한 것으로 보입니다.
안전을 위해 손으로 짠 어셈블리 없이 컴파일할 수 있다는 점은 좋은데, 그 경우 성능은 어떤가요? 제시된 벤치마크에는 이 수치가 없는 것 같은데, 맞나요?
진짜 메모리 안전성을 원하신다면 webp는 fil-C로도 문제없이 빌드됩니다.
이 프로젝트도 훌륭하지만 단서가 붙습니다.
어셈블리 코드가 있습니다. 조심스럽게 짰을 수는 있어도 탈출구(escape hatch)입니다.
의존성이 조금이라도 있다면 그것들이 연쇄적으로 unsafe 코드를 더 끌어들일 가능성이 높습니다.
Fil-C가 충분히 의미 있는 경우도 있지만, 이건 그런 경우가 아닙니다.
WUFFS를 쓸 여유가 없다면 이 Rust 구현이 합리적인 선택입니다. Fil-C는 쓸 여유가 되는데 WUFFS는 쓸 여유가 안 되는 현실적인 경우는 없을 겁니다.
비슷한 개발 노력을 들였을 때 WUFFS 코덱은 Fil-C보다 분명히 훨씬 빠를 뿐 아니라 컴파일 타임에 완전히 안전합니다. 반면 Fil-C에서는 실수가 있으면 운영 환경에서 잡히고, 그 결과는 서비스 거부(DoS)입니다.
그런 면에서 Rust는 절충안입니다. WUFFS가 컴파일 중에 잡아내는 것을 전부 잡지는 못하지만, Fil-C보다는 훨씬 많이 잡아냅니다.