[PATCH v5 2/9] proc/sysctl: Provide additional ctl_table.flags checks
From: Waiman Long
Date: Fri Mar 16 2018 - 14:14:57 EST
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