mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-27 14:14:24 +08:00
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/livepatching/livepatching
Pull livepatching updates from Jiri Kosina: - support for something we call 'atomic replace', and allows for much better handling of cumulative patches (which is something very useful for distros), from Jason Baron with help of Petr Mladek and Joe Lawrence - improvement of handling of tasks blocking finalization, from Miroslav Benes - update of MAINTAINERS file to reflect move towards group maintainership * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/livepatching/livepatching: (22 commits) livepatch/selftests: use "$@" to preserve argument list livepatch: Module coming and going callbacks can proceed with all listed patches livepatch: Proper error handling in the shadow variables selftest livepatch: return -ENOMEM on ptr_id() allocation failure livepatch: Introduce klp_for_each_patch macro livepatch: core: Return EOPNOTSUPP instead of ENOSYS selftests/livepatch: add DYNAMIC_DEBUG config dependency livepatch: samples: non static warnings fix livepatch: update MAINTAINERS livepatch: Remove signal sysfs attribute livepatch: Send a fake signal periodically selftests/livepatch: introduce tests livepatch: Remove ordering (stacking) of the livepatches livepatch: Atomic replace and cumulative patches documentation livepatch: Remove Nop structures when unused livepatch: Add atomic replace livepatch: Use lists to manage patches, objects and functions livepatch: Simplify API by removing registration step livepatch: Don't block the removal of patches loaded after a forced transition livepatch: Consolidate klp_free functions ...
This commit is contained in:
commit
b7af27bf94
@ -33,18 +33,6 @@ Description:
|
|||||||
An attribute which indicates whether the patch is currently in
|
An attribute which indicates whether the patch is currently in
|
||||||
transition.
|
transition.
|
||||||
|
|
||||||
What: /sys/kernel/livepatch/<patch>/signal
|
|
||||||
Date: Nov 2017
|
|
||||||
KernelVersion: 4.15.0
|
|
||||||
Contact: live-patching@vger.kernel.org
|
|
||||||
Description:
|
|
||||||
A writable attribute that allows administrator to affect the
|
|
||||||
course of an existing transition. Writing 1 sends a fake
|
|
||||||
signal to all remaining blocking tasks. The fake signal
|
|
||||||
means that no proper signal is delivered (there is no data in
|
|
||||||
signal pending structures). Tasks are interrupted or woken up,
|
|
||||||
and forced to change their patched state.
|
|
||||||
|
|
||||||
What: /sys/kernel/livepatch/<patch>/force
|
What: /sys/kernel/livepatch/<patch>/force
|
||||||
Date: Nov 2017
|
Date: Nov 2017
|
||||||
KernelVersion: 4.15.0
|
KernelVersion: 4.15.0
|
||||||
|
@ -118,488 +118,9 @@ similar change to their hw_features value. (Client functions of the
|
|||||||
value may need to be updated accordingly.)
|
value may need to be updated accordingly.)
|
||||||
|
|
||||||
|
|
||||||
Test cases
|
Other Examples
|
||||||
==========
|
==============
|
||||||
|
|
||||||
What follows is not an exhaustive test suite of every possible livepatch
|
Sample livepatch modules demonstrating the callback API can be found in
|
||||||
pre/post-(un)patch combination, but a selection that demonstrates a few
|
samples/livepatch/ directory. These samples were modified for use in
|
||||||
important concepts. Each test case uses the kernel modules located in
|
kselftests and can be found in the lib/livepatch directory.
|
||||||
the samples/livepatch/ and assumes that no livepatches are loaded at the
|
|
||||||
beginning of the test.
|
|
||||||
|
|
||||||
|
|
||||||
Test 1
|
|
||||||
------
|
|
||||||
|
|
||||||
Test a combination of loading a kernel module and a livepatch that
|
|
||||||
patches a function in the first module. (Un)load the target module
|
|
||||||
before the livepatch module:
|
|
||||||
|
|
||||||
- load target module
|
|
||||||
- load livepatch
|
|
||||||
- disable livepatch
|
|
||||||
- unload target module
|
|
||||||
- unload livepatch
|
|
||||||
|
|
||||||
First load a target module:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 34.475708] livepatch_callbacks_mod: livepatch_callbacks_mod_init
|
|
||||||
|
|
||||||
On livepatch enable, before the livepatch transition starts, pre-patch
|
|
||||||
callbacks are executed for vmlinux and livepatch_callbacks_mod (those
|
|
||||||
klp_objects currently loaded). After klp_objects are patched according
|
|
||||||
to the klp_patch, their post-patch callbacks run and the transition
|
|
||||||
completes:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 36.503719] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 36.504213] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 36.504238] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 36.504721] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 36.505849] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
[ 37.727133] livepatch: 'livepatch_callbacks_demo': completing patching transition
|
|
||||||
[ 37.727232] livepatch_callbacks_demo: post_patch_callback: vmlinux
|
|
||||||
[ 37.727860] livepatch_callbacks_demo: post_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 37.728792] livepatch: 'livepatch_callbacks_demo': patching complete
|
|
||||||
|
|
||||||
Similarly, on livepatch disable, pre-patch callbacks run before the
|
|
||||||
unpatching transition starts. klp_objects are reverted, post-patch
|
|
||||||
callbacks execute and the transition completes:
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 38.510209] livepatch: 'livepatch_callbacks_demo': initializing unpatching transition
|
|
||||||
[ 38.510234] livepatch_callbacks_demo: pre_unpatch_callback: vmlinux
|
|
||||||
[ 38.510982] livepatch_callbacks_demo: pre_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 38.512209] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 39.711132] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 39.711210] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 39.711779] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 39.712735] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 42.534183] livepatch_callbacks_mod: livepatch_callbacks_mod_exit
|
|
||||||
|
|
||||||
|
|
||||||
Test 2
|
|
||||||
------
|
|
||||||
|
|
||||||
This test is similar to the previous test, but (un)load the livepatch
|
|
||||||
module before the target kernel module. This tests the livepatch core's
|
|
||||||
module_coming handler:
|
|
||||||
|
|
||||||
- load livepatch
|
|
||||||
- load target module
|
|
||||||
- disable livepatch
|
|
||||||
- unload livepatch
|
|
||||||
- unload target module
|
|
||||||
|
|
||||||
|
|
||||||
On livepatch enable, only pre/post-patch callbacks are executed for
|
|
||||||
currently loaded klp_objects, in this case, vmlinux:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 44.553328] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 44.553997] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 44.554049] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 44.554845] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
[ 45.727128] livepatch: 'livepatch_callbacks_demo': completing patching transition
|
|
||||||
[ 45.727212] livepatch_callbacks_demo: post_patch_callback: vmlinux
|
|
||||||
[ 45.727961] livepatch: 'livepatch_callbacks_demo': patching complete
|
|
||||||
|
|
||||||
When a targeted module is subsequently loaded, only its pre/post-patch
|
|
||||||
callbacks are executed:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 46.560845] livepatch: applying patch 'livepatch_callbacks_demo' to loading module 'livepatch_callbacks_mod'
|
|
||||||
[ 46.561988] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 46.563452] livepatch_callbacks_demo: post_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 46.565495] livepatch_callbacks_mod: livepatch_callbacks_mod_init
|
|
||||||
|
|
||||||
On livepatch disable, all currently loaded klp_objects' (vmlinux and
|
|
||||||
livepatch_callbacks_mod) pre/post-unpatch callbacks are executed:
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 48.568885] livepatch: 'livepatch_callbacks_demo': initializing unpatching transition
|
|
||||||
[ 48.568910] livepatch_callbacks_demo: pre_unpatch_callback: vmlinux
|
|
||||||
[ 48.569441] livepatch_callbacks_demo: pre_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 48.570502] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 49.759091] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 49.759171] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 49.759742] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 49.760690] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 52.592283] livepatch_callbacks_mod: livepatch_callbacks_mod_exit
|
|
||||||
|
|
||||||
|
|
||||||
Test 3
|
|
||||||
------
|
|
||||||
|
|
||||||
Test loading the livepatch after a targeted kernel module, then unload
|
|
||||||
the kernel module before disabling the livepatch. This tests the
|
|
||||||
livepatch core's module_going handler:
|
|
||||||
|
|
||||||
- load target module
|
|
||||||
- load livepatch
|
|
||||||
- unload target module
|
|
||||||
- disable livepatch
|
|
||||||
- unload livepatch
|
|
||||||
|
|
||||||
First load a target module, then the livepatch:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 54.607948] livepatch_callbacks_mod: livepatch_callbacks_mod_init
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 56.613919] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 56.614411] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 56.614436] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 56.614818] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 56.615656] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
[ 57.759070] livepatch: 'livepatch_callbacks_demo': completing patching transition
|
|
||||||
[ 57.759147] livepatch_callbacks_demo: post_patch_callback: vmlinux
|
|
||||||
[ 57.759621] livepatch_callbacks_demo: post_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 57.760307] livepatch: 'livepatch_callbacks_demo': patching complete
|
|
||||||
|
|
||||||
When a target module is unloaded, the livepatch is only reverted from
|
|
||||||
that klp_object (livepatch_callbacks_mod). As such, only its pre and
|
|
||||||
post-unpatch callbacks are executed when this occurs:
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 58.623409] livepatch_callbacks_mod: livepatch_callbacks_mod_exit
|
|
||||||
[ 58.623903] livepatch_callbacks_demo: pre_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_GOING] Going away
|
|
||||||
[ 58.624658] livepatch: reverting patch 'livepatch_callbacks_demo' on unloading module 'livepatch_callbacks_mod'
|
|
||||||
[ 58.625305] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_GOING] Going away
|
|
||||||
|
|
||||||
When the livepatch is disabled, pre and post-unpatch callbacks are run
|
|
||||||
for the remaining klp_object, vmlinux:
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 60.638420] livepatch: 'livepatch_callbacks_demo': initializing unpatching transition
|
|
||||||
[ 60.638444] livepatch_callbacks_demo: pre_unpatch_callback: vmlinux
|
|
||||||
[ 60.638996] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 61.727088] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 61.727165] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 61.727985] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
|
|
||||||
|
|
||||||
Test 4
|
|
||||||
------
|
|
||||||
|
|
||||||
This test is similar to the previous test, however the livepatch is
|
|
||||||
loaded first. This tests the livepatch core's module_coming and
|
|
||||||
module_going handlers:
|
|
||||||
|
|
||||||
- load livepatch
|
|
||||||
- load target module
|
|
||||||
- unload target module
|
|
||||||
- disable livepatch
|
|
||||||
- unload livepatch
|
|
||||||
|
|
||||||
First load the livepatch:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 64.661552] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 64.662147] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 64.662175] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 64.662850] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
[ 65.695056] livepatch: 'livepatch_callbacks_demo': completing patching transition
|
|
||||||
[ 65.695147] livepatch_callbacks_demo: post_patch_callback: vmlinux
|
|
||||||
[ 65.695561] livepatch: 'livepatch_callbacks_demo': patching complete
|
|
||||||
|
|
||||||
When a targeted kernel module is subsequently loaded, only its
|
|
||||||
pre/post-patch callbacks are executed:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 66.669196] livepatch: applying patch 'livepatch_callbacks_demo' to loading module 'livepatch_callbacks_mod'
|
|
||||||
[ 66.669882] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 66.670744] livepatch_callbacks_demo: post_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 66.672873] livepatch_callbacks_mod: livepatch_callbacks_mod_init
|
|
||||||
|
|
||||||
When the target module is unloaded, the livepatch is only reverted from
|
|
||||||
the livepatch_callbacks_mod klp_object. As such, only pre and
|
|
||||||
post-unpatch callbacks are executed when this occurs:
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 68.680065] livepatch_callbacks_mod: livepatch_callbacks_mod_exit
|
|
||||||
[ 68.680688] livepatch_callbacks_demo: pre_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_GOING] Going away
|
|
||||||
[ 68.681452] livepatch: reverting patch 'livepatch_callbacks_demo' on unloading module 'livepatch_callbacks_mod'
|
|
||||||
[ 68.682094] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_GOING] Going away
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 70.689225] livepatch: 'livepatch_callbacks_demo': initializing unpatching transition
|
|
||||||
[ 70.689256] livepatch_callbacks_demo: pre_unpatch_callback: vmlinux
|
|
||||||
[ 70.689882] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 71.711080] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 71.711481] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 71.711988] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
|
|
||||||
|
|
||||||
Test 5
|
|
||||||
------
|
|
||||||
|
|
||||||
A simple test of loading a livepatch without one of its patch target
|
|
||||||
klp_objects ever loaded (livepatch_callbacks_mod):
|
|
||||||
|
|
||||||
- load livepatch
|
|
||||||
- disable livepatch
|
|
||||||
- unload livepatch
|
|
||||||
|
|
||||||
Load the livepatch:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 74.711081] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 74.711595] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 74.711639] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 74.712272] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
[ 75.743137] livepatch: 'livepatch_callbacks_demo': completing patching transition
|
|
||||||
[ 75.743219] livepatch_callbacks_demo: post_patch_callback: vmlinux
|
|
||||||
[ 75.743867] livepatch: 'livepatch_callbacks_demo': patching complete
|
|
||||||
|
|
||||||
As expected, only pre/post-(un)patch handlers are executed for vmlinux:
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 76.716254] livepatch: 'livepatch_callbacks_demo': initializing unpatching transition
|
|
||||||
[ 76.716278] livepatch_callbacks_demo: pre_unpatch_callback: vmlinux
|
|
||||||
[ 76.716666] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 77.727089] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 77.727194] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 77.727907] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
|
|
||||||
|
|
||||||
Test 6
|
|
||||||
------
|
|
||||||
|
|
||||||
Test a scenario where a vmlinux pre-patch callback returns a non-zero
|
|
||||||
status (ie, failure):
|
|
||||||
|
|
||||||
- load target module
|
|
||||||
- load livepatch -ENODEV
|
|
||||||
- unload target module
|
|
||||||
|
|
||||||
First load a target module:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 80.740520] livepatch_callbacks_mod: livepatch_callbacks_mod_init
|
|
||||||
|
|
||||||
Load the livepatch module, setting its 'pre_patch_ret' value to -19
|
|
||||||
(-ENODEV). When its vmlinux pre-patch callback executed, this status
|
|
||||||
code will propagate back to the module-loading subsystem. The result is
|
|
||||||
that the insmod command refuses to load the livepatch module:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko pre_patch_ret=-19
|
|
||||||
[ 82.747326] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 82.747743] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 82.747767] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 82.748237] livepatch: pre-patch callback failed for object 'vmlinux'
|
|
||||||
[ 82.748637] livepatch: failed to enable patch 'livepatch_callbacks_demo'
|
|
||||||
[ 82.749059] livepatch: 'livepatch_callbacks_demo': canceling transition, going to unpatch
|
|
||||||
[ 82.749060] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 82.749868] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
[ 82.765809] insmod: ERROR: could not insert module samples/livepatch/livepatch-callbacks-demo.ko: No such device
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 84.774238] livepatch_callbacks_mod: livepatch_callbacks_mod_exit
|
|
||||||
|
|
||||||
|
|
||||||
Test 7
|
|
||||||
------
|
|
||||||
|
|
||||||
Similar to the previous test, setup a livepatch such that its vmlinux
|
|
||||||
pre-patch callback returns success. However, when a targeted kernel
|
|
||||||
module is later loaded, have the livepatch return a failing status code:
|
|
||||||
|
|
||||||
- load livepatch
|
|
||||||
- setup -ENODEV
|
|
||||||
- load target module
|
|
||||||
- disable livepatch
|
|
||||||
- unload livepatch
|
|
||||||
|
|
||||||
Load the livepatch, notice vmlinux pre-patch callback succeeds:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 86.787845] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 86.788325] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 86.788427] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 86.788821] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
[ 87.711069] livepatch: 'livepatch_callbacks_demo': completing patching transition
|
|
||||||
[ 87.711143] livepatch_callbacks_demo: post_patch_callback: vmlinux
|
|
||||||
[ 87.711886] livepatch: 'livepatch_callbacks_demo': patching complete
|
|
||||||
|
|
||||||
Set a trap so subsequent pre-patch callbacks to this livepatch will
|
|
||||||
return -ENODEV:
|
|
||||||
|
|
||||||
% echo -19 > /sys/module/livepatch_callbacks_demo/parameters/pre_patch_ret
|
|
||||||
|
|
||||||
The livepatch pre-patch callback for subsequently loaded target modules
|
|
||||||
will return failure, so the module loader refuses to load the kernel
|
|
||||||
module. Notice that no post-patch or pre/post-unpatch callbacks are
|
|
||||||
executed for this klp_object:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 90.796976] livepatch: applying patch 'livepatch_callbacks_demo' to loading module 'livepatch_callbacks_mod'
|
|
||||||
[ 90.797834] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 90.798900] livepatch: pre-patch callback failed for object 'livepatch_callbacks_mod'
|
|
||||||
[ 90.799652] livepatch: patch 'livepatch_callbacks_demo' failed for module 'livepatch_callbacks_mod', refusing to load module 'livepatch_callbacks_mod'
|
|
||||||
[ 90.819737] insmod: ERROR: could not insert module samples/livepatch/livepatch-callbacks-mod.ko: No such device
|
|
||||||
|
|
||||||
However, pre/post-unpatch callbacks run for the vmlinux klp_object:
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 92.823547] livepatch: 'livepatch_callbacks_demo': initializing unpatching transition
|
|
||||||
[ 92.823573] livepatch_callbacks_demo: pre_unpatch_callback: vmlinux
|
|
||||||
[ 92.824331] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 93.727128] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 93.727327] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 93.727861] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
|
|
||||||
|
|
||||||
Test 8
|
|
||||||
------
|
|
||||||
|
|
||||||
Test loading multiple targeted kernel modules. This test-case is
|
|
||||||
mainly for comparing with the next test-case.
|
|
||||||
|
|
||||||
- load busy target module (0s sleep),
|
|
||||||
- load livepatch
|
|
||||||
- load target module
|
|
||||||
- unload target module
|
|
||||||
- disable livepatch
|
|
||||||
- unload livepatch
|
|
||||||
- unload busy target module
|
|
||||||
|
|
||||||
|
|
||||||
Load a target "busy" kernel module which kicks off a worker function
|
|
||||||
that immediately exits:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-busymod.ko sleep_secs=0
|
|
||||||
[ 96.910107] livepatch_callbacks_busymod: livepatch_callbacks_mod_init
|
|
||||||
[ 96.910600] livepatch_callbacks_busymod: busymod_work_func, sleeping 0 seconds ...
|
|
||||||
[ 96.913024] livepatch_callbacks_busymod: busymod_work_func exit
|
|
||||||
|
|
||||||
Proceed with loading the livepatch and another ordinary target module,
|
|
||||||
notice that the post-patch callbacks are executed and the transition
|
|
||||||
completes quickly:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 98.917892] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 98.918426] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 98.918453] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 98.918955] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_busymod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 98.923835] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
[ 99.743104] livepatch: 'livepatch_callbacks_demo': completing patching transition
|
|
||||||
[ 99.743156] livepatch_callbacks_demo: post_patch_callback: vmlinux
|
|
||||||
[ 99.743679] livepatch_callbacks_demo: post_patch_callback: livepatch_callbacks_busymod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 99.744616] livepatch: 'livepatch_callbacks_demo': patching complete
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 100.930955] livepatch: applying patch 'livepatch_callbacks_demo' to loading module 'livepatch_callbacks_mod'
|
|
||||||
[ 100.931668] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 100.932645] livepatch_callbacks_demo: post_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 100.934125] livepatch_callbacks_mod: livepatch_callbacks_mod_init
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 102.942805] livepatch_callbacks_mod: livepatch_callbacks_mod_exit
|
|
||||||
[ 102.943640] livepatch_callbacks_demo: pre_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_GOING] Going away
|
|
||||||
[ 102.944585] livepatch: reverting patch 'livepatch_callbacks_demo' on unloading module 'livepatch_callbacks_mod'
|
|
||||||
[ 102.945455] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_GOING] Going away
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 104.953815] livepatch: 'livepatch_callbacks_demo': initializing unpatching transition
|
|
||||||
[ 104.953838] livepatch_callbacks_demo: pre_unpatch_callback: vmlinux
|
|
||||||
[ 104.954431] livepatch_callbacks_demo: pre_unpatch_callback: livepatch_callbacks_busymod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 104.955426] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 106.719073] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 106.722633] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 106.723282] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_busymod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 106.724279] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-busymod.ko
|
|
||||||
[ 108.975660] livepatch_callbacks_busymod: livepatch_callbacks_mod_exit
|
|
||||||
|
|
||||||
|
|
||||||
Test 9
|
|
||||||
------
|
|
||||||
|
|
||||||
A similar test as the previous one, but force the "busy" kernel module
|
|
||||||
to do longer work.
|
|
||||||
|
|
||||||
The livepatching core will refuse to patch a task that is currently
|
|
||||||
executing a to-be-patched function -- the consistency model stalls the
|
|
||||||
current patch transition until this safety-check is met. Test a
|
|
||||||
scenario where one of a livepatch's target klp_objects sits on such a
|
|
||||||
function for a long time. Meanwhile, load and unload other target
|
|
||||||
kernel modules while the livepatch transition is in progress.
|
|
||||||
|
|
||||||
- load busy target module (30s sleep)
|
|
||||||
- load livepatch
|
|
||||||
- load target module
|
|
||||||
- unload target module
|
|
||||||
- disable livepatch
|
|
||||||
- unload livepatch
|
|
||||||
- unload busy target module
|
|
||||||
|
|
||||||
|
|
||||||
Load the "busy" kernel module, this time make it do 30 seconds worth of
|
|
||||||
work:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-busymod.ko sleep_secs=30
|
|
||||||
[ 110.993362] livepatch_callbacks_busymod: livepatch_callbacks_mod_init
|
|
||||||
[ 110.994059] livepatch_callbacks_busymod: busymod_work_func, sleeping 30 seconds ...
|
|
||||||
|
|
||||||
Meanwhile, the livepatch is loaded. Notice that the patch transition
|
|
||||||
does not complete as the targeted "busy" module is sitting on a
|
|
||||||
to-be-patched function:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
[ 113.000309] livepatch: enabling patch 'livepatch_callbacks_demo'
|
|
||||||
[ 113.000764] livepatch: 'livepatch_callbacks_demo': initializing patching transition
|
|
||||||
[ 113.000791] livepatch_callbacks_demo: pre_patch_callback: vmlinux
|
|
||||||
[ 113.001289] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_busymod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 113.005208] livepatch: 'livepatch_callbacks_demo': starting patching transition
|
|
||||||
|
|
||||||
Load a second target module (this one is an ordinary idle kernel
|
|
||||||
module). Note that *no* post-patch callbacks will be executed while the
|
|
||||||
livepatch is still in transition:
|
|
||||||
|
|
||||||
% insmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 115.012740] livepatch: applying patch 'livepatch_callbacks_demo' to loading module 'livepatch_callbacks_mod'
|
|
||||||
[ 115.013406] livepatch_callbacks_demo: pre_patch_callback: livepatch_callbacks_mod -> [MODULE_STATE_COMING] Full formed, running module_init
|
|
||||||
[ 115.015315] livepatch_callbacks_mod: livepatch_callbacks_mod_init
|
|
||||||
|
|
||||||
Request an unload of the simple kernel module. The patch is still
|
|
||||||
transitioning, so its pre-unpatch callbacks are skipped:
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-mod.ko
|
|
||||||
[ 117.022626] livepatch_callbacks_mod: livepatch_callbacks_mod_exit
|
|
||||||
[ 117.023376] livepatch: reverting patch 'livepatch_callbacks_demo' on unloading module 'livepatch_callbacks_mod'
|
|
||||||
[ 117.024533] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_mod -> [MODULE_STATE_GOING] Going away
|
|
||||||
|
|
||||||
Finally the livepatch is disabled. Since none of the patch's
|
|
||||||
klp_object's post-patch callbacks executed, the remaining klp_object's
|
|
||||||
pre-unpatch callbacks are skipped:
|
|
||||||
|
|
||||||
% echo 0 > /sys/kernel/livepatch/livepatch_callbacks_demo/enabled
|
|
||||||
[ 119.035408] livepatch: 'livepatch_callbacks_demo': reversing transition from patching to unpatching
|
|
||||||
[ 119.035485] livepatch: 'livepatch_callbacks_demo': starting unpatching transition
|
|
||||||
[ 119.711166] livepatch: 'livepatch_callbacks_demo': completing unpatching transition
|
|
||||||
[ 119.714179] livepatch_callbacks_demo: post_unpatch_callback: vmlinux
|
|
||||||
[ 119.714653] livepatch_callbacks_demo: post_unpatch_callback: livepatch_callbacks_busymod -> [MODULE_STATE_LIVE] Normal state
|
|
||||||
[ 119.715437] livepatch: 'livepatch_callbacks_demo': unpatching complete
|
|
||||||
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-demo.ko
|
|
||||||
% rmmod samples/livepatch/livepatch-callbacks-busymod.ko
|
|
||||||
[ 141.279111] livepatch_callbacks_busymod: busymod_work_func exit
|
|
||||||
[ 141.279760] livepatch_callbacks_busymod: livepatch_callbacks_mod_exit
|
|
||||||
|
102
Documentation/livepatch/cumulative-patches.txt
Normal file
102
Documentation/livepatch/cumulative-patches.txt
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
===================================
|
||||||
|
Atomic Replace & Cumulative Patches
|
||||||
|
===================================
|
||||||
|
|
||||||
|
There might be dependencies between livepatches. If multiple patches need
|
||||||
|
to do different changes to the same function(s) then we need to define
|
||||||
|
an order in which the patches will be installed. And function implementations
|
||||||
|
from any newer livepatch must be done on top of the older ones.
|
||||||
|
|
||||||
|
This might become a maintenance nightmare. Especially when more patches
|
||||||
|
modified the same function in different ways.
|
||||||
|
|
||||||
|
An elegant solution comes with the feature called "Atomic Replace". It allows
|
||||||
|
creation of so called "Cumulative Patches". They include all wanted changes
|
||||||
|
from all older livepatches and completely replace them in one transition.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
The atomic replace can be enabled by setting "replace" flag in struct klp_patch,
|
||||||
|
for example:
|
||||||
|
|
||||||
|
static struct klp_patch patch = {
|
||||||
|
.mod = THIS_MODULE,
|
||||||
|
.objs = objs,
|
||||||
|
.replace = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
All processes are then migrated to use the code only from the new patch.
|
||||||
|
Once the transition is finished, all older patches are automatically
|
||||||
|
disabled.
|
||||||
|
|
||||||
|
Ftrace handlers are transparently removed from functions that are no
|
||||||
|
longer modified by the new cumulative patch.
|
||||||
|
|
||||||
|
As a result, the livepatch authors might maintain sources only for one
|
||||||
|
cumulative patch. It helps to keep the patch consistent while adding or
|
||||||
|
removing various fixes or features.
|
||||||
|
|
||||||
|
Users could keep only the last patch installed on the system after
|
||||||
|
the transition to has finished. It helps to clearly see what code is
|
||||||
|
actually in use. Also the livepatch might then be seen as a "normal"
|
||||||
|
module that modifies the kernel behavior. The only difference is that
|
||||||
|
it can be updated at runtime without breaking its functionality.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
The atomic replace allows:
|
||||||
|
|
||||||
|
+ Atomically revert some functions in a previous patch while
|
||||||
|
upgrading other functions.
|
||||||
|
|
||||||
|
+ Remove eventual performance impact caused by core redirection
|
||||||
|
for functions that are no longer patched.
|
||||||
|
|
||||||
|
+ Decrease user confusion about dependencies between livepatches.
|
||||||
|
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
------------
|
||||||
|
|
||||||
|
+ Once the operation finishes, there is no straightforward way
|
||||||
|
to reverse it and restore the replaced patches atomically.
|
||||||
|
|
||||||
|
A good practice is to set .replace flag in any released livepatch.
|
||||||
|
Then re-adding an older livepatch is equivalent to downgrading
|
||||||
|
to that patch. This is safe as long as the livepatches do _not_ do
|
||||||
|
extra modifications in (un)patching callbacks or in the module_init()
|
||||||
|
or module_exit() functions, see below.
|
||||||
|
|
||||||
|
Also note that the replaced patch can be removed and loaded again
|
||||||
|
only when the transition was not forced.
|
||||||
|
|
||||||
|
|
||||||
|
+ Only the (un)patching callbacks from the _new_ cumulative livepatch are
|
||||||
|
executed. Any callbacks from the replaced patches are ignored.
|
||||||
|
|
||||||
|
In other words, the cumulative patch is responsible for doing any actions
|
||||||
|
that are necessary to properly replace any older patch.
|
||||||
|
|
||||||
|
As a result, it might be dangerous to replace newer cumulative patches by
|
||||||
|
older ones. The old livepatches might not provide the necessary callbacks.
|
||||||
|
|
||||||
|
This might be seen as a limitation in some scenarios. But it makes life
|
||||||
|
easier in many others. Only the new cumulative livepatch knows what
|
||||||
|
fixes/features are added/removed and what special actions are necessary
|
||||||
|
for a smooth transition.
|
||||||
|
|
||||||
|
In any case, it would be a nightmare to think about the order of
|
||||||
|
the various callbacks and their interactions if the callbacks from all
|
||||||
|
enabled patches were called.
|
||||||
|
|
||||||
|
|
||||||
|
+ There is no special handling of shadow variables. Livepatch authors
|
||||||
|
must create their own rules how to pass them from one cumulative
|
||||||
|
patch to the other. Especially that they should not blindly remove
|
||||||
|
them in module_exit() functions.
|
||||||
|
|
||||||
|
A good practice might be to remove shadow variables in the post-unpatch
|
||||||
|
callback. It is called only when the livepatch is properly disabled.
|
@ -12,12 +12,12 @@ Table of Contents:
|
|||||||
4. Livepatch module
|
4. Livepatch module
|
||||||
4.1. New functions
|
4.1. New functions
|
||||||
4.2. Metadata
|
4.2. Metadata
|
||||||
4.3. Livepatch module handling
|
|
||||||
5. Livepatch life-cycle
|
5. Livepatch life-cycle
|
||||||
5.1. Registration
|
5.1. Loading
|
||||||
5.2. Enabling
|
5.2. Enabling
|
||||||
5.3. Disabling
|
5.3. Replacing
|
||||||
5.4. Unregistration
|
5.4. Disabling
|
||||||
|
5.5. Removing
|
||||||
6. Sysfs
|
6. Sysfs
|
||||||
7. Limitations
|
7. Limitations
|
||||||
|
|
||||||
@ -143,9 +143,9 @@ without HAVE_RELIABLE_STACKTRACE are not considered fully supported by
|
|||||||
the kernel livepatching.
|
the kernel livepatching.
|
||||||
|
|
||||||
The /sys/kernel/livepatch/<patch>/transition file shows whether a patch
|
The /sys/kernel/livepatch/<patch>/transition file shows whether a patch
|
||||||
is in transition. Only a single patch (the topmost patch on the stack)
|
is in transition. Only a single patch can be in transition at a given
|
||||||
can be in transition at a given time. A patch can remain in transition
|
time. A patch can remain in transition indefinitely, if any of the tasks
|
||||||
indefinitely, if any of the tasks are stuck in the initial patch state.
|
are stuck in the initial patch state.
|
||||||
|
|
||||||
A transition can be reversed and effectively canceled by writing the
|
A transition can be reversed and effectively canceled by writing the
|
||||||
opposite value to the /sys/kernel/livepatch/<patch>/enabled file while
|
opposite value to the /sys/kernel/livepatch/<patch>/enabled file while
|
||||||
@ -158,12 +158,11 @@ If a patch is in transition, this file shows 0 to indicate the task is
|
|||||||
unpatched and 1 to indicate it's patched. Otherwise, if no patch is in
|
unpatched and 1 to indicate it's patched. Otherwise, if no patch is in
|
||||||
transition, it shows -1. Any tasks which are blocking the transition
|
transition, it shows -1. Any tasks which are blocking the transition
|
||||||
can be signaled with SIGSTOP and SIGCONT to force them to change their
|
can be signaled with SIGSTOP and SIGCONT to force them to change their
|
||||||
patched state. This may be harmful to the system though.
|
patched state. This may be harmful to the system though. Sending a fake signal
|
||||||
/sys/kernel/livepatch/<patch>/signal attribute provides a better alternative.
|
to all remaining blocking tasks is a better alternative. No proper signal is
|
||||||
Writing 1 to the attribute sends a fake signal to all remaining blocking
|
actually delivered (there is no data in signal pending structures). Tasks are
|
||||||
tasks. No proper signal is actually delivered (there is no data in signal
|
interrupted or woken up, and forced to change their patched state. The fake
|
||||||
pending structures). Tasks are interrupted or woken up, and forced to change
|
signal is automatically sent every 15 seconds.
|
||||||
their patched state.
|
|
||||||
|
|
||||||
Administrator can also affect a transition through
|
Administrator can also affect a transition through
|
||||||
/sys/kernel/livepatch/<patch>/force attribute. Writing 1 there clears
|
/sys/kernel/livepatch/<patch>/force attribute. Writing 1 there clears
|
||||||
@ -298,117 +297,110 @@ into three levels:
|
|||||||
see the "Consistency model" section.
|
see the "Consistency model" section.
|
||||||
|
|
||||||
|
|
||||||
4.3. Livepatch module handling
|
|
||||||
------------------------------
|
|
||||||
|
|
||||||
The usual behavior is that the new functions will get used when
|
|
||||||
the livepatch module is loaded. For this, the module init() function
|
|
||||||
has to register the patch (struct klp_patch) and enable it. See the
|
|
||||||
section "Livepatch life-cycle" below for more details about these
|
|
||||||
two operations.
|
|
||||||
|
|
||||||
Module removal is only safe when there are no users of the underlying
|
|
||||||
functions. This is the reason why the force feature permanently disables
|
|
||||||
the removal. The forced tasks entered the functions but we cannot say
|
|
||||||
that they returned back. Therefore it cannot be decided when the
|
|
||||||
livepatch module can be safely removed. When the system is successfully
|
|
||||||
transitioned to a new patch state (patched/unpatched) without being
|
|
||||||
forced it is guaranteed that no task sleeps or runs in the old code.
|
|
||||||
|
|
||||||
|
|
||||||
5. Livepatch life-cycle
|
5. Livepatch life-cycle
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
Livepatching defines four basic operations that define the life cycle of each
|
Livepatching can be described by five basic operations:
|
||||||
live patch: registration, enabling, disabling and unregistration. There are
|
loading, enabling, replacing, disabling, removing.
|
||||||
several reasons why it is done this way.
|
|
||||||
|
|
||||||
First, the patch is applied only when all patched symbols for already
|
Where the replacing and the disabling operations are mutually
|
||||||
loaded objects are found. The error handling is much easier if this
|
exclusive. They have the same result for the given patch but
|
||||||
check is done before particular functions get redirected.
|
not for the system.
|
||||||
|
|
||||||
Second, it might take some time until the entire system is migrated with
|
|
||||||
the hybrid consistency model being used. The patch revert might block
|
|
||||||
the livepatch module removal for too long. Therefore it is useful to
|
|
||||||
revert the patch using a separate operation that might be called
|
|
||||||
explicitly. But it does not make sense to remove all information until
|
|
||||||
the livepatch module is really removed.
|
|
||||||
|
|
||||||
|
|
||||||
5.1. Registration
|
5.1. Loading
|
||||||
-----------------
|
------------
|
||||||
|
|
||||||
Each patch first has to be registered using klp_register_patch(). This makes
|
The only reasonable way is to enable the patch when the livepatch kernel
|
||||||
the patch known to the livepatch framework. Also it does some preliminary
|
module is being loaded. For this, klp_enable_patch() has to be called
|
||||||
computing and checks.
|
in the module_init() callback. There are two main reasons:
|
||||||
|
|
||||||
In particular, the patch is added into the list of known patches. The
|
First, only the module has an easy access to the related struct klp_patch.
|
||||||
addresses of the patched functions are found according to their names.
|
|
||||||
The special relocations, mentioned in the section "New functions", are
|
Second, the error code might be used to refuse loading the module when
|
||||||
applied. The relevant entries are created under
|
the patch cannot get enabled.
|
||||||
/sys/kernel/livepatch/<name>. The patch is rejected when any operation
|
|
||||||
fails.
|
|
||||||
|
|
||||||
|
|
||||||
5.2. Enabling
|
5.2. Enabling
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
Registered patches might be enabled either by calling klp_enable_patch() or
|
The livepatch gets enabled by calling klp_enable_patch() from
|
||||||
by writing '1' to /sys/kernel/livepatch/<name>/enabled. The system will
|
the module_init() callback. The system will start using the new
|
||||||
start using the new implementation of the patched functions at this stage.
|
implementation of the patched functions at this stage.
|
||||||
|
|
||||||
When a patch is enabled, livepatch enters into a transition state where
|
First, the addresses of the patched functions are found according to their
|
||||||
tasks are converging to the patched state. This is indicated by a value
|
names. The special relocations, mentioned in the section "New functions",
|
||||||
of '1' in /sys/kernel/livepatch/<name>/transition. Once all tasks have
|
are applied. The relevant entries are created under
|
||||||
been patched, the 'transition' value changes to '0'. For more
|
/sys/kernel/livepatch/<name>. The patch is rejected when any above
|
||||||
information about this process, see the "Consistency model" section.
|
operation fails.
|
||||||
|
|
||||||
If an original function is patched for the first time, a function
|
Second, livepatch enters into a transition state where tasks are converging
|
||||||
specific struct klp_ops is created and an universal ftrace handler is
|
to the patched state. If an original function is patched for the first
|
||||||
registered.
|
time, a function specific struct klp_ops is created and an universal
|
||||||
|
ftrace handler is registered[*]. This stage is indicated by a value of '1'
|
||||||
|
in /sys/kernel/livepatch/<name>/transition. For more information about
|
||||||
|
this process, see the "Consistency model" section.
|
||||||
|
|
||||||
Functions might be patched multiple times. The ftrace handler is registered
|
Finally, once all tasks have been patched, the 'transition' value changes
|
||||||
only once for the given function. Further patches just add an entry to the
|
to '0'.
|
||||||
list (see field `func_stack`) of the struct klp_ops. The last added
|
|
||||||
entry is chosen by the ftrace handler and becomes the active function
|
|
||||||
replacement.
|
|
||||||
|
|
||||||
Note that the patches might be enabled in a different order than they were
|
[*] Note that functions might be patched multiple times. The ftrace handler
|
||||||
registered.
|
is registered only once for a given function. Further patches just add
|
||||||
|
an entry to the list (see field `func_stack`) of the struct klp_ops.
|
||||||
|
The right implementation is selected by the ftrace handler, see
|
||||||
|
the "Consistency model" section.
|
||||||
|
|
||||||
|
That said, it is highly recommended to use cumulative livepatches
|
||||||
|
because they help keeping the consistency of all changes. In this case,
|
||||||
|
functions might be patched two times only during the transition period.
|
||||||
|
|
||||||
|
|
||||||
5.3. Disabling
|
5.3. Replacing
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Enabled patches might get disabled either by calling klp_disable_patch() or
|
All enabled patches might get replaced by a cumulative patch that
|
||||||
by writing '0' to /sys/kernel/livepatch/<name>/enabled. At this stage
|
has the .replace flag set.
|
||||||
either the code from the previously enabled patch or even the original
|
|
||||||
code gets used.
|
|
||||||
|
|
||||||
When a patch is disabled, livepatch enters into a transition state where
|
Once the new patch is enabled and the 'transition' finishes then
|
||||||
tasks are converging to the unpatched state. This is indicated by a
|
all the functions (struct klp_func) associated with the replaced
|
||||||
value of '1' in /sys/kernel/livepatch/<name>/transition. Once all tasks
|
patches are removed from the corresponding struct klp_ops. Also
|
||||||
have been unpatched, the 'transition' value changes to '0'. For more
|
the ftrace handler is unregistered and the struct klp_ops is
|
||||||
information about this process, see the "Consistency model" section.
|
freed when the related function is not modified by the new patch
|
||||||
|
and func_stack list becomes empty.
|
||||||
|
|
||||||
Here all the functions (struct klp_func) associated with the to-be-disabled
|
See Documentation/livepatch/cumulative-patches.txt for more details.
|
||||||
|
|
||||||
|
|
||||||
|
5.4. Disabling
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Enabled patches might get disabled by writing '0' to
|
||||||
|
/sys/kernel/livepatch/<name>/enabled.
|
||||||
|
|
||||||
|
First, livepatch enters into a transition state where tasks are converging
|
||||||
|
to the unpatched state. The system starts using either the code from
|
||||||
|
the previously enabled patch or even the original one. This stage is
|
||||||
|
indicated by a value of '1' in /sys/kernel/livepatch/<name>/transition.
|
||||||
|
For more information about this process, see the "Consistency model"
|
||||||
|
section.
|
||||||
|
|
||||||
|
Second, once all tasks have been unpatched, the 'transition' value changes
|
||||||
|
to '0'. All the functions (struct klp_func) associated with the to-be-disabled
|
||||||
patch are removed from the corresponding struct klp_ops. The ftrace handler
|
patch are removed from the corresponding struct klp_ops. The ftrace handler
|
||||||
is unregistered and the struct klp_ops is freed when the func_stack list
|
is unregistered and the struct klp_ops is freed when the func_stack list
|
||||||
becomes empty.
|
becomes empty.
|
||||||
|
|
||||||
Patches must be disabled in exactly the reverse order in which they were
|
Third, the sysfs interface is destroyed.
|
||||||
enabled. It makes the problem and the implementation much easier.
|
|
||||||
|
|
||||||
|
|
||||||
5.4. Unregistration
|
5.5. Removing
|
||||||
-------------------
|
-------------
|
||||||
|
|
||||||
Disabled patches might be unregistered by calling klp_unregister_patch().
|
Module removal is only safe when there are no users of functions provided
|
||||||
This can be done only when the patch is disabled and the code is no longer
|
by the module. This is the reason why the force feature permanently
|
||||||
used. It must be called before the livepatch module gets unloaded.
|
disables the removal. Only when the system is successfully transitioned
|
||||||
|
to a new patch state (patched/unpatched) without being forced it is
|
||||||
At this stage, all the relevant sys-fs entries are removed and the patch
|
guaranteed that no task sleeps or runs in the old code.
|
||||||
is removed from the list of known patches.
|
|
||||||
|
|
||||||
|
|
||||||
6. Sysfs
|
6. Sysfs
|
||||||
@ -418,8 +410,8 @@ Information about the registered patches can be found under
|
|||||||
/sys/kernel/livepatch. The patches could be enabled and disabled
|
/sys/kernel/livepatch. The patches could be enabled and disabled
|
||||||
by writing there.
|
by writing there.
|
||||||
|
|
||||||
/sys/kernel/livepatch/<patch>/signal and /sys/kernel/livepatch/<patch>/force
|
/sys/kernel/livepatch/<patch>/force attributes allow administrator to affect a
|
||||||
attributes allow administrator to affect a patching operation.
|
patching operation.
|
||||||
|
|
||||||
See Documentation/ABI/testing/sysfs-kernel-livepatch for more details.
|
See Documentation/ABI/testing/sysfs-kernel-livepatch for more details.
|
||||||
|
|
||||||
|
@ -8967,10 +8967,10 @@ F: drivers/platform/x86/hp_accel.c
|
|||||||
|
|
||||||
LIVE PATCHING
|
LIVE PATCHING
|
||||||
M: Josh Poimboeuf <jpoimboe@redhat.com>
|
M: Josh Poimboeuf <jpoimboe@redhat.com>
|
||||||
M: Jessica Yu <jeyu@kernel.org>
|
|
||||||
M: Jiri Kosina <jikos@kernel.org>
|
M: Jiri Kosina <jikos@kernel.org>
|
||||||
M: Miroslav Benes <mbenes@suse.cz>
|
M: Miroslav Benes <mbenes@suse.cz>
|
||||||
R: Petr Mladek <pmladek@suse.com>
|
M: Petr Mladek <pmladek@suse.com>
|
||||||
|
R: Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
S: Maintained
|
S: Maintained
|
||||||
F: kernel/livepatch/
|
F: kernel/livepatch/
|
||||||
F: include/linux/livepatch.h
|
F: include/linux/livepatch.h
|
||||||
@ -8979,8 +8979,9 @@ F: arch/x86/kernel/livepatch.c
|
|||||||
F: Documentation/livepatch/
|
F: Documentation/livepatch/
|
||||||
F: Documentation/ABI/testing/sysfs-kernel-livepatch
|
F: Documentation/ABI/testing/sysfs-kernel-livepatch
|
||||||
F: samples/livepatch/
|
F: samples/livepatch/
|
||||||
|
F: tools/testing/selftests/livepatch/
|
||||||
L: live-patching@vger.kernel.org
|
L: live-patching@vger.kernel.org
|
||||||
T: git git://git.kernel.org/pub/scm/linux/kernel/git/jikos/livepatching.git
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/livepatching/livepatching.git
|
||||||
|
|
||||||
LLC (802.2)
|
LLC (802.2)
|
||||||
L: netdev@vger.kernel.org
|
L: netdev@vger.kernel.org
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/ftrace.h>
|
#include <linux/ftrace.h>
|
||||||
#include <linux/completion.h>
|
#include <linux/completion.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
|
||||||
#if IS_ENABLED(CONFIG_LIVEPATCH)
|
#if IS_ENABLED(CONFIG_LIVEPATCH)
|
||||||
|
|
||||||
@ -40,11 +41,14 @@
|
|||||||
* @new_func: pointer to the patched function code
|
* @new_func: pointer to the patched function code
|
||||||
* @old_sympos: a hint indicating which symbol position the old function
|
* @old_sympos: a hint indicating which symbol position the old function
|
||||||
* can be found (optional)
|
* can be found (optional)
|
||||||
* @old_addr: the address of the function being patched
|
* @old_func: pointer to the function being patched
|
||||||
* @kobj: kobject for sysfs resources
|
* @kobj: kobject for sysfs resources
|
||||||
|
* @node: list node for klp_object func_list
|
||||||
* @stack_node: list node for klp_ops func_stack list
|
* @stack_node: list node for klp_ops func_stack list
|
||||||
* @old_size: size of the old function
|
* @old_size: size of the old function
|
||||||
* @new_size: size of the new function
|
* @new_size: size of the new function
|
||||||
|
* @kobj_added: @kobj has been added and needs freeing
|
||||||
|
* @nop: temporary patch to use the original code again; dyn. allocated
|
||||||
* @patched: the func has been added to the klp_ops list
|
* @patched: the func has been added to the klp_ops list
|
||||||
* @transition: the func is currently being applied or reverted
|
* @transition: the func is currently being applied or reverted
|
||||||
*
|
*
|
||||||
@ -77,10 +81,13 @@ struct klp_func {
|
|||||||
unsigned long old_sympos;
|
unsigned long old_sympos;
|
||||||
|
|
||||||
/* internal */
|
/* internal */
|
||||||
unsigned long old_addr;
|
void *old_func;
|
||||||
struct kobject kobj;
|
struct kobject kobj;
|
||||||
|
struct list_head node;
|
||||||
struct list_head stack_node;
|
struct list_head stack_node;
|
||||||
unsigned long old_size, new_size;
|
unsigned long old_size, new_size;
|
||||||
|
bool kobj_added;
|
||||||
|
bool nop;
|
||||||
bool patched;
|
bool patched;
|
||||||
bool transition;
|
bool transition;
|
||||||
};
|
};
|
||||||
@ -115,8 +122,12 @@ struct klp_callbacks {
|
|||||||
* @funcs: function entries for functions to be patched in the object
|
* @funcs: function entries for functions to be patched in the object
|
||||||
* @callbacks: functions to be executed pre/post (un)patching
|
* @callbacks: functions to be executed pre/post (un)patching
|
||||||
* @kobj: kobject for sysfs resources
|
* @kobj: kobject for sysfs resources
|
||||||
|
* @func_list: dynamic list of the function entries
|
||||||
|
* @node: list node for klp_patch obj_list
|
||||||
* @mod: kernel module associated with the patched object
|
* @mod: kernel module associated with the patched object
|
||||||
* (NULL for vmlinux)
|
* (NULL for vmlinux)
|
||||||
|
* @kobj_added: @kobj has been added and needs freeing
|
||||||
|
* @dynamic: temporary object for nop functions; dynamically allocated
|
||||||
* @patched: the object's funcs have been added to the klp_ops list
|
* @patched: the object's funcs have been added to the klp_ops list
|
||||||
*/
|
*/
|
||||||
struct klp_object {
|
struct klp_object {
|
||||||
@ -127,7 +138,11 @@ struct klp_object {
|
|||||||
|
|
||||||
/* internal */
|
/* internal */
|
||||||
struct kobject kobj;
|
struct kobject kobj;
|
||||||
|
struct list_head func_list;
|
||||||
|
struct list_head node;
|
||||||
struct module *mod;
|
struct module *mod;
|
||||||
|
bool kobj_added;
|
||||||
|
bool dynamic;
|
||||||
bool patched;
|
bool patched;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,35 +150,54 @@ struct klp_object {
|
|||||||
* struct klp_patch - patch structure for live patching
|
* struct klp_patch - patch structure for live patching
|
||||||
* @mod: reference to the live patch module
|
* @mod: reference to the live patch module
|
||||||
* @objs: object entries for kernel objects to be patched
|
* @objs: object entries for kernel objects to be patched
|
||||||
* @list: list node for global list of registered patches
|
* @replace: replace all actively used patches
|
||||||
|
* @list: list node for global list of actively used patches
|
||||||
* @kobj: kobject for sysfs resources
|
* @kobj: kobject for sysfs resources
|
||||||
|
* @obj_list: dynamic list of the object entries
|
||||||
|
* @kobj_added: @kobj has been added and needs freeing
|
||||||
* @enabled: the patch is enabled (but operation may be incomplete)
|
* @enabled: the patch is enabled (but operation may be incomplete)
|
||||||
|
* @forced: was involved in a forced transition
|
||||||
|
* @free_work: patch cleanup from workqueue-context
|
||||||
* @finish: for waiting till it is safe to remove the patch module
|
* @finish: for waiting till it is safe to remove the patch module
|
||||||
*/
|
*/
|
||||||
struct klp_patch {
|
struct klp_patch {
|
||||||
/* external */
|
/* external */
|
||||||
struct module *mod;
|
struct module *mod;
|
||||||
struct klp_object *objs;
|
struct klp_object *objs;
|
||||||
|
bool replace;
|
||||||
|
|
||||||
/* internal */
|
/* internal */
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
struct kobject kobj;
|
struct kobject kobj;
|
||||||
|
struct list_head obj_list;
|
||||||
|
bool kobj_added;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
|
bool forced;
|
||||||
|
struct work_struct free_work;
|
||||||
struct completion finish;
|
struct completion finish;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define klp_for_each_object(patch, obj) \
|
#define klp_for_each_object_static(patch, obj) \
|
||||||
for (obj = patch->objs; obj->funcs || obj->name; obj++)
|
for (obj = patch->objs; obj->funcs || obj->name; obj++)
|
||||||
|
|
||||||
#define klp_for_each_func(obj, func) \
|
#define klp_for_each_object_safe(patch, obj, tmp_obj) \
|
||||||
|
list_for_each_entry_safe(obj, tmp_obj, &patch->obj_list, node)
|
||||||
|
|
||||||
|
#define klp_for_each_object(patch, obj) \
|
||||||
|
list_for_each_entry(obj, &patch->obj_list, node)
|
||||||
|
|
||||||
|
#define klp_for_each_func_static(obj, func) \
|
||||||
for (func = obj->funcs; \
|
for (func = obj->funcs; \
|
||||||
func->old_name || func->new_func || func->old_sympos; \
|
func->old_name || func->new_func || func->old_sympos; \
|
||||||
func++)
|
func++)
|
||||||
|
|
||||||
int klp_register_patch(struct klp_patch *);
|
#define klp_for_each_func_safe(obj, func, tmp_func) \
|
||||||
int klp_unregister_patch(struct klp_patch *);
|
list_for_each_entry_safe(func, tmp_func, &obj->func_list, node)
|
||||||
|
|
||||||
|
#define klp_for_each_func(obj, func) \
|
||||||
|
list_for_each_entry(func, &obj->func_list, node)
|
||||||
|
|
||||||
int klp_enable_patch(struct klp_patch *);
|
int klp_enable_patch(struct klp_patch *);
|
||||||
int klp_disable_patch(struct klp_patch *);
|
|
||||||
|
|
||||||
void arch_klp_init_object_loaded(struct klp_patch *patch,
|
void arch_klp_init_object_loaded(struct klp_patch *patch,
|
||||||
struct klp_object *obj);
|
struct klp_object *obj);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,17 @@
|
|||||||
#include <linux/livepatch.h>
|
#include <linux/livepatch.h>
|
||||||
|
|
||||||
extern struct mutex klp_mutex;
|
extern struct mutex klp_mutex;
|
||||||
|
extern struct list_head klp_patches;
|
||||||
|
|
||||||
|
#define klp_for_each_patch_safe(patch, tmp_patch) \
|
||||||
|
list_for_each_entry_safe(patch, tmp_patch, &klp_patches, list)
|
||||||
|
|
||||||
|
#define klp_for_each_patch(patch) \
|
||||||
|
list_for_each_entry(patch, &klp_patches, list)
|
||||||
|
|
||||||
|
void klp_free_patch_start(struct klp_patch *patch);
|
||||||
|
void klp_discard_replaced_patches(struct klp_patch *new_patch);
|
||||||
|
void klp_discard_nops(struct klp_patch *new_patch);
|
||||||
|
|
||||||
static inline bool klp_is_object_loaded(struct klp_object *obj)
|
static inline bool klp_is_object_loaded(struct klp_object *obj)
|
||||||
{
|
{
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
static LIST_HEAD(klp_ops);
|
static LIST_HEAD(klp_ops);
|
||||||
|
|
||||||
struct klp_ops *klp_find_ops(unsigned long old_addr)
|
struct klp_ops *klp_find_ops(void *old_func)
|
||||||
{
|
{
|
||||||
struct klp_ops *ops;
|
struct klp_ops *ops;
|
||||||
struct klp_func *func;
|
struct klp_func *func;
|
||||||
@ -42,7 +42,7 @@ struct klp_ops *klp_find_ops(unsigned long old_addr)
|
|||||||
list_for_each_entry(ops, &klp_ops, node) {
|
list_for_each_entry(ops, &klp_ops, node) {
|
||||||
func = list_first_entry(&ops->func_stack, struct klp_func,
|
func = list_first_entry(&ops->func_stack, struct klp_func,
|
||||||
stack_node);
|
stack_node);
|
||||||
if (func->old_addr == old_addr)
|
if (func->old_func == old_func)
|
||||||
return ops;
|
return ops;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,7 +118,15 @@ static void notrace klp_ftrace_handler(unsigned long ip,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOPs are used to replace existing patches with original code.
|
||||||
|
* Do nothing! Setting pc would cause an infinite loop.
|
||||||
|
*/
|
||||||
|
if (func->nop)
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
klp_arch_set_pc(regs, (unsigned long)func->new_func);
|
klp_arch_set_pc(regs, (unsigned long)func->new_func);
|
||||||
|
|
||||||
unlock:
|
unlock:
|
||||||
preempt_enable_notrace();
|
preempt_enable_notrace();
|
||||||
}
|
}
|
||||||
@ -142,17 +150,18 @@ static void klp_unpatch_func(struct klp_func *func)
|
|||||||
|
|
||||||
if (WARN_ON(!func->patched))
|
if (WARN_ON(!func->patched))
|
||||||
return;
|
return;
|
||||||
if (WARN_ON(!func->old_addr))
|
if (WARN_ON(!func->old_func))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ops = klp_find_ops(func->old_addr);
|
ops = klp_find_ops(func->old_func);
|
||||||
if (WARN_ON(!ops))
|
if (WARN_ON(!ops))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (list_is_singular(&ops->func_stack)) {
|
if (list_is_singular(&ops->func_stack)) {
|
||||||
unsigned long ftrace_loc;
|
unsigned long ftrace_loc;
|
||||||
|
|
||||||
ftrace_loc = klp_get_ftrace_location(func->old_addr);
|
ftrace_loc =
|
||||||
|
klp_get_ftrace_location((unsigned long)func->old_func);
|
||||||
if (WARN_ON(!ftrace_loc))
|
if (WARN_ON(!ftrace_loc))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -174,17 +183,18 @@ static int klp_patch_func(struct klp_func *func)
|
|||||||
struct klp_ops *ops;
|
struct klp_ops *ops;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (WARN_ON(!func->old_addr))
|
if (WARN_ON(!func->old_func))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
if (WARN_ON(func->patched))
|
if (WARN_ON(func->patched))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
ops = klp_find_ops(func->old_addr);
|
ops = klp_find_ops(func->old_func);
|
||||||
if (!ops) {
|
if (!ops) {
|
||||||
unsigned long ftrace_loc;
|
unsigned long ftrace_loc;
|
||||||
|
|
||||||
ftrace_loc = klp_get_ftrace_location(func->old_addr);
|
ftrace_loc =
|
||||||
|
klp_get_ftrace_location((unsigned long)func->old_func);
|
||||||
if (!ftrace_loc) {
|
if (!ftrace_loc) {
|
||||||
pr_err("failed to find location for function '%s'\n",
|
pr_err("failed to find location for function '%s'\n",
|
||||||
func->old_name);
|
func->old_name);
|
||||||
@ -236,17 +246,28 @@ err:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void klp_unpatch_object(struct klp_object *obj)
|
static void __klp_unpatch_object(struct klp_object *obj, bool nops_only)
|
||||||
{
|
{
|
||||||
struct klp_func *func;
|
struct klp_func *func;
|
||||||
|
|
||||||
klp_for_each_func(obj, func)
|
klp_for_each_func(obj, func) {
|
||||||
|
if (nops_only && !func->nop)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (func->patched)
|
if (func->patched)
|
||||||
klp_unpatch_func(func);
|
klp_unpatch_func(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj->dynamic || !nops_only)
|
||||||
obj->patched = false;
|
obj->patched = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void klp_unpatch_object(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
__klp_unpatch_object(obj, false);
|
||||||
|
}
|
||||||
|
|
||||||
int klp_patch_object(struct klp_object *obj)
|
int klp_patch_object(struct klp_object *obj)
|
||||||
{
|
{
|
||||||
struct klp_func *func;
|
struct klp_func *func;
|
||||||
@ -267,11 +288,21 @@ int klp_patch_object(struct klp_object *obj)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void klp_unpatch_objects(struct klp_patch *patch)
|
static void __klp_unpatch_objects(struct klp_patch *patch, bool nops_only)
|
||||||
{
|
{
|
||||||
struct klp_object *obj;
|
struct klp_object *obj;
|
||||||
|
|
||||||
klp_for_each_object(patch, obj)
|
klp_for_each_object(patch, obj)
|
||||||
if (obj->patched)
|
if (obj->patched)
|
||||||
klp_unpatch_object(obj);
|
__klp_unpatch_object(obj, nops_only);
|
||||||
|
}
|
||||||
|
|
||||||
|
void klp_unpatch_objects(struct klp_patch *patch)
|
||||||
|
{
|
||||||
|
__klp_unpatch_objects(patch, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void klp_unpatch_objects_dynamic(struct klp_patch *patch)
|
||||||
|
{
|
||||||
|
__klp_unpatch_objects(patch, true);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
* struct klp_ops - structure for tracking registered ftrace ops structs
|
* struct klp_ops - structure for tracking registered ftrace ops structs
|
||||||
*
|
*
|
||||||
* A single ftrace_ops is shared between all enabled replacement functions
|
* A single ftrace_ops is shared between all enabled replacement functions
|
||||||
* (klp_func structs) which have the same old_addr. This allows the switch
|
* (klp_func structs) which have the same old_func. This allows the switch
|
||||||
* between function versions to happen instantaneously by updating the klp_ops
|
* between function versions to happen instantaneously by updating the klp_ops
|
||||||
* struct's func_stack list. The winner is the klp_func at the top of the
|
* struct's func_stack list. The winner is the klp_func at the top of the
|
||||||
* func_stack (front of the list).
|
* func_stack (front of the list).
|
||||||
@ -25,10 +25,11 @@ struct klp_ops {
|
|||||||
struct ftrace_ops fops;
|
struct ftrace_ops fops;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct klp_ops *klp_find_ops(unsigned long old_addr);
|
struct klp_ops *klp_find_ops(void *old_func);
|
||||||
|
|
||||||
int klp_patch_object(struct klp_object *obj);
|
int klp_patch_object(struct klp_object *obj);
|
||||||
void klp_unpatch_object(struct klp_object *obj);
|
void klp_unpatch_object(struct klp_object *obj);
|
||||||
void klp_unpatch_objects(struct klp_patch *patch);
|
void klp_unpatch_objects(struct klp_patch *patch);
|
||||||
|
void klp_unpatch_objects_dynamic(struct klp_patch *patch);
|
||||||
|
|
||||||
#endif /* _LIVEPATCH_PATCH_H */
|
#endif /* _LIVEPATCH_PATCH_H */
|
||||||
|
@ -29,11 +29,13 @@
|
|||||||
#define MAX_STACK_ENTRIES 100
|
#define MAX_STACK_ENTRIES 100
|
||||||
#define STACK_ERR_BUF_SIZE 128
|
#define STACK_ERR_BUF_SIZE 128
|
||||||
|
|
||||||
|
#define SIGNALS_TIMEOUT 15
|
||||||
|
|
||||||
struct klp_patch *klp_transition_patch;
|
struct klp_patch *klp_transition_patch;
|
||||||
|
|
||||||
static int klp_target_state = KLP_UNDEFINED;
|
static int klp_target_state = KLP_UNDEFINED;
|
||||||
|
|
||||||
static bool klp_forced = false;
|
static unsigned int klp_signals_cnt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This work can be performed periodically to finish patching or unpatching any
|
* This work can be performed periodically to finish patching or unpatching any
|
||||||
@ -87,6 +89,11 @@ static void klp_complete_transition(void)
|
|||||||
klp_transition_patch->mod->name,
|
klp_transition_patch->mod->name,
|
||||||
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");
|
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");
|
||||||
|
|
||||||
|
if (klp_transition_patch->replace && klp_target_state == KLP_PATCHED) {
|
||||||
|
klp_discard_replaced_patches(klp_transition_patch);
|
||||||
|
klp_discard_nops(klp_transition_patch);
|
||||||
|
}
|
||||||
|
|
||||||
if (klp_target_state == KLP_UNPATCHED) {
|
if (klp_target_state == KLP_UNPATCHED) {
|
||||||
/*
|
/*
|
||||||
* All tasks have transitioned to KLP_UNPATCHED so we can now
|
* All tasks have transitioned to KLP_UNPATCHED so we can now
|
||||||
@ -136,13 +143,6 @@ static void klp_complete_transition(void)
|
|||||||
pr_notice("'%s': %s complete\n", klp_transition_patch->mod->name,
|
pr_notice("'%s': %s complete\n", klp_transition_patch->mod->name,
|
||||||
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");
|
klp_target_state == KLP_PATCHED ? "patching" : "unpatching");
|
||||||
|
|
||||||
/*
|
|
||||||
* klp_forced set implies unbounded increase of module's ref count if
|
|
||||||
* the module is disabled/enabled in a loop.
|
|
||||||
*/
|
|
||||||
if (!klp_forced && klp_target_state == KLP_UNPATCHED)
|
|
||||||
module_put(klp_transition_patch->mod);
|
|
||||||
|
|
||||||
klp_target_state = KLP_UNDEFINED;
|
klp_target_state = KLP_UNDEFINED;
|
||||||
klp_transition_patch = NULL;
|
klp_transition_patch = NULL;
|
||||||
}
|
}
|
||||||
@ -224,11 +224,11 @@ static int klp_check_stack_func(struct klp_func *func,
|
|||||||
* Check for the to-be-patched function
|
* Check for the to-be-patched function
|
||||||
* (the previous func).
|
* (the previous func).
|
||||||
*/
|
*/
|
||||||
ops = klp_find_ops(func->old_addr);
|
ops = klp_find_ops(func->old_func);
|
||||||
|
|
||||||
if (list_is_singular(&ops->func_stack)) {
|
if (list_is_singular(&ops->func_stack)) {
|
||||||
/* original function */
|
/* original function */
|
||||||
func_addr = func->old_addr;
|
func_addr = (unsigned long)func->old_func;
|
||||||
func_size = func->old_size;
|
func_size = func->old_size;
|
||||||
} else {
|
} else {
|
||||||
/* previously patched function */
|
/* previously patched function */
|
||||||
@ -347,6 +347,47 @@ done:
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sends a fake signal to all non-kthread tasks with TIF_PATCH_PENDING set.
|
||||||
|
* Kthreads with TIF_PATCH_PENDING set are woken up.
|
||||||
|
*/
|
||||||
|
static void klp_send_signals(void)
|
||||||
|
{
|
||||||
|
struct task_struct *g, *task;
|
||||||
|
|
||||||
|
if (klp_signals_cnt == SIGNALS_TIMEOUT)
|
||||||
|
pr_notice("signaling remaining tasks\n");
|
||||||
|
|
||||||
|
read_lock(&tasklist_lock);
|
||||||
|
for_each_process_thread(g, task) {
|
||||||
|
if (!klp_patch_pending(task))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* There is a small race here. We could see TIF_PATCH_PENDING
|
||||||
|
* set and decide to wake up a kthread or send a fake signal.
|
||||||
|
* Meanwhile the task could migrate itself and the action
|
||||||
|
* would be meaningless. It is not serious though.
|
||||||
|
*/
|
||||||
|
if (task->flags & PF_KTHREAD) {
|
||||||
|
/*
|
||||||
|
* Wake up a kthread which sleeps interruptedly and
|
||||||
|
* still has not been migrated.
|
||||||
|
*/
|
||||||
|
wake_up_state(task, TASK_INTERRUPTIBLE);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Send fake signal to all non-kthread tasks which are
|
||||||
|
* still not migrated.
|
||||||
|
*/
|
||||||
|
spin_lock_irq(&task->sighand->siglock);
|
||||||
|
signal_wake_up(task, 0);
|
||||||
|
spin_unlock_irq(&task->sighand->siglock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
read_unlock(&tasklist_lock);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try to switch all remaining tasks to the target patch state by walking the
|
* Try to switch all remaining tasks to the target patch state by walking the
|
||||||
* stacks of sleeping tasks and looking for any to-be-patched or
|
* stacks of sleeping tasks and looking for any to-be-patched or
|
||||||
@ -359,6 +400,7 @@ void klp_try_complete_transition(void)
|
|||||||
{
|
{
|
||||||
unsigned int cpu;
|
unsigned int cpu;
|
||||||
struct task_struct *g, *task;
|
struct task_struct *g, *task;
|
||||||
|
struct klp_patch *patch;
|
||||||
bool complete = true;
|
bool complete = true;
|
||||||
|
|
||||||
WARN_ON_ONCE(klp_target_state == KLP_UNDEFINED);
|
WARN_ON_ONCE(klp_target_state == KLP_UNDEFINED);
|
||||||
@ -396,6 +438,10 @@ void klp_try_complete_transition(void)
|
|||||||
put_online_cpus();
|
put_online_cpus();
|
||||||
|
|
||||||
if (!complete) {
|
if (!complete) {
|
||||||
|
if (klp_signals_cnt && !(klp_signals_cnt % SIGNALS_TIMEOUT))
|
||||||
|
klp_send_signals();
|
||||||
|
klp_signals_cnt++;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Some tasks weren't able to be switched over. Try again
|
* Some tasks weren't able to be switched over. Try again
|
||||||
* later and/or wait for other methods like kernel exit
|
* later and/or wait for other methods like kernel exit
|
||||||
@ -407,7 +453,18 @@ void klp_try_complete_transition(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* we're done, now cleanup the data structures */
|
/* we're done, now cleanup the data structures */
|
||||||
|
patch = klp_transition_patch;
|
||||||
klp_complete_transition();
|
klp_complete_transition();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* It would make more sense to free the patch in
|
||||||
|
* klp_complete_transition() but it is called also
|
||||||
|
* from klp_cancel_transition().
|
||||||
|
*/
|
||||||
|
if (!patch->enabled) {
|
||||||
|
klp_free_patch_start(patch);
|
||||||
|
schedule_work(&patch->free_work);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -446,6 +503,8 @@ void klp_start_transition(void)
|
|||||||
if (task->patch_state != klp_target_state)
|
if (task->patch_state != klp_target_state)
|
||||||
set_tsk_thread_flag(task, TIF_PATCH_PENDING);
|
set_tsk_thread_flag(task, TIF_PATCH_PENDING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
klp_signals_cnt = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -568,47 +627,6 @@ void klp_copy_process(struct task_struct *child)
|
|||||||
/* TIF_PATCH_PENDING gets copied in setup_thread_stack() */
|
/* TIF_PATCH_PENDING gets copied in setup_thread_stack() */
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Sends a fake signal to all non-kthread tasks with TIF_PATCH_PENDING set.
|
|
||||||
* Kthreads with TIF_PATCH_PENDING set are woken up. Only admin can request this
|
|
||||||
* action currently.
|
|
||||||
*/
|
|
||||||
void klp_send_signals(void)
|
|
||||||
{
|
|
||||||
struct task_struct *g, *task;
|
|
||||||
|
|
||||||
pr_notice("signaling remaining tasks\n");
|
|
||||||
|
|
||||||
read_lock(&tasklist_lock);
|
|
||||||
for_each_process_thread(g, task) {
|
|
||||||
if (!klp_patch_pending(task))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* There is a small race here. We could see TIF_PATCH_PENDING
|
|
||||||
* set and decide to wake up a kthread or send a fake signal.
|
|
||||||
* Meanwhile the task could migrate itself and the action
|
|
||||||
* would be meaningless. It is not serious though.
|
|
||||||
*/
|
|
||||||
if (task->flags & PF_KTHREAD) {
|
|
||||||
/*
|
|
||||||
* Wake up a kthread which sleeps interruptedly and
|
|
||||||
* still has not been migrated.
|
|
||||||
*/
|
|
||||||
wake_up_state(task, TASK_INTERRUPTIBLE);
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* Send fake signal to all non-kthread tasks which are
|
|
||||||
* still not migrated.
|
|
||||||
*/
|
|
||||||
spin_lock_irq(&task->sighand->siglock);
|
|
||||||
signal_wake_up(task, 0);
|
|
||||||
spin_unlock_irq(&task->sighand->siglock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
read_unlock(&tasklist_lock);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Drop TIF_PATCH_PENDING of all tasks on admin's request. This forces an
|
* Drop TIF_PATCH_PENDING of all tasks on admin's request. This forces an
|
||||||
* existing transition to finish.
|
* existing transition to finish.
|
||||||
@ -620,6 +638,7 @@ void klp_send_signals(void)
|
|||||||
*/
|
*/
|
||||||
void klp_force_transition(void)
|
void klp_force_transition(void)
|
||||||
{
|
{
|
||||||
|
struct klp_patch *patch;
|
||||||
struct task_struct *g, *task;
|
struct task_struct *g, *task;
|
||||||
unsigned int cpu;
|
unsigned int cpu;
|
||||||
|
|
||||||
@ -633,5 +652,6 @@ void klp_force_transition(void)
|
|||||||
for_each_possible_cpu(cpu)
|
for_each_possible_cpu(cpu)
|
||||||
klp_update_patch_state(idle_task(cpu));
|
klp_update_patch_state(idle_task(cpu));
|
||||||
|
|
||||||
klp_forced = true;
|
klp_for_each_patch(patch)
|
||||||
|
patch->forced = true;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ void klp_cancel_transition(void);
|
|||||||
void klp_start_transition(void);
|
void klp_start_transition(void);
|
||||||
void klp_try_complete_transition(void);
|
void klp_try_complete_transition(void);
|
||||||
void klp_reverse_transition(void);
|
void klp_reverse_transition(void);
|
||||||
void klp_send_signals(void);
|
|
||||||
void klp_force_transition(void);
|
void klp_force_transition(void);
|
||||||
|
|
||||||
#endif /* _LIVEPATCH_TRANSITION_H */
|
#endif /* _LIVEPATCH_TRANSITION_H */
|
||||||
|
@ -1985,6 +1985,28 @@ config TEST_MEMCAT_P
|
|||||||
|
|
||||||
If unsure, say N.
|
If unsure, say N.
|
||||||
|
|
||||||
|
config TEST_LIVEPATCH
|
||||||
|
tristate "Test livepatching"
|
||||||
|
default n
|
||||||
|
depends on DYNAMIC_DEBUG
|
||||||
|
depends on LIVEPATCH
|
||||||
|
depends on m
|
||||||
|
help
|
||||||
|
Test kernel livepatching features for correctness. The tests will
|
||||||
|
load test modules that will be livepatched in various scenarios.
|
||||||
|
|
||||||
|
To run all the livepatching tests:
|
||||||
|
|
||||||
|
make -C tools/testing/selftests TARGETS=livepatch run_tests
|
||||||
|
|
||||||
|
Alternatively, individual tests may be invoked:
|
||||||
|
|
||||||
|
tools/testing/selftests/livepatch/test-callbacks.sh
|
||||||
|
tools/testing/selftests/livepatch/test-livepatch.sh
|
||||||
|
tools/testing/selftests/livepatch/test-shadow-vars.sh
|
||||||
|
|
||||||
|
If unsure, say N.
|
||||||
|
|
||||||
config TEST_OBJAGG
|
config TEST_OBJAGG
|
||||||
tristate "Perform selftest on object aggreration manager"
|
tristate "Perform selftest on object aggreration manager"
|
||||||
default n
|
default n
|
||||||
@ -1993,7 +2015,6 @@ config TEST_OBJAGG
|
|||||||
Enable this option to test object aggregation manager on boot
|
Enable this option to test object aggregation manager on boot
|
||||||
(or module load).
|
(or module load).
|
||||||
|
|
||||||
If unsure, say N.
|
|
||||||
|
|
||||||
endif # RUNTIME_TESTING_MENU
|
endif # RUNTIME_TESTING_MENU
|
||||||
|
|
||||||
|
@ -78,6 +78,8 @@ obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o
|
|||||||
obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
|
obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
|
||||||
obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
|
obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
|
||||||
|
|
||||||
|
obj-$(CONFIG_TEST_LIVEPATCH) += livepatch/
|
||||||
|
|
||||||
ifeq ($(CONFIG_DEBUG_KOBJECT),y)
|
ifeq ($(CONFIG_DEBUG_KOBJECT),y)
|
||||||
CFLAGS_kobject.o += -DDEBUG
|
CFLAGS_kobject.o += -DDEBUG
|
||||||
CFLAGS_kobject_uevent.o += -DDEBUG
|
CFLAGS_kobject_uevent.o += -DDEBUG
|
||||||
|
15
lib/livepatch/Makefile
Normal file
15
lib/livepatch/Makefile
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
#
|
||||||
|
# Makefile for livepatch test code.
|
||||||
|
|
||||||
|
obj-$(CONFIG_TEST_LIVEPATCH) += test_klp_atomic_replace.o \
|
||||||
|
test_klp_callbacks_demo.o \
|
||||||
|
test_klp_callbacks_demo2.o \
|
||||||
|
test_klp_callbacks_busy.o \
|
||||||
|
test_klp_callbacks_mod.o \
|
||||||
|
test_klp_livepatch.o \
|
||||||
|
test_klp_shadow_vars.o
|
||||||
|
|
||||||
|
# Target modules to be livepatched require CC_FLAGS_FTRACE
|
||||||
|
CFLAGS_test_klp_callbacks_busy.o += $(CC_FLAGS_FTRACE)
|
||||||
|
CFLAGS_test_klp_callbacks_mod.o += $(CC_FLAGS_FTRACE)
|
57
lib/livepatch/test_klp_atomic_replace.c
Normal file
57
lib/livepatch/test_klp_atomic_replace.c
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/livepatch.h>
|
||||||
|
|
||||||
|
static int replace;
|
||||||
|
module_param(replace, int, 0644);
|
||||||
|
MODULE_PARM_DESC(replace, "replace (default=0)");
|
||||||
|
|
||||||
|
#include <linux/seq_file.h>
|
||||||
|
static int livepatch_meminfo_proc_show(struct seq_file *m, void *v)
|
||||||
|
{
|
||||||
|
seq_printf(m, "%s: %s\n", THIS_MODULE->name,
|
||||||
|
"this has been live patched");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct klp_func funcs[] = {
|
||||||
|
{
|
||||||
|
.old_name = "meminfo_proc_show",
|
||||||
|
.new_func = livepatch_meminfo_proc_show,
|
||||||
|
}, {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_object objs[] = {
|
||||||
|
{
|
||||||
|
/* name being NULL means vmlinux */
|
||||||
|
.funcs = funcs,
|
||||||
|
}, {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_patch patch = {
|
||||||
|
.mod = THIS_MODULE,
|
||||||
|
.objs = objs,
|
||||||
|
/* set .replace in the init function below for demo purposes */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int test_klp_atomic_replace_init(void)
|
||||||
|
{
|
||||||
|
patch.replace = replace;
|
||||||
|
return klp_enable_patch(&patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_klp_atomic_replace_exit(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(test_klp_atomic_replace_init);
|
||||||
|
module_exit(test_klp_atomic_replace_exit);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_INFO(livepatch, "Y");
|
||||||
|
MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Livepatch test: atomic replace");
|
43
lib/livepatch/test_klp_callbacks_busy.c
Normal file
43
lib/livepatch/test_klp_callbacks_busy.c
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/workqueue.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
|
||||||
|
static int sleep_secs;
|
||||||
|
module_param(sleep_secs, int, 0644);
|
||||||
|
MODULE_PARM_DESC(sleep_secs, "sleep_secs (default=0)");
|
||||||
|
|
||||||
|
static void busymod_work_func(struct work_struct *work);
|
||||||
|
static DECLARE_DELAYED_WORK(work, busymod_work_func);
|
||||||
|
|
||||||
|
static void busymod_work_func(struct work_struct *work)
|
||||||
|
{
|
||||||
|
pr_info("%s, sleeping %d seconds ...\n", __func__, sleep_secs);
|
||||||
|
msleep(sleep_secs * 1000);
|
||||||
|
pr_info("%s exit\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_klp_callbacks_busy_init(void)
|
||||||
|
{
|
||||||
|
pr_info("%s\n", __func__);
|
||||||
|
schedule_delayed_work(&work,
|
||||||
|
msecs_to_jiffies(1000 * 0));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_klp_callbacks_busy_exit(void)
|
||||||
|
{
|
||||||
|
cancel_delayed_work_sync(&work);
|
||||||
|
pr_info("%s\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(test_klp_callbacks_busy_init);
|
||||||
|
module_exit(test_klp_callbacks_busy_exit);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Livepatch test: busy target module");
|
121
lib/livepatch/test_klp_callbacks_demo.c
Normal file
121
lib/livepatch/test_klp_callbacks_demo.c
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/livepatch.h>
|
||||||
|
|
||||||
|
static int pre_patch_ret;
|
||||||
|
module_param(pre_patch_ret, int, 0644);
|
||||||
|
MODULE_PARM_DESC(pre_patch_ret, "pre_patch_ret (default=0)");
|
||||||
|
|
||||||
|
static const char *const module_state[] = {
|
||||||
|
[MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
|
||||||
|
[MODULE_STATE_COMING] = "[MODULE_STATE_COMING] Full formed, running module_init",
|
||||||
|
[MODULE_STATE_GOING] = "[MODULE_STATE_GOING] Going away",
|
||||||
|
[MODULE_STATE_UNFORMED] = "[MODULE_STATE_UNFORMED] Still setting it up",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void callback_info(const char *callback, struct klp_object *obj)
|
||||||
|
{
|
||||||
|
if (obj->mod)
|
||||||
|
pr_info("%s: %s -> %s\n", callback, obj->mod->name,
|
||||||
|
module_state[obj->mod->state]);
|
||||||
|
else
|
||||||
|
pr_info("%s: vmlinux\n", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object patching (ie, patch enablement) */
|
||||||
|
static int pre_patch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
return pre_patch_ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object unpatching (ie, patch disablement) */
|
||||||
|
static void post_patch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object unpatching (ie, patch disablement) */
|
||||||
|
static void pre_unpatch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object unpatching (ie, patch disablement) */
|
||||||
|
static void post_unpatch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void patched_work_func(struct work_struct *work)
|
||||||
|
{
|
||||||
|
pr_info("%s\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct klp_func no_funcs[] = {
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_func busymod_funcs[] = {
|
||||||
|
{
|
||||||
|
.old_name = "busymod_work_func",
|
||||||
|
.new_func = patched_work_func,
|
||||||
|
}, {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_object objs[] = {
|
||||||
|
{
|
||||||
|
.name = NULL, /* vmlinux */
|
||||||
|
.funcs = no_funcs,
|
||||||
|
.callbacks = {
|
||||||
|
.pre_patch = pre_patch_callback,
|
||||||
|
.post_patch = post_patch_callback,
|
||||||
|
.pre_unpatch = pre_unpatch_callback,
|
||||||
|
.post_unpatch = post_unpatch_callback,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
.name = "test_klp_callbacks_mod",
|
||||||
|
.funcs = no_funcs,
|
||||||
|
.callbacks = {
|
||||||
|
.pre_patch = pre_patch_callback,
|
||||||
|
.post_patch = post_patch_callback,
|
||||||
|
.pre_unpatch = pre_unpatch_callback,
|
||||||
|
.post_unpatch = post_unpatch_callback,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
.name = "test_klp_callbacks_busy",
|
||||||
|
.funcs = busymod_funcs,
|
||||||
|
.callbacks = {
|
||||||
|
.pre_patch = pre_patch_callback,
|
||||||
|
.post_patch = post_patch_callback,
|
||||||
|
.pre_unpatch = pre_unpatch_callback,
|
||||||
|
.post_unpatch = post_unpatch_callback,
|
||||||
|
},
|
||||||
|
}, { }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_patch patch = {
|
||||||
|
.mod = THIS_MODULE,
|
||||||
|
.objs = objs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int test_klp_callbacks_demo_init(void)
|
||||||
|
{
|
||||||
|
return klp_enable_patch(&patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_klp_callbacks_demo_exit(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(test_klp_callbacks_demo_init);
|
||||||
|
module_exit(test_klp_callbacks_demo_exit);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_INFO(livepatch, "Y");
|
||||||
|
MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Livepatch test: livepatch demo");
|
93
lib/livepatch/test_klp_callbacks_demo2.c
Normal file
93
lib/livepatch/test_klp_callbacks_demo2.c
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/livepatch.h>
|
||||||
|
|
||||||
|
static int replace;
|
||||||
|
module_param(replace, int, 0644);
|
||||||
|
MODULE_PARM_DESC(replace, "replace (default=0)");
|
||||||
|
|
||||||
|
static const char *const module_state[] = {
|
||||||
|
[MODULE_STATE_LIVE] = "[MODULE_STATE_LIVE] Normal state",
|
||||||
|
[MODULE_STATE_COMING] = "[MODULE_STATE_COMING] Full formed, running module_init",
|
||||||
|
[MODULE_STATE_GOING] = "[MODULE_STATE_GOING] Going away",
|
||||||
|
[MODULE_STATE_UNFORMED] = "[MODULE_STATE_UNFORMED] Still setting it up",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void callback_info(const char *callback, struct klp_object *obj)
|
||||||
|
{
|
||||||
|
if (obj->mod)
|
||||||
|
pr_info("%s: %s -> %s\n", callback, obj->mod->name,
|
||||||
|
module_state[obj->mod->state]);
|
||||||
|
else
|
||||||
|
pr_info("%s: vmlinux\n", callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object patching (ie, patch enablement) */
|
||||||
|
static int pre_patch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object unpatching (ie, patch disablement) */
|
||||||
|
static void post_patch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object unpatching (ie, patch disablement) */
|
||||||
|
static void pre_unpatch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Executed on object unpatching (ie, patch disablement) */
|
||||||
|
static void post_unpatch_callback(struct klp_object *obj)
|
||||||
|
{
|
||||||
|
callback_info(__func__, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct klp_func no_funcs[] = {
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_object objs[] = {
|
||||||
|
{
|
||||||
|
.name = NULL, /* vmlinux */
|
||||||
|
.funcs = no_funcs,
|
||||||
|
.callbacks = {
|
||||||
|
.pre_patch = pre_patch_callback,
|
||||||
|
.post_patch = post_patch_callback,
|
||||||
|
.pre_unpatch = pre_unpatch_callback,
|
||||||
|
.post_unpatch = post_unpatch_callback,
|
||||||
|
},
|
||||||
|
}, { }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_patch patch = {
|
||||||
|
.mod = THIS_MODULE,
|
||||||
|
.objs = objs,
|
||||||
|
/* set .replace in the init function below for demo purposes */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int test_klp_callbacks_demo2_init(void)
|
||||||
|
{
|
||||||
|
patch.replace = replace;
|
||||||
|
return klp_enable_patch(&patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_klp_callbacks_demo2_exit(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(test_klp_callbacks_demo2_init);
|
||||||
|
module_exit(test_klp_callbacks_demo2_exit);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_INFO(livepatch, "Y");
|
||||||
|
MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Livepatch test: livepatch demo2");
|
24
lib/livepatch/test_klp_callbacks_mod.c
Normal file
24
lib/livepatch/test_klp_callbacks_mod.c
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
|
||||||
|
static int test_klp_callbacks_mod_init(void)
|
||||||
|
{
|
||||||
|
pr_info("%s\n", __func__);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_klp_callbacks_mod_exit(void)
|
||||||
|
{
|
||||||
|
pr_info("%s\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(test_klp_callbacks_mod_init);
|
||||||
|
module_exit(test_klp_callbacks_mod_exit);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Livepatch test: target module");
|
51
lib/livepatch/test_klp_livepatch.c
Normal file
51
lib/livepatch/test_klp_livepatch.c
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (C) 2014 Seth Jennings <sjenning@redhat.com>
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/livepatch.h>
|
||||||
|
|
||||||
|
#include <linux/seq_file.h>
|
||||||
|
static int livepatch_cmdline_proc_show(struct seq_file *m, void *v)
|
||||||
|
{
|
||||||
|
seq_printf(m, "%s: %s\n", THIS_MODULE->name,
|
||||||
|
"this has been live patched");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct klp_func funcs[] = {
|
||||||
|
{
|
||||||
|
.old_name = "cmdline_proc_show",
|
||||||
|
.new_func = livepatch_cmdline_proc_show,
|
||||||
|
}, { }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_object objs[] = {
|
||||||
|
{
|
||||||
|
/* name being NULL means vmlinux */
|
||||||
|
.funcs = funcs,
|
||||||
|
}, { }
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct klp_patch patch = {
|
||||||
|
.mod = THIS_MODULE,
|
||||||
|
.objs = objs,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int test_klp_livepatch_init(void)
|
||||||
|
{
|
||||||
|
return klp_enable_patch(&patch);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_klp_livepatch_exit(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(test_klp_livepatch_init);
|
||||||
|
module_exit(test_klp_livepatch_exit);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_INFO(livepatch, "Y");
|
||||||
|
MODULE_AUTHOR("Seth Jennings <sjenning@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Livepatch test: livepatch module");
|
258
lib/livepatch/test_klp_shadow_vars.c
Normal file
258
lib/livepatch/test_klp_shadow_vars.c
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
// Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/list.h>
|
||||||
|
#include <linux/livepatch.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep a small list of pointers so that we can print address-agnostic
|
||||||
|
* pointer values. Use a rolling integer count to differentiate the values.
|
||||||
|
* Ironically we could have used the shadow variable API to do this, but
|
||||||
|
* let's not lean too heavily on the very code we're testing.
|
||||||
|
*/
|
||||||
|
static LIST_HEAD(ptr_list);
|
||||||
|
struct shadow_ptr {
|
||||||
|
void *ptr;
|
||||||
|
int id;
|
||||||
|
struct list_head list;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void free_ptr_list(void)
|
||||||
|
{
|
||||||
|
struct shadow_ptr *sp, *tmp_sp;
|
||||||
|
|
||||||
|
list_for_each_entry_safe(sp, tmp_sp, &ptr_list, list) {
|
||||||
|
list_del(&sp->list);
|
||||||
|
kfree(sp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ptr_id(void *ptr)
|
||||||
|
{
|
||||||
|
struct shadow_ptr *sp;
|
||||||
|
static int count;
|
||||||
|
|
||||||
|
list_for_each_entry(sp, &ptr_list, list) {
|
||||||
|
if (sp->ptr == ptr)
|
||||||
|
return sp->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
sp = kmalloc(sizeof(*sp), GFP_ATOMIC);
|
||||||
|
if (!sp)
|
||||||
|
return -ENOMEM;
|
||||||
|
sp->ptr = ptr;
|
||||||
|
sp->id = count++;
|
||||||
|
|
||||||
|
list_add(&sp->list, &ptr_list);
|
||||||
|
|
||||||
|
return sp->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shadow variable wrapper functions that echo the function and arguments
|
||||||
|
* to the kernel log for testing verification. Don't display raw pointers,
|
||||||
|
* but use the ptr_id() value instead.
|
||||||
|
*/
|
||||||
|
static void *shadow_get(void *obj, unsigned long id)
|
||||||
|
{
|
||||||
|
void *ret = klp_shadow_get(obj, id);
|
||||||
|
|
||||||
|
pr_info("klp_%s(obj=PTR%d, id=0x%lx) = PTR%d\n",
|
||||||
|
__func__, ptr_id(obj), id, ptr_id(ret));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *shadow_alloc(void *obj, unsigned long id, size_t size,
|
||||||
|
gfp_t gfp_flags, klp_shadow_ctor_t ctor,
|
||||||
|
void *ctor_data)
|
||||||
|
{
|
||||||
|
void *ret = klp_shadow_alloc(obj, id, size, gfp_flags, ctor,
|
||||||
|
ctor_data);
|
||||||
|
pr_info("klp_%s(obj=PTR%d, id=0x%lx, size=%zx, gfp_flags=%pGg), ctor=PTR%d, ctor_data=PTR%d = PTR%d\n",
|
||||||
|
__func__, ptr_id(obj), id, size, &gfp_flags, ptr_id(ctor),
|
||||||
|
ptr_id(ctor_data), ptr_id(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *shadow_get_or_alloc(void *obj, unsigned long id, size_t size,
|
||||||
|
gfp_t gfp_flags, klp_shadow_ctor_t ctor,
|
||||||
|
void *ctor_data)
|
||||||
|
{
|
||||||
|
void *ret = klp_shadow_get_or_alloc(obj, id, size, gfp_flags, ctor,
|
||||||
|
ctor_data);
|
||||||
|
pr_info("klp_%s(obj=PTR%d, id=0x%lx, size=%zx, gfp_flags=%pGg), ctor=PTR%d, ctor_data=PTR%d = PTR%d\n",
|
||||||
|
__func__, ptr_id(obj), id, size, &gfp_flags, ptr_id(ctor),
|
||||||
|
ptr_id(ctor_data), ptr_id(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void shadow_free(void *obj, unsigned long id, klp_shadow_dtor_t dtor)
|
||||||
|
{
|
||||||
|
klp_shadow_free(obj, id, dtor);
|
||||||
|
pr_info("klp_%s(obj=PTR%d, id=0x%lx, dtor=PTR%d)\n",
|
||||||
|
__func__, ptr_id(obj), id, ptr_id(dtor));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void shadow_free_all(unsigned long id, klp_shadow_dtor_t dtor)
|
||||||
|
{
|
||||||
|
klp_shadow_free_all(id, dtor);
|
||||||
|
pr_info("klp_%s(id=0x%lx, dtor=PTR%d)\n",
|
||||||
|
__func__, id, ptr_id(dtor));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Shadow variable constructor - remember simple pointer data */
|
||||||
|
static int shadow_ctor(void *obj, void *shadow_data, void *ctor_data)
|
||||||
|
{
|
||||||
|
int **shadow_int = shadow_data;
|
||||||
|
*shadow_int = ctor_data;
|
||||||
|
pr_info("%s: PTR%d -> PTR%d\n",
|
||||||
|
__func__, ptr_id(shadow_int), ptr_id(ctor_data));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void shadow_dtor(void *obj, void *shadow_data)
|
||||||
|
{
|
||||||
|
pr_info("%s(obj=PTR%d, shadow_data=PTR%d)\n",
|
||||||
|
__func__, ptr_id(obj), ptr_id(shadow_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_klp_shadow_vars_init(void)
|
||||||
|
{
|
||||||
|
void *obj = THIS_MODULE;
|
||||||
|
int id = 0x1234;
|
||||||
|
size_t size = sizeof(int *);
|
||||||
|
gfp_t gfp_flags = GFP_KERNEL;
|
||||||
|
|
||||||
|
int var1, var2, var3, var4;
|
||||||
|
int **sv1, **sv2, **sv3, **sv4;
|
||||||
|
|
||||||
|
void *ret;
|
||||||
|
|
||||||
|
ptr_id(NULL);
|
||||||
|
ptr_id(&var1);
|
||||||
|
ptr_id(&var2);
|
||||||
|
ptr_id(&var3);
|
||||||
|
ptr_id(&var4);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* With an empty shadow variable hash table, expect not to find
|
||||||
|
* any matches.
|
||||||
|
*/
|
||||||
|
ret = shadow_get(obj, id);
|
||||||
|
if (!ret)
|
||||||
|
pr_info(" got expected NULL result\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate a few shadow variables with different <obj> and <id>.
|
||||||
|
*/
|
||||||
|
sv1 = shadow_alloc(obj, id, size, gfp_flags, shadow_ctor, &var1);
|
||||||
|
if (!sv1)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
sv2 = shadow_alloc(obj + 1, id, size, gfp_flags, shadow_ctor, &var2);
|
||||||
|
if (!sv2)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
sv3 = shadow_alloc(obj, id + 1, size, gfp_flags, shadow_ctor, &var3);
|
||||||
|
if (!sv3)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify we can find our new shadow variables and that they point
|
||||||
|
* to expected data.
|
||||||
|
*/
|
||||||
|
ret = shadow_get(obj, id);
|
||||||
|
if (!ret)
|
||||||
|
return -EINVAL;
|
||||||
|
if (ret == sv1 && *sv1 == &var1)
|
||||||
|
pr_info(" got expected PTR%d -> PTR%d result\n",
|
||||||
|
ptr_id(sv1), ptr_id(*sv1));
|
||||||
|
|
||||||
|
ret = shadow_get(obj + 1, id);
|
||||||
|
if (!ret)
|
||||||
|
return -EINVAL;
|
||||||
|
if (ret == sv2 && *sv2 == &var2)
|
||||||
|
pr_info(" got expected PTR%d -> PTR%d result\n",
|
||||||
|
ptr_id(sv2), ptr_id(*sv2));
|
||||||
|
ret = shadow_get(obj, id + 1);
|
||||||
|
if (!ret)
|
||||||
|
return -EINVAL;
|
||||||
|
if (ret == sv3 && *sv3 == &var3)
|
||||||
|
pr_info(" got expected PTR%d -> PTR%d result\n",
|
||||||
|
ptr_id(sv3), ptr_id(*sv3));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allocate or get a few more, this time with the same <obj>, <id>.
|
||||||
|
* The second invocation should return the same shadow var.
|
||||||
|
*/
|
||||||
|
sv4 = shadow_get_or_alloc(obj + 2, id, size, gfp_flags, shadow_ctor, &var4);
|
||||||
|
if (!sv4)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
ret = shadow_get_or_alloc(obj + 2, id, size, gfp_flags, shadow_ctor, &var4);
|
||||||
|
if (!ret)
|
||||||
|
return -EINVAL;
|
||||||
|
if (ret == sv4 && *sv4 == &var4)
|
||||||
|
pr_info(" got expected PTR%d -> PTR%d result\n",
|
||||||
|
ptr_id(sv4), ptr_id(*sv4));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free the <obj=*, id> shadow variables and check that we can no
|
||||||
|
* longer find them.
|
||||||
|
*/
|
||||||
|
shadow_free(obj, id, shadow_dtor); /* sv1 */
|
||||||
|
ret = shadow_get(obj, id);
|
||||||
|
if (!ret)
|
||||||
|
pr_info(" got expected NULL result\n");
|
||||||
|
|
||||||
|
shadow_free(obj + 1, id, shadow_dtor); /* sv2 */
|
||||||
|
ret = shadow_get(obj + 1, id);
|
||||||
|
if (!ret)
|
||||||
|
pr_info(" got expected NULL result\n");
|
||||||
|
|
||||||
|
shadow_free(obj + 2, id, shadow_dtor); /* sv4 */
|
||||||
|
ret = shadow_get(obj + 2, id);
|
||||||
|
if (!ret)
|
||||||
|
pr_info(" got expected NULL result\n");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We should still find an <id+1> variable.
|
||||||
|
*/
|
||||||
|
ret = shadow_get(obj, id + 1);
|
||||||
|
if (!ret)
|
||||||
|
return -EINVAL;
|
||||||
|
if (ret == sv3 && *sv3 == &var3)
|
||||||
|
pr_info(" got expected PTR%d -> PTR%d result\n",
|
||||||
|
ptr_id(sv3), ptr_id(*sv3));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Free all the <id+1> variables, too.
|
||||||
|
*/
|
||||||
|
shadow_free_all(id + 1, shadow_dtor); /* sv3 */
|
||||||
|
ret = shadow_get(obj, id);
|
||||||
|
if (!ret)
|
||||||
|
pr_info(" shadow_get() got expected NULL result\n");
|
||||||
|
|
||||||
|
|
||||||
|
free_ptr_list();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_klp_shadow_vars_exit(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(test_klp_shadow_vars_init);
|
||||||
|
module_exit(test_klp_shadow_vars_exit);
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_AUTHOR("Joe Lawrence <joe.lawrence@redhat.com>");
|
||||||
|
MODULE_DESCRIPTION("Livepatch test: shadow variables");
|
@ -195,22 +195,11 @@ static struct klp_patch patch = {
|
|||||||
|
|
||||||
static int livepatch_callbacks_demo_init(void)
|
static int livepatch_callbacks_demo_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
return klp_enable_patch(&patch);
|
||||||
|
|
||||||
ret = klp_register_patch(&patch);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
ret = klp_enable_patch(&patch);
|
|
||||||
if (ret) {
|
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void livepatch_callbacks_demo_exit(void)
|
static void livepatch_callbacks_demo_exit(void)
|
||||||
{
|
{
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(livepatch_callbacks_demo_init);
|
module_init(livepatch_callbacks_demo_init);
|
||||||
|
@ -69,22 +69,11 @@ static struct klp_patch patch = {
|
|||||||
|
|
||||||
static int livepatch_init(void)
|
static int livepatch_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
return klp_enable_patch(&patch);
|
||||||
|
|
||||||
ret = klp_register_patch(&patch);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
ret = klp_enable_patch(&patch);
|
|
||||||
if (ret) {
|
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void livepatch_exit(void)
|
static void livepatch_exit(void)
|
||||||
{
|
{
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(livepatch_init);
|
module_init(livepatch_init);
|
||||||
|
@ -71,7 +71,7 @@ static int shadow_leak_ctor(void *obj, void *shadow_data, void *ctor_data)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct dummy *livepatch_fix1_dummy_alloc(void)
|
static struct dummy *livepatch_fix1_dummy_alloc(void)
|
||||||
{
|
{
|
||||||
struct dummy *d;
|
struct dummy *d;
|
||||||
void *leak;
|
void *leak;
|
||||||
@ -113,7 +113,7 @@ static void livepatch_fix1_dummy_leak_dtor(void *obj, void *shadow_data)
|
|||||||
__func__, d, *shadow_leak);
|
__func__, d, *shadow_leak);
|
||||||
}
|
}
|
||||||
|
|
||||||
void livepatch_fix1_dummy_free(struct dummy *d)
|
static void livepatch_fix1_dummy_free(struct dummy *d)
|
||||||
{
|
{
|
||||||
void **shadow_leak;
|
void **shadow_leak;
|
||||||
|
|
||||||
@ -157,25 +157,13 @@ static struct klp_patch patch = {
|
|||||||
|
|
||||||
static int livepatch_shadow_fix1_init(void)
|
static int livepatch_shadow_fix1_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
return klp_enable_patch(&patch);
|
||||||
|
|
||||||
ret = klp_register_patch(&patch);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
ret = klp_enable_patch(&patch);
|
|
||||||
if (ret) {
|
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void livepatch_shadow_fix1_exit(void)
|
static void livepatch_shadow_fix1_exit(void)
|
||||||
{
|
{
|
||||||
/* Cleanup any existing SV_LEAK shadow variables */
|
/* Cleanup any existing SV_LEAK shadow variables */
|
||||||
klp_shadow_free_all(SV_LEAK, livepatch_fix1_dummy_leak_dtor);
|
klp_shadow_free_all(SV_LEAK, livepatch_fix1_dummy_leak_dtor);
|
||||||
|
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(livepatch_shadow_fix1_init);
|
module_init(livepatch_shadow_fix1_init);
|
||||||
|
@ -50,7 +50,7 @@ struct dummy {
|
|||||||
unsigned long jiffies_expire;
|
unsigned long jiffies_expire;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool livepatch_fix2_dummy_check(struct dummy *d, unsigned long jiffies)
|
static bool livepatch_fix2_dummy_check(struct dummy *d, unsigned long jiffies)
|
||||||
{
|
{
|
||||||
int *shadow_count;
|
int *shadow_count;
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ static void livepatch_fix2_dummy_leak_dtor(void *obj, void *shadow_data)
|
|||||||
__func__, d, *shadow_leak);
|
__func__, d, *shadow_leak);
|
||||||
}
|
}
|
||||||
|
|
||||||
void livepatch_fix2_dummy_free(struct dummy *d)
|
static void livepatch_fix2_dummy_free(struct dummy *d)
|
||||||
{
|
{
|
||||||
void **shadow_leak;
|
void **shadow_leak;
|
||||||
int *shadow_count;
|
int *shadow_count;
|
||||||
@ -129,25 +129,13 @@ static struct klp_patch patch = {
|
|||||||
|
|
||||||
static int livepatch_shadow_fix2_init(void)
|
static int livepatch_shadow_fix2_init(void)
|
||||||
{
|
{
|
||||||
int ret;
|
return klp_enable_patch(&patch);
|
||||||
|
|
||||||
ret = klp_register_patch(&patch);
|
|
||||||
if (ret)
|
|
||||||
return ret;
|
|
||||||
ret = klp_enable_patch(&patch);
|
|
||||||
if (ret) {
|
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void livepatch_shadow_fix2_exit(void)
|
static void livepatch_shadow_fix2_exit(void)
|
||||||
{
|
{
|
||||||
/* Cleanup any existing SV_COUNTER shadow variables */
|
/* Cleanup any existing SV_COUNTER shadow variables */
|
||||||
klp_shadow_free_all(SV_COUNTER, NULL);
|
klp_shadow_free_all(SV_COUNTER, NULL);
|
||||||
|
|
||||||
WARN_ON(klp_unregister_patch(&patch));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(livepatch_shadow_fix2_init);
|
module_init(livepatch_shadow_fix2_init);
|
||||||
|
@ -96,15 +96,15 @@ MODULE_DESCRIPTION("Buggy module for shadow variable demo");
|
|||||||
* Keep a list of all the dummies so we can clean up any residual ones
|
* Keep a list of all the dummies so we can clean up any residual ones
|
||||||
* on module exit
|
* on module exit
|
||||||
*/
|
*/
|
||||||
LIST_HEAD(dummy_list);
|
static LIST_HEAD(dummy_list);
|
||||||
DEFINE_MUTEX(dummy_list_mutex);
|
static DEFINE_MUTEX(dummy_list_mutex);
|
||||||
|
|
||||||
struct dummy {
|
struct dummy {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
unsigned long jiffies_expire;
|
unsigned long jiffies_expire;
|
||||||
};
|
};
|
||||||
|
|
||||||
noinline struct dummy *dummy_alloc(void)
|
static __used noinline struct dummy *dummy_alloc(void)
|
||||||
{
|
{
|
||||||
struct dummy *d;
|
struct dummy *d;
|
||||||
void *leak;
|
void *leak;
|
||||||
@ -129,7 +129,7 @@ noinline struct dummy *dummy_alloc(void)
|
|||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
noinline void dummy_free(struct dummy *d)
|
static __used noinline void dummy_free(struct dummy *d)
|
||||||
{
|
{
|
||||||
pr_info("%s: dummy @ %p, expired = %lx\n",
|
pr_info("%s: dummy @ %p, expired = %lx\n",
|
||||||
__func__, d, d->jiffies_expire);
|
__func__, d, d->jiffies_expire);
|
||||||
@ -137,7 +137,8 @@ noinline void dummy_free(struct dummy *d)
|
|||||||
kfree(d);
|
kfree(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
noinline bool dummy_check(struct dummy *d, unsigned long jiffies)
|
static __used noinline bool dummy_check(struct dummy *d,
|
||||||
|
unsigned long jiffies)
|
||||||
{
|
{
|
||||||
return time_after(jiffies, d->jiffies_expire);
|
return time_after(jiffies, d->jiffies_expire);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ TARGETS += ir
|
|||||||
TARGETS += kcmp
|
TARGETS += kcmp
|
||||||
TARGETS += kvm
|
TARGETS += kvm
|
||||||
TARGETS += lib
|
TARGETS += lib
|
||||||
|
TARGETS += livepatch
|
||||||
TARGETS += membarrier
|
TARGETS += membarrier
|
||||||
TARGETS += memfd
|
TARGETS += memfd
|
||||||
TARGETS += memory-hotplug
|
TARGETS += memory-hotplug
|
||||||
|
8
tools/testing/selftests/livepatch/Makefile
Normal file
8
tools/testing/selftests/livepatch/Makefile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
TEST_GEN_PROGS := \
|
||||||
|
test-livepatch.sh \
|
||||||
|
test-callbacks.sh \
|
||||||
|
test-shadow-vars.sh
|
||||||
|
|
||||||
|
include ../lib.mk
|
43
tools/testing/selftests/livepatch/README
Normal file
43
tools/testing/selftests/livepatch/README
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
====================
|
||||||
|
Livepatch Self Tests
|
||||||
|
====================
|
||||||
|
|
||||||
|
This is a small set of sanity tests for the kernel livepatching.
|
||||||
|
|
||||||
|
The test suite loads and unloads several test kernel modules to verify
|
||||||
|
livepatch behavior. Debug information is logged to the kernel's message
|
||||||
|
buffer and parsed for expected messages. (Note: the tests will clear
|
||||||
|
the message buffer between individual tests.)
|
||||||
|
|
||||||
|
|
||||||
|
Config
|
||||||
|
------
|
||||||
|
|
||||||
|
Set these config options and their prerequisites:
|
||||||
|
|
||||||
|
CONFIG_LIVEPATCH=y
|
||||||
|
CONFIG_TEST_LIVEPATCH=m
|
||||||
|
|
||||||
|
|
||||||
|
Running the tests
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Test kernel modules are built as part of lib/ (make modules) and need to
|
||||||
|
be installed (make modules_install) as the test scripts will modprobe
|
||||||
|
them.
|
||||||
|
|
||||||
|
To run the livepatch selftests, from the top of the kernel source tree:
|
||||||
|
|
||||||
|
% make -C tools/testing/selftests TARGETS=livepatch run_tests
|
||||||
|
|
||||||
|
|
||||||
|
Adding tests
|
||||||
|
------------
|
||||||
|
|
||||||
|
See the common functions.sh file for the existing collection of utility
|
||||||
|
functions, most importantly set_dynamic_debug() and check_result(). The
|
||||||
|
latter function greps the kernel's ring buffer for "livepatch:" and
|
||||||
|
"test_klp" strings, so tests be sure to include one of those strings for
|
||||||
|
result comparison. Other utility functions include general module
|
||||||
|
loading and livepatch loading helpers (waiting for patch transitions,
|
||||||
|
sysfs entries, etc.)
|
1
tools/testing/selftests/livepatch/config
Normal file
1
tools/testing/selftests/livepatch/config
Normal file
@ -0,0 +1 @@
|
|||||||
|
CONFIG_TEST_LIVEPATCH=m
|
198
tools/testing/selftests/livepatch/functions.sh
Normal file
198
tools/testing/selftests/livepatch/functions.sh
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
# Shell functions for the rest of the scripts.
|
||||||
|
|
||||||
|
MAX_RETRIES=600
|
||||||
|
RETRY_INTERVAL=".1" # seconds
|
||||||
|
|
||||||
|
# log(msg) - write message to kernel log
|
||||||
|
# msg - insightful words
|
||||||
|
function log() {
|
||||||
|
echo "$1" > /dev/kmsg
|
||||||
|
}
|
||||||
|
|
||||||
|
# die(msg) - game over, man
|
||||||
|
# msg - dying words
|
||||||
|
function die() {
|
||||||
|
log "ERROR: $1"
|
||||||
|
echo "ERROR: $1" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# set_dynamic_debug() - setup kernel dynamic debug
|
||||||
|
# TODO - push and pop this config?
|
||||||
|
function set_dynamic_debug() {
|
||||||
|
cat << EOF > /sys/kernel/debug/dynamic_debug/control
|
||||||
|
file kernel/livepatch/* +p
|
||||||
|
func klp_try_switch_task -p
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# loop_until(cmd) - loop a command until it is successful or $MAX_RETRIES,
|
||||||
|
# sleep $RETRY_INTERVAL between attempts
|
||||||
|
# cmd - command and its arguments to run
|
||||||
|
function loop_until() {
|
||||||
|
local cmd="$*"
|
||||||
|
local i=0
|
||||||
|
while true; do
|
||||||
|
eval "$cmd" && return 0
|
||||||
|
[[ $((i++)) -eq $MAX_RETRIES ]] && return 1
|
||||||
|
sleep $RETRY_INTERVAL
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function is_livepatch_mod() {
|
||||||
|
local mod="$1"
|
||||||
|
|
||||||
|
if [[ $(modinfo "$mod" | awk '/^livepatch:/{print $NF}') == "Y" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function __load_mod() {
|
||||||
|
local mod="$1"; shift
|
||||||
|
|
||||||
|
local msg="% modprobe $mod $*"
|
||||||
|
log "${msg%% }"
|
||||||
|
ret=$(modprobe "$mod" "$@" 2>&1)
|
||||||
|
if [[ "$ret" != "" ]]; then
|
||||||
|
die "$ret"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for module in sysfs ...
|
||||||
|
loop_until '[[ -e "/sys/module/$mod" ]]' ||
|
||||||
|
die "failed to load module $mod"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# load_mod(modname, params) - load a kernel module
|
||||||
|
# modname - module name to load
|
||||||
|
# params - module parameters to pass to modprobe
|
||||||
|
function load_mod() {
|
||||||
|
local mod="$1"; shift
|
||||||
|
|
||||||
|
is_livepatch_mod "$mod" &&
|
||||||
|
die "use load_lp() to load the livepatch module $mod"
|
||||||
|
|
||||||
|
__load_mod "$mod" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# load_lp_nowait(modname, params) - load a kernel module with a livepatch
|
||||||
|
# but do not wait on until the transition finishes
|
||||||
|
# modname - module name to load
|
||||||
|
# params - module parameters to pass to modprobe
|
||||||
|
function load_lp_nowait() {
|
||||||
|
local mod="$1"; shift
|
||||||
|
|
||||||
|
is_livepatch_mod "$mod" ||
|
||||||
|
die "module $mod is not a livepatch"
|
||||||
|
|
||||||
|
__load_mod "$mod" "$@"
|
||||||
|
|
||||||
|
# Wait for livepatch in sysfs ...
|
||||||
|
loop_until '[[ -e "/sys/kernel/livepatch/$mod" ]]' ||
|
||||||
|
die "failed to load module $mod (sysfs)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# load_lp(modname, params) - load a kernel module with a livepatch
|
||||||
|
# modname - module name to load
|
||||||
|
# params - module parameters to pass to modprobe
|
||||||
|
function load_lp() {
|
||||||
|
local mod="$1"; shift
|
||||||
|
|
||||||
|
load_lp_nowait "$mod" "$@"
|
||||||
|
|
||||||
|
# Wait until the transition finishes ...
|
||||||
|
loop_until 'grep -q '^0$' /sys/kernel/livepatch/$mod/transition' ||
|
||||||
|
die "failed to complete transition"
|
||||||
|
}
|
||||||
|
|
||||||
|
# load_failing_mod(modname, params) - load a kernel module, expect to fail
|
||||||
|
# modname - module name to load
|
||||||
|
# params - module parameters to pass to modprobe
|
||||||
|
function load_failing_mod() {
|
||||||
|
local mod="$1"; shift
|
||||||
|
|
||||||
|
local msg="% modprobe $mod $*"
|
||||||
|
log "${msg%% }"
|
||||||
|
ret=$(modprobe "$mod" "$@" 2>&1)
|
||||||
|
if [[ "$ret" == "" ]]; then
|
||||||
|
die "$mod unexpectedly loaded"
|
||||||
|
fi
|
||||||
|
log "$ret"
|
||||||
|
}
|
||||||
|
|
||||||
|
# unload_mod(modname) - unload a kernel module
|
||||||
|
# modname - module name to unload
|
||||||
|
function unload_mod() {
|
||||||
|
local mod="$1"
|
||||||
|
|
||||||
|
# Wait for module reference count to clear ...
|
||||||
|
loop_until '[[ $(cat "/sys/module/$mod/refcnt") == "0" ]]' ||
|
||||||
|
die "failed to unload module $mod (refcnt)"
|
||||||
|
|
||||||
|
log "% rmmod $mod"
|
||||||
|
ret=$(rmmod "$mod" 2>&1)
|
||||||
|
if [[ "$ret" != "" ]]; then
|
||||||
|
die "$ret"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for module in sysfs ...
|
||||||
|
loop_until '[[ ! -e "/sys/module/$mod" ]]' ||
|
||||||
|
die "failed to unload module $mod (/sys/module)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# unload_lp(modname) - unload a kernel module with a livepatch
|
||||||
|
# modname - module name to unload
|
||||||
|
function unload_lp() {
|
||||||
|
unload_mod "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# disable_lp(modname) - disable a livepatch
|
||||||
|
# modname - module name to unload
|
||||||
|
function disable_lp() {
|
||||||
|
local mod="$1"
|
||||||
|
|
||||||
|
log "% echo 0 > /sys/kernel/livepatch/$mod/enabled"
|
||||||
|
echo 0 > /sys/kernel/livepatch/"$mod"/enabled
|
||||||
|
|
||||||
|
# Wait until the transition finishes and the livepatch gets
|
||||||
|
# removed from sysfs...
|
||||||
|
loop_until '[[ ! -e "/sys/kernel/livepatch/$mod" ]]' ||
|
||||||
|
die "failed to disable livepatch $mod"
|
||||||
|
}
|
||||||
|
|
||||||
|
# set_pre_patch_ret(modname, pre_patch_ret)
|
||||||
|
# modname - module name to set
|
||||||
|
# pre_patch_ret - new pre_patch_ret value
|
||||||
|
function set_pre_patch_ret {
|
||||||
|
local mod="$1"; shift
|
||||||
|
local ret="$1"
|
||||||
|
|
||||||
|
log "% echo $ret > /sys/module/$mod/parameters/pre_patch_ret"
|
||||||
|
echo "$ret" > /sys/module/"$mod"/parameters/pre_patch_ret
|
||||||
|
|
||||||
|
# Wait for sysfs value to hold ...
|
||||||
|
loop_until '[[ $(cat "/sys/module/$mod/parameters/pre_patch_ret") == "$ret" ]]' ||
|
||||||
|
die "failed to set pre_patch_ret parameter for $mod module"
|
||||||
|
}
|
||||||
|
|
||||||
|
# check_result() - verify dmesg output
|
||||||
|
# TODO - better filter, out of order msgs, etc?
|
||||||
|
function check_result {
|
||||||
|
local expect="$*"
|
||||||
|
local result
|
||||||
|
|
||||||
|
result=$(dmesg | grep -v 'tainting' | grep -e 'livepatch:' -e 'test_klp' | sed 's/^\[[ 0-9.]*\] //')
|
||||||
|
|
||||||
|
if [[ "$expect" == "$result" ]] ; then
|
||||||
|
echo "ok"
|
||||||
|
else
|
||||||
|
echo -e "not ok\n\n$(diff -upr --label expected --label result <(echo "$expect") <(echo "$result"))\n"
|
||||||
|
die "livepatch kselftest(s) failed"
|
||||||
|
fi
|
||||||
|
}
|
587
tools/testing/selftests/livepatch/test-callbacks.sh
Executable file
587
tools/testing/selftests/livepatch/test-callbacks.sh
Executable file
@ -0,0 +1,587 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
. $(dirname $0)/functions.sh
|
||||||
|
|
||||||
|
MOD_LIVEPATCH=test_klp_callbacks_demo
|
||||||
|
MOD_LIVEPATCH2=test_klp_callbacks_demo2
|
||||||
|
MOD_TARGET=test_klp_callbacks_mod
|
||||||
|
MOD_TARGET_BUSY=test_klp_callbacks_busy
|
||||||
|
|
||||||
|
set_dynamic_debug
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: target module before livepatch
|
||||||
|
#
|
||||||
|
# Test a combination of loading a kernel module and a livepatch that
|
||||||
|
# patches a function in the first module. Load the target module
|
||||||
|
# before the livepatch module. Unload them in the same order.
|
||||||
|
#
|
||||||
|
# - On livepatch enable, before the livepatch transition starts,
|
||||||
|
# pre-patch callbacks are executed for vmlinux and $MOD_TARGET (those
|
||||||
|
# klp_objects currently loaded). After klp_objects are patched
|
||||||
|
# according to the klp_patch, their post-patch callbacks run and the
|
||||||
|
# transition completes.
|
||||||
|
#
|
||||||
|
# - Similarly, on livepatch disable, pre-patch callbacks run before the
|
||||||
|
# unpatching transition starts. klp_objects are reverted, post-patch
|
||||||
|
# callbacks execute and the transition completes.
|
||||||
|
|
||||||
|
echo -n "TEST: target module before livepatch ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_mod $MOD_TARGET
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
unload_mod $MOD_TARGET
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_init
|
||||||
|
% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH
|
||||||
|
% rmmod $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_exit"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: module_coming notifier
|
||||||
|
#
|
||||||
|
# This test is similar to the previous test, but (un)load the livepatch
|
||||||
|
# module before the target kernel module. This tests the livepatch
|
||||||
|
# core's module_coming handler.
|
||||||
|
#
|
||||||
|
# - On livepatch enable, only pre/post-patch callbacks are executed for
|
||||||
|
# currently loaded klp_objects, in this case, vmlinux.
|
||||||
|
#
|
||||||
|
# - When a targeted module is subsequently loaded, only its
|
||||||
|
# pre/post-patch callbacks are executed.
|
||||||
|
#
|
||||||
|
# - On livepatch disable, all currently loaded klp_objects' (vmlinux and
|
||||||
|
# $MOD_TARGET) pre/post-unpatch callbacks are executed.
|
||||||
|
|
||||||
|
echo -n "TEST: module_coming notifier ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
load_mod $MOD_TARGET
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
unload_mod $MOD_TARGET
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% modprobe $MOD_TARGET
|
||||||
|
livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_init
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH
|
||||||
|
% rmmod $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_exit"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: module_going notifier
|
||||||
|
#
|
||||||
|
# Test loading the livepatch after a targeted kernel module, then unload
|
||||||
|
# the kernel module before disabling the livepatch. This tests the
|
||||||
|
# livepatch core's module_going handler.
|
||||||
|
#
|
||||||
|
# - First load a target module, then the livepatch.
|
||||||
|
#
|
||||||
|
# - When a target module is unloaded, the livepatch is only reverted
|
||||||
|
# from that klp_object ($MOD_TARGET). As such, only its pre and
|
||||||
|
# post-unpatch callbacks are executed when this occurs.
|
||||||
|
#
|
||||||
|
# - When the livepatch is disabled, pre and post-unpatch callbacks are
|
||||||
|
# run for the remaining klp_object, vmlinux.
|
||||||
|
|
||||||
|
echo -n "TEST: module_going notifier ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_mod $MOD_TARGET
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
unload_mod $MOD_TARGET
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_init
|
||||||
|
% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% rmmod $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_exit
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away
|
||||||
|
livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: module_coming and module_going notifiers
|
||||||
|
#
|
||||||
|
# This test is similar to the previous test, however the livepatch is
|
||||||
|
# loaded first. This tests the livepatch core's module_coming and
|
||||||
|
# module_going handlers.
|
||||||
|
#
|
||||||
|
# - First load the livepatch.
|
||||||
|
#
|
||||||
|
# - When a targeted kernel module is subsequently loaded, only its
|
||||||
|
# pre/post-patch callbacks are executed.
|
||||||
|
#
|
||||||
|
# - When the target module is unloaded, the livepatch is only reverted
|
||||||
|
# from the $MOD_TARGET klp_object. As such, only pre and
|
||||||
|
# post-unpatch callbacks are executed when this occurs.
|
||||||
|
|
||||||
|
echo -n "TEST: module_coming and module_going notifiers ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
load_mod $MOD_TARGET
|
||||||
|
unload_mod $MOD_TARGET
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% modprobe $MOD_TARGET
|
||||||
|
livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_init
|
||||||
|
% rmmod $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_exit
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away
|
||||||
|
livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: target module not present
|
||||||
|
#
|
||||||
|
# A simple test of loading a livepatch without one of its patch target
|
||||||
|
# klp_objects ever loaded ($MOD_TARGET).
|
||||||
|
#
|
||||||
|
# - Load the livepatch.
|
||||||
|
#
|
||||||
|
# - As expected, only pre/post-(un)patch handlers are executed for
|
||||||
|
# vmlinux.
|
||||||
|
|
||||||
|
echo -n "TEST: target module not present ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: pre-patch callback -ENODEV
|
||||||
|
#
|
||||||
|
# Test a scenario where a vmlinux pre-patch callback returns a non-zero
|
||||||
|
# status (ie, failure).
|
||||||
|
#
|
||||||
|
# - First load a target module.
|
||||||
|
#
|
||||||
|
# - Load the livepatch module, setting its 'pre_patch_ret' value to -19
|
||||||
|
# (-ENODEV). When its vmlinux pre-patch callback executes, this
|
||||||
|
# status code will propagate back to the module-loading subsystem.
|
||||||
|
# The result is that the insmod command refuses to load the livepatch
|
||||||
|
# module.
|
||||||
|
|
||||||
|
echo -n "TEST: pre-patch callback -ENODEV ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_mod $MOD_TARGET
|
||||||
|
load_failing_mod $MOD_LIVEPATCH pre_patch_ret=-19
|
||||||
|
unload_mod $MOD_TARGET
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_init
|
||||||
|
% modprobe $MOD_LIVEPATCH pre_patch_ret=-19
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
test_klp_callbacks_demo: pre_patch_callback: vmlinux
|
||||||
|
livepatch: pre-patch callback failed for object 'vmlinux'
|
||||||
|
livepatch: failed to enable patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': canceling patching transition, going to unpatch
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
modprobe: ERROR: could not insert '$MOD_LIVEPATCH': No such device
|
||||||
|
% rmmod $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_exit"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: module_coming + pre-patch callback -ENODEV
|
||||||
|
#
|
||||||
|
# Similar to the previous test, setup a livepatch such that its vmlinux
|
||||||
|
# pre-patch callback returns success. However, when a targeted kernel
|
||||||
|
# module is later loaded, have the livepatch return a failing status
|
||||||
|
# code.
|
||||||
|
#
|
||||||
|
# - Load the livepatch, vmlinux pre-patch callback succeeds.
|
||||||
|
#
|
||||||
|
# - Set a trap so subsequent pre-patch callbacks to this livepatch will
|
||||||
|
# return -ENODEV.
|
||||||
|
#
|
||||||
|
# - The livepatch pre-patch callback for subsequently loaded target
|
||||||
|
# modules will return failure, so the module loader refuses to load
|
||||||
|
# the kernel module. No post-patch or pre/post-unpatch callbacks are
|
||||||
|
# executed for this klp_object.
|
||||||
|
#
|
||||||
|
# - Pre/post-unpatch callbacks are run for the vmlinux klp_object.
|
||||||
|
|
||||||
|
echo -n "TEST: module_coming + pre-patch callback -ENODEV ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
set_pre_patch_ret $MOD_LIVEPATCH -19
|
||||||
|
load_failing_mod $MOD_TARGET
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% echo -19 > /sys/module/$MOD_LIVEPATCH/parameters/pre_patch_ret
|
||||||
|
% modprobe $MOD_TARGET
|
||||||
|
livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
livepatch: pre-patch callback failed for object '$MOD_TARGET'
|
||||||
|
livepatch: patch '$MOD_LIVEPATCH' failed for module '$MOD_TARGET', refusing to load module '$MOD_TARGET'
|
||||||
|
modprobe: ERROR: could not insert '$MOD_TARGET': No such device
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: multiple target modules
|
||||||
|
#
|
||||||
|
# Test loading multiple targeted kernel modules. This test-case is
|
||||||
|
# mainly for comparing with the next test-case.
|
||||||
|
#
|
||||||
|
# - Load a target "busy" kernel module which kicks off a worker function
|
||||||
|
# that immediately exits.
|
||||||
|
#
|
||||||
|
# - Proceed with loading the livepatch and another ordinary target
|
||||||
|
# module. Post-patch callbacks are executed and the transition
|
||||||
|
# completes quickly.
|
||||||
|
|
||||||
|
echo -n "TEST: multiple target modules ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_mod $MOD_TARGET_BUSY sleep_secs=0
|
||||||
|
# give $MOD_TARGET_BUSY::busymod_work_func() a chance to run
|
||||||
|
sleep 5
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
load_mod $MOD_TARGET
|
||||||
|
unload_mod $MOD_TARGET
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
unload_mod $MOD_TARGET_BUSY
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_TARGET_BUSY sleep_secs=0
|
||||||
|
$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init
|
||||||
|
$MOD_TARGET_BUSY: busymod_work_func, sleeping 0 seconds ...
|
||||||
|
$MOD_TARGET_BUSY: busymod_work_func exit
|
||||||
|
% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% modprobe $MOD_TARGET
|
||||||
|
livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_init
|
||||||
|
% rmmod $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_exit
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away
|
||||||
|
livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH
|
||||||
|
% rmmod $MOD_TARGET_BUSY
|
||||||
|
$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: busy target module
|
||||||
|
#
|
||||||
|
# A similar test as the previous one, but force the "busy" kernel module
|
||||||
|
# to do longer work.
|
||||||
|
#
|
||||||
|
# The livepatching core will refuse to patch a task that is currently
|
||||||
|
# executing a to-be-patched function -- the consistency model stalls the
|
||||||
|
# current patch transition until this safety-check is met. Test a
|
||||||
|
# scenario where one of a livepatch's target klp_objects sits on such a
|
||||||
|
# function for a long time. Meanwhile, load and unload other target
|
||||||
|
# kernel modules while the livepatch transition is in progress.
|
||||||
|
#
|
||||||
|
# - Load the "busy" kernel module, this time make it do 10 seconds worth
|
||||||
|
# of work.
|
||||||
|
#
|
||||||
|
# - Meanwhile, the livepatch is loaded. Notice that the patch
|
||||||
|
# transition does not complete as the targeted "busy" module is
|
||||||
|
# sitting on a to-be-patched function.
|
||||||
|
#
|
||||||
|
# - Load a second target module (this one is an ordinary idle kernel
|
||||||
|
# module). Note that *no* post-patch callbacks will be executed while
|
||||||
|
# the livepatch is still in transition.
|
||||||
|
#
|
||||||
|
# - Request an unload of the simple kernel module. The patch is still
|
||||||
|
# transitioning, so its pre-unpatch callbacks are skipped.
|
||||||
|
#
|
||||||
|
# - Finally the livepatch is disabled. Since none of the patch's
|
||||||
|
# klp_object's post-patch callbacks executed, the remaining
|
||||||
|
# klp_object's pre-unpatch callbacks are skipped.
|
||||||
|
|
||||||
|
echo -n "TEST: busy target module ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_mod $MOD_TARGET_BUSY sleep_secs=10
|
||||||
|
load_lp_nowait $MOD_LIVEPATCH
|
||||||
|
# Don't wait for transition, load $MOD_TARGET while the transition
|
||||||
|
# is still stalled in $MOD_TARGET_BUSY::busymod_work_func()
|
||||||
|
sleep 5
|
||||||
|
load_mod $MOD_TARGET
|
||||||
|
unload_mod $MOD_TARGET
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
unload_mod $MOD_TARGET_BUSY
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_TARGET_BUSY sleep_secs=10
|
||||||
|
$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_init
|
||||||
|
$MOD_TARGET_BUSY: busymod_work_func, sleeping 10 seconds ...
|
||||||
|
% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
% modprobe $MOD_TARGET
|
||||||
|
livepatch: applying patch '$MOD_LIVEPATCH' to loading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: $MOD_TARGET -> [MODULE_STATE_COMING] Full formed, running module_init
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_init
|
||||||
|
% rmmod $MOD_TARGET
|
||||||
|
$MOD_TARGET: ${MOD_TARGET}_exit
|
||||||
|
livepatch: reverting patch '$MOD_LIVEPATCH' on unloading module '$MOD_TARGET'
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET -> [MODULE_STATE_GOING] Going away
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': reversing transition from patching to unpatching
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: $MOD_TARGET_BUSY -> [MODULE_STATE_LIVE] Normal state
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH
|
||||||
|
% rmmod $MOD_TARGET_BUSY
|
||||||
|
$MOD_TARGET_BUSY: busymod_work_func exit
|
||||||
|
$MOD_TARGET_BUSY: ${MOD_TARGET_BUSY}_exit"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: multiple livepatches
|
||||||
|
#
|
||||||
|
# Test loading multiple livepatches. This test-case is mainly for comparing
|
||||||
|
# with the next test-case.
|
||||||
|
#
|
||||||
|
# - Load and unload two livepatches, pre and post (un)patch callbacks
|
||||||
|
# execute as each patch progresses through its (un)patching
|
||||||
|
# transition.
|
||||||
|
|
||||||
|
echo -n "TEST: multiple livepatches ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
load_lp $MOD_LIVEPATCH2
|
||||||
|
disable_lp $MOD_LIVEPATCH2
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH2
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% modprobe $MOD_LIVEPATCH2
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH2'
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': completing patching transition
|
||||||
|
$MOD_LIVEPATCH2: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': patching complete
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': unpatching complete
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: pre_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH: post_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH2
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: atomic replace
|
||||||
|
#
|
||||||
|
# Load multiple livepatches, but the second as an 'atomic-replace'
|
||||||
|
# patch. When the latter loads, the original livepatch should be
|
||||||
|
# disabled and *none* of its pre/post-unpatch callbacks executed. On
|
||||||
|
# the other hand, when the atomic-replace livepatch is disabled, its
|
||||||
|
# pre/post-unpatch callbacks *should* be executed.
|
||||||
|
#
|
||||||
|
# - Load and unload two livepatches, the second of which has its
|
||||||
|
# .replace flag set true.
|
||||||
|
#
|
||||||
|
# - Pre and post patch callbacks are executed for both livepatches.
|
||||||
|
#
|
||||||
|
# - Once the atomic replace module is loaded, only its pre and post
|
||||||
|
# unpatch callbacks are executed.
|
||||||
|
|
||||||
|
echo -n "TEST: atomic replace ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
load_lp $MOD_LIVEPATCH2 replace=1
|
||||||
|
disable_lp $MOD_LIVEPATCH2
|
||||||
|
unload_lp $MOD_LIVEPATCH2
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
$MOD_LIVEPATCH: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% modprobe $MOD_LIVEPATCH2 replace=1
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH2'
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': initializing patching transition
|
||||||
|
$MOD_LIVEPATCH2: pre_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': completing patching transition
|
||||||
|
$MOD_LIVEPATCH2: post_patch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': patching complete
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH2/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': initializing unpatching transition
|
||||||
|
$MOD_LIVEPATCH2: pre_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': completing unpatching transition
|
||||||
|
$MOD_LIVEPATCH2: post_unpatch_callback: vmlinux
|
||||||
|
livepatch: '$MOD_LIVEPATCH2': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH2
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
exit 0
|
168
tools/testing/selftests/livepatch/test-livepatch.sh
Executable file
168
tools/testing/selftests/livepatch/test-livepatch.sh
Executable file
@ -0,0 +1,168 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
. $(dirname $0)/functions.sh
|
||||||
|
|
||||||
|
MOD_LIVEPATCH=test_klp_livepatch
|
||||||
|
MOD_REPLACE=test_klp_atomic_replace
|
||||||
|
|
||||||
|
set_dynamic_debug
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: basic function patching
|
||||||
|
# - load a livepatch that modifies the output from /proc/cmdline and
|
||||||
|
# verify correct behavior
|
||||||
|
# - unload the livepatch and make sure the patch was removed
|
||||||
|
|
||||||
|
echo -n "TEST: basic function patching ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
if [[ "$(cat /proc/cmdline)" != "$MOD_LIVEPATCH: this has been live patched" ]] ; then
|
||||||
|
echo -e "FAIL\n\n"
|
||||||
|
die "livepatch kselftest(s) failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
if [[ "$(cat /proc/cmdline)" == "$MOD_LIVEPATCH: this has been live patched" ]] ; then
|
||||||
|
echo -e "FAIL\n\n"
|
||||||
|
die "livepatch kselftest(s) failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: multiple livepatches
|
||||||
|
# - load a livepatch that modifies the output from /proc/cmdline and
|
||||||
|
# verify correct behavior
|
||||||
|
# - load another livepatch and verify that both livepatches are active
|
||||||
|
# - unload the second livepatch and verify that the first is still active
|
||||||
|
# - unload the first livepatch and verify none are active
|
||||||
|
|
||||||
|
echo -n "TEST: multiple livepatches ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
load_lp $MOD_REPLACE replace=0
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
disable_lp $MOD_REPLACE
|
||||||
|
unload_lp $MOD_REPLACE
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
disable_lp $MOD_LIVEPATCH
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
$MOD_LIVEPATCH: this has been live patched
|
||||||
|
% modprobe $MOD_REPLACE replace=0
|
||||||
|
livepatch: enabling patch '$MOD_REPLACE'
|
||||||
|
livepatch: '$MOD_REPLACE': initializing patching transition
|
||||||
|
livepatch: '$MOD_REPLACE': starting patching transition
|
||||||
|
livepatch: '$MOD_REPLACE': completing patching transition
|
||||||
|
livepatch: '$MOD_REPLACE': patching complete
|
||||||
|
$MOD_LIVEPATCH: this has been live patched
|
||||||
|
$MOD_REPLACE: this has been live patched
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled
|
||||||
|
livepatch: '$MOD_REPLACE': initializing unpatching transition
|
||||||
|
livepatch: '$MOD_REPLACE': starting unpatching transition
|
||||||
|
livepatch: '$MOD_REPLACE': completing unpatching transition
|
||||||
|
livepatch: '$MOD_REPLACE': unpatching complete
|
||||||
|
% rmmod $MOD_REPLACE
|
||||||
|
$MOD_LIVEPATCH: this has been live patched
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_LIVEPATCH/enabled
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing unpatching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': unpatching complete
|
||||||
|
% rmmod $MOD_LIVEPATCH"
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: atomic replace livepatch
|
||||||
|
# - load a livepatch that modifies the output from /proc/cmdline and
|
||||||
|
# verify correct behavior
|
||||||
|
# - load an atomic replace livepatch and verify that only the second is active
|
||||||
|
# - remove the first livepatch and verify that the atomic replace livepatch
|
||||||
|
# is still active
|
||||||
|
# - remove the atomic replace livepatch and verify that none are active
|
||||||
|
|
||||||
|
echo -n "TEST: atomic replace livepatch ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
load_lp $MOD_REPLACE replace=1
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
unload_lp $MOD_LIVEPATCH
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
disable_lp $MOD_REPLACE
|
||||||
|
unload_lp $MOD_REPLACE
|
||||||
|
|
||||||
|
grep 'live patched' /proc/cmdline > /dev/kmsg
|
||||||
|
grep 'live patched' /proc/meminfo > /dev/kmsg
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_LIVEPATCH
|
||||||
|
livepatch: enabling patch '$MOD_LIVEPATCH'
|
||||||
|
livepatch: '$MOD_LIVEPATCH': initializing patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': starting patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': completing patching transition
|
||||||
|
livepatch: '$MOD_LIVEPATCH': patching complete
|
||||||
|
$MOD_LIVEPATCH: this has been live patched
|
||||||
|
% modprobe $MOD_REPLACE replace=1
|
||||||
|
livepatch: enabling patch '$MOD_REPLACE'
|
||||||
|
livepatch: '$MOD_REPLACE': initializing patching transition
|
||||||
|
livepatch: '$MOD_REPLACE': starting patching transition
|
||||||
|
livepatch: '$MOD_REPLACE': completing patching transition
|
||||||
|
livepatch: '$MOD_REPLACE': patching complete
|
||||||
|
$MOD_REPLACE: this has been live patched
|
||||||
|
% rmmod $MOD_LIVEPATCH
|
||||||
|
$MOD_REPLACE: this has been live patched
|
||||||
|
% echo 0 > /sys/kernel/livepatch/$MOD_REPLACE/enabled
|
||||||
|
livepatch: '$MOD_REPLACE': initializing unpatching transition
|
||||||
|
livepatch: '$MOD_REPLACE': starting unpatching transition
|
||||||
|
livepatch: '$MOD_REPLACE': completing unpatching transition
|
||||||
|
livepatch: '$MOD_REPLACE': unpatching complete
|
||||||
|
% rmmod $MOD_REPLACE"
|
||||||
|
|
||||||
|
|
||||||
|
exit 0
|
60
tools/testing/selftests/livepatch/test-shadow-vars.sh
Executable file
60
tools/testing/selftests/livepatch/test-shadow-vars.sh
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
# Copyright (C) 2018 Joe Lawrence <joe.lawrence@redhat.com>
|
||||||
|
|
||||||
|
. $(dirname $0)/functions.sh
|
||||||
|
|
||||||
|
MOD_TEST=test_klp_shadow_vars
|
||||||
|
|
||||||
|
set_dynamic_debug
|
||||||
|
|
||||||
|
|
||||||
|
# TEST: basic shadow variable API
|
||||||
|
# - load a module that exercises the shadow variable API
|
||||||
|
|
||||||
|
echo -n "TEST: basic shadow variable API ... "
|
||||||
|
dmesg -C
|
||||||
|
|
||||||
|
load_mod $MOD_TEST
|
||||||
|
unload_mod $MOD_TEST
|
||||||
|
|
||||||
|
check_result "% modprobe $MOD_TEST
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR0
|
||||||
|
$MOD_TEST: got expected NULL result
|
||||||
|
$MOD_TEST: shadow_ctor: PTR6 -> PTR1
|
||||||
|
$MOD_TEST: klp_shadow_alloc(obj=PTR5, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR1 = PTR6
|
||||||
|
$MOD_TEST: shadow_ctor: PTR8 -> PTR2
|
||||||
|
$MOD_TEST: klp_shadow_alloc(obj=PTR9, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR2 = PTR8
|
||||||
|
$MOD_TEST: shadow_ctor: PTR10 -> PTR3
|
||||||
|
$MOD_TEST: klp_shadow_alloc(obj=PTR5, id=0x1235, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR3 = PTR10
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR6
|
||||||
|
$MOD_TEST: got expected PTR6 -> PTR1 result
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR9, id=0x1234) = PTR8
|
||||||
|
$MOD_TEST: got expected PTR8 -> PTR2 result
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1235) = PTR10
|
||||||
|
$MOD_TEST: got expected PTR10 -> PTR3 result
|
||||||
|
$MOD_TEST: shadow_ctor: PTR11 -> PTR4
|
||||||
|
$MOD_TEST: klp_shadow_get_or_alloc(obj=PTR12, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR4 = PTR11
|
||||||
|
$MOD_TEST: klp_shadow_get_or_alloc(obj=PTR12, id=0x1234, size=8, gfp_flags=GFP_KERNEL), ctor=PTR7, ctor_data=PTR4 = PTR11
|
||||||
|
$MOD_TEST: got expected PTR11 -> PTR4 result
|
||||||
|
$MOD_TEST: shadow_dtor(obj=PTR5, shadow_data=PTR6)
|
||||||
|
$MOD_TEST: klp_shadow_free(obj=PTR5, id=0x1234, dtor=PTR13)
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR0
|
||||||
|
$MOD_TEST: got expected NULL result
|
||||||
|
$MOD_TEST: shadow_dtor(obj=PTR9, shadow_data=PTR8)
|
||||||
|
$MOD_TEST: klp_shadow_free(obj=PTR9, id=0x1234, dtor=PTR13)
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR9, id=0x1234) = PTR0
|
||||||
|
$MOD_TEST: got expected NULL result
|
||||||
|
$MOD_TEST: shadow_dtor(obj=PTR12, shadow_data=PTR11)
|
||||||
|
$MOD_TEST: klp_shadow_free(obj=PTR12, id=0x1234, dtor=PTR13)
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR12, id=0x1234) = PTR0
|
||||||
|
$MOD_TEST: got expected NULL result
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1235) = PTR10
|
||||||
|
$MOD_TEST: got expected PTR10 -> PTR3 result
|
||||||
|
$MOD_TEST: shadow_dtor(obj=PTR5, shadow_data=PTR10)
|
||||||
|
$MOD_TEST: klp_shadow_free_all(id=0x1235, dtor=PTR13)
|
||||||
|
$MOD_TEST: klp_shadow_get(obj=PTR5, id=0x1234) = PTR0
|
||||||
|
$MOD_TEST: shadow_get() got expected NULL result
|
||||||
|
% rmmod test_klp_shadow_vars"
|
||||||
|
|
||||||
|
exit 0
|
Loading…
Reference in New Issue
Block a user