diff options
| author | Sasha Levin <sashal@kernel.org> | 2025-07-11 07:42:45 -0400 |
|---|---|---|
| committer | Sasha Levin <sashal@kernel.org> | 2026-10-07 18:26:44 -0400 |
| commit | ce4c52682d78fc08c9b1e2217aae6f00fa02bab3 (patch) | |
| tree | 04e29746babd97d3f4fc6b5beff753bb807ab31c /kernel/api/kapi_kunit.c | |
| parent | a90ee4305c4a5df72c11b31dacfdc76e00fcf78a (diff) | |
| download | linux-next-ce4c52682d78fc08c9b1e2217aae6f00fa02bab3.tar.gz linux-next-ce4c52682d78fc08c9b1e2217aae6f00fa02bab3.zip | |
kernel/api: introduce kernel API specification framework
Add a framework for formally documenting kernel APIs with inline
specifications. This framework provides:
- Structured API documentation with parameter specifications, return
values, error conditions, and execution context requirements
- Runtime validation capabilities for debugging
(CONFIG_KAPI_RUNTIME_CHECKS)
- Support for both internal kernel APIs and system calls
The framework stores specifications in a dedicated ELF section and
provides infrastructure for:
- Runtime querying of API documentation
- Integration with existing SYSCALL_DEFINE macros
A KUnit suite (CONFIG_KAPI_KUNIT_TEST) covers registration, lookup,
parameter and return value validation, and JSON export.
No specifications are added here; subsequent patches add them for
individual syscalls. With CONFIG_KAPI_SPEC=n the SYSCALL_DEFINEx()
expansion is unchanged. CONFIG_KAPI_RUNTIME_CHECKS is a debug option:
a syscall whose arguments violate its specification returns -EINVAL
without running.
Assisted-by: LLM
Signed-off-by: Sasha Levin <sashal@kernel.org>
Diffstat (limited to 'kernel/api/kapi_kunit.c')
| -rw-r--r-- | kernel/api/kapi_kunit.c | 720 |
1 files changed, 720 insertions, 0 deletions
diff --git a/kernel/api/kapi_kunit.c b/kernel/api/kapi_kunit.c new file mode 100644 index 000000000000..4f6f874c33b8 --- /dev/null +++ b/kernel/api/kapi_kunit.c @@ -0,0 +1,720 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Sasha Levin <sashal@kernel.org> + * + * KUnit tests for the Kernel API Specification Framework + * + * Tests registration, lookup, validation, and JSON export functionality. + */ + +#include <kunit/test.h> +#include <linux/kernel_api_spec.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/capability.h> +#include <linux/mm.h> +#include <linux/signal.h> +#include <linux/uaccess.h> + +static void init_test_spec(struct kernel_api_spec *spec, const char *name) +{ + memset(spec, 0, sizeof(*spec)); + spec->name = name; + spec->version = 1; + spec->description = "Test API"; +} + +/* kapi_register_spec with valid spec returns 0 */ +static void test_register_valid(struct kunit *test) +{ + struct kernel_api_spec *spec; + int ret; + + spec = kzalloc_obj(*spec, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + init_test_spec(spec, "test_register_valid"); + + ret = kapi_register_spec(spec); + KUNIT_EXPECT_EQ(test, ret, 0); + + kapi_unregister_spec("test_register_valid"); + kfree(spec); +} + +/* kapi_get_spec returns registered spec */ +static void test_lookup_registered(struct kunit *test) +{ + struct kernel_api_spec *spec; + const struct kernel_api_spec *found; + int ret; + + spec = kzalloc_obj(*spec, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + init_test_spec(spec, "test_lookup_func"); + + ret = kapi_register_spec(spec); + KUNIT_ASSERT_EQ(test, ret, 0); + + found = kapi_get_spec("test_lookup_func"); + KUNIT_EXPECT_PTR_EQ(test, found, (const struct kernel_api_spec *)spec); + + kapi_unregister_spec("test_lookup_func"); + kfree(spec); +} + +/* Double registration returns -EEXIST */ +static void test_double_register(struct kunit *test) +{ + struct kernel_api_spec *spec; + int ret; + + spec = kzalloc_obj(*spec, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + init_test_spec(spec, "test_double_reg"); + + ret = kapi_register_spec(spec); + KUNIT_ASSERT_EQ(test, ret, 0); + + ret = kapi_register_spec(spec); + KUNIT_EXPECT_EQ(test, ret, -EEXIST); + + kapi_unregister_spec("test_double_reg"); + kfree(spec); +} + +/* Unregister makes spec unfindable */ +static void test_unregister(struct kunit *test) +{ + struct kernel_api_spec *spec; + const struct kernel_api_spec *found; + int ret; + + spec = kzalloc_obj(*spec, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + init_test_spec(spec, "test_unreg_func"); + + ret = kapi_register_spec(spec); + KUNIT_ASSERT_EQ(test, ret, 0); + + kapi_unregister_spec("test_unreg_func"); + + found = kapi_get_spec("test_unreg_func"); + KUNIT_EXPECT_NULL(test, found); + + kfree(spec); +} + +/* kapi_get_spec(NULL) returns NULL */ +static void test_get_spec_null(struct kunit *test) +{ + const struct kernel_api_spec *found; + + found = kapi_get_spec(NULL); + KUNIT_EXPECT_NULL(test, found); +} + +/* kapi_register_spec(NULL) returns -EINVAL */ +static void test_register_null(struct kunit *test) +{ + int ret; + + ret = kapi_register_spec(NULL); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/* Spec with a NULL name is rejected */ +static void test_register_null_name(struct kunit *test) +{ + struct kernel_api_spec *spec; + int ret; + + spec = kzalloc_obj(*spec, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + /* spec->name == NULL after zero-init; registration rejects it. */ + ret = kapi_register_spec(spec); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + + kfree(spec); +} + +#ifdef CONFIG_KAPI_RUNTIME_CHECKS + +/* RANGE constraint - value in range is valid */ +static void test_constraint_range_valid(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_param"; + param.constraint_type = KAPI_CONSTRAINT_RANGE; + param.min_value = 0; + param.max_value = 100; + + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 50)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 100)); + + param.min_value = -10; + param.max_value = -1; + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, -10)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, -1)); + + /* An unsigned maximum such as U64_MAX reads as -1 */ + param.min_value = 0; + param.max_value = (s64)U64_MAX; + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, S64_MAX)); +} + +/* RANGE constraint - value out of range is invalid */ +static void test_constraint_range_invalid(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_param"; + param.constraint_type = KAPI_CONSTRAINT_RANGE; + param.min_value = 0; + param.max_value = 100; + + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, -1)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 101)); + + param.min_value = -10; + param.max_value = -1; + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, -11)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 5)); +} + +/* MASK constraint - valid bits pass */ +static void test_constraint_mask_valid(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_flags"; + param.constraint_type = KAPI_CONSTRAINT_MASK; + param.valid_mask = 0xFF; + + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0x00)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0x0F)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0xFF)); +} + +/* MASK constraint - extra bits fail */ +static void test_constraint_mask_invalid(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_flags"; + param.constraint_type = KAPI_CONSTRAINT_MASK; + param.valid_mask = 0xFF; + + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0x100)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0x1FF)); +} + +/* POWER_OF_TWO constraint */ +static void test_constraint_power_of_two(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_pot"; + param.constraint_type = KAPI_CONSTRAINT_POWER_OF_TWO; + + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 1)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 2)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 4)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 8)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 3)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 5)); +} + +/* PAGE_ALIGNED constraint */ +static void test_constraint_page_aligned(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_page"; + param.constraint_type = KAPI_CONSTRAINT_PAGE_ALIGNED; + + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, PAGE_SIZE)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 2 * PAGE_SIZE)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 1)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, PAGE_SIZE - 1)); +} + +/* NONZERO constraint */ +static void test_constraint_nonzero(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_nz"; + param.constraint_type = KAPI_CONSTRAINT_NONZERO; + + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 1)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, -1)); +} + +/* Return value validation - success */ +static void test_return_validation(struct kunit *test) +{ + struct kernel_api_spec *spec; + + spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + spec->name = "test_ret"; + spec->return_magic = KAPI_MAGIC_RETURN; + spec->return_spec.check_type = KAPI_RETURN_EXACT; + spec->return_spec.success_value = 0; + + KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 0)); +} + +/* Return value validation - known error */ +static void test_return_known_error(struct kunit *test) +{ + struct kernel_api_spec *spec; + + spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + spec->name = "test_ret_err"; + spec->return_magic = KAPI_MAGIC_RETURN; + spec->return_spec.check_type = KAPI_RETURN_FD; + spec->error_count = 1; + spec->errors[0].error_code = -ENOENT; + spec->errors[0].name = "ENOENT"; + + /* -ENOENT is in the error list, so it's valid */ + KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, -ENOENT)); +} + +/* Return value validation - unknown error */ +static void test_return_unknown_error(struct kunit *test) +{ + struct kernel_api_spec *spec; + + spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + spec->name = "test_ret_unk"; + spec->return_magic = KAPI_MAGIC_RETURN; + spec->return_spec.check_type = KAPI_RETURN_FD; + spec->error_count = 1; + spec->errors[0].error_code = -ENOENT; + spec->errors[0].name = "ENOENT"; + + /* -EPERM is not in the error list, but unlisted errors are accepted + * since filesystem/device-specific errors may not be exhaustively listed + */ + KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, -EPERM)); +} + +/* ALIGNMENT constraint */ +static void test_constraint_alignment(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_align"; + param.constraint_type = KAPI_CONSTRAINT_ALIGNMENT; + param.alignment = 8; + + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 8)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 16)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 1)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 7)); +} + +/* FD validation rejects values > INT_MAX */ +static void test_fd_int_overflow(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_fd"; + param.type = KAPI_TYPE_FD; + param.constraint_type = KAPI_CONSTRAINT_NONE; + + /* Value that overflows int: 0x100000003 -> truncates to 3 */ + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0x100000003LL)); +} + +/* ENUM constraint */ +static const s64 test_enum_vals[] = { 1, 5, 10 }; + +static void test_constraint_enum(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_enum"; + param.constraint_type = KAPI_CONSTRAINT_ENUM; + param.enum_values = test_enum_vals; + param.enum_count = ARRAY_SIZE(test_enum_vals); + + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 1)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 5)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 10)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 3)); + KUNIT_EXPECT_FALSE(test, kapi_validate_param(¶m, 11)); +} + +/* BUFFER constraint always accepts (size checked at runtime) */ +static void test_constraint_buffer(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + param.name = "test_buf"; + param.constraint_type = KAPI_CONSTRAINT_BUFFER; + + /* Buffer constraint doesn't validate the value itself */ + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 0)); + KUNIT_EXPECT_TRUE(test, kapi_validate_param(¶m, 4096)); +} + +/* RETURN_RANGE check type */ +static void test_return_range(struct kunit *test) +{ + struct kernel_api_spec *spec; + + spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + spec->name = "test_ret_range"; + spec->return_magic = KAPI_MAGIC_RETURN; + spec->return_spec.check_type = KAPI_RETURN_RANGE; + spec->return_spec.success_min = 0; + spec->return_spec.success_max = 100; + + KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 0)); + KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 50)); + KUNIT_EXPECT_TRUE(test, kapi_validate_return_value(spec, 100)); +} + +static void init_dyn_buf_param(struct kapi_param_spec *param) +{ + param->name = "buf"; + param->type = KAPI_TYPE_USER_PTR; + param->constraint_type = KAPI_CONSTRAINT_BUFFER; + param->size_param_idx = 2; +} + +static bool validate_dyn_buf(const struct kapi_param_spec *param, + const void __user *ptr, s64 count) +{ + s64 params[] = { (s64)(unsigned long)ptr, count }; + + return kapi_validate_param_with_context(param, params[0], params, + ARRAY_SIZE(params)); +} + +/* Dynamic buffer: any pointer is accepted when the size is 0 */ +static void test_dyn_buf_zero_count(struct kunit *test) +{ + struct kapi_param_spec param = {}; + const void __user *kernel_addr = (void __user *)-(unsigned long)PAGE_SIZE; + + init_dyn_buf_param(¶m); + + KUNIT_EXPECT_TRUE(test, validate_dyn_buf(¶m, NULL, 0)); + KUNIT_EXPECT_TRUE(test, validate_dyn_buf(¶m, (void __user *)PAGE_SIZE, 0)); + KUNIT_EXPECT_TRUE(test, validate_dyn_buf(¶m, kernel_addr, 0)); +} + +/* Dynamic buffer: NULL is rejected when the size is non-zero */ +static void test_dyn_buf_null_nonzero_count(struct kunit *test) +{ + struct kapi_param_spec param = {}; + + init_dyn_buf_param(¶m); + + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, NULL, 1)); + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, NULL, PAGE_SIZE)); +} + +/* + * Some architectures (separate user address space, !MMU) accept any address + * in access_ok(), so the rejection cases below only apply where it bounds + * user ranges. + */ +static bool kapi_rejects_kernel_addr(const void __user *addr) +{ + return !access_ok(addr, 1); +} + +static bool kapi_bounds_user_range(const void __user *base) +{ + return access_ok(base, PAGE_SIZE) && !access_ok(base, 3 * PAGE_SIZE); +} + +/* Dynamic buffer: addresses outside user space are rejected when the size is non-zero */ +static void test_dyn_buf_kernel_addr(struct kunit *test) +{ + struct kapi_param_spec param = {}; + const void __user *kernel_addr = (void __user *)-(unsigned long)PAGE_SIZE; + const void __user *top = (void __user *)ULONG_MAX; + + init_dyn_buf_param(¶m); + + if (!kapi_rejects_kernel_addr(kernel_addr) || !kapi_rejects_kernel_addr(top)) + kunit_skip(test, "access_ok() accepts kernel addresses on this architecture"); + + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, kernel_addr, 1)); + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, top, 1)); +} + +/* Dynamic buffer: an unset size_multiplier means byte units */ +static void test_dyn_buf_default_multiplier(struct kunit *test) +{ + struct kapi_param_spec param = {}; + const void __user *near_top = (void __user *)(TASK_SIZE_MAX - 2 * PAGE_SIZE); + + init_dyn_buf_param(¶m); + + KUNIT_EXPECT_TRUE(test, validate_dyn_buf(¶m, (void __user *)PAGE_SIZE, 16)); + KUNIT_EXPECT_TRUE(test, validate_dyn_buf(¶m, near_top, 0)); + + if (!kapi_bounds_user_range(near_top)) + kunit_skip(test, "access_ok() does not bound user ranges on this architecture"); + + KUNIT_EXPECT_TRUE(test, validate_dyn_buf(¶m, near_top, PAGE_SIZE)); + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, near_top, 3 * PAGE_SIZE)); +} + +/* Dynamic buffer: explicit multiplier scales the size, negative and overflowing counts fail */ +static void test_dyn_buf_multiplier_and_bad_count(struct kunit *test) +{ + struct kapi_param_spec param = {}; + const void __user *near_top = (void __user *)(TASK_SIZE_MAX - 2 * PAGE_SIZE); + + init_dyn_buf_param(¶m); + param.size_multiplier = 8; + + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, (void __user *)PAGE_SIZE, + (s64)(SIZE_MAX / 8 + 1))); + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, (void __user *)PAGE_SIZE, -1)); + + if (!kapi_bounds_user_range(near_top)) + kunit_skip(test, "access_ok() does not bound user ranges on this architecture"); + + KUNIT_EXPECT_TRUE(test, validate_dyn_buf(¶m, near_top, PAGE_SIZE / 8)); + KUNIT_EXPECT_FALSE(test, validate_dyn_buf(¶m, near_top, PAGE_SIZE)); +} + +#endif /* CONFIG_KAPI_RUNTIME_CHECKS */ + +static const struct kernel_api_spec kapi_test_macro_spec = { + .name = "test_macro_spec", + KAPI_SIGNAL_MASK_COUNT(1) + KAPI_SIGNAL_MASK(0, "blocked", "Signals blocked while waiting") + KAPI_SIGNAL_MASK_SIGNALS(SIGINT, SIGTERM, SIGQUIT) + }, + KAPI_CAPABILITY_COUNT(1) + KAPI_CAPABILITY(0, CAP_SYS_ADMIN, "CAP_SYS_ADMIN", KAPI_CAP_BYPASS_CHECK) + KAPI_CAP_ALTERNATIVE(CAP_SYS_RESOURCE, CAP_NET_ADMIN) + }, +}; + +/* Signal mask and capability alternative macros fill in their counts */ +static void test_signal_mask_and_cap_alternative(struct kunit *test) +{ + const struct kernel_api_spec *spec = &kapi_test_macro_spec; + char *buf; + + KUNIT_EXPECT_EQ(test, spec->signal_mask_count, 1U); + KUNIT_EXPECT_EQ(test, spec->signal_masks[0].signal_count, 3U); + KUNIT_EXPECT_EQ(test, spec->signal_masks[0].signals[2], SIGQUIT); + KUNIT_EXPECT_EQ(test, spec->capabilities[0].alternative_count, 2U); + KUNIT_EXPECT_EQ(test, spec->capabilities[0].alternative[1], CAP_NET_ADMIN); + + buf = kunit_kzalloc(test, 8192, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, buf); + KUNIT_ASSERT_GT(test, kapi_export_json(spec, buf, 8192), 0); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"blocked\"")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"alternatives\"")); +} + +/* Unregister non-existent spec is a no-op */ +static void test_unregister_nonexistent(struct kunit *test) +{ + /* Should not crash or error */ + kapi_unregister_spec("nonexistent_spec_xyz"); +} + +/* Multiple specs can be registered and looked up */ +static void test_multiple_specs(struct kunit *test) +{ + struct kernel_api_spec *spec1, *spec2; + const struct kernel_api_spec *found; + + spec1 = kzalloc_obj(*spec1, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec1); + spec2 = kzalloc_obj(*spec2, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec2); + + init_test_spec(spec1, "multi_spec_1"); + init_test_spec(spec2, "multi_spec_2"); + + KUNIT_ASSERT_EQ(test, kapi_register_spec(spec1), 0); + KUNIT_ASSERT_EQ(test, kapi_register_spec(spec2), 0); + + found = kapi_get_spec("multi_spec_1"); + KUNIT_EXPECT_PTR_EQ(test, found, (const struct kernel_api_spec *)spec1); + + found = kapi_get_spec("multi_spec_2"); + KUNIT_EXPECT_PTR_EQ(test, found, (const struct kernel_api_spec *)spec2); + + kapi_unregister_spec("multi_spec_1"); + kapi_unregister_spec("multi_spec_2"); + kfree(spec1); + kfree(spec2); +} + +/* JSON export produces valid output */ +static void test_json_export(struct kunit *test) +{ + static const s64 enum_vals[] = { 3, -1 }; + static const s64 err_vals[] = { -EINVAL, -EFAULT }; + struct kernel_api_spec *spec; + char *buf; + int ret; + + spec = kzalloc_obj(*spec, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + + buf = kzalloc(4096, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf); + + init_test_spec(spec, "test_json"); + spec->param_count = 1; + spec->params[0].name = "arg0"; + spec->params[0].type_name = "int"; + spec->params[0].constraint_type = KAPI_CONSTRAINT_ENUM; + spec->params[0].enum_values = enum_vals; + spec->params[0].enum_count = ARRAY_SIZE(enum_vals); + spec->params[0].valid_mask = 0xff; + spec->params[0].size_param_idx = 2; + spec->return_spec.check_type = KAPI_RETURN_ERROR_CHECK; + spec->return_spec.error_values = err_vals; + spec->return_spec.error_count = ARRAY_SIZE(err_vals); + + ret = kapi_export_json(spec, buf, 4096); + KUNIT_EXPECT_GT(test, ret, 0); + + /* Verify it starts with '{' and ends with '}' */ + KUNIT_EXPECT_EQ(test, buf[0], '{'); + KUNIT_ASSERT_GT(test, ret, 1); + /* Find last non-whitespace char */ + while (ret > 0 && (buf[ret - 1] == '\n' || buf[ret - 1] == ' ')) + ret--; + KUNIT_EXPECT_EQ(test, buf[ret - 1], '}'); + + /* Verify key fields are present */ + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"name\"")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"test_json\"")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"parameters\"")); + + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"constraint_type\": \"enum\"")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"enum_values\": [3, -1]")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"valid_mask\": \"0xff\"")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"size_param_idx\": 1")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"error_values\": [-22, -14]")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"state_transitions\"")); + KUNIT_EXPECT_NOT_NULL(test, strstr(buf, "\"struct_specs\"")); + + kfree(buf); + kfree(spec); +} + +/* JSON export with NULL args returns -EINVAL */ +static void test_json_export_null(struct kunit *test) +{ + struct kernel_api_spec *spec; + char buf[64]; + int ret; + + ret = kapi_export_json(NULL, buf, sizeof(buf)); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + + spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + init_test_spec(spec, "test"); + + ret = kapi_export_json(spec, NULL, 64); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); + + ret = kapi_export_json(spec, buf, 0); + KUNIT_EXPECT_EQ(test, ret, -EINVAL); +} + +/* JSON export into a buffer that is too small reports -E2BIG */ +static void test_json_export_small_buffer(struct kunit *test) +{ + struct kernel_api_spec *spec; + char buf[64]; + int ret; + + spec = kunit_kzalloc(test, sizeof(*spec), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, spec); + init_test_spec(spec, "test_small"); + + ret = kapi_export_json(spec, buf, sizeof(buf)); + + KUNIT_EXPECT_EQ(test, ret, -E2BIG); +} + +static struct kunit_case kapi_test_cases[] = { + KUNIT_CASE(test_register_valid), + KUNIT_CASE(test_lookup_registered), + KUNIT_CASE(test_double_register), + KUNIT_CASE(test_unregister), + KUNIT_CASE(test_get_spec_null), + KUNIT_CASE(test_register_null), + KUNIT_CASE(test_register_null_name), +#ifdef CONFIG_KAPI_RUNTIME_CHECKS + KUNIT_CASE(test_constraint_range_valid), + KUNIT_CASE(test_constraint_range_invalid), + KUNIT_CASE(test_constraint_mask_valid), + KUNIT_CASE(test_constraint_mask_invalid), + KUNIT_CASE(test_constraint_power_of_two), + KUNIT_CASE(test_constraint_page_aligned), + KUNIT_CASE(test_constraint_nonzero), + KUNIT_CASE(test_return_validation), + KUNIT_CASE(test_return_known_error), + KUNIT_CASE(test_return_unknown_error), + KUNIT_CASE(test_constraint_alignment), + KUNIT_CASE(test_fd_int_overflow), + KUNIT_CASE(test_constraint_enum), + KUNIT_CASE(test_constraint_buffer), + KUNIT_CASE(test_return_range), + KUNIT_CASE(test_dyn_buf_zero_count), + KUNIT_CASE(test_dyn_buf_null_nonzero_count), + KUNIT_CASE(test_dyn_buf_kernel_addr), + KUNIT_CASE(test_dyn_buf_default_multiplier), + KUNIT_CASE(test_dyn_buf_multiplier_and_bad_count), +#endif + KUNIT_CASE(test_signal_mask_and_cap_alternative), + KUNIT_CASE(test_unregister_nonexistent), + KUNIT_CASE(test_multiple_specs), + KUNIT_CASE(test_json_export), + KUNIT_CASE(test_json_export_null), + KUNIT_CASE(test_json_export_small_buffer), + {} +}; + +static struct kunit_suite kapi_test_suite = { + .name = "kapi", + .test_cases = kapi_test_cases, +}; + +kunit_test_suite(kapi_test_suite); + +MODULE_DESCRIPTION("KUnit tests for Kernel API Specification Framework"); +MODULE_LICENSE("GPL"); |
