首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

編寫安全的 SQL Server 擴展預存程序:DLL 參考計數與執行緒安全實務

     編輯:關於SqlServer

擴展預存程序本質

本質是 Windows DLL,依 ODS 規範匯出 xp_* 函數。DLL 載入 SQL Server 進程位址空間,全域/靜態變數僅初始化一次,後續 LoadLibrary 僅增加參考計數。若不處理重複載入,會造成資源洩漏或狀態污染。

案例:三函數介面

  • xp_part_init:讀檔案載入記憶體結構
  • xp_part_process:記憶體內查詢
  • xp_part_finalize:釋放記憶體

參考計數解決重複載入

HINSTANCE hInst = NULL;
int nRef = 0;

SRVRETCODE xp_part_init(SRV_PROC* pSrvProc) {
    if (nRef == 0) {
        hInst = ::LoadLibrary("part.dll");
        if (!hInst) { /* 錯誤處理 */ return XP_ERROR; }
        auto theFunc = (bool(*)())::GetProcAddress(hInst, "Init");
        if (!theFunc()) { ::FreeLibrary(hInst); /* 錯誤 */ return XP_ERROR; }
    }
    ++nRef;
    return XP_NOERROR;
}

SRVRETCODE xp_part_finalize(SRV_PROC* pSrvProc) {
    if (nRef == 0) return XP_NOERROR;
    auto theFunc = (void(*)())::GetProcAddress(hInst, "Fin");
    if (--nRef == 0) {
        theFunc();
        ::FreeLibrary(hInst);
        hInst = NULL;
    }
    return XP_NOERROR;
}

首次 init 載入 DLL,最後一次 finalize 釋放,中間 process 直接複用記憶體資料。

為何不用 TLS (Thread Local Storage)?

嘗試用 TlsAlloc/SetValue/GetValue 隔離使用者狀態,但發現:
- SQL Server 內部使用執行緒池,同一使用者的多次呼叫可能落在不同執行緒。
- TLS 綁定執行緒而非工作階段/連線,無法保證同一使用者看到相同 TLS 值。
- 實測查詢分析器多次執行,執行緒 ID 不固定,證實執行緒池機制。
結論:TLS 無法解決多使用者隔離,反而引入錯誤。

安全性補充建議

  • 參考計數必須執行緒安全:若 SQL Server 並行呼叫 init/finalize,需用 InterlockedIncrement/Decrement 或關鍵區段保護 nRef。
  • 避免全域狀態:盡量將狀態封裝在每次呼叫的堆疊或堆積記憶體,透過 srv_proc 關聯,而非 DLL 全域變數。
  • 嚴格檢查輸入參數:srv_paraminfo 取得長度後,務必手動加結尾 \0 防緩衝區溢位。
  • 最小權限原則:DLL 內若需存取檔案/網路,以 SQL Server 服務帳號權限為準,勿提權。

結語

擴展預存程序運行於 SQL Server 核心進程,一行錯誤代碼即可癱瘓整個資料庫引擎。參考計數解決資源管理,拒絕 TLS 避免執行緒池陷阱,嚴守邊界檢查與最小權限,是編寫安全擴展預存程序的鐵律。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved