rustfs/rustfs · error · io::Error

unexpected EOF while reading DARE header

Error message

unexpected EOF while reading DARE header

What it means

DecryptReader received EOF from the inner reader after reading 1-15 of the 16 DARE package header bytes. Unlike the clean-boundary case, this is a mid-header cutoff: the stream ends a few bytes into a frame, so framing cannot complete and the reader returns UnexpectedEof. It indicates the ciphertext is short by less than one header, or the read window ended inside a header.

Source

Thrown at crates/rio-v2/src/encrypt_reader.rs:481

                        if n == 0 {
                            if *this.header_read == 0 {
                                // Clean EOF at a package boundary. Execution only reaches here
                                // with `finalized == false` (the finalized case is consumed at the
                                // loop top). If at least one package of the current part has been
                                // decrypted (`ref_nonce.is_some()`) but we never saw a final-flagged
                                // package, the final package is missing => DARE truncation. Zero
                                // decrypted packages (`ref_nonce.is_none()`) is a legitimately empty
                                // object (encrypt emits no packages for empty plaintext), so accept.
                                if this.ref_nonce.is_some() {
                                    return Poll::Ready(Err(io::Error::new(
                                        io::ErrorKind::UnexpectedEof,
                                        "DARE stream truncated before a finalized package",
                                    )));
                                }
                                *this.finished = true;
                                return Poll::Ready(Ok(()));
                            }
                            return Poll::Ready(Err(io::Error::new(
                                io::ErrorKind::UnexpectedEof,
                                "unexpected EOF while reading DARE header",
                            )));
                        }
                        *this.header_read += n;
                    }
                    Poll::Ready(Err(err)) => return Poll::Ready(Err(err)),
                }
            }

            let header = this.header_buf;
            if header[0] != DARE_VERSION_20 {
                return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, "unsupported DARE version")));
            }
            if !matches!(header[1], DARE_CIPHER_AES_256_GCM | DARE_CIPHER_CHACHA20_POLY1305) {
                return Poll::Ready(Err(io::Error::new(io::ErrorKind::InvalidData, "unsupported DARE cipher suite")));
            }
            let payload_len = usize::from(u16::from_le_bytes([header[2], header[3]])) + 1;

View on GitHub (pinned to 9e6e02ea09)

Solutions

  1. Verify object size against metadata (the encrypted size must be a valid DARE stream length) and heal or re-upload.
  2. When assembling encrypted objects from parts, align reads to package boundaries derived from the stream, not arbitrary offsets.
  3. Check the inner reader's source for early termination (Content-Length mismatch, connection cut).
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the encrypted stream length is consistent before decrypting
if stored_size % DARE_PACKAGE_ALIGNMENT == 0 || stored_size < DARE_HEADER_SIZE {
    return Err(marker_error("encrypted object size is not a valid DARE stream"));
}

Try / catch

match r.read_buf(&mut buf).await {
    Ok(n) => Ok(n),
    Err(e) if e.kind() == io::ErrorKind::UnexpectedEof && e.to_string().contains("DARE header") => {
        // Stream ends inside a 16-byte header: truncated object or misaligned range — fail, do not skip.
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Object truncated mid-header by a few bytes (short write, damaged tail shard), or a byte range/assembly that stops partway into a 16-byte DARE header instead of at a package boundary.

Common situations: Interrupted writes leaving a ragged tail; erasure reassembly short by a few bytes; custom range readers slicing encrypted objects at arbitrary offsets.

Related errors


AI-assisted analysis of rustfs/rustfs@9e6e02ea09 (2026-08-16). Data as JSON: /api/errors/75d1b8bf49603fa5. Report an issue: GitHub.