【发布时间】:2014-08-08 15:27:48
【问题描述】:
我注意到Marshal.GetLastWin32Error() 在对Kernal32.CloseHandle(IntPtr p_handle) 执行p/invoke 后的dispose 方法期间返回错误122,即使在导入定义中将导入属性SetLastError 设置为true。我将原因追踪到在句柄关闭之前创建new WindowsIdentity,我想知道处理这个问题的正确方法是什么。
- 是否应该仅在 CloseHandle 返回 false 时检查 Win32 错误?
- 在 p/调用 CloseHandle 之前强制 SetLastError 为 0?
- 在
new WindowsIdentity(DangereousHandle)之后将LastError 强制设置为0
第 3 点对我来说似乎很不合理,所以我认为这显然不是。
第 2 点似乎最合理,但可能是不必要的激进。我选择这个而不是第 1 点的唯一原因是因为我不确定我是否能保证如果 CloseHandle 返回 false 错误代码将是相关的。功能文档能保证这一点吗?
第 1 点似乎是我应该做的。也就是说,我应该只在 CloseHandle 显式返回 false 时调用 GetLastWin32Error,并假设返回的代码将指示 CloseHandle 失败的原因,而不是一些先前不相关的 Win API 调用。
代码
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security;
namespace Common.InterOp
{
// CloseHandle http://msdn.microsoft.com/en-ca/library/windows/desktop/ms724211%28v=vs.85%29.aspx
internal static partial class Kernel32
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
[SuppressUnmanagedCodeSecurity]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool CloseHandle(IntPtr handle);
}
}
这里是关闭句柄被调用的地方
using Common.InterOp;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.InteropServices;
namespace Common.InterOp
{
// http://msdn.microsoft.com/en-us/library/system.security.principal.windowsimpersonationcontext%28v=vs.100%29.aspx
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{ }
protected override bool ReleaseHandle()
{
int Win32Error =
Marshal.GetLastWin32Error(); // <-- Already has code 122
var Closed =
Kernel32.CloseHandle(handle); // <-- has SetLastError=true attribute
Win32Error =
Marshal.GetLastWin32Error(); // <-- Still has code 122
if (!Closed && Win32Error != 0) // This works, but is it correct?
throw
new Win32Exception(Win32Error);
return Closed;
}
}
}
这是在具有 var WinID = new WindowsIdentity(DangerousHandle); 的行上实际生成错误 122 的代码块
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using Common.InterOp;
using Common.Environment;
namespace Common.Authentication
{
public static partial class Authentication
{
/// <summary>
/// Authenticates the log on credentials of a Windows user.
/// Returns a WindowsIdentity if authentication succeeds for the specified user; Otherwise returns null.
/// </summary>
/// <param name="p_userName">The user's Windows account name.</param>
/// <param name="p_password">The user's Windows account password.</param>
/// <param name="p_domainName">The user's domain name or the name of the local machine for non-networked accounts.</param>
/// <returns>a <seealso cref="WindowsIdentity"/> if authentication succeeds for the specified user; Otherwise returns null.</returns>
/// <exception cref="System.ArgumentException"></exception>
/// <exception cref="System.ComponentModel.Win32Exception"></exception>
/// <exception cref="System.Security.SecurityException"></exception>
public static WindowsIdentity LogonUser(SecureString p_userName, SecureString p_password, SecureString p_domainName, LogonUserOptions p_logonOptions)
{
// The following functions seem related, but it's unclear if or in what scenario they would ever be required
// DuplicateToken http://msdn.microsoft.com/en-us/library/windows/desktop/aa446616%28v=vs.85%29.aspx
// DuplicateTokenEx http://msdn.microsoft.com/en-us/library/windows/desktop/aa446617%28v=vs.85%29.aspx
SafeTokenHandle UserAccountToken = null;
try
{
using (var DecryptedUserName = new DecryptAndMarshallSecureString(p_userName))
using (var DecryptedPassword = new DecryptAndMarshallSecureString(p_password))
using (var DecryptedDomainName = new DecryptAndMarshallSecureString(p_domainName))
{
// Call LogonUser, passing the unmanaged (and decrypted) copies of the SecureString credentials.
bool ReturnValue =
AdvApi32.LogonUser(
p_userName: DecryptedUserName.GetHandle(),
p_domainName: DecryptedDomainName.GetHandle(),
p_password: DecryptedPassword.GetHandle(),
p_logonType: p_logonOptions.Type, // LogonType.LOGON32_LOGON_INTERACTIVE,
p_logonProvider: p_logonOptions.Provider, // LogonProvider.LOGON32_PROVIDER_DEFAULT,
p_userToken: out UserAccountToken);
// Get the Last win32 Error and throw an exception.
int Win32Error = Marshal.GetLastWin32Error();
if (!ReturnValue && UserAccountToken.DangerousGetHandle() == IntPtr.Zero)
throw
new Win32Exception(Win32Error);
// The token that is passed to the following constructor must
// be a primary token in order to use it for impersonation.
var DangerousHandle = UserAccountToken.DangerousGetHandle();
Win32Error = Marshal.GetLastWin32Error(); // No error
var WinID = new WindowsIdentity(DangerousHandle);
Win32Error = Marshal.GetLastWin32Error(); // error 122
// Kernel32.SetLastError(0); // Resets error to 0
Sleep.For(0);
//new Win32Exception(Win32Error); // Also resets error to 0
Win32Error = Marshal.GetLastWin32Error(); // Still error 122 unless one of the reset lines above is uncommented
return WinID;
}
}
finally
{
if (UserAccountToken != null)
UserAccountToken.Dispose(); // This calls CloseHandle which is where I first noticed the error being non-zero
}
}
}
}
编辑 2014, 08, 09
根据 hvd 的 cmets 和答案更新了类。
using Common.Attributes;
using Microsoft.Win32.SafeHandles;
using System.ComponentModel;
using System.Runtime.ConstrainedExecution;
namespace Common.InterOp
{
// SafeHandle.ReleaseHandle Method http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.safehandle.releasehandle%28v=vs.110%29.aspx
// http://msdn.microsoft.com/en-us/library/system.security.principal.windowsimpersonationcontext%28v=vs.100%29.aspx
// http://stackoverflow.com/questions/25206969/shouldnt-getlastwin32error-be-reset-if-p-invoke-attribute-setlasterror-true
[jwdebug(2014, 08, 09, "releaseHandleFailed MDA http://msdn.microsoft.com/en-us/library/85eak4a0%28v=vs.110%29.aspx")]
public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeTokenHandle()
: base(true)
{ }
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
override protected bool ReleaseHandle()
{
// Here, we must obey all rules for constrained execution regions.
return
Kernel32.CloseHandle(handle);
// If ReleaseHandle failed, it can be reported via the
// "releaseHandleFailed" managed debugging assistant (MDA). This
// MDA is disabled by default, but can be enabled in a debugger
// or during testing to diagnose handle corruption problems.
// We do not throw an exception because most code could not recover
// from the problem.
}
}
}
【问题讨论】:
-
虽然与您的实际问题无关,但看起来您根本不应该抛出任何异常。
SafeHandle.ReleaseHandle上的示例甚至使用CloseHandle,就像您的课程一样:如果失败,它只会返回false。 -
@hvd 太棒了...现在我需要在某个时候阅读 MDA...也许某个晚上我失眠的时候。感谢您提供额外信息!我将使用现在的代码为我的问题添加一个编辑。
标签: c# .net-4.0 pinvoke unmanaged