All,
I noticed these two patches too late to comment on the associated pull
request:
- patch 9.0.2111: [security]: overflow in get_number:
- patch 9.0.2109: [security]: overflow in nv_z_get_count:
Both perform overflow detection similarly, verifying that multiplication
by 10 does not overflow; in both cases, the detection logic lacks a
necessary extra check to ensure the addition of the new digit value does
not overflow.
For the `int` case, on a typical box with 32-bit `int` the value of
`INT_MAX` will be
2147483647. Consider the case where all but the last
`7` has been typed; the current value is then 214748364, which is no
greater than `INT_MAX/10`, so another digit is accepted. Multiplying by
10 yields 21474836470. Any digit up to `7` will remain in range, but
`8` and `9` will cause overflow. Similar logic applies for the `long` case.
The below example program shows helper functions for detecting whether a
given value (`int` or `long`) can accept another digit.
```c
#include <stdio.h>
#include <limits.h>
#define OK 1
#define FAIL 0
#define CHECK_ADDITION 1
int
vim_append_digit_int(int *value, int digit)
{
int x = *value;
if (x > (INT_MAX / 10))
return FAIL;
x *= 10;
#if CHECK_ADDITION
if (x > (INT_MAX - digit))
return FAIL;
#endif
x += digit;
*value = x;
return OK;
}
int
vim_append_digit_long(long *value, int digit)
{
long x = *value;
if (x > (LONG_MAX / 10))
return FAIL;
x *= 10;
#if CHECK_ADDITION
if (x > (LONG_MAX - digit))
return FAIL;
#endif
x += digit;
*value = x;
return OK;
}
static void
testInt(int value, int digit)
{
int oldValue = value;
int success = vim_append_digit_int(&value, digit);
if (success)
{
printf("int: %d:%d = %d <= %d\n", oldValue, digit, value, INT_MAX);
}
else
{
printf("int: %d:%d > %d (FAIL)\n", oldValue, digit, INT_MAX);
}
}
static void
testLong(long value, int digit)
{
long oldValue = value;
int success = vim_append_digit_long(&value, digit);
if (success)
{
printf("long: %ld:%d = %ld <= %ld\n", oldValue, digit, value,
LONG_MAX);
}
else
{
printf("long: %ld:%d > %ld (FAIL)\n", oldValue, digit, LONG_MAX);
}
}
int
main(void)
{
testInt(INT_MAX / 10, 0);
testInt(INT_MAX / 10, INT_MAX % 10);
testInt(INT_MAX / 10, INT_MAX % 10 + 1);
testLong(LONG_MAX / 10, 0);
testLong(LONG_MAX / 10, LONG_MAX % 10);
testLong(LONG_MAX / 10, LONG_MAX % 10 + 1);
return 0;
}
```
On my 64-bit Ubuntu Linux machine, I get the following output:
```
int: 214748364:0 =
2147483640 <=
2147483647
int: 214748364:7 =
2147483647 <=
2147483647
int: 214748364:8 >
2147483647 (FAIL)
long: 922337203685477580:0 = 9223372036854775800 <= 9223372036854775807
long: 922337203685477580:7 = 9223372036854775807 <= 9223372036854775807
long: 922337203685477580:8 > 9223372036854775807 (FAIL)
```
In the sample program change `CHECK_ADDITION` to `0` to demonstrate the
need for the second check.
Michael Henry