Skip to content
This repository has been archived by the owner on Oct 15, 2020. It is now read-only.

Commit

Permalink
deps: update ChakraCore to chakra-core/ChakraCore@bfbcfa5b9e
Browse files Browse the repository at this point in the history
[1.8>1.9] Add 1ms jitter to Date.now and getMilliseconds

Both of these functions in Chakra use HiResTimer, so this changeset
copies the methodology used in Edge's implementation of time jitter
in a way that allows us to easily change the quantization frequency
if future mitigations require it.

Reviewed-By: chakrabot <chakrabot@users.noreply.github.com>
  • Loading branch information
Penguinwizzard authored and kfarnung committed Mar 15, 2018
1 parent 6936504 commit dd916ed
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 4 deletions.
3 changes: 2 additions & 1 deletion deps/chakrashim/core/Build/Chakra.Build.props
Expand Up @@ -78,7 +78,8 @@
<PropertyGroup>
<ChakraCommonLinkDependencies>
oleaut32.lib;
version.lib
version.lib;
bcrypt.lib
</ChakraCommonLinkDependencies>
<RLCommonLinkDependencies>
kernel32.lib;
Expand Down
1 change: 1 addition & 0 deletions deps/chakrashim/core/bin/ChakraCore/ChakraCore.vcxproj
Expand Up @@ -50,6 +50,7 @@
advapi32.lib;
ole32.lib;
Rpcrt4.lib;
bcrypt.lib;
$(ChakraCommonLinkDependencies)
</AdditionalDependencies>
<AdditionalDependencies Condition="'$(IntlICU)'=='true'">
Expand Down
1 change: 1 addition & 0 deletions deps/chakrashim/core/bin/ch/ch.vcxproj
Expand Up @@ -38,6 +38,7 @@
kernel32.lib;
Rpcrt4.lib;
version.lib;
bcrypt.lib;
</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
Expand Down
Expand Up @@ -6,11 +6,163 @@
#include "RuntimePlatformAgnosticPch.h"
#include "Common.h"
#include "ChakraPlatform.h"
#include <Bcrypt.h>

