Files
tor/src/lib/confmgt/unitparse.c
T
José M. Guisado 42ba3997d6 Check memunit parsing for overflow in confparse
Before, when parsing memunits, if overflow occured it failed silently.
Use nowrap u64 math to detect overflow, compare to INT64_MAX and if
greater tell user and fail accordingly.

15000000.5 TB fails double check as it a greater floating number than
(double)INT64_MAX

8388608.1 TB passes double check because it falls in the same value as
(double)INT64_MAX (which is 2^63), but will fail the int check because
(uint64_t)d, which is 2^63, is strictly greater than 2^63-1 (INT64_MAX).

Fixes #30920
Signed-off-by: José M. Guisado <guigom@riseup.net>
2019-10-28 11:38:41 +01:00

234 lines
6.2 KiB
C

/* Copyright (c) 2001 Matej Pfajfar.
* Copyright (c) 2001-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2019, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
* @file unitparse.c
* @brief Functions for parsing values with units from a configuration file.
**/
#include "orconfig.h"
#include "lib/confmgt/unitparse.h"
#include "lib/log/log.h"
#include "lib/log/util_bug.h"
#include "lib/string/parse_int.h"
#include "lib/string/util_string.h"
#include "lib/intmath/muldiv.h"
#include <string.h>
/** Table to map the names of memory units to the number of bytes they
* contain. */
const struct unit_table_t memory_units[] = {
{ "", 1 },
{ "b", 1<< 0 },
{ "byte", 1<< 0 },
{ "bytes", 1<< 0 },
{ "kb", 1<<10 },
{ "kbyte", 1<<10 },
{ "kbytes", 1<<10 },
{ "kilobyte", 1<<10 },
{ "kilobytes", 1<<10 },
{ "kilobits", 1<<7 },
{ "kilobit", 1<<7 },
{ "kbits", 1<<7 },
{ "kbit", 1<<7 },
{ "m", 1<<20 },
{ "mb", 1<<20 },
{ "mbyte", 1<<20 },
{ "mbytes", 1<<20 },
{ "megabyte", 1<<20 },
{ "megabytes", 1<<20 },
{ "megabits", 1<<17 },
{ "megabit", 1<<17 },
{ "mbits", 1<<17 },
{ "mbit", 1<<17 },
{ "gb", 1<<30 },
{ "gbyte", 1<<30 },
{ "gbytes", 1<<30 },
{ "gigabyte", 1<<30 },
{ "gigabytes", 1<<30 },
{ "gigabits", 1<<27 },
{ "gigabit", 1<<27 },
{ "gbits", 1<<27 },
{ "gbit", 1<<27 },
{ "tb", UINT64_C(1)<<40 },
{ "tbyte", UINT64_C(1)<<40 },
{ "tbytes", UINT64_C(1)<<40 },
{ "terabyte", UINT64_C(1)<<40 },
{ "terabytes", UINT64_C(1)<<40 },
{ "terabits", UINT64_C(1)<<37 },
{ "terabit", UINT64_C(1)<<37 },
{ "tbits", UINT64_C(1)<<37 },
{ "tbit", UINT64_C(1)<<37 },
{ NULL, 0 },
};
/** Table to map the names of time units to the number of seconds they
* contain. */
const struct unit_table_t time_units[] = {
{ "", 1 },
{ "second", 1 },
{ "seconds", 1 },
{ "minute", 60 },
{ "minutes", 60 },
{ "hour", 60*60 },
{ "hours", 60*60 },
{ "day", 24*60*60 },
{ "days", 24*60*60 },
{ "week", 7*24*60*60 },
{ "weeks", 7*24*60*60 },
{ "month", 2629728, }, /* about 30.437 days */
{ "months", 2629728, },
{ NULL, 0 },
};
/** Table to map the names of time units to the number of milliseconds
* they contain. */
const struct unit_table_t time_msec_units[] = {
{ "", 1 },
{ "msec", 1 },
{ "millisecond", 1 },
{ "milliseconds", 1 },
{ "second", 1000 },
{ "seconds", 1000 },
{ "minute", 60*1000 },
{ "minutes", 60*1000 },
{ "hour", 60*60*1000 },
{ "hours", 60*60*1000 },
{ "day", 24*60*60*1000 },
{ "days", 24*60*60*1000 },
{ "week", 7*24*60*60*1000 },
{ "weeks", 7*24*60*60*1000 },
{ NULL, 0 },
};
/** Parse a string <b>val</b> containing a number, zero or more
* spaces, and an optional unit string. If the unit appears in the
* table <b>u</b>, then multiply the number by the unit multiplier.
* On success, set *<b>ok</b> to 1 and return this product.
* Otherwise, set *<b>ok</b> to 0.
* Warns user when overflow or a negative value is detected.
*/
uint64_t
config_parse_units(const char *val, const unit_table_t *u, int *ok)
{
uint64_t v = 0;
double d = 0;
int use_float = 0;
char *cp;
tor_assert(ok);
v = tor_parse_uint64(val, 10, 0, UINT64_MAX, ok, &cp);
if (!*ok || (cp && *cp == '.')) {
d = tor_parse_double(val, 0, (double)UINT64_MAX, ok, &cp);
if (!*ok)
goto done;
use_float = 1;
}
if (BUG(!cp)) {
// cp should always be non-NULL if the parse operation succeeds.
// LCOV_EXCL_START
*ok = 1;
v = use_float ? ((uint64_t)d) : v;
goto done;
// LCOV_EXCL_STOP
}
cp = (char*) eat_whitespace(cp);
for ( ;u->unit;++u) {
if (!strcasecmp(u->unit, cp)) {
if (use_float) {
d = u->multiplier * d;
if (d < 0) {
log_warn(LD_CONFIG, "Negative value arised when parsing %s %s",
val, u->unit);
*ok = 0;
goto done;
}
// Some compilers may warn about casting a double to an unsigned type
// because they don't know if d is >= 0
if (d >= 0 && (d > (double)INT64_MAX || (uint64_t)d > INT64_MAX)) {
log_warn(LD_CONFIG, "Overflow detected parsing %s %s", val, u->unit);
*ok = 0;
goto done;
}
v = (uint64_t) d;
} else {
v = tor_mul_u64_nowrap(v, u->multiplier);
if (v > INT64_MAX) {
log_warn(LD_CONFIG, "Overflow detected parsing %s %s", val, u->unit);
*ok = 0;
goto done;
}
}
*ok = 1;
goto done;
}
}
log_warn(LD_CONFIG, "Unknown unit '%s'.", cp);
*ok = 0;
done:
if (*ok)
return v;
else
return 0;
}
/** Parse a string in the format "number unit", where unit is a unit of
* information (byte, KB, M, etc). On success, set *<b>ok</b> to true
* and return the number of bytes specified. Otherwise, set
* *<b>ok</b> to false and return 0. */
uint64_t
config_parse_memunit(const char *s, int *ok)
{
uint64_t u = config_parse_units(s, memory_units, ok);
return u;
}
/** Parse a string in the format "number unit", where unit is a unit of
* time in milliseconds. On success, set *<b>ok</b> to true and return
* the number of milliseconds in the provided interval. Otherwise, set
* *<b>ok</b> to 0 and return -1. */
int
config_parse_msec_interval(const char *s, int *ok)
{
uint64_t r;
r = config_parse_units(s, time_msec_units, ok);
if (r > INT_MAX) {
log_warn(LD_CONFIG, "Msec interval '%s' is too long", s);
*ok = 0;
return -1;
}
return (int)r;
}
/** Parse a string in the format "number unit", where unit is a unit of time.
* On success, set *<b>ok</b> to true and return the number of seconds in
* the provided interval. Otherwise, set *<b>ok</b> to 0 and return -1.
*/
int
config_parse_interval(const char *s, int *ok)
{
uint64_t r;
r = config_parse_units(s, time_units, ok);
if (r > INT_MAX) {
log_warn(LD_CONFIG, "Interval '%s' is too long", s);
*ok = 0;
return -1;
}
return (int)r;
}