|
|
本帖最後由 good7 於 2026-7-24 20:54 編輯
================================================================================
Flycast 專案最佳化、安全性漏洞及其它記錄
================================================================================
整理日期:2026-07-24
工作目錄:Z:\f
================================================================================
================================================================================
一、Critical 級安全漏洞修復(7 項)
================================================================================
[C-01] CHD sscanf 堆疊緩衝區溢位
檔案:core/imgread/chd.cpp:198,203,218
修改:三處 sscanf 的格式字串中 %s → %15s,限制寫入寬度
行數:309 → 312 (+3)
說明:type[16] 等四個 16 byte 緩衝區不再被超長元資料溢位
[C-02] ISO9660 目錄記錄越界讀取
檔案:core/imgread/isofs.cpp:76
修改:if (index >= data.size()) → if (index + 34 > data.size())
行數:156 → 156 (0)
說明:確保剩餘空間足夠容納完整的目錄記錄結構(至少 34 byte)
[C-03] RetroArch 存檔 MEM 區段大小未驗證
檔案:core/serialize.cpp:117-122
修改:加入 if (sectionSize > limit) throw Exception(...)
行數:157 → 158 (+1)
說明:MEM 區段的 sectionSize 現在會與剩餘資料長度比較
[C-05] JIT 代碼緩衝區 RWX(W^X 違規)
檔案:core/windows/win_vmem.cpp:188
修改:mem_alloc 初始分配 PAGE_EXECUTE_READWRITE → PAGE_READWRITE
行數:282 → 282 (0)
說明:初始分配改為 RW,編譯完成後 jit_set_exec(true) 切為 RX
[C-06] jit_set_exec 為空操作
檔案:core/windows/win_vmem.cpp:254-256
修改:實作 VirtualProtect 切換 PAGE_READWRITE / PAGE_EXECUTE_READ
行數:277 → 282 (+5)
說明:JIT 代碼區現在能在寫入時設為 RW、執行時設為 RX
[C-08] Lua 無沙箱 — 任意程式碼執行
檔案:core/lua/lua.cpp:622-623
修改:luaL_openlibs(L) 替換為白名單載入(base/table/string/math/utf8)
行數:652 → 663 (+11)
說明:移除 os/io/package/debug 函式庫,消除任意命令執行能力
[C-10] getSubPath 路徑遍歷漏洞
檔案:core/oslib/storage.cpp:151-155
修改:加入 .. 和絕對路徑檢查,不合法時 throw StorageException
行數:353 → 355 (+2)
說明:所有 36+ 處呼叫端現在都受到路徑遍歷保護
================================================================================
二、High 級安全漏洞修復(20 項)
================================================================================
[H-01] CDI fread 回傳值未檢查
檔案:core/deps/chdpsr/cdipsr.cpp:47-103
修改:CDI_read_track 中 13 處 fread 全部加入 != 1 return false 檢查
行數:161 → 161 (0)
[H-02] GDI 軌道計數無上限 → OOM DoS
檔案:core/imgread/gdi.cpp:34
修改:iso_tc == 0 → iso_tc == 0 || iso_tc > 100
[H-03] GDI EndFAD 整數下溢
檔案:core/imgread/gdi.cpp:99-100
修改:加入 OFFSET 範圍檢查(< 0 或 > fileInfo.size 時 throw)
行數:125 → 129 (+4)
[H-04] CDI fseek 偏移量整數溢位
檔案:core/imgread/cdi.cpp:183
修改:track.total_length * track.sector_size → (long long)... * ...
行數:212 → 212 (0)
[H-05] CHD 數值欄位無範圍驗證
檔案:core/imgread/chd.cpp:222
修改:加入 frames/padframes/pregap/postgap 範圍檢查
[H-06] DNS 回應解析無邊界 strlen
檔案:core/network/dns.cpp:130-137
修改:加入最小長度檢查;strlen → strnlen;加 reader > end 檢查
行數:207 → 211 (+4)
[H-07] DHCP 選項越界讀取
檔案:core/network/dcnet.cpp:709
修改:while 條件 options - frame < len → options - frame + 2 < len
行數:793 → 793 (0)
[H-08] GGPO VF4Card 缺少長度驗證
檔案:core/network/ggpo.cpp:468
修改:加入 len >= sizeof(type) + sizeof(card) 檢查
行數:947 → 948 (+1)
[H-09] registerHandler 使用 assert(Release 被移除)
檔案:core/hw/mem/addrspace.cpp:236
修改:assert(rv < HANDLER_COUNT) → verify(rv < HANDLER_COUNT)
行數:556 → 558 (+2)
[H-10] 處理器 ID 索引無邊界檢查
檔案:core/hw/mem/addrspace.cpp:123,168
修改:readt/writet 中加入 verify(id < HANDLER_COUNT)
[H-11] init() 中分配無錯誤檢查
檔案:core/windows/win_vmem.cpp:67,72
修改:加入 verify(mem_handle != NULL) 和 verify(base_alloc != NULL)
[H-13] UAF 風險:Stale Block 與代碼快取重用
檔案:core/hw/sh4/dyna/blockmanager.cpp:316
修改:bm_ResetCache 中加入 del_blocks.clear()
行數:604 → 605 (+1)
[H-14] cheats 計數無上限 → DoS/OOM
檔案:core/cheats.cpp:401
修改:加入 count 範圍檢查 (0-10000)
行數:1136 → 1149 (+13)
[H-15] cheat.size 位元移位 UB
檔案:core/cheats.cpp:417
修改:1 << → 1u <<,限制 memSize 範圍 [0,5]
[H-16] apply() 位址遞增無邊界
檔案:core/cheats.cpp:765,785
修改:copy 迴圈加邊界條件;setValue 迴圈加 address >= RAM_SIZE break
[H-17] INI 格式注入
檔案:core/cfg/ini.cpp:282-296
修改:加入 sanitizeIniValue() 過濾 \n \r;save() 兩處 fprintf 套用
行數:319 → 332 (+13)
[H-20] GDB stack 命令堆疊緩衝區溢位
檔案:core/debug/gdb_server.cpp:587-605
修改:固定/VLA 緩衝區改為 std::string 動態分配
行數:989 → 984 (-5)
[H-22] ELF 載入器越界讀取 + 整數下溢
檔案:core/reios/reios_elf.cpp:55-66
修改:加入 offset+len <= filesize 和 memSize >= len 檢查
行數:71 → 77 (+6)
[H-23] readdir 非執行緒安全
檔案:core/oslib/directory.cpp:52
修改:static dirent d → thread_local dirent d
行數:122 → 122 (0)
[H-24] UDP AP 發現越界讀取
檔案:core/network/dcnet.cpp:470
修改:while 條件改為 p + 5 <= recvbuf + len
[H-25] GDB 繫結所有介面
檔案:core/debug/gdb_server.cpp:140
修改:tcp::v4() → address_v4::loopback()
[H-26] getArcadeFlashPath 路徑遍歷
檔案:core/oslib/oslib.cpp:103
修改:加入 fileName 淨化(拒絕 .. / \)
行數:491 → 500 (+9)
================================================================================
三、Medium 級安全漏洞修復(8 項)
================================================================================
[M-12] GGPO Deserializer 限制不一致
檔案:core/network/ggpo.cpp:448
修改:1_MB → settings.platform.isNaomi() ? 20_MB : 10_MB
[M-13] CDI 負軌道長度僅警告
檔案:core/imgread/cdi.cpp:166
修改:track.length < 0 WARN → track.length <= 0 throw
行數:212 → 212 (0)
[M-14b] repeatCount 無上限
檔案:core/cheats.cpp:419
修改:限制 repeatCount ≤ 65536
[M-15] INI get_int/64 strstr("0x") 邏輯缺陷
檔案:core/cfg/ini.cpp:15-25, 27-33
修改:strstr → 字首檢查 value[0]=='0' && value[1]=='x'/'X'
[M-16] option.h 路徑列表越界存取
檔案:core/cfg/option.h:212,220
修改:加入 if (start >= paths.size()) break 邊界檢查
行數:553 → 557 (+4)
[M-24] saveScreenshot 路徑遍歷
檔案:core/oslib/oslib.cpp:301
修改:加入 name 淨化(拒絕 .. / \)
[M-25] descrambl 非執行緒安全全域狀態
檔案:core/reios/descrambl.cpp:16,33
修改:static → thread_local(seed 和 idx[])
行數:76 → 76 (0)
[M-27] stdclass 路徑拼接無驗證
檔案:core/stdclass.cpp:67-81,100-114
修改:get_writable_config_path/data_path 加入 filename 淨化
行數:257 → 273 (+16)
[M-29] HTTP 回應無大小限制
檔案:core/oslib/http_client.cpp:62,233
修改:加入 10MB 上限檢查(Windows + curl 兩處)
行數:342 → 346 (+4)
[M-31] GDB unpack 無輸入驗證
檔案:core/debug/gdb_server.cpp:38-46
修改:加入嚴格 hex 字元範圍驗證,非 hex 回傳 0
================================================================================
四、系統級防護強化(CMAKE 級,3 項)
================================================================================
[CMAKE-01] Stack Canary 恢復
檔案:CMakeLists.txt:102
修改前:target_compile_options(${PROJECT_NAME} PRIVATE /GR /GS-)
修改後:target_compile_options(${PROJECT_NAME} PRIVATE /GR /GS)
說明:/GS- 明確關閉 stack canary,改為 /GS 恢復保護
[CMAKE-02] GCC Stack Protector 啟用
檔案:CMakeLists.txt:106
修改:加入 -fstack-protector-strong 編譯選項
說明:GCC build 加入 stack protector,與 MSVC 的 /GS 對等
[CMAKE-03] 移除 _CRT_SECURE_NO_WARNINGS
檔案:CMakeLists.txt:112
修改:移除 $<$<BOOL {MSVC}>:_CRT_SECURE_NO_WARNINGS> 定義
說明:恢復 MSVC 對不安全 CRT 函數的編譯警告
================================================================================
五、穩定性問題修復(7 項)
================================================================================
[#2] Renderer 例外導致死結
目標:core\hw\pvr\Renderer_if.cpp:194-215
問題:renderer-> rocess/Render 拋出例外時,FinishRender 不執行 → rqueue 不清空
→ frame_finished 不 Set → 下一幀 QueueRender 永久阻塞 → 模擬器死結
對策:用 try/catch 包裝 Process+Render+present 區段;catch 中執行
renderEnd.Set() + rend_allow_rollback() 確保渲染狀態一致
行數:548 → 553 (+5)
[#4] ctx_list 跨執行緒無鎖存取
目標:core\hw\pvr\ta_ctx.cpp
問題:ctx_list 在 Emu 執行緒與主執行緒之間無鎖存取,可能導致迭代器失效
對策:新增 static std::recursive_mutex mtx_list;在 5 個存取點加入 lock_guard
行數:297 → 305 (+8)
[#6] FrameCount 非原子跨執行緒讀寫
目標:core\hw\pvr\Renderer_if.h:7, Renderer_if.cpp:22, ta_ctx.cpp:87, spg.cpp:184
問題:FrameCount 為普通 u32,多執行緒讀寫 → C++ data race (UB)
對策:將 u32 FrameCount 改為 std::atomic<u32> FrameCount
行數:Renderer_if.h 120→121(+1)
[#9] TA FSM ListEndInterrupt 陣列越界
目標:core\hw\pvr\ta.cpp:228-234
問題:ListEndInterrupt[5] 僅 5 元素(索引 0-4),ListType 5-7 時越界讀取
對策:在陣列存取前加邊界檢查
行數:595 → 598 (+3)
[#11] GDROM SPI_SET_MODE 整數下溢導致堆積溢寫
目標:core\hw\gdrom\gdromv3.cpp:826-827
問題:u32 Offset = data_8[2](0-255);10 - Offset 在 Offset>10 時無號下溢
為 0xFFFFFFFF → memcpy 溢寫堆積
對策:在計算前加 Offset >= 10 的 early break
行數:1471 → 1478 (+7)
[#12] GDROM SPI_REQ_MODE 緩衝區越界讀取
目標:core\hw\gdrom\gdromv3.cpp:739
問題:gd_spi_pio_end 中 offset 和 length 完全由遊戲控制,無邊界檢查
對策:加 offset + length <= sizeof(GD_HardwareInfo) 的邊界檢查
[#21] dc_reset 軟重設與 unloadGame 可能並行
目標:core\emulator.cpp:720-726
問題:dc_reset(false) 不在 Emulator::mutex 內,可能與 unloadGame 並行修改
對策:在 dc_reset 呼叫外包裹 std::lock_guard<std::mutex>
行數:1139 → 1142 (+3)
================================================================================
六、效能最佳化(6 項)
================================================================================
【最佳化 1】ta_thd_data32_i() — TA 資料接收瓶頸
目標:core/hw/pvr/ta.cpp:545-588
方法:非時態寫入 + 預取 + 批次展開 + PCW 讀取合併
行數:598 → 624 (+26)
修改:
1. 新增 simd256_t::stream_copy() 方法(非時態寫入)
2. 新增 #include <emmintrin.h>(x86 SSE2 標頭)
3. ta_thd_data32_i():使用 dst->stream_copy(data)、__builtin_prefetch、PCW 讀取合併
4. ta_vtx_data():批次展開從 4 次增加到 8 次
【最佳化 2】ReadFramebuffer() — 像素格式轉換 SIMD 化
目標:core/rend/TexCache.cpp:634-757
方法:SSE2 SIMD 加速 fbde_0555 和 fbde_565 格式的像素轉換
行數:1181 → 1258 (+77)
修改:
1. 新增 #include <emmintrin.h>、#include <type_traits>
2. fbde_0555 格式 SIMD 路徑:每次處理 4 個 16 位元像素
3. fbde_565 格式 SIMD 路徑:每次處理 4 個 16 位元像素
4. 標量回退路徑:保留原始程式碼處理剩餘像素
5. 條件編譯:#if HOST_CPU == CPU_X86
【最佳化 3】GLES Shader Uniform 迴圈 — 版本計數器延遲更新
目標:core/rend/gles/gles.cpp:1185-1190
方法:版本計數器機制,消除每幀 N 次 UseProgram + Set
行數:gles.h 582→586(+4)、gles.cpp 1371→1366(-5)、gldraw.cpp 1067→1073(+6)
修改:
1. PipelineShader 新增 u32 uniformVersion = 0
2. ShaderUniforms_t 新增 u32 version = 0
3. renderFrame 中 6 行迴圈取代為 ShaderUniforms.version++
4. SetGPState 中 UseProgram 後加延遲檢查
【最佳化 4】makeIndex / makePrimRestartIndex — reserve + is_vertex_inf 位元級最佳化
目標:core/hw/pvr/ta_util.cpp:520, 438, 29
方法:is_vertex_inf 位元級檢查 + idx 向量 reserve
行數:ta_util.cpp 608 → 625 (+17)
修改:
1. is_vertex_inf:使用 float_bits() 輔助函式,6 浮點運算 → 2 整數 AND+比較
2. makeIndex/makePrimRestartIndex:函式開頭加 reserve(size + totalVerts*2)
【最佳化 5】AppendPolyVertex — verts/idx 初始 reserve 提升
目標:core/hw/pvr/ta_ctx.h:359-360
行數:441 → 441 (0,常數值修改)
修改:rend.verts.reserve(32768) → 65536、rend.idx.reserve(32768) → 65536
理由:遊戲實測 vtx 最大 34,046、idx 最大 47,178,原始 32768 不足
【最佳化 6】Provoking Vertex 修正 — memcpy 合併
目標:core/rend/sorter.cpp:23
方法:將 4 次 4B memcpy 合併為 2 次 8B memcpy
行數:80 → 76 (-4)
修改:sizeof(vertex.col) + sizeof(vertex.spc) 合併複製
================================================================================
七、GLES u32→u16 索引轉換最佳化
================================================================================
[#6] GLES u32→u16 索引轉換 — resize + 自動向量化
目標:core/rend/gles/gles.cpp:1090(upload_vertex_indices)
行數:gles.cpp 1395 → 1397 (+2)
修改:
1. resize 替代 clear+reserve:單一呼叫設定大小
2. 直接賦值替代 push_back:消除每元素容量檢查分支
3. 緊密迴圈 + const 指標:GCC 13 可自動向量化(SSE2 pack 指令)
================================================================================
八、GLES/GL4 後端移植至新 core API
================================================================================
對策日期:2026-07-22
目標:core/rend/gles/ + core/rend/gl4/(整個 OpenGL/GLES 後端)
修改檔案(10 個):
CMakeLists.txt: USE_OPENGL 區塊加入 gles/gl4 原始碼 + 編譯定義 + opengl32
11.bat: USE_OPENGL=OFF → ON
wsi/context.h: 加 virtual void swap() {}
wsi/sdl.h: swap() → swap() override
gles/gles.h: TransformMatrix → TransformMatrix<COORD_OPENGL>
gles/gles.cpp: FlycastException×3, GLGraphicsContext cast, 移除swapInterval
gles/gldraw.cpp: setTileClip用GetTileClip, Rect→int[4], writeFramebufferToVRAM重寫
gles/gltex.cpp: fbClip→fb_X/Y_CLIP, WriteTextureToVRam去除clip參數
gl4/gldraw.cpp: FlycastException, setTileClip新API, Rect→int[4]
gl4/gles.cpp: getBaseScissor新API
解決的 12 個不相容點:
1. TransformMatrix 模板化
2. Rect 型別消除(改用 int clip_rect[4])
3. getTileClip → GetTileClip 自由函式
4. getBaseScissor → GetScissorMatrix + fb_X/Y_CLIP 計算
5. GraphicsContext 版本方法
6. swap() → GraphicsContext 加虛擬方法
7. setSwapInterval → 移除
8. globClip → ta_GLOB_TILE_CLIP 計算
9. fbClip → fb_X_CLIP/fb_Y_CLIP
10. swapInterval → 移除
11. RendererException → FlycastException
12. getWriteFBToVramParams/writeFramebufferToVRAM → 重寫
================================================================================
九、UploadToGPUGl4 immutable storage 尺寸/格式變化檢查缺失修復
================================================================================
目標:core/rend/gles/gltex.cpp:97-105
問題:UploadToGPUGl4 使用 glTexStorage2D 建立不可變紋理,但只在 texID == 0 時建立
後續紋理更新時不檢查尺寸/格式/mipmapLevels 是否一致
修改:
1. gles.h — TextureCacheData 加入 4 個 storage 追蹤成員變數
2. gltex.cpp — UploadToGPUGl4 加入屬性變化檢查 + 刪舊建新邏輯
行數:gles.h 586→590(+4)、gltex.cpp 532→542(+10)
================================================================================
十一、未修復項目(需外部配合)
================================================================================
[M-08] 未加密 WebSocket (ice.cpp:234, ws:// → wss://)
原因:需伺服器端(lobby.flyca.st)支援 wss://
[M-09] 硬編碼 TURN 憑證 (ice.cpp:436)
原因:需伺服器端 REST API 發放短期憑證
[M-10] NaomiNetwork 競態條件 (naomi_network.h)
原因:需架構調整(_startNow 改為 atomic)
================================================================================
記錄整理完成:2026-07-24
================================================================================
|
|