为什么我的 Android 应用程序(具有 root 权限)无法访问 /dev/input?

Why can't my Android app (has root privilege) access /dev/input?

我的应用程序针对已获得root权限的Android设备,它具有root权限并需要访问目录/dev/input,但为什么它抛出opendir failed, Permission denied即使/dev/input有已经 chmod777?

我使用下面的代码获取root权限:

Process root = Runtime.getRuntime().exec("su");

并使用下面的代码更改/dev/input的权限:

Shell.runCommand("chmod 777 /dev/input");

上面两步都成功了,为什么我的app还是访问不了?查了一下,有人说APP的运行时权限与文件系统中文件的权限无关。 Android 运行时的权限系统是什么?如何让应用程序能够访问 /dev/input?

加法:

我的测试环境是Android5.1.1,主要部分代码是:

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;
}

来自 /prog/kmsg

的 SELinux 错误
<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

正如评论中指出的那样,现代 Android 除了 Linux 文件权限之外还有许多额外的防御层。其中之一是 SELinux.

即使拥有更高的权限,围绕 SELinux 的工作也是 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

与其尝试自己克服 SELinux,我建议您利用已安装的 su 应用程序所做的一切。例如,SuperSu 运行s 命令,在不受限制的 SELinux 上下文中传递给它(参见上面 Chainfire 站点的 link),基本上就好像 SELinux 没有为之而存在。这允许您通过 su 运行 宁专用二进制文件来克服 SELinux,这会为您完成肮脏的工作。

遗憾的是,很少有 public 高级 API 可用于此类纯本机二进制文件。您可以使用 Linux 内核系统调用和一些 C 库函数……仅此而已。幸运的是,如果您只想打开一堆受保护的文件,则无需在本机帮助程序二进制文件中移动大量逻辑。相反,您可以使用 "open server" 库,例如 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) {
    ...
}

免责声明:我是 linked 图书馆的作者。

"open server"的概念很简单:

  1. 普通 Android 应用创建 Linux domain socket
  2. 正常 Android 应用程序通过系统启动二进制文件 "su"
  3. 二进制文件连接到套接字
  4. 二进制文件读取应用程序写入套接字的文件名并打开它们
  5. 二进制文件通过相同的套接字将所述文件的文件描述符发送到应用程序(该技术也称为 "file descriptor passing"

只要安装的 "su" 应用程序成功克服 SELinux 并通过它为 运行 命令提供不受限制的上下文,这个巧妙的技巧就会起作用。我所知道的所有现代人都这样做。


编辑:这个答案已经写了一段时间了。最新的 Android sepolicy 格式不再被视为 "modified",它们的更改已成功上游(幽默地导致创建 又一个 向后不兼容的 sepolicy 格式)。上面 linked 的库总体上仍然可以正常工作,但它的功能已受到现代 SEAndroid 政策的进一步限制,因此您可能会对它感兴趣 new iteration。由于 SELinux 策略在 open 期间除了标准 Unix 检查外,还对每个人 read/write 强制执行额外检查,使用共享可能更明智内存和 Linux 管道仔细解决策略,而不是将原始描述符传递给调用者。