Thursday, July 7, 2016

DON’T LOG TO THE SD CARD!!!

This keeps coming up, so let me shout it from the mountain top for all the internets to hear:

“DON’T EVER EVER EVER LOG TO THE SD CARD!”

Selinux policy often contains evidence that a process can or may store logs on external storage. I commonly see groups of permissions that look something like:

allow foo fuse:dir { write search read create open add_name };

allow foo fuse:file { read write create open };

allow foo mnt_user_file:dir search;

allow foo mnt_user_file:lnk_file read;

allow foo storage_file:dir search;

allow foo storage_file:lnk_file read;

Why is logging to the sdcard dangerous?

  1. Logs contain very sensitive information e.g. websites or IP addresses visited, application data, personal data, passwords, auth tokens, memory dumps, stack dumps, register values, etc.
  2. The sdcard is not encrypted.
  3. The sdcard is not wiped on factory reset.
  4. Applications have access to the sdcard, meaning they can read (and potentially exfiltrate) any logs on the sdcard.

Logging to the sdcard is bad for users for the reasons above, but it’s not just user data at risk. When companies log to the sdcard (e.g. during platform development) their data is at risk. Third party applications may access or exfiltrate logs and all the sensitive data contained. This should be extremely concerning to device manufacturers.

Here are some safe guidelines when it comes to logging.

  1. Logs should never be persisted to disk on production (user) builds.
  2. On debug/eng builds, persisted logs should be encrypted on the data partition
  3. Only persist logs using logcat’s built in logpersist APIs.
  4. Device specific daemons and processes should only log to Android’s built in system log. Android’s built in logging mechanisms have been extensively reviewed and have safe defaults. Using these built in logging capabilities means that you will be safe by default.

Let me know if you have any questions.

Friday, May 6, 2016

SELinux kernel patches for upgrading from M

When upgrading from Android M to N, you may run into the following error:
[ 5.762658] init: SELinux: Could not load policy: Invalid argument
[ 5.768144] init: failed to load policy: Invalid argument
[ 5.773426] init: Security failure; rebooting into recovery mode...

Here are the list of backported upstream SELinux patches needed to run Android N for kernel versions 3.10, 3.14, and 3.18. Similar patches may be found or easily cherry-picked to Android 3.4 and 4.1 kernels.

$ make -j32 :)

3.10

Required:

Revert "SELinux: ss: Fix policy write for ioctl operations"

https://android-review.googlesource.com/162273

Revert "SELinux: use deletion-safe iterator to free list"

https://android-review.googlesource.com/162274

Revert "SELinux: per-command whitelisting of ioctls"

https://android-review.googlesource.com/162275

Revert "security: lsm_audit: add ioctl specific auditing"

https://android-review.googlesource.com/162276

selinux: remove unnecessary pointer reassignment

https://android-review.googlesource.com/162277

security: add ioctl specific auditing to lsm_audit

https://android-review.googlesource.com/162278

selinux: extended permissions for ioctls

https://android-review.googlesource.com/162279

Optional for backwards compatibility:

selinux: Android kernel compatibility with M userspace

https://android-review.googlesource.com/#/c/179155

Other SELinux bug fixes

selinux: do not check open perm on ftruncate call

https://android-review.googlesource.com/#/c/173321

mm: reorder can_do_mlock to fix audit denial

https://android-review.googlesource.com/140751

3.14

Required:

Revert "SELinux: ss: Fix policy write for ioctl operations"

ttps://android-review.googlesource.com/162282

Revert "SELinux: use deletion-safe iterator to free list"

https://android-review.googlesource.com/162283

Revert "SELinux: per-command whitelisting of ioctls"

https://android-review.googlesource.com/162284

Revert "security: lsm_audit: add ioctl specific auditing"

https://android-review.googlesource.com/162285

selinux: remove unnecessary pointer reassignment

https://android-review.googlesource.com/162286

security: add ioctl specific auditing to lsm_audit

https://android-review.googlesource.com/162287

selinux: extended permissions for ioctls

https://android-review.googlesource.com/162288

Optional for backwards compatibility:

selinux: Android kernel compatibility with M userspace

https://android-review.googlesource.com/#/c/179245

Other SELinux bug fixes

selinux: do not check open perm on ftruncate call

https://android-review.googlesource.com/173225

mm: reorder can_do_mlock to fix audit denial

https://android-review.googlesource.com/180251

3.18

Required:

Revert "SELinux: ss: Fix policy write for ioctl operations"

https://android-review.googlesource.com/162310

Revert "SELinux: use deletion-safe iterator to free list"

https://android-review.googlesource.com/162311

Revert "SELinux: per-command whitelisting of ioctls"

https://android-review.googlesource.com/162312

Revert "security: lsm_audit: add ioctl specific auditing"

https://android-review.googlesource.com/162313

selinux: remove unnecessary pointer reassignment

https://android-review.googlesource.com/162314

security: add ioctl specific auditing to lsm_audit

https://android-review.googlesource.com/162315

selinux: extended permissions for ioctls

