kunit: Test event import, delivery and timeout accounting

Move the existing client layout into a private header and link
separate suites with shared fixtures. Add task-scoped usercopy and
allocation boundaries around the actual ioctl and timeout paths.

Use private clients without scheduling work or accessing userspace
memory. Preserve payload validation and counter behavior; the
delivered-timeout assertion exposes the existing double decrement.

Include the private event headers needed by ordinary in-tree builds
in generated kernel patches.
This commit is contained in:
Gleb Chesnokov
2026-10-14 15:08:03 +03:00
parent bd6671cdda
commit bc20ee1ca1
10 changed files with 910 additions and 37 deletions
+1 -1
View File
@@ -60,7 +60,7 @@ validates the selection against the supplied `.ko` files.
| Module | Labels |
| --- | --- |
| `scst.ko` | `ALUA CDB LIB ORDER UNMAP` |
| `scst.ko` | `ALUA CDB EVENT LIB ORDER UNMAP` |
The runner reads suite metadata from the modules and loads selected owners
with their dependencies. Unselected suites are not counted as skipped.
+3 -2
View File
@@ -309,8 +309,9 @@ scst_03_public_headers="scst/include/scst.h scst/include/scst_const.h \
scst/include/scst_event.h scst/include/backport.h"
scst_04_main="scst/src/scst_main.c scst/src/scst_priv.h \
scst/src/scst_copy_mgr.c scst/src/scst_dlm.c scst/src/scst_dlm.h \
scst/src/scst_event.c scst/src/scst_no_dlm.c \
scst/src/scst_test.h"
scst/src/scst_event.c scst/src/scst_event_priv.h scst/src/scst_no_dlm.c \
scst/src/scst_test.h \
scst/src/scst_event_test.h"
scst_05_targ="scst/src/scst_targ.c scst/src/scst_local_cmd.c \
scst/src/scst_local_cmd.h \
scst/src/scst_targ_test.h"
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "scst_event_fixture.h"
void __user * const event_test_arg = (void __user *)0x1000UL;
static int event_test_get_size(s32 *size, const void __user *arg)
{
struct kunit *test = kunit_get_current_test();
struct event_test_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, arg, event_test_arg);
f->reads++;
if (!f->read_error)
*size = f->user_size;
return f->read_error;
}
static int event_test_get_payload_size(u32 *size, struct scst_event_user __user *arg)
{
s32 value;
int res = event_test_get_size(&value, arg);
if (!res)
*size = value;
return res;
}
static int event_test_put_size(s32 size, void __user *arg)
{
struct kunit *test = kunit_get_current_test();
struct event_test_fixture *f = test->priv;
KUNIT_EXPECT_PTR_EQ(test, arg, event_test_arg);
f->puts++;
f->needed_size = size;
return f->put_error;
}
static struct scst_event_entry *event_test_alloc(size_t size)
{
struct event_test_fixture *f = kunit_get_current_test()->priv;
f->allocations++;
f->allocation_size = size;
f->allocation = f->fail_alloc ? NULL : kzalloc(size, GFP_KERNEL);
return f->allocation;
}
static unsigned long event_test_copy_from(void *to, const void __user *from, size_t size)
{
struct kunit *test = kunit_get_current_test();
struct event_test_fixture *f = test->priv;
size_t left = min(f->from_left, size);
KUNIT_EXPECT_PTR_EQ(test, from, event_test_arg);
f->imports++;
f->imported = size;
/* Reject the historical entry-size copy before it can overrun the fixture. */
if (size != f->input_size || size > sizeof(f->input)) {
KUNIT_FAIL(test, "incorrect usercopy size: %zu, expected %zu", size, f->input_size);
return size;
}
if (f->importing &&
(!f->allocation || to != &f->allocation->event ||
size + offsetof(struct scst_event_entry, event) > f->allocation_size)) {
KUNIT_FAIL(test, "import does not fit the allocated event member");
return size;
}
memcpy(to, f->input, size - left);
/* copy_from_user() zeroes the uncopied tail. */
memset((u8 *)to + size - left, 0, left);
return left;
}
static unsigned long event_test_copy_to(void __user *to, const void *from, size_t size)
{
struct kunit *test = kunit_get_current_test();
struct event_test_fixture *f = test->priv;
size_t offset = offsetof(struct scst_event_user, out_event);
size_t left = min(f->to_left, size);
KUNIT_EXPECT_EQ(test, (unsigned long)to, (unsigned long)event_test_arg + offset);
f->exports++;
f->exported = size;
if (size > sizeof(struct scst_event) + SCST_MAX_PAYLOAD) {
KUNIT_FAIL(test, "oversized event export: %zu", size);
return size;
}
memcpy(f->output + offset, from, size - left);
return left;
}
static void event_test_notify(struct scst_event *event, void *data, int status)
{
struct event_test_fixture *f = data;
lockdep_assert_not_held(&scst_event_mutex);
f->notifications++;
f->notify_status = status;
f->notify_id = event->event_id;
}
static void event_test_fill(struct scst_event *event, unsigned int length, u32 code,
const char *issuer, u32 id)
{
unsigned int i;
event->payload_len = length;
event->event_code = code;
event->event_id = id;
strscpy(event->issuer_name, issuer, sizeof(event->issuer_name));
for (i = 0; i < length; i++)
event->payload[i] = i;
}
struct scst_event *event_test_input(struct event_test_fixture *f, unsigned int length,
u32 code, const char *issuer)
{
struct scst_event *event = (void *)f->input;
memset(f->input, 0, sizeof(f->input));
event_test_fill(event, length, code, issuer, 77);
f->user_size = length;
f->input_size = sizeof(*event) + length;
f->importing = true;
return event;
}
long event_test_ioctl(struct event_test_fixture *f, unsigned int command)
{
return scst_event_ioctl(&f->file, command, (unsigned long)event_test_arg);
}
struct scst_event_entry *event_test_queue(struct event_test_fixture *f,
unsigned int length, u32 id, bool notify)
{
struct scst_event_entry *e = kzalloc(sizeof(*e) + length, GFP_KERNEL);
if (!e)
return NULL;
event_test_fill(&e->event, length, 1, "kunit", id);
INIT_DELAYED_WORK(&e->event_timeout_work, scst_event_timeout_fn);
if (notify) {
e->event_notify_fn = event_test_notify;
e->notify_fn_priv = f;
}
e->pqueued_events_cnt = &f->priv.queued_events_cnt;
list_add_tail(&e->events_list_entry, &f->priv.queued_events_list);
f->priv.queued_events_cnt++;
return e;
}
void event_test_ack_input(struct event_test_fixture *f, u32 id, int status)
{
struct scst_event_notify_done n = { .event_id = id, .status = status };
memcpy(f->input, &n, sizeof(n));
f->input_size = sizeof(n);
f->importing = false;
}
unsigned int event_test_list_count(struct list_head *head)
{
struct list_head *pos;
unsigned int count = 0;
list_for_each(pos, head)
count++;
return count;
}
int event_test_init(struct kunit *test)
{
struct event_test_fixture *f = kunit_kzalloc(test, sizeof(*f), GFP_KERNEL);
test->priv = f;
if (!f)
return -ENOMEM;
INIT_LIST_HEAD(&f->priv.privs_list_entry);
INIT_LIST_HEAD(&f->priv.allowed_events_list);
INIT_LIST_HEAD(&f->priv.queued_events_list);
INIT_LIST_HEAD(&f->priv.processing_events_list);
init_waitqueue_head(&f->priv.queued_events_waitQ);
f->file.private_data = &f->priv;
f->user_size = INT_MAX;
memset(f->output, 0xa5, sizeof(f->output));
kunit_activate_static_stub(test, scst_event_get_user_payload_size,
event_test_get_payload_size);
kunit_activate_static_stub(test, scst_event_get_user_size, event_test_get_size);
kunit_activate_static_stub(test, scst_event_put_user_size, event_test_put_size);
kunit_activate_static_stub(test, scst_event_alloc_user_entry, event_test_alloc);
kunit_activate_static_stub(test, scst_event_copy_from_user, event_test_copy_from);
kunit_activate_static_stub(test, scst_event_copy_to_user, event_test_copy_to);
return 0;
}
static void event_test_free_list(struct list_head *head, bool work_initialized)
{
struct scst_event_entry *e, *next;
list_for_each_entry_safe(e, next, head, events_list_entry) {
list_del(&e->events_list_entry);
if (work_initialized)
cancel_delayed_work_sync(&e->event_timeout_work);
kfree(e);
}
}
void event_test_exit(struct kunit *test)
{
struct event_test_fixture *f = test->priv;
if (!f)
return;
/* No entry or client is registered globally, and no work is scheduled. */
event_test_free_list(&f->priv.allowed_events_list, false);
event_test_free_list(&f->priv.queued_events_list, true);
event_test_free_list(&f->priv.processing_events_list, true);
kfree(f->detached);
test->priv = NULL;
}
+65
View File
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef SCST_EVENT_FIXTURE_H
#define SCST_EVENT_FIXTURE_H
#include "../scst_kunit.h"
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst.h>
#else
#include "scst.h"
#endif
#include "../../scst_priv.h"
#include <linux/fs.h>
#include <linux/limits.h>
#ifdef INSIDE_KERNEL_TREE
#include <scst/scst_event.h>
#else
#include "scst_event.h"
#endif
#include "../../scst_event_priv.h"
#include "../../scst_event_test.h"
struct event_test_fixture {
struct file file;
struct scst_event_priv priv;
u8 input[sizeof(struct scst_event) + SCST_MAX_PAYLOAD];
u8 output[sizeof(struct scst_event_user) + SCST_MAX_PAYLOAD + 16];
size_t input_size;
s32 user_size;
s32 needed_size;
int read_error;
int put_error;
unsigned int reads;
unsigned int puts;
unsigned int imports;
unsigned int exports;
unsigned int allocations;
size_t from_left;
size_t to_left;
size_t imported;
size_t exported;
size_t allocation_size;
struct scst_event_entry *allocation;
struct scst_event_entry *detached;
bool fail_alloc;
bool importing;
unsigned int notifications;
int notify_status;
u32 notify_id;
};
/* Stubs interpret this token; it is never dereferenced as a userspace address. */
extern void __user * const event_test_arg;
struct scst_event *event_test_input(struct event_test_fixture *f, unsigned int length,
u32 code, const char *issuer);
long event_test_ioctl(struct event_test_fixture *f, unsigned int command);
struct scst_event_entry *event_test_queue(struct event_test_fixture *f,
unsigned int length, u32 id, bool notify);
void event_test_ack_input(struct event_test_fixture *f, u32 id, int status);
unsigned int event_test_list_count(struct list_head *head);
int event_test_init(struct kunit *test);
void event_test_exit(struct kunit *test);
#endif /* SCST_EVENT_FIXTURE_H */
@@ -0,0 +1,279 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "../scst_event_fixture.h"
/* Event ioctl ownership and error paths, without registering an event client. */
struct event_delivery_case {
const char *name;
unsigned int length;
int delta;
int result;
size_t uncopied;
bool large_buffer;
bool zero_buffer;
bool negative_buffer;
bool read_fault;
bool put_fault;
bool notify;
bool empty;
bool exiting;
};
static const struct event_delivery_case event_delivery_cases[] = {
{ .name = "empty_payload_exact_buffer" },
{ .name = "one_byte_exact_buffer", .length = 1 },
{ .name = "binary_payload", .length = 256 },
{ .name = "maximum_payload", .length = SCST_MAX_PAYLOAD },
{ .name = "larger_buffer", .length = 3, .delta = 1 },
{ .name = "int_max_buffer", .length = 3, .large_buffer = true },
{ .name = "zero_buffer", .zero_buffer = true, .result = -ENOSPC },
{ .name = "negative_buffer", .negative_buffer = true, .result = -ENOSPC },
{ .name = "short_header", .delta = -1, .result = -ENOSPC },
{ .name = "short_payload", .length = SCST_MAX_PAYLOAD, .delta = -1,
.result = -ENOSPC },
{ .name = "needed_size_write_fault", .delta = -1, .put_fault = true,
.result = -EFAULT },
{ .name = "capacity_read_fault", .read_fault = true, .result = -EFAULT },
{ .name = "full_copy_fault", .uncopied = SIZE_MAX, .result = -EFAULT },
{ .name = "last_header_byte_fault", .uncopied = 1, .result = -EFAULT },
{ .name = "partial_payload_fault", .length = 256, .uncopied = 128,
.result = -EFAULT },
{ .name = "notify_moves_to_processing", .length = 3, .notify = true },
{ .name = "notify_copy_fault_stays_queued", .length = 3, .notify = true,
.uncopied = 1, .result = -EFAULT },
{ .name = "notify_short_buffer_stays_queued", .length = 3, .notify = true,
.delta = -1, .result = -ENOSPC },
{ .name = "empty_nonblocking_queue", .empty = true, .result = -EAGAIN },
{ .name = "client_exiting", .empty = true, .exiting = true, .result = -EINTR },
};
static void event_delivery_desc(const struct event_delivery_case *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
}
KUNIT_ARRAY_PARAM(event_delivery, event_delivery_cases, event_delivery_desc);
static void event_delivery_test(struct kunit *test)
{
const struct event_delivery_case *p = test->param_value;
struct event_test_fixture *f = test->priv;
struct scst_event_entry *e;
size_t needed = sizeof(struct scst_event) + p->length;
size_t offset = offsetof(struct scst_event_user, out_event);
bool short_buffer, copied;
unsigned int queued, processing;
size_t i, bytes;
if (!p->empty) {
e = event_test_queue(f, p->length, 42, p->notify);
KUNIT_ASSERT_NOT_NULL(test, e);
memcpy(f->input, &e->event, needed);
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 0, 43, false));
}
f->user_size = needed + p->delta;
if (p->large_buffer)
f->user_size = INT_MAX;
if (p->zero_buffer)
f->user_size = 0;
if (p->negative_buffer)
f->user_size = -1;
f->read_error = p->read_fault ? -EFAULT : 0;
f->put_error = p->put_fault ? -EFAULT : 0;
f->to_left = p->uncopied;
f->priv.going_to_exit = p->exiting;
short_buffer = !p->empty && !p->read_fault && (int)needed > f->user_size;
copied = !p->empty && !p->read_fault && !short_buffer;
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)p->result);
KUNIT_EXPECT_EQ(test, f->reads, 1U);
KUNIT_EXPECT_EQ(test, f->puts, (unsigned int)short_buffer);
KUNIT_EXPECT_EQ(test, f->exports, (unsigned int)copied);
KUNIT_EXPECT_EQ(test, f->notifications, 0U);
if (short_buffer)
KUNIT_EXPECT_EQ(test, f->needed_size, (s32)needed);
bytes = copied ? needed - min(needed, p->uncopied) : 0;
if (copied) {
KUNIT_EXPECT_EQ(test, f->exported, needed);
KUNIT_EXPECT_EQ(test, memcmp(f->output + offset, f->input, bytes), 0);
}
/* Neither the outer size field nor bytes after the event are exported. */
for (i = 0; i < offset; i++)
KUNIT_EXPECT_EQ(test, f->output[i], (u8)0xa5);
KUNIT_EXPECT_NULL(test, memchr_inv(f->output + offset + bytes, 0xa5,
sizeof(f->output) - offset - bytes));
queued = p->empty ? 0 : p->result ? 2 : 1;
processing = p->notify && !p->result;
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, (int)queued);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.queued_events_list), queued);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.processing_events_list), processing);
if (queued) {
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.queued_events_list));
e = list_first_entry(&f->priv.queued_events_list, struct scst_event_entry,
events_list_entry);
KUNIT_EXPECT_EQ(test, e->event.event_id, p->result ? 42U : 43U);
}
if (processing) {
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.processing_events_list));
e = list_first_entry(&f->priv.processing_events_list, struct scst_event_entry,
events_list_entry);
KUNIT_EXPECT_EQ(test, e->event.event_id, 42U);
}
}
static void event_delivery_retry_test(struct kunit *test)
{
struct event_test_fixture *f = test->priv;
struct scst_event_user *output = (void *)f->output;
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 256, 42, false));
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 0, 43, false));
f->user_size = 0;
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)-ENOSPC);
KUNIT_EXPECT_EQ(test, f->needed_size, (s32)(sizeof(struct scst_event) + 256));
f->user_size = f->needed_size;
f->to_left = 1;
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)-EFAULT);
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, 2);
f->to_left = 0;
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
KUNIT_EXPECT_EQ(test, output->out_event.event_id, 42U);
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, 1);
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
KUNIT_EXPECT_EQ(test, output->out_event.event_id, 43U);
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, 0);
KUNIT_EXPECT_TRUE(test, list_empty(&f->priv.queued_events_list));
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), (long)-EAGAIN);
}
struct event_ack_case {
const char *name;
int status;
int result;
size_t uncopied;
bool unknown;
bool queued;
bool repeat;
};
static const struct event_ack_case event_ack_cases[] = {
{ .name = "successful_notification" },
{ .name = "negative_status", .status = -EIO },
{ .name = "positive_status", .status = 1 },
{ .name = "unknown_id", .unknown = true, .result = -ENOENT },
{ .name = "queued_event_is_not_delivered", .queued = true, .result = -ENOENT },
{ .name = "input_copy_fault", .uncopied = SIZE_MAX, .result = -EFAULT },
{ .name = "partial_status_copy_fault", .uncopied = 1, .result = -EFAULT },
{ .name = "acknowledge_once", .repeat = true },
};
static void event_ack_desc(const struct event_ack_case *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
}
KUNIT_ARRAY_PARAM(event_ack, event_ack_cases, event_ack_desc);
static void event_ack_test(struct kunit *test)
{
const struct event_ack_case *p = test->param_value;
struct event_test_fixture *f = test->priv;
struct scst_event_entry *e;
unsigned int processing = p->queued ? 0 : p->result ? 2 : 1;
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 3, 42, true));
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 3, 43, true));
if (!p->queued) {
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
}
event_test_ack_input(f, p->unknown ? 99 : 42, p->status);
f->from_left = p->uncopied;
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_NOTIFY_DONE), (long)p->result);
KUNIT_EXPECT_EQ(test, f->imported, sizeof(struct scst_event_notify_done));
KUNIT_EXPECT_EQ(test, f->notifications, (unsigned int)!p->result);
if (!p->result) {
KUNIT_EXPECT_EQ(test, f->notify_status, p->status);
KUNIT_EXPECT_EQ(test, f->notify_id, 42U);
}
if (p->repeat) {
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_NOTIFY_DONE), (long)-ENOENT);
KUNIT_EXPECT_EQ(test, f->notifications, 1U);
}
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, p->queued ? 2 : 0);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.queued_events_list),
p->queued ? 2U : 0U);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.processing_events_list), processing);
if (processing) {
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.processing_events_list));
e = list_first_entry(&f->priv.processing_events_list, struct scst_event_entry,
events_list_entry);
KUNIT_EXPECT_EQ(test, e->event.event_id, p->result ? 42U : 43U);
}
}
struct event_timeout_case {
const char *name;
bool delivered;
bool detached;
bool second;
};
static const struct event_timeout_case event_timeout_cases[] = {
{ .name = "expire_queued_event", .second = true },
{ .name = "expire_delivered_event", .delivered = true },
{ .name = "delivered_timeout_preserves_queued_count", .delivered = true, .second = true },
{ .name = "already_detached_event", .detached = true },
};
static void event_timeout_desc(const struct event_timeout_case *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
}
KUNIT_ARRAY_PARAM(event_timeout, event_timeout_cases, event_timeout_desc);
static void event_timeout_test(struct kunit *test)
{
const struct event_timeout_case *p = test->param_value;
struct event_test_fixture *f = test->priv;
struct scst_event_entry *e = event_test_queue(f, 3, 42, true);
KUNIT_ASSERT_NOT_NULL(test, e);
if (p->second)
KUNIT_ASSERT_NOT_NULL(test, event_test_queue(f, 0, 43, false));
if (p->delivered)
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_GET_NEXT_EVENT), 0L);
if (p->detached) {
list_del_init(&e->events_list_entry);
f->priv.queued_events_cnt--;
f->detached = e;
}
/* Deterministic expiry: the delayed work was initialized but never queued. */
scst_event_timeout_fn(&e->event_timeout_work.work);
KUNIT_EXPECT_EQ(test, f->priv.queued_events_cnt, (int)p->second);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.queued_events_list),
(unsigned int)p->second);
KUNIT_EXPECT_TRUE(test, list_empty(&f->priv.processing_events_list));
KUNIT_EXPECT_EQ(test, f->notifications, (unsigned int)!p->detached);
if (!p->detached) {
KUNIT_EXPECT_EQ(test, f->notify_status, -ETIMEDOUT);
KUNIT_EXPECT_EQ(test, f->notify_id, 42U);
}
event_test_ack_input(f, 42, 0);
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_NOTIFY_DONE), (long)-ENOENT);
KUNIT_EXPECT_EQ(test, f->notifications, (unsigned int)!p->detached);
}
static struct kunit_case scst_event_delivery_cases[] = {
KUNIT_CASE_PARAM(event_delivery_test, event_delivery_gen_params),
KUNIT_CASE(event_delivery_retry_test),
KUNIT_CASE_PARAM(event_ack_test, event_ack_gen_params),
KUNIT_CASE_PARAM(event_timeout_test, event_timeout_gen_params),
{}
};
scst_kunit_suite(scst_event_delivery_suite, "EVENT", "-delivery",
.init = event_test_init,
.exit = event_test_exit,
.test_cases = scst_event_delivery_cases,
);
@@ -0,0 +1,228 @@
// SPDX-License-Identifier: GPL-2.0-only
#include "../scst_event_fixture.h"
struct event_import_case {
const char *name;
int length;
int copied_delta;
size_t uncopied;
int result;
bool read_fault;
bool alloc_fault;
bool unterminated;
};
static const struct event_import_case event_import_cases[] = {
{ .name = "wildcard_empty_payload" },
{ .name = "one_byte", .length = 1 },
{ .name = "binary_payload", .length = 256 },
{ .name = "below_payload_limit", .length = SCST_MAX_PAYLOAD - 1 },
{ .name = "at_payload_limit", .length = SCST_MAX_PAYLOAD },
{ .name = "above_payload_limit", .length = SCST_MAX_PAYLOAD + 1, .result = -EINVAL },
{ .name = "int_max_length", .length = INT_MAX, .result = -EINVAL },
{ .name = "int_min_length", .length = INT_MIN, .result = -EINVAL },
{ .name = "negative_length", .length = -1, .result = -EINVAL },
{ .name = "size_read_fault", .read_fault = true, .result = -EFAULT },
{ .name = "allocation_failure", .alloc_fault = true, .result = -ENOMEM },
{ .name = "header_copy_fault", .uncopied = SIZE_MAX, .result = -EFAULT },
{ .name = "last_header_byte_fault", .uncopied = 1, .result = -EFAULT },
{ .name = "partial_payload_fault", .length = 256, .uncopied = 128, .result = -EFAULT },
{ .name = "last_payload_byte_fault", .length = SCST_MAX_PAYLOAD,
.uncopied = 1, .result = -EFAULT },
{ .name = "length_grew_during_copy", .copied_delta = 1, .result = -EINVAL },
{ .name = "length_shrank_during_copy", .length = 1, .copied_delta = -1,
.result = -EINVAL },
{ .name = "length_became_negative", .copied_delta = -1, .result = -EINVAL },
{ .name = "length_crossed_limit_during_copy", .length = SCST_MAX_PAYLOAD,
.copied_delta = 1, .result = -EINVAL },
{ .name = "unterminated_issuer", .length = 1, .unterminated = true },
};
static void event_import_desc(const struct event_import_case *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
}
KUNIT_ARRAY_PARAM(event_import, event_import_cases, event_import_desc);
static void event_import_test(struct kunit *test)
{
const struct event_import_case *p = test->param_value;
struct event_test_fixture *f = test->priv;
bool valid_length = p->length >= 0 && p->length <= SCST_MAX_PAYLOAD;
unsigned int allocations = !p->read_fault && valid_length;
unsigned int imports = allocations && !p->alloc_fault;
struct scst_event *input = event_test_input(f, valid_length ? p->length : 0, 0, "*");
struct scst_event_entry *e;
f->user_size = p->length;
input->payload_len = valid_length ? p->length + p->copied_delta : p->length;
if (p->unterminated)
memset(input->issuer_name, 'x', sizeof(input->issuer_name));
f->read_error = p->read_fault ? -EFAULT : 0;
f->fail_alloc = p->alloc_fault;
f->from_left = p->uncopied;
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), (long)p->result);
KUNIT_EXPECT_EQ(test, f->reads, 1U);
KUNIT_EXPECT_EQ(test, f->allocations, allocations);
KUNIT_EXPECT_EQ(test, f->imports, imports);
if (allocations)
KUNIT_EXPECT_EQ(test, f->allocation_size, sizeof(*e) + p->length);
if (imports)
KUNIT_EXPECT_EQ(test, f->imported, sizeof(*input) + p->length);
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, !p->result);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
(unsigned int)!p->result);
if (p->result)
return;
KUNIT_ASSERT_FALSE(test, list_empty(&f->priv.allowed_events_list));
e = list_first_entry(&f->priv.allowed_events_list, struct scst_event_entry,
events_list_entry);
input->issuer_name[sizeof(input->issuer_name) - 1] = '\0';
KUNIT_EXPECT_EQ(test, memcmp(&e->event, input, f->input_size), 0);
KUNIT_EXPECT_NULL(test, e->event_notify_fn);
KUNIT_EXPECT_NULL(test, e->notify_fn_priv);
KUNIT_EXPECT_NULL(test, e->pqueued_events_cnt);
}
struct event_subscription_case {
const char *name;
unsigned int command;
u32 code;
const char *issuer;
unsigned int length;
unsigned int remaining;
int result;
bool different_payload;
bool copy_fault;
};
static const struct event_subscription_case event_subscription_cases[] = {
{ "duplicate_ignores_event_id", SCST_EVENT_ALLOW_EVENT, 1, "alpha", 3, 7, -EEXIST },
{ "different_code", SCST_EVENT_ALLOW_EVENT, 3, "alpha", 3, 15 },
{ "different_payload_length", SCST_EVENT_ALLOW_EVENT, 1, "alpha", 1, 15 },
{ "different_binary_payload", SCST_EVENT_ALLOW_EVENT, 1, "alpha", 3, 15,
.different_payload = true },
{ "wildcard_duplicate", SCST_EVENT_ALLOW_EVENT, 0, "*", 0, 7, -EEXIST },
{ "remove_exact", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 3, 6 },
{ "wildcard_code", SCST_EVENT_DISALLOW_EVENT, 0, "beta", 3, 1 },
{ "wildcard_issuer", SCST_EVENT_DISALLOW_EVENT, 1, "*", 3, 2 },
{ "wildcard_payload", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 0, 6 },
{ "remove_all", SCST_EVENT_DISALLOW_EVENT, 0, "*", 0, 0 },
{ "missing_code", SCST_EVENT_DISALLOW_EVENT, 3, "*", 0, 7, -ENOENT },
{ "different_length_does_not_match", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 1,
7, -ENOENT },
{ "different_bytes_do_not_match", SCST_EVENT_DISALLOW_EVENT, 1, "alpha", 3,
7, -ENOENT, .different_payload = true },
{ "failed_disallow_preserves_subscriptions", SCST_EVENT_DISALLOW_EVENT, 0, "*", 0,
7, -EFAULT, .copy_fault = true },
};
static void event_subscription_desc(const struct event_subscription_case *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "%s", p->name);
}
KUNIT_ARRAY_PARAM(event_subscription, event_subscription_cases, event_subscription_desc);
static void event_subscription_test(struct kunit *test)
{
const struct event_subscription_case *p = test->param_value;
struct event_test_fixture *f = test->priv;
struct scst_event_entry *e;
struct scst_event *input;
unsigned int i, remaining = 0;
for (i = 0; i < 3; i++) {
input = event_test_input(f, 3, i == 1 ? 2 : 1, i ? "beta" : "alpha");
input->event_id = i;
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), 0L);
}
input = event_test_input(f, p->length, p->code, p->issuer);
input->event_id = 3;
if (p->different_payload)
input->payload[2] ^= 0xff;
f->from_left = p->copy_fault ? 1 : 0;
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, p->command), (long)p->result);
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, (int)hweight32(p->remaining));
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
hweight32(p->remaining));
list_for_each_entry(e, &f->priv.allowed_events_list, events_list_entry) {
KUNIT_ASSERT_LT(test, e->event.event_id, 4U);
remaining |= BIT(e->event.event_id);
}
KUNIT_EXPECT_EQ(test, remaining, p->remaining);
}
static void event_subscription_limit_test(struct kunit *test)
{
struct event_test_fixture *f = test->priv;
unsigned int i;
for (i = 1; i <= SCST_MAX_EVENTS; i++) {
event_test_input(f, 0, i, "limit");
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), 0L);
}
event_test_input(f, 0, SCST_MAX_EVENTS + 1, "limit");
KUNIT_EXPECT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), (long)-EMFILE);
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, SCST_MAX_EVENTS);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
(unsigned int)SCST_MAX_EVENTS);
event_test_input(f, 0, 1, "limit");
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_DISALLOW_EVENT), 0L);
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, SCST_MAX_EVENTS - 1);
event_test_input(f, 0, SCST_MAX_EVENTS + 1, "limit");
KUNIT_ASSERT_EQ(test, event_test_ioctl(f, SCST_EVENT_ALLOW_EVENT), 0L);
KUNIT_EXPECT_EQ(test, f->priv.allowed_events_cnt, SCST_MAX_EVENTS);
KUNIT_EXPECT_EQ(test, event_test_list_count(&f->priv.allowed_events_list),
(unsigned int)SCST_MAX_EVENTS);
}
static const unsigned int event_clone_lengths[] = { 0, 1, 256, SCST_MAX_PAYLOAD };
static void event_clone_desc(const unsigned int *length, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "payload_%u", *length);
}
KUNIT_ARRAY_PARAM(event_clone, event_clone_lengths, event_clone_desc);
static void event_clone_test(struct kunit *test)
{
struct event_test_fixture *f = test->priv;
unsigned int length = *(const unsigned int *)test->param_value;
struct scst_event_entry *original = event_test_queue(f, length, 42, false);
struct scst_event_entry *clone;
KUNIT_ASSERT_NOT_NULL(test, original);
KUNIT_ASSERT_EQ(test, scst_clone_event(original, &f->detached), 0);
clone = f->detached;
KUNIT_ASSERT_NOT_NULL(test, clone);
KUNIT_EXPECT_PTR_NE(test, clone, original);
KUNIT_EXPECT_EQ(test, memcmp(&clone->event, &original->event,
sizeof(clone->event) + length), 0);
KUNIT_EXPECT_NULL(test, clone->pqueued_events_cnt);
KUNIT_EXPECT_NULL(test, clone->event_notify_fn);
KUNIT_EXPECT_NULL(test, clone->notify_fn_priv);
clone->event.event_id = 99;
if (length) {
clone->event.payload[length - 1] ^= 0xff;
KUNIT_EXPECT_EQ(test, original->event.payload[length - 1], (u8)(length - 1));
}
KUNIT_EXPECT_EQ(test, original->event.event_id, 42U);
}
static struct kunit_case scst_event_import_cases[] = {
KUNIT_CASE_PARAM(event_import_test, event_import_gen_params),
KUNIT_CASE_PARAM(event_subscription_test, event_subscription_gen_params),
KUNIT_CASE(event_subscription_limit_test),
KUNIT_CASE_PARAM(event_clone_test, event_clone_gen_params),
{}
};
scst_kunit_suite(scst_event_import_suite, "EVENT", "-import",
.init = event_test_init,
.exit = event_test_exit,
.test_cases = scst_event_import_cases,
);
+3
View File
@@ -4,6 +4,9 @@
scst-kunit-scst := \
kunit/alua/suites/scst_kunit_alua.o \
kunit/cdb/suites/scst_kunit_cdb.o \
kunit/event/scst_event_fixture.o \
kunit/event/suites/scst_kunit_event_import.o \
kunit/event/suites/scst_kunit_event_delivery.o \
kunit/lib/suites/scst_kunit_lib.o \
kunit/order/suites/scst_kunit_order.o \
kunit/unmap/suites/scst_kunit_unmap.o
+60 -34
View File
@@ -28,6 +28,8 @@
#endif
#include "scst_priv.h"
#include "scst_event_priv.h"
#include "scst_event_test.h"
#undef DEFAULT_SYMBOL_NAMESPACE
#define DEFAULT_SYMBOL_NAMESPACE SCST_NAMESPACE
@@ -37,31 +39,52 @@ static struct class *scst_event_sysfs_class;
static int scst_event_major;
#define SCST_MAX_EVENTS 2048
#define SCST_MAX_PAYLOAD (3 * 1024)
#define SCST_DEFAULT_EVENT_TIMEOUT (60 * HZ)
struct scst_event_priv {
struct list_head privs_list_entry;
int allowed_events_cnt;
int queued_events_cnt;
unsigned int going_to_exit:1;
unsigned int blocking:1;
pid_t owner_pid;
/*
* WARNING: payloads in events in the allowed list queued AS IS from the
* user space, so they are UNTRUSTED and can NOT be used in any other
* way, except as BLOBs for comparison!
*/
struct list_head allowed_events_list;
struct list_head queued_events_list;
wait_queue_head_t queued_events_waitQ;
struct list_head processing_events_list;
};
static DEFINE_MUTEX(scst_event_mutex);
SCST_KUNIT_VISIBLE
struct mutex scst_event_mutex = __MUTEX_INITIALIZER(scst_event_mutex);
static LIST_HEAD(scst_event_privs_list);
SCST_KUNIT_VISIBLE
int scst_event_get_user_payload_size(u32 *size, struct scst_event_user __user *arg)
{
SCST_KUNIT_REDIRECT(scst_event_get_user_payload_size, size, arg);
return get_user(*size, &arg->max_event_size);
}
SCST_KUNIT_VISIBLE
int scst_event_get_user_size(s32 *size, const void __user *arg)
{
SCST_KUNIT_REDIRECT(scst_event_get_user_size, size, arg);
return get_user(*size, (const s32 __user *)arg);
}
SCST_KUNIT_VISIBLE
int scst_event_put_user_size(s32 size, void __user *arg)
{
SCST_KUNIT_REDIRECT(scst_event_put_user_size, size, arg);
return put_user(size, (s32 __user *)arg);
}
SCST_KUNIT_VISIBLE
unsigned long scst_event_copy_from_user(void *to, const void __user *from, size_t size)
{
SCST_KUNIT_REDIRECT(scst_event_copy_from_user, to, from, size);
return copy_from_user(to, from, size);
}
SCST_KUNIT_VISIBLE
unsigned long scst_event_copy_to_user(void __user *to, const void *from, size_t size)
{
SCST_KUNIT_REDIRECT(scst_event_copy_to_user, to, from, size);
return copy_to_user(to, from, size);
}
SCST_KUNIT_VISIBLE
struct scst_event_entry *scst_event_alloc_user_entry(size_t size)
{
SCST_KUNIT_REDIRECT(scst_event_alloc_user_entry, size);
return kzalloc(size, GFP_KERNEL);
}
/*
* Compares events e1_wild and e2, where e1_wild can have wildcard matching,
* i.e.:
@@ -97,7 +120,8 @@ out:
return res;
}
static void scst_event_timeout_fn(struct work_struct *work)
SCST_KUNIT_VISIBLE
void scst_event_timeout_fn(struct work_struct *work)
{
struct scst_event_entry *event_entry = container_of(work, struct scst_event_entry,
event_timeout_work.work);
@@ -132,8 +156,9 @@ out:
TRACE_EXIT();
}
static int scst_clone_event(const struct scst_event_entry *orig_entry,
struct scst_event_entry **new_event_entry)
SCST_KUNIT_VISIBLE
int scst_clone_event(const struct scst_event_entry *orig_entry,
struct scst_event_entry **new_event_entry)
{
int res = 0;
const struct scst_event *event = &orig_entry->event;
@@ -582,7 +607,7 @@ static int scst_event_get_event_from_user(struct scst_event_user __user *arg,
TRACE_ENTRY();
res = get_user(payload_len, &arg->max_event_size);
res = scst_event_get_user_payload_size(&payload_len, arg);
if (res != 0) {
PRINT_ERROR("Failed to get payload len: %d", res);
goto out;
@@ -599,7 +624,7 @@ static int scst_event_get_event_from_user(struct scst_event_user __user *arg,
event_entry_len = sizeof(*event_entry) + payload_len;
event_entry = kzalloc(event_entry_len, GFP_KERNEL);
event_entry = scst_event_alloc_user_entry(event_entry_len);
if (!event_entry) {
PRINT_ERROR("Unable to allocate event entry (size %d)",
event_entry_len);
@@ -612,7 +637,7 @@ static int scst_event_get_event_from_user(struct scst_event_user __user *arg,
event = &event_entry->event;
event_len = sizeof(*event) + payload_len;
rc = copy_from_user((u8 *)event, arg, event_len);
rc = scst_event_copy_from_user((u8 *)event, arg, event_len);
if (rc != 0) {
PRINT_ERROR("Failed to copy %d user's bytes", rc);
res = -EFAULT;
@@ -737,7 +762,7 @@ static int scst_event_user_next_event(struct scst_event_priv *priv, void __user
TRACE_ENTRY();
res = get_user(max_event_size, (int32_t __user *)arg);
res = scst_event_get_user_size(&max_event_size, arg);
if (res != 0) {
PRINT_ERROR("Failed to get max event size: %d", res);
goto out;
@@ -773,13 +798,13 @@ static int scst_event_user_next_event(struct scst_event_priv *priv, void __user
if (needed_size > max_event_size) {
TRACE_DBG("Too big event (size %d, max size %d)",
needed_size, max_event_size);
res = put_user(needed_size, (int32_t __user *)arg);
res = scst_event_put_user_size(needed_size, arg);
if (res == 0)
res = -ENOSPC;
goto out;
}
rc = copy_to_user(&event_user->out_event, &event_entry->event, needed_size);
rc = scst_event_copy_to_user(&event_user->out_event, &event_entry->event, needed_size);
if (rc != 0) {
PRINT_ERROR("Copy to user failed (%d)", rc);
res = -EFAULT;
@@ -815,7 +840,7 @@ static int scst_event_user_notify_done(struct scst_event_priv *priv, void __user
TRACE_ENTRY();
rc = copy_from_user(&n, arg, sizeof(n));
rc = scst_event_copy_from_user(&n, arg, sizeof(n));
if (rc != 0) {
PRINT_ERROR("Failed to copy %d user's bytes of notify done", rc);
res = -EFAULT;
@@ -911,7 +936,8 @@ out_put:
goto out;
}
static long scst_event_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
SCST_KUNIT_VISIBLE
long scst_event_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
long res;
struct scst_event_priv *priv;
+27
View File
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef SCST_EVENT_PRIV_H
#define SCST_EVENT_PRIV_H
#define SCST_MAX_EVENTS 2048
#define SCST_MAX_PAYLOAD (3 * 1024)
#define SCST_DEFAULT_EVENT_TIMEOUT (60 * HZ)
struct scst_event_priv {
struct list_head privs_list_entry;
int allowed_events_cnt;
int queued_events_cnt;
unsigned int going_to_exit:1;
unsigned int blocking:1;
pid_t owner_pid;
/*
* WARNING: payloads in events in the allowed list queued AS IS from the
* user space, so they are UNTRUSTED and can NOT be used in any other
* way, except as BLOBs for comparison!
*/
struct list_head allowed_events_list;
struct list_head queued_events_list;
wait_queue_head_t queued_events_waitQ;
struct list_head processing_events_list;
};
#endif /* SCST_EVENT_PRIV_H */
+23
View File
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef SCST_EVENT_TEST_H
#define SCST_EVENT_TEST_H
#include "scst_test.h"
#ifdef SCST_KUNIT_ENABLED
extern struct mutex scst_event_mutex;
int scst_event_get_user_payload_size(u32 *size, struct scst_event_user __user *arg);
int scst_event_get_user_size(s32 *size, const void __user *arg);
int scst_event_put_user_size(s32 size, void __user *arg);
unsigned long scst_event_copy_from_user(void *to, const void __user *from, size_t size);
unsigned long scst_event_copy_to_user(void __user *to, const void *from, size_t size);
struct scst_event_entry *scst_event_alloc_user_entry(size_t size);
void scst_event_timeout_fn(struct work_struct *work);
int scst_clone_event(const struct scst_event_entry *orig_entry,
struct scst_event_entry **new_event_entry);
long scst_event_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
#endif /* SCST_KUNIT_ENABLED */
#endif /* SCST_EVENT_TEST_H */