【发布时间】:2010-03-24 15:08:15
【问题描述】:
我们维护一个 DLL,它可以做很多与系统相关的事情;遍历文件系统、注册表等。此 DLL 的调用者可能使用模拟,也可能不使用模拟。为了更好地支持所有可能的场景,我正在尝试将其修改为更智能。我将使用删除文件的示例。目前我们只调用 DeleteFile(),如果失败,那就结束了。我想出了以下几点:
BOOL TryReallyHardToDeleteFile(LPCTSTR lpFileName)
{
// 1. caller without privilege
BOOL bSuccess = DeleteFile(lpFileName);
DWORD dwError = GetLastError();
if(!bSuccess && dwError == ERROR_ACCESS_DENIED)
{
// failed with access denied; try with privilege
DWORD dwOldRestorePrivilege = 0;
BOOL bHasRestorePrivilege = SetPrivilege(SE_RESTORE_NAME, SE_PRIVILEGE_ENABLED, &dwOldRestorePrivilege);
if(bHasRestorePrivilege)
{
// 2. caller with privilege
bSuccess = DeleteFile(lpFileName);
dwError = GetLastError();
SetPrivilege(SE_RESTORE_NAME, dwOldRestorePrivilege, NULL);
}
if(!bSuccess && dwError == ERROR_ACCESS_DENIED)
{
// failed with access denied; if caller is impersonating then try as process
HANDLE hToken = NULL;
if(OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_IMPERSONATE, TRUE, &hToken))
{
if(RevertToSelf())
{
// 3. process without privilege
bSuccess = DeleteFile(lpFileName);
dwError = GetLastError();
if(!bSuccess && dwError == ERROR_ACCESS_DENIED)
{
// failed with access denied; try with privilege
bHasRestorePrivilege = SetPrivilege(SE_RESTORE_NAME, SE_PRIVILEGE_ENABLED, &dwOldRestorePrivilege);
if(bHasRestorePrivilege)
{
// 4. process with privilege
bSuccess = DeleteFile(lpFileName);
dwError = GetLastError();
SetPrivilege(SE_RESTORE_NAME, dwOldRestorePrivilege, NULL);
}
}
SetThreadToken(NULL, hToken);
}
CloseHandle(hToken);
hToken = NULL;
}
}
}
if(!bSuccess)
{
SetLastError(dwError);
}
return bSuccess;
}
所以首先它尝试作为调用者。如果访问被拒绝而失败,它会暂时启用调用者令牌中的权限并重试。如果访问被拒绝而失败并且调用者正在模拟,它会暂时取消模拟并重试。如果访问被拒绝而失败,它会暂时启用进程令牌中的权限并重试。我认为这应该可以处理几乎任何情况,但我想知道是否有更好的方法来实现这一点?有很多我们可能想要使用此方法的操作(即几乎所有访问安全对象的操作)。
【问题讨论】:
-
为什么不直接跳到最后一个选项呢?没有异常处理,如果我正确阅读了您的解释,保证可以工作。
-
这不处理进程作为 LocalSystem(即服务)运行并且我们需要读取网络位置上的文件(即只有模拟的用户帐户可以访问文件)的情况.
-
@Chris:这也违背了最小特权的心态。您应该只需要该操作所需的权限,仅此而已。这是做事更难的方式,但更“理想”的方式。
-
@Ioan - 我没有想到这一点。我想这取决于早期尝试失败的可能性有多大。
-
你错过了只读属性标志:)