authorgravatar for dev@sgregoratto.meStephen Gregoratto <dev@sgregoratto.me> 2024-03-12 21:13:13+11:00
committergravatar for dev@sgregoratto.meStephen Gregoratto <dev@sgregoratto.me> 2024-03-16 23:37:50+11:00
log9532f729371d8142fb65c61794b7bd765cd82396
tree5097f26a389fddaab9a63e0a2c68e17034cb7f22
parentdbb11915bd03992ff9b64cd7f373faa428f0cedf

Windows: Replace CreatePipe with ntdll implementation

This implementation is now a direct replacement for the `kernel32` one. New bitflags for named pipes and other generic ones were added based on browsing the ReactOS sources. `UNICODE_STRING.Buffer` has also been changed to be nullable, as this is what makes the implementation work. This required some changes to places accesssing the buffer after a `SUCCESS`ful return, most notably `QueryObjectName` which even referred to it being nullable.

6 files changed, 177 insertions(+), 27 deletions(-)

lib/std/Thread.zig+1-1
......@@ -208,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
208208 )) {
209209 .SUCCESS => {
210210 const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf));
211 const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]);
211 const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer.?[0 .. string.Length / 2]);
212212 return if (len > 0) buffer[0..len] else null;
213213 },
214214 .NOT_IMPLEMENTED => return error.Unsupported,
lib/std/fs.zig+2-2
......@@ -493,7 +493,7 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
493493 // not the path that the symlink points to. However, because we are opening
494494 // the file, we can let the openFileW call follow the symlink for us.
495495 const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
496 const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
496 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
497497 const prefixed_path_w = try os.windows.wToPrefixedFileW(null, image_path_name);
498498 return cwd().openFileW(prefixed_path_w.span(), flags);
499499 }
......@@ -664,7 +664,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
664664 },
665665 .windows => {
666666 const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
667 const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
667 const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
668668
669669 // If ImagePathName is a symlink, then it will contain the path of the
670670 // symlink, not the path that the symlink points to. We want the path
lib/std/os/windows.zig+155-22
......@@ -153,14 +153,131 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
153153 }
154154}
155155
156pub const CreatePipeError = error{Unexpected};
156pub const CreatePipeError = error{ Unexpected, SystemResources };
157157
158var npfs: ?HANDLE = null;
159
160/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls.
161/// It implements similar behavior to `CreatePipe` and is meant to serve
162/// as a direct substitute for that call.
158163pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
159 if (kernel32.CreatePipe(rd, wr, sattr, 0) == 0) {
160 switch (kernel32.GetLastError()) {
161 else => |err| return unexpectedError(err),
164 // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to:
165 //
166 // \??\pipe\Win32Pipes.{pid}.{count}
167 //
168 // where `pid` is the process id and count is a incrementing counter.
169 // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System
170 // and use that as the root directory for `NtCreateNamedPipeFile`.
171 // This object is visible under the NPFS but has no filename attached to it.
172 //
173 // This implementation replicates how `CreatePipe` works in modern Windows versions.
174 const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst);
175 const dev_handle = opt_dev_handle orelse blk: {
176 const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\");
177 const len: u16 = @truncate(str.len * @sizeOf(u16));
178 const name = UNICODE_STRING{
179 .Length = len,
180 .MaximumLength = len,
181 .Buffer = @constCast(@ptrCast(str)),
182 };
183 const attrs = OBJECT_ATTRIBUTES{
184 .ObjectName = @constCast(&name),
185 .Length = @sizeOf(OBJECT_ATTRIBUTES),
186 .RootDirectory = null,
187 .Attributes = 0,
188 .SecurityDescriptor = null,
189 .SecurityQualityOfService = null,
190 };
191
192 var iosb: IO_STATUS_BLOCK = undefined;
193 var handle: HANDLE = undefined;
194 switch (ntdll.NtCreateFile(
195 &handle,
196 GENERIC_READ | SYNCHRONIZE,
197 @constCast(&attrs),
198 &iosb,
199 null,
200 0,
201 FILE_SHARE_READ | FILE_SHARE_WRITE,
202 FILE_OPEN,
203 FILE_SYNCHRONOUS_IO_NONALERT,
204 null,
205 0,
206 )) {
207 .SUCCESS => {},
208 // Judging from the ReactOS sources this is technically possible.
209 .INSUFFICIENT_RESOURCES => return error.SystemResources,
210 .INVALID_PARAMETER => unreachable,
211 else => |e| return unexpectedStatus(e),
162212 }
213 if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| {
214 CloseHandle(handle);
215 break :blk xchg.?;
216 } else break :blk handle;
217 };
218
219 const name = UNICODE_STRING{ .Buffer = null, .Length = 0, .MaximumLength = 0 };
220 var attrs = OBJECT_ATTRIBUTES{
221 .ObjectName = @constCast(&name),
222 .Length = @sizeOf(OBJECT_ATTRIBUTES),
223 .RootDirectory = dev_handle,
224 .Attributes = OBJ_CASE_INSENSITIVE,
225 .SecurityDescriptor = sattr.lpSecurityDescriptor,
226 .SecurityQualityOfService = null,
227 };
228 if (sattr.bInheritHandle != 0) attrs.Attributes |= OBJ_INHERIT;
229
230 // 120 second relative timeout in 100ns units.
231 const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100;
232 var iosb: IO_STATUS_BLOCK = undefined;
233 var read: HANDLE = undefined;
234 switch (ntdll.NtCreateNamedPipeFile(
235 &read,
236 GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
237 &attrs,
238 &iosb,
239 FILE_SHARE_READ | FILE_SHARE_WRITE,
240 FILE_CREATE,
241 FILE_SYNCHRONOUS_IO_NONALERT,
242 FILE_PIPE_BYTE_STREAM_TYPE,
243 FILE_PIPE_BYTE_STREAM_MODE,
244 FILE_PIPE_QUEUE_OPERATION,
245 1,
246 4096,
247 4096,
248 @constCast(&default_timeout),
249 )) {
250 .SUCCESS => {},
251 .INVALID_PARAMETER => unreachable,
252 .INSUFFICIENT_RESOURCES => return error.SystemResources,
253 else => |e| return unexpectedStatus(e),
163254 }
255 errdefer CloseHandle(read);
256
257 attrs.RootDirectory = read;
258
259 var write: HANDLE = undefined;
260 switch (ntdll.NtCreateFile(
261 &write,
262 FILE_GENERIC_WRITE,
263 &attrs,
264 &iosb,
265 null,
266 0,
267 FILE_SHARE_READ,
268 FILE_OPEN,
269 FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
270 null,
271 0,
272 )) {
273 .SUCCESS => {},
274 .INVALID_PARAMETER => unreachable,
275 .INSUFFICIENT_RESOURCES => return error.SystemResources,
276 else => |e| return unexpectedStatus(e),
277 }
278
279 rd.* = read;
280 wr.* = write;
164281}
165282
166283pub fn CreateEventEx(attributes: ?*SECURITY_ATTRIBUTES, name: []const u8, flags: DWORD, desired_access: DWORD) !HANDLE {
......@@ -1050,35 +1167,32 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
10501167 return @as(u64, @bitCast(result));
10511168}
10521169
1053pub fn QueryObjectName(
1054 handle: HANDLE,
1055 out_buffer: []u16,
1056) ![]u16 {
1170pub fn QueryObjectName(handle: HANDLE, out_buffer: []u16) ![]u16 {
10571171 const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong;
10581172
10591173 const info = @as(*OBJECT_NAME_INFORMATION, @ptrCast(out_buffer_aligned));
1060 //buffer size is specified in bytes
1174 // buffer size is specified in bytes
10611175 const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(ULONG);
1062 //last argument would return the length required for full_buffer, not exposed here
1063 const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null);
1064 switch (rc) {
1065 .SUCCESS => {
1176 // last argument would return the length required for full_buffer, not exposed here
1177 return switch (ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) {
1178 .SUCCESS => blk: {
10661179 // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0)
10671180 // if the object was "unnamed", not sure if this can happen for file handles
1068 if (info.Name.MaximumLength == 0) return error.Unexpected;
1181 if (info.Name.MaximumLength == 0) break :blk error.Unexpected;
10691182 // resulting string length is specified in bytes
10701183 const path_length_unterminated = @divExact(info.Name.Length, 2);
1071 return info.Name.Buffer[0..path_length_unterminated];
1184 break :blk info.Name.Buffer.?[0..path_length_unterminated];
10721185 },
1073 .ACCESS_DENIED => return error.AccessDenied,
1074 .INVALID_HANDLE => return error.InvalidHandle,
1186 .ACCESS_DENIED => error.AccessDenied,
1187 .INVALID_HANDLE => error.InvalidHandle,
10751188 // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH),
10761189 // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL)
1077 .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong,
1078 else => |e| return unexpectedStatus(e),
1079 }
1190 .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong,
1191 else => |e| unexpectedStatus(e),
1192 };
10801193}
1081test "QueryObjectName" {
1194
1195test QueryObjectName {
10821196 if (builtin.os.tag != .windows)
10831197 return;
10841198
......@@ -3186,6 +3300,25 @@ pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
31863300pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
31873301pub const FILE_ATTRIBUTE_VIRTUAL = 0x10000;
31883302
3303pub const FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1ff;
3304pub const FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE;
3305pub const FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE;
3306pub const FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE;
3307
3308// Flags for NtCreateNamedPipeFile
3309// NamedPipeType
3310pub const FILE_PIPE_BYTE_STREAM_TYPE = 0x0;
3311pub const FILE_PIPE_MESSAGE_TYPE = 0x1;
3312pub const FILE_PIPE_ACCEPT_REMOTE_CLIENTS = 0x0;
3313pub const FILE_PIPE_REJECT_REMOTE_CLIENTS = 0x2;
3314pub const FILE_PIPE_TYPE_VALID_MASK = 0x3;
3315// CompletionMode
3316pub const FILE_PIPE_QUEUE_OPERATION = 0x0;
3317pub const FILE_PIPE_COMPLETE_OPERATION = 0x1;
3318// ReadMode
3319pub const FILE_PIPE_BYTE_STREAM_MODE = 0x0;
3320pub const FILE_PIPE_MESSAGE_MODE = 0x1;
3321
31893322// flags for CreateEvent
31903323pub const CREATE_EVENT_INITIAL_SET = 0x00000002;
31913324pub const CREATE_EVENT_MANUAL_RESET = 0x00000001;
......@@ -4151,7 +4284,7 @@ pub const OBJ_VALID_ATTRIBUTES = 0x000003F2;
41514284pub const UNICODE_STRING = extern struct {
41524285 Length: c_ushort,
41534286 MaximumLength: c_ushort,
4154 Buffer: [*]WCHAR,
4287 Buffer: ?[*]WCHAR,
41554288};
41564289
41574290pub const ACTIVATION_CONTEXT_DATA = opaque {};
lib/std/os/windows/ntdll.zig+17
......@@ -341,3 +341,20 @@ pub extern "ntdll" fn NtProtectVirtualMemory(
341341pub extern "ntdll" fn RtlExitUserProcess(
342342 ExitStatus: u32,
343343) callconv(WINAPI) noreturn;
344
345pub extern "ntdll" fn NtCreateNamedPipeFile(
346 FileHandle: *HANDLE,
347 DesiredAccess: ULONG,
348 ObjectAttributes: *OBJECT_ATTRIBUTES,
349 IoStatusBlock: *IO_STATUS_BLOCK,
350 ShareAccess: ULONG,
351 CreateDisposition: ULONG,
352 CreateOptions: ULONG,
353 NamedPipeType: ULONG,
354 ReadMode: ULONG,
355 CompletionMode: ULONG,
356 MaximumInstances: ULONG,
357 InboundQuota: ULONG,
358 OutboundQuota: ULONG,
359 DefaultTimeout: *LARGE_INTEGER,
360) callconv(WINAPI) NTSTATUS;
lib/std/os/windows/test.zig+1-1
......@@ -15,7 +15,7 @@ fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !windows.PathSpace {
1515 defer windows.ntdll.RtlFreeUnicodeString(&out);
1616
1717 var path_space: windows.PathSpace = undefined;
18 const out_path = out.Buffer[0 .. out.Length / 2];
18 const out_path = out.Buffer.?[0 .. out.Length / 2];
1919 @memcpy(path_space.data[0..out_path.len], out_path);
2020 path_space.len = out.Length / 2;
2121 path_space.data[path_space.len] = 0;
lib/std/zig/system/windows.zig+1-1
......@@ -160,7 +160,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
160160 => {
161161 var buf = @field(args, field.name).value_buf;
162162 const entry = @as(*align(1) const std.os.windows.UNICODE_STRING, @ptrCast(table[i + 1].EntryContext));
163 const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]);
163 const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer.?[0 .. entry.Length / 2]);
164164 buf[len] = 0;
165165 },
166166