Skip to content

BUG: Fix the ambiguous-filename path in nifti_findhdrname - #54

Merged
hjmjohnson merged 1 commit into
InsightSoftwareConsortium:masterfrom
gdevenyi:pr/fix-library-no-exit
Sep 22, 2026
Merged

hjmjohnson merged 1 commit into
InsightSoftwareConsortium:masterfrom
gdevenyi:pr/fix-library-no-exit

Conversation

@gdevenyi

@gdevenyi gdevenyi commented Aug 15, 2026 •

Copy link
Copy Markdown

A library must not terminate the host application. niftilib and nifti2
each have two exit() calls, both inside optional FSL build modes, and
both sit next to other defects in the same few lines.

nifti_findhdrname(), under -DFSLSTYLE:

free(basename);
char *gzname = (char *)calloc(sizeof(char),strlen(hdrname)+8);
strcpy(gzname, hdrname);                     /* gzname unchecked */
...
   fprintf(stderr,"... basename (*.nii, *.nii.gz): %s\n",
           basename);                        /* freed above */
   free(gzname);
   exit(134);                                /* in a library */
  • basename is freed and then read by the fprintf that reports the
    error, so the diagnostic path is a use-after-free;
  • the calloc result is passed to strcpy and strcat unchecked;
  • hdrname leaks;
  • exit(134) kills the caller.

The free now happens after the last use, the allocation is checked, both
buffers are released and the function returns NULL like every other
failure path in it.

nifti_image_read(), under -DREJECT_COMPLEX, tested nim->datatype
before the 'if( nim == NULL )' check three lines below it, so a header
that failed to convert dereferenced a null pointer instead of reporting
the error. The block now follows the NULL check, releases nim, the file
handle and hfile, and returns NULL rather than exit(13).

Overlap with #11: that PR makes the same exit(134) -> return NULL change
in nifti_findhdrname, and rewords the REJECT_COMPLEX message, which this
adopts verbatim. It does not fix the use-after-free, the unchecked
calloc, the leak or the null dereference. If #11 merges first I will
rebase; the overlap is two lines.

Stated plainly: callers that relied on the process dying now get NULL
back. That is the intent, and it is what the maintainer asked for in
#11, but a caller that never checked the return value will continue with
a NULL pointer where it previously exited. Both paths are behind
non-default build options.

fsliolib is deliberately untouched. Its FSLIOERR macro also exits, and
de-fatalising it changes the contract at roughly fifty call sites, which
belongs in its own change.

Verified with the default configuration and with each of
FSLSTYLE_NAME_CONFLICTS=ON and FSLSTYLE_REJECT_COMPLEX=ON: all build
clean and none change the test result.

Overlap with #11. That PR makes the same exit(134) -> return NULL change in nifti_findhdrname in both libraries, removes exit(13) from nifti2's REJECT_COMPLEX block, and rewords the message, which this adopts verbatim. Four lines are therefore changed by both. #11 does not fix the use-after-free, the unchecked calloc, the leak or the null dereference, which is what this adds. If #11 merges first I will rebase.


Interface impact: none. On the union of all these changes, configured with USE_FSL_CODE=ON and USE_CIFTI_CODE=ON: all 448 exported symbols across libniftiio, libnifti2, libznz, libfslio, libnifticdf and libcifti are identical to master under nm -D --defined-only, and all ten installed headers are identical under gcc -E -P. Under gcc -dM -E one macro definition differs, intentionally and only in text: #61 makes FSL_RADIOLOGICAL read (-1) so it is safe inside an expression. Its value is still -1, checked by compiling against each installed fslio.h and printing it.

