diff --git a/Libraries/LibIPC/CMakeLists.txt b/Libraries/LibIPC/CMakeLists.txt index 095ac9aac1..7c35f64140 100644 --- a/Libraries/LibIPC/CMakeLists.txt +++ b/Libraries/LibIPC/CMakeLists.txt @@ -3,28 +3,24 @@ set(SOURCES Connection.cpp Decoder.cpp Encoder.cpp + File.cpp + Message.cpp TransportHandle.cpp ) if (APPLE AND NOT IOS) list(APPEND SOURCES AttachmentMachPort.cpp - File.cpp MachBootstrapListener.cpp - Message.cpp TransportBootstrapMach.cpp TransportMachPort.cpp) elseif (UNIX) list(APPEND SOURCES Attachment.cpp - File.cpp - Message.cpp TransportSocket.cpp) else() list(APPEND SOURCES Attachment.cpp - FileWindows.cpp - MessageWindows.cpp TransportSocketWindows.cpp) endif() diff --git a/Libraries/LibIPC/File.cpp b/Libraries/LibIPC/File.cpp index c40a3ceb88..30ee5ae9d5 100644 --- a/Libraries/LibIPC/File.cpp +++ b/Libraries/LibIPC/File.cpp @@ -68,7 +68,9 @@ ErrorOr decode(Decoder& decoder) int fd = attachment.to_fd(); if (fd < 0) return Error::from_string_literal("Failed to obtain fd from attachment"); +#ifndef AK_OS_WINDOWS TRY(Core::System::set_close_on_exec(fd, true)); +#endif return File::adopt_fd(fd); } diff --git a/Libraries/LibIPC/FileWindows.cpp b/Libraries/LibIPC/FileWindows.cpp deleted file mode 100644 index c6ad278e42..0000000000 --- a/Libraries/LibIPC/FileWindows.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2025, stasoid - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include -#include - -#include - -namespace IPC { - -File File::adopt_file(NonnullOwnPtr file) -{ - return File(file->leak_fd()); -} - -File File::adopt_fd(int fd) -{ - return File(fd); -} - -ErrorOr File::clone_fd(int fd) -{ - int new_fd = TRY(Core::System::dup(fd)); - return File(new_fd); -} - -File::File(int fd) - : m_fd(fd) -{ -} - -File::File(File&& other) - : m_fd(exchange(other.m_fd, -1)) -{ -} - -File& File::operator=(File&& other) -{ - if (this != &other) { - m_fd = exchange(other.m_fd, -1); - } - return *this; -} - -File::~File() -{ - if (m_fd != -1) - (void)Core::System::close(m_fd); -} - -// FIXME: IPC::Files transferred over the wire always set O_CLOEXEC during decoding. Perhaps we should add an option to -// allow the receiver to decide whether to make it O_CLOEXEC or not. Or an attribute in the .ipc file? -ErrorOr File::clear_close_on_exec() -{ - return Core::System::set_close_on_exec(m_fd, false); -} - -template<> -ErrorOr decode(Decoder& decoder) -{ - auto handle_type = TRY(decoder.decode()); - int handle = -1; - if (handle_type == HandleType::Generic) { - TRY(decoder.decode_into(handle)); - } else if (handle_type == HandleType::Socket) { - WSAPROTOCOL_INFOW pi = {}; - TRY(decoder.decode_into({ reinterpret_cast(&pi), sizeof(pi) })); - handle = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, &pi, 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); - if (handle == -1) - return Error::from_windows_error(); - } else { - return Error::from_string_literal("Invalid handle type"); - } - return File::adopt_fd(handle); -} - -} diff --git a/Libraries/LibIPC/Message.h b/Libraries/LibIPC/Message.h index db67cdaf8d..0deb6bb0c7 100644 --- a/Libraries/LibIPC/Message.h +++ b/Libraries/LibIPC/Message.h @@ -44,9 +44,6 @@ public: private: MessageDataType m_data; Vector m_attachments; -#ifdef AK_OS_WINDOWS - Vector m_handle_offsets; -#endif }; enum class ErrorCode : u32 { diff --git a/Libraries/LibIPC/MessageWindows.cpp b/Libraries/LibIPC/MessageWindows.cpp deleted file mode 100644 index c23e7ee681..0000000000 --- a/Libraries/LibIPC/MessageWindows.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2024, Tim Flynn - * Copyright (c) 2025, stasoid - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#include -#include -#include - -#include - -namespace IPC { - -using MessageSizeType = u32; - -MessageBuffer::MessageBuffer() -{ -} - -ErrorOr MessageBuffer::extend_data_capacity(size_t capacity) -{ - TRY(m_data.try_ensure_capacity(m_data.size() + capacity)); - return {}; -} - -ErrorOr MessageBuffer::append_data(u8 const* values, size_t count) -{ - TRY(m_data.try_append(values, count)); - return {}; -} - -ErrorOr MessageBuffer::append_file_descriptor(int handle) -{ - TRY(m_handle_offsets.try_append(m_data.size())); - - if (Core::System::is_socket(handle)) { - auto type = HandleType::Socket; - TRY(m_data.try_append(to_underlying(type))); - - // The handle will be duplicated and WSAPROTOCOL_INFO will be filled later in TransportSocketWindows::transfer. - // It can't be duplicated here because it requires peer process pid, which only TransportSocketWindows knows about. - WSAPROTOCOL_INFOW pi = {}; - static_assert(sizeof(pi) >= sizeof(int)); - ByteReader::store(reinterpret_cast(&pi), handle); - TRY(m_data.try_append(reinterpret_cast(&pi), sizeof(pi))); - } else { - auto type = HandleType::Generic; - TRY(m_data.try_append(to_underlying(type))); - // The handle will be overwritten by a duplicate handle later in TransportSocketWindows::transfer (for the same reason). - TRY(m_data.try_append(reinterpret_cast(&handle), sizeof(handle))); - } - return {}; -} - -ErrorOr MessageBuffer::append_attachment(Attachment attachment) -{ - return append_file_descriptor(attachment.to_fd()); -} - -ErrorOr MessageBuffer::extend(MessageBuffer&& buffer) -{ - TRY(m_data.try_extend(move(buffer.m_data))); - TRY(m_attachments.try_extend(move(buffer.m_attachments))); - TRY(m_handle_offsets.try_extend(move(buffer.m_handle_offsets))); - return {}; -} - -ErrorOr MessageBuffer::transfer_message(Transport& transport) -{ - Checked checked_message_size { m_data.size() }; - if (checked_message_size.has_overflow()) - return Error::from_string_literal("Message is too large for IPC encoding"); - - TRY(transport.transfer_message(m_data, m_handle_offsets)); - return {}; -} - -} diff --git a/Libraries/LibIPC/TransportSocketWindows.cpp b/Libraries/LibIPC/TransportSocketWindows.cpp index 7eee4598aa..918ba1910b 100644 --- a/Libraries/LibIPC/TransportSocketWindows.cpp +++ b/Libraries/LibIPC/TransportSocketWindows.cpp @@ -20,6 +20,9 @@ namespace IPC { +static constexpr size_t MAX_SERIALIZED_ATTACHMENT_SIZE = sizeof(HandleType) + sizeof(WSAPROTOCOL_INFOW); +static constexpr size_t MAX_ATTACHMENT_DATA_SIZE = MAX_MESSAGE_FD_COUNT * MAX_SERIALIZED_ATTACHMENT_SIZE; + ErrorOr> TransportSocketWindows::from_socket(NonnullOwnPtr socket) { return make(move(socket)); @@ -84,80 +87,122 @@ void TransportSocketWindows::wait_until_readable() VERIFY(readable); } -ErrorOr TransportSocketWindows::duplicate_handles(Bytes bytes, Vector const& handle_offsets) -{ - if (handle_offsets.is_empty()) - return {}; - - if (m_peer_pid == -1) - return Error::from_string_literal("Transport is not initialized"); - - HANDLE peer_process_handle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, m_peer_pid); - if (!peer_process_handle) - return Error::from_windows_error(); - ScopeGuard guard = [&] { CloseHandle(peer_process_handle); }; - - for (auto offset : handle_offsets) { - - auto span = bytes.slice(offset); - if (span.size() < sizeof(HandleType)) - return Error::from_string_literal("Not enough bytes"); - - UnderlyingType raw_type {}; - ByteReader::load(span.data(), raw_type); - auto type = static_cast(raw_type); - if (type != HandleType::Generic && type != HandleType::Socket) - return Error::from_string_literal("Invalid handle type"); - span = span.slice(sizeof(HandleType)); - - if (type == HandleType::Socket) { - if (span.size() < sizeof(WSAPROTOCOL_INFOW)) - return Error::from_string_literal("Not enough bytes for socket handle"); - - // We stashed the bytes of this process's version of the handle at the offset location - int handle = -1; - ByteReader::load(span.data(), handle); - - auto* pi = reinterpret_cast(span.data()); - if (WSADuplicateSocketW(handle, m_peer_pid, pi)) - return Error::from_windows_error(); - } else { - if (span.size() < sizeof(int)) - return Error::from_string_literal("Not enough bytes for generic handle"); - - int handle = -1; - ByteReader::load(span.data(), handle); - - HANDLE new_handle = INVALID_HANDLE_VALUE; - if (!DuplicateHandle(GetCurrentProcess(), to_handle(handle), peer_process_handle, &new_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) - return Error::from_windows_error(); - - ByteReader::store(span.data(), to_fd(new_handle)); - } - } - - return {}; -} - // Maximum size of accumulated unprocessed bytes before we disconnect the peer static constexpr size_t MAX_UNPROCESSED_BUFFER_SIZE = 128 * MiB; struct MessageHeader { - u32 size { 0 }; + u32 payload_size { 0 }; + u32 attachment_data_size { 0 }; + u32 attachment_count { 0 }; }; -ErrorOr TransportSocketWindows::transfer_message(ReadonlyBytes bytes, Vector const& handle_offsets) +ErrorOr> TransportSocketWindows::serialize_attachments(Vector& attachments) { + if (attachments.is_empty()) + return Vector {}; + + VERIFY(m_peer_pid != -1); + + HANDLE peer_process_handle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, m_peer_pid); + if (!peer_process_handle) + return Error::from_windows_error(); + ScopeGuard peer_process_guard = [&] { CloseHandle(peer_process_handle); }; + + Vector serialized_attachments; + TRY(serialized_attachments.try_ensure_capacity(attachments.size() * MAX_SERIALIZED_ATTACHMENT_SIZE)); + + for (auto& attachment : attachments) { + int handle = attachment.to_fd(); + ScopeGuard close_original_handle = [&] { + if (handle != -1) + (void)Core::System::close(handle); + }; + + if (Core::System::is_socket(handle)) { + TRY(serialized_attachments.try_append(to_underlying(HandleType::Socket))); + + WSAPROTOCOL_INFOW pi {}; + if (WSADuplicateSocketW(handle, m_peer_pid, &pi)) + return Error::from_windows_error(); + TRY(serialized_attachments.try_append(reinterpret_cast(&pi), sizeof(pi))); + } else { + TRY(serialized_attachments.try_append(to_underlying(HandleType::Generic))); + + HANDLE duplicated_handle = INVALID_HANDLE_VALUE; + if (!DuplicateHandle(GetCurrentProcess(), to_handle(handle), peer_process_handle, &duplicated_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) + return Error::from_windows_error(); + + auto duplicated_fd = to_fd(duplicated_handle); + TRY(serialized_attachments.try_append(reinterpret_cast(&duplicated_fd), sizeof(duplicated_fd))); + } + } + + attachments.clear(); + return serialized_attachments; +} + +Attachment TransportSocketWindows::deserialize_attachment(ReadonlyBytes& serialized_bytes) +{ + VERIFY(serialized_bytes.size() >= sizeof(HandleType)); + + UnderlyingType raw_type {}; + ByteReader::load(serialized_bytes.data(), raw_type); + auto type = static_cast(raw_type); + serialized_bytes = serialized_bytes.slice(sizeof(HandleType)); + + switch (type) { + case HandleType::Generic: { + VERIFY(serialized_bytes.size() >= sizeof(int)); + + int handle = -1; + ByteReader::load(serialized_bytes.data(), handle); + serialized_bytes = serialized_bytes.slice(sizeof(handle)); + return Attachment::from_fd(handle); + } + case HandleType::Socket: { + VERIFY(serialized_bytes.size() >= sizeof(WSAPROTOCOL_INFOW)); + + WSAPROTOCOL_INFOW pi {}; + memcpy(&pi, serialized_bytes.data(), sizeof(pi)); + serialized_bytes = serialized_bytes.slice(sizeof(pi)); + + auto handle = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, &pi, 0, WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT); + VERIFY(handle != INVALID_SOCKET); + return Attachment::from_fd(handle); + } + } + + VERIFY_NOT_REACHED(); +} + +void TransportSocketWindows::post_message(Vector const& bytes, Vector& attachments) +{ + VERIFY(bytes.size() <= MAX_MESSAGE_PAYLOAD_SIZE); + VERIFY(attachments.size() <= MAX_MESSAGE_FD_COUNT); + + auto attachment_count = attachments.size(); + auto serialized_attachments = MUST(serialize_attachments(attachments)); + VERIFY(serialized_attachments.size() <= MAX_ATTACHMENT_DATA_SIZE); + Vector message_buffer; - message_buffer.resize(sizeof(MessageHeader) + bytes.size()); - MessageHeader header; - header.size = bytes.size(); - memcpy(message_buffer.data(), &header, sizeof(MessageHeader)); - memcpy(message_buffer.data() + sizeof(MessageHeader), bytes.data(), bytes.size()); + MUST(message_buffer.try_resize(sizeof(MessageHeader) + serialized_attachments.size() + bytes.size())); - TRY(duplicate_handles({ message_buffer.data() + sizeof(MessageHeader), bytes.size() }, handle_offsets)); + MessageHeader header { + .payload_size = static_cast(bytes.size()), + .attachment_data_size = static_cast(serialized_attachments.size()), + .attachment_count = static_cast(attachment_count), + }; + memcpy(message_buffer.data(), &header, sizeof(header)); - return transfer(message_buffer.span()); + auto* serialized_attachment_storage = message_buffer.data() + sizeof(MessageHeader); + if (!serialized_attachments.is_empty()) + memcpy(serialized_attachment_storage, serialized_attachments.data(), serialized_attachments.size()); + + auto* payload_storage = serialized_attachment_storage + serialized_attachments.size(); + if (!bytes.is_empty()) + memcpy(payload_storage, bytes.data(), bytes.size()); + + MUST(transfer(message_buffer.span())); } ErrorOr TransportSocketWindows::transfer(ReadonlyBytes bytes_to_write) @@ -235,24 +280,31 @@ TransportSocketWindows::ShouldShutdown TransportSocketWindows::read_as_many_mess while (index + sizeof(MessageHeader) <= m_unprocessed_bytes.size()) { MessageHeader header; memcpy(&header, m_unprocessed_bytes.data() + index, sizeof(MessageHeader)); - if (header.size > MAX_MESSAGE_PAYLOAD_SIZE) { - dbgln("TransportSocketWindows: Rejecting message with size {} exceeding limit {}", header.size, MAX_MESSAGE_PAYLOAD_SIZE); - should_shutdown = ShouldShutdown::Yes; - break; - } - Checked message_size = header.size; + VERIFY(header.payload_size <= MAX_MESSAGE_PAYLOAD_SIZE); + VERIFY(header.attachment_count <= MAX_MESSAGE_FD_COUNT); + VERIFY(header.attachment_data_size <= MAX_ATTACHMENT_DATA_SIZE); + + Checked message_size = header.payload_size; + message_size += header.attachment_data_size; message_size += sizeof(MessageHeader); if (message_size.has_overflow() || message_size.value() > m_unprocessed_bytes.size() - index) break; Message message; - if (message.bytes.try_append(m_unprocessed_bytes.data() + index + sizeof(MessageHeader), header.size).is_error()) { - dbgln("TransportSocketWindows: Failed to allocate message buffer for size {}", header.size); + auto attachment_bytes = ReadonlyBytes { m_unprocessed_bytes.data() + index + sizeof(MessageHeader), header.attachment_data_size }; + for (u32 attachment_index = 0; attachment_index < header.attachment_count; ++attachment_index) + message.attachments.enqueue(deserialize_attachment(attachment_bytes)); + VERIFY(attachment_bytes.is_empty()); + + auto const* payload = m_unprocessed_bytes.data() + index + sizeof(MessageHeader) + header.attachment_data_size; + if (message.bytes.try_append(payload, header.payload_size).is_error()) { + dbgln("TransportSocketWindows: Failed to allocate message buffer for payload_size {}", header.payload_size); should_shutdown = ShouldShutdown::Yes; break; } callback(move(message)); Checked new_index = index; - new_index += header.size; + new_index += header.payload_size; + new_index += header.attachment_data_size; new_index += sizeof(MessageHeader); if (new_index.has_overflow()) { dbgln("TransportSocketWindows: index would overflow"); diff --git a/Libraries/LibIPC/TransportSocketWindows.h b/Libraries/LibIPC/TransportSocketWindows.h index e411fdf1d8..1b416f0ad4 100644 --- a/Libraries/LibIPC/TransportSocketWindows.h +++ b/Libraries/LibIPC/TransportSocketWindows.h @@ -36,7 +36,7 @@ public: void wait_until_readable(); - ErrorOr transfer_message(ReadonlyBytes, Vector const& handle_offsets); + void post_message(Vector const&, Vector& attachments); enum class ShouldShutdown { No, @@ -44,14 +44,15 @@ public: }; struct Message { Vector bytes; - Queue attachments; // always empty, present to avoid OS #ifdefs in Connection.cpp + Queue attachments; }; ShouldShutdown read_as_many_messages_as_possible_without_blocking(Function&&); ErrorOr release_for_transfer(); private: - ErrorOr duplicate_handles(Bytes, Vector const& handle_offsets); + ErrorOr> serialize_attachments(Vector&); + Attachment deserialize_attachment(ReadonlyBytes&); ErrorOr transfer(ReadonlyBytes); private: