diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-12 18:32:48 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-12 18:32:48 +0100 |
| commit | 47a3cb7b6d8fbc749a4073b90826fc26241bbc63 (patch) | |
| tree | 8725287bb56a04e4c3a0976ea7e2cd0dbf5d8ac1 /kernel/api/kernel_api_spec.c | |
| parent | 81d0b805a4aa83b37dfdbd3fa3dc7a7c7c72b018 (diff) | |
| parent | ef777ef05ebcc5ea5393b4bd5af6fc3ad1b3aec9 (diff) | |
| download | linux-next-47a3cb7b6d8fbc749a4073b90826fc26241bbc63.tar.gz linux-next-47a3cb7b6d8fbc749a4073b90826fc26241bbc63.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/api/kernel_api_spec.c')
| -rw-r--r-- | kernel/api/kernel_api_spec.c | 1470 |
1 files changed, 1470 insertions, 0 deletions
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 */ |
