-
Notifications
You must be signed in to change notification settings - Fork 1
Description
각 모듈에 대해 좀 더 기술적인 보안성 검토를 해봤습니다. 지금도 이 네이티브는 탄탄하지만 좀 더 강화할 수 있어 보입니다.
Base64 및 TRNG
- Base64 디코딩 로직의 상수 시간(constant-time) 위반 및 타이밍 공격(timing attack) 노출
- 하드웨어 난수 생성기(trng) 폴백(fallback) 로직의 주석 불일치
base64_ffi.rs의 entlib_b64_decode_secure 함수는 상수 시간 연산을 목표로 설계되었으나, 루프 내부에 데이터 종속적인 분기문이 존재하여 타이밍 공격에 취약해질 수 있어 보입니다.
첫 번째 문제
현재 구현의 문제점이라 생각되는 부분: if is_valid_char == 0xFF 구문은 입력된 바이트가 유효한 문자인지 공백/에러인지에 따라 실행 흐름이 갈라지는 명시적 분기를 생성하고 있습니다. 또한 분기 내부에서 out.push(...)를 호출하고 있어요. Vec::push는 내부적으로 용량을 초과할 경우 재할당(reallocation)을 트리거하기 때문에 특정 입력 패턴에 따라 실행 시간이 기하급수적으로 튀는 현상이 발생합니다.
어떻게 해결할건지에 대한 아이디어: 완벽한 상수-시간 디코딩을 위해서는 입력 길이에 비례하는 고정 크기의 버퍼를 미리 할당하고, 조건부 분기 대신 비트 마스킹을 사용해 더미 데이터와 실제 데이터를 덮어쓰는 방식(oblivious write)으로 구현해야 할 것 같습니다. 에러 상태나 최종 길이는 루프가 끝난 뒤 산출해야 합니다.
두 번째 문제
base_rng.rs의 get_hw_random_u64 함수(x86_64 아키텍처) 주석에는 "실패 시 rdrand 명령어로 폴백합니다." 라고 적어뒀는데 지금 상태는 rdseed 실패 시 rdrand를 호출하지 않고 명시적으로 RngError::EntropyDepletion을 반환하고 있습니다. ㅎㅎ... 명확히 이건 제 실숩니다!
좀 더 엄밀히 검토해봤고, 군사적 수준의 보안이나 PQC를 위한 시드 생성 시, 엔트로피가 보장되지 않는 rdrand로 폴백하지 않고 실패를 명시하는 현재의 코드 구현이 보안상 훨씬 올바른 방향이라고 생각됩니다. 다음 커밋에 이 문제를 해결하여 정책을 명확히 하겠습니다.
Java FFI 상호 작용 및 효율성
현재 entlib_b64_encode_secure, entlib_b64_decode_secure, entlib_rng_hw_generate, entlib_rng_mixed_generate 등 대부분의 ffi 함수가 내부적으로 Vec<u8>을 생성한 뒤 Box::into_raw(Box::new(secure_buf))를 통해 불투명 포인터를 Java로 반환하고 있습니다. #1 에서 다룬 문제고, 해결 방안을 모색 중입니다.
Rust 힙 할당 오버헤드 가능성 및 에러 플래그
- 잦은 Rust 힙 할당(heap allocation)으로 인한 오버헤드 가능성
- 에러 플래그(error flag) 전달 방식의 한계
첫 번째 문제
암호화 연산이 빈번하게 일어나는 서버 환경에서는 매 호출마다 Rust 측의 Heap 할당이 발생합니다. 이후 Java에서 포인터를 소유하다가 entlib_secure_buffer_free를 호출해 해제해야 하는데, FFI 경계를 넘나드는 객체 생명주기 관리 비용이 크게 증가합니다.
개선 방안: 커밋하진 않았지만 이미 secure_buffer_ffi.rs에 entanglement_secure_wipe 함수와 FFIExternalSecureBuffer 구조체를 구현해놨습니다. 이를 적극 활용하여 호출자 할당 패턴으로 구조를 변경하는 것이 효율적일 것 같습니다. Java FFM API의 Arena를 사용해 Off-Heap 메모리를 미리 할당하고, Rust 측에는 *mut u8 포인터와 용량만 전달하여 Rust는 연산 결과를 해당 버퍼에 쓰기만 하도록(write-only) FFI 시그니처를 수정하여 개선하겠습니다.
두 번째 문제
현재는 에러 상태를 확인하기 위해 *mut u8 err_flag를 사용하고 있습니다.
이 방식은 꽤 간단히 개선할 수 있어 보입니다. 포인터를 통해 에러를 기록하는 방식은 ffi 호출 시 Java 측에서 매번 에러 버퍼를 준비해야 하는 번거로움이 있습니다. 함수의 반환 타입을 int나 long 등으로 변경해서 양수일 경우 처리된 데이터의 길이를, 음수일 경우 에러 코드를 반환하는 C 스타일의 ssize_t 반환 패턴을 적용하면 Java 링커 레이어에서의 호출 코드가 훨씬 간결해지겠네요.