3 ways to detect the SELinux status in Android natively

Are you curious about how to detect the SELinux status of an Android device using native code (C) ?

Sat 23 May 2020
  
er

In this article we will see 3 ways on how to detect the SELinux status of an Android device. This is relatively trivial to be done at the Java layer so we describe how to do it on the C layer. The methods we will present do not have the same efficiency and neither the same resistance against techniques to spoof the SELinux status, so be sure to understand the background of each one and its limitations.


A list of the methods we will see is presented below:

  1. Identify the build.selinux Android property
  2. Identify the boot.selinux Android property
  3. Read the enforce file

In each method the code used will be shown along with an example. The application shown in this article was used to run our tests and take our screenshots. It is a very simple application which calls from JNI a random number to be shown upon the press of a button.

After presenting the methods a small efficiency test is shown along with an explanation of the results.

So without further delay lets start.


Android Build properties

For the first two methods since they share a lot in common, we are going to present them together.

But first, what are these android build properties?

It is basically a file that holds information about the current build and other system properties in string key-value pairs. Not all values found in one device are guaranteed to exist in another device!


Since our two first methods are based on detecting a property like that, it is a good idea to make a function that will return the properties we ask for. We dont need to go far for this as Android has our backs. If we check the libc source code of Android we can see the file system_properties.c which has a very interesting function found here, the int __system_property_get(). This function allows to search for the build properties and return the value and its length. The following snippet makes use of this function. Lets first see it and then comment on it.

/*
 * Checking Android Properties against a "bad" value and return
 * 0 = false
 * 1 = true
 * 2 = property not found
 */
int checkProperty(const char *key, const char *value2check) {
    if (value2check == NULL || key == NULL) return false;
    char value[20];
    int length = __system_property_get(key, value);
    int result = 0;
    if (length == 0){
        // Then we know that the property was NOT found
        result = 2;
    }
    if (length > 0) {
        if (strlen(value) >= strlen(value2check) && strstr(value, value2check) != NULL) {
            result = 1;
        }
    }
    return result;
}


In line 10 the __system_property_get function searches for an Android property based on the key and returns it length. Naturally if the property was not found the length will be zero, while if it was found and it has some value the length will have a different value. The value parameter of the function holds the actual value of the property, again if the property was not found, then the value is empty. The final interesting point is on line 18 where we have checked that the length is not zero, and thus we know that something was found and now we are checking if the value returned contains a value of our interest.

We will use this function now to attempt to retrieve the status of SELinux based on two Android properties.

TIP: You can use the checkProperty function to test any of the Android Properties!

ro.build.selinux

This property is the least efficient method in our list and this is because it is not present regularly in builds. This means that there are many cases where it does not exist in the build and therefore it yields no result. In this property we expect a value of 1 if the build is with SeLinux enabled and a value of 0 if not. In order to check for it using the function we made above we have the following:

int robuild = checkProperty("ro.build.selinux", "0");


ro.boot.selinux

The ro.boot.selinux property is found in many cases in the Android build but still not always. It is therefore more efficient from the previous but still we cannot trust it that it will yield a result. It is slightly different than the ro.build.selinux as we dont expect a 0 or 1 result here but rather the actual status of SELinux. This means that this property holds one of the three values the SELinux status will be into, these are enforcing, permissive or disabled. So in order to check for it we have the following which checks if the value of the property is permissive or disabled.

int roboot = checkProperty("ro.boot.selinux", "permissive");



Reading the enforce file

The most efficient way in our list to detect SELinux status natively is this one. The concept is very simple, we directly attempt to read the enforce file.

But first, what is this enforce file?

This file or to be more precise, this pseudo-file, is living in the path /sys/fs/selinux/ and its purpose is to return the current status of SELinux. It returns the status 0 or 1 depending on whether the SELinux is in permissive or enforcing mode, respectively. Do note that if SELinux is disabled then the module is not loaded into the kernel and thus the enforce file does not exist!

Only a process that is already root and is allowed the setenforce permission in SELinux policy can write to /sys/fs/selinux/enforce! It is possible to overwrite the value the enforce file holds with anything, though this would not affect how the kernel reads the value as it checks for zero or non-zero. It should be kept in mind that some userspace code might be comparing with 1, thus leading to false deductions about the state of SELinux!

