Skip to content

Commit

Permalink
Implement driver_register() interceptor to watch drivers registration
Browse files Browse the repository at this point in the history
Some functions in the kernel, even if compiled-in, are only
usable when their respective drivers were loaded. Normally
they're loaded very early in the boot process. However, when
this LKM is loaded very early (i.e. as I/O scheduler) some
drivers will not be loaded yet. This new subsystem allows
to easily wait for the driver to load and execute code
right when the driver loads.
  • Loading branch information
ttg-public committed Jan 1, 1970
1 parent 4427b74 commit 3f90ee3
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 3 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Expand Up @@ -768,4 +768,4 @@ add_definitions(-DCONFIG_SERIAL_8250_NR_UARTS=4)
add_definitions(-DCONFIG_SYNO_BOOT_SATA_DOM) # only some platforms support that, notably 3615xs while 918+ doesn't

add_executable(redpill
redpill_main.c redpill_main.h internal/call_protected.c internal/call_protected.h common.h config/cmdline_delegate.c config/cmdline_delegate.h shim/boot_device_shim.c shim/boot_device_shim.h internal/stealth.c internal/stealth.h config/runtime_config.c config/runtime_config.h test.c shim/bios_shim.c shim/bios_shim.h internal/override_symbol.c internal/override_symbol.h shim/bios/bios_shims_collection.c shim/bios/bios_shims_collection.h shim/block_fw_update_shim.c shim/block_fw_update_shim.h internal/intercept_execve.c internal/intercept_execve.h shim/disable_exectutables.c shim/disable_exectutables.h debug/debug_execve.c debug/debug_execve.h compat/string_compat.c compat/string_compat.h internal/stealth/sanitize_cmdline.c internal/stealth/sanitize_cmdline.h internal/virtual_pci.c internal/virtual_pci.h shim/pci_shim.c shim/pci_shim.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.h internal/uart/virtual_uart.c internal/uart/virtual_uart.h shim/uart_fixer.c shim/uart_fixer.h config/uart_defs.h debug/debug_vuart.h internal/uart/vuart_virtual_irq.c internal/uart/vuart_virtual_irq.h internal/uart/vuart_internal.h shim/boot_dev/usb_boot_shim.c shim/boot_dev/usb_boot_shim.h shim/boot_dev/sata_boot_shim.c shim/boot_dev/sata_boot_shim.h internal/uart/uart_swapper.c internal/uart/uart_swapper.h shim/pmu_shim.c shim/pmu_shim.h)
redpill_main.c redpill_main.h internal/call_protected.c internal/call_protected.h common.h config/cmdline_delegate.c config/cmdline_delegate.h shim/boot_device_shim.c shim/boot_device_shim.h internal/stealth.c internal/stealth.h config/runtime_config.c config/runtime_config.h test.c shim/bios_shim.c shim/bios_shim.h internal/override_symbol.c internal/override_symbol.h shim/bios/bios_shims_collection.c shim/bios/bios_shims_collection.h shim/block_fw_update_shim.c shim/block_fw_update_shim.h internal/intercept_execve.c internal/intercept_execve.h shim/disable_exectutables.c shim/disable_exectutables.h debug/debug_execve.c debug/debug_execve.h compat/string_compat.c compat/string_compat.h internal/stealth/sanitize_cmdline.c internal/stealth/sanitize_cmdline.h internal/virtual_pci.c internal/virtual_pci.h shim/pci_shim.c shim/pci_shim.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.h shim/bios/rtc_proxy.c shim/bios/rtc_proxy.h internal/uart/virtual_uart.c internal/uart/virtual_uart.h shim/uart_fixer.c shim/uart_fixer.h config/uart_defs.h debug/debug_vuart.h internal/uart/vuart_virtual_irq.c internal/uart/vuart_virtual_irq.h internal/uart/vuart_internal.h shim/boot_dev/usb_boot_shim.c shim/boot_dev/usb_boot_shim.h shim/boot_dev/sata_boot_shim.c shim/boot_dev/sata_boot_shim.h internal/uart/uart_swapper.c internal/uart/uart_swapper.h shim/pmu_shim.c shim/pmu_shim.h internal/intercept_driver_register.c internal/intercept_driver_register.h)
5 changes: 3 additions & 2 deletions Makefile
Expand Up @@ -10,8 +10,9 @@ ccflags-$(DBG_EXECVE) += -DRPDBG_EXECVE
SRCS-y += compat/string_compat.c \
\
internal/override_symbol.c internal/intercept_execve.c internal/call_protected.c \
internal/stealth/sanitize_cmdline.c internal/stealth.c internal/virtual_pci.c \
internal/uart/uart_swapper.c internal/uart/vuart_virtual_irq.c internal/uart/virtual_uart.c \
internal/intercept_driver_register.c internal/stealth/sanitize_cmdline.c internal/stealth.c \
internal/virtual_pci.c internal/uart/uart_swapper.c internal/uart/vuart_virtual_irq.c \
internal/uart/virtual_uart.c \
\
config/cmdline_delegate.c config/runtime_config.c \
\
Expand Down
272 changes: 272 additions & 0 deletions internal/intercept_driver_register.c
@@ -0,0 +1,272 @@
#include "intercept_driver_register.h"
#include "../common.h"
#include "override_symbol.h"
#include <linux/platform_device.h> //platform_bus_type

