Invalid speed settings by the user are currently acknowledged as correct but not stored. Instead the return of the store operation of the file "gw_bandwidth" should indicate that the given value is not acceptable.
Signed-off-by: Sven Eckelmann sven@narfation.org --- v3: * no change v2: * rebased on current master * add missing header linux/errno.h
net/batman-adv/gateway_common.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/batman-adv/gateway_common.c b/net/batman-adv/gateway_common.c index c50931c..6b930a6 100644 --- a/net/batman-adv/gateway_common.c +++ b/net/batman-adv/gateway_common.c @@ -19,6 +19,7 @@ #include "main.h"
#include <linux/atomic.h> +#include <linux/errno.h> #include <linux/byteorder/generic.h> #include <linux/kernel.h> #include <linux/netdevice.h> @@ -160,7 +161,7 @@ ssize_t batadv_gw_bandwidth_set(struct net_device *net_dev, char *buff,
ret = batadv_parse_gw_bandwidth(net_dev, buff, &down_new, &up_new); if (!ret) - goto end; + return -EINVAL;
if (!down_new) down_new = 1; @@ -184,7 +185,6 @@ ssize_t batadv_gw_bandwidth_set(struct net_device *net_dev, char *buff, atomic_set(&bat_priv->gw.bandwidth_up, up_new); batadv_gw_tvlv_container_update(bat_priv);
-end: return count; }
Signed-off-by: Sven Eckelmann sven@narfation.org --- v3: * no change v2: * rebased on current master * add missing header linux/errno.h
compat-include/linux/kernel.h | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/compat-include/linux/kernel.h b/compat-include/linux/kernel.h index 2015f7f..663f9e9 100644 --- a/compat-include/linux/kernel.h +++ b/compat-include/linux/kernel.h @@ -36,6 +36,18 @@ _r = -ERANGE;\ _r;\ }) + +#define kstrtou64(cp, base, v)\ +({\ + unsigned long long _v;\ + int _r;\ + _r = strict_strtoull(cp, base, &_v);\ + *(v) = (uint64_t)_v;\ + if ((unsigned long long)*(v) != _v)\ + _r = -ERANGE;\ + _r;\ +}) + #define kstrtoul strict_strtoul #define kstrtol strict_strtol
Signed-off-by: Sven Eckelmann sven@narfation.org --- v3: * no change v2: * rebased on current master * add missing header linux/errno.h
compat-include/linux/kernel.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
diff --git a/compat-include/linux/kernel.h b/compat-include/linux/kernel.h index 663f9e9..c39cbe8 100644 --- a/compat-include/linux/kernel.h +++ b/compat-include/linux/kernel.h @@ -53,4 +53,21 @@
#endif /* < KERNEL_VERSION(2, 6, 39) */
+#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 14, 0) + +#define U8_MAX ((u8)~0U) +#define S8_MAX ((s8)(U8_MAX >> 1)) +#define S8_MIN ((s8)(-S8_MAX - 1)) +#define U16_MAX ((u16)~0U) +#define S16_MAX ((s16)(U16_MAX >> 1)) +#define S16_MIN ((s16)(-S16_MAX - 1)) +#define U32_MAX ((u32)~0U) +#define S32_MAX ((s32)(U32_MAX >> 1)) +#define S32_MIN ((s32)(-S32_MAX - 1)) +#define U64_MAX ((u64)~0ULL) +#define S64_MAX ((s64)(U64_MAX >> 1)) +#define S64_MIN ((s64)(-S64_MAX - 1)) + +#endif /* < KERNEL_VERSION(3, 14, 0) */ + #endif /* _NET_BATMAN_ADV_COMPAT_LINUX_KERNEL_H_ */
The TVLV for the gw_bandwidth stores everything as u32. But the gw_bandwidth reads the signed long which limits the maximum value to (2 ** 31 - 1) on systems with 4 byte long. Also the input value is always converted from either Mibit/s or Kibit/s to 100Kibit/s. This reduces the values even further when the user sets it via the default unit Kibit/s. It may even cause an integer overflow and end up with a value the user never intended.
Instead read the values as u64, check for possible overflows, do the unit adjustments and then reduce the size to u32.
Signed-off-by: Sven Eckelmann sven@narfation.org --- v3: * replaced do_div with faster div_u64 v2: * rebased on current master * add missing header linux/errno.h
net/batman-adv/gateway_common.c | 49 +++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-)
diff --git a/net/batman-adv/gateway_common.c b/net/batman-adv/gateway_common.c index 6b930a6..0cb5e6b 100644 --- a/net/batman-adv/gateway_common.c +++ b/net/batman-adv/gateway_common.c @@ -22,6 +22,7 @@ #include <linux/errno.h> #include <linux/byteorder/generic.h> #include <linux/kernel.h> +#include <linux/math64.h> #include <linux/netdevice.h> #include <linux/stddef.h> #include <linux/string.h> @@ -44,7 +45,7 @@ static bool batadv_parse_gw_bandwidth(struct net_device *net_dev, char *buff, { enum batadv_bandwidth_units bw_unit_type = BATADV_BW_UNIT_KBIT; char *slash_ptr, *tmp_ptr; - long ldown, lup; + u64 ldown, lup; int ret;
slash_ptr = strchr(buff, '/'); @@ -62,7 +63,7 @@ static bool batadv_parse_gw_bandwidth(struct net_device *net_dev, char *buff, *tmp_ptr = '\0'; }
- ret = kstrtol(buff, 10, &ldown); + ret = kstrtou64(buff, 10, &ldown); if (ret) { batadv_err(net_dev, "Download speed of gateway mode invalid: %s\n", @@ -72,14 +73,31 @@ static bool batadv_parse_gw_bandwidth(struct net_device *net_dev, char *buff,
switch (bw_unit_type) { case BATADV_BW_UNIT_MBIT: - *down = ldown * 10; + /* prevent overflow */ + if (U64_MAX / 10 < ldown) { + batadv_err(net_dev, + "Download speed of gateway mode too large: %s\n", + buff); + return false; + } + + ldown *= 10; break; case BATADV_BW_UNIT_KBIT: default: - *down = ldown / 100; + ldown = div_u64(ldown, 100); break; }
+ if (U32_MAX < ldown) { + batadv_err(net_dev, + "Download speed of gateway mode too large: %s\n", + buff); + return false; + } + + *down = ldown; + /* we also got some upload info */ if (slash_ptr) { bw_unit_type = BATADV_BW_UNIT_KBIT; @@ -95,7 +113,7 @@ static bool batadv_parse_gw_bandwidth(struct net_device *net_dev, char *buff, *tmp_ptr = '\0'; }
- ret = kstrtol(slash_ptr + 1, 10, &lup); + ret = kstrtou64(slash_ptr + 1, 10, &lup); if (ret) { batadv_err(net_dev, "Upload speed of gateway mode invalid: %s\n", @@ -105,13 +123,30 @@ static bool batadv_parse_gw_bandwidth(struct net_device *net_dev, char *buff,
switch (bw_unit_type) { case BATADV_BW_UNIT_MBIT: - *up = lup * 10; + /* prevent overflow */ + if (U64_MAX / 10 < lup) { + batadv_err(net_dev, + "Upload speed of gateway mode too large: %s\n", + slash_ptr + 1); + return false; + } + + lup *= 10; break; case BATADV_BW_UNIT_KBIT: default: - *up = lup / 100; + lup = div_u64(lup, 100); break; } + + if (U32_MAX < lup) { + batadv_err(net_dev, + "Upload speed of gateway mode too large: %s\n", + slash_ptr + 1); + return false; + } + + *up = lup; }
return true;
b.a.t.m.a.n@lists.open-mesh.org