Skip to content

Commit 2ddec43

Browse files
committed
Use bit operations for constant-flow padding check
The previous code used comparison operators >= and == that are quite likely to be compiled to branches by some compilers on some architectures (with some optimisation levels). For example, take the following function: void old_update( size_t data_len, size_t *padlen ) { *padlen *= ( data_len >= *padlen + 1 ); } With Clang 3.8, let's compile it for the Arm v6-M architecture: % clang --target=arm-none-eabi -march=armv6-m -Os foo.c -S -o - | sed -n '/^old_update:$/,/\.size/p' old_update: .fnstart @ BB#0: .save {r4, lr} push {r4, lr} ldr r2, [r1] adds r4, r2, #1 movs r3, #0 cmp r4, r0 bls .LBB0_2 @ BB#1: mov r2, r3 .LBB0_2: str r2, [r1] pop {r4, pc} .Lfunc_end0: .size old_update, .Lfunc_end0-old_update We can see an unbalanced secret-dependant branch, resulting in a total execution time depends on the value of the secret (here padlen) in a straightforward way. The new version, based on bit operations, doesn't have this issue: new_update: .fnstart @ BB#0: ldr r2, [r1] subs r0, r0, #1 subs r0, r0, r2 asrs r0, r0, Mbed-TLS#31 bics r2, r0 str r2, [r1] bx lr .Lfunc_end1: .size new_update, .Lfunc_end1-new_update (As a bonus, it's smaller and uses less stack.) While there's no formal guarantee that the version based on bit operations in C won't be translated using branches by the compiler, experiments tend to show that's the case [1], and it is commonly accepted knowledge in the practical crypto community that if we want to sick to C, bit operations are the safest bet [2]. [1] https://github.com/mpg/ct/blob/master/results [2] https://github.com/veorq/cryptocoding Signed-off-by: Manuel Pégourié-Gonnard <[email protected]>
1 parent 523f055 commit 2ddec43

File tree

1 file changed

+98
-9
lines changed

1 file changed

+98
-9
lines changed

library/ssl_msg.c

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,82 @@ int mbedtls_ssl_encrypt_buf( mbedtls_ssl_context *ssl,
10441044
}
10451045

10461046
#if defined(MBEDTLS_SSL_SOME_SUITES_USE_TLS_CBC)
1047+
/*
1048+
* Constant-flow mask generation for "less than" comparison:
1049+
* - if x < y, return all bits 1, that is (size_t) -1
1050+
* - otherwise, return all bits 0, that is 0
1051+
*
1052+
* Use only bit operations to avoid branches that could be used by some
1053+
* compilers on some platforms to translate comparison operators.
1054+
*/
1055+
static size_t mbedtls_ssl_cf_mask_lt(size_t x, size_t y)
1056+
{
1057+
/* This has the msb set if and only if x < y */
1058+
const size_t sub = x - y;
1059+
1060+
/* sub1 = (x < y) in {0, 1} */
1061+
const size_t sub1 = sub >> ( sizeof( sub ) * 8 - 1 );
1062+
1063+
/* MSVC has a warning about unary minus on unsigned integer types,
1064+
* but this is well-defined and precisely what we want to do here. */
1065+
#if defined(_MSC_VER)
1066+
#pragma warning( push )
1067+
#pragma warning( disable : 4146 )
1068+
#endif
1069+
/* mask = (x < y) ? 0xff... : 0x00... */
1070+
const size_t mask = -sub1;
1071+
#if defined(_MSC_VER)
1072+
#pragma warning( pop )
1073+
#endif
1074+
1075+
return( mask );
1076+
}
1077+
1078+
/*
1079+
* Constant-flow mask generation for "greater or equal" comparison:
1080+
* - if x >= y, return all bits 1, that is (size_t) -1
1081+
* - otherwise, return all bits 0, that is 0
1082+
*
1083+
* Use only bit operations to avoid branches that could be used by some
1084+
* compilers on some platforms to translate comparison operators.
1085+
*/
1086+
static size_t mbedtls_ssl_cf_mask_ge(size_t x, size_t y)
1087+
{
1088+
return( ~mbedtls_ssl_cf_mask_lt(x, y) );
1089+
}
1090+
1091+
/*
1092+
* Constant-flow boolean "equal" comparison:
1093+
* return x == y
1094+
*
1095+
* Use only bit operations to avoid branches that could be used by some
1096+
* compilers on some platforms to translate comparison operators.
1097+
*/
1098+
static size_t mbedtls_ssl_cf_bool_eq(size_t x, size_t y)
1099+
{
1100+
/* diff = 0 if x == y, non-zero otherwise */
1101+
const size_t diff = x ^ y;
1102+
1103+
/* MSVC has a warning about unary minus on unsigned integer types,
1104+
* but this is well-defined and precisely what we want to do here. */
1105+
#if defined(_MSC_VER)
1106+
#pragma warning( push )
1107+
#pragma warning( disable : 4146 )
1108+
#endif
1109+
1110+
/* diff_msb's most significant bit is equal to x != y */
1111+
const size_t diff_msb = ( diff | -diff );
1112+
1113+
#if defined(_MSC_VER)
1114+
#pragma warning( pop )
1115+
#endif
1116+
1117+
/* diff1 = (x != y) in {0, 1} */
1118+
const size_t diff1 = diff_msb >> ( sizeof( diff_msb ) * 8 - 1 );
1119+
1120+
return( 1 ^ diff1 );
1121+
}
1122+
10471123
/*
10481124
* Constant-flow conditional memcpy:
10491125
* - if c1 == c2, equivalent to memcpy(dst, src, len),
@@ -1071,7 +1147,7 @@ static void mbedtls_ssl_cf_memcpy_if_eq( unsigned char *dst,
10711147
/* diff_msb's most significant bit is equal to c1 != c2 */
10721148
const size_t diff_msb = ( diff | -diff );
10731149

