maxsize accepts negative values and corrupts balanced preferred content for the repo
- 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