Now that the purpose of the enforce file is known, it must be clear why we want to read it. Before proceeding and read the file there are some things that should be considered. The most important one is that different versions of Android have different versions of SELinux and thus different policies are enforced. More about these differences can be found on the official page about SELinux from Android.

So lets first see the code and then comment on it.

/*
 * Checking Android status based on the enforce file
 * 0 = Enforcing
 * 1 = It is not enforcing
 * 2 = not able to open the file
 */
int selinuxStatusChecker() {
    int result = 0;
    FILE* file = fopen("/sys/fs/selinux/enforce", "r"); // on systems that have sysfs mounted, the mount point is /sys/fs/selinux
    char* line = (char*) calloc(50, sizeof(char));
    if (file == NULL) {
        __android_log_print(ANDROID_LOG_VERBOSE, "JNI simple app", " ---- Unable to read the enforce file");
        result = 2;
        return result;
    }
    while(fgets(line, 50, file)) {
        if (strstr(line, "0")) {
            __android_log_print(ANDROID_LOG_VERBOSE, "JNI simple app", " ---- NOT ENFORCING");
            result = 1;
        } else {
            __android_log_print(ANDROID_LOG_VERBOSE, "JNI simple app", " ---- ENFORCING");
            result = 0;
        }
    }
    if (line) {free(line);}
    fclose(file);

    return result;
}


In line 9 we attempt to read the /sys/fs/selinux/enforce. If this fails then things are pretty simple, the value 2 is returned. Otherwise, in line 17 there is a comparison of the content of the file with "0". Well as we mentioned if the value is zero then the SELinux is not in enforcing mode and thus the value 1 is being returned meaning that the check caught something suspicious. If it is anything else than the zero then the mode is permissive.

In the case that the SELinux is disabled the code above would return the value of "2". This is something you should keep in mind if you deploy to a large number of devices as you probably should differentiate between when you expect to find the enforce file and you are not permitted to read it rather when you don't expect to find it at all(some really old Android versions).


Test the efficiency

Before closing lets make a small test on 4 devices on emulators and one physical device to see what the results will be. We remind that 1 means that something suspicious was found, 0 that it seems ok while 2 that the method failed to find anything.

Device Android Version Environment ro.build.selinux ro.boot.selinux enforce Actual Status
Google Pixel 3 9 Genymotion 2 1 1 Permissive
Google Pixel 3 7 Genymotion 2 2 2 Disabled
Pixel XL 8 AVD 2 2 0 Enforcing
Pixel XL 7 AVD 2 2 0 Enforcing
Xiaomi Redmi 5 8 Physical Device 2 2 2 Enforcing


Most of the results are kind of expected, though lets comment on all of them just to make sure we are on the same page here.

The ro.build.selinux failed to find anything in all cases, this means that none of the builds was built with this property. We have mentioned when explaining this property here, that it is the least efficient method in our list. Now we verify that and we know that there are a few builts that do include this property.

Now the ro.boot.selinux is slightly better as we see that it was found one of the Pixel 3 tests. In the Android 9 device the test was successful, returning that the mode is permissive.

Now for the most interesting method and efficient method in our list. For the Pixel 3 with Android 9 it detects successfully that it is in permissive mode and for the Android 7 version since the SELinux is disable it simply does not find the enforce file at all! For the two Pixel XL devices from AVD, it again successfully detects the modes to be enforcing - remember that 0 means nothing suspicious was found. As mentioned above the cases where 2 is returned, it should be investigated, and our tests here are a perfect example for it, as we see that we get the value 2 for both cases on Pixel 3 (7) and for the physical device (Xiaomi). In the first case the SELinux is disabled and thus the file does not exist, while in the second case the policy which is enforced does not allow the enforce file to be read!

Here is the actual error SELinux is informing us the access to read the enforce file has been denied.

W/m.erev0s.jniapp: type=1400 audit(0.0:14762): avc: denied { read } for name="enforce" dev="selinuxfs" ino=4 scontext=u:r:untrusted_app:s0:c512,c768 tcontext=u:object_r:selinuxfs:s0 tclass=file permissive=0


Conclusion

In this article we have seen three methods that can be used to detect the SELinux status. The efficiency of each method is different and in order to have a complete picture of what happens in the device other parameters from the device environment should be taken under consideration as well. Hope you liked the article and as usual if you have some suggestion to improve it or some question please feel free to send a message.