Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement driver_register() interceptor to watch drivers registration
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
1 parent
4427b74
commit 3f90ee3
Showing
4 changed files
with
335 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |