ITK: Nifti volume of specific size + Shrink filter cause segmentation fault

Description

The combination of NIFTI + Shrink filter causes a segmentation fault for some dimensions and sizes.

Steps to Reproduce (MVE)

  1. Generate a nifti volume of the size (1034, 1034, 1020)

    import itk
    
    pixel_type = itk.US
    dimension = 3
    image_type = itk.Image[pixel_type, dimension]
    
    start = itk.Index[dimension]()
    start.Fill(0)
    
    size = itk.Size[dimension]()
    size[0] = 1034
    size[1] = 1034
    size[2] = 1020
    
    region = itk.ImageRegion[dimension]()
    region.SetIndex(start)
    region.SetSize(size)
    
    image = image_type.New(Regions=region)
    image.Allocate()
    
    itk.imwrite(image, "segmentation-fault-test.nii")
    
  2. Create a pipeline with ImageFileReader + ShrinkImageFilter and update at the end

    import itk
    from itk import ShrinkImageFilter
    
    image_path = "segmentation-fault-test"
    reader = itk.ImageFileReader.New(FileName=image_path)
    shrink = ShrinkImageFilter.New(reader)
    shrink.SetShrinkFactors(4)
    
    print("Update")
    shrink.Update()
    
    print("GetOutput")
    data = shrink.GetOutput()
    
    del shrink
    del reader
    print("Done")
    
    
  3. Run it (for windows add -Xfaulthandler to the python command)

Expected behavior

Script runs through without any errors and outputs all print messages.

Actual behavior

A segmentation fault (linux) / access violation (windows) after the “Update” printout.

Reproducibility

With the mentioned dimensions 100%.

Versions/Environments

Linux:

  • itk 5.1.2 and 5.2.1
  • Python 3.8

Windows:

  • itk 5.3rc4.post2
  • Python 3.9

Additional Information

  • The dimensions matter, e.g.
    • 1034, 1034, 1020 (1090539120) crash
    • 4048, 2048, 132 (1094320128) works
    • 826, 843, 3314(2307597852) crash
  • Other voxel types uint8 (with 1034, 1034, 2040) or float are also affected
  • Seems to be related to the byte size in combination with the dimensions
  • Other shrink factors (as 2) are also affected
  • .nii.gz is also affected
  • nrrd seems to work FINE
  • In some other workflow this causes a corruption of the volume without a seg fault (I try to add a picture later)

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 22 (21 by maintainers)

Most upvoted comments

After a big of digging I think I found the bug in the upstream nifti_clib in nifti_read_subregion_image. The requested size of 1029 * 1029 * 1017 * 2 = 2 153 682 594 bytes is bigger than the signed size of a int causing an overflow. https://github.com/InsightSoftwareConsortium/ITK/blob/2f2b29cbbe3fe91474933e0194db8d581d5b3247/Modules/ThirdParty/NIFTI/src/nifti/niftilib/nifti1_io.c#L6972 The following malloc does not allocate any memory and thus causes a segmentation fault. https://github.com/InsightSoftwareConsortium/ITK/blob/2f2b29cbbe3fe91474933e0194db8d581d5b3247/Modules/ThirdParty/NIFTI/src/nifti/niftilib/nifti1_io.c#L7060

ShrinkImageFilter has had some problems for a long time now. Fixing the crashes using this repro case would be useful for large images, as sometimes people don’t do a proper pyramid and resort to ShrinkImageFilter.

Great job so far!

A small addition: There are cases, where the data only gets corrupted and does not crash on loading. The image below is suppose to be a cylinder and the blocky part in the middle is “corrupted”. This is visualized with itkwidgets and only has a fault on later use. size: (826, 843, 3314), type: unsigned_short

shrink_corrupted_e

It crashes in nifti reader, when trying to read a region with index 3,3,2 and size 1029,1029,1017.

I was able to reproduce the issue in C++:

itkShrinkImageTest2(int, char *[])
{

  using ImageType = itk::Image<unsigned short, 3>;
  ImageType::SizeType   size = { { 1034, 1034, 1020 } };
  ImageType::RegionType region;
  region.SetSize(size);

  auto image = ImageType::New();
  image->SetRegions(region);
  image->Allocate(true);

  const std::string fname{"test.nii"};
  itk::WriteImage(image, fname);


  using ReaderType = itk::ImageFileReader<ImageType>;
  auto reader = ReaderType::New();
  reader->SetFileName(fname);

  using ShrinkFilterType = itk::ShrinkImageFilter<ImageType, ImageType>;
  auto shrinker = ShrinkFilterType::New();
  shrinker->SetInput(reader->GetOutput());
  shrinker->SetShrinkFactors(4);

  shrinker->Update();

  return 0;
}