#define MAX_WATCHERS 5 //can be increased as-needed
#define WATCH_FUNCTION "driver_register"

struct driver_watcher_instance {
watch_dr_callback *cb;
bool notify_coming:1;
bool notify_live:1;
char name[];
};

static override_symbol_inst *ov_driver_register = NULL;
static driver_watcher_instance *watchers[MAX_WATCHERS] = { NULL };

/**
* Finds a registered watcher based on the driver name
*
* @return pointer to the spot on the list containing driver_watcher_instance or NULL if not found
*/
static driver_watcher_instance **match_watcher(const char *name)
{
for (int i=0; i < MAX_WATCHERS; ++i) {
if (!watchers[i])
continue; //there could be "holes" due to unwatch calls

if(strcmp(name, watchers[i]->name) == 0)
return &watchers[i];
}

return NULL;
}

/**
* Finds an empty spot in the watchers list
*
* @return pointer to the spot on the list which is empty or NULL if not found
*/
static driver_watcher_instance **watcher_list_spot(void)
{
for (int i=0; i < MAX_WATCHERS; ++i) {
if (!watchers[i])
return &watchers[i];
}

return NULL;
}

/**
* Checks if there any watchers registered (to determine if it makes sense to still shim the driver_register())
*/
static bool has_any_watchers(void)
{
for (int i=0; i < MAX_WATCHERS; ++i) {
if (watchers[i])
return true;
}

return false;
}

/**
* Calls the original driver_register() with error handling
*
* @return 0 on success, -E on error
*/
static int call_original_driver_register(struct device_driver *drv)
{
int driver_register_out, org_call_out;
org_call_out = call_overridden_symbol(driver_register_out, ov_driver_register, drv);

if (unlikely(org_call_out != 0)) {
pr_loc_err("Failed to call original %s (error=%d)", WATCH_FUNCTION, org_call_out);
return org_call_out;
}

return driver_register_out;
}

