@@ -1240,6 +1240,40 @@ static bool sort_kv_pairs(struct kv_pair *kv, int size, GlobalContext *global)
12401240}
12411241#endif
12421242
1243+ /**
1244+ * @brief Scale a dynamic segment size by its unit.
1245+ *
1246+ * @details Matching opcodes take a segment size from a register and scale it by
1247+ * the segment unit. The size can be negative or large enough for the product to
1248+ * overflow, so it cannot be multiplied blindly: the scaled size is compared
1249+ * against the remaining capacity and added to the match offset, and scaling
1250+ * first lets a negative size wrap to a small one that passes the capacity check,
1251+ * or move the match offset before the start of the binary.
1252+ *
1253+ * @param size the segment size, as a term (must be any integer)
1254+ * @param unit the segment unit
1255+ * @param max_value the largest acceptable scaled size
1256+ * @param scaled_size on success, the scaled size
1257+ * @returns \c true if the scaled size is representable and at most
1258+ * \c max_value, \c false if the match should fail
1259+ */
1260+ static inline bool bs_scaled_size (term size , uint32_t unit , size_t max_value , size_t * scaled_size )
1261+ {
1262+ if (!term_is_integer (size )) {
1263+ // a size that doesn't fit in a small integer cannot fit in max_value
1264+ return false;
1265+ }
1266+ avm_int_t size_val = term_to_int (size );
1267+ if (size_val < 0 ) {
1268+ return false;
1269+ }
1270+ if (unit != 0 && (size_t ) size_val > max_value / unit ) {
1271+ return false;
1272+ }
1273+ * scaled_size = (size_t ) size_val * unit ;
1274+ return true;
1275+ }
1276+
12431277static term maybe_alloc_boxed_integer_fragment (Context * ctx , avm_int64_t value )
12441278{
12451279#if BOXED_TERMS_REQUIRED_FOR_INT64 > 1
@@ -4254,17 +4288,17 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
42544288 DECODE_LITERAL (flags_value , pc )
42554289
42564290 VERIFY_IS_MATCH_STATE (src , "bs_skip_bits2" , 0 );
4257- VERIFY_IS_INTEGER (size , "bs_skip_bits2" , 0 );
4291+ VERIFY_IS_ANY_INTEGER (size , "bs_skip_bits2" , 0 );
42584292 // Ignore flags value as skipping bits is the same whatever the endianness
4259- avm_int_t size_val = term_to_int (size );
4260-
4261- TRACE ("bs_skip_bits2/5, fail=%u src=%p size=0x%lx unit=%u flags=%x\n" , (unsigned ) fail , (void * ) src , (unsigned long ) size_val , (unsigned ) unit , (int ) flags_value );
4293+ TRACE ("bs_skip_bits2/5, fail=%u src=%p unit=%u flags=%x\n" , (unsigned ) fail , (void * ) src , (unsigned ) unit , (int ) flags_value );
42624294
4263- size_t increment = size_val * unit ;
42644295 avm_int_t bs_offset = term_get_match_state_offset (src );
42654296 term bs_bin = term_get_match_state_binary (src );
4266- if ((bs_offset + increment ) > term_binary_size (bs_bin ) * 8 ) {
4267- TRACE ("bs_skip_bits2: Insufficient capacity to skip bits: %lu, inc: %zu\n" , (unsigned long ) bs_offset , increment );
4297+ size_t bs_capacity = term_binary_size (bs_bin ) * 8 ;
4298+ size_t increment ;
4299+ if ((size_t ) bs_offset > bs_capacity
4300+ || !bs_scaled_size (size , unit , bs_capacity - bs_offset , & increment )) {
4301+ TRACE ("bs_skip_bits2: Insufficient capacity to skip bits: %lu\n" , (unsigned long ) bs_offset );
42684302 JUMP_TO_ADDRESS (mod -> labels [fail ]);
42694303 } else {
42704304 term_set_match_state_offset (src , bs_offset + increment );
@@ -4336,16 +4370,22 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
43364370 DECODE_LITERAL (flags_value , pc )
43374371
43384372 VERIFY_IS_MATCH_STATE (src , "bs_get_integer" , 0 );
4339- VERIFY_IS_INTEGER (size , "bs_get_integer" , 0 );
4340-
4341- avm_int_t size_val = term_to_int (size );
4373+ VERIFY_IS_ANY_INTEGER (size , "bs_get_integer" , 0 );
43424374
4343- TRACE ("bs_get_integer2/7, fail=%u src=%p live=%u size=%u unit=%u flags=%x\n" , (unsigned ) fail , (void * ) src , ( unsigned ) size_val , (unsigned ) live , (unsigned ) unit , (int ) flags_value );
4375+ TRACE ("bs_get_integer2/7, fail=%u src=%p live=%u unit=%u flags=%x\n" , (unsigned ) fail , (void * ) src , (unsigned ) live , (unsigned ) unit , (int ) flags_value );
43444376
4345- avm_int_t increment = size_val * unit ;
43464377 union maybe_unsigned_int64 value ;
43474378 term bs_bin = term_get_match_state_binary (src );
43484379 avm_int_t bs_offset = term_get_match_state_offset (src );
4380+ size_t bs_capacity = term_binary_size (bs_bin ) * 8 ;
4381+ size_t increment_bits ;
4382+ if ((size_t ) bs_offset > bs_capacity
4383+ || !bs_scaled_size (size , unit , bs_capacity - bs_offset , & increment_bits )) {
4384+ TRACE ("bs_get_integer2: size is negative or exceeds the remaining capacity\n" );
4385+ JUMP_TO_ADDRESS (mod -> labels [fail ]);
4386+ }
4387+ // bounded by the remaining capacity, so it fits in an avm_int_t
4388+ avm_int_t increment = (avm_int_t ) increment_bits ;
43494389 term t ;
43504390 if (increment <= 64 ) {
43514391 bool status = bitstring_extract_integer (bs_bin , bs_offset , increment , flags_value , & value );
@@ -4409,16 +4449,23 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
44094449 DECODE_LITERAL (flags_value , pc );
44104450
44114451 VERIFY_IS_MATCH_STATE (src , "bs_get_float" , 0 );
4412- VERIFY_IS_INTEGER (size , "bs_get_float" , 0 );
4452+ VERIFY_IS_ANY_INTEGER (size , "bs_get_float" , 0 );
44134453
4414- avm_int_t size_val = term_to_int ( size );
4454+ TRACE ( "bs_get_float2/7, fail=%u src=%p unit=%u flags=%x\n" , ( unsigned ) fail , ( void * ) src , ( unsigned ) unit , ( int ) flags_value );
44154455
4416- TRACE ("bs_get_float2/7, fail=%u src=%p size=%u unit=%u flags=%x\n" , (unsigned ) fail , (void * ) src , (unsigned ) size_val , (unsigned ) unit , (int ) flags_value );
4417-
4418- avm_int_t increment = size_val * unit ;
44194456 avm_float_t value ;
44204457 term bs_bin = term_get_match_state_binary (src );
44214458 avm_int_t bs_offset = term_get_match_state_offset (src );
4459+ size_t bs_capacity = term_binary_size (bs_bin ) * 8 ;
4460+ size_t increment_bits ;
4461+ if ((size_t ) bs_offset > bs_capacity
4462+ || !bs_scaled_size (size , unit , bs_capacity - bs_offset , & increment_bits )) {
4463+ TRACE ("bs_get_float2: size is negative or exceeds the remaining capacity\n" );
4464+ JUMP_TO_ADDRESS (mod -> labels [fail ]);
4465+ }
4466+ // both bounded by the remaining capacity, so they fit in an avm_int_t
4467+ avm_int_t size_val = term_to_int (size );
4468+ avm_int_t increment = (avm_int_t ) increment_bits ;
44224469 bool status ;
44234470 switch (size_val ) {
44244471 case 16 :
@@ -4478,11 +4525,21 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
44784525 TRACE ("bs_get_binary2: Unsupported: unit must be 8.\n" );
44794526 RAISE_ERROR (UNSUPPORTED_ATOM );
44804527 }
4481- avm_int_t size_val = 0 ;
4482- if (term_is_integer (size )) {
4483- size_val = term_to_int (size );
4528+ size_t bs_capacity = term_binary_size (bs_bin );
4529+ if ((size_t ) bs_offset / 8 > bs_capacity ) {
4530+ TRACE ("bs_get_binary2: match state offset is past the end of the binary\n" );
4531+ JUMP_TO_ADDRESS (mod -> labels [fail ]);
4532+ }
4533+ size_t remaining_bytes = bs_capacity - bs_offset / 8 ;
4534+ size_t size_val = 0 ;
4535+ if (term_is_any_integer (size )) {
4536+ // A negative or oversized size fails the match, as on BEAM
4537+ if (!bs_scaled_size (size , 1 , remaining_bytes , & size_val )) {
4538+ TRACE ("bs_get_binary2: size is negative or exceeds the remaining capacity\n" );
4539+ JUMP_TO_ADDRESS (mod -> labels [fail ]);
4540+ }
44844541 } else if (size == ALL_ATOM ) {
4485- size_val = term_binary_size ( bs_bin ) - bs_offset / 8 ;
4542+ size_val = remaining_bytes ;
44864543 } else {
44874544 TRACE ("bs_get_binary2: size is neither an integer nor the atom `all`\n" );
44884545 RAISE_ERROR (BADARG_ATOM );
@@ -4498,8 +4555,8 @@ HOT_FUNC int scheduler_entry_point(GlobalContext *glb)
44984555
44994556 TRACE ("bs_get_binary2/7, fail=%u src=%p live=%u unit=%u\n" , (unsigned ) fail , (void * ) bs_bin , (unsigned ) live , (unsigned ) unit );
45004557
4501- if (( unsigned int ) ( bs_offset / unit + size_val ) > term_binary_size ( bs_bin ) ) {
4502- TRACE ("bs_get_binary2: insufficient capacity -- bs_offset = %d, size_val = %d \n" , (int ) bs_offset , ( int ) size_val );
4558+ if (size_val > remaining_bytes ) {
4559+ TRACE ("bs_get_binary2: insufficient capacity -- bs_offset = %d, size_val = %zu \n" , (int ) bs_offset , size_val );
45034560 JUMP_TO_ADDRESS (mod -> labels [fail ]);
45044561 } else {
45054562 term_set_match_state_offset (src , bs_offset + size_val * unit );
0 commit comments