rustfs/rustfs · critical · io::Error

DARE stream truncated before a finalized package

Error message

DARE stream truncated before a finalized package

What it means

DecryptReader hit a clean EOF exactly at a DARE package boundary, but at least one package of the current part had already been decrypted (ref_nonce set) and none of them carried the final flag. DARE v2 streams must terminate with a final-flagged package; its absence means the encrypted object is truncated, so the reader returns UnexpectedEof. Zero decrypted packages is treated as a legitimately empty object and accepted.

Source

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

            }

            while *this.header_read < DARE_HEADER_SIZE {
                let mut read_buf = ReadBuf::new(&mut this.header_buf[*this.header_read..]);
                match this.inner.as_mut().poll_read(cx, &mut read_buf) {
                    Poll::Pending => return Poll::Pending,
                    Poll::Ready(Ok(())) => {
                        let n = read_buf.filled().len();
                        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)),
                }
            }

View on GitHub (pinned to 9e6e02ea09)

Solutions

  1. Re-upload the object from a known-good copy — the plaintext is unrecoverable past the truncation point.
  2. If multipart, verify the complete-multipart part list matches the actually uploaded parts (order and content) before retrying.
  3. Check server logs around the original write for interrupted/crashed PUTs.
  4. Audit any tooling that reassembles or copies encrypted objects part-by-part; ensure it preserves the final package.
Defensive patterns

Strategy: try-catch

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("finalized package") => {
        // Encrypted object truncated before its final package: unrecoverable — re-upload required.
        integrity::mark_unrecoverable(&object_id);
        Err(e)
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: An interrupted encrypted upload committed without the final package; multipart assembly using a wrong or stale part list so the tail part carrying the final package is missing; the object truncated after write; a range/assembly path that drops the last package.

Common situations: Crash during PUT of an SSE-encrypted object; manual xl.meta or multipart surgery; completing a multipart upload with an outdated parts list; mixed-version clusters during rolling upgrades mishandling the final flag.

Related errors


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