Skip to content

Commit e530b5b

Browse files
authored
Merge pull request #6579 from gilles-peskine-arm/negative-zero-from-add-2.28
Backport 2.28: Fix negative zero from bignum add/subtract
2 parents 0fcc1cb + d64123a commit e530b5b

9 files changed

+882
-181
lines changed
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Bugfix
2+
* In the bignum module, operations of the form (-A) - (+A) or (-A) - (-A)
3+
with A > 0 created an unintended representation of the value 0 which was
4+
not processed correctly by some bignum operations. Fix this. This had no
5+
consequence on cryptography code, but might affect applications that call
6+
bignum directly and use negative numbers.

include/mbedtls/bignum.h

+21-3
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,27 @@ extern "C" {
191191
*/
192192
typedef struct mbedtls_mpi
193193
{
194-
int s; /*!< Sign: -1 if the mpi is negative, 1 otherwise */
195-
size_t n; /*!< total # of limbs */
196-
mbedtls_mpi_uint *p; /*!< pointer to limbs */
194+
/** Sign: -1 if the mpi is negative, 1 otherwise.
195+
*
196+
* The number 0 must be represented with `s = +1`. Although many library
197+
* functions treat all-limbs-zero as equivalent to a valid representation
198+
* of 0 regardless of the sign bit, there are exceptions, so bignum
199+
* functions and external callers must always set \c s to +1 for the
200+
* number zero.
201+
*
202+
* Note that this implies that calloc() or `... = {0}` does not create
203+
* a valid MPI representation. You must call mbedtls_mpi_init().
204+
*/
205+
int s;
206+
207+
/** Total number of limbs in \c p. */
208+
size_t n;
209+
210+
/** Pointer to limbs.
211+
*
212+
* This may be \c NULL if \c n is 0.
213+
*/
214+
mbedtls_mpi_uint *p;
197215
}
198216
mbedtls_mpi;
199217

library/bignum.c

+22-34
Original file line numberDiff line numberDiff line change
@@ -1249,27 +1249,34 @@ int mbedtls_mpi_sub_abs( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi
12491249
return( ret );
12501250
}
12511251

1252-
/*
1253-
* Signed addition: X = A + B
1252+
/* Common function for signed addition and subtraction.
1253+
* Calculate A + B * flip_B where flip_B is 1 or -1.
12541254
*/
1255-
int mbedtls_mpi_add_mpi( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B )
1255+
static int add_sub_mpi( mbedtls_mpi *X,
1256+
const mbedtls_mpi *A, const mbedtls_mpi *B,
1257+
int flip_B )
12561258
{
12571259
int ret, s;
12581260
MPI_VALIDATE_RET( X != NULL );
12591261
MPI_VALIDATE_RET( A != NULL );
12601262
MPI_VALIDATE_RET( B != NULL );
12611263

12621264
s = A->s;
1263-
if( A->s * B->s < 0 )
1265+
if( A->s * B->s * flip_B < 0 )
12641266
{
1265-
if( mbedtls_mpi_cmp_abs( A, B ) >= 0 )
1267+
int cmp = mbedtls_mpi_cmp_abs( A, B );
1268+
if( cmp >= 0 )
12661269
{
12671270
MBEDTLS_MPI_CHK( mbedtls_mpi_sub_abs( X, A, B ) );
1268-
X->s = s;
1271+
/* If |A| = |B|, the result is 0 and we must set the sign bit
1272+
* to +1 regardless of which of A or B was negative. Otherwise,
1273+
* since |A| > |B|, the sign is the sign of A. */
1274+
X->s = cmp == 0 ? 1 : s;
12691275
}
12701276
else
12711277
{
12721278
MBEDTLS_MPI_CHK( mbedtls_mpi_sub_abs( X, B, A ) );
1279+
/* Since |A| < |B|, the sign is the opposite of A. */
12731280
X->s = -s;
12741281
}
12751282
}
@@ -1284,39 +1291,20 @@ int mbedtls_mpi_add_mpi( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi
12841291
return( ret );
12851292
}
12861293

1294+
/*
1295+
* Signed addition: X = A + B
1296+
*/
1297+
int mbedtls_mpi_add_mpi( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B )
1298+
{
1299+
return( add_sub_mpi( X, A, B, 1 ) );
1300+
}
1301+
12871302
/*
12881303
* Signed subtraction: X = A - B
12891304
*/
12901305
int mbedtls_mpi_sub_mpi( mbedtls_mpi *X, const mbedtls_mpi *A, const mbedtls_mpi *B )
12911306
{
1292-
int ret, s;
1293-
MPI_VALIDATE_RET( X != NULL );
1294-
MPI_VALIDATE_RET( A != NULL );
1295-
MPI_VALIDATE_RET( B != NULL );
1296-
1297-
s = A->s;
1298-
if( A->s * B->s > 0 )
1299-
{
1300-
if( mbedtls_mpi_cmp_abs( A, B ) >= 0 )
1301-
{
1302-
MBEDTLS_MPI_CHK( mbedtls_mpi_sub_abs( X, A, B ) );
1303-
X->s = s;
1304-
}
1305-
else
1306-
{
1307-
MBEDTLS_MPI_CHK( mbedtls_mpi_sub_abs( X, B, A ) );
1308-
X->s = -s;
1309-
}
1310-
}
1311-
else
1312-
{
1313-
MBEDTLS_MPI_CHK( mbedtls_mpi_add_abs( X, A, B ) );
1314-
X->s = s;
1315-
}
1316-
1317-
cleanup:
1318-
1319-
return( ret );
1307+
return( add_sub_mpi( X, A, B, -1 ) );
13201308
}
13211309

13221310
/*

tests/include/test/helpers.h

+21-7
Original file line numberDiff line numberDiff line change
@@ -360,20 +360,34 @@ void mbedtls_test_err_add_check( int high, int low,
360360
#if defined(MBEDTLS_BIGNUM_C)
361361
/** Read an MPI from a hexadecimal string.
362362
*
363-
* Like mbedtls_mpi_read_string(), but size the resulting bignum based
364-
* on the number of digits in the string. In particular, construct a
365-
* bignum with 0 limbs for an empty string, and a bignum with leading 0
366-
* limbs if the string has sufficiently many leading 0 digits.
367-
*
368-
* This is important so that the "0 (null)" and "0 (1 limb)" and
369-
* "leading zeros" test cases do what they claim.
363+
* Like mbedtls_mpi_read_string(), but with tighter guarantees around
364+
* edge cases.
365+
*
366+
* - This function guarantees that if \p s begins with '-' then the sign
367+
* bit of the result will be negative, even if the value is 0.
368+
* When this function encounters such a "negative 0", it
369+
* increments #mbedtls_test_case_uses_negative_0.
370+
* - The size of the result is exactly the minimum number of limbs needed
371+
* to fit the digits in the input. In particular, this function constructs
372+
* a bignum with 0 limbs for an empty string, and a bignum with leading 0
373+
* limbs if the string has sufficiently many leading 0 digits.
374+
* This is important so that the "0 (null)" and "0 (1 limb)" and
375+
* "leading zeros" test cases do what they claim.
370376
*
371377
* \param[out] X The MPI object to populate. It must be initialized.
372378
* \param[in] s The null-terminated hexadecimal string to read from.
373379
*
374380
* \return \c 0 on success, an \c MBEDTLS_ERR_MPI_xxx error code otherwise.
375381
*/
376382
int mbedtls_test_read_mpi( mbedtls_mpi *X, const char *s );
383+
384+
/** Nonzero if the current test case had an input parsed with
385+
* mbedtls_test_read_mpi() that is a negative 0 (`"-"`, `"-0"`, `"-00"`, etc.,
386+
* constructing a result with the sign bit set to -1 and the value being
387+
* all-limbs-0, which is not a valid representation in #mbedtls_mpi but is
388+
* tested for robustness).
389+
*/
390+
extern unsigned mbedtls_test_case_uses_negative_0;
377391
#endif /* MBEDTLS_BIGNUM_C */
378392

379393
#endif /* TEST_HELPERS_H */

tests/scripts/generate_bignum_tests.py

+34-12
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@
5454
# See the License for the specific language governing permissions and
5555
# limitations under the License.
5656

57-
import itertools
5857
import sys
59-
import typing
6058

6159
from abc import ABCMeta, abstractmethod
6260
from typing import Iterator, List, Tuple, TypeVar
@@ -68,20 +66,20 @@
6866
T = TypeVar('T') #pylint: disable=invalid-name
6967

7068
def hex_to_int(val: str) -> int:
71-
return int(val, 16) if val else 0
69+
"""Implement the syntax accepted by mbedtls_test_read_mpi().
70+
71+
This is a superset of what is accepted by mbedtls_test_read_mpi_core().
72+
"""
73+
if val in ['', '-']:
74+
return 0
75+
return int(val, 16)
7276

7377
def quote_str(val) -> str:
7478
return "\"{}\"".format(val)
7579

7680
def combination_pairs(values: List[T]) -> List[Tuple[T, T]]:
7781
"""Return all pair combinations from input values."""
78-
# The return value is cast, as older versions of mypy are unable to derive
79-
# the specific type returned by itertools.combinations_with_replacement.
80-
return typing.cast(
81-
List[Tuple[T, T]],
82-
list(itertools.combinations_with_replacement(values, 2))
83-
)
84-
82+
return [(x, y) for x in values for y in values]
8583

8684
class BignumTarget(test_data_generation.BaseTarget, metaclass=ABCMeta):
8785
#pylint: disable=abstract-method
@@ -105,7 +103,8 @@ class BignumOperation(BignumTarget, metaclass=ABCMeta):
105103
"""
106104
symbol = ""
107105
input_values = [
108-
"", "0", "7b", "-7b",
106+
"", "0", "-", "-0",
107+
"7b", "-7b",
109108
"0000000000000000123", "-0000000000000000123",
110109
"1230000000000000000", "-1230000000000000000"
111110
] # type: List[str]
@@ -120,6 +119,11 @@ def __init__(self, val_a: str, val_b: str) -> None:
120119
def arguments(self) -> List[str]:
121120
return [quote_str(self.arg_a), quote_str(self.arg_b), self.result()]
122121

122+
def description_suffix(self) -> str:
123+
#pylint: disable=no-self-use # derived classes need self
124+
"""Text to add at the end of the test case description."""
125+
return ""
126+
123127
def description(self) -> str:
124128
"""Generate a description for the test case.
125129
@@ -133,6 +137,9 @@ def description(self) -> str:
133137
self.symbol,
134138
self.value_description(self.arg_b)
135139
)
140+
description_suffix = self.description_suffix()
141+
if description_suffix:
142+
self.case_description += " " + description_suffix
136143
return super().description()
137144

138145
@abstractmethod
@@ -153,6 +160,8 @@ def value_description(val) -> str:
153160
"""
154161
if val == "":
155162
return "0 (null)"
163+
if val == "-":
164+
return "negative 0 (null)"
156165
if val == "0":
157166
return "0 (1 limb)"
158167

@@ -228,8 +237,21 @@ class BignumAdd(BignumOperation):
228237
]
229238
)
230239

240+
def __init__(self, val_a: str, val_b: str) -> None:
241+
super().__init__(val_a, val_b)
242+
self._result = self.int_a + self.int_b
243+
244+
def description_suffix(self) -> str:
245+
if (self.int_a >= 0 and self.int_b >= 0):
246+
return "" # obviously positive result or 0
247+
if (self.int_a <= 0 and self.int_b <= 0):
248+
return "" # obviously negative result or 0
249+
# The sign of the result is not obvious, so indicate it
250+
return ", result{}0".format('>' if self._result > 0 else
251+
'<' if self._result < 0 else '=')
252+
231253
def result(self) -> str:
232-
return quote_str("{:x}".format(self.int_a + self.int_b))
254+
return quote_str("{:x}".format(self._result))
233255

234256
if __name__ == '__main__':
235257
# Use the section of the docstring relevant to the CLI as description

tests/src/helpers.c

+26-2
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ void mbedtls_test_set_step( unsigned long step )
107107
mbedtls_test_info.step = step;
108108
}
109109

110+
#if defined(MBEDTLS_BIGNUM_C)
111+
unsigned mbedtls_test_case_uses_negative_0 = 0;
112+
#endif
113+
110114
void mbedtls_test_info_reset( void )
111115
{
112116
mbedtls_test_info.result = MBEDTLS_TEST_RESULT_SUCCESS;
@@ -116,6 +120,9 @@ void mbedtls_test_info_reset( void )
116120
mbedtls_test_info.filename = 0;
117121
memset( mbedtls_test_info.line1, 0, sizeof( mbedtls_test_info.line1 ) );
118122
memset( mbedtls_test_info.line2, 0, sizeof( mbedtls_test_info.line2 ) );
123+
#if defined(MBEDTLS_BIGNUM_C)
124+
mbedtls_test_case_uses_negative_0 = 0;
125+
#endif
119126
}
120127

121128
int mbedtls_test_equal( const char *test, int line_no, const char* filename,
@@ -426,14 +433,31 @@ void mbedtls_test_err_add_check( int high, int low,
426433
#if defined(MBEDTLS_BIGNUM_C)
427434
int mbedtls_test_read_mpi( mbedtls_mpi *X, const char *s )
428435
{
436+
int negative = 0;
437+
/* Always set the sign bit to -1 if the input has a minus sign, even for 0.
438+
* This creates an invalid representation, which mbedtls_mpi_read_string()
439+
* avoids but we want to be able to create that in test data. */
440+
if( s[0] == '-' )
441+
{
442+
++s;
443+
negative = 1;
444+
}
429445
/* mbedtls_mpi_read_string() currently retains leading zeros.
430446
* It always allocates at least one limb for the value 0. */
431447
if( s[0] == 0 )
432448
{
433449
mbedtls_mpi_free( X );
434450
return( 0 );
435451
}
436-
else
437-
return( mbedtls_mpi_read_string( X, 16, s ) );
452+
int ret = mbedtls_mpi_read_string( X, 16, s );
453+
if( ret != 0 )
454+
return( ret );
455+
if( negative )
456+
{
457+
if( mbedtls_mpi_cmp_int( X, 0 ) == 0 )
458+
++mbedtls_test_case_uses_negative_0;
459+
X->s = -1;
460+
}
461+
return( 0 );
438462
}
439463
#endif

tests/suites/test_suite_bignum.function

+14-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,21 @@
1111
* constructing the value. */
1212
static int sign_is_valid( const mbedtls_mpi *X )
1313
{
14+
/* Only +1 and -1 are valid sign bits, not e.g. 0 */
1415
if( X->s != 1 && X->s != -1 )
15-
return( 0 ); // invalid sign bit, e.g. 0
16-
if( mbedtls_mpi_bitlen( X ) == 0 && X->s != 1 )
17-
return( 0 ); // negative zero
16+
return( 0 );
17+
18+
/* The value 0 must be represented with the sign +1. A "negative zero"
19+
* with s=-1 is an invalid representation. Forbid that. As an exception,
20+
* we sometimes test the robustness of library functions when given
21+
* a negative zero input. If a test case has a negative zero as input,
22+
* we don't mind if the function has a negative zero output. */
23+
if( ! mbedtls_test_case_uses_negative_0 &&
24+
mbedtls_mpi_bitlen( X ) == 0 && X->s != 1 )
25+
{
26+
return( 0 );
27+
}
28+
1829
return( 1 );
1930
}
2031

0 commit comments

Comments
 (0)