コードに潜む脆弱性の解説
今回のコードに潜む脆弱性は書式指定文字列に関する脆弱性です。talloc_asprintf_append()の使い方に問題があります。この関数の型を確認してみましょう。
char *talloc_asprintf_append(char *s, const char *fmt, ...);
「...」という部分は何かを省略しているわけではなく、実際にこのように記述します。そう、printf()を代表とする可変引数関数です。プロトタイプ宣言と一緒に引用したコメントにも書いてあるように、talloc_asprintf_append()は、第2引数の書式指定文字列を使って第3引数以降で指定されるデータを整形した文字列を、第1引数のsが示す文字列の後ろに連結した文字列を生成します。
talloc_asprintf_append()の呼び出しでは、第2引数のfnameはユーザーが入力した文字列から取ってきています。ここにはユーザーが指定したファイル名が入っているわけですが、talloc_asprintf_append()では、第2引数は書式指定文字列だと想定しています。
では、ユーザーが「get aa%sbb」のような入力を行って書式指定子「%s」を含むファイル名を与えたらどうなるでしょうか? talloc_asprintf_append()はその後ろに「%s」にはめこむべき文字列引数が置いてあるものとしてアクセスしにいくでしょう。しかし、実際の呼び出しは第2引数までしかありません。このコードでは、スタックに残されていたデータを勝手に「%s」の部分にはめこんで処理することになります。
この問題は「書式指定文字列の脆弱性」として知られています。一般にプログラムで使われている書式指定文字列を自由に操作できる場合、スタックに積まれている値を調べることが可能です。また、メモリアドレスを指定してその値を調べたり、さらには任意のメモリアドレスに指定した値を書き込んだり、入力に潜ませたコードを実行させることが可能になることもあります。
今回紹介したコードでは、smbclientプログラムへの入力を処理する部分の問題であり、コマンドラインからsmbclientを起動して使う通常の使用では問題にならないと思われます。しかし、自動化スクリプトやWebアプリケーションのなかからsmbclientを呼び出しているようなケースの場合、外部からの攻撃に悪用される危険性は十分に考えられます。
Sambaでは3.2.13でこの問題を修正しています。修正はいたって簡単。書式指定文字列を明示的に与えるだけです。
- remote_name = talloc_asprintf_append(remote_name, fname); + remote_name = talloc_asprintf_append(remote_name, "%s", fname);
この修正により、書式指定文字列として解釈されるのは「%s」となり、fnameはそこにはめこまれるためにだけ使われるようになります。
なお、cmd_get()から呼び出される関数のなかで、d_printf()もtalloc_asprintf_append()と同様の可変引数関数として定義されており、書式指定文字列を引数に取ります。
int d_printf(const char *format, ...);
しかし、実際の呼び出しでは、外部入力に左右されない文字列リテラルを書式指定文字列として引数に渡しているため、悪用される心配はありません。
終わりに
書式指定文字列の問題は、1999年ころから広く知られるようになり、printf()やsyslog()関数を使う多くのアプリケーションがこぞって修正を行いました。
今回のような問題を作りこまないようにするには、プログラム中で使われる書式指定文字列を外部からの入力で操作されないようにすることが必要です。セキュアコーディングスタンダードでは、「FIO30-C.ユーザーからの入力を使って書式指定文字列を組み立てない」というルールがあります。こちらも一度眺めてみてください。