integer overflow 流行りな昨今
最近、integer overflowが根本原因のセキュリティホールを良く見かけます。普通のバッファオーバーフローについては世間の理解が進んで、最近では
char buf[1024] = {0}; char* dst = buf; while(*src != '=') *dst++ = *src++; // 1023文字目までに = が存在しないとcrash
みたいなコードを書く人はぐっと減りましたけど、
int ret = 0; while(std::isdigit(*src)) ret = (ret * 10) + (*src++ - '0'); return ret;
のような、strtolからオーバーフロー検出機能を取り払ってしまったようなコードを書いてしまうケースは多々あるようです。apache httpdでも、最近このパターンの脆弱性が数件あったような。
英文でよければ、整数オーバーフローの基礎の基礎からの解説や、実例、悪用方法までWeb上にいろいろ解説があります。
- 書籍 Secure Coding in C and C++ のサンプルPDF、 Chapter 5 - Integer Security (解説)
- MSDN: Reviewing Code for Integer Manipulation Vulnerabilities (解説 + 実例)
- Phrack magazine: Basic Integer Overflows (悪用)
最初の本なんて、CERTの人が書いていて、SEI Series in Software Engineering という良書揃いのシリーズでとてもヨサゲです。でも、日本語の情報は、Web上にはあまりみかけません。上記のMSDNも翻訳されていないようだし。
...というわけで、上記参考資料から脆弱なコード例を抜き出してクイズにみました。全部で(1)〜(9)まで9種類あります。んま、ワンパターンといえばワンパターンですけど、結構楽しめるかもしれません。
- 引数は、外部から自由に与えられるものとする
- POSIXを仮定、LP32を仮定
- std::bad_alloc は誰もcatchしていないものとする
- mallocには implementation-defined behavior がひとつありますが、"unique pointer を戻す" ほうの実装になっているものとする
- strlcpy, strlcat は、OpenBSDのこれのこと
- "単なるchar"型の符号有無は、どちらと仮定してもよい..はず
この条件だと、(3)(8)以外は派手にバッファオーバーフローしますので、多くの環境で任意コードの実行が可能じゃないかと思います*1。
クイズ 整数オーバーフロー
Secure Coding in C and C++ 5章と、MSDNから引用しています(一部改変あり)。
(1) 引数の与え方によっては、Buffer Overflowします
#include <cstddef> // size_t #include <climits> #include <cstdio> #include <cstdlib> #include <cstring> bool func1(const char* name, std::size_t cbBuf) { unsigned short cbCalculatedBufSize = cbBuf; char* buf = (char*)std::malloc(cbCalculatedBufSize); if (buf) { std::memcpy(buf, name, cbBuf); // do stuff with buf std::free(buf); return true; } return false; }
(2) 同じくBOします
void func2(int argc, char** argv) { unsigned short total; total = std::strlen(argv[1]) + std::strlen(argv[2]) + 1; char* buff = (char*)std::malloc(total); std::strcpy(buff, argv[1]); std::strcat(buff, argv[2]); }
(3) プロセスがabortします
bool func3(std::size_t cbSize) { if (cbSize < 1024) { char* buf = new char[cbSize - 1]; std::memset(buf, 0, cbSize - 1); // do stuff delete[] buf; return true; } else { return false; } }
(4) BO
bool func4(const char* s1, std::size_t len1, const char* s2, std::size_t len2) { if (1 + len1 + len2 > 64) return false; char* buf = (char*)std::malloc(len1 + len2 + 1); if (buf) { strlcpy(buf, s1, len1 + len2); strlcat(buf, s2, len1 + len2); // BSDがなければ、下記でお試しください // std::strncpy(buf, s1, len1 + len2); } // do other stuff with buf if (buf) std::free(buf); return true; }
(5) BO, 上と似ていますが引数がsigned int
bool func5(const char* s1, int len1, const char* s2, int len2) { char buf[128]; if (1 + len1 + len2 > 128) return false; if (buf) { std::strncpy(buf, s1, len1); std::strncat(buf, s2, len2); } return true; }
(6) BO, MSのGDI+で妙なJPEG読みこみで任意コード実行されてしまう件(MS04-028として大問題になった奴)の実例
void func6_getComment(unsigned int len, const char* src) { // real world example - MS GDI+ vlun unsigned int size; size = len - 2; char* comment = (char*)std::malloc(size + 1); std::memcpy(comment, src, size); return; }
(7) BO
#define BUFF_SIZE 10 void func7(int argc, char** argv){ int len; char buf[BUFF_SIZE]; len = std::atoi(argv[1]); if (len < BUFF_SIZE){ std::memcpy(buf, argv[2], len); } else std::printf("Too much data\n"); }
(8) ほぼ任意の場所の4バイトを書き換え可能
int *table = NULL; int func8(int pos, int value){ if (!table) { table = (int *)std::malloc(sizeof(int) * 100); } if (pos > 99) { return -1; } table[pos] = value; return 0; }
int func9(const char* str, int buf_len) { if (!str) return 1; std::size_t str_len = std::strlen(str); if (str_len > buf_len - sizeof(char)) { // buffer too small return 1; } char* buf = (char*)std::malloc(buf_len); strcpy(buf, str); return 0; }
最後の例は、C言語の
- integer conversion rank (C99規格の§6.3.1.1)
- usual arithmetic conversion (同 §6.3.1.8)
を理解していないと解けないと思うので、微妙に難しいかもしれません。
整数オーバーフローといえば、gcc の -ftrapv 機能というのも気になるところなんですが、これの試用についてはまた今度。
-ftrapv This option generates traps for signed overflow on addition, subtraction, multiplication operations.
stack based buffer overflowといえば、gcc -DFORTIFY_SOURCE についても書きたいなぁ*2。現代版のlibsafeです。
→ 続き