Many embedded Linux systems use a Wayland compositor like Weston for window management. Qt applications act as Wayland clients. Weston composes the windows of the Qt applications into a single window and displays it on a screen. I still have to find a Yocto layer that does not start Qt applications as root. This violates the cybersecurity principle that every application should only run with the least privileges possible. Let us figure out how to run Qt applications as non-root users and make our system more secure. We build our embedded Linux system with Yocto (Yocto 5.0 “scarthgap” at the time of writing). The system uses the default Wayland compositor Weston to show one or more Qt applications – the Wayland clients – on a display. From the Qt example applications in the meta-boot2qt layer or in other vendor BSPs, we might have cobbled together a systemd service unit b4-simple-app.service like this: This service starts the Qt application B4SimpleApp as root. I always felt a bit uneasy about this solution, but it worked and was suggested by experts. Anyway, customers didn’t want to pay for anything better. However, the arrival of the EU Cyber Resilience Act (EU CRA) turned the tables. Running applications as root violates the cybersecurity principle of least privilege – and the EU CRA. Why are Wayland clients run as root? The reason are the permissions of the socket file /run/wayland-0, which the Wayland server and client use to communicate with each other. If the Qt application is started as a non-root user other than weston – say, torizon with user and group ID 1000, starting the Qt application B4SimpleApp will fail with the error The same error occurs, if we start Weston as root. Some BSPs do this, some don’t. Two options suggest themselves: This leaves us with the first option: starting the application as user weston by setting User=weston in the service unit of the application. The environment variable XDG_RUNTIME_DIR follows the pattern /run/user/. is the ID of the user running the application. Now, is not the root ID 0 any more but the ID of the user weston. By default, the Yocto build creates the IDs for the users dynamically. If we add or remove users, the ID for weston may differ from build to build. We better use static user and group IDs as described for the class useradd*. For example, I assigned the static ID 2000 to the user weston. Hence, Weston writes runtime information for its Wayland clients into the directory /run/user/2000. Clients use the environment variable XDG_RUNTIME_DIR to read the information from Weston or to pass information to Weston. As Wayland clients like B4SimpleApp run as user weston with the ID 2000, we could skip setting XDG_RUNTIME_DIR in the clients’ service units. However, this may break, if we figure out how to run Qt applications as other users than weston and root or if we use a relative path for the socket file given in WAYLAND_DISPLAY. So, we should set XDG_RUNTIME_DIR to /run/user/2000 to avoid future debugging sessions. The environment variable WAYLAND_DISPLAY is the filename of the socket that the Wayland compositor and client use for inter-process communication. If WAYLAND_DISPLAY is an absolute path, it is used as is. If it is a relative path, $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY is used as the socket file name. Weston ignores the value of WAYLAND_DISPLAY. The Weston main() function calls the function weston_create_listening_socket(display, NULL), where the socket file name is NULL (both functions in main.c). The latter function generates a socket base name wayland-x – with x between 1 and 32 – so that the socket $XDG_RUNTIME_DIR/wayland-x can be set up successfully. As the socket file name may change, the Wayland client will eventually fail to start with the error message Waiting for an application to fail in the field is a pretty bad idea. Moreover, it violates the availability requirement of the EU CRA. We must ensure that the socket file name is the same for the Wayland server, Weston, and its clients. We can pass the option -S to Weston in the service unit weston.service. Weston main() calls weston_create_listening_socket(display, "/run/wayland-0"). The latter function creates the socket with the name /run/wayland-0. If the socket name is relative, say, wayland-1, it will create a socket with the name $XDG_RUNTIME_DIR/wayland-1. See the function wl_socket_init_for_display_name called by wl_display_add_socket (both in wayland-server.c) called by weston_create_listening_socket to fully understand the generation of the socket filename. On the client side, we set the environment variable WAYLAND_DISPLAY to the same value /run/wayland-0 passed to Weston with option -S. We could also pass -S "wayland-1" to Weston and set WAYLAND_DISPLAY to wayland-1 for B4SimpleApp. Weston and B4SimpleApp would then communicate through the socket /run/user/2000/wayland-1. We could add the following to lines to the service unit of every Wayland client: Obviously, we would have to duplicate the same two lines in all service files. We can fix this by moving the two lines to an environment file /etc/default/weston-client and include this file in the clients’ service units: The environment file has the following content: Furthermore, it would be a bad idea to let the client recipe b4-simple-app.bb create the environment file weston-client and the recipe weston-init.bbappend create the option -S for the Weston call. When we change the socket filename in one place, we might forget to change it in the other place. We avoid this by creating both the environment file and the option in the recipe weston-init.bbappend. We can change the user and group in weston.service and weston.socket, pass the socket file name to the weston command and generate the environment file weston-client in a single place: in the do_install task of the recipe extension weston-init.bbappend. We create the file recipes-graphics/wayland/weston-init.bbappend in our own Yocto layer (e.g., meta-b4-apps for me). What we add to the do_install task depends on how many weston-init.bbappend files extend the original recipe openembedded-core/meta/recipes-graphics/wayland/weston-init.bb. As I’m extending the Torizon Minimal image by Weston and Qt applications, I’m facing three extensions: A good approach is to check the generated files in the work directory of the image recipe (e.g., b4-hmi-product-image) for the required changes. The relevant files are: In my case, the do_install task looks as follows: The changes do the following: We are almost done. The service unit of the Wayland clients – e.g., b4-simple-app.service – needs two little changes. Systemd starts the Qt application B4SimpleApp as the non-root user weston. It also passes the correct socket name WAYLAND_DISPLAY and runtime directory XDG_RUNTIME_DIR to the application – through the environment file /etc/default/weston-client. If all Wayland clients use the same environment file and run as user weston, Weston will be able to display the clients without problems. Neither Weston nor the Qt applications must run as root. Your email address will not be published. Required fields are marked * Name Email Website Comment *
By ticking this checkbox, you consent to this privacy policy.
*
Notify me of follow-up comments by email. Notify me of new posts by email.