Fedora Account System
Red Hat Associate
Red Hat Customer
AI_ONLY_REPORT package: device-mapper-multipath-0.9.9-18.el10 ------ Summary: Local Denial of Service via Blocking IPC Send Operations: `multipathd` can block its IPC listener thread when replying on a blocking client socket to a local client that stops reading responses. Requirements to exploit: Local access to the `multipathd` UNIX control socket, the ability to issue an allowed IPC command, and the ability to keep the connection open without reading replies until the daemon blocks in `send()`. Component affected: `device-mapper-multipath-0.9.9-18.el10`, primarily `multipathd/uxlsnr.c` (`new_client()` and `client_state_machine()` in `CLT_SEND`), with socket reachability influenced by `libmpathutil/uxsock.c` Version affected: `device-mapper-multipath-0.9.9-18.el10` when `multipathd` is running and its local IPC socket is reachable to the attacking user Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not notified. CVSS: CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H - 5.5 (MEDIUM) AV:L - exploitation requires local access to the UNIX domain control socket. AC:L - the attack is straightforward: send valid IPC commands and stop reading replies until the socket send buffer is exhausted. PR:N - no prior privileges are required where the control socket is exposed to unprivileged local users. UI:N - no user interaction is required. S:U - the impact remains within the daemon's own security scope. C:N - no confidentiality impact was established. I:N - no integrity impact was established. A:H - legitimate IPC operations can hang or time out once the listener thread blocks. Impact: Moderate. This issue can let a local user deny availability of the `multipathd` IPC control path in deployments where the daemon's socket is reachable to that user. The available evidence does not show privilege escalation, code execution, or direct confidentiality or integrity impact, and the effect appears limited to the daemon's control interface rather than full system compromise. Under Red Hat's severity guidance, that makes Moderate more appropriate than Important or Critical, while still exceeding Low because the condition appears practically reachable in default-style local IPC exposure. Embargo: no Reason: the issue is a local-only denial of service with no demonstrated confidentiality, integrity, or code-execution impact, and exposure can be reduced through local access controls on the IPC interface. Acknowledgement: Aisle Research Vulnerability Details: Accepted client sockets are left in blocking mode, and the reply path uses blocking `send()` operations. A local client that sends a valid command and then stops reading can cause the listener thread to block once the kernel send buffer fills. Because the listener loop continues processing clients already in `CLT_SEND`, this can stall subsequent IPC servicing for other clients. ```c static void new_client(int ux_sock) { ... fd = accept(ux_sock, &addr, &len); ... c->fd = fd; /* blocking accepted socket */ c->state = CLT_RECV; } ``` ```c case CLT_SEND: if (c->cmd_len == 0) { size_t len = get_strbuf_len(&c->reply) + 1; if (send(c->fd, &len, sizeof(len), MSG_NOSIGNAL) != sizeof(len)) c->error = -ECONNRESET; c->cmd_len = len; return STM_BREAK; } if (c->len < c->cmd_len) { const char *buf = get_strbuf_str(&c->reply); n = send(c->fd, buf + c->len, c->cmd_len - c->len, MSG_NOSIGNAL); ... } ``` The available code also indicates that non-root clients are restricted to `list` commands, but those still produce replies and appear sufficient to exercise this condition where the socket is accessible: ```c if (name[0] != '@' && chmod(name, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) == -1) ... ``` The precise time needed to trigger the blockage depends on runtime reply sizes and socket buffering, but the issue appears reachable by repeatedly issuing valid reply-generating commands on a connection that never consumes responses. Steps to reproduce: 1. Start `multipathd` with its normal IPC listener enabled. 2. Connect to the local control socket from an unprivileged local account where access is permitted. 3. Send a valid command such as `list daemon` and do not read the reply. 4. Continue sending valid commands on the same connection until the server-side socket buffer is saturated. 5. From another terminal, issue a normal IPC command and observe that it hangs or times out while the listener is blocked in `send()`. Minimal PoC client: ```python #!/usr/bin/env python3 import socket, struct sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect("/run/multipathd.socket") # adjust if needed cmd = b"list daemon\x00" frame = struct.pack("=Q", len(cmd)) + cmd while True: sock.sendall(frame) # intentionally never read replies ``` Mitigation: Restrict access to the `multipathd` IPC socket to trusted local administrators only, or otherwise prevent untrusted local users from reaching the control interface. Environments that do not expose the listener to unprivileged users substantially reduce the practical attack surface. Proposed Fix: Set accepted client sockets to non-blocking mode and avoid blocking in `CLT_SEND` unless `POLLOUT` is present. The following minimal patch addresses both points. ```diff diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c index XXXXXXX..YYYYYYY 100644 — a/multipathd/uxlsnr.c +++ b/multipathd/uxlsnr.c @@ -123,6 +123,13 @@ static void new_client(int ux_sock) fd = accept(ux_sock, &addr, &len); if (fd == -1) return; + { + int flags = fcntl(fd, F_GETFL, 0); + if (flags != -1) + (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + c = (struct client *)calloc(1, sizeof(*c)); if (!c) { close(fd); @@ -551,6 +558,9 @@ static int client_state_machine(struct client *c, struct vectors *vecs, case CLT_SEND: + if (!(revents & POLLOUT)) + return STM_BREAK; + if (get_strbuf_len(&c->reply) == 0) default_reply(c, c->error); @@ -558,13 +568,21 @@ static int client_state_machine(struct client *c, struct vectors *vecs, if (send(c>fd, &len, sizeof(len), MSG_NOSIGNAL) - != sizeof(len)) c>error = -ECONNRESET; + n = send(c->fd, &len, sizeof(len), + MSG_NOSIGNAL | MSG_DONTWAIT); + if (n == -1) { + if (!(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) + c->error = -ECONNRESET; + return STM_BREAK; + } + if (n != (ssize_t)sizeof(len)) + return STM_BREAK; c->cmd_len = len; return STM_BREAK; } if (c->len < c->cmd_len) { const char *buf = get_strbuf_str(&c->reply); n = send(c>fd, buf + c->len, c->cmd_len - c->len, MSG_NOSIGNAL); + n = send(c->fd, buf + c->len, c->cmd_len - c->len, + MSG_NOSIGNAL | MSG_DONTWAIT); if (n == -1) { - if (!(errno == EAGAIN || errno == EINTR)) + if (!(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)) c->error = -ECONNRESET; } else c->len += n; ``` ------ This report was generated using AI technology. Always review AI-generated content prior to use