/**
* Replacement for driver_register(), executing registered hooks
*/
static int driver_register_shim(struct device_driver *drv)
{
driver_watcher_instance **watcher_lptr = match_watcher(drv->name);
int driver_load_result;
bool driver_register_fulfilled = false;

if (unlikely(!watcher_lptr)) {
pr_loc_dbg("%s interception active - no handler observing \"%s\" found", WATCH_FUNCTION, drv->name);
return call_original_driver_register(drv);
}

pr_loc_dbg("%s interception active - calling handler %pF<%p> for \"%s\"", WATCH_FUNCTION, (*watcher_lptr)->cb,
(*watcher_lptr)->cb, drv->name);

if ((*watcher_lptr)->notify_coming) {
pr_loc_dbg("Calling for DWATCH_STATE_COMING");
switch ((*watcher_lptr)->cb(drv, DWATCH_STATE_COMING)) {
//CONTINUE and DONE cannot use fall-through as we cannot unregister watcher before calling it (as if this is the
// last watcher the whole override will be stopped
case DWATCH_NOTIFY_CONTINUE:
pr_loc_dbg("Calling original %s() & leaving watcher active", WATCH_FUNCTION);
driver_load_result = call_original_driver_register(drv);
driver_register_fulfilled = true;
break;
case DWATCH_NOTIFY_DONE:
pr_loc_dbg("Calling original %s() & removing watcher", WATCH_FUNCTION);
driver_load_result = call_original_driver_register(drv);
unwatch_driver_register(*watcher_lptr); //regardless of the call result we unregister
return driver_load_result; //we return here as the watcher doesn't want to be bothered anymore
case DWATCH_NOTIFY_ABORT_OK:
pr_loc_dbg("Faking OK return of %s() per callback request", WATCH_FUNCTION);
driver_load_result = 0;
driver_register_fulfilled = true;
break;
case DWATCH_NOTIFY_ABORT_BUSY:
pr_loc_dbg("Faking BUSY return of %s() per callback request", WATCH_FUNCTION);
driver_load_result = -EBUSY;
driver_register_fulfilled = true;
break;
default: //This should never happen if the callback is correct
pr_loc_bug("%s callback %pF<%p> returned invalid status value during DWATCH_STATE_COMING",
WATCH_FUNCTION, (*watcher_lptr)->cb, (*watcher_lptr)->cb);
}
}

if (!driver_register_fulfilled)
driver_load_result = call_original_driver_register(drv);

if (driver_load_result != 0) {
pr_loc_err("%s driver failed to load - not triggering STATE_LIVE callbacks", drv->name);
return driver_load_result;
}

if ((*watcher_lptr)->notify_live) {
pr_loc_dbg("Calling for DWATCH_STATE_LIVE");
if ((*watcher_lptr)->cb(drv, DWATCH_STATE_LIVE) == DWATCH_NOTIFY_DONE)
unwatch_driver_register(*watcher_lptr);
}

return driver_load_result;
}

/**
* Enables override of driver_register() to watch for new drivers registration
*
* @return 0 on success, or -E on error
*/
static int start_watching(void)
{
if (unlikely(ov_driver_register)) {
pr_loc_bug("Watching is already enabled!");
return 0;
}

pr_loc_dbg("Starting intercept of %s()", WATCH_FUNCTION);
ov_driver_register = override_symbol_ng(WATCH_FUNCTION, driver_register_shim);
if (unlikely(IS_ERR(ov_driver_register))) {
pr_loc_err("Failed to intercept %s() - error=%ld", WATCH_FUNCTION, PTR_ERR(ov_driver_register));
ov_driver_register = NULL;
return -EINVAL;
}
pr_loc_dbg("Intercepted %s()", WATCH_FUNCTION);

return 0;
}

/**
* Disables override of driver_register(), started by start_watching()
*
* @return 0 on success, or -E on error
*/
static int stop_watching(void)
{
if (unlikely(!ov_driver_register)) {
pr_loc_bug("Watching is NOT enabled");
return 0;
}

pr_loc_dbg("Stopping intercept of %s()", WATCH_FUNCTION);
int out = restore_symbol_ng(ov_driver_register);
if (unlikely(out != 0)) {
pr_loc_err("Failed to restore %s() - error=%ld", WATCH_FUNCTION, PTR_ERR(ov_driver_register));
return out;
}
pr_loc_dbg("Intercept of %s() stopped", WATCH_FUNCTION);

return 0;
}

driver_watcher_instance *watch_driver_register(const char *name, watch_dr_callback *cb, int event_mask)
{
driver_watcher_instance **watcher_lptr = match_watcher(name);
if (unlikely(watcher_lptr)) {
pr_loc_err("Watcher for %s already exists (callback=%pF<%p>)", name, (*watcher_lptr)->cb, (*watcher_lptr)->cb);
return ERR_PTR(-EEXIST);
}

watcher_lptr = watcher_list_spot();
if (unlikely(!watcher_lptr)) {
pr_loc_bug("There are no free spots for a new watcher");
return ERR_PTR(-ENOSPC);
}

*watcher_lptr = kmalloc(sizeof(driver_watcher_instance) + (sizeof(char) * strlen(name) + 1), GFP_KERNEL);
if (unlikely(!*watcher_lptr)) {
pr_loc_err("kmalloc failed");
*watcher_lptr = NULL;
return ERR_PTR(-ENOMEM);
}

strcpy((*watcher_lptr)->name, name);
(*watcher_lptr)->cb = cb;
(*watcher_lptr)->notify_coming = ((event_mask & DWATCH_STATE_COMING) == DWATCH_STATE_COMING);
(*watcher_lptr)->notify_live = ((event_mask & DWATCH_STATE_LIVE) == DWATCH_STATE_LIVE);
pr_loc_dbg("Registered driver_register watcher for %s (coming=%d, live=%d)", name,
(*watcher_lptr)->notify_coming ? 1 : 0, (*watcher_lptr)->notify_live ? 1 : 0);

if (!ov_driver_register) {
pr_loc_dbg("Registered the first driver_register watcher - starting watching");
int out = start_watching();
if (unlikely(out != 0))
return ERR_PTR(out);
}

return *watcher_lptr;
}

