Skip to content

check: unhandled ValueError on a non-hex object name in packs/ #10051

Description

@ThomasWaldmann

borg check aborts with an unhandled ValueError: Odd-length string and a full traceback if packs/ contains an object whose name is not a valid hex pack id. The stray object is never reported, and no pack after it is checked.

Found while reviewing #9925; it is not caused by that branch, it reproduces on current master (423bc3117).

Reproduce

borg repo-create -e aes256-ocb
borg create test /some/path
touch "$BORG_REPO"/packs/*/not-a-pack-name
borg check --repository-only
Starting full repository check
Starting from beginning.
Local Exception

Error:

ValueError: Odd-length string

...
  File "src/borg/helpers/parseformat.py", line 58, in hex_to_bin
    raise ValueError(str(e)) from None
ValueError: Odd-length string

Exit code 2, and the traceback-with-please-report-this banner.

Cause

Repository.check() decodes every name returned by the packs/ listing without validating it:

https://github.com/borgbackup/borg/blob/423bc3117/src/borg/repository.py#L1024

for info in pack_infos:
    ...
    pack_id = hex_to_bin(info.name)

Any name that is not an even-length hex string raises out of hex_to_bin and unwinds the whole command. Because the listing is walked in name order, packs sorting after the stray name are never verified.

index/ does not have this problem: verify("index", info.name) only compares store.hash(key) against the name, so a stray object there is cleanly reported as Store object index/xxx is corrupted: content does not match its name (sha256). and counted as an error. packs/ should behave the same way.

This is the only place in repository.py that decodes a name coming from a store listing — compact works from the index, not from a listing — so the fix is local.

How likely is this in practice?

Low, but not impossible. borgstore rejects most junk at store time (uppercase, blanks, .., \, over-long names — so e.g. .DS_Store cannot be created through the API), and its list() deliberately skips TMP_SUFFIX entries, so an interrupted upload leaves nothing that reaches this code. What remains is anything that got into packs/ outside borgstore: a lowercase stray file from an operator, a restore/rsync that brought along an extra file, a filesystem or sync tool leaving a lowercase artifact.

The reason it seems worth fixing anyway is that borg check is the command you reach for when you already suspect something is wrong with a repository, and this failure mode gives a traceback rather than the name of the offending object.

Suggested fix

Validate the listing name before decoding, and report a bad one as an error instead of raising — something like:

if not is_valid_pack_name(info.name):  # 64 lowercase hex chars
    logger.error(f"Store object packs/{info.name} is not a valid pack name; ignoring it.")
    pack_errors += 1
    continue

That keeps the scan going, names the offending object, and makes the check fail (exit 2 as a warning) for a reason the user can act on. Whether it should count as an error or a warning is a judgement call — reporting it and continuing is the important part.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions