What could SELinux have done to mitigate the postgresql vulnerability?

what-could-selinux-have-done-to-mitigate-the-postgresql-vulnerability

Sven Vermeulen Tue 16 April 2013

Gentoo is one of the various distributions which supports SELinux as a Mandatory Access Control system to, amongst other things, mitigate the results of a succesfull exploit against software. So what about the recent PostgreSQL vulnerability?

When correctly configured, the PostgreSQL daemon will run in the postgresql_t domain. In SELinux-speak, a domain can be seen as a name granted to a set of permissions (what is allowed) and assigned to one or more processes. A process that "runs in domain postgresql_t" will be governed by the policy rules (what is and isn't allowed) for that domain.

The vulnerability we speak of is about creating new files or overwriting existing files, potentially corrupting the database itself (when the database files are overwritten). Creating new files is handled through the create privilege on files (and add_name on directories), writing into files is handled through the write privilege. Given certain circumstances, one could even write commands inside files that are executed by particular users on the system (btw, the link gives a great explanation on the vulnerability).

So let's look at what SELinux does and could have done.

In the current situation, as we explained, postgresql_t is the only domain we need to take into account (the PostgreSQL policy does not use separate domains for the runtime processes). Let's look at what directory labels it is allowed to write into:

$ sesearch -s postgresql_t -c dir -p add_name -SCATd
Found 11 semantic av rules:
   allow postgresql_t postgresql_log_t : dir { add_name } ; 
   allow postgresql_t var_log_t : dir { add_name } ; 
   allow postgresql_t var_lock_t : dir { add_name } ; 
   allow postgresql_t tmp_t : dir { add_name } ; 
   allow postgresql_t postgresql_tmp_t : dir { add_name } ; 
   allow postgresql_t postgresql_var_run_t : dir { add_name } ; 
   allow postgresql_t postgresql_db_t : dir { add_name } ; 
   allow postgresql_t etc_t : dir { add_name } ; 
   allow postgresql_t tmpfs_t : dir { add_name } ; 
   allow postgresql_t var_lib_t : dir { add_name } ; 
   allow postgresql_t var_run_t : dir { add_name } ;

So the PostgreSQL service is allowed to create files inside directories labeled with one of the following labels:

  • postgresql_log_t, used for PostgreSQL log files (/var/log/postgresql)
  • var_log_t, used for the generic log files (/var/log)
  • var_lock_t, used for lock files (/run/lock or /var/lock)
  • tmp_t, used for the temporary file directory (/tmp or /var/tmp)
  • postgresql_tmp_t, used for the PostgreSQL temporary files/directories
  • postgresql_var_run_t, used for the runtime information (like PID files) of PostgreSQL (/var/run/postgresql)
  • postgresql_db_t, used for the PostgreSQL database files (/var/lib/postgresql)
  • etc_t, used for the generic system configuration files (/etc/)
  • var_lib_t, used for the /var/lib data
  • var_run_t, used for the /var/run or /run data

Next to this, depending on the label of the directory, the PostgreSQL service is allowed to write into files with the following label assigned (of importance to both creating new files as well as overwriting existing ones):

$ sesearch -s postgresql_t -c file -p write -SCATd
Found 11 semantic av rules:
   allow postgresql_t postgresql_log_t : file { write } ; 
   allow postgresql_t postgresql_lock_t : file { write } ; 
   allow postgresql_t faillog_t : file { write } ; 
   allow postgresql_t lastlog_t : file { write } ; 
   allow postgresql_t postgresql_tmp_t : file { write } ; 
   allow postgresql_t hugetlbfs_t : file { write } ; 
   allow postgresql_t postgresql_var_run_t : file { write } ; 
   allow postgresql_t postgresql_db_t : file { write } ; 
   allow postgresql_t postgresql_t : file { write } ; 
   allow postgresql_t security_t : file { write } ; 
   allow postgresql_t etc_t : file { write } ;

Found 6 semantic te rules:
   type_transition postgresql_t var_log_t : file postgresql_log_t; 
   type_transition postgresql_t var_lock_t : file postgresql_lock_t; 
   type_transition postgresql_t tmp_t : file postgresql_tmp_t; 
   type_transition postgresql_t tmpfs_t : file postgresql_tmp_t; 
   type_transition postgresql_t var_lib_t : file postgresql_db_t; 
   type_transition postgresql_t var_run_t : file postgresql_var_run_t;

If an exploit creates a new file, the add_name permission on the directory is needed. If otoh the exploit is overwriting existing files, I think the only permission needed here is the write on the files (also open but all the writes have open as well in the above case).

Now accessing and being able to write files into the database file directory is expected - it is the functionality of the server, so unless we could separate domains more, this is a "hit" we need to take. Sadly though, this is also the label used for the PostgreSQL service account home directory here (not sure if this is for all distributions), making it more realistic that an attacker writes something in the home directory .profile file and hopes for the administrator to do something like su postgres -.

Next, the etc_t write privileges also worry me, not mainly because it can write there, but also because I can hardly understand why - PostgreSQL is supposed to run under its own, non-root user (luckily) so unless there are etc_t labeled directories owned by the PostgreSQL service account (or world writeable - please no, kthx). And this isn't an "inherited" permission from something - the policy currently has files_manage_etc_files(postgresql_t) set, and has been since 2005 or earlier. I'm really wondering if this is still needed.

But I digress. Given that there are no PostgreSQL-owned directories nor world-writeable ones in /etc, let's look at a few other ones.

  • security_t is used for the SELinux pseudo file system, and is used for the SEPostgreSQL support. From the looks of it, only the root Linux user has the rights to do really harmful things on this file system (and only if he too has write permissions on security_t), non-root should be limited to verifying if contexts exist or have particular rights. Still, I might investigate this further as I'm intrigued about many of the pseudo files in /sys/fs/selinux that I'm not fully sure yet what they deal with.
  • tmp_t should not be a major concern. Most (if not all) daemons and services that use temporary files have file transitions to their own type so that access to these files, even if it would be allowed by regular permissions, is still prohibited by SELinux
  • lastlog_t is also a weird one, again because it shouldn't be writeable for anyone else but root accounts; if succesfull, an attacker can overwrite the lastlog information which might be used by some as a means for debugging who was logged on when (part of forensics).

Given the information above, it is a bit sad to see that SELinux can't protect PostgreSQL users from this particular vulnerability - most of the "mitigation" (if any) is because the process runs as non-root to begin with (which is another hint at users not to think SELinux is sufficient to restrict the permissions of processes). But could it have been different?

In my opinion, yes, and I'll see if we can learn from it for the future.

First of all, we should do more policy code auditing. It might not be easy to remove policy rules generally, but we should at least try. I use a small script that enables auditing (SELinux auditing, so auditallow statements) for the entire domain, and then selectively disables auditing until I get no hits anymore. The remainder of auditallow statements warrant a closer look to see if they are still needed or not. I'll get onto that in the next few days.

Second, we might want to have service accounts use a different home directory, where they do have the necessary search privileges for, but no write privileges. Exploits that write stuff into a home directory (hoping for a su postgresql -) are then mitigated a bit.

Third, we might want to look into separating the domains according to the architecture of the service. This requires intimate knowledge of the ins and outs of PostgreSQL and might even require PostgreSQL patching, so is not something light. But if no patching is needed (such as when all process launches are done using known file executions) we could have a separate domain for the master process, server processes and perhaps even the various subfunction processes (like the WAL writer, BG writer, etc.). The Postfix service has such a more diverse (but also complex) policy. Such a subdomain structure in the policy might reduce the risk if the vulnerable process (I think this is the master process) does not need to write to database files (as this is handled by other processes), so no postgresql_db_t write privileges.

If others have ideas on how we can improve service security (for instance through SELinux policy development) or knows of other exploits related to this vulnerability that I didn't come across yet, please give a comment on it below.