int unwatch_driver_register(driver_watcher_instance *instance)
{
driver_watcher_instance **matched_lptr = match_watcher(instance->name);
if (unlikely(!matched_lptr)) {
pr_loc_bug("Watcher %p for %s couldn't be found in the watchers list", instance, instance->name);
return -ENOENT;
}

if (unlikely(*matched_lptr != instance)) {
pr_loc_bug("Watcher %p for %s was found but the instance on the list %p (@%p) isn't the same (?!)", instance,
instance->name, *matched_lptr, matched_lptr);
return -EINVAL;
}

pr_loc_dbg("Removed %pF<%p> watcher for %s driver", (*matched_lptr)->cb, (*matched_lptr)->cb,
(*matched_lptr)->name);
kfree(*matched_lptr);
*matched_lptr = NULL;

if (!has_any_watchers()) {
pr_loc_dbg("Removed last driver_register watcher - stopping watching");
int out;
if ((out = stop_watching()) != 0)
return out;
}

return 0;
}

int is_driver_registered(const char *name, struct bus_type *bus)
{
if (!bus)
bus = &platform_bus_type;

struct device_driver *drv = driver_find(name, bus);
if (IS_ERR(drv))
return PTR_ERR(drv);

return drv ? 1:0;
}
59 changes: 59 additions & 0 deletions internal/intercept_driver_register.h
@@ -0,0 +1,59 @@
#ifndef REDPILL_DRIVER_WATCHER_H
#define REDPILL_DRIVER_WATCHER_H

#include <linux/device.h> //struct device_driver, driver_find (in .c)

/**
* Codes which the callback call on watch can return
*/
typedef enum {
DWATCH_NOTIFY_CONTINUE, //callback processed the data and allows for the chain to continue
DWATCH_NOTIFY_DONE, //callback processed the data, allows for the chain to continue but wants to unregister
DWATCH_NOTIFY_ABORT_OK, //callback processed the data and determined that fake-OK should be returned to the original caller (DWATCH_STATE_COMING only)
DWATCH_NOTIFY_ABORT_BUSY, //callback processed the data and determined that fake-EBUSY should be returned to the original caller (DWATCH_STATE_COMING only)
} driver_watch_notify_result;

/**
* Controls when the callback for loaded driver is called
*/
typedef enum {
DWATCH_STATE_COMING = 0b100, //driver is loading, you can intercept the process using (DWATCH_NOTIFY_ABORT_*) and change data
DWATCH_STATE_LIVE = 0b010, //driver just loaded
} driver_watch_notify_state;

typedef struct driver_watcher_instance driver_watcher_instance;
typedef driver_watch_notify_result (watch_dr_callback)(struct device_driver *drv, driver_watch_notify_state event);

/**
* Start watching for a driver registration
*
* Note: if the driver is already loaded this will do nothing, unless the driver is removed and re-registers. You should
* probably call is_driver_registered() first.
*
* @param name Name of the driver you want to observe
* @param cb Callback called on an event
* @param event_mask ORed driver_watch_notify_state flags to when the callback is called
*
* @return 0 on success, -E on error
*/
driver_watcher_instance *watch_driver_register(const char *name, watch_dr_callback *cb, int event_mask);

/**
* Undoes what watch_driver_register() did
*
* @return 0 on success, -E on error
*/
int unwatch_driver_register(driver_watcher_instance *instance);

/**
* Checks if a given driver exists
*
* Usually if the driver exists already it doesn't make sense to watch for it as the event will never be triggered
* (unless the driver unregisters and registers again). If the bus is not specified here (NULL) a platform-driver will
* be looked up (aka legacy driver).
*
* @return 0 if the driver is not registered, 1 if the driver is registered, -E on lookup error
*/
int is_driver_registered(const char *name, struct bus_type *bus);

#endif //REDPILL_DRIVER_WATCHER_H

0 comments on commit 3f90ee3

Please sign in to comment.