文件/目录权限检查函数导致打印机相关操作异常
文件权限检查函数引发打印机操作异常问题
我编写了如下用于检查文件/目录访问权限的函数:
function CheckFileAccess(const FileName: string): Boolean; const FILE_READ_DATA = $0001; FILE_WRITE_DATA = $0002; FILE_APPEND_DATA = $0004; FILE_READ_EA = $0008; FILE_WRITE_EA = $0010; FILE_EXECUTE = $0020; FILE_READ_ATTRIBUTES = $0080; FILE_WRITE_ATTRIBUTES = $0100; FILE_GENERIC_READ = (STANDARD_RIGHTS_READ or FILE_READ_DATA or FILE_READ_ATTRIBUTES or FILE_READ_EA or SYNCHRONIZE); FILE_GENERIC_WRITE = (STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or FILE_WRITE_ATTRIBUTES or FILE_WRITE_EA or FILE_APPEND_DATA or SYNCHRONIZE); FILE_GENERIC_EXECUTE = (STANDARD_RIGHTS_EXECUTE or FILE_READ_ATTRIBUTES or FILE_EXECUTE or SYNCHRONIZE); FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $1FF; var Token: THandle; Status: LongBool; Access: Cardinal; SecDescSize: Cardinal; PrivSetSize: Cardinal; PrivSet: PRIVILEGE_SET; Mapping: GENERIC_MAPPING; SecDesc: PSECURITY_DESCRIPTOR; CheckedAccess: Cardinal; begin try Result :=False; CheckedAccess:=FILE_ALL_ACCESS; try GetFileSecurity(PChar(Filename), OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION, nil, 0, SecDescSize); SecDesc := GetMemory(SecDescSize); if GetFileSecurity(PChar(Filename), OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION, SecDesc, SecDescSize, SecDescSize) then begin ImpersonateSelf(SecurityImpersonation); OpenThreadToken(GetCurrentThread, TOKEN_QUERY, False, Token); if Token <> 0 then begin Mapping.GenericRead := FILE_GENERIC_READ; Mapping.GenericWrite := FILE_GENERIC_WRITE; Mapping.GenericExecute := FILE_GENERIC_EXECUTE; Mapping.GenericAll := FILE_ALL_ACCESS; MapGenericMask(Access, Mapping); PrivSetSize := SizeOf(PrivSet); AccessCheck(SecDesc, Token, CheckedAccess, Mapping, PrivSet, PrivSetSize, Access, Status); CloseHandle(Token); if Status then Result := Access = FILE_ALL_ACCESS; end; end; finally FreeMem(SecDesc, SecDescSize); end; except end; end;
调用该函数后,执行任何涉及打印机的操作(如打印报表、调用PrintDialog1.Execute)都会抛出“所选打印机不支持该操作”异常。多次检查代码未发现问题,开启ReportMemoryLeaksOnShutdown也未检测到内存泄漏。
问题根源与修复方案
问题原因
函数中调用ImpersonateSelf(SecurityImpersonation)切换了线程的安全上下文,但未调用RevertToSelf()恢复原始上下文。打印机操作依赖线程的原始安全上下文进行权限验证,未还原的模拟状态会导致打印机服务拒绝操作,触发异常。
此外,原代码未处理OpenThreadToken调用失败的情况(比如线程无有效令牌时,Token会为0),虽然不会导致内存泄漏,但存在逻辑漏洞。
修复后的函数代码
function CheckFileAccess(const FileName: string): Boolean; const FILE_READ_DATA = $0001; FILE_WRITE_DATA = $0002; FILE_APPEND_DATA = $0004; FILE_READ_EA = $0008; FILE_WRITE_EA = $0010; FILE_EXECUTE = $0020; FILE_READ_ATTRIBUTES = $0080; FILE_WRITE_ATTRIBUTES = $0100; FILE_GENERIC_READ = (STANDARD_RIGHTS_READ or FILE_READ_DATA or FILE_READ_ATTRIBUTES or FILE_READ_EA or SYNCHRONIZE); FILE_GENERIC_WRITE = (STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or FILE_WRITE_ATTRIBUTES or FILE_WRITE_EA or FILE_APPEND_DATA or SYNCHRONIZE); FILE_GENERIC_EXECUTE = (STANDARD_RIGHTS_EXECUTE or FILE_READ_ATTRIBUTES or FILE_EXECUTE or SYNCHRONIZE); FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED or SYNCHRONIZE or $1FF; var Token: THandle; Status: LongBool; Access: Cardinal; SecDescSize: Cardinal; PrivSetSize: Cardinal; PrivSet: PRIVILEGE_SET; Mapping: GENERIC_MAPPING; SecDesc: PSECURITY_DESCRIPTOR; CheckedAccess: Cardinal; begin Result := False; CheckedAccess := FILE_ALL_ACCESS; SecDesc := nil; try // 获取安全描述符所需大小 if GetFileSecurity(PChar(FileName), OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION, nil, 0, SecDescSize) then begin SecDesc := GetMemory(SecDescSize); if Assigned(SecDesc) and GetFileSecurity(PChar(FileName), OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION, SecDesc, SecDescSize, SecDescSize) then begin Token := 0; if ImpersonateSelf(SecurityImpersonation) then try if OpenThreadToken(GetCurrentThread, TOKEN_QUERY, False, Token) then try Mapping.GenericRead := FILE_GENERIC_READ; Mapping.GenericWrite := FILE_GENERIC_WRITE; Mapping.GenericExecute := FILE_GENERIC_EXECUTE; Mapping.GenericAll := FILE_ALL_ACCESS; MapGenericMask(Access, Mapping); PrivSetSize := SizeOf(PrivSet); if AccessCheck(SecDesc, Token, CheckedAccess, Mapping, PrivSet, PrivSetSize, Access, Status) and Status then begin Result := (Access = FILE_ALL_ACCESS); end; finally CloseHandle(Token); end; finally RevertToSelf; // 必须还原安全上下文 end; end; end; finally if Assigned(SecDesc) then FreeMem(SecDesc, SecDescSize); end; end;
关键修复点
- 在调用
ImpersonateSelf后,通过try...finally确保无论后续操作成功与否,都会执行RevertToSelf()恢复线程原始安全上下文 - 增加了
SecDesc的空值检查,避免无效内存操作 - 为
OpenThreadToken添加try...finally确保句柄被正确关闭 - 优化了错误处理逻辑,减少嵌套层级,提升代码可读性
内容的提问来源于stack exchange,提问作者Mylon
相关产品推荐
相关产品推荐

