Genodians: Secure screenshots part I
I had number of potential topics for my first Genodians article, but none worth publishing without a good screenshot. Secure operating systems should make screen capture difficult or impossible, so I had my excuse not to write those articles. Eventually I did write a screenshot utility, which I describe here. Later articles may cover implementation details and improvements to the scheme described here.
To summarize the state of the art...
Not as hard as it looks here
It just so happens that the display server used by Linux predates the concept of "untrusted software". Capturing the display on Unix desktops is convenient to the point of contempt, a client connects to the display server and asks for a dump of the screen. Not much more to say other than this is a bad idea.
Up until now screenshots of Genode are usually taken over the network using the AMT backdoor available as part of the Intel "Management Engine". ME is a privileged operating system built into modern Intel hardware that is capable of capturing display output in hardware and ex-filtrating images over its own independent networking stack. Intel sells this as a security feature, but not for the security of the person using the screen. ME screen capture is worse than unsecured software capture because it cannot be remove or disabled without effecting the behavior of the hardware. The backdoor frontend for taking screenshots is not available on all hardware, but we may assume the hardware capability is ubiquitous. Conversely, Intel is working towards restricting the ability of software to capture screen content, but with the explicit purpose of managing users' digital rights.
An alternative would be to inject a dedicated capture component into the display software stack. This component would intercept and proxy the display and can capture any content that passes through it. This is a better solution because the capture is explicit, capture is not a default feature, and the ability to capture the screen can be localized and isolated.
The better way
First of all, Genode does not have a desktop API, not in the traditional sense. There is a simple 'Framebuffer' service, an 'Input' event service, and a third service that combines the previous two, called 'Nitpicker'. The 'Nitpicker' service bundles a framebuffer and input service together with controls for selectively viewing, moving, and stacking regions of the framebuffer service. The Nitpicker server that provides this service is tasked with combining the various framebuffers regions from each client onto an output framebuffer and directing input events to the appropriate clients. Bundling the two services makes sense because so there are uses cases for both correlating or isolating the sessions at the client.
The current implementation of Nitpicker performs the necessary compositing of clients but lacks sophisticated window management, this is handled by a dedicated 'window_manager' component. The 'window_manager' proxies the 'Nitpicker' service between graphical applications and the Nitpicker server and thus has the authority to arbitrarily place framebuffer regions within the screen and manipulate screen metadata seen by clients.
From this we see that a screen capture component could be placed between the application and the window manager, the window manager and Nitpicker, or between Nitpicker and the display driver. What has been implemented is the last option. Though it may seem that capturing the complete screen from the Nitpicker server has the greatest risk of an inconvenient leak, a subset of graphical applications can be capturing using a recursive desktop.
The 'flif_capture' component is a server that requests an 'Input' and 'Framebuffer' session and serves a single 'Input' and 'Framebuffer' service. When the *PrtSc* key is intercepted a copy of the current framebuffer is written to file. The framebuffer memory is shared between the client, proxy, and backend, therefore the passthrough is zero-copy. Each input event is inspected before it is forwarded to the client so in this regard some latency is introduced. The component is available from the Genode World repository.
The output file format is FLIF. FLIF is a lossless image format that has an simple API for encoding images, which is the primary reason for using FLIF rather than PNG. The format is efficient but initial encoding is expensive, so in the future the output format may change. Conversion may be performed using the 'flif' tool provided with the reference implementation.
At the time of writing a depot package for Sculpt is available at 'ehmry/pkg/flif_capture/2019-01-30'. In contrast to the actual component the package is a composite of a few components so that it provides and requires a 'Nitpicker' service rather than separate 'Input' and 'Framebuffer' services. When the package is started the nested desktop will appear as a client window of the Sculpt window manager. To route applications through the capture package one must start a second instance of a window manager, a window layouter, and a window decorator (window placement and decoration is externalized so that it may be customized and swapped out at runtime). The nested window manager is routed to the capture component via a `Nitpicker` session, and the nested decorator and layouter are routed to the nested window manager. Clients are obviously routed to the nested window manager, but all other sessions may be routed normally, there is no disruption to networking or file-system services. At the end of this article I've included the launcher files for starting a nested desktop.
It is apparent that this scheme is cumbersome in practice. Starting a second window manage is inconvenient, not more so than the inability to capture applications that are already running in the first window manager. My intention was to implement something secure regardless of convenience, so I consider the project a success and hope to cover the next steps in additional articles.
A screen shot of a small virtual desktop
A screen shot of a screen shot requires recursion