Skip to content

Commit

Permalink
kmod: add support for in-kernel livepatch hooks
Browse files Browse the repository at this point in the history
Upstream 4.15 kernels provide support for pre and post (un)patch
callbacks, inspired by the kpatch load hooks.  Add support for them
in the livepatch-patch-hook.

At the same time, convert the kpatch hooks to use the same API.

Signed-off-by: Joe Lawrence <joe.lawrence@redhat.com>
  • Loading branch information
joe-lawrence committed Mar 23, 2018
1 parent 7e1b495 commit 926e4e0
Show file tree
Hide file tree
Showing 15 changed files with 806 additions and 228 deletions.
94 changes: 70 additions & 24 deletions doc/patch-author-guide.md
Expand Up @@ -105,53 +105,99 @@ Not only is this an easy solution, it's also safer than touching data since
kpatch creates a barrier between the calling of old functions and new
functions.

### Use a kpatch load hook
### Use a kpatch callback macro

If you need to change the contents of an existing variable in-place, you can
use the `KPATCH_LOAD_HOOK` macro to specify a function to be called when the
patch module is loaded.
Kpatch supports livepatch style callbacks, as described by the kernel's
[Documentation/livepatch/callbacks.txt](https://github.com/torvalds/linux/blob/master/Documentation/livepatch/callbacks.txt).

`kpatch-macros.h` provides `KPATCH_LOAD_HOOK` and `KPATCH_UNLOAD_HOOK` macros
to define such functions. The signature of both hook functions is `void
foo(void)`. Their execution context is as follows:
`kpatch-macros.h` defines the following macros that can be used to
register such callbacks:

* For patches to vmlinux or already loaded kernel modules, hook functions
* `KPATCH_PRE_PATCH_CALLBACK` - executed before patching
* `KPATCH_POST_PATCH_CALLBACK` - executed after patching
* `KPATCH_PRE_UNPATCH_CALLBACK` - executed before unpatching, complements the
post-patch callback.
* `KPATCH_POST_UNPATCH_CALLBACK` - executed after unpatching, complements the
pre-patch callback.

A pre-patch callback routine has the following signature:

```
static int callback(patch_object *obj) { }
```

and any non-zero return status indicates failure to the kpatch core. For more
information on pre-patch callback failure, see the **Pre-patch return status**
section below.

Post-patch, pre-unpatch, and post-unpatch callback routines all share the
following signature:

```
static void callback(patch_object *obj) { }
```

Generally pre-patch callbacks are paired with post-unpatch callbacks, meaning
that anything the former allocates or sets up should be torn down by the former
callback. Likewise for post-patch and pre-unpatch callbacks.

#### Pre-patch return status

If kpatch is currently patching already-loaded objects (vmlinux always by
definition as well as any currently loaded kernel modules), a non-zero pre-patch
callback status results in the kpatch core reverting the current
patch-in-progress. The kpatch-module is rejected, completely reverted, and
unloaded.

If kpatch is patching a newly loaded kernel module, then a failing pre-patch
callback will only result in a WARN message. This is non-intuitive and a
deviation from livepatch callback behavior, but the result of a limitation of
kpatch and linux module notifiers.

In both cases, if a pre-patch callback fails, none of its other callbacks will
be executed.

#### Callback context

* For patches to vmlinux or already loaded kernel modules, callback functions
will be run by `stop_machine` as part of applying or removing a patch.
(Therefore the hooks must not block or sleep.)
(Therefore the callbacks must not block or sleep.)

* For patches to kernel modules which haven't been loaded yet, a
module-notifier will execute load hooks when the associated module is loaded
into the `MODULE_STATE_COMING` state. The load hook is called before any
module_init code.
module-notifier will execute callbacks when the associated module is loaded
into the `MODULE_STATE_COMING` state. The pre and post-patch callbacks
are called before any module_init code.

Example: a kpatch fix for CVE-2016-5389 utilized the `KPATCH_LOAD_HOOK` and
`KPATCH_UNLOAD_HOOK` macros to modify variable `sysctl_tcp_challenge_ack_limit`
in-place:
Example: a kpatch fix for CVE-2016-5389 could utilize the
`KPATCH_PRE_PATCH_CALLBACK` and `KPATCH_POST_UNPATCH_CALLBACK` macros to modify
variable `sysctl_tcp_challenge_ack_limit` in-place:

```
+#include "kpatch-macros.h"
+
+static bool kpatch_write = false;
+void kpatch_load_tcp_send_challenge_ack(void)
+static int kpatch_pre_patch_tcp_send_challenge_ack(patch_object *obj)
+{
+ if (sysctl_tcp_challenge_ack_limit == 100) {
+ sysctl_tcp_challenge_ack_limit = 1000;
+ kpatch_write = true;
+ }
+ return 0;
+}
+void kpatch_unload_tcp_send_challenge_ack(void)
static void kpatch_post_unpatch_tcp_send_challenge_ack(patch_object *obj)
+{
+ if (kpatch_write && sysctl_tcp_challenge_ack_limit == 1000)
+ sysctl_tcp_challenge_ack_limit = 100;
+}
+#include "kpatch-macros.h"
+KPATCH_LOAD_HOOK(kpatch_load_tcp_send_challenge_ack);
+KPATCH_UNLOAD_HOOK(kpatch_unload_tcp_send_challenge_ack);
+KPATCH_PRE_PATCH_CALLBACK(kpatch_pre_patch_tcp_send_challenge_ack);
+KPATCH_POST_UNPATCH_CALLBACK(kpatch_post_unpatch_tcp_send_challenge_ack);
```

Don't forget to protect access to the data as needed.

Also be careful when upgrading. If patch A has a load hook which writes to X,
and then you load patch B which is a superset of A, in some cases you may want
to prevent patch B from writing to X, if A is already loaded.
Also be careful when upgrading. If patch A has a pre/post-patch callback which
writes to X, and then you load patch B which is a superset of A, in some cases
you may want to prevent patch B from writing to X, if A is already loaded.


### Use a shadow variable
Expand Down Expand Up @@ -339,7 +385,7 @@ Init code changes

Any code which runs in an `__init` function or during module or device
initialization is problematic, as it may have already run before the patch was
applied. The patch may require a load hook function which detects whether such
applied. The patch may require a pre-patch callback which detects whether such
init code has run, and which rewrites or changes the original initialization to
force it into the desired state. Some changes involving hardware init are
inherently incompatible with live patching.
Expand Down
112 changes: 88 additions & 24 deletions kmod/core/core.c
Expand Up @@ -305,12 +305,52 @@ static int kpatch_verify_activeness_safety(struct kpatch_module *kpmod)
return ret;
}

