Skip to content

plumbing: format/packfile, Tighten delta validation#2089

Merged
pjbgf merged 1 commit into
go-git:mainfrom
hiddeco:tighten-delta-validation
May 8, 2026
Merged

plumbing: format/packfile, Tighten delta validation#2089
pjbgf merged 1 commit into
go-git:mainfrom
hiddeco:tighten-delta-validation

Conversation

@hiddeco

@hiddeco hiddeco commented May 7, 2026

Copy link
Copy Markdown
Member

Each delta operation's declared size is validated against the size still remaining in the target buffer, not the total target size. The previous check against the total accepted a sequence of operations whose cumulative output would exceed the declared target, leaving the unsigned remaining counter unable to reach zero and the loop free to keep writing past the declared target until the delta payload was consumed.

Three sibling code paths shared the flaw and are updated together: patchDelta (used by PatchDelta and ApplyDelta), ReaderFromDelta, and patchDeltaWriter (used by the packfile parser).

The loop structure is rewritten as for remainingTargetSz > 0 so the invariant is explicit, and a post-loop check rejects deltas that leave bytes unconsumed -- matching the data != top sanity check in patch-delta.c1. Empty-target deltas (header-only, no operations) are now accepted, also matching upstream.

Several incidental defects on the same call paths are fixed: patchDelta's copy-from-src failure used break, which only exited the switch and let the loop keep consuming delta bytes; patchDeltaWriter returned (0, ZeroHash, nil) on invalid input, silently reporting success; and ReaderFromDelta's copy-from-src failure closed the writer without an error, silently truncating the consumer stream. Each now propagates ErrInvalidDelta (or ErrDeltaCmd) consistently.

packfile.getMemoryObject was re-inflating delta payloads into the buffer the scanner had already populated, appending a duplicate copy that previously went unnoticed because the loop silently ignored anything past the target. The fix matches the cached-content pattern already used in Scanner.WriteObject.

@hiddeco hiddeco force-pushed the tighten-delta-validation branch 4 times, most recently from f4c3439 to 9e3c92b Compare May 8, 2026 07:14
Each delta operation's declared size is validated against the
size still remaining in the target buffer, not the total target
size. The previous check against the total accepted a sequence of
operations whose cumulative output would exceed the declared
target, leaving the unsigned remaining counter unable to reach
zero and the loop free to keep writing past the declared target
until the delta payload was consumed.

Three sibling code paths shared the flaw and are updated together:
`patchDelta` (used by `PatchDelta` and `ApplyDelta`),
`ReaderFromDelta`, and `patchDeltaWriter` (used by the packfile
parser).

The loop structure is rewritten as `for remainingTargetSz > 0`
so the invariant is explicit, and a post-loop check rejects
deltas that leave bytes unconsumed -- matching the `data != top`
sanity check in `patch-delta.c`[1]. Empty-target deltas
(header-only, no operations) are now accepted, also matching
upstream.

Several incidental defects on the same call paths are fixed:
`patchDelta`'s copy-from-src failure used `break`, which only
exited the switch and let the loop keep consuming delta bytes;
`patchDeltaWriter` returned `(0, ZeroHash, nil)` on invalid input,
silently reporting success; and `ReaderFromDelta`'s copy-from-src
failure closed the writer without an error, silently truncating
the consumer stream. Each now propagates `ErrInvalidDelta` (or
`ErrDeltaCmd`) consistently.

`packfile.getMemoryObject` was re-inflating delta payloads into
the buffer the scanner had already populated, appending a
duplicate copy that previously went unnoticed because the loop
silently ignored anything past the target. The fix matches the
cached-content pattern already used in `Scanner.WriteObject`.

[1]: https://github.com/git/git/blob/c2c5f6b1e479f2c38e0e01345350620944e3527f/patch-delta.c

Assisted-by: Claude Opus 4.7
Signed-off-by: Hidde Beydals <[email protected]>
@hiddeco hiddeco force-pushed the tighten-delta-validation branch from 9e3c92b to f5526de Compare May 8, 2026 08:20

@pjbgf pjbgf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hiddeco thanks for working on this. 🙇

@pjbgf pjbgf merged commit e5e199c into go-git:main May 8, 2026
17 checks passed
hiddeco added a commit to hiddeco/go-git that referenced this pull request May 8, 2026
Each delta operation's declared size is validated against the
size still remaining in the target buffer, not the total target
size. The previous check against the total accepted a sequence of
operations whose cumulative output would exceed the declared
target, leaving the unsigned remaining counter unable to reach
zero and the loop free to keep writing past the declared target
until the delta payload was consumed.

Three sibling code paths shared the flaw and are updated together:
`patchDelta` (used by `PatchDelta` and `ApplyDelta`),
`ReaderFromDelta`, and `patchDeltaWriter` (used by the packfile
parser).

The loop structure is rewritten as `for remainingTargetSz > 0`
so the invariant is explicit, and a post-loop check rejects
deltas that leave bytes unconsumed -- matching the `data != top`
sanity check in `patch-delta.c`[1]. Empty-target deltas
(header-only, no operations) are now accepted, also matching
upstream.

Several incidental defects on the same call paths are fixed:
`patchDelta`'s copy-from-src failure used `break`, which only
exited the switch and let the loop keep consuming delta bytes;
`patchDeltaWriter` returned `(0, ZeroHash, nil)` on invalid input,
silently reporting success; and `ReaderFromDelta`'s copy-from-src
failure closed the writer without an error, silently truncating
the consumer stream. Each now propagates `ErrInvalidDelta` (or
`ErrDeltaCmd`) consistently.

[1]: https://github.com/git/git/blob/c2c5f6b1e479f2c38e0e01345350620944e3527f/patch-delta.c

Backport of go-git#2089.

Assisted-by: Claude Opus 4.7
Signed-off-by: Hidde Beydals <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants