diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-10 01:31:20 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-10 01:31:20 +0100 |
| commit | dba9716e82ae2c9eacfdc0925360375cd0bb6830 (patch) | |
| tree | b0a41f80f6a35a4be3c607e3957a9d08718baca8 /kernel | |
| parent | c148298f22c67c053bf6507ed092e0ae58a4b2a8 (diff) | |
| parent | ef777ef05ebcc5ea5393b4bd5af6fc3ad1b3aec9 (diff) | |
| download | linux-next-dba9716e82ae2c9eacfdc0925360375cd0bb6830.tar.gz linux-next-dba9716e82ae2c9eacfdc0925360375cd0bb6830.zip | |
Merge branch 'spec' of https://git.kernel.org/pub/scm/linux/kernel/git/sashal/kapi.git
# Conflicts:
# Makefile
# fs/open.c
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/Makefile | 1 | ||||
| -rw-r--r-- | kernel/api/Kconfig | 74 | ||||
| -rw-r--r-- | kernel/api/Makefile | 13 | ||||
| -rw-r--r-- | kernel/api/internal.h | 25 | ||||
| -rw-r--r-- | kernel/api/kapi_debugfs.c | 553 | ||||
| -rw-r--r-- | kernel/api/kapi_kunit.c | 720 | ||||
| -rw-r--r-- | kernel/api/kernel_api_spec.c | 1470 |
7 files changed, 2856 insertions, 0 deletions
diff --git a/kernel/Makefile b/kernel/Makefile index 2944487af036..d6c2f42567b7 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -59,6 +59,7 @@ obj-y += dma/ obj-y += entry/ obj-y += unwind/ obj-$(CONFIG_MODULES) += module/ +obj-$(CONFIG_KAPI_SPEC) += api/ obj-$(CONFIG_KCMP) += kcmp.o obj-$(CONFIG_FREEZER) += freezer.o diff --git a/kernel/api/Kconfig b/kernel/api/Kconfig new file mode 100644 index 000000000000..6075ab82bd23 --- /dev/null +++ b/kernel/api/Kconfig @@ -0,0 +1,74 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Kernel API Specification Framework Configuration +# + +config KAPI_SPEC + bool "Kernel API Specification Framework" + help + This option enables the kernel API specification framework, + which provides formal documentation of kernel APIs in both + human and machine-readable formats. + + The framework allows developers to document APIs inline with + their implementation, including parameter specifications, + return values, error conditions, locking requirements, and + execution context constraints. + + When enabled, API specifications can be queried at runtime + and exported in JSON format through debugfs. + + If unsure, say N. + +config KAPI_RUNTIME_CHECKS + bool "Runtime API specification checks" + depends on KAPI_SPEC + depends on DEBUG_KERNEL + # The hook is only in the generic and the x86 __SYSCALL_DEFINEx() + depends on X86 || !ARCH_HAS_SYSCALL_WRAPPER + help + Validate the arguments and the return value of system calls that + have an API specification, in their SYSCALL_DEFINEx() wrapper. + Violations are reported with pr_warn_ratelimited(). This adds + overhead to every system call. + + DEBUG-ONLY: Enabling this changes the errno seen by userspace for + syscalls that violate their parameter specification. On violation + the validator short-circuits the syscall and returns -EINVAL + before the real handler runs, masking whatever errno the handler + would otherwise have produced. Do not enable on production + kernels. + + If unsure, say N. + +config KAPI_SPEC_DEBUGFS + bool "Export kernel API specifications via debugfs" + depends on KAPI_SPEC + depends on DEBUG_FS + help + This option enables exporting kernel API specifications through + the debugfs filesystem. When enabled, specifications can be + accessed at /sys/kernel/debug/kapi/. + + The debugfs interface provides: + - A list of all available API specifications + - Detailed information for each API including parameters, + return values, errors, locking requirements, and constraints + - Complete machine-readable representation of the specs + + This is useful for documentation tools, static analyzers, and + runtime introspection of kernel APIs. + + If unsure, say N. + +config KAPI_KUNIT_TEST + tristate "KUnit tests for KAPI framework" if !KUNIT_ALL_TESTS + depends on KAPI_SPEC + depends on KUNIT + default KUNIT_ALL_TESTS + help + KUnit tests for the Kernel API Specification Framework. + Tests registration, lookup, validation constraints, and + JSON export functionality. + + If unsure, say N. diff --git a/kernel/api/Makefile b/kernel/api/Makefile new file mode 100644 index 000000000000..2e1c3555d5aa --- /dev/null +++ b/kernel/api/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the Kernel API Specification Framework +# + +# Core API specification framework +obj-y += kernel_api_spec.o + +# Debugfs interface for kernel API specs +obj-$(CONFIG_KAPI_SPEC_DEBUGFS) += kapi_debugfs.o + +# KUnit tests +obj-$(CONFIG_KAPI_KUNIT_TEST) += kapi_kunit.o diff --git a/kernel/api/internal.h b/kernel/api/internal.h new file mode 100644 index 000000000000..b6112bcc2e8d --- /dev/null +++ b/kernel/api/internal.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2026 Sasha Levin <sashal@kernel.org> + * + * Internal declarations shared by the KAPI core and its debugfs + * interface. Not part of the public kernel API. + */ + +#ifndef _KERNEL_API_INTERNAL_H +#define _KERNEL_API_INTERNAL_H + +#include <linux/kernel_api_spec.h> + +/* + * Section boundaries for the `.kapi_specs` array. Defined by the + * linker script in include/asm-generic/vmlinux.lds.h. + */ +extern const struct kernel_api_spec * const __start_kapi_specs[]; +extern const struct kernel_api_spec * const __stop_kapi_specs[]; + +const char *kapi_param_type_to_string(enum kapi_param_type type); +const char *kapi_lock_type_to_string(enum kapi_lock_type type); +const char *kapi_lock_scope_to_string(enum kapi_lock_scope scope); + +#endif /* _KERNEL_API_INTERNAL_H */ diff --git a/kernel/api/kapi_debugfs.c b/kernel/api/kapi_debugfs.c new file mode 100644 index 000000000000..5d9c2649a0dc --- /dev/null +++ b/kernel/api/kapi_debugfs.c @@ -0,0 +1,553 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Sasha Levin <sashal@kernel.org> + * + * Kernel API specification debugfs interface + * + * This provides a debugfs interface to expose kernel API specifications + * at runtime, allowing tools and users to query the complete API specs. + */ + +#include <linux/debugfs.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/seq_file.h> +#include <linux/kernel_api_spec.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include "internal.h" + +/* Helper to print parameter flags */ +static void print_param_flags(struct seq_file *m, u32 flags) +{ + seq_puts(m, " flags: "); + if ((flags & KAPI_PARAM_INOUT) == KAPI_PARAM_INOUT) + seq_puts(m, "INOUT "); + else if (flags & KAPI_PARAM_IN) + seq_puts(m, "IN "); + else if (flags & KAPI_PARAM_OUT) + seq_puts(m, "OUT "); + if (flags & KAPI_PARAM_OPTIONAL) + seq_puts(m, "OPTIONAL "); + if (flags & KAPI_PARAM_CONST) + seq_puts(m, "CONST "); + if (flags & KAPI_PARAM_USER) + seq_puts(m, "USER "); + if (flags & KAPI_PARAM_VOLATILE) + seq_puts(m, "VOLATILE "); + if (flags & KAPI_PARAM_DMA) + seq_puts(m, "DMA "); + if (flags & KAPI_PARAM_ALIGNED) + seq_puts(m, "ALIGNED "); + seq_puts(m, "\n"); +} + +/* Helper to print context flags */ +static void print_context_flags(struct seq_file *m, u32 flags) +{ + seq_puts(m, "Context flags: "); + if (flags & KAPI_CTX_PROCESS) + seq_puts(m, "PROCESS "); + if (flags & KAPI_CTX_HARDIRQ) + seq_puts(m, "HARDIRQ "); + if (flags & KAPI_CTX_SOFTIRQ) + seq_puts(m, "SOFTIRQ "); + if (flags & KAPI_CTX_NMI) + seq_puts(m, "NMI "); + if (flags & KAPI_CTX_SLEEPABLE) + seq_puts(m, "SLEEPABLE "); + if (flags & KAPI_CTX_ATOMIC) + seq_puts(m, "ATOMIC "); + if (flags & KAPI_CTX_PREEMPT_DISABLED) + seq_puts(m, "PREEMPT_DISABLED "); + if (flags & KAPI_CTX_IRQ_DISABLED) + seq_puts(m, "IRQ_DISABLED "); + seq_puts(m, "\n"); +} + +/* + * Print a multi-line value with every line indented, so that no line of it + * can be mistaken for a field of its own. + */ +static void print_block(struct seq_file *m, const char *text) +{ + while (*text) { + size_t len = strcspn(text, "\n"); + + if (len) + seq_printf(m, " %.*s", (int)len, text); + seq_putc(m, '\n'); + text += len; + if (*text == '\n') + text++; + } +} + +/* Helper to print a list of signed values */ +static void print_s64_list(struct seq_file *m, const char *label, + const s64 *vals, u32 count) +{ + u32 i; + + if (!vals || !count) + return; + + seq_printf(m, "%s:", label); + for (i = 0; i < count; i++) + seq_printf(m, " %lld", vals[i]); + seq_puts(m, "\n"); +} + +/* Helper to print which parameter carries a buffer's element count */ +static void print_size_param(struct seq_file *m, + const struct kernel_api_spec *spec, + const struct kapi_param_spec *param) +{ + int idx = param->size_param_idx - 1; + + if (idx < 0) + return; + + seq_printf(m, " size_param: %s (index %d)\n", + idx < spec->param_count && idx < KAPI_MAX_PARAMS ? + spec->params[idx].name : "?", idx); + if (param->size_multiplier) + seq_printf(m, " size_multiplier: %zu\n", + param->size_multiplier); +} + +/* Show function for individual API spec */ +static int kapi_spec_show(struct seq_file *m, void *v) +{ + struct kernel_api_spec *spec = m->private; + int i; + + seq_puts(m, "Kernel API Specification\n"); + seq_puts(m, "========================\n\n"); + + /* Basic info */ + seq_printf(m, "Name: %s\n", spec->name); + seq_printf(m, "Version: %u\n", spec->version); + seq_printf(m, "Description: %s\n", spec->description); + if (spec->long_description && *spec->long_description) { + seq_puts(m, "Long description:\n"); + print_block(m, spec->long_description); + } + + /* Context */ + print_context_flags(m, spec->context_flags); + seq_puts(m, "\n"); + + /* Parameters */ + if (spec->param_count > 0) { + seq_printf(m, "Parameters (%u):\n", spec->param_count); + for (i = 0; i < spec->param_count && i < KAPI_MAX_PARAMS; i++) { + struct kapi_param_spec *param = &spec->params[i]; + + seq_printf(m, " [%d] %s:\n", i, param->name); + seq_printf(m, " type: %s (%s)\n", + kapi_param_type_to_string(param->type), param->type_name); + print_param_flags(m, param->flags); + if (param->description && *param->description) + seq_printf(m, " description: %s\n", param->description); + if (param->size > 0) + seq_printf(m, " size: %zu\n", param->size); + if (param->alignment > 0) + seq_printf(m, " alignment: %zu\n", param->alignment); + + /* Print constraints if any */ + if (param->constraint_type != KAPI_CONSTRAINT_NONE || + (param->constraints && *param->constraints)) { + seq_puts(m, " constraints:\n"); + switch (param->constraint_type) { + case KAPI_CONSTRAINT_NONE: + break; + case KAPI_CONSTRAINT_RANGE: + seq_puts(m, " type: range\n"); + seq_printf(m, " min: %lld\n", param->min_value); + seq_printf(m, " max: %lld\n", param->max_value); + break; + case KAPI_CONSTRAINT_MASK: + seq_puts(m, " type: mask\n"); + seq_printf(m, " valid_bits: 0x%llx\n", + param->valid_mask); + break; + case KAPI_CONSTRAINT_ENUM: + seq_puts(m, " type: enum\n"); + seq_printf(m, " count: %u\n", param->enum_count); + print_s64_list(m, " values", + param->enum_values, + param->enum_count); + break; + case KAPI_CONSTRAINT_USER_STRING: + seq_puts(m, " type: user_string\n"); + seq_printf(m, " min_len: %lld\n", param->min_value); + seq_printf(m, " max_len: %lld\n", param->max_value); + break; + case KAPI_CONSTRAINT_USER_PATH: + seq_puts(m, " type: user_path\n"); + seq_puts(m, " max_len: PATH_MAX (4096)\n"); + break; + case KAPI_CONSTRAINT_USER_PTR: + seq_puts(m, " type: user_ptr\n"); + seq_printf(m, " size: %zu bytes\n", param->size); + print_size_param(m, spec, param); + break; + case KAPI_CONSTRAINT_BUFFER: + seq_puts(m, " type: buffer\n"); + print_size_param(m, spec, param); + break; + case KAPI_CONSTRAINT_ALIGNMENT: + seq_puts(m, " type: alignment\n"); + seq_printf(m, " alignment: %zu\n", param->alignment); + break; + case KAPI_CONSTRAINT_POWER_OF_TWO: + seq_puts(m, " type: power_of_two\n"); + break; + case KAPI_CONSTRAINT_PAGE_ALIGNED: + seq_puts(m, " type: page_aligned\n"); + break; + case KAPI_CONSTRAINT_NONZERO: + seq_puts(m, " type: nonzero\n"); + break; + case KAPI_CONSTRAINT_CUSTOM: + seq_puts(m, " type: custom\n"); + break; + default: + seq_printf(m, " type: unknown (%d)\n", + param->constraint_type); + break; + } + if (param->constraints && *param->constraints) + seq_printf(m, " description: %s\n", + param->constraints); + } + seq_puts(m, "\n"); + } + } + + /* Return value */ + seq_puts(m, "Return value:\n"); + seq_printf(m, " type: %s\n", spec->return_spec.type_name); + if (spec->return_spec.description && *spec->return_spec.description) + seq_printf(m, " description: %s\n", spec->return_spec.description); + + switch (spec->return_spec.check_type) { + case KAPI_RETURN_EXACT: + seq_printf(m, " success: == %lld\n", spec->return_spec.success_value); + break; + case KAPI_RETURN_RANGE: + seq_printf(m, " success: [%lld, %lld]\n", + spec->return_spec.success_min, + spec->return_spec.success_max); + break; + case KAPI_RETURN_FD: + seq_puts(m, " success: valid file descriptor (>= 0)\n"); + break; + case KAPI_RETURN_ERROR_CHECK: + seq_puts(m, " success: error check\n"); + print_s64_list(m, " error values", + spec->return_spec.error_values, + spec->return_spec.error_count); + break; + case KAPI_RETURN_CUSTOM: + seq_puts(m, " success: custom check\n"); + break; + case KAPI_RETURN_NO_RETURN: + seq_puts(m, " success: does not return\n"); + break; + default: + break; + } + seq_puts(m, "\n"); + + /* Errors */ + if (spec->error_count > 0) { + seq_printf(m, "Errors (%u):\n", spec->error_count); + for (i = 0; i < spec->error_count && i < KAPI_MAX_ERRORS; i++) { + struct kapi_error_spec *err = &spec->errors[i]; + + seq_printf(m, " %s (%d): %s\n", + err->name, err->error_code, err->description); + if (err->condition && *err->condition) + seq_printf(m, " condition: %s\n", err->condition); + } + seq_puts(m, "\n"); + } + + /* Locks */ + if (spec->lock_count > 0) { + seq_printf(m, "Locks (%u):\n", spec->lock_count); + for (i = 0; i < spec->lock_count && i < KAPI_MAX_LOCKS; i++) { + struct kapi_lock_spec *lock = &spec->locks[i]; + + seq_printf(m, " %s (%s): %s\n", lock->lock_name, + kapi_lock_type_to_string(lock->lock_type), + lock->description); + seq_printf(m, " scope: %s\n", + kapi_lock_scope_to_string(lock->scope)); + } + seq_puts(m, "\n"); + } + + /* Constraints */ + if (spec->constraint_count > 0) { + seq_printf(m, "Additional constraints (%u):\n", spec->constraint_count); + for (i = 0; i < spec->constraint_count && i < KAPI_MAX_CONSTRAINTS; i++) { + struct kapi_constraint_spec *cons = &spec->constraints[i]; + + seq_printf(m, " - %s", cons->name); + if (cons->description && *cons->description) + seq_printf(m, ": %s", cons->description); + seq_puts(m, "\n"); + if (cons->expression && *cons->expression) + seq_printf(m, " expression: %s\n", cons->expression); + } + seq_puts(m, "\n"); + } + + /* Signals */ + if (spec->signal_count > 0) { + seq_printf(m, "Signal handling (%u):\n", spec->signal_count); + for (i = 0; i < spec->signal_count && i < KAPI_MAX_SIGNALS; i++) { + struct kapi_signal_spec *sig = &spec->signals[i]; + + seq_printf(m, " %s (%d):\n", sig->signal_name, sig->signal_num); + seq_puts(m, " direction: "); + if (sig->direction & KAPI_SIGNAL_SEND) + seq_puts(m, "send "); + if (sig->direction & KAPI_SIGNAL_RECEIVE) + seq_puts(m, "receive "); + if (sig->direction & KAPI_SIGNAL_HANDLE) + seq_puts(m, "handle "); + if (sig->direction & KAPI_SIGNAL_BLOCK) + seq_puts(m, "block "); + if (sig->direction & KAPI_SIGNAL_IGNORE) + seq_puts(m, "ignore "); + seq_puts(m, "\n"); + seq_puts(m, " action: "); + switch (sig->action) { + case KAPI_SIGNAL_ACTION_DEFAULT: + seq_puts(m, "default"); + break; + case KAPI_SIGNAL_ACTION_TERMINATE: + seq_puts(m, "terminate"); + break; + case KAPI_SIGNAL_ACTION_COREDUMP: + seq_puts(m, "coredump"); + break; + case KAPI_SIGNAL_ACTION_STOP: + seq_puts(m, "stop"); + break; + case KAPI_SIGNAL_ACTION_CONTINUE: + seq_puts(m, "continue"); + break; + case KAPI_SIGNAL_ACTION_CUSTOM: + seq_puts(m, "custom"); + break; + case KAPI_SIGNAL_ACTION_RETURN: + seq_puts(m, "return"); + break; + case KAPI_SIGNAL_ACTION_RESTART: + seq_puts(m, "restart"); + break; + case KAPI_SIGNAL_ACTION_QUEUE: + seq_puts(m, "queue"); + break; + case KAPI_SIGNAL_ACTION_DISCARD: + seq_puts(m, "discard"); + break; + case KAPI_SIGNAL_ACTION_TRANSFORM: + seq_puts(m, "transform"); + break; + default: + seq_puts(m, "unknown"); + break; + } + seq_puts(m, "\n"); + if (sig->description && *sig->description) + seq_printf(m, " description: %s\n", sig->description); + } + seq_puts(m, "\n"); + } + + /* Side effects */ + if (spec->side_effect_count > 0) { + seq_printf(m, "Side effects (%u):\n", spec->side_effect_count); + for (i = 0; i < spec->side_effect_count && i < KAPI_MAX_SIDE_EFFECTS; i++) { + const struct kapi_side_effect *eff = &spec->side_effects[i]; + + seq_printf(m, " - %s", eff->target); + if (eff->description && *eff->description) + seq_printf(m, ": %s", eff->description); + if (eff->reversible) + seq_puts(m, " (reversible)"); + seq_puts(m, "\n"); + } + seq_puts(m, "\n"); + } + + /* State transitions */ + if (spec->state_trans_count > 0) { + seq_printf(m, "State transitions (%u):\n", spec->state_trans_count); + for (i = 0; i < spec->state_trans_count && i < KAPI_MAX_STATE_TRANS; i++) { + const struct kapi_state_transition *trans = &spec->state_transitions[i]; + + seq_printf(m, " %s: %s -> %s\n", trans->object, + trans->from_state, trans->to_state); + if (trans->description && *trans->description) + seq_printf(m, " %s\n", trans->description); + } + seq_puts(m, "\n"); + } + + /* Capabilities */ + if (spec->capability_count > 0) { + seq_printf(m, "Capabilities (%u):\n", spec->capability_count); + for (i = 0; i < spec->capability_count && i < KAPI_MAX_CAPABILITIES; i++) { + const struct kapi_capability_spec *cap = &spec->capabilities[i]; + + seq_printf(m, " %s (%d):\n", cap->cap_name, + cap->capability); + if (cap->allows && *cap->allows) + seq_printf(m, " allows: %s\n", cap->allows); + if (cap->without_cap && *cap->without_cap) + seq_printf(m, " without: %s\n", cap->without_cap); + } + seq_puts(m, "\n"); + } + + /* Additional info */ + if (spec->examples && *spec->examples) { + seq_puts(m, "Examples:\n"); + print_block(m, spec->examples); + seq_putc(m, '\n'); + } + if (spec->notes && *spec->notes) { + seq_puts(m, "Notes:\n"); + print_block(m, spec->notes); + seq_putc(m, '\n'); + } + + return 0; +} + +static int kapi_spec_open(struct inode *inode, struct file *file) +{ + return single_open(file, kapi_spec_show, inode->i_private); +} + +static const struct file_operations kapi_spec_fops = { + .open = kapi_spec_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * JSON view of a single spec. kapi_export_json() writes straight into the + * seq_file buffer; when the output does not fit, flagging an overflow makes + * seq_file retry with a larger buffer, so no fixed size limit applies. + */ +static int kapi_spec_json_show(struct seq_file *m, void *v) +{ + const struct kernel_api_spec *spec = m->private; + size_t size; + char *buf; + int ret; + + if (!spec) + return -EINVAL; + + size = seq_get_buf(m, &buf); + ret = size ? kapi_export_json(spec, buf, size) : -E2BIG; + if (ret == -E2BIG) { + seq_commit(m, -1); + return 0; + } + if (ret < 0) + return ret; + + seq_commit(m, ret); + return 0; +} + +static int kapi_spec_json_open(struct inode *inode, struct file *file) +{ + return single_open(file, kapi_spec_json_show, inode->i_private); +} + +static const struct file_operations kapi_spec_json_fops = { + .open = kapi_spec_json_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* + * Show all available API specs. + * + * Note: This only iterates the static .kapi_specs section. Specs registered + * dynamically via kapi_register_spec() are not included in this listing + * or in the per-spec debugfs files. + */ +static int kapi_list_show(struct seq_file *m, void *v) +{ + const struct kernel_api_spec * const *pp; + int count = 0; + + seq_puts(m, "Available Kernel API Specifications\n"); + seq_puts(m, "===================================\n\n"); + + for (pp = __start_kapi_specs; pp < __stop_kapi_specs; pp++) { + const struct kernel_api_spec *spec = *pp; + + if (!spec) + continue; + seq_printf(m, "%s - %s\n", spec->name, spec->description); + count++; + } + + seq_printf(m, "\nTotal: %d specifications\n", count); + return 0; +} + +static int kapi_list_open(struct inode *inode, struct file *file) +{ + return single_open(file, kapi_list_show, NULL); +} + +static const struct file_operations kapi_list_fops = { + .open = kapi_list_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int __init kapi_debugfs_init(void) +{ + const struct kernel_api_spec * const *pp; + struct dentry *root, *spec_dir, *json_dir; + + root = debugfs_create_dir("kapi", NULL); + debugfs_create_file("list", 0444, root, NULL, &kapi_list_fops); + spec_dir = debugfs_create_dir("specs", root); + json_dir = debugfs_create_dir("specs-json", root); + + for (pp = __start_kapi_specs; pp < __stop_kapi_specs; pp++) { + const struct kernel_api_spec *spec = *pp; + + if (!spec || !spec->name) + continue; + debugfs_create_file(spec->name, 0444, spec_dir, + (void *)spec, &kapi_spec_fops); + debugfs_create_file(spec->name, 0444, json_dir, + (void *)spec, &kapi_spec_json_fops); + } + + return 0; +} + +/* Initialize as part of kernel, not as a module */ +fs_initcall(kapi_debugfs_init); 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"); diff --git a/kernel/api/kernel_api_spec.c b/kernel/api/kernel_api_spec.c new file mode 100644 index 000000000000..993674ce6778 --- /dev/null +++ b/kernel/api/kernel_api_spec.c @@ -0,0 +1,1470 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Sasha Levin <sashal@kernel.org> + * + * kernel_api_spec.c - Kernel API Specification Framework Implementation + * + * Provides runtime support for kernel API specifications including validation, + * export to various formats, and querying capabilities. + */ + +#define pr_fmt(fmt) "kapi: " fmt + +#include <linux/kernel.h> +#include <linux/kernel_api_spec.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/mutex.h> +#include <linux/export.h> +#include <linux/preempt.h> +#include <linux/hardirq.h> +#include <linux/file.h> +#include <linux/fdtable.h> +#include <linux/uaccess.h> +#include <linux/limits.h> +#include <linux/fcntl.h> +#include <linux/mm.h> +#include <linux/ratelimit.h> + +#include "internal.h" + +/* Dynamic API registration */ +static LIST_HEAD(dynamic_api_specs); +static DEFINE_MUTEX(api_spec_mutex); + +struct dynamic_api_spec { + struct list_head list; + const struct kernel_api_spec *spec; +}; + +/* + * __kapi_find_spec_locked - Internal lookup, caller must hold api_spec_mutex + */ +static const struct kernel_api_spec *__kapi_find_spec_locked(const char *name) +{ + const struct kernel_api_spec * const *pp; + struct dynamic_api_spec *dyn_spec; + + for (pp = __start_kapi_specs; pp < __stop_kapi_specs; pp++) { + const struct kernel_api_spec *spec = *pp; + + if (spec && spec->name && strcmp(spec->name, name) == 0) + return spec; + } + + list_for_each_entry(dyn_spec, &dynamic_api_specs, list) { + if (dyn_spec->spec->name && + strcmp(dyn_spec->spec->name, name) == 0) + return dyn_spec->spec; + } + + return NULL; +} + +/** + * kapi_get_spec - Get API specification by name + * @name: Function name to look up + * + * Return: Pointer to the API specification, or NULL if not found. The + * pointer stays valid for specifications in the ``.kapi_specs`` ELF section + * (built-in, statically defined). A dynamically registered spec stays valid + * only until kapi_unregister_spec() is called for it, so the caller must + * serialize against unregistration. + * + * Context: May sleep. Do not call under spinlock or in IRQ context. + */ +const struct kernel_api_spec *kapi_get_spec(const char *name) +{ + const struct kernel_api_spec *spec; + + if (!name) + return NULL; + + mutex_lock(&api_spec_mutex); + spec = __kapi_find_spec_locked(name); + mutex_unlock(&api_spec_mutex); + + return spec; +} +EXPORT_SYMBOL_GPL(kapi_get_spec); + +/** + * kapi_register_spec - Register a dynamic API specification + * @spec: API specification to register + * + * Return: 0 on success, negative error code on failure + */ +int kapi_register_spec(const struct kernel_api_spec *spec) +{ + struct dynamic_api_spec *dyn_spec; + int ret = 0; + + if (!spec || !spec->name || !spec->name[0]) + return -EINVAL; + + dyn_spec = kzalloc_obj(*dyn_spec, GFP_KERNEL); + if (!dyn_spec) + return -ENOMEM; + + dyn_spec->spec = spec; + + mutex_lock(&api_spec_mutex); + + /* Check if already exists while holding lock to prevent races */ + if (__kapi_find_spec_locked(spec->name)) { + ret = -EEXIST; + kfree(dyn_spec); + } else { + list_add_tail(&dyn_spec->list, &dynamic_api_specs); + } + + mutex_unlock(&api_spec_mutex); + + return ret; +} +EXPORT_SYMBOL_GPL(kapi_register_spec); + +/** + * kapi_unregister_spec - Unregister a dynamic API specification + * @name: Name of API to unregister + */ +void kapi_unregister_spec(const char *name) +{ + struct dynamic_api_spec *dyn_spec, *tmp; + + if (!name) + return; + + mutex_lock(&api_spec_mutex); + list_for_each_entry_safe(dyn_spec, tmp, &dynamic_api_specs, list) { + if (dyn_spec->spec->name && + strcmp(dyn_spec->spec->name, name) == 0) { + list_del(&dyn_spec->list); + kfree(dyn_spec); + break; + } + } + mutex_unlock(&api_spec_mutex); +} +EXPORT_SYMBOL_GPL(kapi_unregister_spec); + +/** + * kapi_param_type_to_string - Convert parameter type to string + * @type: Parameter type + * + * Return: String representation of type + */ +const char *kapi_param_type_to_string(enum kapi_param_type type) +{ + static const char * const type_names[] = { + [KAPI_TYPE_VOID] = "void", + [KAPI_TYPE_INT] = "int", + [KAPI_TYPE_UINT] = "uint", + [KAPI_TYPE_PTR] = "pointer", + [KAPI_TYPE_STRUCT] = "struct", + [KAPI_TYPE_UNION] = "union", + [KAPI_TYPE_ENUM] = "enum", + [KAPI_TYPE_FUNC_PTR] = "function_pointer", + [KAPI_TYPE_ARRAY] = "array", + [KAPI_TYPE_FD] = "file_descriptor", + [KAPI_TYPE_USER_PTR] = "user_pointer", + [KAPI_TYPE_PATH] = "pathname", + [KAPI_TYPE_CUSTOM] = "custom", + }; + + if (type >= ARRAY_SIZE(type_names)) + return "unknown"; + + return type_names[type]; +} + +/** + * kapi_lock_type_to_string - Convert lock type to string + * @type: Lock type + * + * Return: String representation of lock type + */ +const char *kapi_lock_type_to_string(enum kapi_lock_type type) +{ + static const char * const lock_names[] = { + [KAPI_LOCK_NONE] = "none", + [KAPI_LOCK_MUTEX] = "mutex", + [KAPI_LOCK_SPINLOCK] = "spinlock", + [KAPI_LOCK_RWLOCK] = "rwlock", + [KAPI_LOCK_SEQLOCK] = "seqlock", + [KAPI_LOCK_RCU] = "rcu", + [KAPI_LOCK_SEMAPHORE] = "semaphore", + [KAPI_LOCK_CUSTOM] = "custom", + }; + + if (type >= ARRAY_SIZE(lock_names)) + return "unknown"; + + return lock_names[type]; +} + +/** + * kapi_lock_scope_to_string - Convert lock scope to string + * @scope: Lock scope + * + * Return: String representation of lock scope + */ +const char *kapi_lock_scope_to_string(enum kapi_lock_scope scope) +{ + static const char * const scope_names[] = { + [KAPI_LOCK_INTERNAL] = "internal", + [KAPI_LOCK_ACQUIRES] = "acquires", + [KAPI_LOCK_RELEASES] = "releases", + [KAPI_LOCK_CALLER_HELD] = "caller_held", + }; + + if (scope >= ARRAY_SIZE(scope_names)) + return "unknown"; + + return scope_names[scope]; +} + +/** + * return_check_type_to_string - Convert return check type to string + * @type: Return check type + * + * Return: String representation of return check type + */ +static const char *return_check_type_to_string(enum kapi_return_check_type type) +{ + static const char * const check_names[] = { + [KAPI_RETURN_EXACT] = "exact", + [KAPI_RETURN_RANGE] = "range", + [KAPI_RETURN_ERROR_CHECK] = "error_check", + [KAPI_RETURN_FD] = "file_descriptor", + [KAPI_RETURN_CUSTOM] = "custom", + [KAPI_RETURN_NO_RETURN] = "no_return", + }; + + if (type >= ARRAY_SIZE(check_names)) + return "unknown"; + + return check_names[type]; +} + +/** + * capability_action_to_string - Convert capability action to string + * @action: Capability action + * + * Return: String representation of capability action + */ +static const char *capability_action_to_string(enum kapi_capability_action action) +{ + static const char * const action_names[] = { + [KAPI_CAP_BYPASS_CHECK] = "bypass_check", + [KAPI_CAP_INCREASE_LIMIT] = "increase_limit", + [KAPI_CAP_OVERRIDE_RESTRICTION] = "override_restriction", + [KAPI_CAP_GRANT_PERMISSION] = "grant_permission", + [KAPI_CAP_MODIFY_BEHAVIOR] = "modify_behavior", + [KAPI_CAP_ACCESS_RESOURCE] = "access_resource", + [KAPI_CAP_PERFORM_OPERATION] = "perform_operation", + }; + + if (action >= ARRAY_SIZE(action_names)) + return "unknown"; + + return action_names[action]; +} + +/** + * constraint_type_to_string - Convert constraint type to string + * @type: Constraint type + * + * Return: String representation of constraint type + */ +static const char *constraint_type_to_string(enum kapi_constraint_type type) +{ + static const char * const constraint_names[] = { + [KAPI_CONSTRAINT_NONE] = "none", + [KAPI_CONSTRAINT_RANGE] = "range", + [KAPI_CONSTRAINT_MASK] = "mask", + [KAPI_CONSTRAINT_ENUM] = "enum", + [KAPI_CONSTRAINT_ALIGNMENT] = "alignment", + [KAPI_CONSTRAINT_POWER_OF_TWO] = "power_of_two", + [KAPI_CONSTRAINT_PAGE_ALIGNED] = "page_aligned", + [KAPI_CONSTRAINT_NONZERO] = "nonzero", + [KAPI_CONSTRAINT_USER_STRING] = "user_string", + [KAPI_CONSTRAINT_USER_PATH] = "user_path", + [KAPI_CONSTRAINT_USER_PTR] = "user_ptr", + [KAPI_CONSTRAINT_BUFFER] = "buffer", + [KAPI_CONSTRAINT_CUSTOM] = "custom", + }; + + if (type >= ARRAY_SIZE(constraint_names)) + return "unknown"; + + return constraint_names[type]; +} + +/* + * kapi_json_escape - Write a JSON-escaped string into a buffer + * @buf: Output buffer + * @size: Remaining space in buffer + * @str: Input string to escape + * + * Escapes backslash, double-quote, and control characters for JSON output. + * Return: Number of bytes written (via scnprintf semantics) + */ +static int kapi_json_escape(char *buf, size_t size, const char *str) +{ + int ret = 0; + const char *p; + + if (!str || size == 0) + return 0; + + for (p = str; *p && ret < size - 1; p++) { + switch (*p) { + case '\\': + ret += scnprintf(buf + ret, size - ret, "\\\\"); + break; + case '"': + ret += scnprintf(buf + ret, size - ret, "\\\""); + break; + case '\n': + ret += scnprintf(buf + ret, size - ret, "\\n"); + break; + case '\r': + ret += scnprintf(buf + ret, size - ret, "\\r"); + break; + case '\t': + ret += scnprintf(buf + ret, size - ret, "\\t"); + break; + default: + if ((unsigned char)*p < 0x20) { + ret += scnprintf(buf + ret, size - ret, + "\\u%04x", (unsigned char)*p); + } else { + ret += scnprintf(buf + ret, size - ret, + "%c", *p); + } + break; + } + } + + if (ret < size) + buf[ret] = '\0'; + + return ret; +} + +static int kapi_json_str(char *buf, size_t size, const char *str) +{ + int ret = 0; + + ret += scnprintf(buf, size, "\""); + ret += kapi_json_escape(buf + ret, size - ret, str); + ret += scnprintf(buf + ret, size - ret, "\""); + return ret; +} + +static int kapi_json_s64_list(char *buf, size_t size, const s64 *vals, u32 count) +{ + int ret = scnprintf(buf, size, "["); + u32 i; + + for (i = 0; vals && i < count; i++) + ret += scnprintf(buf + ret, size - ret, "%s%lld", + i ? ", " : "", vals[i]); + + ret += scnprintf(buf + ret, size - ret, "]"); + return ret; +} + +static int kapi_json_struct_spec(char *buf, size_t size, + const struct kapi_struct_spec *st) +{ + int ret; + u32 i; + + ret = scnprintf(buf, size, " {\n \"name\": "); + ret += kapi_json_str(buf + ret, size - ret, st->name); + ret += scnprintf(buf + ret, size - ret, + ",\n \"size\": %zu,\n \"alignment\": %zu,\n \"description\": ", + st->size, st->alignment); + ret += kapi_json_str(buf + ret, size - ret, st->description); + ret += scnprintf(buf + ret, size - ret, ",\n \"fields\": [\n"); + + for (i = 0; i < st->field_count && i < KAPI_MAX_PARAMS; i++) { + const struct kapi_struct_field *field = &st->fields[i]; + + ret += scnprintf(buf + ret, size - ret, + " {\n \"name\": "); + ret += kapi_json_str(buf + ret, size - ret, field->name); + ret += scnprintf(buf + ret, size - ret, ",\n \"type\": "); + ret += kapi_json_str(buf + ret, size - ret, field->type_name); + ret += scnprintf(buf + ret, size - ret, + ",\n" + " \"type_class\": \"%s\",\n" + " \"offset\": %zu,\n" + " \"size\": %zu,\n" + " \"flags\": \"0x%x\",\n" + " \"constraint_type\": \"%s\",\n" + " \"min_value\": %lld,\n" + " \"max_value\": %lld,\n" + " \"valid_mask\": \"0x%llx\",\n" + " \"enum_values\": ", + kapi_param_type_to_string(field->type), + field->offset, field->size, field->flags, + constraint_type_to_string(field->constraint_type), + field->min_value, field->max_value, field->valid_mask); + ret += kapi_json_str(buf + ret, size - ret, field->enum_values); + ret += scnprintf(buf + ret, size - ret, + ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, field->description); + ret += scnprintf(buf + ret, size - ret, + "\n }%s\n", + (i < st->field_count - 1) ? "," : ""); + } + + ret += scnprintf(buf + ret, size - ret, " ]\n }"); + return ret; +} + +/** + * kapi_export_json - Export API specification to JSON format + * @spec: API specification to export + * @buf: Buffer to write JSON to + * @size: Size of buffer + * + * Return: Number of bytes written, -EINVAL on bad arguments, or -E2BIG + * if the output did not fit in @buf (the contents are then incomplete) + */ +int kapi_export_json(const struct kernel_api_spec *spec, char *buf, size_t size) +{ + int ret = 0; + int i, j; + + if (!spec || !buf || size == 0) + return -EINVAL; + + ret = scnprintf(buf, size, "{\n \"name\": "); + ret += kapi_json_str(buf + ret, size - ret, spec->name); + ret += scnprintf(buf + ret, size - ret, + ",\n \"version\": %u,\n \"description\": ", + spec->version); + ret += kapi_json_str(buf + ret, size - ret, spec->description); + ret += scnprintf(buf + ret, size - ret, ",\n \"long_description\": "); + ret += kapi_json_str(buf + ret, size - ret, spec->long_description); + ret += scnprintf(buf + ret, size - ret, + ",\n \"context_flags\": \"0x%x\",\n", + spec->context_flags); + + /* Parameters */ + ret += scnprintf(buf + ret, size - ret, " \"parameters\": [\n"); + + for (i = 0; i < spec->param_count && i < KAPI_MAX_PARAMS; i++) { + const struct kapi_param_spec *param = &spec->params[i]; + + ret += scnprintf(buf + ret, size - ret, " {\n \"name\": "); + ret += kapi_json_str(buf + ret, size - ret, param->name); + ret += scnprintf(buf + ret, size - ret, ",\n \"type\": "); + ret += kapi_json_str(buf + ret, size - ret, param->type_name); + ret += scnprintf(buf + ret, size - ret, + ",\n \"type_class\": \"%s\",\n \"flags\": \"0x%x\",\n \"description\": ", + kapi_param_type_to_string(param->type), + param->flags); + ret += kapi_json_str(buf + ret, size - ret, param->description); + ret += scnprintf(buf + ret, size - ret, + ",\n \"constraint_type\": \"%s\",\n \"constraint_desc\": ", + constraint_type_to_string(param->constraint_type)); + ret += kapi_json_str(buf + ret, size - ret, param->constraints); + ret += scnprintf(buf + ret, size - ret, + ",\n" + " \"min_value\": %lld,\n" + " \"max_value\": %lld,\n" + " \"valid_mask\": \"0x%llx\",\n" + " \"enum_values\": ", + param->min_value, param->max_value, param->valid_mask); + ret += kapi_json_s64_list(buf + ret, size - ret, + param->enum_values, param->enum_count); + ret += scnprintf(buf + ret, size - ret, + ",\n \"size\": %zu,\n \"alignment\": %zu,\n \"size_param_idx\": ", + param->size, param->alignment); + if (param->size_param_idx > 0) + ret += scnprintf(buf + ret, size - ret, "%d", + param->size_param_idx - 1); + else + ret += scnprintf(buf + ret, size - ret, "null"); + ret += scnprintf(buf + ret, size - ret, + ",\n \"size_multiplier\": %zu\n }%s\n", + param->size_multiplier, + (i < spec->param_count - 1) ? "," : ""); + } + + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Return value */ + ret += scnprintf(buf + ret, size - ret, " \"return\": {\n \"type\": "); + ret += kapi_json_str(buf + ret, size - ret, spec->return_spec.type_name); + ret += scnprintf(buf + ret, size - ret, + ",\n" + " \"type_class\": \"%s\",\n" + " \"check_type\": \"%s\",\n" + " \"success_value\": %lld,\n" + " \"success_min\": %lld,\n" + " \"success_max\": %lld,\n" + " \"error_values\": ", + kapi_param_type_to_string(spec->return_spec.type), + return_check_type_to_string(spec->return_spec.check_type), + spec->return_spec.success_value, + spec->return_spec.success_min, + spec->return_spec.success_max); + ret += kapi_json_s64_list(buf + ret, size - ret, + spec->return_spec.error_values, + spec->return_spec.error_count); + + ret += scnprintf(buf + ret, size - ret, ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, spec->return_spec.description); + ret += scnprintf(buf + ret, size - ret, "\n },\n"); + + /* Errors */ + ret += scnprintf(buf + ret, size - ret, " \"errors\": [\n"); + + for (i = 0; i < spec->error_count && i < KAPI_MAX_ERRORS; i++) { + const struct kapi_error_spec *error = &spec->errors[i]; + + ret += scnprintf(buf + ret, size - ret, + " {\n \"code\": %d,\n \"name\": ", + error->error_code); + ret += kapi_json_str(buf + ret, size - ret, error->name); + ret += scnprintf(buf + ret, size - ret, ",\n \"condition\": "); + ret += kapi_json_str(buf + ret, size - ret, error->condition); + ret += scnprintf(buf + ret, size - ret, ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, error->description); + ret += scnprintf(buf + ret, size - ret, + "\n }%s\n", + (i < spec->error_count - 1) ? "," : ""); + } + + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Locks */ + ret += scnprintf(buf + ret, size - ret, " \"locks\": [\n"); + + for (i = 0; i < spec->lock_count && i < KAPI_MAX_LOCKS; i++) { + const struct kapi_lock_spec *lock = &spec->locks[i]; + + ret += scnprintf(buf + ret, size - ret, " {\n \"name\": "); + ret += kapi_json_str(buf + ret, size - ret, lock->lock_name); + ret += scnprintf(buf + ret, size - ret, + ",\n \"type\": \"%s\",\n \"scope\": \"%s\",\n \"description\": ", + kapi_lock_type_to_string(lock->lock_type), + kapi_lock_scope_to_string(lock->scope)); + ret += kapi_json_str(buf + ret, size - ret, lock->description); + ret += scnprintf(buf + ret, size - ret, + "\n }%s\n", + (i < spec->lock_count - 1) ? "," : ""); + } + + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Capabilities */ + ret += scnprintf(buf + ret, size - ret, " \"capabilities\": [\n"); + + for (i = 0; i < spec->capability_count && i < KAPI_MAX_CAPABILITIES; i++) { + const struct kapi_capability_spec *cap = &spec->capabilities[i]; + + ret += scnprintf(buf + ret, size - ret, + " {\n \"capability\": %d,\n \"name\": ", + cap->capability); + ret += kapi_json_str(buf + ret, size - ret, cap->cap_name); + ret += scnprintf(buf + ret, size - ret, + ",\n \"action\": \"%s\",\n \"allows\": ", + capability_action_to_string(cap->action)); + ret += kapi_json_str(buf + ret, size - ret, cap->allows); + ret += scnprintf(buf + ret, size - ret, ",\n \"without_cap\": "); + ret += kapi_json_str(buf + ret, size - ret, cap->without_cap); + ret += scnprintf(buf + ret, size - ret, ",\n \"check_condition\": "); + ret += kapi_json_str(buf + ret, size - ret, cap->check_condition); + ret += scnprintf(buf + ret, size - ret, ",\n \"priority\": %u", cap->priority); + + if (cap->alternative_count > 0) { + ret += scnprintf(buf + ret, size - ret, + ",\n \"alternatives\": ["); + for (j = 0; j < cap->alternative_count && j < KAPI_MAX_CAPABILITIES; j++) { + ret += scnprintf(buf + ret, size - ret, + "%s%d", j ? ", " : "", + cap->alternative[j]); + } + ret += scnprintf(buf + ret, size - ret, "]"); + } + + ret += scnprintf(buf + ret, size - ret, + "\n }%s\n", + (i < spec->capability_count - 1) ? "," : ""); + } + + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Constraints */ + ret += scnprintf(buf + ret, size - ret, " \"constraints\": [\n"); + for (i = 0; i < spec->constraint_count && i < KAPI_MAX_CONSTRAINTS; i++) { + const struct kapi_constraint_spec *con = &spec->constraints[i]; + + ret += scnprintf(buf + ret, size - ret, " {\n \"name\": "); + ret += kapi_json_str(buf + ret, size - ret, con->name); + ret += scnprintf(buf + ret, size - ret, ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, con->description); + ret += scnprintf(buf + ret, size - ret, ",\n \"expression\": "); + ret += kapi_json_str(buf + ret, size - ret, con->expression); + ret += scnprintf(buf + ret, size - ret, + "\n }%s\n", + (i < spec->constraint_count - 1) ? "," : ""); + } + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Signals */ + ret += scnprintf(buf + ret, size - ret, " \"signals\": [\n"); + for (i = 0; i < spec->signal_count && i < KAPI_MAX_SIGNALS; i++) { + const struct kapi_signal_spec *sig = &spec->signals[i]; + + ret += scnprintf(buf + ret, size - ret, + " {\n \"signal_num\": %d,\n \"signal_name\": ", + sig->signal_num); + ret += kapi_json_str(buf + ret, size - ret, sig->signal_name); + ret += scnprintf(buf + ret, size - ret, + ",\n \"direction\": \"0x%x\",\n \"action\": %u,\n \"target\": ", + sig->direction, sig->action); + ret += kapi_json_str(buf + ret, size - ret, sig->target); + ret += scnprintf(buf + ret, size - ret, ",\n \"condition\": "); + ret += kapi_json_str(buf + ret, size - ret, sig->condition); + ret += scnprintf(buf + ret, size - ret, ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, sig->description); + ret += scnprintf(buf + ret, size - ret, + ",\n" + " \"restartable\": %s,\n" + " \"sa_flags_required\": \"0x%x\",\n" + " \"sa_flags_forbidden\": \"0x%x\",\n" + " \"error_on_signal\": %d,\n" + " \"transform_to\": %d,\n" + " \"timing\": ", + sig->restartable ? "true" : "false", + sig->sa_flags_required, + sig->sa_flags_forbidden, + sig->error_on_signal, + sig->transform_to); + ret += kapi_json_str(buf + ret, size - ret, sig->timing); + ret += scnprintf(buf + ret, size - ret, + ",\n \"priority\": %u,\n \"interruptible\": %s,\n \"queue_behavior\": ", + sig->priority, + sig->interruptible ? "true" : "false"); + ret += kapi_json_str(buf + ret, size - ret, sig->queue_behavior); + ret += scnprintf(buf + ret, size - ret, + ",\n \"state_required\": \"0x%x\",\n \"state_forbidden\": \"0x%x\"\n }%s\n", + sig->state_required, + sig->state_forbidden, + (i < spec->signal_count - 1) ? "," : ""); + } + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Side effects */ + ret += scnprintf(buf + ret, size - ret, " \"side_effects\": [\n"); + for (i = 0; i < spec->side_effect_count && i < KAPI_MAX_SIDE_EFFECTS; i++) { + const struct kapi_side_effect *eff = &spec->side_effects[i]; + + ret += scnprintf(buf + ret, size - ret, + " {\n \"type\": \"0x%x\",\n \"target\": ", + eff->type); + ret += kapi_json_str(buf + ret, size - ret, eff->target); + ret += scnprintf(buf + ret, size - ret, ",\n \"condition\": "); + ret += kapi_json_str(buf + ret, size - ret, eff->condition); + ret += scnprintf(buf + ret, size - ret, ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, eff->description); + ret += scnprintf(buf + ret, size - ret, + ",\n \"reversible\": %s\n }%s\n", + eff->reversible ? "true" : "false", + (i < spec->side_effect_count - 1) ? "," : ""); + } + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* State transitions */ + ret += scnprintf(buf + ret, size - ret, " \"state_transitions\": [\n"); + for (i = 0; i < spec->state_trans_count && i < KAPI_MAX_STATE_TRANS; i++) { + const struct kapi_state_transition *trans = &spec->state_transitions[i]; + + ret += scnprintf(buf + ret, size - ret, " {\n \"object\": "); + ret += kapi_json_str(buf + ret, size - ret, trans->object); + ret += scnprintf(buf + ret, size - ret, ",\n \"from_state\": "); + ret += kapi_json_str(buf + ret, size - ret, trans->from_state); + ret += scnprintf(buf + ret, size - ret, ",\n \"to_state\": "); + ret += kapi_json_str(buf + ret, size - ret, trans->to_state); + ret += scnprintf(buf + ret, size - ret, ",\n \"condition\": "); + ret += kapi_json_str(buf + ret, size - ret, trans->condition); + ret += scnprintf(buf + ret, size - ret, ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, trans->description); + ret += scnprintf(buf + ret, size - ret, + "\n }%s\n", + (i < spec->state_trans_count - 1) ? "," : ""); + } + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Signal masks */ + ret += scnprintf(buf + ret, size - ret, " \"signal_masks\": [\n"); + for (i = 0; i < spec->signal_mask_count && i < KAPI_MAX_SIGNALS; i++) { + const struct kapi_signal_mask_spec *mask = &spec->signal_masks[i]; + + ret += scnprintf(buf + ret, size - ret, " {\n \"name\": "); + ret += kapi_json_str(buf + ret, size - ret, mask->mask_name); + ret += scnprintf(buf + ret, size - ret, ",\n \"description\": "); + ret += kapi_json_str(buf + ret, size - ret, mask->description); + ret += scnprintf(buf + ret, size - ret, ",\n \"signals\": ["); + for (j = 0; j < mask->signal_count && j < KAPI_MAX_SIGNALS; j++) + ret += scnprintf(buf + ret, size - ret, "%s%d", + j ? ", " : "", mask->signals[j]); + ret += scnprintf(buf + ret, size - ret, + "]\n }%s\n", + (i < spec->signal_mask_count - 1) ? "," : ""); + } + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Structure specifications */ + ret += scnprintf(buf + ret, size - ret, " \"struct_specs\": [\n"); + for (i = 0; i < spec->struct_spec_count && i < KAPI_MAX_STRUCT_SPECS; i++) { + ret += kapi_json_struct_spec(buf + ret, size - ret, + &spec->struct_specs[i]); + ret += scnprintf(buf + ret, size - ret, "%s\n", + (i < spec->struct_spec_count - 1) ? "," : ""); + } + ret += scnprintf(buf + ret, size - ret, " ],\n"); + + /* Additional info */ + ret += scnprintf(buf + ret, size - ret, " \"examples\": "); + ret += kapi_json_str(buf + ret, size - ret, spec->examples); + ret += scnprintf(buf + ret, size - ret, ",\n \"notes\": "); + ret += kapi_json_str(buf + ret, size - ret, spec->notes); + ret += scnprintf(buf + ret, size - ret, "\n}\n"); + + /* scnprintf() never writes past size - 1, so a full buffer means truncation */ + if (ret >= size - 1) + return -E2BIG; + + return ret; +} +EXPORT_SYMBOL_GPL(kapi_export_json); + +#ifdef CONFIG_KAPI_RUNTIME_CHECKS + +#define CREATE_TRACE_POINTS +#include <trace/events/kapi.h> + +/* + * Render a syscall's parameters as a "name=value, ..." string for the + * kapi_syscall_enter tracepoint. Names come from the spec; pointer-like + * values are shown in hex, integers and file descriptors in decimal. + */ +static void kapi_trace_format_params(const struct kernel_api_spec *spec, + const s64 *args, int nargs, + char *buf, size_t size) +{ + int i, used = 0; + + buf[0] = '\0'; + /* Bound by the caller-supplied arg count; the spec arity may differ. */ + for (i = 0; args && i < nargs && i < 6; i++) { + const char *name = "arg"; + bool dec = false; + + if (i < spec->param_count) { + const struct kapi_param_spec *ps = &spec->params[i]; + + if (ps->name) + name = ps->name; + dec = ps->type == KAPI_TYPE_INT || ps->type == KAPI_TYPE_FD; + } + + used += scnprintf(buf + used, size - used, "%s%s=", + i ? ", " : "", name); + if (dec) + used += scnprintf(buf + used, size - used, "%lld", + (long long)args[i]); + else + used += scnprintf(buf + used, size - used, "0x%llx", + (unsigned long long)args[i]); + } +} + +/** + * kapi_validate_fd - Validate that a file descriptor value is in valid range + * @fd: File descriptor to validate + * + * Only the numeric range is checked: AT_FDCWD or a non-negative value. + * Openness is left to the syscall, since the fd can be closed between check + * and use. Other negative values are rejected here, so a syscall such as + * close() fails with EINVAL for them rather than EBADF. + * + * Return: true if fd is in valid range, false otherwise + */ +static bool kapi_validate_fd(int fd) +{ + return fd == AT_FDCWD || fd >= 0; +} + +/** + * kapi_validate_user_ptr - Validate that a user pointer is accessible + * @ptr: User pointer to validate + * @size: Size in bytes to validate + * + * Return: true if user memory is accessible, false otherwise + */ +static bool kapi_validate_user_ptr(const void __user *ptr, size_t size) +{ + /* NULL pointers are not valid; caller handles optional case */ + if (!ptr) + return false; + + return access_ok(ptr, size); +} + +/** + * kapi_validate_user_ptr_with_params - Validate user pointer with dynamic size + * @param_spec: Parameter specification + * @ptr: User pointer to validate + * @all_params: Array of all parameter values + * @param_count: Number of parameters + * + * Return: true if user memory is accessible, false otherwise + */ +static bool kapi_validate_user_ptr_with_params(const struct kapi_param_spec *param_spec, + const void __user *ptr, + const s64 *all_params, + int param_count) +{ + size_t actual_size; + + /* NULL is allowed for optional parameters */ + if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL)) + return true; + + /* + * size_param_idx is stored 1-based (0 means "no dynamic sizing"). + * Convert to a real index before looking into all_params. + */ + if (param_spec->size_param_idx > 0 && + param_spec->size_param_idx - 1 < param_count) { + s64 count = all_params[param_spec->size_param_idx - 1]; + size_t unit = param_spec->size_multiplier ?: 1; + + if (count < 0) { + pr_warn_ratelimited("Parameter %s: size determinant is negative (%lld)\n", + param_spec->name, count); + return false; + } + + /* A zero-length access never dereferences the pointer */ + if (count == 0) + return true; + + if (count > SIZE_MAX / unit) { + pr_warn_ratelimited("Parameter %s: size calculation overflow\n", + param_spec->name); + return false; + } + + actual_size = (size_t)count * unit; + } else { + actual_size = param_spec->size; + } + + return kapi_validate_user_ptr(ptr, actual_size); +} + +/** + * kapi_validate_path - Validate that a pathname is accessible and within limits + * @path: User pointer to pathname + * @param_spec: Parameter specification + * + * Return: true if path is valid, false otherwise + */ +static bool kapi_validate_path(const char __user *path, + const struct kapi_param_spec *param_spec) +{ + size_t len; + + /* NULL is allowed for optional parameters */ + if (!path && (param_spec->flags & KAPI_PARAM_OPTIONAL)) + return true; + + if (!path) { + pr_warn_ratelimited("Parameter %s: NULL path not allowed\n", param_spec->name); + return false; + } + + if (!access_ok(path, 1)) { + pr_warn_ratelimited("Parameter %s: path pointer %p not accessible\n", + param_spec->name, path); + return false; + } + + /* + * Use strnlen_user to check the path length and accessibility. + * Note: strnlen_user() is subject to TOCTOU -- the measured length + * may change if another thread modifies the user memory. This is + * acceptable since the kernel re-copies and re-validates the path + * later in the syscall path. This check is best-effort. + */ + len = strnlen_user(path, PATH_MAX + 1); + if (len == 0) { + pr_warn_ratelimited("Parameter %s: invalid path pointer %p\n", + param_spec->name, path); + return false; + } + + if (len > PATH_MAX) { + pr_warn_ratelimited("Parameter %s: path too long (exceeds PATH_MAX)\n", + param_spec->name); + return false; + } + + return true; +} + +/** + * kapi_validate_user_string - Validate a userspace null-terminated string + * @str: User pointer to string + * @param_spec: Parameter specification containing length constraints + * + * Validates that the userspace string pointer is accessible and that the + * string length (excluding null terminator) is within the range specified + * by min_value and max_value in the parameter specification. + * + * Return: true if string is valid, false otherwise + */ +static bool kapi_validate_user_string(const char __user *str, + const struct kapi_param_spec *param_spec) +{ + size_t len; + size_t max_check_len; + + /* NULL is allowed for optional parameters */ + if (!str && (param_spec->flags & KAPI_PARAM_OPTIONAL)) + return true; + + if (!str) { + pr_warn_ratelimited("Parameter %s: NULL string not allowed\n", param_spec->name); + return false; + } + + if (!access_ok(str, 1)) { + pr_warn_ratelimited("Parameter %s: string pointer %p not accessible\n", + param_spec->name, str); + return false; + } + + /* + * Use strnlen_user to check the string length and validate accessibility. + * Check up to max_value + 1 to detect strings that are too long. + * If max_value is 0 or unset, use PATH_MAX as a reasonable default. + * + * Note: strnlen_user() is subject to TOCTOU -- see comment in + * kapi_validate_path() above. This check is best-effort. + */ + max_check_len = param_spec->max_value > 0 ? + (size_t)param_spec->max_value + 1 : PATH_MAX + 1; + len = strnlen_user(str, max_check_len); + + if (len == 0) { + pr_warn_ratelimited("Parameter %s: invalid string pointer %p\n", + param_spec->name, str); + return false; + } + + /* + * strnlen_user returns the length including the null terminator. + * Convert to string length (excluding terminator) for range check. + */ + len--; + + if (param_spec->min_value > 0 && len < (size_t)param_spec->min_value) { + pr_warn_ratelimited("Parameter %s: string too short (%zu < %lld)\n", + param_spec->name, len, param_spec->min_value); + return false; + } + + if (param_spec->max_value > 0 && len > (size_t)param_spec->max_value) { + pr_warn_ratelimited("Parameter %s: string too long (%zu > %lld)\n", + param_spec->name, len, param_spec->max_value); + return false; + } + + return true; +} + +/** + * kapi_validate_user_ptr_constraint - Validate a userspace pointer with size + * @ptr: User pointer to validate + * @param_spec: Parameter specification containing size + * + * Validates that the userspace pointer is accessible and that the memory + * region of the specified size can be accessed. The size is taken from + * the param_spec->size field. + * + * Return: true if pointer is valid, false otherwise + */ +static bool kapi_validate_user_ptr_constraint(const void __user *ptr, + const struct kapi_param_spec *param_spec) +{ + /* NULL is allowed for optional parameters */ + if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL)) + return true; + + if (!ptr) { + pr_warn_ratelimited("Parameter %s: NULL pointer not allowed\n", param_spec->name); + return false; + } + + if (param_spec->size == 0) { + pr_warn_ratelimited("Parameter %s: size not specified for user pointer validation\n", + param_spec->name); + return false; + } + + if (!access_ok(ptr, param_spec->size)) { + pr_warn_ratelimited("Parameter %s: user pointer %p not accessible for %zu bytes\n", + param_spec->name, ptr, param_spec->size); + return false; + } + + return true; +} + +/* + * check_user_ptr is false once the pointer was validated against its dynamic + * size; the fixed-size check would reject NULL even when that size is 0. + */ +static bool kapi_validate_param_checks(const struct kapi_param_spec *param_spec, + s64 value, bool check_user_ptr) +{ + int i; + + /* Special handling for file descriptor type */ + if (param_spec->type == KAPI_TYPE_FD && + !(param_spec->flags & KAPI_PARAM_OPTIONAL)) { + if (value < INT_MIN || value > INT_MAX) { + pr_warn_ratelimited("Parameter %s: file descriptor %lld out of int range\n", + param_spec->name, value); + return false; + } + if (!kapi_validate_fd((int)value)) { + pr_warn_ratelimited("Parameter %s: invalid file descriptor %lld\n", + param_spec->name, value); + return false; + } + } + + /* Special handling for user pointer type */ + if (check_user_ptr && param_spec->type == KAPI_TYPE_USER_PTR) { + const void __user *ptr = (const void __user *)(unsigned long)value; + + /* NULL is allowed for optional parameters */ + if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL)) + return true; + + if (!kapi_validate_user_ptr(ptr, param_spec->size)) { + pr_warn_ratelimited("Parameter %s: invalid user pointer %p (size: %zu)\n", + param_spec->name, ptr, param_spec->size); + return false; + } + } + + /* Special handling for path type */ + if (param_spec->type == KAPI_TYPE_PATH) { + const char __user *path = (const char __user *)(unsigned long)value; + + if (!kapi_validate_path(path, param_spec)) + return false; + } + + switch (param_spec->constraint_type) { + case KAPI_CONSTRAINT_NONE: + case KAPI_CONSTRAINT_BUFFER: + return true; + + case KAPI_CONSTRAINT_RANGE: + /* + * If max_value is below min_value, it was likely set from an + * unsigned constant (e.g. SIZE_MAX) that overflowed s64. Treat + * as no upper bound; only check the minimum. + */ + if (param_spec->max_value >= param_spec->min_value) { + if (value < param_spec->min_value || + value > param_spec->max_value) { + pr_warn_ratelimited("Parameter %s value %lld out of range [%lld, %lld]\n", + param_spec->name, value, + param_spec->min_value, + param_spec->max_value); + return false; + } + } else { + if (value < param_spec->min_value) { + pr_warn_ratelimited("Parameter %s value %lld below minimum %lld\n", + param_spec->name, value, + param_spec->min_value); + return false; + } + } + return true; + + case KAPI_CONSTRAINT_MASK: + if (value & ~param_spec->valid_mask) { + pr_warn_ratelimited("Parameter %s value 0x%llx contains invalid bits (valid mask: 0x%llx)\n", + param_spec->name, value, param_spec->valid_mask); + return false; + } + return true; + + case KAPI_CONSTRAINT_ENUM: + if (!param_spec->enum_values || param_spec->enum_count == 0) + return true; + + for (i = 0; i < param_spec->enum_count; i++) { + if (value == param_spec->enum_values[i]) + return true; + } + pr_warn_ratelimited("Parameter %s value %lld not in valid enumeration\n", + param_spec->name, value); + return false; + + case KAPI_CONSTRAINT_ALIGNMENT: + if (param_spec->alignment == 0) { + pr_warn_ratelimited("Parameter %s: alignment constraint specified but alignment is 0\n", + param_spec->name); + return false; + } + if (param_spec->alignment & (param_spec->alignment - 1)) { + pr_warn_ratelimited("Parameter %s: alignment %zu is not a power of two\n", + param_spec->name, param_spec->alignment); + return false; + } + if (value & (param_spec->alignment - 1)) { + pr_warn_ratelimited("Parameter %s value 0x%llx not aligned to %zu boundary\n", + param_spec->name, value, param_spec->alignment); + return false; + } + return true; + + case KAPI_CONSTRAINT_POWER_OF_TWO: + if (value == 0 || (value & (value - 1))) { + pr_warn_ratelimited("Parameter %s value %lld is not a power of two\n", + param_spec->name, value); + return false; + } + return true; + + case KAPI_CONSTRAINT_PAGE_ALIGNED: + if (value & (PAGE_SIZE - 1)) { + pr_warn_ratelimited("Parameter %s value 0x%llx not page-aligned (PAGE_SIZE=%ld)\n", + param_spec->name, value, PAGE_SIZE); + return false; + } + return true; + + case KAPI_CONSTRAINT_NONZERO: + if (value == 0) { + pr_warn_ratelimited("Parameter %s must be non-zero\n", param_spec->name); + return false; + } + return true; + + case KAPI_CONSTRAINT_USER_STRING: + return kapi_validate_user_string((const char __user *)(unsigned long)value, + param_spec); + + case KAPI_CONSTRAINT_USER_PATH: + return kapi_validate_path((const char __user *)(unsigned long)value, param_spec); + + case KAPI_CONSTRAINT_USER_PTR: + return kapi_validate_user_ptr_constraint((const void __user *)(unsigned long)value, + param_spec); + + case KAPI_CONSTRAINT_CUSTOM: + if (param_spec->validate) + return param_spec->validate(value); + return true; + + default: + return true; + } +} + +/** + * kapi_validate_param - Validate a parameter against its specification + * @param_spec: Parameter specification + * @value: Parameter value to validate + * + * Return: true if valid, false otherwise + */ +bool kapi_validate_param(const struct kapi_param_spec *param_spec, s64 value) +{ + return kapi_validate_param_checks(param_spec, value, true); +} +EXPORT_SYMBOL_GPL(kapi_validate_param); + +/** + * kapi_validate_param_with_context - Validate parameter with access to all params + * @param_spec: Parameter specification + * @value: Parameter value to validate + * @all_params: Array of all parameter values + * @param_count: Number of parameters + * + * Return: true if valid, false otherwise + */ +bool kapi_validate_param_with_context(const struct kapi_param_spec *param_spec, + s64 value, const s64 *all_params, int param_count) +{ + /* Special handling for user pointer type with dynamic sizing */ + if (param_spec->type == KAPI_TYPE_USER_PTR) { + const void __user *ptr = (const void __user *)(unsigned long)value; + + /* NULL is allowed for optional parameters */ + if (!ptr && (param_spec->flags & KAPI_PARAM_OPTIONAL)) + return true; + + if (!kapi_validate_user_ptr_with_params(param_spec, ptr, all_params, param_count)) { + pr_warn_ratelimited("Parameter %s: invalid user pointer %p\n", + param_spec->name, ptr); + return false; + } + return kapi_validate_param_checks(param_spec, value, false); + } + + /* For other types, fall back to regular validation */ + return kapi_validate_param(param_spec, value); +} +EXPORT_SYMBOL_GPL(kapi_validate_param_with_context); + +/** + * kapi_validate_syscall_params - Validate all syscall parameters together + * @spec: API specification + * @params: Array of parameter values + * @param_count: Number of parameters + * + * Return: -EINVAL if any parameter is invalid, 0 if all valid + */ +int kapi_validate_syscall_params(const struct kernel_api_spec *spec, + const s64 *params, int param_count) +{ + int i, ret = 0; + + if (!spec || !params) + return 0; + + if (trace_kapi_syscall_enter_enabled()) { + char pbuf[KAPI_TP_PARAMS_LEN]; + + kapi_trace_format_params(spec, params, param_count, pbuf, sizeof(pbuf)); + trace_kapi_syscall_enter(spec->name, param_count, params, pbuf); + } + + /* Validate that we have the expected number of parameters */ + if (param_count != spec->param_count) { + pr_warn_ratelimited("API %s: parameter count mismatch (expected %u, got %d)\n", + spec->name, spec->param_count, param_count); + ret = -EINVAL; + goto out; + } + + /* Validate each parameter with context */ + for (i = 0; i < spec->param_count && i < KAPI_MAX_PARAMS; i++) { + const struct kapi_param_spec *param_spec = &spec->params[i]; + + if (!kapi_validate_param_with_context(param_spec, params[i], params, param_count)) { + if (strncmp(spec->name, "sys_", 4) == 0) { + /* For syscalls, we can return EINVAL to userspace */ + ret = -EINVAL; + goto out; + } + } + } + +out: + /* + * Emit the exit event on the rejection path too (the wrapper + * short-circuits the handler on a non-zero return), so every + * kapi_syscall_enter has a matching kapi_syscall_exit. + */ + if (ret) + trace_kapi_syscall_exit(spec->name, ret, false); + + return ret; +} +EXPORT_SYMBOL_GPL(kapi_validate_syscall_params); + +/** + * kapi_check_return_success - Check if return value indicates success + * @return_spec: Return specification + * @retval: Return value to check + * + * Return: true if the return value indicates success according to the spec. + */ +bool kapi_check_return_success(const struct kapi_return_spec *return_spec, s64 retval) +{ + u32 i; + + if (!return_spec) + return true; + + switch (return_spec->check_type) { + case KAPI_RETURN_EXACT: + return retval == return_spec->success_value; + + case KAPI_RETURN_RANGE: + return retval >= return_spec->success_min && + retval <= return_spec->success_max; + + case KAPI_RETURN_ERROR_CHECK: + /* Success if NOT in error list */ + if (return_spec->error_values) { + for (i = 0; i < return_spec->error_count; i++) { + if (retval == return_spec->error_values[i]) + return false; + } + } + return true; + + case KAPI_RETURN_FD: + /* File descriptors: >= 0 is success, < 0 is error */ + return retval >= 0; + + case KAPI_RETURN_CUSTOM: + if (return_spec->is_success) + return return_spec->is_success(retval); + fallthrough; + + default: + return true; + } +} +EXPORT_SYMBOL_GPL(kapi_check_return_success); + +/** + * kapi_validate_return_value - Validate that return value matches spec + * @spec: API specification + * @retval: Return value to validate + * + * Return: false if the spec is a KAPI_RETURN_FD check and a successful @retval + * is not a valid file descriptor, true otherwise. + * + * kapi_check_return_success() runs first. A value that does not satisfy it is + * treated as an error and is accepted. An error code that is not listed in the + * spec is only logged with pr_debug(). + */ +bool kapi_validate_return_value(const struct kernel_api_spec *spec, s64 retval) +{ + int i; + bool is_success; + + if (!spec) + return true; /* No spec means we can't validate */ + + /* First check if this is a success return */ + is_success = kapi_check_return_success(&spec->return_spec, retval); + + if (is_success) { + /* Special validation for file descriptor returns */ + if (spec->return_spec.check_type == KAPI_RETURN_FD) { + if (retval > INT_MAX || !kapi_validate_fd((int)retval)) { + pr_warn_ratelimited("API %s returned invalid file descriptor %lld\n", + spec->name, retval); + return false; + } + } + return true; + } + + if (spec->error_count == 0) { + pr_debug("API %s returned unspecified error %lld\n", + spec->name, retval); + return true; + } + + for (i = 0; i < spec->error_count && i < KAPI_MAX_ERRORS; i++) { + if (retval == spec->errors[i].error_code) + return true; + } + + /* + * Error not in spec - log at debug level since filesystem-specific and + * device-specific error codes may not be exhaustively listed. + */ + pr_debug("API %s returned error code %lld not listed in spec\n", + spec->name, retval); + + return true; +} +EXPORT_SYMBOL_GPL(kapi_validate_return_value); + +/** + * kapi_validate_syscall_return - Validate syscall return value + * @spec: API specification + * @retval: Return value + * + * Return: always 0. A return value that does not match the spec is only + * logged and the syscall result is left unchanged. + */ +int kapi_validate_syscall_return(const struct kernel_api_spec *spec, s64 retval) +{ + bool valid = true; + + if (!spec) + return 0; + + /* Validate against the return spec when one was defined */ + if (spec->return_magic == KAPI_MAGIC_RETURN) + valid = kapi_validate_return_value(spec, retval); + + trace_kapi_syscall_exit(spec->name, retval, valid); + + return 0; +} +EXPORT_SYMBOL_GPL(kapi_validate_syscall_return); + +/** + * kapi_check_context - Check if current context matches API requirements + * @spec: API specification to check against + */ +void kapi_check_context(const struct kernel_api_spec *spec) +{ + bool valid = false; + u32 ctx; + + if (!spec) + return; + + ctx = spec->context_flags; + + if (!ctx) + return; + + /* Check if we're in an allowed context */ + if ((ctx & KAPI_CTX_PROCESS) && !in_interrupt()) + valid = true; + + if ((ctx & KAPI_CTX_SOFTIRQ) && in_softirq()) + valid = true; + + if ((ctx & KAPI_CTX_HARDIRQ) && in_hardirq()) + valid = true; + + if ((ctx & KAPI_CTX_NMI) && in_nmi()) + valid = true; + + if (!valid) + WARN_ONCE(1, "API %s called from invalid context\n", spec->name); + + /* Check specific requirements */ + if ((ctx & KAPI_CTX_ATOMIC) && preemptible()) + WARN_ONCE(1, "API %s requires atomic context\n", spec->name); + + if ((ctx & KAPI_CTX_SLEEPABLE) && !preemptible()) + WARN_ONCE(1, "API %s requires sleepable context\n", spec->name); +} +EXPORT_SYMBOL_GPL(kapi_check_context); + +#endif /* CONFIG_KAPI_RUNTIME_CHECKS */ |
