rustdesk/rustdesk · error · std::io::Error

drm: truncated SCM_RIGHTS control message (MSG_CTRUNC)

Error message

drm: truncated SCM_RIGHTS control message (MSG_CTRUNC)

What it means

drm_recvmsg reads from the _drm Unix socket expecting at most one fd via SCM_RIGHTS. If the kernel sets MSG_CTRUNC on the msghdr, the ancillary data did not fit in the control buffer and file descriptors were silently dropped; the function drops any received fd and returns this error because proceeding would leak or misattribute fds.

Source

Thrown at src/ipc/drm.rs:1239

                let mut rawfd: libc::c_int = -1;
                std::ptr::copy_nonoverlapping(
                    data.add(i * std::mem::size_of::<libc::c_int>()),
                    &mut rawfd as *mut libc::c_int as *mut u8,
                    std::mem::size_of::<libc::c_int>(),
                );
                if rawfd >= 0 {
                    let owned = OwnedFd::from_raw_fd(rawfd);
                    if got.is_none() {
                        got = Some(owned);
                    } // else: surplus fd, dropped here -> closed
                }
            }
        }
        cmsg = libc::CMSG_NXTHDR(&msg, cmsg);
    }
    if msg.msg_flags & libc::MSG_CTRUNC != 0 {
        drop(got);
        return Err(std::io::Error::new(
            std::io::ErrorKind::Other,
            "drm: truncated SCM_RIGHTS control message (MSG_CTRUNC)",
        ));
    }
    Ok((n as usize, got))
}

async fn drm_write_all(
    stream: &tokio::net::UnixStream,
    mut buf: &[u8],
    mut pass_fd: Option<RawFd>,
) -> ResultType<()> {
    // ONE deadline for the whole write: arming it per readiness wait lets a dripping peer re-arm it.
    let deadline =
        tokio::time::Instant::now() + std::time::Duration::from_millis(DRM_SEND_TIMEOUT_MS);
    while !buf.is_empty() {
        match tokio::time::timeout_at(deadline, stream.writable()).await {
            Ok(r) => r?,

View on GitHub (pinned to 91c9fccbb0)

Solutions

  1. Ensure both ends use the same protocol: the sender must pass exactly one fd per frame (drm_sendmsg only supports one)
  2. Enlarge the receive control buffer so it can hold the maximum expected ancillary data
  3. Treat MSG_CTRUNC as fatal for the connection and reconnect, closing the received fds first (this code already drops them)
  4. Verify no other component on the same socket (proxy, forwarder) is batching multiple SCM_RIGHTS messages together
Defensive patterns

Strategy: fallback

Try / catch

match read_result {
    Err(e) if e.to_string().contains("MSG_CTRUNC") => {
        log::warn!("truncated ancillary data; closing and reconnecting");
        reconnect_drm_channel();
    }
    other => other.map_err(|e| e.into()),
}

Prevention

When it happens

Trigger: Calling drm_read_full -> drm_recvmsg when the peer attaches ancillary data larger than DRM_CMSG_CAP (e.g. multiple fds via SCM_RIGHTS) so the kernel truncates the control message (msg_flags & MSG_CTRUNC != 0).

Common situations: Protocol version mismatch where a peer sends several fds per frame but this side only allocates CMSG_SPACE for one; a buffer-size regression shrinking DRM_CMSG_CAP; a malicious/mismatched peer flooding the socket with fd-passing messages.

Related errors


AI-assisted analysis of rustdesk/rustdesk@91c9fccbb0 (2026-09-10). Data as JSON: /api/errors/b7174fafacd06340. Report an issue: GitHub.