Add IOCTL API specification support to the kernel API specification framework. This enables detailed documentation and runtime validation of IOCTL interfaces.
Key features: - IOCTL specification structure with command info and parameter details - Registration/unregistration functions for IOCTL specs - Helper macros for defining IOCTL specifications - KAPI_IOCTL_SPEC_DRIVER macro for simplified driver integration - Runtime validation support with KAPI_DEFINE_FOPS wrapper - Validation of IOCTL parameters and return values - Integration with existing kernel API spec infrastructure The validation framework checks: - Parameter constraints (ranges, enums, masks) - User pointer validity - Buffer size constraints - Return value correctness against specification Signed-off-by: Sasha Levin <sas...@kernel.org> --- include/linux/ioctl_api_spec.h | 540 ++++++++++++++++++++++++++++++++ include/linux/kernel_api_spec.h | 2 +- kernel/api/Makefile | 5 +- kernel/api/ioctl_validation.c | 360 +++++++++++++++++++++ kernel/api/kernel_api_spec.c | 90 +++++- 5 files changed, 994 insertions(+), 3 deletions(-) create mode 100644 include/linux/ioctl_api_spec.h create mode 100644 kernel/api/ioctl_validation.c diff --git a/include/linux/ioctl_api_spec.h b/include/linux/ioctl_api_spec.h new file mode 100644 index 0000000000000..ab3337449ad77 --- /dev/null +++ b/include/linux/ioctl_api_spec.h @@ -0,0 +1,540 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ioctl_api_spec.h - IOCTL API specification framework + * + * Extends the kernel API specification framework to support ioctl validation + * and documentation. + */ + +#ifndef _LINUX_IOCTL_API_SPEC_H +#define _LINUX_IOCTL_API_SPEC_H + +#include <linux/kernel_api_spec.h> +#include <linux/ioctl.h> +#include <linux/types.h> + +/* Forward declarations */ +struct file; + +/** + * struct kapi_ioctl_spec - IOCTL-specific API specification + * @api_spec: Base API specification + * @cmd: IOCTL command number + * @cmd_name: Human-readable command name + * @input_size: Size of input structure (0 if none) + * @output_size: Size of output structure (0 if none) + * @file_ops_name: Name of the file_operations structure + */ +struct kapi_ioctl_spec { + struct kernel_api_spec api_spec; + unsigned int cmd; + const char *cmd_name; + size_t input_size; + size_t output_size; + const char *file_ops_name; +}; + +/* Registry functions for IOCTL specifications */ +#ifdef CONFIG_KAPI_SPEC +int kapi_register_ioctl_spec(const struct kapi_ioctl_spec *spec); +void kapi_unregister_ioctl_spec(unsigned int cmd); +const struct kapi_ioctl_spec *kapi_get_ioctl_spec(unsigned int cmd); + +/* IOCTL validation functions */ +#ifdef CONFIG_KAPI_RUNTIME_CHECKS +int kapi_validate_ioctl(struct file *filp, unsigned int cmd, void __user *arg); +int kapi_validate_ioctl_struct(const struct kapi_ioctl_spec *spec, + const void *data, size_t size); +#else +static inline int kapi_validate_ioctl(struct file *filp, unsigned int cmd, + void __user *arg) +{ + return 0; +} +#endif /* CONFIG_KAPI_RUNTIME_CHECKS */ + +#else /* !CONFIG_KAPI_SPEC */ +static inline int kapi_register_ioctl_spec(const struct kapi_ioctl_spec *spec) +{ + return 0; +} +static inline void kapi_unregister_ioctl_spec(unsigned int cmd) {} +static inline const struct kapi_ioctl_spec *kapi_get_ioctl_spec(unsigned int cmd) +{ + return NULL; +} +#endif /* CONFIG_KAPI_SPEC */ + +/* Helper macros for IOCTL specification */ + +/** + * DEFINE_IOCTL_API_SPEC - Start an IOCTL API specification + * @name: Unique identifier for the specification + * @cmd: IOCTL command number + * @cmd_name_str: String name of the command + */ +#define DEFINE_IOCTL_API_SPEC(name, cmd, cmd_name_str) \ +static const struct kapi_ioctl_spec name##_spec = { \ + .cmd = cmd, \ + .cmd_name = cmd_name_str, \ + .api_spec = { \ + .name = #name, + +/** + * KAPI_IOCTL_SIZE - Specify input/output structure sizes + * @in_size: Size of input structure + * @out_size: Size of output structure + */ +#define KAPI_IOCTL_SIZE(in_size, out_size) \ + }, \ + .input_size = in_size, \ + .output_size = out_size, + +/** + * KAPI_IOCTL_FILE_OPS - Specify the file_operations structure name + * @ops_name: Name of the file_operations structure + */ +#define KAPI_IOCTL_FILE_OPS(ops_name) \ + .file_ops_name = #ops_name, + +/** + * Common IOCTL parameter specifications + */ +#define KAPI_IOCTL_PARAM_SIZE \ + KAPI_PARAM(0, "size", "__u32", "Size of the structure") \ + KAPI_PARAM_FLAGS(KAPI_PARAM_IN) \ + .type = KAPI_TYPE_UINT, \ + .constraint_type = KAPI_CONSTRAINT_CUSTOM, \ + .constraints = "Must match sizeof(struct)", \ + KAPI_PARAM_END + +#define KAPI_IOCTL_PARAM_FLAGS \ + KAPI_PARAM(1, "flags", "__u32", "Feature flags") \ + KAPI_PARAM_FLAGS(KAPI_PARAM_IN) \ + .type = KAPI_TYPE_UINT, \ + .constraint_type = KAPI_CONSTRAINT_MASK, \ + .valid_mask = 0, /* 0 means no flags currently */ \ + KAPI_PARAM_END + +/** + * KAPI_IOCTL_PARAM_USER_BUF - User buffer parameter + * @idx: Parameter index + * @name: Parameter name + * @desc: Parameter description + * @len_idx: Index of the length parameter + */ +#define KAPI_IOCTL_PARAM_USER_BUF(idx, name, desc, len_idx) \ + KAPI_PARAM(idx, name, "__aligned_u64", desc) \ + KAPI_PARAM_FLAGS(KAPI_PARAM_IN | KAPI_PARAM_USER_PTR) \ + .type = KAPI_TYPE_USER_PTR, \ + .size_param_idx = len_idx, \ + KAPI_PARAM_END + +/** + * KAPI_IOCTL_PARAM_USER_OUT_BUF - User output buffer parameter + * @idx: Parameter index + * @name: Parameter name + * @desc: Parameter description + * @len_idx: Index of the length parameter + */ +#define KAPI_IOCTL_PARAM_USER_OUT_BUF(idx, name, desc, len_idx) \ + KAPI_PARAM(idx, name, "__aligned_u64", desc) \ + KAPI_PARAM_FLAGS(KAPI_PARAM_OUT | KAPI_PARAM_USER_PTR) \ + .type = KAPI_TYPE_USER_PTR, \ + .size_param_idx = len_idx, \ + KAPI_PARAM_END + +/** + * KAPI_IOCTL_PARAM_LEN - Buffer length parameter + * @idx: Parameter index + * @name: Parameter name + * @desc: Parameter description + * @max_size: Maximum allowed size + */ +#define KAPI_IOCTL_PARAM_LEN(idx, name, desc, max_size) \ + KAPI_PARAM(idx, name, "__u32", desc) \ + KAPI_PARAM_FLAGS(KAPI_PARAM_INOUT) \ + .type = KAPI_TYPE_UINT, \ + .constraint_type = KAPI_CONSTRAINT_RANGE, \ + .min_value = 0, \ + .max_value = max_size, \ + KAPI_PARAM_END + +/* End the IOCTL specification */ +#define KAPI_IOCTL_END_SPEC \ +}; \ + \ +static int __init name##_spec_init(void) \ +{ \ + return kapi_register_ioctl_spec(&name##_spec); \ +} \ + \ +static void __exit name##_spec_exit(void) \ +{ \ + kapi_unregister_ioctl_spec(name##_spec.cmd); \ +} \ + \ +module_init(name##_spec_init); \ +module_exit(name##_spec_exit); + +/* Inline IOCTL specification support */ + +/* Forward declaration */ +struct fwctl_ucmd; + +/** + * struct kapi_ioctl_handler - IOCTL handler with inline specification + * @spec: IOCTL specification + * @handler: Original IOCTL handler function + */ +struct kapi_ioctl_handler { + struct kapi_ioctl_spec spec; + int (*handler)(struct fwctl_ucmd *ucmd); +}; + +/** + * DEFINE_IOCTL_HANDLER - Define an IOCTL handler with inline specification + * @name: Handler name + * @cmd: IOCTL command number + * @handler_func: Handler function + * @struct_type: Structure type for this IOCTL + * @last_field: Last field in the structure + */ +#define DEFINE_IOCTL_HANDLER(name, cmd, handler_func, struct_type, last_field) \ +static const struct kapi_ioctl_handler name = { \ + .spec = { \ + .cmd = cmd, \ + .cmd_name = #cmd, \ + .input_size = sizeof(struct_type), \ + .output_size = sizeof(struct_type), \ + .api_spec = { \ + .name = #name, + +#define KAPI_IOCTL_HANDLER_END \ + }, \ + }, \ + .handler = handler_func, \ +} + +/** + * kapi_ioctl_wrapper - Wrapper function for transparent IOCTL validation + * @filp: File pointer + * @cmd: IOCTL command + * @arg: User argument + * @real_ioctl: The real ioctl handler + * + * This wrapper performs validation before and after the actual IOCTL call + */ +static inline long kapi_ioctl_wrapper(struct file *filp, unsigned int cmd, + unsigned long arg, + long (*real_ioctl)(struct file *, unsigned int, unsigned long)) +{ + long ret; + +#ifdef CONFIG_KAPI_RUNTIME_CHECKS + /* Pre-validation */ + ret = kapi_validate_ioctl(filp, cmd, (void __user *)arg); + if (ret) + return ret; +#endif + + /* Call the real IOCTL handler */ + ret = real_ioctl(filp, cmd, arg); + +#ifdef CONFIG_KAPI_RUNTIME_CHECKS + /* Post-validation could be added here if needed */ + /* For example, validating output parameters */ +#endif + + return ret; +} + +/** + * KAPI_IOCTL_OPS - Define file_operations with transparent validation + * @name: Name of the file_operations structure + * @real_ioctl: The real ioctl handler function + * @... : Other file operation handlers + */ +#define KAPI_IOCTL_OPS(name, real_ioctl, ...) \ +static long name##_validated_ioctl(struct file *filp, unsigned int cmd, \ + unsigned long arg) \ +{ \ + return kapi_ioctl_wrapper(filp, cmd, arg, real_ioctl); \ +} \ + \ +static const struct file_operations name = { \ + .unlocked_ioctl = name##_validated_ioctl, \ + __VA_ARGS__ \ +} + +/** + * KAPI_IOCTL_OP_ENTRY - Define an IOCTL operation table entry with spec + * @_ioctl: IOCTL command macro + * @_handler: Handler structure (defined with DEFINE_IOCTL_HANDLER) + * @_struct: Structure type + * @_last: Last field name + */ +#define KAPI_IOCTL_OP_ENTRY(_ioctl, _handler, _struct, _last) \ + [_IOC_NR(_ioctl) - FWCTL_CMD_BASE] = { \ + .size = sizeof(_struct) + \ + BUILD_BUG_ON_ZERO(sizeof(union fwctl_ucmd_buffer) < \ + sizeof(_struct)), \ + .min_size = offsetofend(_struct, _last), \ + .ioctl_num = _ioctl, \ + .execute = _handler.handler, \ + } + +/* Helper to register all handlers in a module */ +#define KAPI_REGISTER_IOCTL_HANDLERS(handlers, count) \ +static int __init kapi_ioctl_handlers_init(void) \ +{ \ + int i, ret; \ + for (i = 0; i < count; i++) { \ + ret = kapi_register_ioctl_spec(&handlers[i].spec); \ + if (ret) { \ + while (--i >= 0) \ + kapi_unregister_ioctl_spec(handlers[i].spec.cmd); \ + return ret; \ + } \ + } \ + return 0; \ +} \ + \ +static void __exit kapi_ioctl_handlers_exit(void) \ +{ \ + int i; \ + for (i = 0; i < count; i++) \ + kapi_unregister_ioctl_spec(handlers[i].spec.cmd); \ +} \ + \ +module_init(kapi_ioctl_handlers_init); \ +module_exit(kapi_ioctl_handlers_exit) + +/** + * KAPI_REGISTER_IOCTL_SPECS - Register an array of IOCTL specifications + * @specs: Array of pointers to kapi_ioctl_spec + * @count: Number of specifications + * + * This macro generates init/exit functions to register/unregister + * the IOCTL specifications. The functions return 0 on success or + * negative error code on failure. + * + * Usage: + * static const struct kapi_ioctl_spec *my_ioctl_specs[] = { + * &spec1, &spec2, &spec3, + * }; + * KAPI_REGISTER_IOCTL_SPECS(my_ioctl_specs, ARRAY_SIZE(my_ioctl_specs)) + * + * Then call the generated functions in your module init/exit: + * ret = kapi_register_##name(); + * kapi_unregister_##name(); + */ +#define KAPI_REGISTER_IOCTL_SPECS(name, specs) \ +static int kapi_register_##name(void) \ +{ \ + int i, ret; \ + for (i = 0; i < ARRAY_SIZE(specs); i++) { \ + ret = kapi_register_ioctl_spec(specs[i]); \ + if (ret) { \ + pr_warn("Failed to register IOCTL spec for %s: %d\n", \ + specs[i]->cmd_name, ret); \ + while (--i >= 0) \ + kapi_unregister_ioctl_spec(specs[i]->cmd); \ + return ret; \ + } \ + } \ + pr_info("Registered %zu IOCTL specifications\n", \ + ARRAY_SIZE(specs)); \ + return 0; \ +} \ + \ +static void kapi_unregister_##name(void) \ +{ \ + int i; \ + for (i = 0; i < ARRAY_SIZE(specs); i++) \ + kapi_unregister_ioctl_spec(specs[i]->cmd); \ +} + +/** + * KAPI_DEFINE_IOCTL_SPEC - Define a single IOCTL specification + * @name: Name of the specification variable + * @cmd: IOCTL command number + * @cmd_name: String name of the command + * @in_size: Input structure size + * @out_size: Output structure size + * @fops_name: Name of the file_operations structure + * + * This macro starts the definition of an IOCTL specification. + * It must be followed by the API specification details and + * ended with KAPI_END_IOCTL_SPEC. + * + * Example: + * KAPI_DEFINE_IOCTL_SPEC(my_ioctl_spec, MY_IOCTL, "MY_IOCTL", + * sizeof(struct my_input), sizeof(struct my_output), + * "my_fops") + * KAPI_DESCRIPTION("Description here") + * ... + * KAPI_END_IOCTL_SPEC; + */ +#define KAPI_DEFINE_IOCTL_SPEC(name, cmd, cmd_name_str, in_size, out_size, fops) \ +static const struct kapi_ioctl_spec name = { \ + .cmd = (cmd), \ + .cmd_name = cmd_name_str, \ + .input_size = in_size, \ + .output_size = out_size, \ + .file_ops_name = fops, \ + .api_spec = { \ + .name = #name, + +#define KAPI_END_IOCTL_SPEC \ + }, \ +} + +/** + * KAPI_IOCTL_SPEC_DRIVER - Complete IOCTL specification for a driver + * @driver_name: Name of the driver (used for logging) + * @specs_array: Name of the array containing IOCTL spec pointers + * + * This macro provides everything needed for IOCTL spec registration: + * 1. Generates the specs array declaration + * 2. Creates init/exit functions for registration + * 3. Provides simple function names to call from module init/exit + * + * Usage: + * // Define individual specs + * KAPI_DEFINE_IOCTL_SPEC(spec1, ...) ... KAPI_END_IOCTL_SPEC; + * KAPI_DEFINE_IOCTL_SPEC(spec2, ...) ... KAPI_END_IOCTL_SPEC; + * + * // Create the driver registration (at end of file) + * KAPI_IOCTL_SPEC_DRIVER("my_driver", { + * &spec1, + * &spec2, + * }) + * + * // In module init: ret = kapi_ioctl_specs_init(); + * // In module exit: kapi_ioctl_specs_exit(); + */ +#define KAPI_IOCTL_SPEC_DRIVER(driver_name, ...) \ +static const struct kapi_ioctl_spec *__kapi_ioctl_specs[] = __VA_ARGS__; \ + \ +static int __init kapi_ioctl_specs_init(void) \ +{ \ + int i, ret; \ + for (i = 0; i < ARRAY_SIZE(__kapi_ioctl_specs); i++) { \ + ret = kapi_register_ioctl_spec(__kapi_ioctl_specs[i]); \ + if (ret) { \ + pr_warn("%s: Failed to register %s: %d\n", \ + driver_name, \ + __kapi_ioctl_specs[i]->cmd_name, ret); \ + while (--i >= 0) \ + kapi_unregister_ioctl_spec( \ + __kapi_ioctl_specs[i]->cmd); \ + return ret; \ + } \ + } \ + pr_info("%s: Registered %zu IOCTL specifications\n", \ + driver_name, ARRAY_SIZE(__kapi_ioctl_specs)); \ + return 0; \ +} \ + \ +static void kapi_ioctl_specs_exit(void) \ +{ \ + int i; \ + for (i = 0; i < ARRAY_SIZE(__kapi_ioctl_specs); i++) \ + kapi_unregister_ioctl_spec(__kapi_ioctl_specs[i]->cmd);\ +} + +/* Transparent IOCTL validation wrapper support */ + +#ifdef CONFIG_KAPI_RUNTIME_CHECKS + +/** + * struct kapi_fops_wrapper - Wrapper for file_operations with validation + * @real_fops: Original file_operations + * @wrapped_fops: Modified file_operations with validation wrapper + * @real_ioctl: Original unlocked_ioctl handler + */ +struct kapi_fops_wrapper { + const struct file_operations *real_fops; + const struct file_operations *wrapped_fops; + long (*real_ioctl)(struct file *, unsigned int, unsigned long); +}; + +/* Forward declarations */ +long kapi_ioctl_validation_wrapper(struct file *filp, unsigned int cmd, + unsigned long arg); +void kapi_register_wrapper(struct kapi_fops_wrapper *wrapper); + +/** + * kapi_wrap_file_operations - Wrap file_operations for transparent validation + * @fops: Original file_operations to wrap + * + * This creates a wrapper that intercepts ioctl calls for validation. + * The wrapper is stored in a static variable in the calling module. + */ +#define kapi_wrap_file_operations(fops) \ +({ \ + static struct kapi_fops_wrapper __kapi_wrapper = { \ + .real_fops = &(fops), \ + }; \ + if (__kapi_wrapper.real_fops->unlocked_ioctl) { \ + __kapi_wrapper.wrapped_fops = (fops); \ + __kapi_wrapper.real_ioctl = (fops).unlocked_ioctl; \ + __kapi_wrapper.wrapped_fops.unlocked_ioctl = \ + kapi_ioctl_validation_wrapper; \ + &__kapi_wrapper.wrapped_fops; \ + } else { \ + &(fops); \ + } \ +}) + + +/** + * KAPI_DEFINE_FOPS - Define file_operations with automatic validation + * @name: Name of the file_operations structure + * @... : File operation handlers + * + * Usage: + * KAPI_DEFINE_FOPS(my_fops, + * .owner = THIS_MODULE, + * .open = my_open, + * .unlocked_ioctl = my_ioctl, + * ); + * + * Then in your module init, call: kapi_init_fops_##name() + */ +#define KAPI_DEFINE_FOPS(name, ...) \ +static const struct file_operations __kapi_real_##name = { \ + __VA_ARGS__ \ +}; \ +static struct file_operations __kapi_wrapped_##name; \ +static struct kapi_fops_wrapper __kapi_wrapper_##name; \ +static const struct file_operations *name; \ +static void kapi_init_fops_##name(void) \ +{ \ + if (__kapi_real_##name.unlocked_ioctl) { \ + __kapi_wrapped_##name = __kapi_real_##name; \ + __kapi_wrapper_##name.real_fops = &__kapi_real_##name; \ + __kapi_wrapper_##name.wrapped_fops = &__kapi_wrapped_##name; \ + __kapi_wrapper_##name.real_ioctl = \ + __kapi_real_##name.unlocked_ioctl; \ + __kapi_wrapped_##name.unlocked_ioctl = \ + kapi_ioctl_validation_wrapper; \ + kapi_register_wrapper(&__kapi_wrapper_##name); \ + name = &__kapi_wrapped_##name; \ + } else { \ + name = &__kapi_real_##name; \ + } \ +} + +#else /* !CONFIG_KAPI_RUNTIME_CHECKS */ + +/* When runtime checks are disabled, no wrapping occurs */ +#define kapi_wrap_file_operations(fops) (&(fops)) +#define KAPI_DEFINE_FOPS(name, ...) \ +static const struct file_operations name = { __VA_ARGS__ }; \ +static inline void kapi_init_fops_##name(void) {} + +#endif /* CONFIG_KAPI_RUNTIME_CHECKS */ + +#endif /* _LINUX_IOCTL_API_SPEC_H */ \ No newline at end of file diff --git a/include/linux/kernel_api_spec.h b/include/linux/kernel_api_spec.h index 04df5892bc6d6..9590fe3bb007c 100644 --- a/include/linux/kernel_api_spec.h +++ b/include/linux/kernel_api_spec.h @@ -849,7 +849,7 @@ struct kernel_api_spec { #define KAPI_PARAM_OUT (KAPI_PARAM_OUT) #define KAPI_PARAM_INOUT (KAPI_PARAM_IN | KAPI_PARAM_OUT) #define KAPI_PARAM_OPTIONAL (KAPI_PARAM_OPTIONAL) -#define KAPI_PARAM_USER_PTR (KAPI_PARAM_USER | KAPI_PARAM_PTR) +#define KAPI_PARAM_USER_PTR (KAPI_PARAM_USER) /* Validation and runtime checking */ diff --git a/kernel/api/Makefile b/kernel/api/Makefile index 07b8c007ec156..9d2daf38f0029 100644 --- a/kernel/api/Makefile +++ b/kernel/api/Makefile @@ -6,5 +6,8 @@ # Core API specification framework obj-$(CONFIG_KAPI_SPEC) += kernel_api_spec.o +# IOCTL validation framework +obj-$(CONFIG_KAPI_SPEC) += ioctl_validation.o + # Debugfs interface for kernel API specs -obj-$(CONFIG_KAPI_SPEC_DEBUGFS) += kapi_debugfs.o \ No newline at end of file +obj-$(CONFIG_KAPI_SPEC_DEBUGFS) += kapi_debugfs.o diff --git a/kernel/api/ioctl_validation.c b/kernel/api/ioctl_validation.c new file mode 100644 index 0000000000000..25f6db8cb33eb --- /dev/null +++ b/kernel/api/ioctl_validation.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ioctl_validation.c - Runtime validation for IOCTL API specifications + * + * Provides functions to validate ioctl parameters against their specifications + * at runtime when CONFIG_KAPI_RUNTIME_CHECKS is enabled. + */ + +#include <linux/kernel.h> +#include <linux/ioctl_api_spec.h> +#include <linux/kernel_api_spec.h> +#include <linux/uaccess.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include <linux/container_of.h> +#include <linux/export.h> +#include <uapi/fwctl/fwctl.h> + +#ifdef CONFIG_KAPI_RUNTIME_CHECKS + +/** + * kapi_validate_ioctl - Validate an ioctl call against its specification + * @filp: File pointer + * @cmd: IOCTL command + * @arg: IOCTL argument + * + * Return: 0 if valid, negative errno if validation fails + */ +int kapi_validate_ioctl(struct file *filp, unsigned int cmd, void __user *arg) +{ + const struct kapi_ioctl_spec *spec; + const struct kernel_api_spec *api_spec; + void *data = NULL; + size_t copy_size; + int ret = 0; + int i; + + spec = kapi_get_ioctl_spec(cmd); + if (!spec) + return 0; /* No spec, can't validate */ + + api_spec = &spec->api_spec; + + pr_debug("kapi: validating ioctl %s (0x%x)\n", spec->cmd_name, cmd); + + /* Check if this ioctl requires specific capabilities */ + if (api_spec->param_count > 0) { + for (i = 0; i < api_spec->param_count; i++) { + const struct kapi_param_spec *param = &api_spec->params[i]; + + /* Check for capability requirements in constraints */ + if (param->constraint_type == KAPI_CONSTRAINT_CUSTOM && + param->constraints[0] && strstr(param->constraints, "CAP_")) { + /* Could add capability checks here if needed */ + } + } + } + + /* For ioctls with input/output structures, copy and validate */ + if (spec->input_size > 0 || spec->output_size > 0) { + copy_size = max(spec->input_size, spec->output_size); + + /* Allocate temporary buffer for validation */ + data = kzalloc(copy_size, GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Copy input data from user */ + if (spec->input_size > 0) { + ret = copy_from_user(data, arg, spec->input_size); + if (ret) { + ret = -EFAULT; + goto out; + } + } + + /* Validate structure fields */ + ret = kapi_validate_ioctl_struct(spec, data, copy_size); + if (ret) + goto out; + } + +out: + kfree(data); + return ret; +} +EXPORT_SYMBOL_GPL(kapi_validate_ioctl); + +/** + * struct field_offset - Maps structure fields to their offsets + * @field_idx: Parameter index + * @offset: Offset in structure + * @size: Size of field + */ +struct field_offset { + int field_idx; + size_t offset; + size_t size; +}; + +/* Common ioctl structure layouts */ +static const struct field_offset fwctl_info_offsets[] = { + {0, 0, sizeof(u32)}, /* size */ + {1, 4, sizeof(u32)}, /* flags */ + {2, 8, sizeof(u32)}, /* out_device_type */ + {3, 12, sizeof(u32)}, /* device_data_len */ + {4, 16, sizeof(u64)}, /* out_device_data */ +}; + +static const struct field_offset fwctl_rpc_offsets[] = { + {0, 0, sizeof(u32)}, /* size */ + {1, 4, sizeof(u32)}, /* scope */ + {2, 8, sizeof(u32)}, /* in_len */ + {3, 12, sizeof(u32)}, /* out_len */ + {4, 16, sizeof(u64)}, /* in */ + {5, 24, sizeof(u64)}, /* out */ +}; + +/** + * get_field_offsets - Get field offset information for an ioctl + * @cmd: IOCTL command + * @count: Returns number of fields + * + * Return: Array of field offsets or NULL + */ +static const struct field_offset *get_field_offsets(unsigned int cmd, int *count) +{ + switch (cmd) { + case FWCTL_INFO: + *count = ARRAY_SIZE(fwctl_info_offsets); + return fwctl_info_offsets; + case FWCTL_RPC: + *count = ARRAY_SIZE(fwctl_rpc_offsets); + return fwctl_rpc_offsets; + default: + *count = 0; + return NULL; + } +} + +/** + * extract_field_value - Extract a field value from structure + * @data: Structure data + * @param: Parameter specification + * @offset_info: Field offset information + * + * Return: Field value or 0 on error + */ +static s64 extract_field_value(const void *data, + const struct kapi_param_spec *param, + const struct field_offset *offset_info) +{ + const void *field = data + offset_info->offset; + + switch (param->type) { + case KAPI_TYPE_UINT: + if (offset_info->size == sizeof(u32)) + return *(u32 *)field; + else if (offset_info->size == sizeof(u64)) + return *(u64 *)field; + break; + case KAPI_TYPE_INT: + if (offset_info->size == sizeof(s32)) + return *(s32 *)field; + else if (offset_info->size == sizeof(s64)) + return *(s64 *)field; + break; + case KAPI_TYPE_USER_PTR: + /* User pointers are typically u64 in ioctl structures */ + return (s64)(*(u64 *)field); + default: + break; + } + + return 0; +} + +/** + * kapi_validate_ioctl_struct - Validate an ioctl structure against specification + * @spec: IOCTL specification + * @data: Structure data + * @size: Size of the structure + * + * Return: 0 if valid, negative errno if validation fails + */ +int kapi_validate_ioctl_struct(const struct kapi_ioctl_spec *spec, + const void *data, size_t size) +{ + const struct kernel_api_spec *api_spec = &spec->api_spec; + const struct field_offset *offsets; + int offset_count; + int i, j; + + if (!spec || !data) + return -EINVAL; + + /* Get field offset information for this ioctl */ + offsets = get_field_offsets(spec->cmd, &offset_count); + + /* Validate each parameter in the structure */ + for (i = 0; i < api_spec->param_count && i < KAPI_MAX_PARAMS; i++) { + const struct kapi_param_spec *param = &api_spec->params[i]; + const struct field_offset *offset_info = NULL; + s64 value; + + /* Find offset information for this parameter */ + if (offsets) { + for (j = 0; j < offset_count; j++) { + if (offsets[j].field_idx == i) { + offset_info = &offsets[j]; + break; + } + } + } + + if (!offset_info) { + pr_debug("kapi: no offset info for param %d\n", i); + continue; + } + + /* Extract field value */ + value = extract_field_value(data, param, offset_info); + + /* Special handling for user pointers */ + if (param->type == KAPI_TYPE_USER_PTR) { + /* Check if pointer looks valid (non-kernel address) */ + if (value && (value >= TASK_SIZE)) { + pr_warn("ioctl %s: parameter %s has kernel pointer %llx\n", + spec->cmd_name, param->name, value); + return -EINVAL; + } + + /* For size validation, check against size_param_idx */ + if (param->size_param_idx >= 0 && + param->size_param_idx < offset_count) { + const struct field_offset *size_offset = NULL; + + for (j = 0; j < offset_count; j++) { + if (offsets[j].field_idx == param->size_param_idx) { + size_offset = &offsets[j]; + break; + } + } + + if (size_offset) { + s64 buf_size = extract_field_value(data, + &api_spec->params[param->size_param_idx], + size_offset); + + /* Validate buffer size constraints */ + if (buf_size > 0 && + !kapi_validate_param(&api_spec->params[param->size_param_idx], + buf_size)) { + pr_warn("ioctl %s: buffer size %lld invalid for %s\n", + spec->cmd_name, buf_size, param->name); + return -EINVAL; + } + } + } + } else { + /* Validate using the standard parameter validation */ + if (!kapi_validate_param(param, value)) { + pr_warn("ioctl %s: parameter %s validation failed (value=%lld)\n", + spec->cmd_name, param->name, value); + return -EINVAL; + } + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(kapi_validate_ioctl_struct); + +/* Global registry of wrappers - in real implementation this would be per-module */ +static struct kapi_fops_wrapper *kapi_global_wrapper; + +/** + * kapi_register_wrapper - Register a wrapper (called from macro) + * @wrapper: Wrapper to register + */ +void kapi_register_wrapper(struct kapi_fops_wrapper *wrapper) +{ + /* Simple implementation - just store the last one */ + kapi_global_wrapper = wrapper; +} +EXPORT_SYMBOL_GPL(kapi_register_wrapper); + +/** + * kapi_find_wrapper - Find wrapper for given file_operations + * @fops: File operations structure to check + * + * Return: Wrapper structure or NULL if not wrapped + */ +static struct kapi_fops_wrapper *kapi_find_wrapper(const struct file_operations *fops) +{ + /* Simple implementation - just return the global one if it matches */ + if (kapi_global_wrapper && kapi_global_wrapper->wrapped_fops == fops) + return kapi_global_wrapper; + return NULL; +} + +/** + * kapi_ioctl_validation_wrapper - Wrapper function for transparent validation + * @filp: File pointer + * @cmd: IOCTL command + * @arg: User argument + * + * This function is called instead of the real ioctl handler when validation + * is enabled. It performs pre-validation, calls the real handler, then does + * post-validation. + * + * Return: Result from the real ioctl handler or error + */ +long kapi_ioctl_validation_wrapper(struct file *filp, unsigned int cmd, + unsigned long arg) +{ + struct kapi_fops_wrapper *wrapper; + const struct kapi_ioctl_spec *spec; + long ret; + + wrapper = kapi_find_wrapper(filp->f_op); + if (!wrapper || !wrapper->real_ioctl) + return -EINVAL; + + /* Pre-validation */ + spec = kapi_get_ioctl_spec(cmd); + if (spec) { + ret = kapi_validate_ioctl(filp, cmd, (void __user *)arg); + if (ret) + return ret; + } + + /* Call the real ioctl handler */ + ret = wrapper->real_ioctl(filp, cmd, arg); + + /* Post-validation - check return value against spec */ + if (spec && spec->api_spec.error_count > 0) { + /* Validate that returned error is in the spec */ + if (ret < 0) { + int i; + bool found = false; + for (i = 0; i < spec->api_spec.error_count; i++) { + if (ret == spec->api_spec.errors[i].error_code) { + found = true; + break; + } + } + if (!found) { + pr_warn("IOCTL %s returned unexpected error %ld\n", + spec->cmd_name, ret); + } + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(kapi_ioctl_validation_wrapper); + +#endif /* CONFIG_KAPI_RUNTIME_CHECKS */ diff --git a/kernel/api/kernel_api_spec.c b/kernel/api/kernel_api_spec.c index 29c0c84d87f7c..70e16a49f5dbe 100644 --- a/kernel/api/kernel_api_spec.c +++ b/kernel/api/kernel_api_spec.c @@ -1166,4 +1166,92 @@ static int __init kapi_debugfs_init(void) late_initcall(kapi_debugfs_init); -#endif /* CONFIG_DEBUG_FS */ \ No newline at end of file +#endif /* CONFIG_DEBUG_FS */ + +/* IOCTL specification registry */ +#ifdef CONFIG_KAPI_SPEC + +#include <linux/ioctl_api_spec.h> + +static DEFINE_MUTEX(ioctl_spec_mutex); +static LIST_HEAD(ioctl_specs); + +struct ioctl_spec_entry { + struct list_head list; + const struct kapi_ioctl_spec *spec; +}; + +/** + * kapi_register_ioctl_spec - Register an IOCTL API specification + * @spec: IOCTL specification to register + * + * Return: 0 on success, negative error code on failure + */ +int kapi_register_ioctl_spec(const struct kapi_ioctl_spec *spec) +{ + struct ioctl_spec_entry *entry; + + if (!spec || !spec->cmd_name) + return -EINVAL; + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + return -ENOMEM; + + entry->spec = spec; + + mutex_lock(&ioctl_spec_mutex); + list_add_tail(&entry->list, &ioctl_specs); + mutex_unlock(&ioctl_spec_mutex); + + pr_debug("Registered IOCTL spec: %s (0x%x)\n", spec->cmd_name, spec->cmd); + return 0; +} +EXPORT_SYMBOL_GPL(kapi_register_ioctl_spec); + +/** + * kapi_unregister_ioctl_spec - Unregister an IOCTL API specification + * @cmd: IOCTL command number to unregister + */ +void kapi_unregister_ioctl_spec(unsigned int cmd) +{ + struct ioctl_spec_entry *entry, *tmp; + + mutex_lock(&ioctl_spec_mutex); + list_for_each_entry_safe(entry, tmp, &ioctl_specs, list) { + if (entry->spec->cmd == cmd) { + list_del(&entry->list); + kfree(entry); + pr_debug("Unregistered IOCTL spec for cmd 0x%x\n", cmd); + break; + } + } + mutex_unlock(&ioctl_spec_mutex); +} +EXPORT_SYMBOL_GPL(kapi_unregister_ioctl_spec); + +/** + * kapi_get_ioctl_spec - Retrieve IOCTL specification by command number + * @cmd: IOCTL command number + * + * Return: Pointer to the specification or NULL if not found + */ +const struct kapi_ioctl_spec *kapi_get_ioctl_spec(unsigned int cmd) +{ + struct ioctl_spec_entry *entry; + const struct kapi_ioctl_spec *spec = NULL; + + mutex_lock(&ioctl_spec_mutex); + list_for_each_entry(entry, &ioctl_specs, list) { + if (entry->spec->cmd == cmd) { + spec = entry->spec; + break; + } + } + mutex_unlock(&ioctl_spec_mutex); + + return spec; +} +EXPORT_SYMBOL_GPL(kapi_get_ioctl_spec); + +#endif /* CONFIG_KAPI_SPEC */ -- 2.39.5