Skip to content

Commit

Permalink
Merge tag 'livepatching-for-5.9' of git://git.kernel.org/pub/scm/linu…
Browse files Browse the repository at this point in the history
…x/kernel/git/livepatching/livepatching

Pull livepatching updates from Petr Mladek:
 "Improvements and cleanups of livepatching selftests"

* tag 'livepatching-for-5.9' of git://git.kernel.org/pub/scm/linux/kernel/git/livepatching/livepatching:
  selftests/livepatch: adopt to newer sysctl error format
  selftests/livepatch: Use "comm" instead of "diff" for dmesg
  selftests/livepatch: add test delimiter to dmesg
  selftests/livepatch: refine dmesg 'taints' in dmesg comparison
  selftests/livepatch: Don't clear dmesg when running tests
  selftests/livepatch: fix mem leaks in test-klp-shadow-vars
  selftests/livepatch: more verification in test-klp-shadow-vars
  selftests/livepatch: rework test-klp-shadow-vars
  selftests/livepatch: simplify test-klp-callbacks busy target tests
  • Loading branch information
torvalds committed Aug 6, 2020
2 parents bfdd5aa + 5e4d468 commit 1e21b5c
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 245 deletions.
37 changes: 28 additions & 9 deletions lib/livepatch/test_klp_callbacks_busy.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,53 @@

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.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)");
/* load/run-time control from sysfs writer */
static bool block_transition;
module_param(block_transition, bool, 0644);
MODULE_PARM_DESC(block_transition, "block_transition (default=false)");

static void busymod_work_func(struct work_struct *work);
static DECLARE_DELAYED_WORK(work, busymod_work_func);
static DECLARE_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 enter\n", __func__);

while (READ_ONCE(block_transition)) {
/*
* Busy-wait until the sysfs writer has acknowledged a
* blocked transition and clears the flag.
*/
msleep(20);
}

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));
schedule_work(&work);

if (!block_transition) {
/*
* Serialize output: print all messages from the work
* function before returning from init().
*/
flush_work(&work);
}

return 0;
}

static void test_klp_callbacks_busy_exit(void)
{
cancel_delayed_work_sync(&work);
WRITE_ONCE(block_transition, false);
flush_work(&work);
pr_info("%s\n", __func__);
}

Expand Down
240 changes: 132 additions & 108 deletions lib/livepatch/test_klp_shadow_vars.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,7 @@ static void shadow_free(void *obj, unsigned long id, klp_shadow_dtor_t 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));
pr_info("klp_%s(id=0x%lx, dtor=PTR%d)\n", __func__, id, ptr_id(dtor));
}


Expand All @@ -124,12 +123,16 @@ static int shadow_ctor(void *obj, void *shadow_data, void *ctor_data)
return -EINVAL;

*sv = *var;
pr_info("%s: PTR%d -> PTR%d\n",
__func__, ptr_id(sv), ptr_id(*var));
pr_info("%s: PTR%d -> PTR%d\n", __func__, ptr_id(sv), ptr_id(*var));

return 0;
}

/*
* With more than one item to free in the list, order is not determined and
* shadow_dtor will not be passed to shadow_free_all() which would make the
* test fail. (see pass 6)
*/
static void shadow_dtor(void *obj, void *shadow_data)
{
int **sv = shadow_data;
Expand All @@ -138,132 +141,153 @@ static void shadow_dtor(void *obj, void *shadow_data)
__func__, ptr_id(obj), ptr_id(sv));
}

