Commit graph

47 commits

Author SHA1 Message Date
Shannon Booth
02911253dd LibWeb+LibIPC: Preserve MessagePort queue state across transfer
A MessagePort can be transferred while it already has local queued
state such as incoming messages drained from its transport,
outgoing messages posted before a transport exists, and a pending
shutdown to apply once the port is enabled.

Serialize and restore that state as part of transfer so it moves with
the port instead of being left behind on the old transport.

Also mark transports that are being transferred so shutdown of the old
endpoint during handoff is not reported as peer EOF. That shutdown is
part of moving the transport to the new owner, not peer disconnected.

Co-Authored-By: Alexander Kalenik <kalenik.aliaksandr@gmail.com>
2026-04-09 19:59:16 +02:00
Aliaksandr Kalenik
4ea4d63008 Everywhere: Replace Unix socket IPC transport with Mach ports on macOS
On macOS, use Mach port messaging instead of Unix domain sockets for
all IPC transport. This makes the transport capable of carrying Mach
port rights as message attachments, which is a prerequisite for sending
IOSurface handles over the main IPC channel (currently sent via a
separate out-of-band path). It also avoids the need for the FD
acknowledgement protocol that TransportSocket requires, since Mach port
right transfers are atomic in the kernel.

Three connection establishment patterns:

- Spawned helper processes (WebContent, RequestServer, etc.) use the
  existing MachPortServer: the child sends its task port with a reply
  port, and the parent responds with a pre-created port pair.

- Socket-bootstrapped connections (WebDriver, BrowserProcess) exchange
  Mach port names over the socket, then drop the socket.

- Pre-created pairs for IPC tests and in-message transport transfer.

Attachment on macOS now wraps a MachPort instead of a file descriptor,
converting between the two via fileport_makeport()/fileport_makefd().

The LibIPC socket transport tests are disabled on macOS since they are
socket-specific.
2026-03-23 18:50:48 +01:00
Timothy Flynn
02783a6f1d LibIPC: Disable unused LocalSocket notifier in TransportSocket
TransportSocket uses its own pipe-based notification mechanism on the IO
thread, making LocalSocket's built-in Core::Notifier redundant. When the
socket reaches EOF, this notifier is disabled from the IO thread. Since
the QSocketNotifier lives on the main thread, the its destruction is
deferred. If the socket is closed before the deferred destruction runs,
Qt detects the invalid socket on the next poll and prints:

    QSocketNotifier: Invalid socket 50 and type 'Read', disabling...

Fix this by disabling the redundant socket-level notifier upfront in the
TransportSocket constructor.
2026-03-19 09:44:22 +01:00
Aliaksandr Kalenik
19627bba54 LibIPC: Return TransportHandle directly from create_paired()
Previously, `create_paired()` returned two full Transport objects, and
callers would immediately call `from_transport()` on the remote side to
extract its underlying fd. This wasted resources: the remote
Transport's IO thread, wakeup pipes, and send queue were initialized
only to be torn down without ever sending or receiving a message.

Now `create_paired()` returns `{Transport, TransportHandle}` — the
remote side is born as a lightweight handle containing just the raw fd,
skipping all unnecessary initialization.

Also replace `release_underlying_transport_for_transfer()` (which
returned a raw int fd) with `release_for_transfer()` (which returns a
TransportHandle directly), hiding the socket implementation detail
from callers including MessagePort.
2026-03-14 18:25:18 +01:00
Jonathan Gamble
fd0709b6ce LibIPC: Notify readers when thread exits 2026-03-14 02:05:34 -05:00
Aliaksandr Kalenik
da6b928909 LibIPC+LibWeb: Introduce IPC::Attachment abstraction
Replace IPC::File / AutoCloseFileDescriptor / MessageFileType in
the IPC message pipeline with a new IPC::Attachment class. This
wraps a file descriptor transferred alongside IPC messages, and
provides a clean extension point for platform-specific transport
mechanisms (e.g., Mach ports on macOS) that will be introduced later.
2026-03-13 20:22:50 +01:00
Aliaksandr Kalenik
db9652643a LibIPC+LibWeb+LibWebView: Remove clone_from_transport() API
Replace clone_from_transport() (which dup()s the FD) with
from_transport() (which releases the FD) in the WebWorkerClient
call site. The UI process never uses the WebWorkerClient connection
after spawning — it only passes the transport to WebContent — so
releasing instead of cloning is safe and simpler.

