Granting Specific File Access with SELinux: A Deep Dive for the qemu User

Encountering access restrictions, especially when dealing with shared memory or specific device files, can be a common yet frustrating hurdle in Linux environments. When the qemu user, a critical component for virtualization, finds itself unable to interact with essential files like /dev/shm/looking-glass, it signals a potential mismatch in security policies. While standard file permissions and Access Control Lists (ACLs) are the first line of defense, the presence of SELinux (Security-Enhanced Linux) can introduce an additional layer of control that may be preventing the desired access. At revWhiteShadow, we understand the nuances of such security configurations and are here to guide you through the process of adding an SELinux rule to grant a user read/write access to specific files, ensuring your system operates securely and efficiently.

Your scenario, where setting SELinux to permissive mode (sudo setenforce 0) resolves the access issue for the qemu user with /dev/shm/looking-glass, clearly indicates that SELinux is the operative constraint. The fact that setfacl commands, which modify standard Linux permissions and ACLs, don’t fully resolve the problem further reinforces this. This is because SELinux operates independently of these traditional permission mechanisms, enforcing its own set of security contexts and policies. The file /dev/shm/looking-glass, being managed by systemd-tmpfiles, has its permissions defined by /etc/tmpfiles.d/10-looking-glass.conf, specifying 0660 for owner and group qemu and kvm respectively. While these permissions seem adequate, SELinux requires its own explicit authorization for contexts to interact.

Understanding SELinux Contexts and Policy

Before we delve into the practical steps of creating an SELinux rule, it’s crucial to grasp the fundamental concepts behind SELinux. SELinux operates on the principle of labeling. Every file, process, user, and device on your system is assigned a security context (also known as an SELinux label). These contexts are strings that define the role and capabilities associated with the labeled entity. SELinux policy rules then dictate how these contexts can interact.

For instance, a file might have a context like system_u:object_r:shm_t:s0, indicating its user (system_u), role (object_r), type (shm_t), and sensitivity (s0). A process, like the qemu user’s processes, will have its own context, perhaps system_u:process_r:qemu_t:s0. SELinux policy defines rules like “allow processes with type qemu_t to read and write files with type shm_t”.

Your problem arises because the default SELinux policy likely doesn’t have an explicit rule allowing the qemu process type to access files labeled with the specific type assigned to /dev/shm/looking-glass in its current state, or perhaps the context of the file itself is not what SELinux expects.

Identifying the Current SELinux Contexts

The first step in resolving this is to accurately identify the SELinux contexts of both the target file and the process that needs to access it.

#### Determining the File’s SELinux Context

To find the SELinux context of /dev/shm/looking-glass, we use the ls -Z command. This command, when used with SELinux enabled, displays the security context alongside the standard file permissions.

ls -laZ /dev/shm/looking-glass

You will likely see an output similar to this:

-rw-rw----+ 1 qemu kvm unconfined_u:object_r:shm_t:s0 33554432 Jul 29 01:29 /dev/shm/looking-glass

In this example, the context is unconfined_u:object_r:shm_t:s0. The crucial part here is the type, which is shm_t. This type often signifies files within /dev/shm. However, the unconfined_u user domain might also be a factor, depending on how qemu is running.

#### Determining the qemu User Process’s SELinux Context

Similarly, to determine the SELinux context of the processes running under the qemu user, you can use ps -eZ | grep qemu. This will list all processes and their SELinux contexts. Look for processes associated with the qemu user.

ps -eZ | grep qemu

The output might show something like:

system_u:process_r:unconfined_t:s0-s0:c0.c1023 1234 qemu    ...

Here, unconfined_t is a common type for processes that are not confined by specific SELinux policies. If qemu is running within a more specific domain, you’ll see that type. The goal is to see what type your qemu processes are running as.

Diagnosing SELinux Denials

When SELinux prevents an action, it logs a denial message. These messages are invaluable for understanding what policy is being violated. The most common place to find these denials is in the audit log.

#### Accessing the Audit Log

You can view recent audit messages using the ausearch command:

