Stack overflowを理解する

# はじめに
Stack overflowについて説明します。
gdbを使って理解します。

# Stackとは
stackにはレジスタ、引数、ローカル変数、戻り値、リターンアドレスがおかれます。
例で説明します。

```c:test.c
int main(int argc, char* argv[])
{
    return 0;
}
```

```bash:console
$ gcc -O0 test.c
$ gdb a.out
(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004ed <+0>:	push   %rbp
   0x00000000004004ee <+1>:	mov    %rsp,%rbp
   0x00000000004004f1 <+4>:	mov    %edi,-0x4(%rbp)
   0x00000000004004f4 <+7>:	mov    %rsi,-0x10(%rbp)
   0x00000000004004f8 <+11>:	mov    $0x0,%eax
   0x00000000004004fd <+16>:	pop    %rbp
   0x00000000004004fe <+17>:	retq   
End of assembler dump.
```

push命令を実行するとstackに%rbpがおかれてrspがデクリメントされます。

> push   %rbp

次の命令はrsp=rbpをベースとして-0x4, -0x10にediとrsiを書き込みます。
rspは変更しませんがstackにデータを書き込みます。
ediとrsiはmain関数の引数argcとargvです。

> mov    %edi,-0x4(%rbp)
> mov    %rsi,-0x10(%rbp)

pop命令を実行するとstackのデータをrbpに読み出して、rspをインクリメントします。

> pop    %rbp

ローカル変数はstackにおかれます。次の例で説明します。

```c:test.c
int main(int argc, char* argv[])
{
    int v[10];
    return 0;
}
```

```bash:console
$ gcc -O0 test.c
$ gdb a.out
(gdb) disas main
Dump of assembler code for function main:
   0x00000000004004ed <+0>:	push   %rbp
   0x00000000004004ee <+1>:	mov    %rsp,%rbp
   0x00000000004004f1 <+4>:	mov    %edi,-0x34(%rbp)
   0x00000000004004f4 <+7>:	mov    %rsi,-0x40(%rbp)
   0x00000000004004f8 <+11>:	mov    $0x0,%eax
   0x00000000004004fd <+16>:	pop    %rbp
   0x00000000004004fe <+17>:	retq   
End of assembler dump.
```

ediを-0x34 = -52 しています。

> mov    %edi,-0x34(%rbp)

rbpのサイズは8です。ediのサイズは4です。 v[10]は40です。合計で52となります。
-8 ~ -(48-1)の範囲をv[10]として使用します。
このようにrspを操作しないでstackを暗黙的に確保することがあります。

# stack address / size の 取得 / 設定

stack address / size は pthread_attr_getstackで取得します。
取得できるstack addressはアクセス可能な最小のアドレスです。

stack size は pthread_attr_setstackで設定します。最小サイズは16kです。

```c:test.c
#include <stdio.h>
#include <limits.h>
#define _GNU_SOURCE
#include <pthread.h>
#define NAMELEN 16

void get_stack_info()
{
    void *stackaddr;
    size_t stacksize, guardsize;
    char name[NAMELEN] = {0};
    pthread_attr_t attr;
    pthread_t self = pthread_self();
    pthread_getname_np(self, name);
    pthread_getattr_np(self, &attr);
    pthread_attr_getstack(&attr, &stackaddr, &stacksize);
    pthread_attr_getguardsize(&attr, &guardsize);
    printf("name=%.4s range=[%p-%p] size=%zu guard size=%zu\n",
        name, stackaddr, stackaddr+stacksize-1, stacksize, guardsize);
}

void *start_routine(void* arg)
{
    get_stack_info();
    return 0;
}

int main(int argc, char* argv[])
{
    int ret;
    pthread_t thread;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    ret = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN /* size_t stacksize */);
    if (ret != 0) {
        printf("error pthread_attr_setstacksize %d\n", ret);
        return -1;
    }
    pthread_create(&thread, &attr, start_routine, 0 /* void * arg */);
    pthread_join(thread, 0 /* void **thread_return */);
    return 0;
}
```

```bash:console
$ gcc -g -O0 test.c -lpthread
$ ./a.out 
name=a.ou range=[0x7f5597059000-0x7f559705cfff] size=16384 guard size=4096
```

stackは低位アドレスに成長します。
guardはread / write禁止領域です。アクセスするとSegmentation faultが発生します。
アクセス可能なアドレス範囲は[stackaddr + guard_size, stackaddr + stacksize - 1]です。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/84205/9be8b5d5-ccc3-e5ed-1632-4354c870915d.png)

# stack 使用量 チェック
gccの"-fstack-usage"オプションを利用することで静的にstack使用量を確認できます。
具体例で説明します。

```c:test.c
int main(int argc, char* argv[])
{
    char buf[512];
    return 0;
}
```

```bash:console
$ gcc -fstack-usage test.c && cat test.su
test.c:1:5:main	560	static
```

560 byteのstackを使用していることがわかります。

bufにstaticをつけてみます。

```c:test.c
int main(int argc, char* argv[])
{
    static char buf[512];
    return 0;
}
```

16 byteに減っています。

```bash:console
$ gcc -fstack-usage test.c && cat test.su
test.c:1:5:main	16	static
```

# stack overflowが起こるケース
次のケースでstack overflowが起きる可能性があります。

- 深いcall stack , 再帰呼び出し
- サイズの大きなlocal変数
- サイズの大きな値渡し