Managing the lifetime of shadow variables becomes challenging when atomic replace is used. The new patch cannot determine whether a shadow variable has already been used by a previous live patch or if there is a shadow variable that is no longer in use. Shadow variables are typically used alongside callbacks. At a minimum, the @post_unpatch callback is called to free shadow variables that are no longer needed. Additionally, @post_patch and @pre_unpatch callbacks are sometimes used to enable or disable the use of shadow variables. This is necessary when the shadow variable can only be used when the entire system is capable of handling it. The complexity increases when using the atomic replace feature, as only the callbacks from the new live patch are executed. Newly created live patches might manage obsolete shadow variables, ensuring the upgrade functions correctly. However, older live patches are unaware of shadow variables introduced later, which could lead to leaks during a downgrade. Additionally, these leaked variables might retain outdated information, potentially causing issues if those variables are reused in a subsequent upgrade. These issues are better addressed with the new callbacks associated with a live patch state. These callbacks are triggered both when the states are first introduced and when they become obsolete. Additionally, the callbacks are invoked from the patch that originally supported the state, ensuring that even downgrades are handled safely. Let’s formalize the process: Associate a shadow variable with a live patch state by setting the "state.is_shadow" flag and using the same "id" in both struct klp_shadow and struct klp_state. The shadow variable will then share the same lifetime as the livepatch state, allowing obsolete shadow variables to be automatically freed without requiring an additional callback. A generic callback will free the shadow variables using the state->callbacks.shadow_dtor callback, if provided. Signed-off-by: Petr Mladek <pmladek@xxxxxxxx> --- include/linux/livepatch.h | 15 ++++++++++----- kernel/livepatch/state.c | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/include/linux/livepatch.h b/include/linux/livepatch.h index 79dddf3dbd52..c624f1105663 100644 --- a/include/linux/livepatch.h +++ b/include/linux/livepatch.h @@ -132,6 +132,11 @@ struct klp_object { struct klp_patch; struct klp_state; +typedef int (*klp_shadow_ctor_t)(void *obj, + void *shadow_data, + void *ctor_data); +typedef void (*klp_shadow_dtor_t)(void *obj, void *shadow_data); + /** * struct klp_state_callbacks - callbacks manipulating the state * @pre_patch: executed only when the state is being enabled @@ -142,6 +147,7 @@ struct klp_state; * before code unpatching * @post_unpatch: executed only when the state is being disabled * after code unpatching + * @shadow_dtor: destructor for the related shadow variable * @pre_patch_succeeded: internal state used by a rollback on error * * All callbacks are optional. @@ -158,6 +164,7 @@ struct klp_state_callbacks { void (*post_patch)(struct klp_patch *patch, struct klp_state *state); void (*pre_unpatch)(struct klp_patch *patch, struct klp_state *state); void (*post_unpatch)(struct klp_patch *patch, struct klp_state *state); + klp_shadow_dtor_t shadow_dtor; bool pre_patch_succeeded; }; @@ -166,12 +173,15 @@ struct klp_state_callbacks { * @id: system state identifier (non-zero) * @version: version of the change * @callbacks: optional callbacks used when enabling or disabling the state + * @is_shadow: the state handles lifetime of a shadow variable with + * the same @id * @data: custom data */ struct klp_state { unsigned long id; unsigned int version; struct klp_state_callbacks callbacks; + bool is_shadow; void *data; }; @@ -246,11 +256,6 @@ static inline bool klp_have_reliable_stack(void) IS_ENABLED(CONFIG_HAVE_RELIABLE_STACKTRACE); } -typedef int (*klp_shadow_ctor_t)(void *obj, - void *shadow_data, - void *ctor_data); -typedef void (*klp_shadow_dtor_t)(void *obj, void *shadow_data); - void *klp_shadow_get(void *obj, unsigned long id); void *klp_shadow_alloc(void *obj, unsigned long id, size_t size, gfp_t gfp_flags, diff --git a/kernel/livepatch/state.c b/kernel/livepatch/state.c index bf7ed988d2bb..16ad695b1e88 100644 --- a/kernel/livepatch/state.c +++ b/kernel/livepatch/state.c @@ -201,6 +201,9 @@ void klp_states_post_unpatch(struct klp_patch *patch) if (state->callbacks.post_unpatch) state->callbacks.post_unpatch(patch, state); + if (state->is_shadow) + klp_shadow_free_all(state->id, state->callbacks.shadow_dtor); + state->callbacks.pre_patch_succeeded = 0; } } -- 2.47.1