InsightSoftwareConsortium / InsightSoftwareConsortium/ITK

Python: GetMetaDataDictionary() on a temporary image segfaults (use-after-free)

Open
#6,812 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
1.7k
Forks
748
Avg merge
1d 1h
Merged PRs (30d)
64

Description

### Description

`itk.Image.GetMetaDataDictionary()` returns a reference that does not keep its
image alive. If the image is a temporary, the returned dictionary outlives the
object that owns it, and reading from it is a use-after-free that **terminates
the interpreter with SIGSEGV** rather than raising.

This is a Python-wrapping lifetime issue, not a `MetaDataDictionary` defect.

### Steps to reproduce

```python
import itk

def make():
image = itk.Image[itk.F, 3].New()
image.SetRegions([2, 2, 2])
image.Allocate(True)
image.GetMetaDataDictionary()["key"] = "value"
return image

held = make()
print("owner alive:", held.GetMetaDataDictionary()["key"]) # -> value

dictionary = make().GetMetaDataDictionary() # image is a temporary
print(dictionary["key"]) # -> SIGSEGV
```

```console
$ python3 repro.py
owner alive: value
owner dead: reading...
$ echo $?
139 # SIGSEGV
```

Faulting frame:

```
File ".../itk/ITKCommonBasePython.py", line 2056 in Get
File ".../itk/ITKCommonBasePython.py", line 2128 in __getitem__
```

### Expected behavior

Either the returned dictionary keeps its image alive (as
`itk.image_view_from_array` does for the NumPy buffer it views, via
`_SetBase`), or the call raises. A hard interpreter crash gives the caller
nothing to catch and no indication of the real problem.

### Scope

Specific to this accessor. Other accessors called on a temporary are fine,
because they return by value:

| Call on a temporary image | Result |
|---|---|
| `make().GetSpacing()` | `(1.0, 2.0, 3.0)` |
| `make().GetLargestPossibleRegion().GetSize()` | `(2, 2, 2)` |
| `make().GetMetaDataDictionary()["key"]` | **SIGSEGV** |

Not a regression — reproduced identically on:

- ITK **5.4.6**, released PyPI wheel, Python 3.12
- ITK **6.0.0**, local build of `main`, Python 3.12

macOS 15, arm64.

### Why this is easy to hit

The dangerous form is the natural one to write, and it differs from the safe
form only by whether an intermediate name exists:

```python
metadata = itk.imread(path).GetMetaDataDictionary() # crashes on read
image = itk.imread(path); metadata = image.GetMetaDataDictionary() # fine
```

Found while writing conversion tests, where the one-liner form read as
obviously equivalent to the two-line form.

### Possible direction

`image_view_from_array` solves the analogous problem for array buffers by
calling `_SetBase` on the result. An equivalent owner reference on the object
returned by `GetMetaDataDictionary()` — or a SWIG `keep_reference` typemap —
would make the one-liner safe.

Contributor guide

Open the contributing guide

Research direction

Start at itk.Image.GetMetaDataDictionary and the generated ITKCommonBasePython.py frames around lines 2056 and 2128; compare image_view_from_array's _SetBase handling and the SWIG keep_reference option. Add a regression test that reads metadata from make().GetMetaDataDictionary() after the temporary image is gone, and confirm the interpreter no longer crashes.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
developer-experience
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
70/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.