compact: authenticate a gap object before dropping its bytes, #10093 - #10167
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #10167 +/- ##
==========================================
+ Coverage 87.93% 88.05% +0.11%
==========================================
Files 103 103
Lines 18893 18913 +20
Branches 2917 2919 +2
==========================================
+ Hits 16613 16653 +40
+ Misses 1583 1567 -16
+ Partials 697 693 -4 ☔ View full report in Codecov by Harness. |
|
@mr-raj12 please rebase onto current master. and then ask claude for feedback. |
|
@mr-raj12 ping |
d1597ec to
c128990
Compare
|
review by claude fable 5.1 max Verdict. The authentication part is right and I found no correctness bug in it. I would still ask for one change before merging: drop the 1. Blocking: the size-equality check regresses reclaim. Equal chunk ids mean equal plaintext, not equal stored size. I ran
Sizes differ whenever two clients use different compression, when The check buys nothing that the validator does not already provide. The PR text itself notes that 2. Minor: the new read-ahead constant duplicates an existing one. 3. Minor: docs wording. "payloads are user content stored as it is" is only true with 4. Test nit. Verified fine:
Checks run at c128990: ruff, black and
|
|
@mr-raj12 ping?! |
c128990 to
ac58e4c
Compare
|
Thanks, all four addressed in ac58e4c. 1. Dropped the
The "second source" bullet is gone from Worth noting the removal costs no safety property: 2. 3. Reworded, but not to "stored unencrypted" alone: in the 4. Added Full suite: 2993 passed, 1107 skipped. ruff, black and |
|
review by claude fable 5.1 max (follow-up on ac58e4c) The four points from the first review are addressed as requested; I re-ran the two-compression experiment on ac58e4c and every row is dropped now, with the dropped range equal to the gap copy's own authenticated size. Full suite here: 3011 passed, 993 skipped. The Sphinx build is clean for this change (the only two warnings are the theme's parallel-read notices), and the new section renders as intended. On the inline comment about delete-obj belongs in the code, not in the format docs. The general rule stays in the docs: without a validator the walk drops no gap bytes. The delete-obj consequence is a TODO in Wrong claim in the same paragraph. "A blob that does not authenticate keeps its bytes, for A corrupt header in a gap blocks reclaim behind it for good. This is what the comment "the walk steps by obj_size before anything authenticates it: a wrong one lands the walk at a wrong offset, where nothing authenticates either" describes. Data is safe, but after the wrong step the walk breaks out of the gap, every rewrite copies the gap verbatim, and repair keeps a dropped object's bytes, so the same desync repeats on every compact and the superseded duplicates behind the corrupt object are never reclaimed. The repair walk already has the tool for this: The walk gives up silently. Neither a non-authenticating candidate nor an unparsable header inside a gap produces a log line, so nobody learns that a pack holds bytes compact will never reclaim. The default silently turns reclaim off. Checked and not a problem. No other caller lacks a validator: compact and repo-compress always have a manifest, and |
|
ping |
ac58e4c to
c9c296f
Compare
|
Addressed in c9c296f: |
|
…10093 debug delete-obj stops on a wrong passphrase; key_from_repository loads the key for check and the debug commands.
c9c296f to
8c6ea89
Compare
|
Thanks for the review, and sorry for the slow reply here. All three points are handled in 8c6ea89, which went in before the merge, so noting it here for the record. 1. Docs, the no-validator case
2. Wrong passphrase must not delete anything You were right, this was a real problem. Two parts to it:
The validator is also built once before the delete loop in Tests in
3. Key type identification existing in three places Agreed, better to reduce it first and then extend. There is a single helper now, All four places go through it: The only direct Unit tests for the helper itself are in |
Fixes #10093.
superseded_gap_rangescomputed the byte ranges compact drops from unverified object headers. A wrongdata_sizein a header whose chunk id is indexed elsewhere extended the dropped range over the gap bytes after the object, which can hold the only copy of a chunk (for example from a backup that crashed before writing its index).Changes
validate(repoobj.object_validator) accepts its header and metadata slot. That verifies its chunk id,meta_sizeanddata_size, so the dropped range is exactly the object. The stored size of the indexed copy may differ (other compression, obfuscation padding) and is not compared.validateis a required argument ofdelete(),compact_pack(),transform_pack()andsuperseded_gap_ranges();Nonedrops no gap bytes.compact,repo-compressandcheck --repairpass the repository's object validator.crypto.key.key_from_repositoryloads the key from the first stored object that identifies the key type: the manifest, then up to 999 indexed objects, skipping damaged objects and unsupported key types.ArchiveChecker.make_key,debug dump-repo-objs,debug search-repo-objsanddebug delete-objuse it.checknow skips a damaged object and tries the next one;dump-repo-objsandsearch-repo-objsread the manifest first and raiseIntegrityErrorif no object identifies the key type.borg debug delete-objvalidates with the repository key, so it asks for the passphrase of an encrypted repository. A wrong or missing passphrase stops the command before anything is deleted. If no key is found, the key is invalid, or no stored object identifies the key type, it logs a warning and deletes without dropping gap bytes. The key is set up only if at least one given id is valid.docs/internals/packs.rst: new "Gap bytes" section.Left as TODO
A wrong
obj_sizein a gap header usually ends the walk over that gap, so the superseded duplicates after it are kept on every rewrite. Resyncing withPackReader._find_headercan resume at a copy of an object inside another object's unencrypted data (none-*andauthenticated-*modes), whose range can cover the gap objects after it.Full suite: 3039 passed, 1125 skipped. ruff and black clean.