You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

文件/目录权限检查函数导致打印机相关操作异常

文件权限检查函数引发打印机操作异常问题

我编写了如下用于检查文件/目录访问权限的函数:

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;

关键修复点

  1. 在调用ImpersonateSelf后,通过try...finally确保无论后续操作成功与否,都会执行RevertToSelf()恢复线程原始安全上下文
  2. 增加了SecDesc的空值检查,避免无效内存操作
  3. 为OpenThreadToken添加try...finally确保句柄被正确关闭
  4. 优化了错误处理逻辑,减少嵌套层级,提升代码可读性

内容的提问来源于stack exchange,提问作者Mylon

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 17:42:16