This removes clone_from_transport() from TransportHandle, and
clone_for_transfer() from TransportSocket/TransportSocketWindows,
as they no longer have any callers.
2026-03-13 15:34:15 +01:00
Aliaksandr Kalenik
2e881978af LibIPC+LibWeb+LibWebView+Services: Add Transport::create_paired()
Consolidate the repeated socketpair + adopt + configure pattern from
4 call sites into a single Transport::create_paired() factory method.
This fixes inconsistent error handling and socket configuration across
call sites, and prepares for future mach port support on macOS.
2026-03-11 14:42:24 +01:00
Jelle Raaijmakers
d3e1396ece LibIPC: Remove message buffer allocations for enqueuing messages
We were allocating vectors on the heap and copying the message header's
and payload's bytes to it before passing them on to
`::enqueue_message()`.

Remove these allocations and just pass `ReadonlyBytes` views into the
message header and payload directly. On my machine, this reduces the
time spent on the send-side queuing path by 13% to 42%, depending on the
message size.
2026-03-04 17:17:49 -05:00
Jelle Raaijmakers
0b1206cf54 LibIPC: Do not allocate for unprocessed bytes
While we're processing received messages, we can end up with unprocessed
bytes after the last message. Instead of copying the data into a new
ByteBuffer, just move the bytes inside the existing buffer and resize
it.

This does mean that as long as there are unprocessed bytes after reading
incoming messages, the buffer does not shrink. But as soon as there's
nothing left, we clear this buffer again.
2026-03-04 17:15:21 -05:00
Zaggy1024
e2635af2ed Everywhere: Move the thread name parameter for Thread constructors
The name parameter formats very poorly when a lambda is passed to
Thread, so let's instead put it first now that all Threads are named.
2026-01-26 15:51:46 -06:00
Zaggy1024
d2a1d727ac Everywhere: Give unnamed threads names 2026-01-26 15:51:46 -06:00
Andreas Kling
a0c389846e Revert "LibIPC: Move message decoding from main thread to I/O thread"
This reverts commit 757795ada4.

Appears to have regressed WPT.
2026-01-25 12:19:53 +01:00
Andreas Kling
757795ada4 LibIPC: Move message decoding from main thread to I/O thread
Previously, IPC messages were decoded on the main thread:

1. I/O thread received raw bytes and file descriptors
2. I/O thread stored them in a queue and notified main thread
3. Main thread decoded bytes into Message objects
4. Main thread processed the messages

Now, decoding happens on the I/O thread:

1. I/O thread receives raw bytes and file descriptors
2. I/O thread decodes them using a configurable MessageDecoder
3. I/O thread calls MessageHandler which stores decoded messages
4. I/O thread signals condition variable (for sync waiters)
5. I/O thread wakes main event loop via deferred_invoke()
6. Main thread processes already-decoded messages

This is achieved by:

- Adding MessageDecoder and MessageHandler callbacks to TransportSocket
- Connection template sets up the decoder (tries both endpoints)
- ConnectionBase::initialize_messaging() sets up the handler
- Storing a WeakEventLoopReference to wake the main thread
- Using mutex + condition variable for thread-safe queue access
- Sync message waiting now uses the CV directly instead of polling

The raw message API (read_as_many_messages_as_possible_without_blocking)
is preserved for MessagePort which uses its own decoding logic.

