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 <long...@redhat.com>
---
 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

--
To unsubscribe from this list: send the line "unsubscribe linux-doc" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to