최소 스택 할당

최소 스택 할당

ELF 사양을 공부하고 있습니다 (http://www.skyfree.org/linux/references/ELF_Format.pdf) 그리고 프로그램 로딩 프로세스에 대해 나에게 명확하지 않은 점은 스택이 초기화되는 방식과 초기 페이지 크기가 무엇인지입니다. 테스트는 다음과 같습니다(Ubuntu x86-64).

$ cat test.s
.text
  .global _start
_start:
  mov $0x3c,%eax
  mov $0,%edi
  syscall
$ as test.s -o test.o && ld test.o
$ gdb a.out -q
Reading symbols from a.out...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x400078
(gdb) run
Starting program: ~/a.out 

Breakpoint 1, 0x0000000000400078 in _start ()
(gdb) print $sp
$1 = (void *) 0x7fffffffdf00
(gdb) info proc map
process 20062
Mapped address spaces:

          Start Addr           End Addr       Size     Offset objfile
            0x400000           0x401000     0x1000        0x0 ~/a.out
      0x7ffff7ffa000     0x7ffff7ffd000     0x3000        0x0 [vvar]
      0x7ffff7ffd000     0x7ffff7fff000     0x2000        0x0 [vdso]
      0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
  0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

ELF 사양에는 애초에 이 스택 페이지가 어떻게, 왜 존재하는지에 대해 언급한 내용이 거의 없지만, argv, envp 및 바로 위의 보조 벡터를 사용하여 argc를 가리키는 SP로 스택을 초기화해야 한다는 참조를 찾을 수 있습니다. 그리고 나는 이것을 확인했습니다. 그런데 SP 아래에는 얼마나 많은 공간을 사용할 수 있습니까? 내 시스템에는 SP 아래에 매핑된 바이트가 있지만 0x1FF00아마도 이것은 스택의 맨 위에서 카운트다운 되고 전체 매핑에 바이트 0x7ffffffff000가 있습니다 . 0x21000이 숫자에 어떤 영향을 미치나요?

나는 스택 바로 아래에 있는 페이지가 자동으로 쓰기 가능해지고 내가 페이지에 쓰면 "스택 아래로 자라는" "가드 페이지"라는 것을 알고 있습니다(아마 순진한 스택 처리가 "그냥 작동"하도록). 거대한 스택 프레임을 사용하면 가드 페이지와 세그폴트를 초과할 수 있으므로 프로세스 시작 시에 나에게 이미 적절하게 할당된 공간이 얼마나 되는지 확인하고 싶습니다.

편집하다: 데이터가 더 많아지면 무슨 일이 일어나고 있는지 더욱 확신할 수 없게 됩니다. 테스트는 다음과 같습니다.

.text
  .global _start
_start:
  subq $0x7fe000,%rsp
  movq $1,(%rsp)
  mov $0x3c,%eax
  mov $0,%edi
  syscall

0x7fe000나는 무슨 일이 일어나는지 보기 위해 여기서 상수의 다른 값을 가지고 놀았고 , 이 값에 대해 세그폴트가 발생하는지 여부는 결정적이지 않습니다. GDB에 따르면, subq명령어 자체가 mmap의 크기를 확장할 것입니다. 이것은 나에게 미스터리입니다(리눅스는 내 레지스터에 무엇이 있는지 어떻게 알 수 있습니까?). 그러나 이 프로그램은 일반적으로 어떤 이유로 종료 시 GDB와 충돌합니다. GOT나 PLT 섹션을 사용하지 않기 때문에 ASLR이 비결정성을 유발할 수 없습니다. 실행 파일은 항상 가상 메모리의 동일한 위치에 로드됩니다. 그렇다면 이것은 PID 또는 물리적 메모리의 무작위성입니까? 전체적으로 나는 실제로 무작위 액세스에 합법적으로 사용할 수 있는 스택의 양과 RSP를 변경하거나 합법적인 메모리의 "범위를 벗어난" 영역에 쓸 때 얼마나 많은 스택이 요청되는지에 대해 매우 혼란스럽습니다.

답변1

나는 이 질문이 실제로 ELF와 관련이 있다고 생각하지 않습니다. 내가 아는 한, ELF는 "플랫 팩"프로그램 이미지를 파일로 만든 다음 첫 번째 실행을 위해 다시 조립합니다. 스택이 무엇인지, 어떻게 구현되는지에 대한 정의는 OS 동작이 POSIX로 향상되지 않은 경우 CPU 특정과 OS 특정 사이 어딘가에 있습니다. 의심의 여지 없이 ELF 사양은 스택에 필요한 사항에 대해 몇 가지 요구 사항을 제시합니다.

최소 스택 할당

귀하의 질문에서 :

나는 스택 바로 아래에 있는 페이지가 자동으로 쓰기 가능해지고 내가 페이지에 쓰면 "스택 아래로 자라는" "가드 페이지"라는 것을 알고 있습니다(아마 순진한 스택 처리가 "그냥 작동"하도록). 거대한 스택 프레임을 사용하면 가드 페이지와 세그폴트를 초과할 수 있으므로 프로세스 시작 시에 나에게 이미 적절하게 할당된 공간이 얼마나 되는지 확인하고 싶습니다.

이에 대한 권위 있는 참고 자료를 찾기 위해 고심하고 있습니다. 그러나 나는 이것이 틀렸다고 제안할 만큼 신뢰할 수 없는 참조가 충분히 많다는 것을 발견했습니다.

내가 읽은 바에 따르면 가드 페이지는 "정상적인" 스택 증가가 아닌 최대 스택 할당 외부의 액세스를 잡는 데 사용됩니다. 실제 메모리 할당(페이지를 메모리 주소에 매핑)은 요청 시 수행됩니다. 즉, 스택 베이스와 스택 베이스(max-stack-size + 1) 사이에 있는 메모리의 매핑되지 않은 주소에 액세스하면 CPU에 의해 예외가 트리거될 수 있지만 커널은 페이지를 매핑하여 예외를 처리합니다. 분할 오류가 계단식으로 발생하지 않는 메모리입니다.

따라서 최대 할당 내 스택에 액세스해도 분할 오류가 발생해서는 안 됩니다. 당신이 발견한 대로

최대 스택 할당

문서 조사는 스레드 생성 및 이미지 로딩에 대한 Linux 문서의 라인을 따라야 합니다(포크(2),클론(2),execve(2)). execve 문서흥미로운 내용을 언급합니다.

인수 및 환경의 크기 제한

...한조각...

커널 2.6.23 이상에서 대부분의 아키텍처는 소프트에서 파생된 크기 제한을 지원합니다.RLIMIT_STACK리소스 제한(참조getrlimit(2))

...한조각...

이는 제한에 이를 지원하는 아키텍처가 필요하고 제한되는 경우 참조도 필요함을 확인합니다(getrlimit(2)).

RLIMIT_STACK

이는 프로세스 스택의 최대 크기(바이트)입니다. 이 한계에 도달하면 SIGSEGV 신호가 생성됩니다. 이 신호를 처리하려면 프로세스가 대체 신호 스택(sigaltstack(2))을 사용해야 합니다.

Linux 2.6.23부터 이 제한은 프로세스의 명령줄 인수 및 환경 변수에 사용되는 공간의 양도 결정합니다. 자세한 내용은 execve(2)를 참조하세요.

RSP 레지스터를 변경하여 스택 늘리기

나는 x86 어셈블러를 모른다. 그러나 SS 레지스터가 변경될 때 x86 CPU에 의해 트리거될 수 있는 "Stack Fault Exception"에 주의를 기울이겠습니다. 내가 틀렸다면 정정해주세요., 하지만 x86-64 SS:SP에서는 "RSP"가 되었다고 생각합니다. 따라서 제가 올바르게 이해했다면 감소된 RSP에 의해 스택 오류 예외가 트리거될 수 있습니다(subq $0x7fe000,%rsp .

여기 222페이지를 참조하세요.https://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce.html

답변2

모든 프로세스 메모리 영역(예: 코드, 정적 데이터, 힙, 스택 등)에는 경계가 있으며 영역 외부의 메모리 액세스 또는 읽기 전용 영역에 대한 쓰기 액세스는 CPU 예외를 생성합니다. 커널은 이러한 메모리 영역을 유지 관리합니다. 영역 외부의 액세스는 분할 오류 신호의 형태로 사용자 공간까지 전파됩니다.

영역 외부의 메모리에 액세스하여 모든 예외가 생성되는 것은 아닙니다. 지역 내 액세스에서도 예외가 발생할 수 있습니다. 예를 들어 페이지가 실제 메모리에 매핑되지 않은 경우 페이지 오류 처리기는 이를 실행 중인 프로세스에 투명하게 처리합니다.

프로세스 메인 스택 영역에는 처음에는 매핑된 페이지 프레임 수가 적지만 스택 포인터를 통해 더 많은 데이터가 여기에 푸시되면 자동으로 커집니다. 예외 처리기는 액세스가 여전히 스택에 예약된 영역 내에 있는지 확인하고, 그렇다면 새 페이지 프레임을 할당합니다. 이는 사용자 수준 코드의 관점에서 자동으로 발생합니다.

스택 영역의 오버런을 감지하기 위해 스택 영역 끝 바로 뒤에 가드 페이지가 배치됩니다. 최근(2017년) 일부 사람들은 단일 가드 페이지로는 충분하지 않다는 것을 깨달았습니다. 왜냐하면 프로그램이 잠재적으로 스택 포인터를 대량으로 감소시키도록 속일 수 있고 이로 인해 스택 포인터가 쓰기를 허용하는 다른 영역을 가리킬 수 있기 때문입니다. 이 문제에 대한 "해결책"은 4kB 가드 페이지를 1MB 가드 영역으로 교체하는 것이었습니다. 이것 좀 봐LWN 기사.

이 취약점은 악용하기가 쉽지 않습니다. 예를 들어 사용자는 에 대한 호출을 통해 프로그램이 할당하는 메모리 양을 제어할 수 있어야 합니다 alloca. 강력한 프로그램은 alloca특히 사용자 입력에서 파생된 경우 에 전달된 매개변수를 확인해야 합니다 .

관련 정보