クリップボードからの貼り付け時のクラッシュ対応(int32_t)#2453
Open
hpmy-dev wants to merge 9 commits intosakura-editor:masterfrom
Open
Conversation
Contributor
|
差分を確認しましたがタイトル「クリップボードからの貼り付け時のクラッシュ対応(int32_t)」以外の変更も行われています。 |
Author
|
はい、下記理由の為です。 ・既にPRでsrc/test/cpp/tests1/test-cclipboard.cppは修正が入ってましたので、どっちみちMERGEが発生する。 上記2点はこの分です。 |
Contributor
|
もとの実装がだいぶおかしいので、色んな切り口で検討してみる、も価値あることだと思っています。 ざっくり。
マクロ専用関数 Get / Set ClipboardByFormat の不審点
テキストエディタの機能なのに、バイナリモードがある。 |
Author
|
確かに同様に文字コードの自動判定処理もオリジナルになってますね。最後はタイプ別設定に紐づけられるって笑 クリップボードに関してnotepadは重たい感じがありますが、notepad++などいろいろ参考にされてはどうですかね。 私としてはPRしている32bit版でgrepのマルチスレッドを早く32bit版ででも |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR対象
カテゴリ
PR の背景
PR #2067 において SAKURAClipW 形式のヘッダ型が
intからsize_tに変更されたことにより、32bit版(size_t= 4バイト)と64bit版(size_t= 8バイト)の間でクリップボードのバイナリレイアウトが不一致となり、クロスビット間のコピー&ペースト時にヘッダの読み取り位置がずれて異常なサイズを確保しようとしクラッシュする問題が発生しています。関連: #2450, #2067, Issue #2325
仕様・動作説明
1. SAKURAClipW ヘッダ型の
int32_t固定化ヘッダ型を環境依存の
size_tから固定長のint32_t(4バイト)に変更し、v2.4.2 リリース版(int= 4バイト)とのバイナリ互換性を回復します。ヘッダ構造体SSakuraClipHeaderを新設し、#pragma pack(push, 1)+static_assertでレイアウトを保証。フォーマット名
SAKURAClipWは変更しません(バイナリレイアウトが v2.4.2 と一致するため)。2. SAKURAClipW メモリレイアウト
3. GlobalSize() による3段階フェイルセーフ(GetText 読み込み時)
SAKURAClipW 形式の読み込み時に、ヘッダの自己申告値を鵜呑みにせず
GlobalSize()が返す実際のメモリサイズと突き合わせて検証します。pData == nullptr || cbData < sizeof(SSakuraClipHeader)cchRaw < 0cchData > cchMaxフォーマット未指定(デフォルト)の場合、SAKURAClipW の検証が失敗すると CF_UNICODETEXT へ自動フォールバックします。
4. INT32_MAX 超過時の SAKURAClipW スキップ(SetText 書き込み時)
nDataLen > INT32_MAXの場合、SAKURAClipW 形式のみをスキップし、CF_UNICODETEXT・矩形選択フラグ・行選択フラグは正常に書き込みます。これにより 2GiB 超のテキストでも CF_UNICODETEXT + 矩形選択フラグが正しく書き込まれ、ペースト側は CF_UNICODETEXT へフォールバックして動作します。
5. SIZE_T オーバーフロー防止(SetText CF_UNICODETEXT 書き込み時)
(nDataLen + 1) * sizeof(wchar_t)がSIZE_Tの上限を超えてオーバーフローする場合を検出し、CF_UNICODETEXT のGlobalAllocをスキップします。6. CLIPBOARD_MAX_CHARS による読み込み上限
CClipboard::CLIPBOARD_MAX_CHARS = INT32_MAXを定義し、CF_UNICODETEXT / CF_OEMTEXT / GetClipboardByFormat の読み込み時にデータ長を上限で切り詰めます。7. SEH 例外ハンドリング(STATUS_NO_MEMORY 対策)
大容量データのペースト時に Windows ヒープが投げる SEH 例外
STATUS_NO_MEMORY(0xC0000017)に対応するため、__try/__exceptを使用した安全なラッパー関数を導入しました。CMemory::AllocBuffer内部のmalloc/reallocは C++ 例外を投げず、std::wstring::appendはstd::bad_allocの前に Windows ヒープが SEH 例外を投げるため、C++ のtry-catch(std::bad_alloc&)では捕捉できません。MSVC の制約上、__try/__exceptはデストラクタを持つ C++ オブジェクトと共存できないため、独立した関数に分離しています。参照: https://learn.microsoft.com/ja-jp/windows/win32/debug/using-an-exception-handler
SafeAppendIWBuffer::Appendの保護CClipboard.cppSafeSetStringCNativeW::SetStringの保護CEditView_Mouse.cppSafeNewBytesnew BYTE[]の保護CDropTarget.cpp8. CDropTarget.cpp の安全化
SafeNewBytesによるメモリ確保の SEH 保護m_pData配列のゼロ初期化(goto fail時のdelete[]未定義動作防止)int32_t対応(memcpy_raw使用)nTextLen > INT32_MAX時の SAKURAClipW スキップ(CF_UNICODETEXT・矩形選択フラグは維持)9. CEditView_Mouse.cpp(Drop)の安全化
SafeSetStringによる SEH 保護int32_t対応 +GlobalSize()チェックCLIPBOARD_MAX_CHARS切り詰め仕様・動作説明(ファイル別ロジック詳細)
ファイル別変更詳細
1.
sakura_core/_os/CClipboard.hSSakuraClipHeader 構造体の新設
SAKURAClipW 独自クリップボード形式のバイナリヘッダを、
#pragma pack(push, 1)で1バイトアラインメントに固定した構造体として定義。static_assertでサイズが正確に4バイトであることをコンパイル時に保証する。従来はsize_tをキャストして直接書き込んでおり、32bit(4バイト)と64bit(8バイト)でレイアウトが不一致になっていた。CLIPBOARD_MAX_CHARS 定数の追加
CClipboardクラスのstatic constexprメンバとしてCLIPBOARD_MAX_CHARS = INT32_MAXを定義。ヘッダ末尾にグローバルスコープのstatic constexprエイリアスも配置し、無名名前空間内のSafeAppend等から参照可能にしている。変更なしの項目
SetText()/GetText()の関数シグネチャ — パラメータ型size_t nDataLenは PR #2067 でintから変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、intには戻さずそのまま維持する。int32_tに固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。2.
sakura_core/_os/CClipboard.cppSafeAppend ヘルパー関数の追加(無名名前空間)
IWBuffer::Appendの呼び出しを Windows SEH(__try/__except)で保護する static 関数。STATUS_NO_MEMORY(0xC0000017)を捕捉した場合のみEXCEPTION_EXECUTE_HANDLERを返しfalseで復帰する。それ以外の例外はEXCEPTION_CONTINUE_SEARCHで上位に伝播させる。C++ のtry-catch(std::bad_alloc&)ではなく SEH を使用する理由は、CMemory::AllocBuffer内部のmalloc/reallocが C++ 例外を投げないこと、およびstd::wstring::appendがstd::bad_allocを投げる前に Windows ヒープが SEH 例外を投げる場合があるため。MSVC の制約上、__try/__exceptはデストラクタを持つ C++ オブジェクトと同一関数に配置できないため、独立した関数に分離している。SetText() の変更
bCanUseSakuraFormatフラグの導入:nDataLen <= INT32_MAXを関数冒頭で評価し、以降の SAKURAClipW 書き込み判定に使用。return falseで関数全体を中断するのではなく、bSakuraTextの条件に組み込むことで、CF_UNICODETEXT・矩形選択フラグ・行選択フラグはnDataLen > INT32_MAXでも正常に書き込まれる。nDataLen + 1の加算オーバーフローとcchUnicode * sizeof(wchar_t)の乗算オーバーフローを各々検出し、発生時はbreakで書き込みをスキップ。int32_t書き込み:memcpy(pClip, &header, sizeof(header))でSSakuraClipHeaderサイズ分を書き込む。sizeof(nDataLen)(64bit 環境で8バイト)ではなくsizeof(SSakuraClipHeader)(固定4バイト)を使用。SSakuraClipHeaderベースに修正。bCanUseSakuraFormat && !hgClipSakuraの場合のみ SAKURAClipW 確保失敗をfalseとする。nDataLen > INT32_MAXで意図的にスキップした場合は失敗扱いにしない。GetText(IWBuffer*) の変更
pData == nullptr || cbData < sizeof(SSakuraClipHeader)でロック失敗・データ不足を検出。memcpy(&cchRaw, pData, sizeof(cchRaw))でヘッダを読み取り、cchRaw < 0で負値(符号反転による不正値)を検出。cchData > cchMax(cchMax = (cbData - sizeof(SSakuraClipHeader)) / sizeof(wchar_t))でヘッダの自己申告値が実メモリを超過するケースを検出し、破損データとして拒否。GlobalUnlock(hSakura)を呼んでからリターンまたはフォールスルー。uGetFormat == -1)の場合、検証失敗時は CF_UNICODETEXT へフォールスルー。明示指定(uGetFormat == uFormatSakuraClip)の場合はreturn false。AppendをSafeAppendで保護。SafeAppendがfalseを返した場合も、フォーマット未指定なら CF_UNICODETEXT へフォールスルー。GlobalSize()からwcsnlenで実文字数を算出し、std::min(cchTotal, CLIPBOARD_MAX_CHARS)で上限に切り詰め。SafeAppendで SEH を保護し、失敗時はGlobalUnlock→return false。GlobalSize()をCLIPBOARD_MAX_CHARS * sizeof(wchar_t)で切り詰めてから SJIS→UNICODE 変換。変換後の文字数もCLIPBOARD_MAX_CHARSで再切り詰め。SafeAppendで保護。GetClipboardByFormat() の変更
GetLengthByMode()で取得したnLengthを、モードに応じた上限値(バイナリモードはCLIPBOARD_MAX_CHARS、それ以外はCLIPBOARD_MAX_CHARS * sizeof(wchar_t))で切り詰め。nEndMode == -1で再取得したnLengthにも同じ上限を適用。3.
sakura_core/_os/CDropTarget.cppSafeNewBytes ヘルパー関数の追加(無名名前空間)
new BYTE[]を__try/__exceptで保護する static 関数。STATUS_NO_MEMORY捕捉時は*ppOut = nullptrを設定してfalseを返す。CDataObject::SetText() の変更
bUseSakuraFormatフラグの導入:nTextLen <= INT32_MAXを評価。falseの場合は SAKURAClipW エントリを除外してm_nFormatを調整(2 or 3)し、CF_UNICODETEXT・CF_TEXT・矩形選択フラグは維持。m_pData配列のゼロ初期化:new DATA[m_nFormat]の直後に全エントリのdataをnullptrに初期化。goto fail時に未初期化ポインタをdelete[]する未定義動作を防止。new BYTE[]をSafeNewBytesに置き換え: CF_UNICODETEXT、CF_TEXT、SAKURAClipW、MSDEVColumnSelect の各確保を SEH で保護。失敗時はgoto failで全エントリをクリーンアップ。int32_t書き込み:memcpy_raw(m_pData[i].data, &cchData, sizeof(cchData))でSSakuraClipHeaderサイズ分を書き込む。4.
sakura_core/view/CEditView_Mouse.cppSafeSetString ヘルパー関数の追加(無名名前空間)
CNativeW::SetStringを__try/__exceptで保護する static 関数。STATUS_NO_MEMORY捕捉時はfalseを返す。Drop() 関数の変更 — ドロップデータ取得ループ内
memcpy_raw(&header, pData, sizeof(header))でヘッダを読み取り(R1: エイリアシング安全)。header.cchData >= 0かつcchData <= cchMax(cchMax = (nSize - sizeof(SSakuraClipHeader)) / sizeof(wchar_t))で検証。検証失敗時はGlobalUnlock→GlobalFree→ CF_UNICODETEXT / CF_TEXT へフォールバック。wcsnlenで文字数を算出し、CLIPBOARD_MAX_CHARSで切り詰め。SafeSetStringで SEH を保護。失敗時はGlobalUnlock→GlobalFree→E_OUTOFMEMORYを返す。CLIPBOARD_MAX_CHARS * sizeof(wchar_t)でバイト数を切り詰めてから SJIS→UNICODE 変換。変換後の文字数もCLIPBOARD_MAX_CHARSで再切り詰め。5.
src/test/cpp/tests1/test-cclipboard.cpp新規追加テスト
ClipboardMaxCharsConstantCLIPBOARD_MAX_CHARS == INT32_MAXかつ正値であることの確認SetText7nDataLen > INT32_MAXでサクラ形式指定時にSetClipboardDataが呼ばれずfalseを返すSetText8nDataLen > INT32_MAXでもデフォルト指定で CF_UNICODETEXT と矩形選択フラグが書き込まれtrueを返す。SAKURAClipW はTimes(0)で呼ばれないことを検証SetTextSizeTOverflownDataLen = size_t::maxで SIZE_T オーバーフロー防御が機能しfalseを返すSakuraFormatNegativeLengthcchData = -1でGetTextがフォーマット指定なしでfalseを返すSakuraFormatOverflowLengthcchData = 2だが実データ1文字分でGetTextがfalseを返すCorruptedSakuraFallsBackToUnicodetrueを返す既存テストの変更
SakuraFormatInGlobalMemoryマッチャ: ヘッダの読み取りをSSakuraClipHeader構造体経由に変更し、cchData < 0を検出。CClipboardGetText:sakuraMemoryの確保サイズをsizeof(SSakuraClipHeader) + ...に変更。ヘッダ書き込みを((SSakuraClipHeader*)p)->cchData = static_cast<int32_t>(...)に変更。#include <limits>を追加(std::numeric_limits<size_t>::max()使用のため)。PR の影響範囲
修正対象ファイル
sakura_core/_os/CClipboard.hSSakuraClipHeader構造体新設、CLIPBOARD_MAX_CHARS定数追加sakura_core/_os/CClipboard.cppSafeAppend追加、SetText/GetText のint32_t対応、GlobalSize()チェック、SEH ハンドリングsakura_core/_os/CDropTarget.cppSafeNewBytes追加、int32_t対応、ゼロ初期化、SAKURAClipW スキップsakura_core/view/CEditView_Mouse.cppSafeSetString追加、Drop() のint32_t対応、GlobalSize()チェックsrc/test/cpp/tests1/test-cclipboard.cpp変更しないもの
SAKURAClipW(バイナリレイアウトが v2.4.2 と一致するため)SetText()/GetText()の関数シグネチャ — パラメータ型size_t nDataLenは PR INT_MAXより大きなバイト数のテキストのクリップボードへのコピーが行えるようにする変更 #2067 でintから変更されたものだが、関数インターフェースとしては大容量データの受け渡しに必要なため、intには戻さずそのまま維持する。int32_tに固定化したのは SAKURAClipW のバイナリヘッダのみであり、関数の引数型とは独立している。SetHtmlText()/HDROP処理(大容量データに該当しない)テスト内容
ユニットテスト(test-cclipboard.cpp)
SetText 系
nDataLen > INT32_MAXでサクラ形式指定時にfalseを返すnDataLen > INT32_MAXでも矩形選択フラグが書き込まれるGetText 系
動作テスト
ビルド確認
関連 issue, PR
size_t変更の元凶となったPR参考資料
__try/__exceptのリファレンスsize_t変更の経緯とレビュアー指摘