2016年2月16日火曜日

セキュアでないセキュアプログラミング

お仕事でプログラムをするときは、たとえば長い文字列を渡されてプログラムが攻撃されても大丈夫なようにセキュアプログラミングをしないといけません。
言語ごとにセキュアプログラミングのポイントが異なるのですが、とくにC言語はセキュアにプログラムをするのが本当に難しいです。

例えばアップルとかは以下のガイドラインを出してます。
セキュアコーディングガイド - Apple Developer

これにもC言語でセキュアプログラミングをする場合、「sprintf()、vsprint()でなくてsnprintf()、vsnprint()を使いましょう」って書いてあります。

でもこれ嘘です。

たとえば、Visual Studio 2013のC++のsnprintf()、vsnprint()は文字列が指定したサイズをオーバーしそうになった場合、文字列をnullで終端しません。

なのでsnprintf()を使って文字列を切り捨てているLinuxアプリをWindowsに移植するとへんな文字列が表示されたりプログラムが落ちます。

ANSI規格でnull終端する決まりになっていますが、決まりができるより前からあるVisualStudioではそうなっていないのです。

MSDNにも以下のように書かれています。
----------------------
Visual Studio 2015 その他のバージョン
解説
これらの関数は、引数リストへのポインターを使用し、データを書式指定して count 文字数までの文字を buffer が指すメモリに書き込みます。終端に空きがある場合 (つまり、書き込む文字数が count 文字数未満の場合)、バッファーは null で終わります。
----------------------

なんでしょうね。このへたくそな解説と翻訳。
一見nullで終わりますって書いてあるけど、よく読むと条件付きでnullで終わります。
つまり、nullでおわるとは限らないのです。
英語文化圏の人にはわかりやすいんでしょうがこれが日本語の壁というかなんというか。

さらにひどい会社とかのルールになるとstrcpy()、strcat()の代わりにstrncpy()、strncat()を使いましょうっていうのもあります。

もうこのルール作ったの誰だよ!これもそうとう怪しいです。
だってstrncpy()もVisualStudioのsnprintf()と同様にnullで終端しない可能性があるし、strncat()にいたっては連結する最大文字列であってバッファの最大サイズではないんです。
strcpy()、strcat()を使ってはいけないまではあっているのに・・・・。
もう、何が安全なのかさっぱりわかりません。


C言語のセキュアプログラミングについてどれも信用できないし、strcpy()、strcat()、sprintf()、snprintf()の関数って結構便利で使いたいですよね。
C言語の場合どの解説もよい代替案がないので、それが問題なんですよね。

解決策として、マイクロソフトはマイクロソフトがセキュリティー開発ライフサイクル(SDL)プログラミングっていうのを推奨しています。
これによると、マイクロソフトはstrcpy_s()、strcat_s()、sprintf_s()、snprintf_s()という関数を使用することを推奨していますが、これってLinuxにないので困ります。

また、アップルは上記にあげた先ほどのガイドラインで、strlcpy()とstrlcat()を使うことを推奨しています。これもMacOSXとかiPhoneでしか動かない。

なので、「僕だったらこう作ります。」というstrcpy_y()、strcat_y()、sprintf_y()を作ってみました。
基本的にはアップルと同じものをアップル以外にも動くようにした感じで、マイクロソフトの良いところも取り入れてみました。

こう書けばどのOSでも動くし、#include"safestring.h"すれば出力バッファが関数内で定義されているという条件付きですがstrcat()とかsprintf()とかそのままでも結構安全に動きます。

一流企業でもセキュアプログラミングの方針が全く違うので、C言語での標準化なんて出来そうにないですね。やはりC言語でのセキュアプログラミングのやり方って難しいんですね。

safestring.h
-------------------------------
#ifndef df_SAFESTRING_H_
#define df_SAFESTRING_H_


#define strcpy(a,b) strcpy_y(a,sizeof(a),b)
#define strcat(a,b) strcat_y(a,sizeof(a),b)
#define sprintf(a,b,...) sprintf_y(a,sizeof(a),b,__VA_ARGS__)
#define snprintf(a,b,fmt,...) sprintf_y(a,b,fmt,__VA_ARGS__)

#ifdef __cplusplus
extern "C"
{
#endif


char* strcpy_y(char* a,int sz,const char* b);
char* strcat_y(char* a, int sz, const char* b);
int sprintf_y(char* a,int sz,const char* fmt,...);

#ifdef __cplusplus
}
#endif


#endif /* df_SAFESTRING_H_ */
-------------------------------




safestring.c
-------------------------------
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "safestring.h"

#ifdef _MSC_VER
#if _MSC_VER >= 1400
#pragma warning( disable : 4996 )
#pragma warning( disable : 4819 )
#endif
#endif


char* strcpy_y(char* a, int sz, const char* b)
{
if (a == NULL || b == NULL || sz<1)return a;

*a = 0;
strncpy(a, b, sz);
a[sz - 1] = 0;
return a;
}


char* strcat_y(char* a, int sz, const char* b)
{
int l;
if (a == NULL || b == NULL || sz<1)return a;

l=sz-1-strlen(a);
if (l< 1)return a;

strncat(a, b, l);
a[sz - 1] = 0;
return a;
}

int sprintf_y(char* a, int sz, const char* fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret=vsnprintf(a, sz, fmt, ap);
a[sz - 1] = 0;
//va_end(ap);
return ret;
}

static int snprintf_bug(char* a, int sz, const char* fmt, ...)
{
va_list ap;
int ret;
va_start(ap, fmt);
ret = vsnprintf(a, sz, fmt, ap);
//a[sz - 1] = 0;
//va_end(ap);
return ret;
}

#if 1

int main(int argc, char* argv[])
{
char a[8];
char* p;
p = a;
char b[16];

strcpy(a, "1234567890");
printf("a=>>%s<<\n",a);

strcpy(a, "a");
strcat(a, "bcdefgihj");
printf("a=>>%s<<\n", a);

strcpy(p, "a");
strcat(p, "bcdefgihj");
printf("a=>>%s<<\n", a);

sprintf(a, "--%d--", 123456);
printf("a=>>%s<<\n", a);

snprintf(a, sizeof(a), "++%d++", 123456);
printf("a=>>%s<<\n", a);

strcpy(b, "###############");
snprintf_bug(b, 8, "--%d--", 123456);
printf("b=>>%s<<\n", b);

return 0;
}
#endif

-------------------------------


0 件のコメント:

コメントを投稿