Rewrite root input capturing to be compatible with Android 6.0 (and be much more secure in general)

This commit is contained in:
Cameron Gutman
2015-12-19 23:55:34 -08:00
parent 05f8fa21de
commit d6a8db97d8
8 changed files with 454 additions and 564 deletions

View File

@@ -10,4 +10,23 @@ LOCAL_MODULE := evdev_reader
LOCAL_SRC_FILES := evdev_reader.c
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
# This next portion of the makefile is mostly copied from build-executable.mk but
# creates a binary with the libXXX.so form so the APK will install and drop
# the binary correctly.
LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE
LOCAL_MAKEFILE := $(local-makefile)
$(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT))
$(call check-LOCAL_MODULE,$(LOCAL_MAKEFILE))
$(call check-LOCAL_MODULE_FILENAME)
# we are building target objects
my := TARGET_
$(call handle-module-filename,lib,$(TARGET_SONAME_EXTENSION))
$(call handle-module-built)
LOCAL_MODULE_CLASS := EXECUTABLE
include $(BUILD_SYSTEM)/build-module.mk

View File

@@ -1,5 +1,6 @@
#include <stdlib.h>
#include <jni.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -8,111 +9,318 @@
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <dirent.h>
#include <pthread.h>
#include <android/log.h>
JNIEXPORT jint JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_open(JNIEnv *env, jobject this, jstring absolutePath) {
const char *path;
path = (*env)->GetStringUTFChars(env, absolutePath, NULL);
return open(path, O_RDWR);
}
#define REL_X 0x00
#define REL_Y 0x01
#define KEY_Q 16
#define BTN_LEFT 0x110
#define BTN_GAMEPAD 0x130
JNIEXPORT jboolean JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_grab(JNIEnv *env, jobject this, jint fd) {
return ioctl(fd, EVIOCGRAB, 1) == 0;
}
struct DeviceEntry {
struct DeviceEntry *next;
pthread_t thread;
int fd;
char devName[128];
};
JNIEXPORT jboolean JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_ungrab(JNIEnv *env, jobject this, jint fd) {
return ioctl(fd, EVIOCGRAB, 0) == 0;
}
static struct DeviceEntry *DeviceListHead;
static int grabbing = 1;
static pthread_mutex_t DeviceListLock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t StdoutLock = PTHREAD_MUTEX_INITIALIZER;
// has*() and friends are based on Android's EventHub.cpp
// This is a small executable that runs in a root shell. It reads input
// devices and writes the evdev output packets to stdout. This allows
// Moonlight to read input devices without having to muck with changing
// device permissions or modifying SELinux policy (which is prevented in
// Marshmallow anyway).
#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
JNIEXPORT jboolean JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_hasRelAxis(JNIEnv *env, jobject this, jint fd, jshort axis) {
static int hasRelAxis(int fd, short axis) {
unsigned char relBitmask[(REL_MAX + 1) / 8];
ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relBitmask)), relBitmask);
return test_bit(axis, relBitmask);
}
JNIEXPORT jboolean JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_hasAbsAxis(JNIEnv *env, jobject this, jint fd, jshort axis) {
unsigned char absBitmask[(ABS_MAX + 1) / 8];
ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absBitmask)), absBitmask);
return test_bit(axis, absBitmask);
}
JNIEXPORT jboolean JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_hasKey(JNIEnv *env, jobject this, jint fd, jshort key) {
static int hasKey(int fd, short key) {
unsigned char keyBitmask[(KEY_MAX + 1) / 8];
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keyBitmask)), keyBitmask);
return test_bit(key, keyBitmask);
}
JNIEXPORT jint JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_read(JNIEnv *env, jobject this, jint fd, jbyteArray buffer) {
jint ret;
jbyte *data;
int pollres;
static void outputEvdevData(char *data, int dataSize) {
// We need to hold our StdoutLock to avoid garbling
// input data when multiple threads try to write at once.
pthread_mutex_lock(&StdoutLock);
fwrite(&dataSize, sizeof(dataSize), 1, stdout);
fwrite(data, dataSize, 1, stdout);
fflush(stdout);
pthread_mutex_unlock(&StdoutLock);
}
void* pollThreadFunc(void* context) {
struct DeviceEntry *device = context;
struct pollfd pollinfo;
data = (*env)->GetByteArrayElements(env, buffer, NULL);
if (data == NULL) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"Failed to get byte array");
int pollres, ret;
char data[64];
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Polling /dev/input/%s", device->devName);
if (grabbing) {
// Exclusively grab the input device (required to make the Android cursor disappear)
if (ioctl(device->fd, EVIOCGRAB, 1) < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"EVIOCGRAB failed for %s: %d", device->devName, errno);
goto cleanup;
}
}
for (;;) {
do {
// Unwait every 250 ms to return to caller if the fd is closed
pollinfo.fd = device->fd;
pollinfo.events = POLLIN;
pollinfo.revents = 0;
pollres = poll(&pollinfo, 1, 250);
}
while (pollres == 0);
if (pollres > 0 && (pollinfo.revents & POLLIN)) {
// We'll have data available now
ret = read(device->fd, data, sizeof(struct input_event));
if (ret < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"read() failed: %d", errno);
goto cleanup;
}
else if (ret == 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"read() graceful EOF");
goto cleanup;
}
else if (grabbing) {
// Write out the data to our client
outputEvdevData(data, ret);
}
}
else {
if (pollres < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"poll() failed: %d", errno);
}
else {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"Unexpected revents: %d", pollinfo.revents);
}
// Terminate this thread
goto cleanup;
}
}
cleanup:
__android_log_print(ANDROID_LOG_INFO, "EvdevReader", "Closing /dev/input/%s", device->devName);
// Remove the context from the linked list
{
struct DeviceEntry *lastEntry;
if (DeviceListHead == device) {
DeviceListHead = device->next;
}
else {
lastEntry = DeviceListHead;
while (lastEntry->next != NULL) {
if (lastEntry->next == device) {
lastEntry->next = device->next;
break;
}
lastEntry = lastEntry->next;
}
}
}
// Free the context
ioctl(device->fd, EVIOCGRAB, 0);
close(device->fd);
free(device);
return NULL;
}
static int precheckDeviceForPolling(int fd) {
int isMouse;
int isKeyboard;
int isGamepad;
// This is the same check that Android does in EventHub.cpp
isMouse = hasRelAxis(fd, REL_X) &&
hasRelAxis(fd, REL_Y) &&
hasKey(fd, BTN_LEFT);
// This is the same check that Android does in EventHub.cpp
isKeyboard = hasKey(fd, KEY_Q);
isGamepad = hasKey(fd, BTN_GAMEPAD);
// We only handle keyboards and mice that aren't gamepads
return (isMouse || isKeyboard) && !isGamepad;
}
static void startPollForDevice(char* deviceName) {
struct DeviceEntry *currentEntry;
char fullPath[256];
int fd;
// Lock the device list
pthread_mutex_lock(&DeviceListLock);
// Check if the device is already being polled
currentEntry = DeviceListHead;
while (currentEntry != NULL) {
if (strcmp(currentEntry->devName, deviceName) == 0) {
// Already polling this device
goto unlock;
}
currentEntry = currentEntry->next;
}
// Open the device
sprintf(fullPath, "/dev/input/%s", deviceName);
fd = open(fullPath, O_RDWR);
if (fd < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Couldn't open %s: %d", fullPath, errno);
goto unlock;
}
// Allocate a context
currentEntry = malloc(sizeof(*currentEntry));
if (currentEntry == NULL) {
close(fd);
goto unlock;
}
// Populate context
currentEntry->fd = fd;
strcpy(currentEntry->devName, deviceName);
// Check if we support polling this device
if (!precheckDeviceForPolling(fd)) {
// Nope, get out
free(currentEntry);
close(fd);
goto unlock;
}
// Start the polling thread
if (pthread_create(&currentEntry->thread, NULL, pollThreadFunc, currentEntry) != 0) {
free(currentEntry);
close(fd);
goto unlock;
}
// Queue this onto the device list
currentEntry->next = DeviceListHead;
DeviceListHead = currentEntry;
unlock:
// Unlock and return
pthread_mutex_unlock(&DeviceListLock);
}
static int enumerateDevices(void) {
DIR *inputDir;
struct dirent *dirEnt;
inputDir = opendir("/dev/input");
if (!inputDir) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Couldn't open /dev/input: %d", errno);
return -1;
}
do
{
// Unwait every 250 ms to return to caller if the fd is closed
pollinfo.fd = fd;
pollinfo.events = POLLIN;
pollinfo.revents = 0;
pollres = poll(&pollinfo, 1, 250);
}
while (pollres == 0);
if (pollres > 0 && (pollinfo.revents & POLLIN)) {
// We'll have data available now
ret = read(fd, data, sizeof(struct input_event));
if (ret < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"read() failed: %d", errno);
// Start polling each device in /dev/input
while ((dirEnt = readdir(inputDir)) != NULL) {
if (strcmp(dirEnt->d_name, ".") == 0 || strcmp(dirEnt->d_name, "..") == 0) {
// Skip these virtual directories
continue;
}
}
else {
// There must have been a failure
ret = -1;
if (pollres < 0) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"poll() failed: %d", errno);
}
else {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader",
"Unexpected revents: %d", pollinfo.revents);
}
}
(*env)->ReleaseByteArrayElements(env, buffer, data, 0);
return ret;
startPollForDevice(dirEnt->d_name);
}
closedir(inputDir);
return 0;
}
JNIEXPORT jint JNICALL
Java_com_limelight_binding_input_evdev_EvdevReader_close(JNIEnv *env, jobject this, jint fd) {
return close(fd);
}
#define UNGRAB_REQ 1
#define REGRAB_REQ 2
int main(int argc, char* argv[]) {
int ret;
int pollres;
struct pollfd pollinfo;
// Perform initial enumeration
ret = enumerateDevices();
if (ret < 0) {
return ret;
}
// Wait for requests from the client
for (;;) {
unsigned char requestId;
do {
// Every second we poll again for new devices if
// we haven't received any new events
pollinfo.fd = STDIN_FILENO;
pollinfo.events = POLLIN;
pollinfo.revents = 0;
pollres = poll(&pollinfo, 1, 1000);
if (pollres == 0) {
// Timeout, re-enumerate devices
enumerateDevices();
}
}
while (pollres == 0);
ret = fread(&requestId, sizeof(requestId), 1, stdin);
if (ret < sizeof(requestId)) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Short read on input");
return errno;
}
if (requestId != UNGRAB_REQ && requestId != REGRAB_REQ) {
__android_log_print(ANDROID_LOG_ERROR, "EvdevReader", "Unknown request");
return requestId;
}
{
struct DeviceEntry *currentEntry;
pthread_mutex_lock(&DeviceListLock);
// Update state for future devices
grabbing = (requestId == REGRAB_REQ);
// Carry out the requested action on each device
currentEntry = DeviceListHead;
while (currentEntry != NULL) {
ioctl(currentEntry->fd, EVIOCGRAB, grabbing);
currentEntry = currentEntry->next;
}
pthread_mutex_unlock(&DeviceListLock);
}
}
}