2

VC++10 std::locale オブジェクトでバッファ オーバーフローのバグを発見したと思います。私が何か間違ったことをしているかどうかについてのセカンドオピニオンに感謝します。

以下のコードは、問題を示すために単純化されています。wchar_t (UTF-16) 文字を (Windows) ANSI コード ページ 51949 (別名 EUC-KR) に変換しようとしています。2 バイト (DBCS) エンコーディングを使用するため、コード ページが選択されました。

「部分的」または「エラー」のステータスで失敗することを期待して、2 バイトの結果に対して意図的に単一の char バッファーを提供しています。

代わりに、ステータス「ok」を返し、バッファの末尾を超えて書き込み、終了しようとしたときに破損したスタックの例外を引き起こします。

#include <iostream>
#include <locale>
#include <cwchar>

typedef std::codecvt<wchar_t, char, mbstate_t> cvt_t;

void PrintResult(cvt_t::result r)
{
    switch (r)
    {
        case cvt_t::ok: 
            std::cout << "ok\n";
            break;
        case cvt_t::partial: 
            std::cout << "partial\n";
            break;
        case cvt_t::error: 
            std::cout << "error\n";
            break;
        case cvt_t::noconv: 
            std::cout << "noconv\n";
            break;
    }
}
int main()
{
    const wchar_t src = L'안';
    char          dst = 0;
    std::locale   loc("Korean_Korea.51949");
    mbstate_t     state = { 0 };

    const cvt_t &facet = std::use_facet<cvt_t>(loc);
    cvt_t::result res;

    const wchar_t *pSrc;
    char *pDst;

    res = facet.out(state, &src, &src+1, pSrc, &dst, &dst+1, pDst);
    PrintResult(res);

    return 0;
}

デバッガーを使用して facet.out() 内にステップ インすると、次の関数の中にいることに気付きます (いくつかのレベルを下ります)。

virtual result __CLR_OR_THIS_CALL do_out(_Statype& _State,
    const _Elem *_First1, const _Elem *_Last1, const _Elem *& _Mid1,
        _Byte *_First2, _Byte *_Last2, _Byte *& _Mid2) const
    {   // convert [_First1, _Last1) to bytes [_First2, _Last)
    _DEBUG_RANGE(_First1, _Last1);
    _DEBUG_RANGE(_First2, _Last2);
    _Mid1 = _First1, _Mid2 = _First2;
    result _Ans = _Mid1 == _Last1 ? ok : partial;
    int _Bytes;

    while (_Mid1 != _Last1 && _Mid2 != _Last2)
        if ((int)MB_CUR_MAX <= _Last2 - _Mid2)
            if ((_Bytes = _Wcrtomb(_Mid2, *_Mid1,
                &_State, &_Cvt)) < 0)
                return (error); // locale-specific wcrtomb failed
            else
                ++_Mid1, _Mid2 += _Bytes, _Ans = ok;
        else
            {   // destination too small, convert into buffer
            _Byte _Buf[MB_LEN_MAX];
            _Statype _Stsave = _State;

            if ((_Bytes = _Wcrtomb(_Buf, *_Mid1,
                &_State, &_Cvt)) < 0)
                return (error); // locale-specific wcrtomb failed
            else if (_Last2 - _Mid2 < _Bytes)
                {   // converted too many, roll back and return previous
                _State = _Stsave;
                return (_Ans);
                }
            else
                {   // copy converted bytes from buffer
                _CSTD memcpy(_Mid2, _Buf, _Bytes);
                ++_Mid1, _Mid2 += _Bytes, _Ans = ok;
                }
            }
    return (_Ans);
    }

問題は次のようです。

if ((int)MB_CUR_MAX <= _Last2 - _Mid2)で、MB_CURR_MAX (関数 ___mb_cur_max_func() に #defined である) は、DBCS char に期待される "2" ではなく、"1" を返しています。

_CRTIMP int __cdecl ___mb_cur_max_func(void)
{
    /*
     * Note that we don't need _LocaleUpdate in this function.
     * The main reason being, that this is a leaf function in
     * locale usage terms.
     */
    _ptiddata ptd = _getptd();
    pthreadlocinfo ptloci = ptd->ptlocinfo;

    __UPDATE_LOCALE(ptd, ptloci);

    return ptloci->mb_cur_max;
}

この関数は、ファセットの初期化時に指定されたロケールではなく、グローバル ロケールにアクセスしているようです。

グローバル ロケールを次のように設定した場合:

std::locale::global(loc);

それはすべて正常に動作します(そしてステータス「部分的」で失敗します)。

out()したがって、この特定の実装では、オブジェクトの構築時に提供されたロケールではなく、MB_CUR_MAX を決定するためにグローバル ロケールを誤って使用しているように思われます。そのため、バッファ オーバーフロー バグが発生しています。

これで私の間違いを指摘できる人はいますか?

------------------------------
2013 年 5 月 20 日更新
---------------- --------------

これをバグ 787227 として MS に報告しました。

http://connect.microsoft.com/VisualStudio/feedback/details/787227/buffer-overflow-bug-in-std-use-facet-outを参照してください

------------------------------
2015 年 7 月 27 日更新
---------------- --------------

Microsoft から、このバグは Visual Studio 2015 の新しい RTM で修正されたというメールが届きました。 (Yay)

4

0 に答える 0