最近、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です。
→ 続き