All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: Johannes Schindelin <johannes.schindelin@gmx.de>
Subject: [PATCH v2 00/12] coverity: fix leaks and error paths
Date: Sun, 05 Jul 2026 08:24:17 +0000	[thread overview]
Message-ID: <pull.2163.v2.git.1783239870.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2163.git.1782889472.gitgitgadget@gmail.com>

I wanted to whittle down the many issues reported by Coverity in the Git for
Windows project. Turns out: The vast majority of the issues are false
positives. Most of the remaining issues are in core Git proper.

This effort was forced on pause while Coverity was down from May 16
[https://web.archive.org/web/20260516152422/https://scan.coverity.com/] to
June 22
[https://web.archive.org/web/20260622182153/https://scan.coverity.com/]).

Here is a first batch of fixes for those issues.

Changes since v1:

 * Edited the commit messages to put function names in backticks, and
   reflowed the messages afterwards.
 * Took Junio's suggestion to avoid (ab-)using errno to determine the return
   value of load_one_loose_object_map().
 * Dropped the obsolete patch "run_diff_files: avoid memory leak".
 * Rewrote the commit message of "dir: free allocations on parse-error paths
   in read_one_dir()" to clarify ownership of the allocated untracked/dirs
   buffers.
 * Changed "submodule: fix cwd leak in get_superproject_working_tree()" to
   reduce the cognitive load on the reader (i.e. to make it a lot easier to
   reason about the correctness of the patch).

Johannes Schindelin (12):
  load_one_loose_object_map(): fix resource leak
  loose: avoid closing invalid fd on error path
  download_https_uri_to_file(): do not leak fd upon failure
  run-command: avoid `close(-1)` in `start_command()` error paths
  line-log: avoid redundant copy that leaks in process_ranges
  dir: free allocations on parse-error paths in `read_one_dir()`
  submodule: fix cwd leak in `get_superproject_working_tree()`
  worktree: fix resource leaks when branch creation fails
  imap-send: avoid leaking the IMAP upload buffer
  reftable/table: release filter on error path
  fsmonitor: plug token-data leak on early daemon-startup failures
  mingw: make `exit_process()` own the process handle on all paths

 builtin/fsmonitor--daemon.c |  2 ++
 builtin/worktree.c          |  7 +++++--
 bundle-uri.c                |  2 +-
 compat/mingw.c              |  4 +---
 compat/win32/exit-process.h |  1 +
 dir.c                       |  9 +++++++--
 imap-send.c                 |  1 +
 line-log.c                  |  3 +--
 loose.c                     | 12 ++++++------
 reftable/table.c            |  4 ++++
 run-command.c               |  6 +++---
 submodule.c                 | 19 ++++++++++---------
 12 files changed, 42 insertions(+), 28 deletions(-)


base-commit: e9019fcafe0040228b8631c30f97ae1adb61bcdc
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2163%2Fdscho%2Fcoverity-fixes-leaks-and-error-paths-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2163/dscho/coverity-fixes-leaks-and-error-paths-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/2163

Range-diff vs v1:

  1:  17242c249f !  1:  80ae35227d load_one_loose_object_map(): fix resource leak
     @@ loose.c: static int load_one_loose_object_map(struct repository *repo, struct od
       
       	if (!loose->map)
       		loose_object_map_init(&loose->map);
     +@@ loose.c: static int load_one_loose_object_map(struct repository *repo, struct odb_source_
     + 		return 0;
     + 	}
     + 
     +-	errno = 0;
     + 	if (strbuf_getwholeline(&buf, fp, '\n') || strcmp(buf.buf, loose_object_header))
     + 		goto err;
     + 	while (!strbuf_getline_lf(&buf, fp)) {
      @@ loose.c: static int load_one_loose_object_map(struct repository *repo, struct odb_source_
       		insert_loose_map(loose, &oid, &compat_oid);
       	}
     @@ loose.c: static int load_one_loose_object_map(struct repository *repo, struct od
      -	strbuf_release(&buf);
      -	strbuf_release(&path);
      -	return errno ? -1 : 0;
     -+	ret = 0;
     ++	ret = ferror(fp) ? -1 : 0;
       err:
      +	fclose(fp);
       	strbuf_release(&buf);
  2:  a1cd229e33 !  2:  546a7c5d9f loose: avoid closing invalid fd on error path
     @@ Metadata
       ## Commit message ##
          loose: avoid closing invalid fd on error path
      
     -    write_one_object() opens a file at line 186 and jumps to the
     -    errout label on failure. The errout cleanup unconditionally calls
     -    close(fd), but when open() itself failed, fd is -1. Calling
     -    close(-1) is harmless on most platforms (returns EBADF) but is
     -    undefined behavior per POSIX and can confuse fd tracking in
     -    sanitizer builds.
     +    `write_one_object()` opens a file at line 186 and jumps to the errout
     +    label on failure. The errout cleanup unconditionally calls `close(fd)`,
     +    but when `open()` itself failed, fd is -1. Calling `close(-1)` is
     +    harmless on most platforms (returns EBADF) but is undefined behavior per
     +    POSIX and can confuse fd tracking in sanitizer builds.
      
          Guard the close with fd >= 0.
      
  3:  a770d9708d =  3:  17c3b4ce4f download_https_uri_to_file(): do not leak fd upon failure
  4:  d7bcdda312 !  4:  0360016d91 run-command: avoid close(-1) in start_command() error paths
     @@ Metadata
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       ## Commit message ##
     -    run-command: avoid close(-1) in start_command() error paths
     +    run-command: avoid `close(-1)` in `start_command()` error paths
      
     -    When start_command() fails to set up a pipe partway through, it
     -    rolls back by closing the pipe ends it has already opened. For
     -    descriptors supplied by the caller rather than allocated locally,
     -    that rollback tested `if (cmd->in)` / `if (cmd->out)` before calling
     -    close(). The CHILD_PROCESS_INIT default of -1 ("no descriptor") is
     -    non-zero and so passes the test, meaning a caller that sets
     -    cmd->no_stdin or cmd->no_stdout without supplying a real fd ends up
     -    triggering close(-1) on the error path.
     +    When `start_command()` fails to set up a pipe partway through, it rolls
     +    back by closing the pipe ends it has already opened. For descriptors
     +    supplied by the caller rather than allocated locally, that rollback
     +    tested `if (cmd->in)` / `if (cmd->out)` before calling close(). The
     +    CHILD_PROCESS_INIT default of -1 ("no descriptor") is non-zero and so
     +    passes the test, meaning a caller that sets cmd->no_stdin or
     +    cmd->no_stdout without supplying a real fd ends up triggering close(-1)
     +    on the error path.
      
     -    The stdin-pipe failure branch a few lines above already uses the
     -    right idiom, `if (cmd->out > 0)`, which rejects both the -1 sentinel
     -    and 0 (the parent's own standard streams). Apply it to the three
     -    remaining rollback sites.
     +    The stdin-pipe failure branch a few lines above already uses the right
     +    idiom, `if (cmd->out > 0)`, which rejects both the -1 sentinel and 0
     +    (the parent's own standard streams). Apply it to the three remaining
     +    rollback sites.
      
          Reported by Coverity as CID 1049722 ("Argument cannot be negative").
      
  5:  860bc8f52d <  -:  ---------- run_diff_files: avoid memory leak
  6:  5a6b17f075 !  5:  8c623cc28f line-log: avoid redundant copy that leaks in process_ranges
     @@ Metadata
       ## Commit message ##
          line-log: avoid redundant copy that leaks in process_ranges
      
     -    When bloom_filter_check() indicates that a commit does not touch
     -    any of the tracked paths, line_log_process_ranges_arbitrary_commit()
     +    When `bloom_filter_check()` indicates that a commit does not touch any
     +    of the tracked paths, `line_log_process_ranges_arbitrary_commit()`
          propagates the current ranges to the parent by calling
     -    line_log_data_copy() and passing the copy to add_line_range().
     -    However, add_line_range() always makes its own copy internally
     -    (via line_log_data_copy or line_log_data_merge), so the caller's
     -    copy is never freed and leaks every time this path is taken.
     +    `line_log_data_copy()` and passing the copy to add_line_range().
     +    However, `add_line_range()` always makes its own copy internally (via
     +    line_log_data_copy or line_log_data_merge), so the caller's copy is
     +    never freed and leaks every time this path is taken.
      
     -    Pass range directly to add_line_range() instead of making a
     -    redundant intermediate copy. The callee's internal copy handles
     -    ownership correctly.
     +    Pass range directly to `add_line_range()` instead of making a redundant
     +    intermediate copy. The callee's internal copy handles ownership
     +    correctly.
      
          Pointed out by Coverity.
      
  7:  62ce03454a !  6:  8a8fe2d3e3 dir: free allocations on parse-error paths in read_one_dir()
     @@ Metadata
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       ## Commit message ##
     -    dir: free allocations on parse-error paths in read_one_dir()
     +    dir: free allocations on parse-error paths in `read_one_dir()`
      
     -    When read_one_dir() encounters a parse error while reading the
     -    untracked cache from disk, it returns -1 immediately. Two
     -    allocations made earlier in the function can leak on these
     -    early-return paths: ud.untracked (allocated at line 3846 when
     -    untracked_nr > 0) and ud.dirs (allocated at line 3851).
     +    Two of `read_one_dir()`'s parse-error early returns leak ud.untracked
     +    and ud.dirs. Plug them.
      
     -    Free both before returning on the two error paths between these
     -    allocations and the point where they are transferred into the
     -    final xmalloc'd struct at line 3857.
     +    The other early returns in the same function are fine: they occur after
     +    the `xmalloc()`+`memcpy()` that copies ud into `*untracked_`, at which
     +    point ownership is transferred to the caller.
     +    `read_untracked_extension()` then releases everything via
     +    `free_untracked_cache()` on failure.
      
          Pointed out by Coverity.
      
  8:  6a43f95241 !  7:  5397ea785c submodule: fix cwd leak in get_superproject_working_tree()
     @@ Metadata
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       ## Commit message ##
     -    submodule: fix cwd leak in get_superproject_working_tree()
     +    submodule: fix cwd leak in `get_superproject_working_tree()`
      
     -    get_superproject_working_tree() allocates cwd via xgetcwd() at
     -    the top of the function, but two early-return paths (when not
     -    inside a work tree, and when strbuf_realpath for "../" fails)
     -    return 0 without freeing it.
     +    `get_superproject_working_tree()` allocates cwd via `xgetcwd()` at the
     +    top of the function, but two early-return paths (when not inside a work
     +    tree, and when strbuf_realpath for "../" fails) return 0 without freeing
     +    it.
      
     -    Redirect these early returns through a cleanup label that frees
     -    cwd before returning.
     +    Redirect these early returns through a cleanup label that frees cwd
     +    before returning.
      
          Pointed out by Coverity.
      
     @@ submodule.c: int get_superproject_working_tree(struct strbuf *buf)
      +		goto out;
       
       	subpath = relative_path(cwd, one_up.buf, &sb);
     - 	strbuf_release(&one_up);
     +-	strbuf_release(&one_up);
     + 
     + 	prepare_submodule_repo_env(&cp.env);
     + 	strvec_pop(&cp.env);
      @@ submodule.c: int get_superproject_working_tree(struct strbuf *buf)
     + 		ret = 1;
     + 		free(super_wt);
     + 	}
     +-	free(cwd);
     +-	strbuf_release(&sb);
     + 
     + 	code = finish_command(&cp);
     + 
     + 	if (code == 128)
     + 		/* '../' is not a git repository */
     +-		return 0;
     +-	if (code == 0 && len == 0)
     ++		ret = 0;
     ++	else if (code == 0 && len == 0)
     + 		/* There is an unrelated git repository at '../' */
     +-		return 0;
     +-	if (code)
     ++		ret = 0;
     ++	else if (code)
       		die(_("ls-tree returned unexpected return code %d"), code);
       
     - 	return ret;
     -+
      +out:
     ++	strbuf_release(&sb);
     ++	strbuf_release(&one_up);
      +	free(cwd);
     -+	return 0;
     + 	return ret;
       }
       
     - /*
  9:  e39e2f5aa4 !  8:  0048c0ca27 worktree: fix resource leaks when branch creation fails
     @@ Metadata
       ## Commit message ##
          worktree: fix resource leaks when branch creation fails
      
     -    In the "add" subcommand, when run_command() fails while creating
     -    a new branch (line 948), the function returns -1 immediately
     -    without freeing the allocations made earlier: path (from
     -    prefix_filename at line 858), opt_track, branch_to_free, and
     -    new_branch_to_free.
     +    In the "add" subcommand, when `run_command()` fails while creating a new
     +    branch (line 948), the function returns -1 immediately without freeing
     +    the allocations made earlier: path (from prefix_filename at line 858),
     +    opt_track, branch_to_free, and new_branch_to_free.
      
     -    Redirect the error return through the existing cleanup block at
     -    the end of the function so all four allocations are properly
     -    freed.
     +    Redirect the error return through the existing cleanup block at the end
     +    of the function so all four allocations are properly freed.
      
          Pointed out by Coverity.
      
 10:  cc19a300f5 !  9:  4048a225a5 imap-send: avoid leaking the IMAP upload buffer
     @@ Metadata
       ## Commit message ##
          imap-send: avoid leaking the IMAP upload buffer
      
     -    When uploading messages via libcurl, curl_append_msgs_to_imap()
     -    accumulates each one in a strbuf that grows across loop iterations
     -    but is never released before the function returns.
     +    When uploading messages via libcurl, `curl_append_msgs_to_imap()`
     +    accumulates each one in a strbuf that grows across loop iterations but
     +    is never released before the function returns.
      
          Release it alongside the existing libcurl cleanup.
      
 11:  198062addd ! 10:  13ecebcdee reftable/table: release filter on error path
     @@ Metadata
       ## Commit message ##
          reftable/table: release filter on error path
      
     -    reftable_table_refs_for_unindexed() allocates a filtering_ref_iterator
     -    and then calls reftable_buf_add() to populate its oid buffer. On
     +    `reftable_table_refs_for_unindexed()` allocates a filtering_ref_iterator
     +    and then calls `reftable_buf_add()` to populate its oid buffer. On
          success ownership is transferred to the output iterator, but if
     -    reftable_buf_add() fails, the goto-out cleanup only frees the table
     -    iterator and walks away from both the filter allocation and the
     -    oid buffer that reftable_buf_add() may have grown.
     +    `reftable_buf_add()` fails, the goto-out cleanup only frees the table
     +    iterator and walks away from both the filter allocation and the oid
     +    buffer that `reftable_buf_add()` may have grown.
      
          Release filter->oid and free filter alongside the existing table
          iterator cleanup.
 12:  8ad6b220e9 = 11:  97049d7cc3 fsmonitor: plug token-data leak on early daemon-startup failures
 13:  23ab9864b2 ! 12:  a5a6c27184 mingw: make exit_process() own the process handle on all paths
     @@ Metadata
      Author: Johannes Schindelin <johannes.schindelin@gmx.de>
      
       ## Commit message ##
     -    mingw: make exit_process() own the process handle on all paths
     +    mingw: make `exit_process()` own the process handle on all paths
      
          After "mingw: kill child processes in a gentler way", the ownership of
     -    the HANDLE passed to exit_process() and terminate_process_tree() is
     -    inconsistent. terminate_process_tree() always closes the handle;
     -    exit_process() closes it on success and on the terminate-tree
     +    the HANDLE passed to `exit_process()` and `terminate_process_tree()` is
     +    inconsistent. `terminate_process_tree()` always closes the handle;
     +    `exit_process()` closes it on success and on the terminate-tree
          fallback, but leaks it on the early return where GetExitCodeProcess()
          fails or reports the process is no longer STILL_ACTIVE.
      
     -    mingw_kill() compensated by closing the handle on its own error path,
     -    which is a double-close on every error path that does not hit that
     -    one leaky branch -- the callee has already closed the handle by then.
     +    `mingw_kill()` compensated by closing the handle on its own error path,
     +    which is a double-close on every error path that does not hit that one
     +    leaky branch -- the callee has already closed the handle by then.
          Coverity flagged the resulting use-after-free as CID 1437238.
      
     -    Pin down the invariant that exit_process() and
     -    terminate_process_tree() own the handle from the call onward and
     -    close it on every return path; with that, the bogus close in
     -    mingw_kill() goes away.
     +    Pin down the invariant that `exit_process()` and
     +    `terminate_process_tree()` own the handle from the call onward and close
     +    it on every return path; with that, the bogus close in `mingw_kill()`
     +    goes away.
      
          Assisted-by: Opus 4.7
          Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>

-- 
gitgitgadget

  parent reply	other threads:[~2026-07-05  8:24 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-01  7:04 [PATCH 00/13] coverity: fix leaks and error paths Johannes Schindelin via GitGitGadget
2026-07-01  7:04 ` [PATCH 01/13] load_one_loose_object_map(): fix resource leak Johannes Schindelin via GitGitGadget
2026-07-01  7:56   ` Patrick Steinhardt
2026-07-01 16:25   ` Junio C Hamano
2026-07-04  8:58     ` Johannes Schindelin
2026-07-01  7:04 ` [PATCH 02/13] loose: avoid closing invalid fd on error path Johannes Schindelin via GitGitGadget
2026-07-01  7:56   ` Patrick Steinhardt
2026-07-01  7:04 ` [PATCH 03/13] download_https_uri_to_file(): do not leak fd upon failure Johannes Schindelin via GitGitGadget
2026-07-01  7:04 ` [PATCH 04/13] run-command: avoid close(-1) in start_command() error paths Johannes Schindelin via GitGitGadget
2026-07-01  7:56   ` Patrick Steinhardt
2026-07-01  7:04 ` [PATCH 05/13] run_diff_files: avoid memory leak Johannes Schindelin via GitGitGadget
2026-07-01  7:56   ` Patrick Steinhardt
2026-07-04  8:58     ` Johannes Schindelin
2026-07-01  7:04 ` [PATCH 06/13] line-log: avoid redundant copy that leaks in process_ranges Johannes Schindelin via GitGitGadget
2026-07-01  8:02   ` Jeff King
2026-07-01  7:04 ` [PATCH 07/13] dir: free allocations on parse-error paths in read_one_dir() Johannes Schindelin via GitGitGadget
2026-07-01  7:56   ` Patrick Steinhardt
2026-07-04  8:58     ` Johannes Schindelin
2026-07-01  7:04 ` [PATCH 08/13] submodule: fix cwd leak in get_superproject_working_tree() Johannes Schindelin via GitGitGadget
2026-07-01  7:56   ` Patrick Steinhardt
2026-07-04  8:59     ` Johannes Schindelin
2026-07-01  7:04 ` [PATCH 09/13] worktree: fix resource leaks when branch creation fails Johannes Schindelin via GitGitGadget
2026-07-01  7:04 ` [PATCH 10/13] imap-send: avoid leaking the IMAP upload buffer Johannes Schindelin via GitGitGadget
2026-07-01  7:04 ` [PATCH 11/13] reftable/table: release filter on error path Johannes Schindelin via GitGitGadget
2026-07-01  7:04 ` [PATCH 12/13] fsmonitor: plug token-data leak on early daemon-startup failures Johannes Schindelin via GitGitGadget
2026-07-01  7:04 ` [PATCH 13/13] mingw: make exit_process() own the process handle on all paths Johannes Schindelin via GitGitGadget
2026-07-01 17:34 ` [PATCH 00/13] coverity: fix leaks and error paths Junio C Hamano
2026-07-05  8:24 ` Johannes Schindelin via GitGitGadget [this message]
2026-07-05  8:24   ` [PATCH v2 01/12] load_one_loose_object_map(): fix resource leak Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 02/12] loose: avoid closing invalid fd on error path Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 03/12] download_https_uri_to_file(): do not leak fd upon failure Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 04/12] run-command: avoid `close(-1)` in `start_command()` error paths Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 05/12] line-log: avoid redundant copy that leaks in process_ranges Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 06/12] dir: free allocations on parse-error paths in `read_one_dir()` Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 07/12] submodule: fix cwd leak in `get_superproject_working_tree()` Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 08/12] worktree: fix resource leaks when branch creation fails Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 09/12] imap-send: avoid leaking the IMAP upload buffer Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 10/12] reftable/table: release filter on error path Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 11/12] fsmonitor: plug token-data leak on early daemon-startup failures Johannes Schindelin via GitGitGadget
2026-07-05  8:24   ` [PATCH v2 12/12] mingw: make `exit_process()` own the process handle on all paths Johannes Schindelin via GitGitGadget

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=pull.2163.v2.git.1783239870.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=johannes.schindelin@gmx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.