From: Thierry FOURNIER Date: Tue, 7 Jul 2015 23:10:21 +0000 (+0200) Subject: MEDIUM: sample: switch to saturated arithmetic X-Git-Tag: v1.6-dev3~5 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=00c005c72637b68758d891599017024ded13879d;p=thirdparty%2Fhaproxy.git MEDIUM: sample: switch to saturated arithmetic This patch check calculus for overflow and returns capped values. This permits to protect against integer overflow in certain operations involving ratios, percentages, limits or anything. That can sometimes be critically important with some operations (eg: content-length < X). --- diff --git a/doc/configuration.txt b/doc/configuration.txt index 2782c2046c..1ae983476b 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -11290,8 +11290,8 @@ mod() mul() Multiplies the input value of type signed integer by , and returns - the product as an signed integer. In case of overflow, the higher bits are - lost, leading to seemingly strange values. + the product as an signed integer. In case of overflow, the largest possible + value for the sign is returned so that the operation doesn't wrap around. neg Takes the input value of type signed integer, computes the opposite value, diff --git a/src/sample.c b/src/sample.c index 15c004ff30..41e754011a 100644 --- a/src/sample.c +++ b/src/sample.c @@ -2053,12 +2053,38 @@ static int sample_conv_binary_xor(const struct arg *arg_p, struct sample *smp, v return 1; } +static inline long long int arith_add(long long int a, long long int b) +{ + /* Prevent overflow and makes capped calculus. + * We must ensure that the check calculus doesn't + * exceed the signed 64 bits limits. + * + * +----------+----------+ + * | a<0 | a>=0 | + * +------+----------+----------+ + * | b<0 | MIN-a>b | no check | + * +------+----------+----------+ + * | b>=0 | no check | MAX-a= 0) { + /* signs are differents. */ + if (a < 0) { + if (LLONG_MIN - a > b) + return LLONG_MIN; + } + if (LLONG_MAX - a < b) + return LLONG_MAX; + } + return a + b; +} + /* Takes a SINT on input, applies an arithmetic "add" with the UINT in arg_p, * and returns the SINT result. */ static int sample_conv_arith_add(const struct arg *arg_p, struct sample *smp, void *private) { - smp->data.sint += arg_p->data.sint; + smp->data.sint = arith_add(smp->data.sint, arg_p->data.sint); return 1; } @@ -2068,7 +2094,21 @@ static int sample_conv_arith_add(const struct arg *arg_p, struct sample *smp, vo static int sample_conv_arith_sub(const struct arg *arg_p, struct sample *smp, void *private) { - smp->data.sint -= arg_p->data.sint; + /* We cannot represent -LLONG_MIN because abs(LLONG_MIN) is greater + * than abs(LLONG_MAX). So, the following code use LLONG_MAX in place + * of -LLONG_MIN and correct the result. + */ + if (arg_p->data.sint == LLONG_MIN) { + smp->data.sint = arith_add(smp->data.sint, LLONG_MAX); + if (smp->data.sint < LLONG_MAX) + smp->data.sint++; + return 1; + } + + /* standard substraction: we use the "add" function and negate + * the second operand. + */ + smp->data.sint = arith_add(smp->data.sint, -arg_p->data.sint); return 1; } @@ -2078,7 +2118,35 @@ static int sample_conv_arith_sub(const struct arg *arg_p, static int sample_conv_arith_mul(const struct arg *arg_p, struct sample *smp, void *private) { - smp->data.sint *= arg_p->data.sint; + long long int c; + + /* prevent divide by 0 during the check */ + if (!smp->data.sint || !arg_p->data.sint) { + smp->data.sint = 0; + return 1; + } + + /* The multiply between LLONG_MIN and -1 returns a + * "floting point exception". + */ + if (smp->data.sint == LLONG_MIN && arg_p->data.sint == -1) { + smp->data.sint = LLONG_MAX; + return 1; + } + + /* execute standard multiplication. */ + c = smp->data.sint * arg_p->data.sint; + + /* check for overflow and makes capped multiply. */ + if (smp->data.sint != c / arg_p->data.sint) { + if ((smp->data.sint < 0) == (arg_p->data.sint < 0)) { + smp->data.sint = LLONG_MAX; + return 1; + } + smp->data.sint = LLONG_MIN; + return 1; + } + smp->data.sint = c; return 1; } @@ -2089,10 +2157,18 @@ static int sample_conv_arith_mul(const struct arg *arg_p, static int sample_conv_arith_div(const struct arg *arg_p, struct sample *smp, void *private) { - if (arg_p->data.sint) + if (arg_p->data.sint) { + /* The divide between LLONG_MIN and -1 returns a + * "floting point exception". + */ + if (smp->data.sint == LLONG_MIN && arg_p->data.sint == -1) { + smp->data.sint = LLONG_MAX; + return 1; + } smp->data.sint /= arg_p->data.sint; - else - smp->data.sint = ~0; + return 1; + } + smp->data.sint = LLONG_MAX; return 1; } @@ -2103,10 +2179,18 @@ static int sample_conv_arith_div(const struct arg *arg_p, static int sample_conv_arith_mod(const struct arg *arg_p, struct sample *smp, void *private) { - if (arg_p->data.sint) + if (arg_p->data.sint) { + /* The divide between LLONG_MIN and -1 returns a + * "floting point exception". + */ + if (smp->data.sint == LLONG_MIN && arg_p->data.sint == -1) { + smp->data.sint = 0; + return 1; + } smp->data.sint %= arg_p->data.sint; - else - smp->data.sint = 0; + return 1; + } + smp->data.sint = 0; return 1; } @@ -2116,7 +2200,10 @@ static int sample_conv_arith_mod(const struct arg *arg_p, static int sample_conv_arith_neg(const struct arg *arg_p, struct sample *smp, void *private) { - smp->data.sint = -smp->data.sint; + if (smp->data.sint == LLONG_MIN) + smp->data.sint = LLONG_MAX; + else + smp->data.sint = -smp->data.sint; return 1; }