diff --git a/Libraries/LibIPC/CMakeLists.txt b/Libraries/LibIPC/CMakeLists.txt index 114d0c482b0..9383718df87 100644 --- a/Libraries/LibIPC/CMakeLists.txt +++ b/Libraries/LibIPC/CMakeLists.txt @@ -2,11 +2,18 @@ set(SOURCES Connection.cpp Decoder.cpp Encoder.cpp - Message.cpp ) if (UNIX) - list(APPEND SOURCES TransportSocket.cpp) + list(APPEND SOURCES + File.cpp + Message.cpp + TransportSocket.cpp) +else() + list(APPEND SOURCES + FileWindows.cpp + MessageWindows.cpp + TransportSocketWindows.cpp) endif() serenity_lib(LibIPC ipc) diff --git a/Libraries/LibIPC/Connection.h b/Libraries/LibIPC/Connection.h index 229227c7c14..9bbf65b0c30 100644 --- a/Libraries/LibIPC/Connection.h +++ b/Libraries/LibIPC/Connection.h @@ -57,7 +57,7 @@ protected: RefPtr m_responsiveness_timer; Vector> m_unprocessed_messages; - Queue m_unprocessed_fds; + Queue m_unprocessed_fds; // unused on Windows ByteBuffer m_unprocessed_bytes; u32 m_local_endpoint_magic { 0 }; diff --git a/Libraries/LibIPC/Decoder.cpp b/Libraries/LibIPC/Decoder.cpp index 022e52661cd..5334859783c 100644 --- a/Libraries/LibIPC/Decoder.cpp +++ b/Libraries/LibIPC/Decoder.cpp @@ -14,7 +14,6 @@ #include #include #include -#include namespace IPC { @@ -120,17 +119,6 @@ ErrorOr decode(Decoder& decoder) return URL::Host { move(value) }; } -template<> -ErrorOr decode(Decoder& decoder) -{ - auto file = TRY(decoder.files().try_dequeue()); - auto fd = file.fd(); - - auto fd_flags = TRY(Core::System::fcntl(fd, F_GETFD)); - TRY(Core::System::fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC)); - return file; -} - template<> ErrorOr decode(Decoder&) { diff --git a/Libraries/LibIPC/File.cpp b/Libraries/LibIPC/File.cpp new file mode 100644 index 00000000000..148a9c7d849 --- /dev/null +++ b/Libraries/LibIPC/File.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace IPC { + +// FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding. +// Perhaps we should add an option to IPC::File 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() +{ + auto fd_flags = TRY(Core::System::fcntl(m_fd, F_GETFD)); + fd_flags &= ~FD_CLOEXEC; + TRY(Core::System::fcntl(m_fd, F_SETFD, fd_flags)); + return {}; +} + +template<> +ErrorOr decode(Decoder& decoder) +{ + auto file = TRY(decoder.files().try_dequeue()); + auto fd = file.fd(); + + auto fd_flags = TRY(Core::System::fcntl(fd, F_GETFD)); + TRY(Core::System::fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC)); + return file; +} + +} diff --git a/Libraries/LibIPC/File.h b/Libraries/LibIPC/File.h index 5172f53a4d6..77c5f3d86be 100644 --- a/Libraries/LibIPC/File.h +++ b/Libraries/LibIPC/File.h @@ -63,16 +63,7 @@ public: return exchange(m_fd, -1); } - // FIXME: IPC::Files transferred over the wire are always set O_CLOEXEC during decoding. - // Perhaps we should add an option to IPC::File to allow the receiver to decide whether to - // make it O_CLOEXEC or not. Or an attribute in the .ipc file? - ErrorOr clear_close_on_exec() - { - auto fd_flags = TRY(Core::System::fcntl(m_fd, F_GETFD)); - fd_flags &= ~FD_CLOEXEC; - TRY(Core::System::fcntl(m_fd, F_SETFD, fd_flags)); - return {}; - } + ErrorOr clear_close_on_exec(); private: explicit File(int fd) diff --git a/Libraries/LibIPC/FileWindows.cpp b/Libraries/LibIPC/FileWindows.cpp new file mode 100644 index 00000000000..2999e1f1847 --- /dev/null +++ b/Libraries/LibIPC/FileWindows.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include + +namespace IPC { + +ErrorOr File::clear_close_on_exec() +{ + if (!SetHandleInformation(to_handle(m_fd), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) + return Error::from_windows_error(); + return {}; +} + +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_INFO pi = {}; + TRY(decoder.decode_into({ reinterpret_cast(&pi), sizeof(pi) })); + handle = WSASocket(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/HandleType.h b/Libraries/LibIPC/HandleType.h new file mode 100644 index 00000000000..047d938fbea --- /dev/null +++ b/Libraries/LibIPC/HandleType.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace IPC { + +enum class HandleType : u8 { + Generic, + Socket +}; + +} diff --git a/Libraries/LibIPC/Message.h b/Libraries/LibIPC/Message.h index 413cb03dde9..c0b9b392917 100644 --- a/Libraries/LibIPC/Message.h +++ b/Libraries/LibIPC/Message.h @@ -12,8 +12,8 @@ #include #include #include +#include #include -#include namespace IPC { @@ -27,7 +27,7 @@ public: ~AutoCloseFileDescriptor() { if (m_fd != -1) - close(m_fd); + (void)Core::System::close(m_fd); } int value() const { return m_fd; } @@ -45,11 +45,14 @@ public: ErrorOr append_file_descriptor(int fd); - ErrorOr transfer_message(Transport& socket); + ErrorOr transfer_message(Transport& transport); private: Vector m_data; Vector, 1> m_fds; +#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 new file mode 100644 index 00000000000..e15126c2a97 --- /dev/null +++ b/Libraries/LibIPC/MessageWindows.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024, Tim Flynn + * Copyright (c) 2025, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include + +namespace IPC { + +using MessageSizeType = u32; + +MessageBuffer::MessageBuffer() +{ + m_data.resize(sizeof(MessageSizeType)); +} + +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_fds.try_append(adopt_ref(*new AutoCloseFileDescriptor(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_INFO 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::transfer_message(Transport& transport) +{ + Checked checked_message_size { m_data.size() }; + checked_message_size -= sizeof(MessageSizeType); + + if (checked_message_size.has_overflow()) + return Error::from_string_literal("Message is too large for IPC encoding"); + + MessageSizeType const message_size = checked_message_size.value(); + m_data.span().overwrite(0, reinterpret_cast(&message_size), sizeof(message_size)); + + TRY(transport.transfer(m_data.span(), m_handle_offsets)); + return {}; +} + +} diff --git a/Libraries/LibIPC/Transport.h b/Libraries/LibIPC/Transport.h index fdc063fc056..9862d418499 100644 --- a/Libraries/LibIPC/Transport.h +++ b/Libraries/LibIPC/Transport.h @@ -10,6 +10,8 @@ #if !defined(AK_OS_WINDOWS) # include +#else +# include #endif namespace IPC { @@ -18,7 +20,7 @@ namespace IPC { // Unix Domain Sockets using Transport = TransportSocket; #else -# error "LibIPC Transport has not been ported to this platform" +using Transport = TransportSocketWindows; #endif } diff --git a/Libraries/LibIPC/TransportSocketWindows.cpp b/Libraries/LibIPC/TransportSocketWindows.cpp new file mode 100644 index 00000000000..e2c16a6dc2a --- /dev/null +++ b/Libraries/LibIPC/TransportSocketWindows.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * Copyright (c) 2025, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +#include + +namespace IPC { + +TransportSocketWindows::TransportSocketWindows(NonnullOwnPtr socket) + : m_socket(move(socket)) +{ +} + +void TransportSocketWindows::set_peer_pid(int pid) +{ + m_peer_pid = pid; +} + +void TransportSocketWindows::set_up_read_hook(Function hook) +{ + VERIFY(m_socket->is_open()); + m_socket->on_ready_to_read = move(hook); +} + +bool TransportSocketWindows::is_open() const +{ + return m_socket->is_open(); +} + +void TransportSocketWindows::close() +{ + m_socket->close(); +} + +void TransportSocketWindows::wait_until_readable() +{ + auto readable = MUST(m_socket->can_read_without_blocking(-1)); + 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_INFO)) + 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 (WSADuplicateSocket(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 {}; +} + +ErrorOr TransportSocketWindows::transfer(Bytes bytes_to_write, Vector const& handle_offsets) +{ + TRY(duplicate_handles(bytes_to_write, handle_offsets)); + + while (!bytes_to_write.is_empty()) { + + ErrorOr maybe_nwritten = m_socket->write_some(bytes_to_write); + + if (maybe_nwritten.is_error()) { + auto error = maybe_nwritten.release_error(); + if (error.code() != EWOULDBLOCK) + return error; + + struct pollfd pollfd = { + .fd = static_cast(m_socket->fd().value()), + .events = POLLOUT, + .revents = 0 + }; + + auto result = WSAPoll(&pollfd, 1, -1); + if (result == 1) + continue; + if (result == SOCKET_ERROR) + return Error::from_windows_error(); + VERIFY_NOT_REACHED(); + } + + bytes_to_write = bytes_to_write.slice(maybe_nwritten.value()); + } + return {}; +} + +TransportSocketWindows::ReadResult TransportSocketWindows::read_as_much_as_possible_without_blocking(Function schedule_shutdown) +{ + ReadResult result; + + while (is_open()) { + + u8 buffer[4096]; + auto maybe_bytes_read = m_socket->read_without_waiting({ buffer, sizeof(buffer) }); + + if (maybe_bytes_read.is_error()) { + auto error = maybe_bytes_read.release_error(); + if (error.code() == EWOULDBLOCK) + break; + if (error.code() == ECONNRESET) { + schedule_shutdown(); + break; + } + VERIFY_NOT_REACHED(); + } + + auto bytes_read = maybe_bytes_read.release_value(); + if (bytes_read.is_empty()) { + schedule_shutdown(); + break; + } + + result.bytes.append(bytes_read.data(), bytes_read.size()); + } + + return result; +} + +ErrorOr TransportSocketWindows::release_underlying_transport_for_transfer() +{ + return m_socket->release_fd(); +} + +ErrorOr TransportSocketWindows::clone_for_transfer() +{ + return IPC::File::clone_fd(m_socket->fd().value()); +} + +} diff --git a/Libraries/LibIPC/TransportSocketWindows.h b/Libraries/LibIPC/TransportSocketWindows.h new file mode 100644 index 00000000000..3c4102b759c --- /dev/null +++ b/Libraries/LibIPC/TransportSocketWindows.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * Copyright (c) 2025, stasoid + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace IPC { + +class TransportSocketWindows { + AK_MAKE_NONCOPYABLE(TransportSocketWindows); + AK_MAKE_DEFAULT_MOVABLE(TransportSocketWindows); + +public: + explicit TransportSocketWindows(NonnullOwnPtr socket); + + void set_peer_pid(int pid); + void set_up_read_hook(Function); + bool is_open() const; + void close(); + + void wait_until_readable(); + + ErrorOr transfer(Bytes, Vector const& handle_offsets); + + struct [[nodiscard]] ReadResult { + Vector bytes; + Vector fds; // always empty, present to avoid OS #ifdefs in Connection.cpp + }; + ReadResult read_as_much_as_possible_without_blocking(Function schedule_shutdown); + + // Obnoxious name to make it clear that this is a dangerous operation. + ErrorOr release_underlying_transport_for_transfer(); + + ErrorOr clone_for_transfer(); + +private: + ErrorOr duplicate_handles(Bytes, Vector const& handle_offsets); + +private: + NonnullOwnPtr m_socket; + int m_peer_pid = -1; +}; + +}