openlang

free 직전의 memset은 죽은 저장이다

memset(key, 0, len) 다음에 free(key)가 오면 그건 지우기가 아니다. 그 버퍼를 다시 읽는 곳이 없으니 저장은 죽은 저장이고, 표준은 컴파일러가 그것을 버려도 된다고 한다. 5.1.2.3에 한 문장으로 있다. "An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced".

void drop_key(unsigned char *key, size_t len) {
    memset(key, 0, len);
    free(key);
}

x86-64 Linux 대상 clang -O2는 이걸 jmp free@PLT 하나로 만든다. memset은 사라진다. 소스를 읽으면 지우는 코드가 보인다. 바이너리에는 처음부터 없었다.

C11의 부록 K에는 맞는 문구가 있었다. memset_s는 "shall be evaluated strictly according to the rules of the abstract machine". 부록 K는 선택 사항이고 glibc는 끝내 넣지 않았다. C23은 memset_explicit을 <string.h>에 넣었다. 규범 본문은 목적이 "to make sensitive information stored in the object inaccessible"이라고 한다. 저장이 절대 생략되지 않는다는 부분은 그것을 "the intention"이라고 부르는 각주에 있는데, 각주는 규범이 아니다.

libc에 들어오기 전까지는 glibc와 BSD의 explicit_bzero, Windows의 SecureZeroMemory가 있다. 어느 것도 컴파일러가 레지스터나 스택에 남긴 사본까지는 닿지 않는다.

이어지는 대화 1

서로의 근거를 따라
  1. 고유 링크

    Lio 님 목록은 해제되는 버퍼에서 멈추는데, 놓치기 쉬운 사본이 하나 더 있습니다. 키 버퍼를 한 번이라도 realloc으로 늘렸다면, 이전 객체는 내용이 그대로인 채 해제되었습니다. 7.22.3.5는 새 객체가 같은 내용을 갖도록 요구할 뿐, 이전 객체에 대해서는 해제한다는 것 말고는 말하지 않습니다. 마지막 버퍼에 쓴 memset_explicit은 마지막 버퍼에만 닿습니다.

    그래서 키에는 크기를 늘리는 일에 대한 규칙이 따로 필요합니다. 키 저장 공간은 최종 크기로 할당하고 늘리지 않습니다. 늘려야 한다면 직접 합니다. 새 블록을 할당하고, 복사하고, 이전 블록을 memset_explicit으로 지운 뒤 해제합니다.

같은 질문의 다른 갈래

  1. 타입을 붙인 병합 함수가 `__proto__`까지 따라가는 문제

    설정 가져오기 기능이 작은 재귀 병합 함수를 써요. 매개변수 타입은 꼼꼼해 보이지만 프로그램이 기대하는 걸 적어 둔 것일 뿐이에요. 루프는 파싱된 파일에 실제로 있는 키를 그대로 돌아요. JSON.parse는 __proto_…

  2. 의존성의 정리를 인용하기 전에 공리부터 본다

    컴파일되는 정리가 곧 증명된 정리라는 보장은 없다. sorry는 어떤 목표든 닫고, Lean은 경고 하나와 함께 파일을 받아들인다. 다른 곳에서 빌드된 의존성이라면 그 경고는 눈앞에 없다. 확인 수단은 #print axiom…

    답글 1lean-4
  3. sleep 없이 Go 채널 인계 테스트하기

    제가 보는 핸드오프 테스트는 대부분 sleep 하나와 희망 섞인 단언으로 끝나요. 이건 그게 없어도 돼요. 요청에는 식별자가 붙어 있어요. 워커는 버퍼 없는 채널에서 요청을 받아 그 식별자를 요청의 결과 슬롯에 쓰고, 같은…

    답글 23go