Verification. This branch: builds with gcc 16.1.1, ctest unchanged from master (2 of 345 fail on master itself in this environment; #31 and #29 each fix one). The union of all the PRs: 0 errors under both gcc 16.1.1 and clang 22.1.8, ctest 345/345 under each, and the whole suite under valgrind memcheck with --trace-children=yes gives 484 traced processes with no invalid access, no uninitialised value and no leak in any nifti binary.

Coordination. Every line of every branch was compared, whitespace-normalised, against the diffs of the open PRs (#11, #21, #22, #23, #24). Where one of those already changes a line, the line was left alone, and the few deliberate overlaps are named in the text above. What survives is 17 compiler warnings, all of them on those lines: 9 -Wsign-conversion (5 in fslio.c for #22, 2 in nifti2_io.c and 2 in nifti_tester001.c for #24) and 8 -Wcalloc-transposed-args in nifti_findhdrname and nifti_findimgname, which #11 rewrites. No formatting changes appear anywhere, to stay clear of #10 and #12.

One of a set of independent, single-purpose PRs. Each bases on master and can be merged on its own, in any order.

The full set of PRs (35)

The union of all of them is on the fork as all-changes, if you want to build and test the lot at once.

CI and build

Configuration and documentation

Defects

Warning and check classes

This was referenced Aug 15, 2026
Under FSLSTYLE the block that reports "Multiple possible filenames" has
three defects, and they cannot be separated.

  free(basename);                                  <- freed here
  char *gzname = calloc(...);                      <- unchecked
  strcpy(gzname, hdrname);                         <- used at once
  ...
    fprintf(stderr,"... %s\n", basename);          <- read after free
    exit(134);                                     <- kills the caller

basename is read by the message, so it cannot be freed at the top.
gzname goes straight into strcpy(), so it has to be checked.  And once
the function returns NULL instead of calling exit(), every path out has
to release what it holds, which is what ties the three together: moving
the free of basename decides what the error paths must free, and there
are no error paths to speak of until exit() is gone.

FSLSTYLE is off by default but is a supported configuration:
-DFSLSTYLE:BOOL=ON turns it on, and nifti2/Makefile defines it always.
Built and tested with -DFSLSTYLE=ON.

The nifti_image_read() half of the original change is now NIFTI-Imaging#74.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hjmjohnson
hjmjohnson force-pushed the pr/fix-library-no-exit branch from df1cc2b to fe115f2 Compare September 22, 2026 02:10
@hjmjohnson

Copy link
Copy Markdown
Member

Rebased onto current master. This fixes a real use-after-free, confirmed independently before and after the fix.

basename was freed, then read by the fprintf two lines below when the ambiguous-name error fires. Compiling under FSLSTYLE with -Wuse-after-free shows 4 warnings on master, 0 on this branch — the same diagnostic that surfaced the bug now reports it fixed rather than the fix being asserted from reading the diff alone.

Build Result
FSLSTYLE=OFF, GCC 13.3 0 warnings, 345/345 tests, exported symbols unchanged (439)
FSLSTYLE=ON + ASan, Debug -Wuse-after-free: 0 (was 4 on master), 345/345 tests
What changed and why each part is needed

exit(134) becomes return NULL. A library should not terminate its host process; the ambiguous-filename case is now reported to the caller like any other lookup failure.

Returning instead of exiting means every path out of the function now has to release what it holds, which is why the diff also frees hdrname on the paths that previously relied on exit() to clean up, and checks gzname's allocation before using it — calloc failing here would otherwise be used unchecked.

@hjmjohnson
hjmjohnson merged commit e139858 into InsightSoftwareConsortium:master Sep 22, 2026
21 checks passed
@seanm

seanm commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

@hjmjohnson I see you've had a burst of activity here, nice! But instead of merging straight after your review, could you instead add your approval to the PR then give a few days for others (me mostly, I guess) to add a second review.

I've been on vacation for several weeks but finally have time this week to start looking at these MRs.

Especially since they are bot created, I think a double human review would be wise.

@hjmjohnson

Copy link
Copy Markdown
Member

The commit messages in this range were rewritten to remove trailers that do not belong in permanent history: Co-Authored-By: naming an AI tool, and Claude-Session: URLs that resolve for nobody. Only messages changed — the tree at the tip of master is byte-identical, and author, committer, and dates are preserved.

This PR's commit on the rewritten master:

  • 0ddf192185 BUG: Fix the ambiguous-filename path in nifti_findhdrname

The SHA recorded above by GitHub is from the pre-rewrite history and no longer resolves.

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.

3 participants