sudo ausearch -m AVC,USER_AVC -ts recent

Alternatively, you can monitor the audit log in real-time using tail:

sudo tail -f /var/log/audit/audit.log | grep qemu

When the qemu user attempts to access /dev/shm/looking-glass and is denied, you should see an AVC (Access Vector Cache) denial message in the audit log. This message will specify the source context (the context of the qemu process), the target context (the context of /dev/shm/looking-glass), and the permission that was denied (e.g., read, write, create).

A typical denial might look like this:

type=AVC msg=audit(1678886400.123:456): avc:  denied  { read write } for  pid=1234 comm="qemu-process" name="looking-glass" dev="tmpfs" ino=789 scontext=system_u:process_r:unconfined_t:s0:c0.c1023 tcontext=system_u:object_r:shm_t:s0 tclass=file permissive=0

From this denial, we can extract:

  • Source Context: system_u:process_r:unconfined_t:s0:c0.c1023 (the qemu process)
  • Target Context: system_u:object_r:shm_t:s0 (the file /dev/shm/looking-glass)
  • Denied Permission: { read write }
  • Target Class: file

Creating a Custom SELinux Module

The most robust and recommended way to grant specific access is by creating a custom SELinux policy module. This involves defining new rules that permit the desired interaction without weakening the overall security posture of your system. We will use the audit2allow tool to help generate these rules.

#### Using audit2allow to Generate Policy Rules

audit2allow is a utility that parses the audit log for AVC denials and generates SELinux policy language that allows those actions.

Step 1: Capture the Denial

Ensure you have reproduced the access issue and that the denial is logged. If you are monitoring /var/log/audit/audit.log in real-time, you can pipe the relevant denial into audit2allow. A more practical approach is to let audit2allow read the log file directly after you’ve triggered the denial.

First, let’s focus the audit log on the specific denial. If the denial from the example above is the one we want to address, we can process it.

sudo ausearch -m AVC -ts recent | audit2allow -m qemu_shm -M qemu_shm
  • ausearch -m AVC -ts recent: This searches the audit log for all AVC messages from the last run.
  • audit2allow: This is the tool that translates denials into policy.
  • -m qemu_shm: This specifies that we want to create a module named qemu_shm. This will generate two files: qemu_shm.te (Type Enforcement rules) and qemu_shm.if (Interface file, often not needed for simple cases).
  • -M: This flag tells audit2allow to generate both the .te and .fc (File Context) files, but for this specific problem, we only need to manipulate the Type Enforcement rules. If the file context itself were incorrect, a .fc file would be generated to correct it.

If the denial you are targeting is not immediately obvious in recent logs, you might need to be more specific with ausearch or ensure the denial is the only one happening. A common practice is to run the problematic operation, then immediately run audit2allow on the log.

Step 2: Review and Edit the .te File

After running audit2allow, you will have a file named qemu_shm.te. Open it with a text editor:

nano qemu_shm.te

The content will look something like this, based on our example denial:

module qemu_shm 1.0;

require {
	type shm_t;
	type unconfined_t;
	class file { read write };
}

#============= unconfined_t ==============

allow unconfined_t shm_t:file read write;

This file defines a rule: Allow processes with the type unconfined_t to perform read and write operations on files with the type shm_t.

This rule directly addresses the denial we observed. However, it’s important to ensure that unconfined_t is indeed the correct type for your qemu processes. If the ps -eZ | grep qemu output showed a different type, you would need to adjust this accordingly, or ideally, create a more specific SELinux type for qemu processes if they are running in unconfined_t without a good reason.

Step 3: Compile and Install the Policy Module

Once you are satisfied with the .te file, you need to compile it into a loadable kernel module and install it.

make -f /usr/share/selinux/devel/Makefile qemu_shm.pp
sudo semodule -i qemu_shm.pp
  • make -f /usr/share/selinux/devel/Makefile qemu_shm.pp: This command uses the SELinux development Makefile to compile the .te file into a policy package (.pp file).
  • sudo semodule -i qemu_shm.pp: This command installs the compiled policy package into the SELinux policy database. The -i flag stands for install.