static int test_klp_shadow_vars_init(void)
{
void *obj = THIS_MODULE;
int id = 0x1234;
gfp_t gfp_flags = GFP_KERNEL;
/* number of objects we simulate that need shadow vars */
#define NUM_OBJS 3

int var1, var2, var3, var4;
int *pv1, *pv2, *pv3, *pv4;
int **sv1, **sv2, **sv3, **sv4;
/* dynamically created obj fields have the following shadow var id values */
#define SV_ID1 0x1234
#define SV_ID2 0x1235

int **sv;
/*
* The main test case adds/removes new fields (shadow var) to each of these
* test structure instances. The last group of fields in the struct represent
* the idea that shadow variables may be added and removed to and from the
* struct during execution.
*/
struct test_object {
/* add anything here below and avoid to define an empty struct */
struct shadow_ptr sp;

pv1 = &var1;
pv2 = &var2;
pv3 = &var3;
pv4 = &var4;
/* these represent shadow vars added and removed with SV_ID{1,2} */
/* char nfield1; */
/* int nfield2; */
};

static int test_klp_shadow_vars_init(void)
{
struct test_object objs[NUM_OBJS];
char nfields1[NUM_OBJS], *pnfields1[NUM_OBJS], **sv1[NUM_OBJS];
char *pndup[NUM_OBJS];
int nfields2[NUM_OBJS], *pnfields2[NUM_OBJS], **sv2[NUM_OBJS];
void **sv;
int ret;
int i;

ptr_id(NULL);
ptr_id(pv1);
ptr_id(pv2);
ptr_id(pv3);
ptr_id(pv4);

/*
* With an empty shadow variable hash table, expect not to find
* any matches.
*/
sv = shadow_get(obj, id);
sv = shadow_get(&objs[0], SV_ID1);
if (!sv)
pr_info(" got expected NULL result\n");

/*
* Allocate a few shadow variables with different <obj> and <id>.
*/
sv1 = shadow_alloc(obj, id, sizeof(pv1), gfp_flags, shadow_ctor, &pv1);
if (!sv1)
return -ENOMEM;

sv2 = shadow_alloc(obj + 1, id, sizeof(pv2), gfp_flags, shadow_ctor, &pv2);
if (!sv2)
return -ENOMEM;

sv3 = shadow_alloc(obj, id + 1, sizeof(pv3), gfp_flags, shadow_ctor, &pv3);
if (!sv3)
return -ENOMEM;

/*
* Verify we can find our new shadow variables and that they point
* to expected data.
*/
sv = shadow_get(obj, id);
if (!sv)
return -EINVAL;
if (sv == sv1 && *sv1 == pv1)
pr_info(" got expected PTR%d -> PTR%d result\n",
ptr_id(sv1), ptr_id(*sv1));

sv = shadow_get(obj + 1, id);
if (!sv)
return -EINVAL;
if (sv == sv2 && *sv2 == pv2)
pr_info(" got expected PTR%d -> PTR%d result\n",
ptr_id(sv2), ptr_id(*sv2));
sv = shadow_get(obj, id + 1);
if (!sv)
return -EINVAL;
if (sv == sv3 && *sv3 == pv3)
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, sizeof(pv4), gfp_flags, shadow_ctor, &pv4);
if (!sv4)
return -ENOMEM;

sv = shadow_get_or_alloc(obj + 2, id, sizeof(pv4), gfp_flags, shadow_ctor, &pv4);
if (!sv)
return -EINVAL;
if (sv == sv4 && *sv4 == pv4)
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 */
sv = shadow_get(obj, id);
if (!sv)
pr_info(" got expected NULL result\n");
/* pass 1: init & alloc a char+int pair of svars for each objs */
for (i = 0; i < NUM_OBJS; i++) {
pnfields1[i] = &nfields1[i];
ptr_id(pnfields1[i]);

if (i % 2) {
sv1[i] = shadow_alloc(&objs[i], SV_ID1,
sizeof(pnfields1[i]), GFP_KERNEL,
shadow_ctor, &pnfields1[i]);
} else {
sv1[i] = shadow_get_or_alloc(&objs[i], SV_ID1,
sizeof(pnfields1[i]), GFP_KERNEL,
shadow_ctor, &pnfields1[i]);
}
if (!sv1[i]) {
ret = -ENOMEM;
goto out;
}

pnfields2[i] = &nfields2[i];
ptr_id(pnfields2[i]);
sv2[i] = shadow_alloc(&objs[i], SV_ID2, sizeof(pnfields2[i]),
GFP_KERNEL, shadow_ctor, &pnfields2[i]);
if (!sv2[i]) {
ret = -ENOMEM;
goto out;
}
}

shadow_free(obj + 1, id, shadow_dtor); /* sv2 */
sv = shadow_get(obj + 1, id);
if (!sv)
pr_info(" got expected NULL result\n");
/* pass 2: verify we find allocated svars and where they point to */
for (i = 0; i < NUM_OBJS; i++) {
/* check the "char" svar for all objects */
sv = shadow_get(&objs[i], SV_ID1);
if (!sv) {
ret = -EINVAL;
goto out;
}
if ((char **)sv == sv1[i] && *sv1[i] == pnfields1[i])
pr_info(" got expected PTR%d -> PTR%d result\n",
ptr_id(sv1[i]), ptr_id(*sv1[i]));

/* check the "int" svar for all objects */
sv = shadow_get(&objs[i], SV_ID2);
if (!sv) {
ret = -EINVAL;
goto out;
}
if ((int **)sv == sv2[i] && *sv2[i] == pnfields2[i])
pr_info(" got expected PTR%d -> PTR%d result\n",
ptr_id(sv2[i]), ptr_id(*sv2[i]));
}

shadow_free(obj + 2, id, shadow_dtor); /* sv4 */
sv = shadow_get(obj + 2, id);
if (!sv)
pr_info(" got expected NULL result\n");
/* pass 3: verify that 'get_or_alloc' returns already allocated svars */
for (i = 0; i < NUM_OBJS; i++) {
pndup[i] = &nfields1[i];
ptr_id(pndup[i]);

sv = shadow_get_or_alloc(&objs[i], SV_ID1, sizeof(pndup[i]),
GFP_KERNEL, shadow_ctor, &pndup[i]);
if (!sv) {
ret = -EINVAL;
goto out;
}
if ((char **)sv == sv1[i] && *sv1[i] == pnfields1[i])
pr_info(" got expected PTR%d -> PTR%d result\n",
ptr_id(sv1[i]), ptr_id(*sv1[i]));
}

/*
* We should still find an <id+1> variable.
*/
sv = shadow_get(obj, id + 1);
if (!sv)
return -EINVAL;
if (sv == sv3 && *sv3 == pv3)
pr_info(" got expected PTR%d -> PTR%d result\n",
ptr_id(sv3), ptr_id(*sv3));
/* pass 4: free <objs[*], SV_ID1> pairs of svars, verify removal */
for (i = 0; i < NUM_OBJS; i++) {
shadow_free(&objs[i], SV_ID1, shadow_dtor); /* 'char' pairs */
sv = shadow_get(&objs[i], SV_ID1);
if (!sv)
pr_info(" got expected NULL result\n");
}

/*
* Free all the <id+1> variables, too.
*/
shadow_free_all(id + 1, shadow_dtor); /* sv3 */
sv = shadow_get(obj, id);
if (!sv)
pr_info(" shadow_get() got expected NULL result\n");
/* pass 5: check we still find <objs[*], SV_ID2> svar pairs */
for (i = 0; i < NUM_OBJS; i++) {
sv = shadow_get(&objs[i], SV_ID2); /* 'int' pairs */
if (!sv) {
ret = -EINVAL;
goto out;
}
if ((int **)sv == sv2[i] && *sv2[i] == pnfields2[i])
pr_info(" got expected PTR%d -> PTR%d result\n",
ptr_id(sv2[i]), ptr_id(*sv2[i]));
}

/* pass 6: free all the <objs[*], SV_ID2> svar pairs too. */
shadow_free_all(SV_ID2, NULL); /* 'int' pairs */
for (i = 0; i < NUM_OBJS; i++) {
sv = shadow_get(&objs[i], SV_ID2);
if (!sv)
pr_info(" got expected NULL result\n");
}

free_ptr_list();

return 0;
out:
shadow_free_all(SV_ID1, NULL); /* 'char' pairs */
shadow_free_all(SV_ID2, NULL); /* 'int' pairs */
free_ptr_list();

return ret;
}

static void test_klp_shadow_vars_exit(void)
Expand Down
16 changes: 8 additions & 8 deletions tools/testing/selftests/livepatch/README
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ 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.)
buffer and parsed for expected messages. (Note: the tests will compare
the message buffer for only the duration of each individual test.)


Config
Expand Down Expand Up @@ -35,9 +35,9 @@ Adding tests
------------

See the common functions.sh file for the existing collection of utility
functions, most importantly setup_config() 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.)
functions, most importantly setup_config(), start_test() 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.)
Loading

0 comments on commit 1e21b5c

Please sign in to comment.