namespace PlatformAgnostic
{
namespace DateTime
{
// Quantization code adapated from the version in Edge
template<uint64 frequencyOfQuantization>
class JitterManager
{
double frequencyOfSampling = 1.0;
double quantizationToSelectedScaleFactor = 1.0;
bool highFreq = false;
double currentRandomWindowScaled = 0.0;
ULONGLONG currentQuantizedQpc = 0;
public:
JitterManager::JitterManager()
{
// NOTE: We could cache the (1000/frequency) operation, as a double,
// that is used later to convert from seconds to milliseconds so that
// we don't keep recomputing it every time we need to convert from QPC
// to milliseconds (high-resolution only). However, doing so would mean
// we have to worry about loss of precision that occurs through rounding
// and its propagation through any arithmetic operations subsequent to
// the conversion. Such loss of precision isn't necessarily significant,
// since the time values returned from CTimeManager aren't expected to be
// used in more than 1 or 2 subsequent arithmetic operations. The other
// potential source of precision loss occurs when a floating point value
// gets converted from a normal form to a denormal form, which only happens
// when trying to store a number smaller than 2^(-126), and we should never
// see a frequency big enough to cause that. For example, worst case we would
// need a processor frequency of (1000/(2^(-126))=8.50705917*10^(31) GHz
// to go denormal.
LARGE_INTEGER tempFreq;
highFreq = QueryPerformanceFrequency(&tempFreq);
if (!highFreq)
{
// If we don't have a high-frequency source, the best we can do is GetSystemTime,
// which has a 1ms frequency.
frequencyOfSampling = 1000;
}
else
{
frequencyOfSampling = (double)tempFreq.QuadPart;
}

// frequency.QuadPart is the frequency of the QPC in counts per second. For quantinization,
// we want to scale the QPC to units of counts per selected frequency. We calculate the scale
// factor now:
//
// e.g. 500us = 2000 counts per second
//
quantizationToSelectedScaleFactor = frequencyOfSampling / frequencyOfQuantization;

// If the scale factor is less than one, it means the hardware's QPC frequency is already
// the selected frequency or greater. In this case, we clamp to 1. This makes the arithmetic
// in QuantizeToFreq a no-op, which avoids losing further precision.
//
// We clamp to 1 rather than use an early exit to avoid untested blocks.
quantizationToSelectedScaleFactor = max(quantizationToSelectedScaleFactor, 1.0);
}

uint64 JitterManager::QuantizedQPC(uint64 qpc)
{
// Due to further analysis of some attacks, we're jittering on a more granular
// frequency of as much as a full millisecond.

// 'qpc' is currently in units of frequencyOfSampling counts per second. We want to
// convert to units of frequencyOfQuantization, where sub-frequencyOfQuantization precision
// is expressed via the fractional part of a floating point number.
//
// We perform the conversion now, using the scale factor we precalculated in the
// constructor.
double preciseScaledResult = qpc / quantizationToSelectedScaleFactor;

// We now remove the fractional components, quantizing precision to the selected frequency by both ceiling and floor.
double ceilResult = ceil(preciseScaledResult);
double floorResult = floor(preciseScaledResult);

// Convert the results back to original QPC units, now at selected precision.
ceilResult *= quantizationToSelectedScaleFactor;
floorResult *= quantizationToSelectedScaleFactor;

// Convert these back to an integral value, taking the ceiling, even for the floored result.
// This will give us consistent results as the quantum moves (i.e. what is currently the
// quantizedQPC ends up being the floored QPC once we roll into the next window).
ULONGLONG quantizedQpc = static_cast<ULONGLONG>(ceil(ceilResult));
ULONGLONG quantizedQpcFloored = static_cast<ULONGLONG>(ceil(floorResult));

// The below converts the delta to milliseconds and checks that our quantized value does not
// diverge by more than our target quantization (plus an epsilon equal to 1 tick of the QPC).
AssertMsg(((quantizedQpc - qpc) * 1000.0 / frequencyOfSampling) <= (1000.0 / frequencyOfQuantization) + (1000.0 / frequencyOfSampling),
"W3C Timing: Difference between 'qpc' and 'quantizedQpc' expected to be <= selected frequency + epsilon.");

// If we're seeing this particular quantum for the first time, calculate a random portion of
// the beginning of the quantum in which we'll floor (and continue to report the previous quantum)
// instead of snapping to the next quantum.
// We don't do any of this quantiziation randomness if the scale factor is 1 (presumably because the
// QPC resolution was less than our selected quantum).
if (quantizationToSelectedScaleFactor != 1.0 && quantizedQpc != currentQuantizedQpc)
{
BYTE data[1];
NTSTATUS status = BCryptGenRandom(nullptr, data, sizeof(data), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
AssertOrFailFast(status == 0);
//Release_Assert(status == 0); IE does not have Release_Assert, but Chakra does.
if (BCRYPT_SUCCESS(status))
{
// Convert the random byte to a double in the range [0.0, 1.0)
// Note: if this ends up becoming performance critical, we can substitute the calculation with a
// 2K lookup table (256 * 8) bytes.
const double VALUES_IN_BYTE = 256.0;
const double random0to1 = ((double)data[0] / VALUES_IN_BYTE);

currentRandomWindowScaled = random0to1;
}
else
{
currentRandomWindowScaled = (double)(rand() % 256) / 256.0;
}

// Record the fact that we've already generated the random range for this particular quantum.
// Note that this is not the reported one, but the actual quantum as calculated from the QPC.
currentQuantizedQpc = quantizedQpc;
}

// Grab off the fractional portion of the preciseScaledResult. As an example:
// QueryPerformanceFrequency is 1,000,000
// This means a QPC is 1us.
// Quantum of 1000us means a QPC of 125 would result in a fractional portion of 0.125
// (math works out to (125 % 1000) / 1000).
// This fractional portion is then evaluated in order to determine whether or not to snap
// forward and use the next quantum, or use the floored one. This calculation gives us random
// windows in which specific 1000us timestamps are observed for a non-deterministic amount of time.
double preciseScaledFractional = preciseScaledResult - ((ULONGLONG)preciseScaledResult);
if (preciseScaledFractional < currentRandomWindowScaled)
{
quantizedQpc = quantizedQpcFloored;
}

return quantizedQpc;
}
};

// Set to jitter to an accuracy of 1000 ticks/second or 1ms
static thread_local JitterManager<1000> timeJitter;

double GetQuantizedSystemTime()
{
SYSTEMTIME stTime;
::GetSystemTime(&stTime);
// In the event that we have no high-res timers available, we don't have the needed
// granularity to jitter at a 1ms precision. We need to do something (as otherwise,
// the timer precision is higher than on a high-precision machine), but the best we
// can do is likely to strongly reduce the timer accuracy. Here we group by 4ms.
stTime.wMilliseconds = (stTime.wMilliseconds / 4) * 4;
return Js::DateUtilities::TimeFromSt(&stTime);
}

double HiResTimer::GetSystemTime()
{
Expand Down Expand Up @@ -38,15 +190,15 @@ namespace DateTime
{
if(!data.fHiResAvailable)
{
return GetSystemTime();
return GetQuantizedSystemTime();
}

if(!data.fInit)
{
if (!QueryPerformanceFrequency((LARGE_INTEGER *) &(data.freq)))
{
data.fHiResAvailable = false;
return GetSystemTime();
return GetQuantizedSystemTime();
}
data.fInit = true;
}
Expand All @@ -60,11 +212,14 @@ namespace DateTime
if( !QueryPerformanceCounter((LARGE_INTEGER *) &count))
{
data.fHiResAvailable = false;
return GetSystemTime();
return GetQuantizedSystemTime();
}

double time = GetSystemTime();

// quantize the timestamp to hinder timing attacks
count = timeJitter.QuantizedQPC(count);

// there is a base time and count set.
if (!data.fReset
&& (count >= data.baseMsCount)) // Make sure we don't regress
Expand Down

0 comments on commit dd916ed

Please sign in to comment.