After installation, SELinux will now enforce this new rule.

Step 4: Test the Access

Try accessing /dev/shm/looking-glass with the qemu user again. The access should now be permitted without SELinux enforcing any denials. You can verify by running your qemu process or attempting the specific operation that was previously failing.

In some scenarios, the issue might be that the file itself has an incorrect SELinux context. While audit2allow can also generate file context rules (in a .fc file), directly changing the context of files in /dev/shm is generally discouraged because /dev/shm is a temporary filesystem (tmpfs) and its contents are often recreated. Modifying its context might be temporary or interfere with system operations.

However, if audit2allow were to suggest a file context change, the process would involve:

  1. Generating the .fc file: This is done by adding the -f flag to audit2allow or by reviewing the output of audit2allow for file context suggestions.
  2. Applying the context: Using the chcon command if it’s a manual change, or using semanage fcontext followed by restorecon for persistent changes.

For example, if audit2allow suggested changing the context of /dev/shm/looking-glass to svpn_shm_t, you might see a line like:

/dev/shm/looking-glass      system_u:object_r:svpn_shm_t:s0

You would then use:

sudo semanage fcontext -a -t svpn_shm_t "/dev/shm/looking-glass"
sudo restorecon -v /dev/shm/looking-glass

However, given that /dev/shm/looking-glass is managed by systemd-tmpfiles and likely intended to have a shm_t context, creating a rule to allow the qemu process type to interact with shm_t is the cleaner and more appropriate solution.

Refining SELinux Policies for qemu

While the audit2allow approach provides a quick solution, for long-term maintainability and better security practices, it’s beneficial to understand and potentially create more specific SELinux types for your qemu processes if they are currently running in unconfined_t.

#### Creating a Specific SELinux Type for qemu

If qemu processes are running as unconfined_t, it means they are not subject to any fine-grained SELinux policies. A more secure approach would be to define a specific SELinux type for qemu, say qemu_process_t, and then explicitly grant it the necessary permissions.

This involves a more extensive SELinux policy development process, which might include:

  • Defining the new type: In a .te file, you’d declare type qemu_process_t;.
  • Assigning the type to the process: This is typically done through policy rules that map processes to types based on their executable path or other attributes. For instance, if the qemu executable is /usr/bin/qemu-system-x86_64, you might have a rule like:
    type_transition system_management_dom, qemu_exec_t:file qemu_process_t;
    
    or directly label the executable:
    /usr/bin/qemu-system-x86_64 -- context system_u:object_r:qemu_exec_t:s0
    
    And then allow qemu_exec_t to transition to qemu_process_t.
  • Granting permissions: Once qemu has its own type (qemu_process_t), you can create rules specifically for it:
    allow qemu_process_t shm_t:file { read write };
    

This is a more advanced topic and would require deeper knowledge of SELinux policy writing. However, for the immediate problem of granting access to /dev/shm/looking-glass, the audit2allow method is efficient and effective.

Troubleshooting and Best Practices

  • Be specific: When using audit2allow, try to trigger only the specific denial you want to fix. If multiple denials occur, you might need to process them individually or carefully review the generated .te file.
  • Understand the context: Always verify the SELinux contexts of the involved file and process. Incorrect contexts can lead to unexpected behavior.
  • Test thoroughly: After installing a new policy module, test all relevant functionalities to ensure no other access has been inadvertently blocked or allowed.
  • Maintainability: For system updates, SELinux policies can sometimes be affected. If your custom module stops working after a system update, revisit the audit logs to see if new denials have appeared.
  • Documentation: Keep track of the custom SELinux rules you implement, including the rationale behind them, for future reference.

By carefully following these steps, you can effectively add an SELinux rule to grant the qemu user read/write access to the specific file /dev/shm/looking-glass, ensuring your system operates securely while meeting the functional requirements of your virtualization setup. At revWhiteShadow, we aim to provide the most comprehensive and actionable guidance for navigating complex system configurations, helping you achieve optimal performance and security.