docs/operations/rebalance-stored-representation-impact.md
This guide covers the historical data-movement read defect tracked by
rustfs/backlog#1850. It is an
impact-assessment and read-only triage guide. It does not repair, rewrite,
migrate, delete, or quarantine any object.
The defect affected data movement when the source reader returned logical
plaintext but the target writer preserved the source's stored-representation
metadata and sizes. Compressed objects could therefore be copied as plaintext
under compression metadata. Server-managed encrypted objects could be copied as
plaintext under encryption metadata. The forward rebalance fix reached main
in commit
e11fcfbd
through PR #6057.
Upgrading prevents this defect in later rebalance runs. It does not validate or repair copies produced by an earlier run.
Treat a deployment as exposed when both conditions are true:
For an exposed deployment:
The release boundaries below were verified by tag ancestry. Commit
a236b0d0
introduced the merged rebalance and decommission implementation. Commit
2f25cf60
introduced the raw stored-representation read mode and wired it into
decommission. Commit e11fcfbd wired the same mode into rebalance.
| Release or commit range | Rebalance | Decommission | Operator classification |
|---|---|---|---|
Through 1.0.0-alpha.90, before a236b0d0 | Path not present | Path not present | Not affected by this data-movement path |
1.0.0-alpha.91 through 1.0.0-beta.8, from a236b0d0 up to but excluding 2f25cf60 | Decoded read | Decoded read | Both operations require assessment |
1.0.0-beta.9 through 1.0.0-rc.1, from 2f25cf60 up to but excluding e11fcfbd | Decoded read | Raw stored-representation read | Rebalance requires assessment; decommission is not affected by this defect |
1.0.0-rc.2 and later, at or after e11fcfbd | Raw stored-representation read | Raw stored-representation read | Forward-fixed; earlier copies still require assessment |
Preview tags follow the commit they reference. In particular, the rc.1
preview is affected and the rc.2 preview contains the forward fix. For custom
or untagged builds, compare the deployed commit with the three commit boundaries
rather than inferring behavior from a version string.
The historical decommission result is narrower than the rebalance result but is
not empty. Before 2f25cf60, decommission used the same ordinary decoded reader.
From 1.0.0-beta.9 onward it explicitly used raw_data_movement_read: true.
Any code change or automated remediation for the earlier decommission window is
outside this report and requires a separate issue.
The migration pipeline is a stored-representation copier. It preserves the
source ETag and internal metadata, uses stored part.size values to divide the
stream, and carries the decoded compression index. The affected rebalance read
options supplied only the version ID and lock setting, so the normal GET read
plan decompressed or decrypted the stream first. A target write could therefore
complete while its bytes no longer matched the metadata that described them.
Historical rebalance cleanup ran only after every version in an entry was reported moved. It then deleted the source entry. A target write accepted as a successful move could therefore be followed by source deletion even though a later GET of the target would fail. Conversely, a source-read failure prevented the version from being counted as moved and prevented normal source cleanup.
| Stored object class | Affected read result | Risk | Triage priority |
|---|---|---|---|
| Plain, uncompressed, unencrypted | Stored bytes and logical bytes are the same | No corruption expected from this defect alone | Low; sample to validate the scope assumption |
| Compressed | Decompressed bytes were divided using compressed part sizes while compression metadata and indexes were retained | Silent truncation or malformed compressed representation; GET can fail or return truncated data | High |
| SSE-S3 | Decrypted plaintext could be written while encryption metadata and ciphertext sizes were retained | Plaintext at rest on the target plus later decrypt failure | Critical |
| SSE-KMS | Decrypted plaintext could be written while KMS/encryption metadata and ciphertext sizes were retained | Plaintext at rest on the target plus later decrypt failure | Critical |
| SSE-C | The migration request did not have the customer key, so the normal read failed closed | Migration failure and possible incomplete progress; no successful corrupting copy is expected from this path | Medium; confirm the source was retained |
| Any compressed and encrypted combination | Multiple stored-representation assumptions were violated | Confidentiality exposure and data corruption | Critical |
The classification is specific to this defect. A low-risk classification does not certify an object against unrelated corruption.
Record the exact RustFS version and commit for every node that participated. Collect the authenticated rebalance status response, decommission status when applicable, service logs, deployment change records, and release history.
Persisted rebalance metadata records the run ID, participating pools, start and end state, bucket lists, counters, and the last bucket/object progress value. It does not persist a complete per-object movement ledger. Status metadata can prove that a run occurred and narrow time, pool, and bucket scope, but it cannot by itself enumerate every moved object.
If no reliable operation record remains, assume that every object version in a bucket present during the affected deployment interval is a candidate until other evidence narrows the set.
Use read-only S3 list and list-object-versions operations for the buckets in scope. Preserve bucket, key, version ID, last-modified time, size, ETag, storage class, and any client-side content digest. Join that list with:
Do not use ETag equality as proof of content integrity. The migration writer preserved the source ETag, including for a malformed target copy, and multipart or encrypted ETags are not general-purpose content hashes.
When API and application records cannot classify a candidate, copy xl.meta
from each relevant shard disk to a restricted evidence location and inspect the
copy on an offline host. Do not edit or decode metadata in place on a live data
path. Keep the evidence copies under the same access controls as the object.
The existing rustfs-filemeta example can decode an evidence copy. It prints
metadata values, some of which are sensitive encryption material, so redact
metadata values before they reach a terminal or report:
cargo run --quiet -p rustfs-filemeta --example dump_fileinfo -- /evidence/object/xl.meta |
sed -E 's/^(meta\[[^]]+\])=.*/\1=<redacted>/'
Use the output only as a screen:
x-rustfs-internal-compression or
x-minio-internal-compression key marks a compressed representation;actual-size, per-part size/actual_size, and compression-index totals
should be arithmetically consistent;Never include encryption metadata values in tickets, logs, chat, or assessment reports. Metadata consistency is necessary but not sufficient: the defect preserved metadata, so plausible sizes and a decodable index do not prove that the stored bytes match it.
For each high- or critical-risk candidate, perform a complete authenticated GET of the exact version into a restricted validation sink. Supply the customer key only for an authorized SSE-C check. Record the status, byte count, and a cryptographic digest calculated by the validation client. Compare it with a digest from an independently trusted source, backup, replica, or application record.
Interpret the result conservatively:
Storage-level confirmation for managed-SSE candidates may expose plaintext and sealed-key material. It must be performed only by the incident/security owner on offline evidence copies. Do not print, upload, or serve raw shard bytes, and do not bypass RustFS to return them to an application.
Record one result for every candidate version:
confirmed-good: full logical bytes match an independent digest;confirmed-affected: target decode/decrypt/length evidence and a trusted
source establish the mismatch, or authorized storage review confirms
plaintext under managed-SSE metadata;suspected: the version and operation window match, but proof is incomplete;not-applicable: evidence proves the object was plain and uncompressed or was
never selected by an affected operation;unrecoverable-pending-source: affected or suspected, with no verified source
yet found.Retain the evidence used for each decision. Do not collapse object versions with the same key into one result.
Successful historical migration could be followed by source-entry deletion. Therefore, neither successful rebalance status nor absence from the old source pool proves that the target bytes are sound. Recovery is possible only from a separately verified source, such as:
SSE-C normally failed before the target copy was accepted because the migration read had no customer key. That failure prevented normal source cleanup, but operators must verify the exact version on retained source media rather than assuming it is present.
If no verified source exists, mark the version unrecoverable for this incident.
Do not edit xl.meta, rewrite shard files, clear encryption/compression markers,
or overwrite the object in place. Those actions can destroy evidence, violate
retention/versioning policy, or turn a visible read failure into silent data
substitution. Any restoration or replacement procedure needs its own reviewed,
rollback-aware plan.
Release notes for 1.0.0-rc.2 and later should state:
Rebalance now copies the stored object representation for compressed and encrypted objects. Deployments that ran rebalance on versions from
1.0.0-alpha.91through1.0.0-rc.1should preserve old pool media and run the read-only assessment in this guide. Upgrading prevents new copies from this defect but does not repair historical copies. Deployments that ran decommission from1.0.0-alpha.91through1.0.0-beta.8require the same assessment. SSE-S3 and SSE-KMS candidates require security incident handling.
Do not recommend rerunning rebalance as remediation. Do not remove or repurpose old pool media until high- and critical-risk candidates have a recorded outcome and the incident owner has accepted the recovery limits.
The conclusions above are grounded in these repository facts:
crates/ecstore/src/services/rebalance/migration.rs now sets both
data_movement and raw_data_movement_read for rebalance source reads;crates/ecstore/src/core/pools.rs sets the same flags for decommission source
reads;crates/ecstore/src/object_api/readers.rs returns the stored byte range before
compression or encryption transforms when raw_data_movement_read is set;crates/ecstore/src/data_movement/mod.rs preserves stored part sizes, ETags,
indexes, and internal metadata during migration;a236b0d0 rebalance and decommission readers both used normal
read options, while 2f25cf60 changed only decommission to the raw mode;1.0.0-alpha.91, 1.0.0-beta.9, and
1.0.0-rc.2 for the implementation, decommission raw-read fix, and rebalance
raw-read fix respectively.