Conversation
|
There is an issue in commit 957966d:
|
As Git for Windows' Coverity run after merging v2.56.0-rc0 reported, `writev_in_full()` keeps its cumulative successful output in an `ssize_t`. Although `xwritev()` limits each individual write to a syscall-sized amount, repeated successful writes can still exceed `SSIZE_MAX`. The unchecked accumulation was introduced by d70eb7f (wrapper: introduce writev(3p) wrappers, 2026-08-07). Treat an aggregate that would overflow the signed total as an I/O failure. Assisted-by: GPT-5.6 Luna Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
After merging v2.56.0-rc0 into Git for Windows, its Coverity run reported the following issue: The `parse_signed_buffer()` function accepts object buffers with an explicit size, while `get_format_by_sig()` uses `starts_with()`, i.e. it expects a NUL-terminated buffer. A tag object with a non-NUL-terminated payload ending in a partial signature prefix, such as a final '-' byte, could therefore cause an invalid read past the object buffer. The observable consequences are limited to reading past the allocation. In practice it can crash Git if the read enters an unmapped page. It can also misplace the payload/signature split, corrupting the compat-hash object being written. The older unbounded matcher predates this path, but c8762c3 (object-file-convert: convert tag objects when writing, 2023-10-01) exposed the defect by passing exact-sized converted tag buffers to `parse_signed_buffer()`. That commit first shipped in v2.45.0, so the defect has been latent in every release since. This pattern was noticed on the mailing list in February 2024. Reviewing a patch for a very similar issue in commit.c's find_header_mem(), Jeff King observed in https://lore.kernel.org/git/20240208214137.GB1090198@coredump.intra.peff.net/: But more interestingly: even though we pass a buf/len pair to parse_signed_buffer(), it then calls get_format_by_sig() which takes only a NUL-terminated string. [...] That raises the question of whether parse_signed_buffer() has a similar walk-too-far problem. ;) The answer is no, because we feed it from a strbuf. But it's not a great pattern overall. That reasoning surveyed the callers that existed at the time and missed c8762c3 (object-file-convert: convert tag objects when writing, 2023-10-01), which was four months old at that time, and does not feed from a strbuf; `convert_tag_object()` hands `parse_signed_buffer()` an exact-sized `xmalloc()` buffer, and the concern flagged and dismissed in that thread is exactly the defect Coverity now reports. Jeff went on to add `starts_with_mem()` a month later, in https://lore.kernel.org/git/20240307092638.GK2080210@coredump.intra.peff.net/, precisely for "cases where the buffer is not NUL-terminated (and we instead have an explicit size or end pointer)", so the tool for this fix has been in the tree since v2.45.0. Even though the issue had been latent, it most likely surfaced via Coverity because of 215d305 (odb: compute compat object ID in `odb_write_object_ext()`, 2026-07-17), which moved `convert_object_file()` out of the `source->write_object` function pointer into a direct call in `odb_write_object_ext()`. Preserve the existing NUL-terminated behavior for callers that provide strings while making signature-prefix matching honor the known buffer lengths, via the `starts_with_mem()` helper. This keeps reads within the object data without implying exploitability beyond the observed invalid read. Assisted-by: GPT-5.6 Luna Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Incremental MIDX support made object-offset pack IDs local to each layer and then converted them to chain-global IDs by adding `num_packs_in_base`. The conversion was introduced by 1941982 (midx: teach `nth_midxed_pack_int_id()` about incremental MIDXs, 2024-08-06). Chain-aware pack preparation followed in 1820bd8 (midx: teach `prepare_midx_pack()` about incremental MIDXs, 2024-08-06), but the final `midx_fill_entry()` lookup remained tied to the original layer. Only with 8f909ff (packfile: recover when a multi-pack-index names a removed pack, 2026-08-29) did Coverity point out this issue: a local ID such as `UINT32_MAX` could wrap when the base-pack count was added, producing a plausible but incorrect global ID. After `prepare_midx_pack()` resolved the chain, `midx_fill_entry()` could then underflow or address the wrong layer while indexing the current layer's pack array, causing an invalid memory access and crashing Git. Validate each local pack ID against its layer's pack count before adding the base count, and obtain the final pack through `nth_midxed_pack()`, which resolves the correct MIDX layer. This prevents an invalid local ID from wrapping during conversion and ensures that the lookup uses the layer identified by the resolved chain-global ID. Assisted-by: GPT-5.6 Luna Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
`rerere` can mark a conflict variant as resolved even when writing its preimage or postimage fails. A later invocation may then replay incomplete data from the cache, turning a local filesystem failure into an incorrect working-tree change. 629716d (rerere: do use multiple variants, 2015-07-30) introduced the code paths without checks for those I/O results. Treat such failures as failures, report them, and leave the rerere status unchanged unless the corresponding data was recorded successfully. The defect has been latent since 2015. Git for Windows' Coverity run only reported it after merging v2.56.0-rc0, for reasons that could not be figured out in a reasonable amount of time. Assisted-by: GPT-5.6 Luna Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Coverity pointed out that the `test_reftable_table__seek_invalid_log_offset()` test, which was introduced by a1c085d (reftable/table: fix NULL pointer access when seeking to bogus offsets, 2026-07-03), ignores the result of `reftable_table_init_log_iterator()` and proceeds to `reftable_iterator_seek_log()`, although initialization can return `REFTABLE_OUT_OF_MEMORY_ERROR` without installing an ops table. Under allocation failure, the test then dereferences a NULL function table. Assert successful iterator initialization before seeking. Assisted-by: GPT-5.6 Luna Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The reftable fuzzer introduced by adf4516 (oss-fuzz: add fuzzer for parsing reftables, 2026-07-03) ignored failures from `reftable_table_init_ref_iterator()` and `reftable_table_init_log_iterator()`. Coverity reported that under allocation failure, either constructor can return `REFTABLE_OUT_OF_MEMORY_ERROR` without installing an ops table, allowing a subsequent seek to dereference NULL. Treat iterator initialization failure as a reason to skip the corresponding seek and iteration while retaining safe destruction for an uninitialized iterator. Assisted-by: GPT-5.6 Luna Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
The `--show-objects` mode of `read_midx_file()` uses the output of `midx_fill_entry()` without checking whether the lookup succeeded. A failed lookup or unavailable pack can leave that output unusable, allowing malformed or concurrently changed MIDX data to make this test helper crash instead of reporting a controlled error. Reject the entry unless `midx_fill_entry()` returns `MIDX_FILL_HIT`. The unchecked call was introduced by 86d174b (t/helper/test-read-midx.c: add '--show-objects', 2021-03-30); later incremental-MIDX changes expanded the possible failure modes, but this remains a test-helper robustness issue, not a production Git attack surface or an arbitrary-code-execution vulnerability. It is unclear why Coverity reports this issue in Git for Windows only after merging v2.56.0-rc0; The issue was not reported before. Assisted-by: GPT-5.6 Luna Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
dscho
force-pushed
the
fix-coverity-high-severity
branch
from
September 17, 2026 17:48
957966d to
60599d2
Compare
Member
Author
|
/submit |
|
Submitted as pull.2231.git.1789667556.gitgitgadget@gmail.com To fetch this version into To fetch this version to local tag |
| @@ -133,20 +133,20 @@ static struct gpg_format *get_format_by_name(const char *str) | |||
| return NULL; | |||
There was a problem hiding this comment.
Junio C Hamano wrote on the Git mailing list (how to reply to this email):
"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> @@ -669,7 +669,7 @@ int check_signature(struct signature_check *sigc,
> sigc->result = 'N';
> sigc->trust_level = TRUST_UNDEFINED;
>
> - fmt = get_format_by_sig(signature);
> + fmt = get_format_by_sig(signature, slen);
> if (!fmt)
> die(_("bad/incompatible signature '%s'"), signature);
All the existing callers of check_signature() pass a NUL-terminated
buffer which is <buf, len> pair of a strbuf. Another approach that
may be simpler is to drop the slen parameter from check_signature().
There was a problem hiding this comment.
Johannes Schindelin wrote on the Git mailing list (how to reply to this email):
Hi Junio,
On Thu, 17 Sep 2026, Junio C Hamano wrote:
> "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
>
> > @@ -669,7 +669,7 @@ int check_signature(struct signature_check *sigc,
> > sigc->result = 'N';
> > sigc->trust_level = TRUST_UNDEFINED;
> >
> > - fmt = get_format_by_sig(signature);
> > + fmt = get_format_by_sig(signature, slen);
This hunk is a direct consequence of `get_format_by_sig()` gaining a
length parameter earlier in the same patch: it now has three callers,
`get_signature_format()`, `check_signature()` here, and the loop inside
`parse_signed_buffer()` (the actual site of the bug this series fixes,
Coverity issue with CID 1678690 if you want to double-check).
Once the function takes a (sig, len) pair uniformly, every one of them has
to pass a length, so this call site is not optional scaffolding; it is
what makes all three callers correct by construction instead of leaving
two of them trusting NUL-termination and one bounds-checked.
> > if (!fmt)
> > die(_("bad/incompatible signature '%s'"), signature);
>
> All the existing callers of check_signature() pass a NUL-terminated
> buffer which is <buf, len> pair of a strbuf.
That holds for six of the seven call sites: `commit.c`, `tag.c`,
`builtin/fast-import.c`, `fmt-merge-msg.c`, and `log-tree.c` (twice).
`builtin/receive-pack.c` is a minor wrinkle worth flagging: it passes
`push_cert.buf + bogs` ("bogs" = "beginning_of_gpg_sig") and
`push_cert.len - bogs`, an offset sub-buffer of `push_cert`, not that
strbuf's own buf/len pair verbatim. It still ends on `push_cert`'s own
terminating NUL, so the observation holds in spirit, but strictly the
pattern is "ends at some strbuf's own NUL", which is more a matter of
code-review convention across call sites than something
`check_signature()`'s own signature guarantees.
> Another approach that may be simpler is to drop the slen parameter from
> check_signature().
I would rather keep it right where it is, for (at least 😊) two reasons.
First, `slen` isn't new here: `check_signature()` has taken a `(sigc,
signature, slen)` signature since 02769437e142 (ssh signing: use sigc
struct to pass payload, 2021-12-09), three years before this series, so
dropping it now would fold an unrelated API change into a bug fix.
Second, and this is the one that actually worries me: `slen` is used twice
inside `check_signature()`, not once. Besides the `get_format_by_sig()`
call above, the pre-existing `fmt->verify_signed_buffer(sigc, fmt,
signature, slen)` a few lines down depends on it too (there it is named
`signature_size`). Both concrete implementations of that vtable member,
`verify_gpg_signed_buffer()` and `verify_ssh_signed_buffer()`, use
`signature_size` to decide exactly how many bytes to `write_in_full()`
into the temporary file that then gets handed to `gpg`/`ssh-keygen` as the
detached signature to verify. That is the authoritative byte count of the
blob being verified, not a defensive nicety. If we dropped `slen` and let
`check_signature()` fall back on `strlen(signature)`, a signature blob
with an embedded NUL before its logical end would get truncated before it
ever reaches the external verifier: a correctness regression in the actual
cryptographic verification path, not merely in the prefix-matching helper
this series fixes. `check_signature()` has no doc comment promising
`signature` is free of embedded NULs, so dropping `slen` would trade an
explicit length for an implicit assumption.
As the commit message notes, we have been down this road with this exact
function chain before. In February 2024, Peff concluded there was no
walk-too-far problem in `parse_signed_buffer()` "because we feed it from a
strbuf":
https://lore.kernel.org/git/20240208214137.GB1090198@coredump.intra.peff.net/
But that conclusion was already four months stale: c8762c30df5b
(object-file-convert: convert tag objects when writing, 2023-10-01) had
already added `convert_tag_object()` as a caller that does _not_ feed from
a strbuf: the same gap this series closes. Applying the same "audit
today's callers and assume it holds" reasoning to `check_signature()` now
risks reproducing that failure mode a second time.
So I would like to keep `slen` and the `get_format_by_sig(signature,
slen)` call as in the patch.
Ciao,
JohannesThere was a problem hiding this comment.
Junio C Hamano wrote on the Git mailing list (how to reply to this email):
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> That holds for six of the seven call sites: `commit.c`, `tag.c`,
> `builtin/fast-import.c`, `fmt-merge-msg.c`, and `log-tree.c` (twice).
> `builtin/receive-pack.c` is a minor wrinkle worth flagging: it passes
> `push_cert.buf + bogs` ("bogs" = "beginning_of_gpg_sig") and
> `push_cert.len - bogs`, an offset sub-buffer of `push_cert`, not that
> strbuf's own buf/len pair verbatim. It still ends on `push_cert`'s own
> terminating NUL, so the observation holds in spirit, but strictly the
> pattern is "ends at some strbuf's own NUL", which is more a matter of
> code-review convention across call sites than something
> `check_signature()`'s own signature guarantees.
Yes but the audit was "is slen our callers pass redundant?", and not
"does everybody pass strbuf and we are better off passing a pionter
to a strbuf?". And the answer to the former question is "yes".
And I do not quite understand or agree with the logic here.
> ... Applying the same "audit
> today's callers and assume it holds" reasoning to `check_signature()` now
> risks reproducing that failure mode a second time.
What I was saying was to force all current *and* *future* callers to
pass NUL-terminated string by removing slen.
Having said all that, I think this falls into "once the code is
written (and more importantly, once it is reviewed, as that is a lot
more costly part of the development process for machine written
code), it is not worth going back and change it, as the difference
is not large enough either way."| @@ -476,8 +476,11 @@ static int handle_file(struct index_state *istate, | |||
| unlink_or_warn(output); | |||
There was a problem hiding this comment.
Junio C Hamano wrote on the Git mailing list (how to reply to this email):
"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:
> @@ -778,7 +798,9 @@ static void do_rerere_one_path(struct index_state *istate,
> assign_variant(id);
>
> variant = id->variant;
> - handle_file(istate, path, NULL, rerere_path(&buf, id, "preimage"));
> + if (handle_file(istate, path, NULL,
> + rerere_path(&buf, id, "preimage")) < 0)
> + goto out;
> if (id->collection->status[variant] & RR_HAS_POSTIMAGE) {
> const char *path = rerere_path(&buf, id, "postimage");
> if (unlink(path))
Good to see this one, which is the only unchecked call to the
handle_file() function, checked for an error. Looking good.
Thanks.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
These Coverity reports are new as of this -rc cycle; Apart from the
writevone, I don't think any of these are pressing, in most cases I am still puzzled why they were reported only now.