My app aims the rooted Android devices, it has the root privilege and needs accessing the directory /dev/input
, but why does it throw opendir failed, Permission denied
even /dev/input
has already been chmod
to 777
?
I use the code below to get the root privilege:
Process root = Runtime.getRuntime().exec("su");
And use the code below to change the permissions of /dev/input
:
Shell.runCommand("chmod 777 /dev/input");
Both the two steps above are successful, but why can't it be accessed by my app still? From the searching, someone says the runtime permissions of an app is nothing to do with the permissions of the file in file system. What's the permissions system of Android runtime? How can I make the app be able to access /dev/input
?
Addition:
My test environment is Android 5.1.1, main part of the code is:
jint Java_com_foo_funnyapp_Native_scanInputDevicesJNI(JNIEnv* env, jclass clazz)
{
const char *dirname = "/dev/input";
DIR *dir;
dir = opendir(dirname); // opendir failed, Permission denied
if(dir == NULL)
return -1;
......
return 0;
}
SELinux error from /prog/kmsg
<36>[19700411_05:32:43.957165]@0 type=1400 audit(8631163.939:1105): avc: denied { write } for pid=15706 comm="app_process64_o" name="system@framework@boot.art" dev="mmcblk0p43" ino=442379 scontext=u:r:shell:s0 tcontext=u:object_r:dalvikcache_data_file:s0 tclass=file permissive=0
<11>[19700411_05:32:44.118202]@0 init: untracked pid 15674 exited with status 0
<11>[19700411_05:32:44.202288]@0 init: untracked pid 15704 exited with status 224
<36>[19700411_05:32:44.225334]@0 type=1400 audit(8631164.209:1106): avc: denied { read } for pid=15734 comm="Thread-111" name="input" dev="tmpfs" ino=12525 scontext=u:r:untrusted_app:s0 tcontext=u:object_r:input_device:s0 tclass=dir permissive=0
<36>[19700411_05:32:44.332135]@0 type=1400 audit(8631164.319:1107): avc: denied { write } for pid=15742 comm="app_process64_o" name="system@framework@boot.art" dev="mmcblk0p43" ino=442379 scontext=u:r:shell:s0 tcontext=u:object_r:dalvikcache_data_file:s0 tclass=file permissive=0
As was pointed out in comments, modern Android has many additional defensive layers besides Linux file permissions. One of them is SELinux.
Even with elevated privileges, working around SELinux is rather complex — it is designed specifically to prevent that. All Android SELinux settings are stored in a single file of modified sepolicy format. That file is a part of read-only system image, and patching it basically equals to rooting a device. Pretty much only people working on that are developers of Superuser apps, such as author of SuperSu or this one.
Instead of trying to overcome SELinux yourself, I recommend you to leverage whatever was already done by installed su app. For example, SuperSu runs commands, passed to it, in unrestricted SELinux context (see link to Chainfire's site above), essentially as if SELinux didn't exist for it. This allows you to overcome SELinux by running specialized binaries via su, which do the dirty job for you.
Sadly, there are very few public high-level APIs, available to such pure native binaries. You can use Linux kernel syscalls and some C library functions... and that is it. Fortunately, if all you want is opening a bunch of protected files, there is no need to move lots of logic in native helper binary. Instead you can use an "open server" library, such as this one:
Context context = ...
try (FileDescriptorFactory factory = FileDescriptorFactory.create(context);
ParcelFileDescriptor fd = factory.open("/dev/input", 2))
{
// the file descriptor is yours, as if you have gotten it by
// calling ParcelFileDescriptor#open
// You can use it from Java or pass to native code to read/write/ioctl on it
...
} catch (FactoryBrokenException oups) {
// most likely the root access being denied
...
} catch (IOException ioerr) {
...
}
Disclaimer: I am the author of linked library.
The concept of "open server" is pretty simple:
- Normal Android app creates a Linux domain socket
- Normal Android app launches binary via system "su"
- The binary connects to the socket
- The binary reads names of files written by app to the socket and opens them
- The binary sends file descriptors of said files to app via the same socket (the technique also known as "file descriptor passing")
This neat trick is going to work as long as installed "su" app successfully overcomes SELinux and provides unrestricted context to commands being run via it. All modern ones I know about do.
EDIT: This answer has been written a while ago. The latest Android sepolicy format is no longer considered "modified", their changes has been successfully upstreamed (humorously leading to creation of yet another backward-incompatible sepolicy format). The library, linked above, still works fine in general, but it's functioning has been further restricted by modern SEAndroid policies, so you may be interested in it's new iteration. Due to the fact, that SELinux policies enforce additional checks on each individual read
/write
in addition to standard Unix checks during open
, it might be wiser to use shared memory and Linux pipes to carefully work around the policy, rather then passing the original descriptors to caller.