Writing GIO applications

The information in the GLib documentation about writing GLib applications is generally applicable when writing GIO applications.

Threads

GDBus has its own private worker thread, so applications using GDBus have at least 3 threads. GIO makes heavy use of the concept of a thread-default main context to execute callbacks of asynchronous methods in the same context in which the operation was started.

Asynchronous Programming

Many GIO functions come in two versions: synchronous and asynchronous, denoted by an _async suffix. It is important to use these appropriately: synchronous calls should not be used from within a main loop which is shared with other code, such as one in the application’s main thread. Synchronous calls block until they complete, and I/O operations can take noticeable amounts of time (even on ‘fast’ SSDs). Blocking a main loop iteration while waiting for I/O means that other sources in the main loop will not be dispatched, such as input and redraw handlers for the application’s UI. This can cause the application to ‘freeze’ until I/O completes.

A few self-contained groups of functions, such as code generated by gdbus-codegen, use a different convention: functions are asynchronous default, and it is the synchronous version which has a _sync suffix. Aside from naming differences, they should be treated the same way as functions following the normal convention above.

The asynchronous (_async) versions of functions return control to the caller immediately, after scheduling the I/O in the kernel and adding a callback for it to the main loop. This callback will be invoked when the operation has completed. From the callback, the paired _finish function should be called to retrieve the return value of the I/O operation, and any errors which occurred. For more information on using and implementing asynchronous functions, see GAsyncResult and GTask.

By starting multiple asynchronous operations in succession, they will be executed in parallel (up to an arbitrary limit imposed by GIO’s internal worker thread pool).

The synchronous versions of functions can be used early in application startup when there is no main loop to block, for example to load initial configuration files. They can also be used for I/O on files which are guaranteed to be small and on the local disk. Note that the user’s home directory is not guaranteed to be on the local disk.

Security

When your program needs to carry out some privileged operation (say, create a new user account), there are various ways in which you can go about this:

  • Implement a daemon that offers the privileged operation. A convenient way to do this is as a D-Bus system-bus service. The daemon will probably need ways to check the identity and authorization of the caller before executing the operation. polkit is a framework that allows this.

  • Use a small helper that is executed with elevated privileges via pkexec. pkexec is a small program launcher that is part of polkit.

  • Use a small helper that is executed with elevated privileges by being suid root.

None of these approaches is the clear winner, they all have their advantages and disadvantages.

When writing code that runs with elevated privileges, it is important to follow some basic rules of secure programming. David Wheeler has an excellent book on this topic, Secure Programming for Linux and Unix HOWTO.

When using GIO in code that runs with elevated privileges, you have to be careful. GIO has extension points whose implementations get loaded from modules (executable code in shared objects), which could allow an attacker to sneak his own code into your application by tricking it into loading the code as a module. However, GIO will never load modules from your home directory except when explictly asked to do so via an environment variable.

In most cases, your helper program should be so small that you don't need GIO, whose APIs are largely designed to support full-blown desktop applications. If you can't resist the convenience of these APIs, here are some steps you should take:

  • Clear the environment, e.g. using the clearenv() function. David Wheeler has a good explanation for why it is important to sanitize the environment. See Running GIO applications for a list of all environment variables affecting GIO. In particular, PATH (used to locate binaries), GIO_EXTRA_MODULES (used to locate loadable modules) and DBUS_{SYSTEM,SESSION}_BUS_ADDRESS (used to locate the D-Bus system and session bus) are important.

  • Don't use GVfs, by setting GIO_USE_VFS=local in the environment. The reason to avoid GVfs in security-sensitive programs is that it uses many libraries which have not necessarily been audited for security problems. Gvfs is also heavily distributed and relies on a session bus to be present.