A .nii or .nii.gz file can come from anywhere: a download, an archive, a
shared folder, a public dataset. Selecting it in Files and pressing Space is
enough for it to be read, so every file is treated as hostile. Reading one
must never freeze the desktop, crash the previewer, or do anything but read.
The file is parsed by nifti-preview, a separate process started for each
preview and stopped when it closes. GNOME Sushi only receives a few small JSON
replies and RGB pixels from it.
Bounded work and memory (backend):
max-image-mb (3 GB by
default; settings), .nii and .nii.gz alike. A
spectroscopy (NIfTI-MRS) spectrum needs all the data to fit; when they do
not, its maps may still show.vox_offset) must be within the first 256 MB; only the first
MB of header extensions is read.fstat. If the path is replaced by
a FIFO or a device afterwards, nothing changes..nhdr
or an MRtrix .mih (at most 16 384, in its folder: an absolute path or .. is refused, as for a
MetaImage .mhd) get the same checks as the image
(a regular file, opened without blocking); they are only read. A symbolic link
among them is followed only to a file in the header’s folder: the file actually
opened is checked (/proc/self/fd). The images a DICOMDIR lists stay in its
folder the same way, checked when the DICOMDIR is read...). Parsing is
bounded (nesting depth, element count, value sizes, fragments) and stops at
the pixel data. JPEG, JPEG-LS and RLE are decoded by Rust code written for
this project; JPEG 2000 by OpenJPEG (C), inside the same sandbox, after the
image size of the codestream was checked. NRRD’s bzip2, ascii and hex data
are decoded by Rust code written for this project too, never beyond the size
the header declares. Decoders are fuzzed in CI.Sandbox (backend, Linux, best effort on older kernels):
RLIMIT_DATA;
read-only file mappings do not count).PR_SET_NO_NEW_PRIVS).A memory bug in the code that parses the file, including the C library used
for gzip, therefore cannot write to your files, run a program or reach the
network. On a kernel without Landlock (before 5.13) the file restrictions are
missing; the socket filter, the memory cap and no new privileges remain.
NIFTI_PREVIEW_DEBUG=1 prints what was applied, and the hello line of the
protocol reports it.
Terminal viewer (nitty):
descrip, DICOM tags, NRRD fields), a label name of a user table or a
terminal’s own answer cannot send an escape sequence (set the clipboard, the
title, move the cursor).fuzz/fuzz_targets/terminal.rs,
also a CI job).t=f).reset restores
the terminal.Previewer side (GJS):
Thumbnails: GNOME Files runs gnome-nifti-preview-thumbnailer thumbnail
in its own bubblewrap sandbox (only /usr, the file and the output; no
network, no home folder). The program then opens its output and applies the
sandbox above before it reads the file, with a lower memory cap (1 GB of voxel
data) and a limit on the compressed data it inflates ([thumbnails] max-file-mb).
Installers:
get.sh, install.sh and uninstall.sh refuse to run as root, except to
install or remove the terminal viewer alone (--terminal, for containers),
which writes only nitty in its folder (~/.local/bin, --bin-dir, or
/usr/local/bin as root). As root, install.sh --terminal builds nothing
and installs only a file that nobody but root or the user running sudo could
have changed. nitty --version is asked with a time limit, no input and a
bounded answer; uninstall.sh --terminal removes nitty only when it is a
regular file that answers nitty <version>.install.sh copies the backend to
/usr/local/bin/gnome-nifti-preview-thumbnailer with sudo (GNOME’s sandbox
cannot run programs from home folders). It copies again only when the backend
changed; uninstall.sh offers to remove it, and removes only that recorded
path when it is still our program.uninstall.sh deletes only the paths that install.sh creates, whatever its
manifest says, and never through a symbolic link planted in its folder.install.sh never rewrites a mimeapps.list that is a symbolic link,
unreadable or invalid. If it became a link after the install (dotfiles
adopting it), uninstall.sh still removes our entries from the file it
points to: otherwise they would name launchers that no longer exist.uninstall.sh puts back mimeapps.list byte for byte when nothing else
changed it since the first install; otherwise it removes only our entries.
An external edit detected during a reinstall disables exact restoration
permanently for that installation, even if a later run changes the file again.PATH, a desktop entry or a download folder).get.sh downloads over HTTPS only, without downgrade, and checks the
archive’s SHA-256 before running anything from it.Releases:
cargo deny) on every change and every week.sha256sum -c gnome-nifti-preview.tar.gz.sha256 # or nitty.tar.gz.sha256
# public repository: also check where and how it was built
gh attestation verify gnome-nifti-preview.tar.gz --repo EloiNavet/gnome-nifti-preview
Please do not open a public issue. Use Security → Report a vulnerability on the GitHub repository, so that the report stays private until a fix is released.