This architecture prepares for future multi-thread dispatch where
different message types could be routed to different handler threads
(e.g., scrolling messages to a dedicated scroll thread).
2026-01-25 09:32:51 +01:00
Andreas Kling
cb3bc3c493 LibIPC: Move IPC message limits to a shared Limits.h header
This consolidates the message size and FD count limits into a single
header file that can be used by both the encoding and decoding sides
of the IPC layer.
2026-01-22 17:38:15 +01:00
Andreas Kling
bef09fc277 LibIPC: Add limits on accumulated unprocessed data in TransportSocket
A malicious or misbehaving peer could send data faster than we process
it, causing unbounded memory growth in the unprocessed bytes buffer
and file descriptor queue.

Add MAX_UNPROCESSED_BUFFER_SIZE (128 MiB) and MAX_UNPROCESSED_FDS (512)
limits. When exceeded, the peer is disconnected gracefully rather than
allowing memory exhaustion.
2026-01-22 17:38:15 +01:00
Andreas Kling
bdca85f985 LibIPC: Add maximum limits for IPC message size and FD count
Reject messages from peers that exceed reasonable limits:
- Maximum payload size: 64 MiB
- Maximum file descriptor count: 128

Also use checked arithmetic for message size calculations to prevent
integer overflow attacks.

These limits prevent malicious peers from causing excessive memory
allocation or resource exhaustion.
2026-01-22 17:38:15 +01:00
Andreas Kling
d15acc46d6 LibIPC: Use checked arithmetic for message size calculations
The message parsing loop performs arithmetic on payload_size (u32) and
index (size_t). While overflow is unlikely on 64-bit systems, use
Checked<size_t> to explicitly validate:

1. message_size = payload_size + sizeof(MessageHeader)
2. new_index = index + payload_size + sizeof(MessageHeader)

This prevents potential integer overflow attacks from malicious peers.
2026-01-22 17:38:15 +01:00
Andreas Kling
9e34434f3b LibIPC: Handle allocation failures gracefully in TransportSocket
Replace crash-on-OOM patterns with graceful error handling:

- Use try_append() instead of append() for buffer operations
- Handle ByteBuffer::copy() failure instead of using MUST()

A malicious peer could send messages with large payload sizes to
trigger OOM conditions. Instead of crashing, we now disconnect
the misbehaving peer.
2026-01-22 17:38:15 +01:00
Andreas Kling
1a213a9020 LibIPC: Harden TransportSocket against malicious FD count values
A malicious peer could exploit the file descriptor acknowledgement
protocol in several ways:

1. Send messages with fd_count values that overflow when accumulated
2. Send acknowledgements claiming more FDs were received than we sent

Both attacks would crash the recipient via VERIFY failures in
Queue::dequeue() or integer overflow.

Fix by using Checked<u32> for fd counters and validating the queue
isn't empty before dequeuing acknowledged FDs.
2026-01-22 17:38:15 +01:00
Andreas Kling
01c07a9f78 LibIPC: Replace crash-on-error with graceful peer disconnect
A misbehaving or compromised IPC peer (e.g. a WebContent process running
malicious JavaScript) could previously crash the UI process by sending
malformed messages. This was a problem: untrusted peers should only be
able to kill their own connection, not crash the process handling them.

Replace VERIFY_NOT_REACHED() and VERIFY() calls in error paths with
graceful disconnection using the existing shutdown infrastructure
(m_peer_eof, IOThreadState::Stopped, ShouldShutdown::Yes, etc.)

Affected error paths:
- Malformed IPC messages that fail to parse
- poll() errors and POLLERR/POLLNVAL conditions
- Unexpected send/receive errors on the socket
- Invalid message header types
- Protocol violations (e.g. bad FileDescriptorAcknowledgement)

