Skip to content

Commit 6b2e1ef

Browse files
committed
[MERGE #5888 @Cellule] WebAssembly LEB128 check extra bits
Merge pull request #5888 from Cellule:users/micfer/leb128 When reading the last byte of a LEB128, the additional unused bits should be all zeros (or can be all ones if signed LEB128). For instance, when reading a uint32, you need to read 5 bytes with 7 bits used per bytes that's 35 bits, so there are 3 bits that needs to be checked. This doesn't change anything in practice other than spec compliance. I took the opportunity to simplify the LEB128 decoding code <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/microsoft/chakracore/5888) <!-- Reviewable:end -->
2 parents cada852 + 714bfa3 commit 6b2e1ef

File tree

2 files changed

+43
-32
lines changed

2 files changed

+43
-32
lines changed

lib/WasmReader/WasmBinaryReader.cpp

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1247,40 +1247,61 @@ LEBType WasmBinaryReader::LEB128(uint32 &length)
12471247
constexpr bool sign = LEBType(-1) < LEBType(0);
12481248
LEBType result = 0;
12491249
uint32 shift = 0;
1250-
byte b = 0;
1250+
byte b = 0x80;
12511251
length = 0;
1252-
constexpr uint32 maxReads = (uint32) (((bits % 7) == 0) ? bits/7 : bits/7 + 1);
1253-
CompileAssert(maxReads > 0);
1252+
constexpr uint32 maxBytes = (uint32)((bits + 6) / 7);
1253+
CompileAssert(maxBytes > 0);
12541254

1255-
for (uint32 i = 0; i < maxReads; ++i)
1255+
uint32 iByte = 0;
1256+
for (; iByte < maxBytes && (b & 0x80) == 0x80; ++iByte)
12561257
{
12571258
CheckBytesLeft(1);
12581259
b = *m_pc++;
1259-
++length;
12601260
result = result | ((LEBType)(b & 0x7f) << shift);
1261-
if (sign)
1262-
{
1263-
shift += 7;
1264-
if ((b & 0x80) == 0)
1265-
break;
1266-
}
1267-
else
1268-
{
1269-
if ((b & 0x80) == 0)
1270-
break;
1271-
shift += 7;
1272-
}
1261+
shift += 7;
12731262
}
12741263

1275-
if (b & 0x80 || m_pc > m_end)
1264+
if ((b & 0x80) == 0x80)
12761265
{
12771266
ThrowDecodingError(_u("Invalid LEB128 format"));
12781267
}
12791268

1280-
if (sign && (shift < (sizeof(LEBType) * 8)) && (0x40 & b))
1269+
const bool isLastByte = iByte == maxBytes;
1270+
constexpr bool hasExtraBits = (maxBytes * 7) != bits;
1271+
if (hasExtraBits && isLastByte)
1272+
{
1273+
// A signed-LEB128 must sign-extend the final byte, excluding its most-significant bit
1274+
// For unsigned values, the extra bits must be all zero.
1275+
// For signed values, the extra bits *plus* the most significant bit must either be 0, or all ones.
1276+
1277+
// e.g. for a 32-bit LEB128:
1278+
// bitsInLastByte = 4 (== 32 - (5-1) * 7)
1279+
// 0bYYYXXXX where Y are extra bits and X are bits included in LEB128
1280+
// if signed: check if 0bYYYXXXX is 0b0000XXX or 0b1111XXX
1281+
// is unsigned: check if 0bYYYXXXX is 0b000XXXX
1282+
1283+
constexpr int bitsInLastByte = bits - (maxBytes - 1) * 7;
1284+
constexpr int lastBitToCheck = bitsInLastByte - (sign ? 1 : 0);
1285+
constexpr byte signExtendedExtraBits = 0x7f & (0xFF << lastBitToCheck);
1286+
const byte checkedBits = b & (0xFF << lastBitToCheck);
1287+
bool validExtraBits =
1288+
checkedBits == 0 ||
1289+
(sign && checkedBits == signExtendedExtraBits);
1290+
if (!validExtraBits)
1291+
{
1292+
ThrowDecodingError(_u("Invalid LEB128 format"));
1293+
}
1294+
}
1295+
1296+
length = iByte;
1297+
if (sign)
12811298
{
1282-
#pragma prefast(suppress:26453)
1283-
result |= ((~(LEBType)0) << shift);
1299+
// Perform sign extension
1300+
const int signExtendShift = (sizeof(LEBType) * 8) - shift;
1301+
if (signExtendShift > 0)
1302+
{
1303+
result = static_cast<LEBType>(result << signExtendShift) >> signExtendShift;
1304+
}
12841305
}
12851306
return result;
12861307
}
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
(50) testsuite/core/binary.wast:192: assert_malformed module failed. Should have had an error
2-
(51) testsuite/core/binary.wast:200: assert_malformed module failed. Should have had an error
3-
(52) testsuite/core/binary.wast:210: assert_malformed module failed. Should have had an error
4-
(53) testsuite/core/binary.wast:220: assert_malformed module failed. Should have had an error
5-
(54) testsuite/core/binary.wast:230: assert_malformed module failed. Should have had an error
6-
(55) testsuite/core/binary.wast:240: assert_malformed module failed. Should have had an error
7-
(56) testsuite/core/binary.wast:251: assert_malformed module failed. Should have had an error
8-
(57) testsuite/core/binary.wast:261: assert_malformed module failed. Should have had an error
9-
(58) testsuite/core/binary.wast:271: assert_malformed module failed. Should have had an error
10-
(59) testsuite/core/binary.wast:281: assert_malformed module failed. Should have had an error
11-
71/81 tests passed.
1+
81/81 tests passed.

0 commit comments

Comments
 (0)