Skip to content

secure_buffer, rng, base64 보안성, ffi 상호 작용 및 효율성 검토 #2

@Quant-TheodoreFelix

Description

@Quant-TheodoreFelix

각 모듈에 대해 좀 더 기술적인 보안성 검토를 해봤습니다. 지금도 이 네이티브는 탄탄하지만 좀 더 강화할 수 있어 보입니다.

Base64 및 TRNG

  1. Base64 디코딩 로직의 상수 시간(constant-time) 위반 및 타이밍 공격(timing attack) 노출
  2. 하드웨어 난수 생성기(trng) 폴백(fallback) 로직의 주석 불일치

base64_ffi.rsentlib_b64_decode_secure 함수는 상수 시간 연산을 목표로 설계되었으나, 루프 내부에 데이터 종속적인 분기문이 존재하여 타이밍 공격에 취약해질 수 있어 보입니다.

첫 번째 문제

현재 구현의 문제점이라 생각되는 부분: if is_valid_char == 0xFF 구문은 입력된 바이트가 유효한 문자인지 공백/에러인지에 따라 실행 흐름이 갈라지는 명시적 분기를 생성하고 있습니다. 또한 분기 내부에서 out.push(...)를 호출하고 있어요. Vec::push는 내부적으로 용량을 초과할 경우 재할당(reallocation)을 트리거하기 때문에 특정 입력 패턴에 따라 실행 시간이 기하급수적으로 튀는 현상이 발생합니다.

어떻게 해결할건지에 대한 아이디어: 완벽한 상수-시간 디코딩을 위해서는 입력 길이에 비례하는 고정 크기의 버퍼를 미리 할당하고, 조건부 분기 대신 비트 마스킹을 사용해 더미 데이터와 실제 데이터를 덮어쓰는 방식(oblivious write)으로 구현해야 할 것 같습니다. 에러 상태나 최종 길이는 루프가 끝난 뒤 산출해야 합니다.

두 번째 문제

base_rng.rsget_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 힙 할당 오버헤드 가능성 및 에러 플래그

  1. 잦은 Rust 힙 할당(heap allocation)으로 인한 오버헤드 가능성
  2. 에러 플래그(error flag) 전달 방식의 한계

첫 번째 문제

암호화 연산이 빈번하게 일어나는 서버 환경에서는 매 호출마다 Rust 측의 Heap 할당이 발생합니다. 이후 Java에서 포인터를 소유하다가 entlib_secure_buffer_free를 호출해 해제해야 하는데, FFI 경계를 넘나드는 객체 생명주기 관리 비용이 크게 증가합니다.

개선 방안: 커밋하진 않았지만 이미 secure_buffer_ffi.rsentanglement_secure_wipe 함수와 FFIExternalSecureBuffer 구조체를 구현해놨습니다. 이를 적극 활용하여 호출자 할당 패턴으로 구조를 변경하는 것이 효율적일 것 같습니다. Java FFM API의 Arena를 사용해 Off-Heap 메모리를 미리 할당하고, Rust 측에는 *mut u8 포인터와 용량만 전달하여 Rust는 연산 결과를 해당 버퍼에 쓰기만 하도록(write-only) FFI 시그니처를 수정하여 개선하겠습니다.

두 번째 문제

현재는 에러 상태를 확인하기 위해 *mut u8 err_flag를 사용하고 있습니다.

이 방식은 꽤 간단히 개선할 수 있어 보입니다. 포인터를 통해 에러를 기록하는 방식은 ffi 호출 시 Java 측에서 매번 에러 버퍼를 준비해야 하는 번거로움이 있습니다. 함수의 반환 타입을 intlong 등으로 변경해서 양수일 경우 처리된 데이터의 길이를, 음수일 경우 에러 코드를 반환하는 C 스타일의 ssize_t 반환 패턴을 적용하면 Java 링커 레이어에서의 호출 코드가 훨씬 간결해지겠네요.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions