Skip to content

Commit e38511c

Browse files
committed
feat: finish implementing new IsClosed property
It is now used to discard a closed or invalid handle during the keep() function and SafeHandleEx ctors
1 parent 28b576d commit e38511c

File tree

4 files changed

+70
-16
lines changed

4 files changed

+70
-16
lines changed

deadlock-dotnet-sdk/Domain/FileLockerEx.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
namespace deadlock_dotnet_sdk.Domain
55
{
66
//TODO: Add RefreshList(). This should clear Lockers and call FindLockingHandles again.
7-
//TODO: If a handle is closed or invalid, remove it from Lockers. SafeHandle.IsClosed is unreliable—it only works on handles managed by the current process.
87
//https://sourcegraph.com/github.com/dotnet/runtime@main/-/blob/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs
98
public class FileLockerEx
109
{

deadlock-dotnet-sdk/Domain/NativeMethods.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ bool keep(SafeFileHandleEx h)
175175
{
176176
bool keep = false;
177177

178+
if (h.IsClosed)
179+
return false;
180+
178181
if (!string.IsNullOrEmpty(query))
179182
{
180183
// only keep if FullFilePath contains query (with normalized directory separators)

deadlock-dotnet-sdk/Domain/SafeFileHandleEx.cs

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ internal SafeFileHandleEx(SafeHandleEx safeHandleEx) : this(safeHandleEx.SysHand
4040
/// <param name="sysHandleEx"></param>
4141
internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(sysHandleEx: sysHandleEx)
4242
{
43+
if (IsClosed)
44+
{
45+
ExceptionLog.Add(new NullReferenceException("This handle was closed before it was passed to this SafeFileHandleEx constructor."));
46+
return;
47+
}
48+
4349
if (IsFileHandle.v is true)
4450
{
4551
try
@@ -71,7 +77,10 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(
7177
{
7278
if (isDirectory is (null, null))
7379
{
80+
const string errUnableMsg = "Unable to query " + nameof(IsDirectory) + "; ";
7481
const string errFailedMsg = "Failed to query " + nameof(IsDirectory) + "; ";
82+
if (IsClosed)
83+
return isDirectory = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
7584
FILE_ATTRIBUTE_TAG_INFO attr = default;
7685
bool success;
7786
unsafe
@@ -92,11 +101,25 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(
92101
}
93102
}
94103

95-
public (bool? v, Exception? ex) IsFileHandle => isFileHandle is (null, null)
96-
? HandleObjectType.v == "File"
104+
public (bool? v, Exception? ex) IsFileHandle
105+
{
106+
get
107+
{
108+
if (isFileHandle is (null, null))
109+
{
110+
const string errUnableMsg = "Unable to query " + nameof(IsFileHandle) + "; ";
111+
if (IsClosed)
112+
return isFileHandle = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
113+
return HandleObjectType.v == "File"
97114
? (isFileHandle = (true, null))
98-
: (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex)))
99-
: isFileHandle;
115+
: (isFileHandle = (null, new Exception("Failed to determine if this handle's object is a file/directory; Failed to query the object's type.", HandleObjectType.ex)));
116+
}
117+
else
118+
{
119+
return isFileHandle;
120+
}
121+
}
122+
}
100123

101124
/// <summary>
102125
/// TRUE if the file object's path is a network path i.e. SMB2 network share. FALSE if the file was opened via a local disk path.
@@ -126,6 +149,11 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(
126149
{
127150
if (isFilePathRemote is (null, null))
128151
{
152+
const string errUnableMsg = "Unable to query " + nameof(IsFilePathRemote) + "; ";
153+
const string errFailedMsg = "Failed to query " + nameof(IsFilePathRemote) + "; ";
154+
if (IsClosed)
155+
return isFilePathRemote = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
156+
129157
Win32ErrorCode err;
130158
FILE_REMOTE_PROTOCOL_INFO info;
131159
unsafe
@@ -134,7 +162,7 @@ internal SafeFileHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(
134162
? (isFilePathRemote = (true, null))
135163
: (err = (Win32ErrorCode)Marshal.GetLastPInvokeError()) is Win32ErrorCode.ERROR_INVALID_PARAMETER
136164
? (isFilePathRemote = (false, null))
137-
: (isFilePathRemote = (null, new Win32Exception(err)));
165+
: (isFilePathRemote = (null, new Win32Exception(err, errFailedMsg + err.GetMessage())));
138166
}
139167
}
140168
else
@@ -155,10 +183,12 @@ public unsafe (string? v, Exception? ex) FileFullPath
155183
{
156184
if (fileFullPath is (null, null))
157185
{
186+
const string errUnableMsg = "Unable to query " + nameof(FileFullPath) + "; ";
187+
const string errFailedMsg = "Failed to query " + nameof(FileFullPath) + "; ";
188+
if (IsClosed)
189+
return fileFullPath = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
158190
try
159191
{
160-
const string errUnableMsg = "Unable to query " + nameof(FileFullPath) + "; ";
161-
const string errFailedMsg = "Failed to query " + nameof(FileFullPath) + "; ";
162192
if (ProcessInfo.ProcessProtection.v is null)
163193
return fileFullPath = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ProcessInfo.ProcessProtection.ex));
164194
if (ProcessInfo.ProcessProtection.v?.Type is PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeProtected)
@@ -288,6 +318,8 @@ public unsafe (string? v, Exception? ex) FileFullPath
288318
{
289319
const string errUnableMsg = "Unable to query " + nameof(FileHandleType) + "; ";
290320
const string errFailedMsg = "Failed to query " + nameof(FileHandleType) + "; ";
321+
if (IsClosed)
322+
return fileHandleType = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
291323
if (ProcessInfo.ProcessProtection.ex is not null)
292324
return fileHandleType = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level."));
293325
if (ProcessInfo.ProcessProtection.ex is not null)
@@ -320,6 +352,8 @@ public unsafe (string? v, Exception? ex) FileFullPath
320352
{
321353
const string errUnableMsg = "Unable to query " + nameof(FileName) + "; ";
322354
const string errFailedMsg = "Failed to query " + nameof(FileName) + "; ";
355+
if (IsClosed)
356+
return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
323357
if (FileFullPath.v is not null)
324358
{
325359
getFileOrDirectoryName(FileFullPath.v);
@@ -370,6 +404,8 @@ public unsafe (string? v, Exception? ex) FileNameInfo
370404
{
371405
const string errUnableMsg = "Unable to query " + nameof(FileNameInfo) + "; ";
372406
const string errFailedMsg = "Failed to query " + nameof(FileNameInfo) + "; ";
407+
if (IsClosed)
408+
return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
373409
if (ProcessInfo.ProcessProtection.ex is not null)
374410
return fileNameInfo = (null, new NullReferenceException(errUnableMsg + "Failed to query the process's protection level.", ProcessInfo.ProcessProtection.ex));
375411
if (ProcessInfo.ProcessProtection.v?.Type is not PS_PROTECTION.PS_PROTECTED_TYPE.PsProtectedTypeNone)

deadlock-dotnet-sdk/Domain/SafeHandleEx.cs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
namespace deadlock_dotnet_sdk.Domain;
1616

17-
//TODO: check if handle is closed. If true, FileLockerEx can remove this object from its locker list. See relevant TODO in FileLockerEx
1817
/// <summary>
1918
/// A SafeHandleZeroOrMinusOneIsInvalid wrapping a SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX<br/>
2019
/// Before querying for system handles, call <see cref="Process.EnterDebugMode()"/>
@@ -24,7 +23,9 @@ namespace deadlock_dotnet_sdk.Domain;
2423
/// </summary>
2524
public class SafeHandleEx : SafeHandleZeroOrMinusOneIsInvalid
2625
{
26+
protected const string errHandleClosedMsgSuffix = "The handle is closed.";
2727
protected (string? v, Exception? ex) handleObjectType;
28+
private bool isClosed;
2829
protected (string? v, Exception? ex) objectName;
2930
private ProcessInfo? processInfo;
3031

@@ -57,10 +58,14 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals
5758
{
5859
if (handleObjectType is (null, null))
5960
{
61+
const string errUnableMsg = "Unable to query " + nameof(HandleObjectType) + "; ";
62+
const string errFailedMsg = "Failed to query " + nameof(HandleObjectType) + "; ";
63+
if (IsClosed)
64+
return handleObjectType = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
6065
var (v, ex) = ProcessInfo.ProcessProtection;
6166
if (v is null)
6267
{
63-
return handleObjectType = (null, new InvalidOperationException("Unable to query the kernel object's Type; Failed to query the process's protection:\r\n" + ex, ex));
68+
return handleObjectType = (null, new InvalidOperationException(errUnableMsg + "Failed to query the process's protection.", ex));
6469
}
6570
else if (v.Value.Type is PsProtectedTypeNone or PsProtectedTypeProtectedLight)
6671
{
@@ -70,12 +75,12 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals
7075
}
7176
catch (Exception e)
7277
{
73-
return handleObjectType = (null, e);
78+
return handleObjectType = (null, new InvalidOperationException(errFailedMsg + e.Message, e));
7479
}
7580
}
7681
else
7782
{
78-
return handleObjectType = (null, new UnauthorizedAccessException("Unable to query the kernel object's Type; The process is protected."));
83+
return handleObjectType = (null, new UnauthorizedAccessException(errUnableMsg + "The process is protected."));
7984
}
8085
}
8186
else
@@ -88,20 +93,29 @@ internal SafeHandleEx(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX sysHandleEx) : base(fals
8893
/// <summary>
8994
/// (non-persistent) Pass the handle to GetHandleInformation and check for ERROR_INVALID_HANDLE to determine if the handle is open or closed.
9095
/// </summary>
91-
public new bool IsClosed => GetIsClosed();
96+
public new bool IsClosed
97+
{
98+
get
99+
{
100+
return isClosed ? isClosed : (isClosed = GetIsClosed());
101+
}
102+
}
92103

93104
private bool GetIsClosed()
94105
{
95106
try
96107
{
97108
HANDLE_FLAGS flags = GetHandleInformation(this);
98109
}
99-
catch (PInvoke.Win32Exception ex) when (ex.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_HANDLE)
110+
catch (Exception ex) when (
111+
(ex is Win32Exception inWin32Exception && inWin32Exception.NativeErrorCode is (int)Win32ErrorCode.ERROR_INVALID_HANDLE)
112+
|| (ex is PInvoke.Win32Exception piWin32Exception && piWin32Exception.NativeErrorCode is Win32ErrorCode.ERROR_INVALID_HANDLE))
100113
{
101114
return true;
102115
}
103-
catch (PInvoke.Win32Exception ex)
116+
catch (Exception ex)
104117
{
118+
Console.Error.WriteLine($"GetIsClosed failed; {ex.ToString}");
105119
Trace.TraceError(ex.ToString());
106120
}
107121
return false;
@@ -122,10 +136,12 @@ public unsafe (string? v, Exception? ex) ObjectName
122136
{
123137
get
124138
{
125-
if (objectName == default)
139+
if (objectName is (null, null))
126140
{
127141
const string errUnableMsg = "Unable to query " + nameof(ObjectName) + "; ";
128142
const string errFailedMsg = "Failed to query " + nameof(ObjectName) + "; ";
143+
if (IsClosed)
144+
return objectName = (null, new NullReferenceException(errUnableMsg + errHandleClosedMsgSuffix));
129145
var (v, ex) = ProcessInfo.ProcessProtection;
130146
// I'm assuming process protection prohibits access. I've not tested it.
131147
// This information is not queryable in SystemInformer when a process has Full protection.

0 commit comments

Comments
 (0)