보안을 위해 힙이 0으로 초기화되면 왜 스택이 초기화되지 않습니까? free(b); return

데비안 GNU / 리눅스 9 시스템에서 바이너리가 실행될 때

  • 스택은 초기화되지 않았지만
  • 힙은 0으로 초기화됩니다.

왜?

나는 0으로 초기화하면 보안을 촉진한다고 가정하지만 힙의 경우 스택도 아닌 이유는 무엇입니까? 스택에도 보안이 필요하지 않습니까?

내 질문은 내가 아는 한 데비안에만 국한된 것은 아닙니다.

샘플 C 코드 :

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

const size_t n = 8;

// --------------------------------------------------------------------
// UNINTERESTING CODE
// --------------------------------------------------------------------
static void print_array(
  const int *const p, const size_t size, const char *const name
)
{
    printf("%s at %p: ", name, p);
    for (size_t i = 0; i < size; ++i) printf("%d ", p[i]);
    printf("\n");
}

// --------------------------------------------------------------------
// INTERESTING CODE
// --------------------------------------------------------------------
int main()
{
    int a[n];
    int *const b = malloc(n*sizeof(int));
    print_array(a, n, "a");
    print_array(b, n, "b");
    free(b);
    return 0;
}

산출:

a at 0x7ffe118997e0: 194 0 294230047 32766 294230046 32766 -550453275 32713
b at 0x561d4bbfe010: 0 0 0 0 0 0 0 0

C 표준은 malloc()물론 메모리를 할당하기 전에 메모리를 지우 도록 요구하지는 않지만 C 프로그램은 단지 설명을위한 것입니다. 이 질문은 C 나 C의 표준 라이브러리에 관한 질문이 아닙니다. 오히려, 질문은 왜 커널 및 / 또는 런타임 로더가 스택을 제외하고 힙을 제로화하는지에 대한 질문입니다.

다른 실험

내 질문은 표준 문서의 요구 사항이 아닌 관찰 가능한 GNU / Linux 동작에 관한 것입니다. 무슨 뜻인지 확실하지 않은 경우이 코드를 사용하면 추가 정의되지 않은 동작 ( 정의되지 않은, 즉 C 표준에 관한 한)을 호출 하여 요점을 설명 할 수 있습니다.

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

const size_t n = 4;

int main()
{
    for (size_t i = n; i; --i) {
        int *const p = malloc(sizeof(int));
        printf("%p %d ", p, *p);
        ++*p;
        printf("%d\n", *p);
        free(p);
    }
    return 0;
}

내 기계에서 출력 :

0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1
0x555e86696010 0 1

C 표준에 관한 한, 행동은 정의되어 있지 않으므로 제 질문은 C 표준을 고려하지 않습니다. 호출 할 malloc()때마다 동일한 주소를 반환 할 필요는 없지만이 호출 malloc()은 매번 같은 주소를 반환하기 때문에 힙에있는 메모리가 매번 0으로 표시됩니다.

대조적으로 스택은 0으로 보이지 않았습니다.

GNU / Linux 시스템의 어떤 계층이 관찰 된 동작을 일으키는 지 알 수 없기 때문에 후자의 코드가 시스템에서 무엇을할지 모르겠습니다. 시도해 볼 수 있습니다.

최신 정보

@Kusalananda는 의견에서 관찰했습니다.

가치가있는 것에 대해 가장 최근의 코드는 OpenBSD에서 실행될 때 다른 주소와 (때로는) 초기화되지 않은 (0이 아닌) 데이터를 반환합니다. 이것은 분명히 당신이 리눅스에서 목격하는 행동에 대해 아무 말도하지 않습니다.

내 결과가 OpenBSD의 결과와 다르다는 것은 실제로 흥미 롭습니다. 분명히, 내 실험은 내가 생각한 것처럼 커널 (또는 링커) 보안 프로토콜이 아니라 단순한 구현 아티팩트를 발견했습니다.

이 관점에서, @mosvy, @StephenKitt 및 @AndreasGrapentin의 아래 답변이 내 질문을 해결했다고 믿습니다.

스택 오버 플로우 : malloc이 gcc에서 0으로 값을 초기화하는 이유 도 참조하십시오 . (신용 : @bta).



답변

malloc ()에 의해 리턴 된 저장 영역은 0으로 초기화 되지 않았습니다 . 절대로 가정하지 마십시오.

테스트 프로그램에서, 그것은 단지 우연이다 : 나는 추측 malloc()단지 새로운 블록을 가지고 mmap()하나,하지만에 의존하지 않습니다.

예를 들어, 내 컴퓨터에서 다음과 같이 프로그램을 실행하면 :

$ echo 'void __attribute__((constructor)) p(void){
    void *b = malloc(4444); memset(b, 4, 4444); free(b);
}' | cc -include stdlib.h -include string.h -xc - -shared -o pollute.so

