0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

はじめに

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

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

目次

  1. open関数とopenat関数について
  2. TOCTTOUエラーとは
  3. 本題:openat関数とTOCTTOUエラーの関連性
  4. 参考文献

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_EXECO_SEARCHの2つはあまりサポートされていないようです。手元のmacOSでman 2 openしてもこの2つは出てきませんでした。(The GNU Libraryによれば、O_EXECはGNU/Hurdでしかサポートされていないらしい。)

TOCTTOUエラーとは

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

本題: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);
}

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?