static inline int pre_patch_callback(struct kpatch_object *object)
{
int ret;

if (kpatch_object_linked(object) &&
object->pre_patch_callback) {
ret = (*object->pre_patch_callback)(object);
if (ret) {
object->callbacks_enabled = false;
return ret;
}
}
object->callbacks_enabled = true;

return 0;
}

static inline void post_patch_callback(struct kpatch_object *object)
{
if (kpatch_object_linked(object) &&
object->post_patch_callback &&
object->callbacks_enabled)
(*object->post_patch_callback)(object);
}

static inline void pre_unpatch_callbacks(struct kpatch_object *object)
{
if (kpatch_object_linked(object) &&
object->pre_unpatch_callback &&
object->callbacks_enabled)
(*object->pre_unpatch_callback)(object);
}

static inline void post_unpatch_callback(struct kpatch_object *object)
{
if (kpatch_object_linked(object) &&
object->post_unpatch_callback &&
object->callbacks_enabled)
(*object->post_unpatch_callback)(object);
}

/* Called from stop_machine */
static int kpatch_apply_patch(void *data)
{
struct kpatch_module *kpmod = data;
struct kpatch_func *func;
struct kpatch_hook *hook;
struct kpatch_object *object;
int ret;

Expand All @@ -320,6 +360,16 @@ static int kpatch_apply_patch(void *data)
return ret;
}

/* run any user-defined pre-patch callbacks */
list_for_each_entry(object, &kpmod->objects, list) {
ret = pre_patch_callback(object);
if (ret) {
pr_err("pre-patch callback failed!\n");
kpatch_state_finish(KPATCH_STATE_FAILURE);
goto err;
}
}

/* tentatively add the new funcs to the global func hash */
do_for_each_linked_func(kpmod, func) {
hash_add_rcu(kpatch_func_hash, &func->node, func->old_addr);
Expand All @@ -341,27 +391,28 @@ static int kpatch_apply_patch(void *data)
hash_del_rcu(&func->node);
} while_for_each_linked_func();