All error paths now log via dbgln() for debugging before disconnecting.
2026-01-22 17:38:15 +01:00
Andreas Kling
dacd6b4530 LibIPC: Handle EAGAIN on non-blocking wakeup pipe read
The wakeup pipe is created with O_NONBLOCK, but MUST() was used for
reads. This could cause crashes on spurious wakeups when EAGAIN is
returned. Since the read is just to drain the pipe and wake the IO
thread, we can safely ignore any errors.
2026-01-13 21:05:58 +01:00
Aliaksandr Kalenik
b5db79be6d LibIPC: Change TransportSocket to read and write on I/O thread
Previously, TransportSocket sent queued messages from a separate thread
but performed all reading on the main thread. With this change, both
reading and writing are handled on the same I/O thread. This would allow
us to read IPC messages even while the main thread is blocked and
process them on a different thread (e.g., a rendering thread).
2025-11-02 22:43:10 +01:00
Zaggy1024
2aaf53bd2c Everywhere: Use a forward declaration for pointers to Threading::Thread 2025-09-22 17:28:21 -05:00
stasoid
ccf303eefc LibIPC: Move AutoCloseFileDescriptor to its own header 2025-06-17 15:36:47 -06:00
Andrew Kaster
8095663f86 LibIPC: Chunk sent file descriptors by MAX_TRANSFER_FDS
This limitation of the underlying Unix socket implementation can
cause IPC failures on pages with tons of images and network requests.

Modify the code called from TransportSocket's send thread to limit the
number of fds to MAX_TRANSFER_FDS, and ensure that we will keep sending
as long as we have either bytes or file descriptors to send.
2025-05-24 19:15:06 +03:00
Andrew Kaster
87fbfcadd1 LibIPC: Refactor message header encoding to use a helper method
Manually memcpying into a Vector in the body of post_message is
a bit much.
2025-05-24 19:15:06 +03:00
Timothy Flynn
36da270dbe LibIPC+LibWeb: Flush MessagePort messages before closing
The spec isn't super clear on what disentagling a MessagePort means. But
we are required to send all pending messages before closing the port.

This is a bit tricky because the transport socket performs writes on a
background thread. From the main thread, where the disentanglement will
occur, we don't really know the state of the write thread. So what we do
here is stop the background thread then flush all remaining data from
the main thread.
2025-05-21 06:54:44 -04:00
Timothy Flynn
8b3355ed0d LibIPC: Address a couple of clangd warnings in IPC::TransportSocket
* We need the full definition of IPC::File in the header.
* We need(ed) Core::System in the header. Move AutoCloseFileDescriptor's
  ctor and dtor out-of-line to avoid this.
2025-05-21 06:54:44 -04:00
Timothy Flynn
08e82ddaf0 LibCore: Prefer Error::is_errno over Error::is_syscall
These are the only users of the latter, so let's just use is_errno
(which will effectively give us the same result) to avoid confusion
over these.
2025-05-10 21:19:46 -04:00
Andrew Kaster
16f72ca36c LibIPC: Don't verify that socket is closed when getting EPIPE on send
We can get an EPIPE when the other end of the connection closes, such
as when switching to a new WebContent process due to site isolation.
2025-04-30 11:12:23 -04:00
Andrew Kaster
27db7ed11f LibIPC: Guard better against closure in the TransportSocket send thread
And crash less when the socket is closed while there are still messages
in the queue.
2025-04-29 09:51:22 -06:00
Aliaksandr Kalenik
466c793fdb LibIPC: Use AllocatingMemoryStream in TransportSocket send queue
Memory stream is a more suitable container for the socket send queue,
as using it results in fewer allocations than trying to emulate a stream
using a Vector.
2025-04-15 18:48:53 +02:00
Aliaksandr Kalenik
b53694b4c0 LibIPC+LibWeb: Delete LargeMessageWrapper workaround in IPC connection
Bring back 2d625f5c23
2025-04-10 23:40:02 +02:00
Aliaksandr Kalenik
681333d329 LibIPC: Protect underlying socket of TransportSocket with RWLock
This is necessary to prevent the socket from being closed while it is
being used for reading or writing.
2025-04-10 23:40:02 +02:00
Aliaksandr Kalenik
14dc7686c3 LibIPC: Change TransportSocket to write large messages in small chunks
Bring back d6080d1fdc with a missing check
whether underlying socket is closed, before accessing `fd()` that is
optional and empty in case of closed socket.
2025-04-10 23:40:02 +02:00
Tim Ledbetter
1ee56d34e7 Revert "LibIPC+LibWeb: Delete LargeMessageWrapper workaround in IPC…
…connection"

