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上にいろいろ解説があります。

最初の本なんて、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;
}

(9) BO, NetBSDで似た事例があったそうです

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です。


続き

*1: (8)も、別途メモリ上にコードを置くことができれば可能

*2:某雑誌にはちらっと書いたんですが、ここに書くほど深追いはできておらず。