-
Notifications
You must be signed in to change notification settings - Fork 35
/
NtUtils.Errors.pas
293 lines (228 loc) · 8.79 KB
/
NtUtils.Errors.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
unit NtUtils.Errors;
{
This module provides support for manipulating and converting error codes
between NTSTATUS, HRESULT, and Win32 Error formats.
}
interface
uses
Ntapi.WinNt, Ntapi.ntdef;
// RtlGetLastNtStatus with extra checks to ensure the result is correct
function RtlxGetLastNtStatus(EnsureUnsuccessful: Boolean = False): NTSTATUS;
type
TNtStatusHelper = record helper for NTSTATUS
// Checks
function IsSuccess: Boolean;
function IsWin32Error: Boolean;
function IsHResult: Boolean;
// Conversion
function ToHResult: HResult;
function ToWin32Error: TWin32Error;
function Canonicalize: NTSTATUS;
// Representation
function ToString: String;
end;
THResultHelper = record helper for HResult
// Checks
function IsSuccess: Boolean;
function IsWin32Error: Boolean;
function IsNtStatus: Boolean;
// Conversion
function ToNtStatus: NTSTATUS;
function Canonicalize: HResult;
// Representation
function ToString: String;
end;
TWin32ErrorHelper = record helper for TWin32Error
// Conversions
function ToHResult: HResult;
function ToNtStatus: NTSTATUS;
// Representation
function ToString: String;
end;
var
// A custom callback for representing errors (provided by NtUiLib.Errors)
RtlxNtStatusRepresenter: function (Status: NTSTATUS): String;
implementation
uses
Ntapi.ntrtl, Ntapi.WinError, Ntapi.ntstatus, NtUtils.SysUtils;
{$BOOLEVAL OFF}
{$IFOPT R+}{$DEFINE R+}{$ENDIF}
{$IFOPT Q+}{$DEFINE Q+}{$ENDIF}
const
// For NTSTATUS, indicates that the underlying error comes from an HRESULT;
// For HRESULT, indicates that the underlying error comes from an NTSTATUS.
FACILITY_SWAP_BIT = Ntapi.WinError.FACILITY_NT_BIT;
function RtlxGetLastNtStatus;
begin
// If the last Win32 error was set using RtlNtStatusToDosError followed by
// RtlSetLastWin32Error (aka SetLastError), the LastStatusValue in TEB should
// contain the correct NTSTATUS value. The way to check the correctness is to
// convert the status to a Win32 error and compare with LastErrorValue from
// TEB. If, for some reason, they don't match, return a fake NTSTATUS with a
// Win32 facility.
// Note that RtlNtStatusToDosError ignores the NT facility bit. If we got a
// match and this bit is set, somebody probably passed it an HRESULT with an
// NTSTATUS packed inside. Clear this bit as a workaround.
if RtlNtStatusToDosErrorNoTeb(RtlGetLastNtStatus) = RtlGetLastWin32Error then
Result := RtlGetLastNtStatus and not FACILITY_NT_BIT
else
case RtlGetLastWin32Error of
// Explicitly convert buffer-related errors
ERROR_INSUFFICIENT_BUFFER: Result := STATUS_BUFFER_TOO_SMALL;
ERROR_MORE_DATA: Result := STATUS_BUFFER_OVERFLOW;
ERROR_BAD_LENGTH: Result := STATUS_INFO_LENGTH_MISMATCH;
// After converting, ERROR_SUCCESS becomes unsuccessful, fix it
ERROR_SUCCESS: Result := STATUS_SUCCESS;
// Common errors which we might want to compare
ERROR_ACCESS_DENIED: Result := STATUS_ACCESS_DENIED;
ERROR_PRIVILEGE_NOT_HELD: Result := STATUS_PRIVILEGE_NOT_HELD;
else
Result := RtlGetLastWin32Error.ToNtStatus;
end;
// Sometimes WinApi functions can fail with ERROR_SUCCESS. If necessary,
// make sure that failures always result in an unsuccessful status.
if EnsureUnsuccessful and Result.IsSuccess then
Result := RtlGetLastWin32Error.ToNtStatus;
end;
{ TNtStatusHelper }
function TNtStatusHelper.Canonicalize;
begin
// The only ambiguity we have is with Win32 Errors. They can appear within
// either an HRESULT or an NTSTATUS. We call NTSTATUS being canonical when
// Win32 Errors appear in it directly (without the facility swap bit).
// NTSTATUS_FROM_WIN32 yields this result (in form of 0xC007xxxx); inline it.
if IsWin32Error then
Result := WIN32_NTSTATUS_BITS or (Self and WIN32_CODE_MASK)
else
Result := Self;
end;
function TNtStatusHelper.IsHResult;
begin
// Just like HRESULTs can store NTSTATUSes using the NT Facility bit, we make
// NTSTATUSes store HRESULTs using the same bit. We call it a Swap bit.
Result := Self and FACILITY_SWAP_BIT <> 0;
end;
function TNtStatusHelper.IsSuccess;
begin
// Inline NT_SUCCESS / Succeeded
Result := Integer(Self) >= 0;
end;
function TNtStatusHelper.IsWin32Error;
begin
// Regardless of whether the value is a native NTSTATUS or a converted HRESULT,
// the Win32 Facility indicates that the error originally comes from Win32.
Result := Self and FACILITY_MASK = FACILITY_WIN32_BITS;
end;
function TNtStatusHelper.ToHResult;
begin
// If the status has the Win32 Facility, then it was derived from a Win32
// error. The HRESULT should be 0x8007xxxx in this case.
// Statuses with a FACILITY_SWAP_BIT were derived from HRESULTs.
// To get the original HRESULT back, remove this bit.
// Statuses without the FACILITY_SWAP_BIT are native NTSTATUS codes.
// Setting this bit (which, in case of HRESULTs, is called the NT Facility
// bit) yields a valid HRESULT derived from an NTSTATUS.
if IsWin32Error then
Cardinal(Result) := WIN32_HRESULT_BITS or (Self and WIN32_CODE_MASK)
else
Cardinal(Result) := Self xor FACILITY_SWAP_BIT;
end;
function TNtStatusHelper.ToString;
begin
if Assigned(RtlxNtStatusRepresenter) then
Result := RtlxNtStatusRepresenter(Self)
else
Result := RtlxUIntToStr(Self, nsHexadecimal, 8);
end;
function TNtStatusHelper.ToWin32Error;
begin
// If the status comes from a Win32 error, reconstruct it
if IsWin32Error then
Result := Self and WIN32_CODE_MASK
// If the status is a native NTSTATUS, ask ntdll to map it to Win32
else if not IsHResult then
Result := RtlNtStatusToDosErrorNoTeb(Self)
// Is it a successful code ntdll does not know about?
else if IsSuccess then
Result := ERROR_SUCCESS
// The original code comes from an HRESULT; even though it's not a Win32
// error, they are reasonably compatible, so we can use them interchangeably
// when formatting error messages.
else
Result := TWin32Error(Self xor FACILITY_SWAP_BIT);
end;
{ THResultHelper }
function THResultHelper.Canonicalize: HResult;
begin
// The only ambiguity we have is with Win32 Errors. They can appear within
// either an HRESULT or an NTSTATUS. We call HRESULT being canonical when
// Win32 Errors appear in it directly (without the NT Facility bit) i.e,
// in form of 0x8007xxxx.
if IsWin32Error then
Cardinal(Result) := WIN32_HRESULT_BITS or
(Cardinal(Self) and WIN32_CODE_MASK)
else
Result := Self;
end;
function THResultHelper.IsNtStatus: Boolean;
begin
// HRESULTs can store NTSTATUSes using the NT Facility bit
Result := Self and FACILITY_NT_BIT <> 0;
end;
function THResultHelper.IsSuccess: Boolean;
begin
// Inline Succeeded / NT_SUCCESS
Result := Integer(Self) >= 0;
end;
function THResultHelper.IsWin32Error: Boolean;
begin
// Regardless of whether the value is a native HRESULT or a converted NTSTATUS,
// the Win32 Facility indicates that the error originally comes from Win32.
Result := Self and FACILITY_MASK = FACILITY_WIN32_BITS;
end;
function THResultHelper.ToNtStatus: NTSTATUS;
begin
// If the value has the Win32 Facility, then it was derived from a Win32
// error. A canonical NTSTATUS should be 0xC007xxxx in this case.
// Values with a FACILITY_NT_BIT were derived from NTSTATUSes.
// To get the original NTSTATUS back, remove this bit.
// Values without the FACILITY_NT_BIT are native HRESULTs codes.
// Setting this bit (which, in case of NTSTATUSes, we cal the Facility Swap
// bit) yields a valid NTSATUS derived from an HRESULT.
if IsWin32Error then
Cardinal(Result) := WIN32_NTSTATUS_BITS or
(Cardinal(Self) and WIN32_CODE_MASK)
else
Cardinal(Result) := Cardinal(Self) xor FACILITY_NT_BIT;
end;
function THResultHelper.ToString;
begin
Result := Self.ToNtStatus.ToString;
end;
{ TWin32ErrorHelper }
function TWin32ErrorHelper.ToHResult;
begin
// Win32 Errors are supposed to be positive 16-bit integers. A negative value
// indicates that someone used an HRESULT in place of a Win32 Error. But since
// they are reasonably compatible (when formatting error messages), it is Ok.
// For regular Win32 Errors, prepare a canonical HRESULT (0x8007xxxx).
if Integer(Self) < 0 then
Result := HResult(Self)
else
Result := HResult(WIN32_HRESULT_BITS or (Self and WIN32_CODE_MASK));
end;
function TWin32ErrorHelper.ToNtStatus: NTSTATUS;
begin
// Negative values indicate usage of HRESULTs in place of true Win32 Erorors.
// Toggle the Facility Swap bit to convert one to NTSTATUS.
// Otherwise, construct a canonical NTSTATUS (0xC007xxxx)
if Integer(Self) < 0 then
Result := Self xor FACILITY_SWAP_BIT
else
Result := WIN32_NTSTATUS_BITS or (Self and WIN32_CODE_MASK);
end;
function TWin32ErrorHelper.ToString;
begin
Result := Self.ToNtStatus.ToString;
end;
end.