This reverts commit 2d625f5c23.
2025-04-10 16:24:38 +01:00
Tim Ledbetter
3fcdbef327 Revert "LibIPC: Change TransportSocket to write large messages in…"
…small chunks.

This reverts commit d6080d1fdc.
2025-04-10 16:24:38 +01:00
Aliaksandr Kalenik
2d625f5c23 LibIPC+LibWeb: Delete LargeMessageWrapper workaround in IPC connection
It's no longer needed because TransportSocket is now capable of properly
sending large messages.
2025-04-10 01:30:08 +02:00
Aliaksandr Kalenik
d6080d1fdc LibIPC: Change TransportSocket to write large messages in small chunks
With this change TransportSocket becomes capable of sending large
messages without relying on workarounds, such as sending the message as
a shared memory file descriptor when it can't fully fit into the socket
buffer.

It's implemented by combining all enqueued messages into two buffers:
one for bytes and another for fds, and repeatedly attempts to write them
in smaller chunks, waiting for the socket to become writable again if
the receiver needs time to consume the data.

Another significant improvement brought by this change is that we no
longer drop messages queued for sending if the socket doesn't become
writable after a 100ms timeout. Instead, we return the message to the
send buffer and continue waiting for the socket to become writable.
2025-04-10 01:30:08 +02:00
Aliaksandr Kalenik
db8c443392 Everywhere: Make TransportSocket non-movable
Instead of wrapping all non-movable members of TransportSocket in OwnPtr
to keep it movable, make TransportSocket itself non-movable and wrap it
in OwnPtr.
2025-04-09 15:27:52 +02:00
Aliaksandr Kalenik
14bac7b287 LibIPC: Move send thread from IPC connection to the transport layer
By doing this we also make MessagePort, that relies on IPC transport,
to send messages from separate thread, which solves the problem when
WebWorker and WebContent could deadlock if both were trying to post
messages at the same time.

Fixes https://github.com/LadybirdBrowser/ladybird/issues/4254
2025-04-08 21:09:24 +02:00
Aliaksandr Kalenik
ab35325003 LibIPC: Move early fd deallocation workaround to the transport layer
Reimplements c3121c9d at the transport layer, allowing us to solve the
same problem once, in a single place, for both the LibIPC connection and
MessagePort. This avoids exposing a workaround for a macOS specific Unix
domain socket issue to higher abstraction layers.
2025-04-08 21:09:24 +02:00
Aliaksandr Kalenik
ac643aa392 LibIPC: Break from message parsing if whole message payload is not ready
Fixes the bug when we try to read message payload without checking if we
received enough bytes or file descriptors.
2025-04-07 20:26:01 +02:00
Aliaksandr Kalenik
a371f849e3 LibIPC: Make TransportSocket responsible for reading entire messages
With this change, the responsibility for prepending messages with their
size and ensuring the entire message is received before returning it to
the caller is moved to TransportSocket. This removes the need to
duplicate this logic in both LibIPC and MessagePort.

Another advantage of reducing message granularity at IPC::Transport
layer is that it will make it easier to support alternative transport
implementations (like Mach ports, which unlike Unix domain sockets are
not stream oriented).
2025-04-07 16:59:49 +02:00
Aliaksandr Kalenik
4b04e97feb LibWeb: Send IPC messages exceeding socket buffer through shared memory
It turned out that some web applications want to send fairly large
messages to WebWorker through IPC (for example, MapLibre GL sends
~1200KiB), which led to failures (at least on macOS) because buffer size
of TransportSocket is limited to 128KiB. This change solves the problem
by wrapping messages that exceed socket buffer size into another message
that holds wrapped message content in shared memory.

Co-Authored-By: Luke Wilde <luke@ladybird.org>
2025-04-03 13:55:41 +02:00
Timothy Flynn
93712b24bf Everywhere: Hoist the Libraries folder to the top-level 2024-11-10 12:50:45 +01:00
Renamed from Userland/Libraries/LibIPC/TransportSocket.cpp (Browse further)