https://android-review.googlesource.com/162316

UPSTREAM: selinux: fix bug in conditional rules handling

https://android-review.googlesource.com/#/c/197120/

Optional for backwards compatibility:

selinux: Android kernel compatibility with M userspace

https://android-review.googlesource.com/#/c/178861

Other SELinux bug fixes

selinux: do not check open perm on ftruncate call

https://android-review.googlesource.com/#/c/173332/

mm: reorder can_do_mlock to fix audit denial

https://android-review.googlesource.com/#/c/180272/1

Wednesday, February 3, 2016

Collecting ioctl denials for selinux ioctl whitelisting - Android Marshmallow




This requires building your own kernel and putting selinux into permissive mode via the kernel command line. These seem like reasonable requirements as anyone writing selinux policy for a device should have these capabilities. My patch is based on the Nexus-9 Marshmallow-mr1 kernel (branch android-tegra-flounder-3.10-marshmallow-mr1).*

Apply the following patch to your Android-M kernel:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index f3dbbc0..0ac7908 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -686,6 +686,9 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass,
 
   hvalue = avc_hash(ssid, tsid, tclass);
   avc_node_populate(node, ssid, tsid, tclass, avd);
+  /* hack to keep ops_node around for all ioctl capable files */
+  if (avd->allowed & FILE__IOCTL)
+   ops_node->ops.len = 1;
   rc = avc_operation_populate(node, ops_node);
   if (rc) {
    kmem_cache_free(avc_node_cachep, node);

This patch is intended to be temporary for collecting ioctls for selinux policy.

After applying the patch, building the kernel and flashing a new bootimage onto the device logcat should be spewing ioctl denials (all denials collected from a Nexus 9 AOSP m-mr1 build):

avc: denied { ioctl } for path="/dev/ptmx" dev="tmpfs" ino=5495 ioctlcmd=5431 scontext=u:r:init:s0 tcontext=u:object_r:ptmx_device:s0 tclass=chr_file permissive=1
avc: denied { ioctl } for path="/dev/ptmx" dev="tmpfs" ino=5495 ioctlcmd=5430 scontext=u:r:init:s0 tcontext=u:object_r:ptmx_device:s0 tclass=chr_file permissive=1
avc: denied { ioctl } for path="/sys/fs/selinux/null" dev="selinuxfs" ino=22 ioctlcmd=5401 scontext=u:r:fsck:s0 tcontext=u:object_r:null_device:s0 tclass=chr_file permissive=1
avc: denied { ioctl } for path="socket:[5817]" dev="sockfs" ino=5817 ioctlcmd=8913 scontext=u:r:init:s0 tcontext=u:r:init:s0 tclass=udp_socket permissive=1
avc: denied { ioctl } for path="socket:[5817]" dev="sockfs" ino=5817 ioctlcmd=8914 scontext=u:r:init:s0 tcontext=u:r:init:s0 tclass=udp_socket permissive=1
avc: denied { ioctl } for path="/dev/binder" dev="tmpfs" ino=5525 ioctlcmd=6209 scontext=u:r:healthd:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=1
avc: denied { ioctl } for path="/dev/binder" dev="tmpfs" ino=5525 ioctlcmd=6205 scontext=u:r:healthd:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=1
avc: denied { ioctl } for path="/dev/binder" dev="tmpfs" ino=5525 ioctlcmd=6205 scontext=u:r:healthd:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=1
avc: denied { ioctl } for path="/dev/binder" dev="tmpfs" ino=5525 ioctlcmd=6201 scontext=u:r:healthd:s0 tcontext=u:object_r:binder_device:s0 tclass=chr_file permissive=1

Note the "permissive=1" at the end of the denial. If "permissive=0" then the device is still in enforcing mode and the ioctls are actually being denied.

For me, the selinux denials caused so much logging overhead that the device never made it past the google splash screen. That's fine it just means that collecting denials will be a multistep process e.g.:

  1. Log ioctl denials - if using the script below, collect all denials into a single log file i.e. append new denials to the end.
  2. denial --> allow rules (sorry adding this to audit2allow is on my todo list).
  3. rebuild with the new ioctl allow rules
  4. repeat until you have a working device

I have put together a small python script to partially automate the the denial --> allow rule step. Note that this is not an audit2allow replacement. Audit2allow checks the existing policy to avoid duplicate allow rules. This just naively consolidates ioctl commands with source/target/class sets.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import itertools
import sys
import os.path

def print_usage():
    print "You must specify an input file with selinux ioctl denials"
    print "python <myscript.py> <mylogcapture>"

# combine sequential ioctls into ranges e.g. 1, 2, 3, 4 --> 1-4
def listtoranges(i):
    for a, b in itertools.groupby(enumerate(i), lambda (x, y): y - x):
        b = list(b)
        yield b[0][1], b[-1][1]

# group ioctls with source/target/class key
def group_ioctls(f):
    sourcetargetclass = {}
    for line in f:
        if ("avc" in line) and ("ioctlcmd" in line) and ("tcontext" in line) and ("tclass" in line):
            # extract source/target/class
            try:
                source = line.split('scontext=u:r:')[1]
                source = source.split(':')[0]
                target = line.split('tcontext=u:object_r:')
                if len(target) < 2:
                    target = line.split('tcontext=u:r:')
                if len(target) < 2:
                    continue
                target = target[1]
                target = target.split(':')[0]
                tclass = line.split('tclass=')[1]
                tclass = tclass.split(' ')[0]
                path = line.split('path=')[1]
                path = path.split(' ')[0][1:-1]
                cmd = line.split('ioctlcmd=')[1]
                cmd = cmd.split(' ')[0]
                # add cmd to dict
                #key = target+" "+tclass
                key = source+" "+target+" "+tclass
                if key not in sourcetargetclass.keys():
                    sourcetargetclass[key] = [cmd[-4:]]
                else:
                    if cmd[-4:] not in sourcetargetclass[key]:
                        sourcetargetclass[key].append(cmd[-4:])
            except ValueError:
                print ValueError
                print line
                print source
                print target
                print tclass
                print cmd
                continue
    return sourcetargetclass

def print_allow_rules(sourcetargetclass):
    ioctlcmds = []
    for key in sourcetargetclass.keys():
        out = ""
        source, target, tclass = key.split(' ')
        results = sorted([int(cmd,16) for cmd in sourcetargetclass[key]])
        ioctlcmds+= results
        ranges = list(listtoranges(results))
        out+= "allow " + source + " " + target + ":" + tclass + " "
        if (len(ranges) > 1 or (ranges[0][0] != ranges[0][1])):
            out += "{ "
        for range in ranges:
            if (range[0] == range[1]):
                 out += hex(range[0]) + " "
            else:
                out += hex(range[0]) + "-" + hex(range[1]) + " "
        if (len(ranges) > 1 or (ranges[0][0] != ranges[0][1])):
            out += "};"
        out = out[0:-1] + ";"
        print out

if __name__ == "__main__":
    if len(sys.argv) != 2 or not os.path.isfile(sys.argv[1]):
        print_usage()
        exit()

    fin = open(sys.argv[1], 'r')
    sourcetargetclass = group_ioctls(fin)
    print_allow_rules(sourcetargetclass)
    fin.close()

Output:
allow init null_device:chr_file 0x5401;
allow init block_device:blk_file 0x125d;
allow surfaceflinger binder_device:chr_file 0x6209;
allow vold userdata_block_device:blk_file 0x1260;
allow init init:udp_socket 0x8913;
allow shell console_device:chr_file 0x5401;
allow servicemanager binder_device:chr_file 0x6209;
allow init console_device:chr_file 0x540e;
allow ueventd null_device:chr_file 0x5401;
allow init system_block_device:blk_file 0x125d;
allow mediaserver binder_device:chr_file 0x6209;
allow netd ptmx_device:chr_file 0x5431;
allow watchdogd watchdog_device:chr_file { 0x5706-0x5707 };

Let me know if you have any questions or if I am missing anything important. Please feel free to add me as a reviewer to your policy changes.

* June 2015 I submitted the extended permissions for ioctl commands patchset to the upstream kernel allowing per-command whitelisting of ioctls. Unfortunately an earlier patchset had already been submitted to the Android kernel leading to differing versions. The primary difference is nomenclature, functionally the patches are similar although the compiled policy binaries are not compatible. To deal with this I will have another post describing the same process for the patchset accepted by the upstream kernel.

Saturday, January 30, 2016

Android: How to run your script/binary from adb in the application sandbox

This information is only relevant for Android devices running userdebug/eng builds - i.e. builds where the "adb root" command is permitted.

Adb shell runs in a different sandbox than applications - with a different set of permissions. Thus testing application code in the shell sandbox often leads to inaccurate test results. However, when creating proof-of-concept native code or scripts that will eventually run in an app, it can be faster and simpler to test via adb rather than going through the hassle of wrapping the test code in an apk.

Here is how to run an executable in the application sandbox. The executable needs:
  1. Unix UID/GID(s) in the application range as well as supplemental GIDs for additional permissions. Note: not all of the supplemental GIDs listed are granted to non-system apps.
  2. The selinux untrusted_app domain.
In one terminal:
  • $ adb root # run shell as the root user
  • $ adb shell setenforce 0 # temporarily put selinux into permissive mode
In a second terminal (where executable will be run):
  • $ adb shell # Now shelled into the device running as the root user
  • $ runcon u:r:untrusted_app:s0:c512,c768 /system/bin/sh # transition shell to the untrusted_app domain
  • $ su 12345,12345,3003 # run in UID 12345 with GIDs 12345 and 3003 (inet group for internet permission)
Back in  original terminal flip selinux back into enforcing mode:
  • $ adb shell setenforce 1

Executables launched in the second terminal will now run in the untrusted_app selinux domain with UID 12345, and GIDs 12345 and 3003. Customize and automate.

Let me know if you have any questions.