rustdesk/rustdesk · error · io::Error

drm: refusing a dma-buf descriptor with num_planes {} (1..=4

Error message

drm: refusing a dma-buf descriptor with num_planes {} (1..=4)

What it means

The render-side converter refuses a dma-buf descriptor whose num_planes is greater than 4 (0 is normalized to 1 and written back so the C side reads the bounded count). The fixed pitches/offsets arrays hold 4 entries, so a larger count would read out of bounds during plane extent validation and conversion.

Source

Thrown at libs/scrap/src/common/drm_render.rs:80

    /// Returns context-owned linear pixels valid ONLY until the next `convert()`; row stride is `len / height`.
    pub fn convert(
        &mut self,
        desc: &mut drmtap_dmabuf_desc,
        received_fd: RawFd,
    ) -> io::Result<(&[u8], u32, u32, Pixfmt)> {
        {
            let (w, h) = (desc.width, desc.height);
            if w == 0 || h == 0 || w > MAX_DIM || h > MAX_DIM {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!("drm: refusing a dma-buf descriptor with geometry {w}x{h}"),
                ));
            }
            // Reject, do not clamp, and write the normalized count back so the C reads the count bounded here.
            let planes = if desc.num_planes == 0 { 1 } else { desc.num_planes };
            if planes > 4 {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!(
                        "drm: refusing a dma-buf descriptor with num_planes {} (1..=4)",
                        desc.num_planes
                    ),
                ));
            }
            desc.num_planes = planes;
            let planes = planes as usize;
            for p in 0..planes {
                let extent = (desc.pitches[p] as usize)
                    .checked_mul(h as usize)
                    .and_then(|rows| rows.checked_add(desc.offsets[p] as usize));
                match extent {
                    Some(end) if end <= MAX_FRAME_BYTES => {}
                    other => {
                        return Err(io::Error::new(
                            io::ErrorKind::InvalidData,

View on GitHub (pinned to 7aa98d43cf)

Solutions

  1. Rebuild and redeploy the service and converter together so drmtap_dmabuf_desc layouts match.
  2. Verify the libdrmtap version both halves load at runtime.
  3. Drop and re-establish the capture session so a fresh descriptor is sent.
  4. If reproducible with matched builds, report the descriptor contents upstream.
Defensive patterns

Strategy: validation

Validate before calling

fn plane_count_acceptable(desc: &DrmtapDmabufDesc) -> bool {
    desc.num_planes <= 4 // 0 allowed; normalized to 1
}

Type guard

fn is_converter_plane_refusal(e: &std::io::Error) -> bool {
    e.kind() == std::io::ErrorKind::InvalidData
        && e.to_string().contains("num_planes")
}

Try / catch

match converter.convert(&mut desc, fd) {
    Ok(frame) => frame,
    Err(e) if is_converter_plane_refusal(&e) => {
        log::error!("bad plane count from peer: {e}");
        return reimport_or_fallback();
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: convert() receives a descriptor with num_planes > 4 due to IPC corruption, struct layout mismatch between the serializer and the converter, or a malformed descriptor.

Common situations: Build/version skew between the service half and converter half; a compromised or buggy peer process; changed drmtap_dmabuf_desc layout after a libdrmtap upgrade without rebuilding both sides.

Related errors


AI-assisted analysis of rustdesk/rustdesk@7aa98d43cf (2026-08-16). Data as JSON: /api/errors/00f10ad0b3e8a93e. Report an issue: GitHub.