Joshua Brindle

How to Win At Security

Using SE for Android on the Samsung Galaxy S4

So, you want to secure your Galaxy S4?

After my last blog post I really wanted to get my hands on a Galaxy S4 to see what we could do with it, from an SE for Android perspective. Well, I got one yesterday and the answer is, unfortunately, not a whole lot, but not nothing, either.

First, I wanted to try turning on enforcing from an MDM app without rooting the device. Turns out the APIs for working with SE for Android aren't part of the Samsung MDM API (nor do they have the upstream MDM API present). Instead they are calls to a service that require the calling app to either be signed by the platform certificate or on the system image. Doh.

So now, time to root. I adapted the SEAdmin app from upstream SE for Android to use the Samsung APIs instead and put it on the system image. After some kajiggering I got the app to set enforcing (yay!). Fun fact: you have to set the logging level all the way up for it to work... hrm...

So, now let's set it back to permissive. Oh, there isn't an API for that; can't use JNI either, since the app isn't running as system. Well, I guess that is more secure anyway. Too bad it makes development very difficult.

Speaking of making development very difficult, fun fact #2 is that the audit logs for SELinux are turned off in the kernel so you can't get denials if you plan on doing policy development. Guess I'll have to rebuild the kernel when I get to that stage.

Time for a demo:

shell@android:/ $ getenforce
getenforce
Permissive

Here's where I hit the button to go into enforcing

shell@android:/ $ getenforce
getenforce
Could not get enforcing status:  Permission denied

So far, so good; to get out of enforcing just reboot. Fun fact #3, there is no way to keep enforcing persistent across reboots. I guess I'll have to write a service or something that runs at boot.

So, what can we do?

Okay, I found an interface that lets me load a new policy... at least I thought I did.

I updated the admin app and tried pushing policy, "success" it says... how do I test though? Pushing a random policy and going into enforcing is likely to cause all manners of breakage so I spent a couple hours writing a rule injector.

Run that baby:

joshua@QuarkZilla:~/sepolicy-inject$ sesearch -A -s shell -t selinuxfs -c file sepolicy                                    
Found 1 semantic av rules:
   allow appdomain fs_type : file getattr ; 

joshua@QuarkZilla:~/sepolicy-inject$ ./sepolicy-inject -s shell -t selinuxfs -c file -p open -P sepolicy -o sepolicy2      
joshua@QuarkZilla:~/sepolicy-inject$ ./sepolicy-inject -s shell -t selinuxfs -c file -p read -P sepolicy2 -o sepolicy

joshua@QuarkZilla:~/sepolicy-inject$ sesearch -A -s shell -t selinuxfs -c file sepolicy                              
Found 2 semantic av rules:
   allow shell selinuxfs : file { read open } ; 
   allow appdomain fs_type : file getattr ;

woohoo!

Let's test this out. Push the policy with the interface and test it out. Fail. Hrm...

shell@android:/ $ getenforce
getenforce
Could not get enforcing status:  Permission denied

Well, we don't have denials so this is tricky. Let's manually load the policy. The policy was put in /data/security by the interface, it just wasn't loaded:

root@android:/ # setprop selinux.reload_policy 1

How about now?

shell@android:/ $ getenforce
getenforce
Enforcing

Success. Well, at least I know my tool works :)

There are other interfaces to push down other policy files, such as seapp_contexts (fun fact #4: that interface actually misnames the target file as "seapps_context" ... sigh) but since they aren't reloaded there isn't much value there. Libselinux will read a properly named seapp_contexts from /data/security at boot time so you can put one there manually. mac_permissions.xml does not get read from /data/security, so adding signatures won't work there. Luckily that file is on /system which we are already committed to modifying.

Correction: mac_permissions.xml does get read from /data/security at boot time. MMAC is not in enforcing by default but can be put into enforcing mode:

adb shell su -c "setprop persist.mmac.enforce 1"
adb reboot

Summary

Here's the bottom line:

The Good:

  1. SE for Android is present
  2. SE for Android can be turned to enforcing and the phone doesn't catch on fire
  3. There are APIs for updating policies and setting enforcing

The Bad:

  1. Enforcing not on by default
  2. APIs don't actually work
  3. Setting enforcing does not persist across reboots
  4. No denials
  5. No way to set back to permissive programatically
  6. Strange side effects like requiring max log level
  7. Root required to do anything with SE for Android
  8. No way to get or set SELinux booleans

The Ugly:

  1. These APIs are actually not public, meaning they can go away or change at any point
  2. Without vulnerabilities available users won't be able to secure their devices...
  3. Real SE for Android development on the GS4 means rerolling the kernel and boot image
  4. If the management APIs are any indication the SE for Android support is still half-baked on the GS4
  5. SE for Android isn't actually a supported feature on standard GS4's, technically it is a Knox feature, and we don't have Knox yet