
현대 시스템을 위해 설계된 강화된 할당자입니다. Android의 Bionic libc에 통합되어 있으며, 다른 Linux 기반 플랫폼에서 사용하기 위해 musl 및 glibc와 함께 동적 라이브러리로 외부에서 사용할 수 있습니다. 시간이 지남에 따라 더 많은 이식성/통합을 얻게 될 것입니다.
이것은 malloc API와 다양한 확장 기능을 제공하는 보안 중심의 범용 메모리 할당자입니다. 힙 손상 취약점에 대한 상당한 강화 기능을 제공합니다. 보안 중심 설계는 또한 전통적인 할당자 설계보다 메타데이터 오버헤드와 단편화로 인한 메모리 낭비를 훨씬 적게 만듭니다. 할당자 마이크로 벤치마크보다는 장기적인 성능과 메모리 사용에 중점을 두고 적절한 전반적 성능을 제공하는 것을 목표로 합니다. 구성 가능한 수의 완전히 독립적인 arena를 통해 확장성을 제공하며, arena 내부의 잠금은 크기 클래스별로 더 세분화됩니다.
이 프로젝트는 현재 Bionic (Android), musl 및 glibc를 지원합니다. 향후 다른 비-Linux 운영 체제를 지원할 수도 있습니다. Android의 경우 사용자 정의 통합과 다른 강화 기능이 있으며, 이는 향후 musl에도 계획되어 있습니다. glibc 지원은 malloc 구현을 대체하는 것으로 제한될 것인데, musl이 훨씬 더 견고하고 깔끔한 기반이며 동일한 사용 사례를 커버할 수 있기 때문입니다.
이 할당자는 OpenBSD malloc을 다양한 추가 보안 기능으로 확장한 이전 구현의 후속작으로 의도되었습니다. 여전히 OpenBSD malloc 설계에 크게 기반하고 있지만, 해시 테이블 구현을 재사용하는 것 외에는 기존 코드에 기반하지 않습니다. 설계의 주요 차이점은 버그를 찾는 것보다 강화에만 초점을 맞춘다는 점, 내부 단편화를 줄이기 위해 더 세분화된 크기 클래스와 4k를 초과하는 slab 크기를 사용한다는 점, 커널의 세밀한 mmap 무작위화에 의존하지 않는다는 점, 그리고 큰 주소 공간을 적극적으로 활용하기 위해 64비트만 대상으로 한다는 점입니다. 구현 접근 방식에는 많은 작은 차이점이 있습니다. 이는 카나리아를 위한 할당 패딩 추가(현재 OpenBSD malloc 카나리아와는 다름), 기존의 해제 시 초기화와 연계된 해제 후 쓰기 감지, 할당 격리를 위한 기존 무작위화된 배열과 함께하는 큐, 그리고 격리된 할당에 대한 적절한 이중 해제 감지를 포함하여 OpenBSD malloc에 가해진 이전 확장들을 통합합니다. 자체 무작위 기반을 가진 크기 클래스별 메모리 영역은 PartitionAlloc의 크기 및 유형 기반 분할에서 느슨하게 영감을 받았습니다. OpenBSD malloc에 대한 계획된 변경 사항은 결국 너무 광범위하고 침습적이 되어, 목표를 더 잘 달성할 수 있는 새로운 구현으로 이 프로젝트가 시작되었습니다. 32비트의 경우, 이 할당자가 근본적으로 해당 환경을 지원하지 않으므로 작은 확장을 가한 OpenBSD malloc의 포팅을 대신 사용할 수 있습니다.
Debian stable (현재 Debian 13)이 지원되는 가장 오래된 의존성 집합을 결정합니다:
Android의 경우 Linux GKI 6.1, 6.6 및 6.12 브랜치가 지원됩니다.
그러나 더 최신 릴리스를 사용할 것을 강력히 권장합니다. 의존성의 이전 버전은 현재 호환될 수 있지만 테스트되지 않았으며 명시적으로 지원되지 않습니다.
musl을 사용한 외부 malloc 대체에는 musl 1.1.20이 필요합니다. 다만, 향후 C 표준 라이브러리 구현에 대한 다른 하드닝과 함께 더 나은 성능을 제공하는 사용자 정의 통합이 있을 예정입니다.
Android의 경우, 현재 세대의 활발히 개발 중인 Android Open Source Project 유지보수 브랜치만 지원되며, 현재로써는 android17-release를 의미합니다.
preload.sh 스크립트는 glibc 또는 musl을 사용하는 동적 링크 실행 파일과의 테스트에 사용할 수 있습니다:
./preload.sh krita --new-image RGBA,U8,500,500
가드 슬랩과 대형 할당 가드 영역으로 인해 발생하는 대량의 매핑을 수용하려면 vm.max_map_count sysctl을 상당히 높여야 할 수 있습니다. CONFIG_GUARD_SLABS_INTERVAL을 크게 높여 매핑 수를 대폭 줄일 수도 있지만, 이 기능은 성능 및 메모리 사용 비용이 낮으므로 권장되지 않습니다.
C 표준 라이브러리에 통합하면 약간 더 나은 성능을 제공할 수 있으며, C 표준 라이브러리 및 동적 링커 구현 내에서 유사한 하드닝을 위한 다른 기회도 있습니다. 예를 들어, 이 할당자가 서로 다른 크기 클래스에 걸쳐 제공하는 것과 유사한 격리를 동적 라이브러리에 제공하도록 라이브러리 영역을 구현할 수 있습니다. 이는 Bionic 및 musl C 표준 라이브러리의 하드닝된 변형의 일부로 제공될 예정입니다.
간단한 자동화 테스트 모음이 제공되며, 다음과 같이 make 명령으로 실행할 수 있습니다:
make test
seccomp-bpf 필터에서 프로세스를 종료하는 대신 mprotect PROT_READ|PROT_WRITE 시스템 호출을 허용하려면 OpenSSH 8.1 이상이 필요합니다.
GrapheneOS에서는 hardened_malloc이 표준 malloc 구현으로서 표준 C 라이브러리에 통합되어 있습니다. 다른 Android 기반 운영 체제는 통합 코드를 재사용하여 이를 제공할 수 있습니다. 원한다면 hardened_malloc을 조건부로만 사용하여 jemalloc을 런타임 구성 옵션으로 남겨두고 사용자가 성능과 보안 중에서 선택할 수 있게 할 수 있습니다. 그러나 이는 영구 상태가 신뢰되지 않는 위협 모델, 즉 검증된 부팅 및 증명(attestation)에 대한 보안을 감소시킵니다(증명 자매 프로젝트 참조).
hardened_malloc이 생성하는 매우 많은 수의 가드 페이지를 수용하려면 vm.max_map_count도 상당히 높여야 합니다. 이는 init.rc(system/core/rootdir/init.rc)에서 다른 가상 메모리 구성 근처에 다음과 같이 설정할 수 있습니다:
write /proc/sys/vm/max_map_count 1048576
빌드 구성에서 CONFIG_GUARD_SLABS_INTERVAL을 매우 큰 값으로 설정하면 이는 불필요합니다.
전통적인 Linux 기반 운영 체제에서는 hardened_malloc을 표준 malloc 구현의 대체로서 libc 구현에 통합하거나 동적 라이브러리로 로드할 수 있습니다. 각 실행 파일을 이에 링크되도록 다시 빌드하는 대신, /etc/ld.so.preload에 프리로드 라이브러리로 추가할 수 있습니다. 예를 들어, libhardened_malloc.so가 /usr/local/lib/libhardened_malloc.so에 설치된 경우, 해당 전체 경로를 /etc/ld.so.preload 구성 파일에 한 줄로 추가합니다:
/usr/local/lib/libhardened_malloc.so
이 구성 파일의 형식은 공백으로 구분된 목록이므로, 각 라이브러리를 별도의 줄에 두는 것이 좋은 관행입니다.
최대 호환성을 위해 libhardened_malloc.so를 /usr/lib/에 설치하여 AppArmor 프로필이나 systemd ExecPaths= 제한으로 인한 프리로드 실패를 피할 수 있습니다. 다음과 같은 형식의 로그를 확인하십시오:
ERROR: ld.so: object '/usr/local/lib/libhardened_malloc.so' from /etc/ld.so.preload cannot be preloaded (failed to map segment from shared object): ignored.
LD_PRELOAD 환경 변수를 사용하여 사례별로 로드하는 방식은 setuid 바이너리와 같이 AT_SECURE가 설정된 경우에는 작동하지 않습니다. 또한 일반적으로 프로덕션 용도로 권장되는 접근 방식도 아닙니다. 권장되는 방법은 전역적으로 활성화하고, 성능이 중요한 경우에는 해당 기능을 활성화하지 않은 컨테이너/네임스페이스에서 애플리케이션을 실행하여 예외를 두는 것입니다.
hardened_malloc이 생성하는 매우 많은 수의 가드 페이지를 수용할 수 있도록 vm.max_map_count도 상당히 높여야 합니다. 예를 들어 /etc/sysctl.d/hardened_malloc.conf에서 다음과 같이 설정합니다:
vm.max_map_count = 1048576
빌드 구성에서 CONFIG_GUARD_SLABS_INTERVAL을 매우 큰 값으로 설정한 경우에는 이 설정이 필요하지 않습니다.
arm64에서는 아직 16k 및 64k 페이지에 대한 지원을 추가하지 않았으므로 커널이 4k 페이지를 사용하도록 구성되어 있는지 확인하세요. 또한 커널은 기본 hardened_malloc 구성의 39비트 주소 공간만이 아니라 전체 48비트 주소 공간을 위해 4단계 페이지 테이블을 사용하도록 구성되어야 합니다. 클래스 영역 크기를 상당히 줄여 39비트 주소 공간에서도 작동하게 만드는 것은 가능하지만 기본값으로는 작동하지 않습니다.
다음과 같이 make 명령에 인자를 전달하여 컴파일 시점에 일부 구성 옵션을 설정할 수 있습니다:
make CONFIG_EXAMPLE=false
구성 옵션은 이식성, 성능, 메모리 사용량 또는 보안 사이에 상당한 절충이 있을 때 제공됩니다. 핵심 설계 선택은 구성할 수 없으며, 모든 선택적 기능을 비활성화하더라도 할당자는 여전히 보안에 매우 중점을 둡니다.
구성 시스템은 두 가지 표준 프리셋이 있는 구성 템플릿 시스템을 지원합니다: 기본 구성(config/default.mk)과 경량 구성(config/light.mk)입니다. 패키저는 표준 default 및 light 구성을 모두 제공할 것을 강력히 권장합니다. make VARIANT=light를 사용하여 빌드할 구성을 선택할 수 있으며, make VARIANT=default는 make와 동일합니다. 기본이 아닌 구성 템플릿은 libhardened_malloc-light.so와 같이 -variant 접미사가 붙은 라이브러리를 빌드하며, 빌드에는 out 대신 out-variant 디렉터리를 사용합니다.
default 구성 템플릿은 모든 일반적인 선택적 보안 기능이 활성화되어 있으며(틈새 기능인 CONFIG_SEAL_METADATA만 제외), 보안을 위해 성능과 메모리 사용량을 희생하는 측면에서 상당히 공격적입니다. light 구성 템플릿은 슬랩 격리, 해제 후 쓰기 검사, 슬롯 무작위화를 비활성화하고 가드 슬랩 간격을 1에서 8로 높이지만, 해제 시 0으로 채우기와 슬랩 카나리는 활성화된 상태로 둡니다. light 구성은 견고한 성능과 메모리 사용량을 유지하면서도 주류 할당자보다 훨씬 더 안전하며 보안 속성이 훨씬 우수합니다. 해제 시 0으로 채우기를 비활성화하면 성능을 더 얻을 수 있지만, 슬랩 카나리도 함께 비활성화하지 않는 한 작은 할당에서는 큰 차이가 없습니다. 슬랩 카나리는 메모리 사용량을 약간 높이고 성능을 약간 저하시키지만, 작은 오버플로와 C 문자열 오버플로를 완화하는 데 매우 중요합니다. 슬랩 카나리를 비활성화하는 것은 할당에 헤더를 두고 기본적인 일관성 검사를 수행하는 전통적인 할당자보다 엄격한 업그레이드가 아니게 되므로 대부분의 경우 권장되지 않습니다.
성능을 희생하여 메모리 사용량을 줄이려면(이렇게 하면 빈 슬랩 캐시와 격리의 크기도 줄어들어 많은 메모리를 절약할 수 있습니다. 현재 이들은 가장 큰 크기 클래스의 크기를 기준으로 하기 때문입니다):
make \
CONFIG_N_ARENA=1 \
CONFIG_EXTENDED_SIZE_CLASSES=false
다음과 같은 불리언 구성 옵션을 사용할 수 있습니다: