0. まず、一つのプログラムと二つの結果を見る

0.1 最後まで使うCソース

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

void win(void) {
    puts("YOU WIN");
    exit(0);
}

void greet(const char *name) {
    printf("Hello, %.15s\n", name);
}

void read_name(void) {
    char name[16] = {0};

    puts("Enter your name:");
    read(STDIN_FILENO, name, 128);
    greet(name);
}

int main(void) {
    read_name();
    puts("Goodbye");
    return 0;
}

ひと目で読むと、呼び出しは次の形である。

main
  └─ read_name
       └─ greet

win ← どこからも呼ばれていない

それなのに、正常実行では到達しないwinが、入力によって実行される。これが今日の問いである。

0.2 正常系の事前収録

Enter your name:
Alice
Hello, Alice
Goodbye

read_nameが入力を受け、greetが表示し、mainの続きへ戻る。収録では改行を含まない5バイトのAliceを入力する。

0.3 異常系の事前収録

Enter your name:
AAAAAAAAAAAAAAAA...
Hello, AAAAAAAAAAAAAAA
YOU WIN

mainにwin()はない。分岐もない。しかしCPUはwinの命令を実行する。この差は、Cの制御構造ではなく、メモリ上の戻り先が変わったことから生まれる。

安全上の境界:デモは自作バイナリとローカルの隔離環境だけで行う。本資料は再利用可能な攻撃入力や自動化器を提供しない。