TL; DR: This is a question about the final step, in a portable, developer-oriented rooting process, that works across all Android machines. It is not based on any exploit - it is something that we are legally and morally allowed to do, as developers, to our own machines. If I get an answer and manage to chroot inside my Debian, I will make a concise blog post detailing all the steps of this process for all the fellow developers that want root access to their tablets - and don't want to trust dubious-origin "one-click-roots" that do God-knows-what to their machines (botnet members?)... The only dependencies will be the machine's kernel sources (which the manufacturer is legally obligated to provide) and the boot partition image (boot.img), which is 99% of the times inside the manufacturer-provided Over-the-air updates, or individually downloadable as a standalone flash-able image.

So, a week passed where I spent all my free time on my new Android tablet.

And I have almost completely succeeded - in creating a portable, developer-oriented process, for achieving root in my Android 5.0.2 tablet.

But there's one thing missing yet - I can't do a chroot (which I need to run my debootstrap-ed Debian!)

What I did so far

  1. First, I did a minor patch in my tablet's (manufacturer-provided) kernel sources, and then compiled my own kernel - where I disabled the checks for changing SELINUX enforcing mode. Specifically...

In security/selinux/selinuxfs.c:

if (new_value != selinux_enforcing) {
    /* Commented out by ttsiodras.
    length = task_has_security(current, SECURITY__SETENFORCE);
    if (length)
        goto out;
    audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS,
        "enforcing=%d old_enforcing=%d auid=%u ses=%u",
        new_value, selinux_enforcing,
  1. I then changed my initrd image's /default.prop to contain: ro.secure=0 and ro.debuggable=1

  2. Since my manufacturer's initrd.img was missing it, I also compiled su.c from https://android.googlesource.com/platform/system/extras/+/master/su/ and placed the resulting binary under /sbin, making sure it is set to SUID root (chmod 04755 /sbin/su).

After that, I packaged the new kernel and the new initrd, as I explained in Episode 2 of my previous post - and booted from my own image:

adb reboot boot-loader ; fastboot boot myboot.img

So, are you root?

Yes, it initially appeared to be successful:

$ adb shell

shell@K01E_2:/ $ id

uid=2000(shell) gid=2000(shell) groups=1004(input),1007(log),1011(adb),

shell@K01E_2:/ $ ls -l /sbin/su /sbin/_su
-rwxr-xr-x root     root          131 2015-10-03 10:44 su
-rwsr-xr-x root     root         9420 2015-10-03 01:31 _su

(the _su is the binary I compiled, set to SUID root, and "su" is
 a script I wrote to tell "su" to add me to all these groups...)

shell@K01E_2:/ $ cat /sbin/su

export PATH=/system/bin:$PATH
exec /sbin/_su 0,0,1000,1028,2000,2001,1004,1007,1011,1015,\

And I have now achieved root:

shell@K01E_2:/ $ su

root@K01E_2:/ # id

uid=0(root) gid=0(root) 

I am 100% sure I am root - not only because id says so, but because I can also do things that normal processes definitely can't:

root@K01E_2:/ # ls -l /dev/block/platform/msm_sdcc.1/by-name/boot
lrwxrwxrwx root root 2015-10-03 10:47 boot -> /dev/block/mmcblk0p16

root@K01E_2:/ # dd if=/dev/block/mmcblk0p16 of=/dev/null bs=1M
16+0 records in
16+0 records out
16777216 bytes transferred in 0.569 secs (29485441 bytes/sec)

Lo and behold - I can finally read raw partitions out of my tablet!

And SELinux is indeed in "down, dog" mode:

root@K01E_2:/ # getenforce                                                     

But... there are still things I can't do:

root@K01E_2:/ # mkdir /my_mnt

root@K01E_2:/ # mount -t ext4 /dev/block/mmcblk1p2 /my_mnt
mount: Operation not permitted

That is, I can't mount my EXT4-fs formatted 2nd partition of my external SD card.

I also can't chroot to my lovely debootstrap-ed Debian:

root@K01E_2:/ # chroot /data/debian/ /bin/bash                             
chroot() fail
Operation not permitted

Is it because of SELinux?

I don't know - I am new (very new - one week old) to SELinux. I thought that when you put it to sleep (getenforce reporting "Permissive") it no longer interferes...

Apparently, I was wrong. Down the rabbit hole we go again...

Could it be because of my process context?

Remember that id returned... "uid=0(root) gid=0(root)... context=u:r:shell:s0"

Can I change that context? Being root and all, can I move away from shell? And if so, move to what?

The answer to the first question is runcon:

shell@K01E_2:/ $ runcon u:r:debuggerd:s0 /sbin/su

root@K01E_2:/ # id
uid=0(root) gid=0(root)... context=u:r:debuggerd:s0

Good. But what context will allow me to mount and chroot?

Reading some more about SELinux, back in my main machine, I parse the /sepolicy file on the root of the initrd.img:

linuxbox$ $ sesearch -A sepolicy | grep chroot
allow init_shell init_shell : capability { chown sys_chroot ...
allow init init : capability { chown dac_read_search sys_chroot ...
allow kernel kernel : capability { chown dac_override sys_chroot ... 
allow asus-dbug-d asus-dbug-d : capability { chown sys_chroot ...

OK, a number of possibilities! Especially that kernel one seems promising:

shell@K01E_2:/ $ runcon u:r:kernel:s0 /sbin/su

root@K01E_2:/ # id
uid=0(root) gid=0(root)... context=u:r:kernel:s0

root@K01E_2:/ # chroot /data/debian/ /bin/bash                             
chroot() fail
Operation not permitted


Who the heck is blocking me from chrooting?

Any advice most welcome...


Who the heck is blocking me from chrooting?

It wasn't SELinux - that was a wild goose chase (getenforce returning "Permissive" means that SELinux is indeed no longer in the picture).

The culprit - after adding quite a number of printk in the kernel's source to trace the failures of both chroot and mount - turned out to be capabilities. More specifically, Android's "capability bounding set" - you can read all about them via your man (man 7 capabilities) and I confess I have never before bothered looking into them - my everyday UNIX tasks depended on them and I had no idea... try this in your linux box to see for yourself:

$ getfattr -d -m - /sbin/ping
getfattr: Removing leading '/' from absolute path names
# file: sbin/ping

See? Ping is no longer SUID root - it uses information stored in the filesystem's extended attributes to know that it has access to the raw sockets layer (so it can do it's ICMP thing - at the IP level that is).

Anyway, I digress - the surgery point in my kernel where I stopped the "drop my capabilities set" - in an arguably disgusting, "let them all march in" manner - was this (security/commoncap.c):

static long cap_prctl_drop(struct cred *new, unsigned long cap)
    if (!capable(CAP_SETPCAP))
        return -EPERM;
    if (!cap_valid(cap))
        return -EINVAL;

    // ttsiodras: come in, everyone, the water's fine!
    //cap_lower(new->cap_bset, cap);
    return 0;

This means that capabilities are NEVER dropped - a very secure configuration, indeed :-)

$ adb shell

shell@K01E_2:/ $ su

root@K01E_2:/ # chroot /data/debian/ /bin/bash

root@localhost:/# export PATH=/bin:/sbin:/usr/bin:/usr/sbin:\

root@localhost:/# cat /etc/issue
Debian GNU/Linux 8 \n \l

Hello, my sweet Debian :-)

Oh, and "Root checker" works, too - I snipped "su.c", so everyone in my tablet can become root:

int main(int argc, char **argv)
  struct passwd *pw;
  uid_t uid, myuid;
  gid_t gid, gids[50];

  /* Until we have something better, only root and shell can use su. */
  myuid = getuid();
  // ttsiodras - Oh no, you don't :-)
  //if (myuid != AID_ROOT && myuid != AID_SHELL) {
  //    fprintf(stderr,"su: uid %d not allowed to su\n", myuid);
  //    return 1;

Now that it works, I must make it work properly - i.e. allow only my termux and Terminal Emulator users to invoke su and chroot, and not let everyone and their grandmother in :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.