InsightSoftwareConsortium / InsightSoftwareConsortium/ITK
Python: GetMetaDataDictionary() on a temporary image segfaults (use-after-free)
- 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
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