Checking code is added to provide the following additional ctl_table.flags checks: 1) No unknown flag is allowed. 2) Minimum of a range cannot be larger than the maximum value. 3) The signed and unsigned flags are mutually exclusive. 4) The proc_handler should be consistent with the signed or unsigned flags. Two new flags are added to indicate if the min/max values are signed or unsigned - CTL_FLAGS_SIGNED_RANGE & CTL_FLAGS_UNSIGNED_RANGE. These 2 flags can be optionally enabled for range checking purpose. But either one of them must be set with CTL_FLAGS_CLAMP_RANGE. Signed-off-by: Waiman Long <longman@xxxxxxxxxx> --- fs/proc/proc_sysctl.c | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/sysctl.h | 16 +++++++++++-- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 493c975..2863ea1 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -1092,6 +1092,66 @@ static int sysctl_check_table_array(const char *path, struct ctl_table *table) return err; } +static int sysctl_check_flags(const char *path, struct ctl_table *table) +{ + int err = 0; + uint16_t sign_flags = CTL_FLAGS_SIGNED_RANGE|CTL_FLAGS_UNSIGNED_RANGE; + + if ((table->flags & ~CTL_TABLE_FLAGS_ALL) || + ((table->flags & sign_flags) == sign_flags)) + err = sysctl_err(path, table, "invalid flags"); + + if (table->flags & (CTL_FLAGS_CLAMP_RANGE | sign_flags)) { + int range_err = 0; + bool is_int = (table->maxlen == sizeof(int)); + + if (!is_int && (table->maxlen != sizeof(long))) { + range_err++; + } else if (!table->extra1 || !table->extra2) { + /* No min > max checking needed */ + } else if (table->flags & CTL_FLAGS_UNSIGNED_RANGE) { + unsigned long min, max; + + min = is_int ? *(unsigned int *)table->extra1 + : *(unsigned long *)table->extra1; + max = is_int ? *(unsigned int *)table->extra2 + : *(unsigned long *)table->extra2; + range_err += (min > max); + } else if (table->flags & CTL_FLAGS_SIGNED_RANGE) { + + long min, max; + + min = is_int ? *(int *)table->extra1 + : *(long *)table->extra1; + max = is_int ? *(int *)table->extra2 + : *(long *)table->extra2; + range_err += (min > max); + } else { + /* + * Either CTL_FLAGS_UNSIGNED_RANGE or + * CTL_FLAGS_SIGNED_RANGE should be set. + */ + range_err++; + } + + /* + * proc_handler and flag consistency check. + */ + if (((table->proc_handler == proc_douintvec_minmax) || + (table->proc_handler == proc_doulongvec_minmax)) && + !(table->flags & CTL_FLAGS_UNSIGNED_RANGE)) + range_err++; + + if ((table->proc_handler == proc_dointvec_minmax) && + !(table->flags & CTL_FLAGS_SIGNED_RANGE)) + range_err++; + + if (range_err) + err |= sysctl_err(path, table, "Invalid range"); + } + return err; +} + static int sysctl_check_table(const char *path, struct ctl_table *table) { int err = 0; @@ -1111,6 +1171,8 @@ static int sysctl_check_table(const char *path, struct ctl_table *table) (table->proc_handler == proc_doulongvec_ms_jiffies_minmax)) { if (!table->data) err |= sysctl_err(path, table, "No data"); + if (table->flags) + err |= sysctl_check_flags(path, table); if (!table->maxlen) err |= sysctl_err(path, table, "No maxlen"); else diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index e446e1f..088f032 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -134,14 +134,26 @@ struct ctl_table * the input value. No lower bound or upper bound checking will be * done if the corresponding minimum or maximum value isn't provided. * + * @CTL_FLAGS_SIGNED_RANGE: Set to indicate that the extra1 and extra2 + * fields are pointers to minimum and maximum signed values of + * an allowable range. + * + * @CTL_FLAGS_UNSIGNED_RANGE: Set to indicate that the extra1 and extra2 + * fields are pointers to minimum and maximum unsigned values of + * an allowable range. + * * At most 16 different flags are allowed. */ enum ctl_table_flags { CTL_FLAGS_CLAMP_RANGE = BIT(0), - __CTL_FLAGS_MAX = BIT(1), + CTL_FLAGS_SIGNED_RANGE = BIT(1), + CTL_FLAGS_UNSIGNED_RANGE = BIT(2), + __CTL_FLAGS_MAX = BIT(3), }; -#define CTL_TABLE_FLAGS_ALL (__CTL_FLAGS_MAX - 1) +#define CTL_TABLE_FLAGS_ALL (__CTL_FLAGS_MAX - 1) +#define CTL_FLAGS_CLAMP_RANGE_SIGNED (CTL_FLAGS_CLAMP_RANGE|CTL_FLAGS_SIGNED_RANGE) +#define CTL_FLAGS_CLAMP_RANGE_UNSIGNED (CTL_FLAGS_CLAMP_RANGE|CTL_FLAGS_UNSIGNED_RANGE) struct ctl_node { struct rb_node node; -- 1.8.3.1