commit 1b8d1b18c7ec1c8002046d8a7e131bf21ccf92ca (tree)
parent dbb11915bd03992ff9b64cd7f373faa428f0cedf
Author: Andrew Kelley <andrew@ziglang.org>
Date: Sat, 16 Mar 2024 15:44:02 -0700
Merge pull request #19271 from The-King-of-Toasters/falling-metal-pipe
Windows: Replace CreatePipe with ntdll implementation
Diffstat:
8 files changed, 244 insertions(+), 53 deletions(-)
diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig
@@ -208,7 +208,7 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co
)) {
.SUCCESS => {
const string = @as(*const os.windows.UNICODE_STRING, @ptrCast(&buf));
- const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer[0 .. string.Length / 2]);
+ const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer.?[0 .. string.Length / 2]);
return if (len > 0) buffer[0..len] else null;
},
.NOT_IMPLEMENTED => return error.Unsupported,
diff --git a/lib/std/child_process.zig b/lib/std/child_process.zig
@@ -1420,7 +1420,7 @@ fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *cons
const pipe_path = std.fmt.bufPrintZ(
&tmp_buf,
"\\\\.\\pipe\\zig-childprocess-{d}-{d}",
- .{ windows.kernel32.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
+ .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) },
) catch unreachable;
const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable;
tmp_bufw[len] = 0;
diff --git a/lib/std/debug.zig b/lib/std/debug.zig
@@ -814,7 +814,7 @@ pub noinline fn walkStackWindows(addresses: []usize, existing_context: ?*const w
return windows.ntdll.RtlCaptureStackBackTrace(0, addresses.len, @as(**anyopaque, @ptrCast(addresses.ptr)), null);
}
- const tib = @as(*const windows.NT_TIB, @ptrCast(&windows.teb().Reserved1));
+ const tib = &windows.teb().NtTib;
var context: windows.CONTEXT = undefined;
if (existing_context) |context_ptr| {
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
@@ -493,7 +493,7 @@ pub fn openSelfExe(flags: File.OpenFlags) OpenSelfExeError!File {
// not the path that the symlink points to. However, because we are opening
// the file, we can let the openFileW call follow the symlink for us.
const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
- const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
+ const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
const prefixed_path_w = try os.windows.wToPrefixedFileW(null, image_path_name);
return cwd().openFileW(prefixed_path_w.span(), flags);
}
@@ -664,7 +664,7 @@ pub fn selfExePath(out_buffer: []u8) SelfExePathError![]u8 {
},
.windows => {
const image_path_unicode_string = &os.windows.peb().ProcessParameters.ImagePathName;
- const image_path_name = image_path_unicode_string.Buffer[0 .. image_path_unicode_string.Length / 2 :0];
+ const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0];
// If ImagePathName is a symlink, then it will contain the path of the
// symlink, not the path that the symlink points to. We want the path
diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig
@@ -153,14 +153,149 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN
}
}
-pub const CreatePipeError = error{Unexpected};
+pub fn GetCurrentProcess() HANDLE {
+ const process_pseudo_handle: usize = @bitCast(@as(isize, -1));
+ return @ptrFromInt(process_pseudo_handle);
+}
+
+pub fn GetCurrentProcessId() DWORD {
+ return @truncate(@intFromPtr(teb().ClientId.UniqueProcess));
+}
+
+pub fn GetCurrentThread() HANDLE {
+ const thread_pseudo_handle: usize = @bitCast(@as(isize, -2));
+ return @ptrFromInt(thread_pseudo_handle);
+}
+
+pub fn GetCurrentThreadId() DWORD {
+ return @truncate(@intFromPtr(teb().ClientId.UniqueThread));
+}
+
+pub const CreatePipeError = error{ Unexpected, SystemResources };
+var npfs: ?HANDLE = null;
+
+/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls.
+/// It implements similar behavior to `CreatePipe` and is meant to serve
+/// as a direct substitute for that call.
pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void {
- if (kernel32.CreatePipe(rd, wr, sattr, 0) == 0) {
- switch (kernel32.GetLastError()) {
- else => |err| return unexpectedError(err),
+ // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to:
+ //
+ // \??\pipe\Win32Pipes.{pid}.{count}
+ //
+ // where `pid` is the process id and count is a incrementing counter.
+ // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System
+ // and use that as the root directory for `NtCreateNamedPipeFile`.
+ // This object is visible under the NPFS but has no filename attached to it.
+ //
+ // This implementation replicates how `CreatePipe` works in modern Windows versions.
+ const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst);
+ const dev_handle = opt_dev_handle orelse blk: {
+ const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\");
+ const len: u16 = @truncate(str.len * @sizeOf(u16));
+ const name = UNICODE_STRING{
+ .Length = len,
+ .MaximumLength = len,
+ .Buffer = @constCast(@ptrCast(str)),
+ };
+ const attrs = OBJECT_ATTRIBUTES{
+ .ObjectName = @constCast(&name),
+ .Length = @sizeOf(OBJECT_ATTRIBUTES),
+ .RootDirectory = null,
+ .Attributes = 0,
+ .SecurityDescriptor = null,
+ .SecurityQualityOfService = null,
+ };
+
+ var iosb: IO_STATUS_BLOCK = undefined;
+ var handle: HANDLE = undefined;
+ switch (ntdll.NtCreateFile(
+ &handle,
+ GENERIC_READ | SYNCHRONIZE,
+ @constCast(&attrs),
+ &iosb,
+ null,
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_OPEN,
+ FILE_SYNCHRONOUS_IO_NONALERT,
+ null,
+ 0,
+ )) {
+ .SUCCESS => {},
+ // Judging from the ReactOS sources this is technically possible.
+ .INSUFFICIENT_RESOURCES => return error.SystemResources,
+ .INVALID_PARAMETER => unreachable,
+ else => |e| return unexpectedStatus(e),
}
+ if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| {
+ CloseHandle(handle);
+ break :blk xchg.?;
+ } else break :blk handle;
+ };
+
+ const name = UNICODE_STRING{ .Buffer = null, .Length = 0, .MaximumLength = 0 };
+ var attrs = OBJECT_ATTRIBUTES{
+ .ObjectName = @constCast(&name),
+ .Length = @sizeOf(OBJECT_ATTRIBUTES),
+ .RootDirectory = dev_handle,
+ .Attributes = OBJ_CASE_INSENSITIVE,
+ .SecurityDescriptor = sattr.lpSecurityDescriptor,
+ .SecurityQualityOfService = null,
+ };
+ if (sattr.bInheritHandle != 0) attrs.Attributes |= OBJ_INHERIT;
+
+ // 120 second relative timeout in 100ns units.
+ const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100;
+ var iosb: IO_STATUS_BLOCK = undefined;
+ var read: HANDLE = undefined;
+ switch (ntdll.NtCreateNamedPipeFile(
+ &read,
+ GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
+ &attrs,
+ &iosb,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_CREATE,
+ FILE_SYNCHRONOUS_IO_NONALERT,
+ FILE_PIPE_BYTE_STREAM_TYPE,
+ FILE_PIPE_BYTE_STREAM_MODE,
+ FILE_PIPE_QUEUE_OPERATION,
+ 1,
+ 4096,
+ 4096,
+ @constCast(&default_timeout),
+ )) {
+ .SUCCESS => {},
+ .INVALID_PARAMETER => unreachable,
+ .INSUFFICIENT_RESOURCES => return error.SystemResources,
+ else => |e| return unexpectedStatus(e),
}
+ errdefer CloseHandle(read);
+
+ attrs.RootDirectory = read;
+
+ var write: HANDLE = undefined;
+ switch (ntdll.NtCreateFile(
+ &write,
+ GENERIC_WRITE | SYNCHRONIZE | FILE_READ_ATTRIBUTES,
+ &attrs,
+ &iosb,
+ null,
+ 0,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_OPEN,
+ FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
+ null,
+ 0,
+ )) {
+ .SUCCESS => {},
+ .INVALID_PARAMETER => unreachable,
+ .INSUFFICIENT_RESOURCES => return error.SystemResources,
+ else => |e| return unexpectedStatus(e),
+ }
+
+ rd.* = read;
+ wr.* = write;
}
pub fn CreateEventEx(attributes: ?*SECURITY_ATTRIBUTES, name: []const u8, flags: DWORD, desired_access: DWORD) !HANDLE {
@@ -1050,35 +1185,32 @@ pub fn SetFilePointerEx_CURRENT_get(handle: HANDLE) SetFilePointerError!u64 {
return @as(u64, @bitCast(result));
}
-pub fn QueryObjectName(
- handle: HANDLE,
- out_buffer: []u16,
-) ![]u16 {
+pub fn QueryObjectName(handle: HANDLE, out_buffer: []u16) ![]u16 {
const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong;
const info = @as(*OBJECT_NAME_INFORMATION, @ptrCast(out_buffer_aligned));
- //buffer size is specified in bytes
+ // buffer size is specified in bytes
const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(ULONG);
- //last argument would return the length required for full_buffer, not exposed here
- const rc = ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null);
- switch (rc) {
- .SUCCESS => {
+ // last argument would return the length required for full_buffer, not exposed here
+ return switch (ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) {
+ .SUCCESS => blk: {
// info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0)
// if the object was "unnamed", not sure if this can happen for file handles
- if (info.Name.MaximumLength == 0) return error.Unexpected;
+ if (info.Name.MaximumLength == 0) break :blk error.Unexpected;
// resulting string length is specified in bytes
const path_length_unterminated = @divExact(info.Name.Length, 2);
- return info.Name.Buffer[0..path_length_unterminated];
+ break :blk info.Name.Buffer.?[0..path_length_unterminated];
},
- .ACCESS_DENIED => return error.AccessDenied,
- .INVALID_HANDLE => return error.InvalidHandle,
+ .ACCESS_DENIED => error.AccessDenied,
+ .INVALID_HANDLE => error.InvalidHandle,
// triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH),
// or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL)
- .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => return error.NameTooLong,
- else => |e| return unexpectedStatus(e),
- }
+ .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong,
+ else => |e| unexpectedStatus(e),
+ };
}
-test "QueryObjectName" {
+
+test QueryObjectName {
if (builtin.os.tag != .windows)
return;
@@ -3015,29 +3147,29 @@ pub const OVERLAPPED_ENTRY = extern struct {
pub const MAX_PATH = 260;
-// TODO issue #305
-pub const FILE_INFO_BY_HANDLE_CLASS = u32;
-pub const FileBasicInfo = 0;
-pub const FileStandardInfo = 1;
-pub const FileNameInfo = 2;
-pub const FileRenameInfo = 3;
-pub const FileDispositionInfo = 4;
-pub const FileAllocationInfo = 5;
-pub const FileEndOfFileInfo = 6;
-pub const FileStreamInfo = 7;
-pub const FileCompressionInfo = 8;
-pub const FileAttributeTagInfo = 9;
-pub const FileIdBothDirectoryInfo = 10;
-pub const FileIdBothDirectoryRestartInfo = 11;
-pub const FileIoPriorityHintInfo = 12;
-pub const FileRemoteProtocolInfo = 13;
-pub const FileFullDirectoryInfo = 14;
-pub const FileFullDirectoryRestartInfo = 15;
-pub const FileStorageInfo = 16;
-pub const FileAlignmentInfo = 17;
-pub const FileIdInfo = 18;
-pub const FileIdExtdDirectoryInfo = 19;
-pub const FileIdExtdDirectoryRestartInfo = 20;
+pub const FILE_INFO_BY_HANDLE_CLASS = enum(u32) {
+ FileBasicInfo = 0,
+ FileStandardInfo = 1,
+ FileNameInfo = 2,
+ FileRenameInfo = 3,
+ FileDispositionInfo = 4,
+ FileAllocationInfo = 5,
+ FileEndOfFileInfo = 6,
+ FileStreamInfo = 7,
+ FileCompressionInfo = 8,
+ FileAttributeTagInfo = 9,
+ FileIdBothDirectoryInfo = 10,
+ FileIdBothDirectoryRestartInfo = 11,
+ FileIoPriorityHintInfo = 12,
+ FileRemoteProtocolInfo = 13,
+ FileFullDirectoryInfo = 14,
+ FileFullDirectoryRestartInfo = 15,
+ FileStorageInfo = 16,
+ FileAlignmentInfo = 17,
+ FileIdInfo = 18,
+ FileIdExtdDirectoryInfo = 19,
+ FileIdExtdDirectoryRestartInfo = 20,
+};
pub const BY_HANDLE_FILE_INFORMATION = extern struct {
dwFileAttributes: DWORD,
@@ -3186,6 +3318,25 @@ pub const FILE_ATTRIBUTE_SYSTEM = 0x4;
pub const FILE_ATTRIBUTE_TEMPORARY = 0x100;
pub const FILE_ATTRIBUTE_VIRTUAL = 0x10000;
+pub const FILE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1ff;
+pub const FILE_GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE;
+pub const FILE_GENERIC_WRITE = STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE;
+pub const FILE_GENERIC_EXECUTE = STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE;
+
+// Flags for NtCreateNamedPipeFile
+// NamedPipeType
+pub const FILE_PIPE_BYTE_STREAM_TYPE = 0x0;
+pub const FILE_PIPE_MESSAGE_TYPE = 0x1;
+pub const FILE_PIPE_ACCEPT_REMOTE_CLIENTS = 0x0;
+pub const FILE_PIPE_REJECT_REMOTE_CLIENTS = 0x2;
+pub const FILE_PIPE_TYPE_VALID_MASK = 0x3;
+// CompletionMode
+pub const FILE_PIPE_QUEUE_OPERATION = 0x0;
+pub const FILE_PIPE_COMPLETE_OPERATION = 0x1;
+// ReadMode
+pub const FILE_PIPE_BYTE_STREAM_MODE = 0x0;
+pub const FILE_PIPE_MESSAGE_MODE = 0x1;
+
// flags for CreateEvent
pub const CREATE_EVENT_INITIAL_SET = 0x00000002;
pub const CREATE_EVENT_MANUAL_RESET = 0x00000001;
@@ -4151,7 +4302,7 @@ pub const OBJ_VALID_ATTRIBUTES = 0x000003F2;
pub const UNICODE_STRING = extern struct {
Length: c_ushort,
MaximumLength: c_ushort,
- Buffer: [*]WCHAR,
+ Buffer: ?[*]WCHAR,
};
pub const ACTIVATION_CONTEXT_DATA = opaque {};
@@ -4176,7 +4327,11 @@ pub const THREAD_BASIC_INFORMATION = extern struct {
};
pub const TEB = extern struct {
- Reserved1: [12]PVOID,
+ NtTib: NT_TIB,
+ EnvironmentPointer: PVOID,
+ ClientId: CLIENT_ID,
+ ActiveRpcHandle: PVOID,
+ ThreadLocalStoragePointer: PVOID,
ProcessEnvironmentBlock: *PEB,
Reserved2: [399]PVOID,
Reserved3: [1952]u8,
@@ -4188,6 +4343,25 @@ pub const TEB = extern struct {
TlsExpansionSlots: PVOID,
};
+comptime {
+ // Offsets taken from WinDbg info and Geoff Chappell[1] (RIP)
+ // [1]: https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/pebteb/teb/index.htm
+ assert(@offsetOf(TEB, "NtTib") == 0x00);
+ if (@sizeOf(usize) == 4) {
+ assert(@offsetOf(TEB, "EnvironmentPointer") == 0x1C);
+ assert(@offsetOf(TEB, "ClientId") == 0x20);
+ assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x28);
+ assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x2C);
+ assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x30);
+ } else if (@sizeOf(usize) == 8) {
+ assert(@offsetOf(TEB, "EnvironmentPointer") == 0x38);
+ assert(@offsetOf(TEB, "ClientId") == 0x40);
+ assert(@offsetOf(TEB, "ActiveRpcHandle") == 0x50);
+ assert(@offsetOf(TEB, "ThreadLocalStoragePointer") == 0x58);
+ assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60);
+ }
+}
+
pub const EXCEPTION_REGISTRATION_RECORD = extern struct {
Next: ?*EXCEPTION_REGISTRATION_RECORD,
Handler: ?*EXCEPTION_DISPOSITION,
diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig
@@ -341,3 +341,20 @@ pub extern "ntdll" fn NtProtectVirtualMemory(
pub extern "ntdll" fn RtlExitUserProcess(
ExitStatus: u32,
) callconv(WINAPI) noreturn;
+
+pub extern "ntdll" fn NtCreateNamedPipeFile(
+ FileHandle: *HANDLE,
+ DesiredAccess: ULONG,
+ ObjectAttributes: *OBJECT_ATTRIBUTES,
+ IoStatusBlock: *IO_STATUS_BLOCK,
+ ShareAccess: ULONG,
+ CreateDisposition: ULONG,
+ CreateOptions: ULONG,
+ NamedPipeType: ULONG,
+ ReadMode: ULONG,
+ CompletionMode: ULONG,
+ MaximumInstances: ULONG,
+ InboundQuota: ULONG,
+ OutboundQuota: ULONG,
+ DefaultTimeout: *LARGE_INTEGER,
+) callconv(WINAPI) NTSTATUS;
diff --git a/lib/std/os/windows/test.zig b/lib/std/os/windows/test.zig
@@ -15,7 +15,7 @@ fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !windows.PathSpace {
defer windows.ntdll.RtlFreeUnicodeString(&out);
var path_space: windows.PathSpace = undefined;
- const out_path = out.Buffer[0 .. out.Length / 2];
+ const out_path = out.Buffer.?[0 .. out.Length / 2];
@memcpy(path_space.data[0..out_path.len], out_path);
path_space.len = out.Length / 2;
path_space.data[path_space.len] = 0;
diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig
@@ -160,7 +160,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void {
=> {
var buf = @field(args, field.name).value_buf;
const entry = @as(*align(1) const std.os.windows.UNICODE_STRING, @ptrCast(table[i + 1].EntryContext));
- const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer[0 .. entry.Length / 2]);
+ const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer.?[0 .. entry.Length / 2]);
buf[len] = 0;
},