Endless OS is distributed as an immutable OSTree snapshot, with apps added & removed with Flatpak (and podman for power users & developers). Although the snapshot is assembled from Debian packages, it’s not really possible to install additional system packages locally, nor to remove them. Over time, we have tried to remove as many apps out of the immutable OS as possible: Flatpak apps are sandboxed and can be updated at a faster cadence than the OS itself, or removed if not needed.
Evince is one such app built into the OS at present. As a PDF viewer, it handles untrusted input in a complex format with libraries that have historically contained vulnerabilities, so is a good candidate for sandboxing and timely updates. While exploring removing it from the OS in favour of the Flatpak version from Flathub, I learned some things that were non-obvious to me about print preview, and which prevented making this change at the time.
Caveats: the notes below are a simplification, but I believe they are broadly accurate for GNOME on Linux. I’m sure people more familiar with GTK and/or printing already know everything here.
Printing from GTK apps
GTK provides API for applications to print documents. This presents the user with a print dialog, with many knobs to control how your document will be printed. That dialog has a Preview button; when you press it, the dialog vanishes and another one appears, showing a preview of your document. You can press Print on that dialog to print the document, or close it to cancel.
Why does the Preview button close the print dialog and open another one? Why does the preview dialog not have any of the knobs from the print dialog, or a way to return to the print dialog?
The answer lies in the documentation for GtkPrintOperation:
By default GtkPrintOperation uses an external application to do print preview.
The default application is Evince! More specifically, it is the following command:
evince --unlink-tempfile --preview --print-settings %s %f
Cribbing from the manpage:
- --preview
- Run evince as a previewer.
- --unlink-temp-file
- If evince is run in preview mode, this will unlink the temporary file created by GTK+.
- --print-settings %s %f
- This sends the full path of the PDF file, f, and the settings of the print dialog, s, to evince.
So when the user chooses to preview the document, GTK asks the application to render the document with the settings from the dialog, generates a PDF, and then invokes Evince to display that PDF. When you press Print in the preview dialog, it is Evince that sends the job to CUPS and thence to the printer.
What if evince is not present on the $PATH? The button is still displayed, but pressing it does nothing and the following is logged to stderr:
sh: 1: exec: evince: not found
There is code in GTK which attempts to handle this case by logging its own warning and then invoking the default PDF viewer on the generated PDF, but it doesn’t work because GLib actually spawns sh, not evince directly, and then returns success because ‘sh’ was successfully launched.
Printing from sandboxed apps
What happens if the application using the GtkPrintOperation API is a Flatpak app? evince is not part of any runtime, so GTK running in the application process cannot invoke it to preview the document? Well, in the general case the app can’t talk directly to CUPS either. So it uses the print portal’s PreparePrint method to prompt the user to choose a printer & settings, then renders the document and sends it to the portal with the Print method. The desktop portal service, which also uses GTK but is running outside the sandbox, presents the print dialog, and invokes evince if needed. All good, nothing too tricky here.
But notice that a sandboxed app is feeding a PDF to an unsandboxed PDF viewer. If the sandboxed app is malicious and can convince a user to print-preview a document, and there is some arbitrary code execution bug in Evince’s PDF library, then you’re in for a bad day.
What if Evince is a Flatpak?
The Flatpak version of Evince does not put an ‘evince’ command onto the $PATH, by design of Flatpak. So if you remove Evince from the OS and install the Flatpak, print preview stops working.
The evince executable inside the org.gnome.Evince Flatpak supports the --preview flag as normal. So you can put something like the following into ~/.config/gtk-4.0/settings.ini:
# https://docs.gtk.org/gtk4/property.Settings.gtk-print-preview-command.html [Settings] gtk-print-preview-command=flatpak run --file-forwarding org.gnome.Evince --unlink-tempfile --preview --print-settings @@ %s %f @@
--file-forwarding triggers special handling of the arguments bracketed by @@:
If this option is specified, the remaining arguments are scanned, and all arguments that are enclosed between a pair of ‘@@’ arguments are interpreted as file paths, exported in the document store, and passed to the command in the form of the resulting document path.
And this does indeed cause Evince to be spawned. However Evince can’t print the document. This is because its previewer tries to talk directly to CUPS, and its sandbox does not allow it to talk to CUPS. You might try punching some crude holes in the sandbox:
[Settings] gtk-print-preview-command=flatpak run --file-forwarding --socket=system-bus --socket=cups org.gnome.Evince --unlink-tempfile --preview --print-settings @@ %s %f @@
and it seems to get a bit further, but by this point you’ve given up and turned your printer off because you want to go to bed.
What next?
I think it’s desirable for a PDF viewer to be sandboxed. I also think it’s desirable for the print previewer in particular to be sandboxed, or else a malicious-but-sandboxed application could trick the user into printing a PDF that exploits some vulnerability in the previewer and run stuff on the host system.
As I write this up, the gtk-print-preview-command override seems more viable than it did when I first looked into this last year. I think at the time, GTK in the GNOME runtime didn’t have the CUPS backend enabled so it couldn’t print even if you punched the relevant sandbox holes, but apparently it does now, so maybe we can make this change after all. It’s a shame I only realised this after spending hours writing this post!
You could also imagine extending the print portal API to allow an external app to be used for the preview without allowing that app to talk directly to CUPS.
(You could gracefully handle Evince not being installed by putting a wrapper script onto the $PATH which invokes Evince if installed or prompts you to install it if not.)
Are you considering Dangerzone https://freedom.press/news/welcome-to-the-dangerzone/ ?
I hadn’t seen that! Interesting tool. I wonder if they could be encouraged to make it available on Flathub!
One thing I glossed over in this post is that the Evince Flatpak is currently not meaningfully sandboxed: it has access to the whole host filesystem, and from there it’s not too hard to execute arbitrary stuff outside the sandbox to evade the restrictions it does have (such as no network access). But it’s a start.
This remembers me of WebKitGtk struggling and printing from within the sandbox. When the sandbox is turned on printing doens’t work. Even generating PDF from Websites is not possible because it also uses the actual printing tools. Luckily Epiphany/WebKitGtk is nowadays able to save a website – via the context menu – as PNG. But printing sth. like an order confirmation isn’t possible 🙁
https://bugs.webkit.org/show_bug.cgi?id=202363
Printing from within an Flatpak and the sandbox of WebKitGtk are different things but share both the printing facilities of Gtk. Maybe an opportunity to coordinate work upon this within Gtk?
One reason Evince flatpak uses broad host permission it’s cause it needs “neighbouring files” portal to be implemented:
https://github.com/flatpak/xdg-desktop-portal/issues/463
it’s needed for synctex support (looks for a SAME_PDF_NAME.synctex file in the same dir as the pdf) and also for pdfs that have movie or audio files (they are looked in the same dir or below) and may also be needed for pdfs that open other pdfs (eg. see https://gitlab.gnome.org/GNOME/evince/-/issues/48 ).
My colleague Jian-Hong Pan reminded me of another hiccup on the road to an Evince-free immutable OS: Evince provides the thumbnailer for PDFs, but Flatpak apps cannot currently export thumbnailers. https://github.com/flatpak/flatpak/issues/4923
The print preview apps seems like a good fit for the xdg intent spec https://gitlab.freedesktop.org/xdg/xdg-specs/-/merge_requests/45