Advanced Programming in the UNIX Environment (3rd Edition) を読む vol. 1: openat 関数とTOCTTOU

# はじめに
Advanced Programming in the UNIX Environment (3rd Edition) （以下APUEと呼びます）を読んで疑問に思ったこと、調べたことをメモしていきます。

今回はp.62からの「3.3 `open` and `openat` Functions」に書かれている。`openat`関数がTOCTTOUエラーの対策になっている、という記述について、本文中に十分な解説がなかったため補足していきます。

# 目次
<!-- タイトルとアンカー名を編集 -->
1. [`open`関数と`openat`関数について](#Chapter1)
1. [TOCTTOUエラーとは](#Chapter2)
1. [本題：openat関数とTOCTTOUエラーの関連性](#Chapter3)
1. [参考文献](#reference)

<!-- 各チャプター -->
<a id="#Chapter1"></a>
# `open`関数と`openat`関数について
書式は以下の通りです。
```
#include <fcntl.h>
int open(const char *path, int oflag, ... /* mode_t mode */);
int openat(int fd, const char *path, int oflag, ... /* mode_t mode */);
```
`openat`関数では、第一引数`fd`で指定したファイル記述子に対応するディレクトリからの相対パスを第二引数`path`に指定できる点が`open`関数と異なります。

ちなみに、本文ではファイルオープンのオプションとして
- `O_RDONLY`
- `O_WRONLY`
- `O_RDWR`
- `O_EXEC`
- `O_SEARCH`

の5つが列挙されていますが、`O_EXEC`と`O_SEARCH`の2つはあまりサポートされていないようです。手元のmacOSで`man 2 open`してもこの2つは出てきませんでした。（[The GNU Library](https://www.gnu.org/software/libc/manual/html_mono/libc.html#Access-Modes)によれば、`O_EXEC`はGNU/Hurdでしかサポートされていないらしい。）

<a id="#Chapter2"></a>
# TOCTTOUエラーとは
TOCTTOUとは、"time-of-check-to-time-of-use"の略です。race conditionの一種で、ある資源の状態をチェックしたあと、それを実際に使うまでに生じるタイムラグによって発生するエラーを指します。ファイルI/Oの文脈で使われることが多いようです。

<a id="#Chapter3"></a>
# 本題：openat関数とTOCTTOUエラーの関連性
本文には、`openat`関数が用意されている意味について、複数のスレッド（同じプロセスの下にあるスレッドはカレントディレクトリの情報を共有しているため、単純に`open`を使うとどのスレッドでもそのスレッドが属するプロセスのカレントディレクトリからのパスだと認識されます）がそれぞれ異なるディレクトリのファイルをオープンするような操作を容易にすることに加えて、TOCTTOUエラーを回避することができる、と書かれています。これについて、本文にはTOCTTOUエラーについての説明しかないので、`openat`との関連を解説します。

具体例を挙げます。これは記事の最後にある参考文献を参考にしています。多くのUNIXライクなOSには、コマンドラインからメールを送信する`sendmail`というコマンドがあります。このコマンドは、まずメールボックスの属性をチェックします。このチェックとは、例えばディレクトリがシンボリックではないか？などが含まれます。もしシンボリックリンクだとすると、リンク先のディレクトリが攻撃者からアクセスしやすい場所にあったときにそこから情報を盗まれたり、意図しないファイルへの書き込みをしてしまったりするためです。このチェックを終えると、コマンドはメールボックスにルートユーザとして新しいメッセージを書き込みます。

このプロセスが2段階の操作からなっていることがわかると思います。すなわち、このプロセスにはTOCTTOUエラーの余地があります。攻撃者は、メールボックスの属性のチェックを終えたあとに、メールボックスを`/etc/passwd`へのシンボリックリンクに変更することができるかもしれません。もしこの攻撃が成功すると、メッセージは`/etc/passwd`に書き込まれてしまい、攻撃者から見れば、許可されていないはずの`/etc/passwd`というファイルへのアクセスが達成できてしまったことになります。

これがTOCTTOUエラーを利用した攻撃の一例です。次に、`openat`関数がどのようにしてこの攻撃を防ぐかを見ていきます。

この攻撃を防ぐには、まずメールボックスのあるディレクトリをプログラム内でオープンしておき、そのファイル記述子を`openat`関数の引数として与えればよいです。こうすると、例えば攻撃者が別のプログラムを用いてメールボックスを削除し、それを別のディレクトリへのシンボリックリンクで置き換えるような操作を実行しても、`openat`関数の引数として指定されたファイル記述子はもとの（ここでは削除された）ディレクトリを指し続けています。このとき`openat`関数は失敗します。（つまり、意図しない書き込みは行われません。）このように`openat`関数を使うと、TOCTTOUエラーを利用した攻撃を防ぐことができます。

最後にサンプルコードを示します。
```
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define BUFSIZE 4096

int main() {
    int fd_d, fd_f;

    // あらかじめディレクトリをオープンしておく
    if ((fd_d = open("tocttou_test", O_DIRECTORY)) < 0) {
        perror("open");
        exit(1);
    }


    /* 攻撃者が別のプログラムから行う操作
    if ((rmdir("tocttou_test")) < 0) {
        perror("rmdir");
        exit(1);
    }

    if ((symlink("/etc", "tocttou_test")) < 0) {
        perror("symlink");
        exit(1);
    }
    */

    //ファイルオープン
    if ((fd_f = openat(fd_d, "mail.txt", O_RDWR|O_CREAT|O_APPEND, S_IRWXU)) < 0) { //もし別のプログラムによって"tocttou_test"が削除されていれば、openatは失敗する
        perror("openat");
        exit(1);
    }

    char buf[BUFSIZE] = "test message!\n";

    if ((write(fd_f, buf, strlen(buf))) < strlen(buf)) {
        perror("write");
        exit(1);
    }

    close(fd_d);
    close(fd_f);

    exit(0);
}
```

<a id="#reference"></a>
# 参考文献
- [Wei, J., & Pu, C. (2005). TOCTTOU Vulnerabilities in UNIX-Style File Systems: An Anatomical Study. *FAST ’05: 4th USENIX Conference on File and Storage Technologies*, 155-167.](https://www.usenix.org/legacy/event/fast05/tech/full_papers/wei/wei.pdf)