con / con/git-annex

maxsize accepts negative values and corrupts balanced preferred content for the repo

Open Beginner friendly
#279 0 comments 0 reactions 0 assignees View on GitHub
ai-uncurated bug
Dominant language
Python
Stars
29
Forks
5
PR merge metrics
No merged PRs in 30d

Description

`git annex maxsize ` accepts a negative size value (e.g.
`-100M`, or `-100000000` bytes via `git annex maxsize . -- -100M`). The
negative value is committed to the git-annex branch verbatim and is then
displayed by `git annex maxsize` as a negative byte count and `>100%`
full for an empty repo. (`MaxSize 0` is already the sentinel for "no
max" at `Limit.hs:646`, so a negative value cannot reasonably be a
parallel "drop everything" signal — it's just unguarded arithmetic.)

Downstream impact (confirmed by code inspection at `Limit.hs`, not
exercised by the reproducer below): all `balanced`/`sizebalanced`-family
preferred-content paths refuse to send content to a repo with a negative
maxsize. `limitFullyBalanced.toofull` at `Limit.hs:644` compares
unsigned, `repoHasSpace` at `Limit.hs:668` returns false, and
`proportionfree` at `Limit.hs:766-772` explicitly guards `maxsize > 0`
and returns `Nothing`.

```
> git annex init A
> git annex maxsize . -- -100M
maxsize . ok
> git annex maxsize --bytes
repository maxsize %full
A -100000000 >100%
```

Root cause

`Command/MaxSize.hs:63` parses the user-supplied size via
`readSize dataUnits sz` without any signedness check:

```haskell
case readSize dataUnits sz of
Nothing -> giveup "Unable to parse size."
Just n -> do
recordMaxSize u (MaxSize n)
next $ return True
```

`readSize` happily accepts a leading `-` and returns a negative `ByteSize`.

`Logs/MaxSize.hs:45` then serialises whatever integer it is given,
including negatives, into the `Logs.MaxSize` log on the `git-annex` branch:

```haskell
buildMaxSize :: MaxSize -> Builder
buildMaxSize (MaxSize n) = byteString (encodeBS (show n))
```

The reciprocal parser at `Logs/MaxSize.hs:47-49` uses `readish` and likewise
accepts negative integers, so every clone reads back the negative value
verbatim.

Suggested fix: guard `Command/MaxSize.hs:63` with `n < 0 -> giveup "Maxsize cannot be negative."` (or coerce to a `Word64`).

Reproducer (POSIX shell, exits non-zero when bug fires)

```sh
#!/bin/sh
set -eux
PS4='> '

cd "$(mktemp -d "${TMPDIR:-/tmp}/gax-maxsize-negative-XXXXXXX")"

git --version
git annex version | head -1

git init A
cd A
git annex init A

git annex maxsize . -- -100M

LIST_OUT=$(git annex maxsize --bytes)
printf '%s\n' "$LIST_OUT"

INFO_OUT=$(git annex info . --bytes)
printf '%s\n' "$INFO_OUT"

if printf '%s' "$LIST_OUT" | grep -q -- '-100000000'; then
echo "BUG REPRODUCED: negative maxsize -100M accepted and stored as -100000000 bytes"
exit 1
fi

if printf '%s' "$LIST_OUT" | grep -q '>100%'; then
echo "BUG REPRODUCED: empty repo reports >100% full due to negative maxsize"
exit 1
fi

echo "BUG NOT REPRODUCED: negative maxsize was rejected"
exit 0
```

Notes:
- The `>100%` check is corroborating but renders from the same code
path as the negative byte count (`Command/MaxSize.hs:116-121`) — not
an independent signal.
- Verified to fire (exit 1) on git-annex
`10.20260602-g67323e13d3edb9e0515eea8cd5573360b6fa2db2` (master HEAD).
- The downstream behaviour ("preferred content refuses the repo") is
established by reading `Limit.hs` rather than exercised at runtime in
this reproducer; a second-stage reproducer that adds a second repo
with a `sizebalanced=` wanted expression would empirically confirm it.
- The `--` is needed because optparse-applicative would otherwise treat
`-100M` as an unknown option. A realistic CLI shape that hits this
*without* needing `--` is `git annex maxsize "$repo" "$size"` from a
script where `$size` happens to be a negative computation result.

Regression evidence

`maxsize` is a new command, introduced together with the live-RepoSize
infrastructure in 10.20240831. The implementing commit is
[10.20240808-52-g1265d7e5df AKA 10.20240831~90 — `1265d7e5dff989307c868cd541f3d635bc424414` "implement maxsize log and command"](https://github.com/con/git-annex/commit/1265d7e5dff989307c868cd541f3d635bc424414).
Since the feature shipped with this gap, this is an **inception bug**,
not a regression — do NOT label as regression.

The 10.20240831 CHANGELOG entry "`maxsize`: New command to tell git-annex
how large the expected maximum size of a repository is" describes only the
intended behaviour; the negative-value acceptance is unmentioned and
clearly unintended.

Version

```
Verified with: git-annex 10.20260602-g67323e13d3edb9e0515eea8cd5573360b6fa2db2
(autobuild from master HEAD; installed via datalad-installer --method autobuild).
Code path unchanged since 10.20240831: Command/MaxSize.hs:63 and
Logs/MaxSize.hs:45 are identical to the 10.20240831 introduction.
```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start at Command/MaxSize.hs:63 to trace how the parsed size reaches recordMaxSize, then read Logs/MaxSize.hs:45-49 to understand the stored value format. Run the provided POSIX reproducer and verify that a negative size is rejected before it is committed to the git-annex branch.

Written by the indexing model from the issue text.

Assessment

Tech stack
git, haskell
Domain
cli
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.