return -EBUSY;
}

/* run any user-defined load hooks */
list_for_each_entry(object, &kpmod->objects, list) {
if (!kpatch_object_linked(object))
continue;
list_for_each_entry(hook, &object->hooks_load, list)
(*hook->hook)();
ret = -EBUSY;
goto err;
}

/* run any user-defined post-patch callbacks */
list_for_each_entry(object, &kpmod->objects, list)
post_patch_callback(object);

return 0;
err:
/* undo pre-patch callbacks by calling post-unpatch counterparts */
list_for_each_entry(object, &kpmod->objects, list)
post_unpatch_callback(object);

return ret;
}

/* Called from stop_machine */
static int kpatch_remove_patch(void *data)
{
struct kpatch_module *kpmod = data;
struct kpatch_func *func;
struct kpatch_hook *hook;
struct kpatch_object *object;
int ret;

Expand All @@ -371,25 +422,34 @@ static int kpatch_remove_patch(void *data)
return ret;
}

/* run any user-defined pre-unpatch callbacks */
list_for_each_entry(object, &kpmod->objects, list)
pre_unpatch_callbacks(object);

/* Check if any inconsistent NMI has happened while updating */
ret = kpatch_state_finish(KPATCH_STATE_SUCCESS);
if (ret == KPATCH_STATE_FAILURE)
return -EBUSY;
if (ret == KPATCH_STATE_FAILURE) {
ret = -EBUSY;
goto err;
}

/* Succeeded, remove all updating funcs from hash table */
do_for_each_linked_func(kpmod, func) {
hash_del_rcu(&func->node);
} while_for_each_linked_func();

/* run any user-defined unload hooks */
list_for_each_entry(object, &kpmod->objects, list) {
if (!kpatch_object_linked(object))
continue;
list_for_each_entry(hook, &object->hooks_unload, list)
(*hook->hook)();
}
/* run any user-defined post-unpatch callbacks */
list_for_each_entry(object, &kpmod->objects, list)
post_unpatch_callback(object);

return 0;

err:
/* undo pre-unpatch callbacks by calling post-patch counterparts */
list_for_each_entry(object, &kpmod->objects, list)
post_patch_callback(object);

return ret;
}

/*
Expand Down Expand Up @@ -808,7 +868,6 @@ static int kpatch_module_notify(struct notifier_block *nb, unsigned long action,
struct kpatch_module *kpmod;
struct kpatch_object *object;
struct kpatch_func *func;
struct kpatch_hook *hook;
int ret = 0;
bool found = false;

Expand Down Expand Up @@ -839,14 +898,19 @@ static int kpatch_module_notify(struct notifier_block *nb, unsigned long action,

pr_notice("patching newly loaded module '%s'\n", object->name);

/* run any user-defined load hooks */
list_for_each_entry(hook, &object->hooks_load, list)
(*hook->hook)();
/* run user-defined pre-patch callback */
ret = pre_patch_callback(object);
if (ret) {
pr_err("pre-patch callback failed!\n");
goto out; /* and WARN */
}

/* add to the global func hash */
list_for_each_entry(func, &object->funcs, list)
hash_add_rcu(kpatch_func_hash, &func->node, func->old_addr);

/* run user-defined post-patch callback */
post_patch_callback(object);
out:
up(&kpatch_mutex);

Expand Down
13 changes: 6 additions & 7 deletions kmod/core/kpatch.h
Expand Up @@ -60,18 +60,17 @@ struct kpatch_dynrela {
struct list_head list;
};

struct kpatch_hook {
struct list_head list;
void (*hook)(void);
};

struct kpatch_object {
struct list_head list;
const char *name;
struct list_head funcs;
struct list_head dynrelas;
struct list_head hooks_load;
struct list_head hooks_unload;

int (*pre_patch_callback)(struct kpatch_object *);
void (*post_patch_callback)(struct kpatch_object *);
void (*pre_unpatch_callback)(struct kpatch_object *);
void (*post_unpatch_callback)(struct kpatch_object *);
bool callbacks_enabled;

/* private */
struct module *mod;
Expand Down

0 comments on commit 926e4e0

Please sign in to comment.