$ LD_PRELOAD=./pollute.so ./your_program
a at 0x7ffd40d3aa60: 1256994848 21891 1256994464 21891 1087613792 32765 0 0
b at 0x55834c75d010: 67372036 67372036 67372036 67372036 67372036 67372036 67372036 67372036

두 번째 예는 mallocglibc 에서 구현 의 아티팩트를 노출시키는 것입니다 . 8 바이트보다 큰 버퍼로 반복 malloc/ 반복 free하면 다음 샘플 코드에서와 같이 처음 8 바이트 만 0으로 표시됩니다.

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

const size_t n = 4;
const size_t m = 0x10;

int main()
{
    for (size_t i = n; i; --i) {
        int *const p = malloc(m*sizeof(int));
        printf("%p ", p);
        for (size_t j = 0; j < m; ++j) {
            printf("%d:", p[j]);
            ++p[j];
            printf("%d ", p[j]);
        }
        free(p);
        printf("\n");
    }
    return 0;
}

산출:

0x55be12864010 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1 0:1
0x55be12864010 0:1 0:1 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2 1:2
0x55be12864010 0:1 0:1 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3 2:3
0x55be12864010 0:1 0:1 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4 3:4


답변

스택이 초기화되는 방법에 관계없이 C 라이브러리는 호출하기 전에 많은 작업을 수행 main하고 스택에 닿기 때문에 원시 스택이 표시되지 않습니다 .

GNU C 라이브러리와, – 64에, 실행 상기 시작 _start의 호출 진입 점, __libc_start_main세트 일까지, 및 호출까지 후자의 끝 main. 그러나를 호출하기 전에 main여러 다른 함수를 호출하여 다양한 데이터 조각을 스택에 씁니다. 스택의 내용은 함수 호출 사이에서 지워지지 않으므로에 들어가면 main스택에 이전 함수 호출의 남은 내용이 포함됩니다.

이것은 스택에서 얻은 결과만을 설명하며 일반적인 접근 방식과 가정에 관한 다른 답변을 참조하십시오.


답변

두 경우 모두 초기화되지 않은 메모리 를 얻게 되며 그 내용에 대해 어떤 가정도 할 수 없습니다.

OS가 프로세스에 새 페이지를 할당해야하는 경우 (스택에 사용 된 영역이든 또는 사용중인 영역이든 malloc()) 다른 프로세스의 데이터가 노출되지 않도록합니다. 그것을 보장하는 일반적인 방법은 0으로 채우는 것입니다 (그러나 페이지 가치를 포함하여 다른 것으로 덮어 쓰는 것도 똑같이 유효합니다 /dev/urandom-실제로 일부 디버깅 malloc()구현은 0과 다른 패턴을 작성하여 실수와 같은 잘못된 가정을 포착합니다).

malloc()이 프로세스에서 이미 사용하고 해제 한 메모리의 요청을 만족시킬 수 있으면 내용이 지워지지 않습니다 (사실 지우기와 관련이 없으며 수행 malloc()할 수 없습니다)-메모리가 맵핑되기 전에 발생해야합니다. 주소 공간). 프로세스 / 프로그램에 의해 이전에 작성된 메모리가있을 수 있습니다 (예 : 이전 main()).

예제 프로그램 malloc()에서이 프로세스에 의해 아직 작성되지 않은 영역 (즉, 새 페이지에서 직접 작성 됨)과 ( main()프로그램의 사전 코드에 의해 작성된) 스택이 표시됩니다. 더 많은 스택을 살펴보면 성장 방향으로 0으로 채워져 있음을 알 수 있습니다.

OS 수준에서 무슨 일이 일어나고 있는지 이해하려면 C 라이브러리 계층을 우회 brk()하고 mmap()대신 시스템 호출을 사용하여 상호 작용하는 것이 좋습니다.


답변

당신의 전제는 잘못되었습니다.

‘보안’이라고 설명하는 것은 실제로 기밀성입니다 . 즉, 프로세스간에 메모리가 명시 적으로 공유되지 않으면 프로세스가 다른 프로세스 메모리를 읽을 수 없습니다. 운영 체제에서 이는 동시 활동 또는 프로세스 격리 의 한 측면입니다 .

운영 체제가이 격리를 보장하기 위해 수행하는 작업은 힙 또는 스택 할당 프로세스에서 메모리가 요청 될 때마다 0으로 채워지는 실제 메모리의 영역에서 가져 오거나 같은 과정 에서 나옵니다 .

당신이 오직 비밀이 보장되도록, 제로, 또는 자신의 쓰레기를보고하고, 그이 보장하지만 모두 반드시 (제로)가 초기화이기는하지만 스택이는 ‘보안’.

측정 값을 너무 많이 읽고 있습니다.


답변