1074-
/* diff1 = c1 != c2 */
1150+
/* diff1 = (c1 != c2) in {0, 1} */
10751151
const size_t diff1 = diff_msb >> ( sizeof( diff_msb ) * 8 - 1 );
10761152

10771153
/* mask = c1 != c2 ? 0xff : 0x00 */
@@ -1528,8 +1604,11 @@ int mbedtls_ssl_decrypt_buf( mbedtls_ssl_context const *ssl,
15281604

15291605
if( auth_done == 1 )
15301606
{
1531-
correct *= ( rec->data_len >= padlen + 1 );
1532-
padlen *= ( rec->data_len >= padlen + 1 );
1607+
const size_t mask = mbedtls_ssl_cf_mask_ge(
1608+
rec->data_len,
1609+
padlen + 1 );
1610+
correct &= mask;
1611+
padlen &= mask;
15331612
}
15341613
else
15351614
{
@@ -1543,8 +1622,11 @@ int mbedtls_ssl_decrypt_buf( mbedtls_ssl_context const *ssl,
15431622
}
15441623
#endif
15451624

1546-
correct *= ( rec->data_len >= transform->maclen + padlen + 1 );
1547-
padlen *= ( rec->data_len >= transform->maclen + padlen + 1 );
1625+
const size_t mask = mbedtls_ssl_cf_mask_ge(
1626+
rec->data_len,
1627+
transform->maclen + padlen + 1 );
1628+
correct &= mask;
1629+
padlen &= mask;
15481630
}
15491631

15501632
padlen++;
@@ -1555,6 +1637,9 @@ int mbedtls_ssl_decrypt_buf( mbedtls_ssl_context const *ssl,
15551637
#if defined(MBEDTLS_SSL_PROTO_SSL3)
15561638
if( transform->minor_ver == MBEDTLS_SSL_MINOR_VERSION_0 )
15571639
{
1640+
/* This is the SSL 3.0 path, we don't have to worry about Lucky
1641+
* 13, because there's a strictly worse padding attack built in
1642+
* the protocol (known as part of POODLE), so branches are OK. */
15581643
if( padlen > transform->ivlen )
15591644
{
15601645
#if defined(MBEDTLS_SSL_DEBUG_ALL)
@@ -1578,7 +1663,6 @@ int mbedtls_ssl_decrypt_buf( mbedtls_ssl_context const *ssl,
15781663
* `min(256,plaintext_len)` reads (but take into account
15791664
* only the last `padlen` bytes for the padding check). */
15801665
size_t pad_count = 0;
1581-
size_t real_count = 0;
15821666
volatile unsigned char* const check = data;
15831667

15841668
/* Index of first padding byte; it has been ensured above
@@ -1590,10 +1674,15 @@ int mbedtls_ssl_decrypt_buf( mbedtls_ssl_context const *ssl,
15901674

15911675
for( idx = start_idx; idx < rec->data_len; idx++ )
15921676
{
1593-
real_count |= ( idx >= padding_idx );
1594-
pad_count += real_count * ( check[idx] == padlen - 1 );
1677+
/* pad_count += (idx >= padding_idx) &&
1678+
* (chech[idx] == padlen - 1);
1679+
*/
1680+
const size_t mask = mbedtls_ssl_cf_mask_ge( idx, padding_idx );
1681+
const size_t equal = mbedtls_ssl_cf_bool_eq( check[idx],
1682+
padlen - 1 );
1683+
pad_count += mask & equal;
15951684
}
1596-
correct &= ( pad_count == padlen );
1685+
correct &= mbedtls_ssl_cf_bool_eq( pad_count, padlen );
15971686

15981687
#if defined(MBEDTLS_SSL_DEBUG_ALL)
15991688
if( padlen > 0 && correct == 0 )

0 commit comments

Comments
 (0)