编写安全的SQLServer扩展存储过程

编写安全的SQLServer扩展存储过程,第1张

编写安全的SQLServer扩展存储过程,第2张

SQL Server的扩展存储过程其实就是一个普通的Windows DLL,只是按照一些规则实现了一些功能。

最近在写一个扩展存储过程的时候,发现写这种动态库还是有一些需要特别注意的地方。之所以特别关注,是因为DLL运行在SQL Server的地址空之间,SQL Server如何调度线程是我们所不了解的,即使了解也无法控制。

我们一般都是自己写动态库自己用,就算给别人用,也很少像SQL Server那样。一个动态库很可能会加载很多次,全部加载到一个进程的地址空中。我们知道,当一个动态库加载到进程的address 空中时,DLL的所有全局和局部变量只初始化一次,以后再次调用LoadLibrary函数时,只增加引用计数。所以很明显,如果有一个全局int,初始化为0,调用一个函数把它加到自己身上,此时它的值为1,那么再次调用LoadLibray,调用output函数利用返回的句柄输出。Windows是进程无关的,在线程方面,如果不注意,上述情况很可能会给程序员带来麻烦。

介绍我的扩展存储过程。这个动态库导出三个函数:Init、work、Final,Init读取文件并将信息存储在内存中。工作只是从这个内存中检索信息,并最终回收内存。如上所述,如果不考虑同一个进程多次加载的问题空,调用Init两次会造成不必要的浪费,因为我第一次已经读入内存了,如果我通过堆分配内存,也会造成内存泄漏。

我使用引用计数来解决这个问题。代码很短,直接粘贴:

# include " STD afx . h "
# include

使用命名空间std

extern " C " {
RETCODE _ _ declspec(dll export)XP _ part _ init(SRV _ PROC * srvproc);
RETCODE _ _ declspec(dllexport)XP _ part _ process(SRV _ PROC * srvproc);
RETCODE _ _ declspec(dllexport)XP _ part _ finalize(SRV _ PROC * srvproc);
}

# define XP _ no ERROR 0
# define XP _ ERROR 1

HINSTANCE hInst = NULL
int nRef = 0;

void print error(SRV _ PROC * psrv PROC,CHAR * szErrorMsg);

ULONG _ _ getx VERSION(){ return ODS _ VERSION;}

SRVRETCODE XP _ part _ init(SRV _ PROC * pSrvProc){
typedef bool(* Func)();

if(nRef = = 0){
hInst =::LoadLibrary(" part . dll ");
if(hinst = = null){
printerror(psrvroc,"无法加载part . dll ");
返回XP _ ERROR
}
Func the Func =(Func)::GetProcAddress(hInst," Init ");
如果(!theFunc()){
::free library(hInst);
printerror (psrvroc,“无法获取分类号与相册的对应表”);
返回XP _ ERROR
}
}

++ nRef;
return(XP _ no error);
}

SRVRETCODE XP _ part _ process(SRV _ PROC * pSrvProc){
typedef bool(* Func)(char *);

if(nref = = 0){
printError(psrvroc,"函数尚未初始化,请先调用xp _ part _ init
返回XP _ ERROR
}
Func the Func =(Func)::GetProcAddress(hInst," Get ");

字节bType
ULONG cbMaxLen,cbActualLen
BOOL fNull;
char SZ input[256]= { 0 };

If (srv _ paraminfo (psrvroc,1,& btype,(ulong *) & cbmaxlen,(ulong *) & cbactualllen,(byte *) szinput,& fnull)= = FAIL){
printerror(psrvroc," SRV _
return XP _ ERROR;
}
SZ input[cbActualLen]= 0;

string strInput = szInput
string strOutput = ";";
int cur,old = 0;
while(string::npos!=(cur = strinput . find(';'),old)) ){
strncpy(szInput,strInput.c_str() + old,cur-old);
SZ input[cur-old]= 0;
old = cur+1;
theFunc(SZ input);

if(string::NPOs = = stroutput . find((string)";"+SZ input))
strOutput+= SZ input;
}

strcpy(szInput,str output . c _ str());
if(fail = = srv _ paramsettout(psrv roc,1,(byte *) (szinput+1),strlen (szinput)-1,false)){
printerror(psrv roc," SRV _ paramsettout调用失败。
返回XP _ ERROR
}

srv_senddone(pSrvProc,(SRV_DONE_COUNT | SRV_DONE_MORE),0,0);
返回XP _ NOERROR
}

SRVRETCODE XP _ part _ finalize(SRV _ PROC * pSrvProc){
typedef void(* Func)();

if(nRef = = 0)
返回XP _ NOERROR
Func the Func =(Func)::GetProcAddress(hInst," Fin ");

if((-nRef)= = 0){
the func();
*免费图书馆(hInst);
hInst = NULL;
}
return(XP _ no error);
}


我觉得虽然看起来不太聪明,但是问题应该是解决了。

还有一点,为什么不用Tls?老实说,我考虑过用它,因为代码有问题。如果一个用户调用xp_part_init,然后另一个用户调用xp_part_init,注意我们的存储过程是服务器端的,然后第一个用户调用xp_part_finalize,会发生什么情况?他还是可以正常使用xp_part_process的,这没关系。但是第一个用户可以通过调用xp_part_finalize两次来影响第二个用户,他的xp_part_process会返回一个错误。

似乎Tls可以解决这个问题。例如,添加另一个tls_index变量,调用TlsSetValue保存用户的私有数据,调用TlsGetValue检索私有数据。当xp_part_init为0时,执行正常的初始化过程,即在上述xp_part_init成功执行后,将私有数据存储为1。如果是1,直接返回。xp_part_finalize时,如果私有数据为1,则执行正常的xp_part_finalize,然后将私有数据设置为0。如果是0,直接返回。

看起来想法不错,这样隔离了多个用户,安全性似乎提高了不少。然而,事实并不可行。因为Tls保存的不是私有数据,而是线程局部变量,所以我们不能保证一个用户的多个操作都是由同一个线程执行的。这由SQL Server本身控制。事实上,我在查询分析器中多次执行的结果显示,在SQL Server内部似乎使用了一个线程池。在这种情况下,只能放弃这个想法。

位律师回复
DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
白度搜_经验知识百科全书 » 编写安全的SQLServer扩展存储过程

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情