An Incorrect Instruction Choice in the JIT Resulting in an Incorrect Calculation

We discussed this vulnerability during Episode 108 on 14 December 2021

This is a interesting primitive, an unsigned 32bit integer can mistakenly be kept unsigned after it is supposedly converted to a signed 64bit integer and passed in somewhere expecting a signed value.

The root of the bug is in InstructionSelector::VisitChangeInt32ToInt64 which as the name implies (there is a unsigned version also) is to decide what instruction to output to convert a signed Int32 into a signed Int64. The problem is that it actually operate conditionally, doing a sign extension if the input was signed, a zero-fill if unsigned. This one its own might not be an issue but other instructions assume the output will be signed.

As it appears this is normally used only with signed values, hitting it is as bit of an edge case. The optimizer when optimizing out an XOR by a value that is provably 0 (does nothing) will simply remove the XOR operation completely, the XOR operation implicitly would output signed values. But after optimizing, if the loaded operand was unsigned, it’ll be passed unsigned into the ChangeInt32... procedure triggering the bug condition.

There will be a third post in this blog series regarding exploiting this primitive, but at the time of this podcast that was not yet released.

Patch: They removed the conditional logic and only do a sign extended conversion now.