Skip to content

coverity: fix leaks and error paths

Johannes Schindelin via GitGitGadget git 2 versions – In review

Get series with b4
b4 am [email protected]

Versions newest first · linked by author and title

  1. v2 Latest12 patches 4 repliesIn review
  2. v113 patches 13 repliesSuperseded

Changes in v2 from the cover letter

* 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).

Patches in v2 No applies recorded yet

#Subject and filesSizeRepliesApplied
1/12load_one_loose_object_map(): fix resource leakloose.c+4 -51 reply
2/12loose: avoid closing invalid fd on error pathloose.c+2 -10 replies
3/12download_https_uri_to_file(): do not leak fd upon failurebundle-uri.c+1 -10 replies
4/12run-command: avoid `close(-1)` in `start_command()` error pathsrun-command.c+3 -30 replies
5/12line-log: avoid redundant copy that leaks in process_rangesline-log.c+1 -20 replies
6/12dir: free allocations on parse-error paths in `read_one_dir()`dir.c+7 -20 replies
7/12submodule: fix cwd leak in `get_superproject_working_tree()`submodule.c+10 -91 reply
8/12worktree: fix resource leaks when branch creation failsbuiltin/worktree.c+5 -20 replies
9/12imap-send: avoid leaking the IMAP upload bufferimap-send.c+1 -00 replies
10/12reftable/table: release filter on error pathreftable/table.c+4 -00 replies
11/12fsmonitor: plug token-data leak on early daemon-startup failuresbuiltin/fsmonitor--daemon.c+2 -00 replies
12/12mingw: make `exit_process()` own the process handle on all pathscompat/mingw.c compat/win32/exit-process.h+2 -30 replies

Reviews trailers per patch

No Reviewed-by, Acked-by or Tested-by trailers for v2 yet.

Read the v2 thread

Cover letter [PATCH v2 00/12] coverity: fix leaks and error paths

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 <[email protected]>

## 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 <[email protected]>

## 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 <[email protected]>

## 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 <[email protected]>

## 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
Signature · 1 line
gitgitgadget