diff --git a/browser/components/resistfingerprinting/test/browser/browser.ini b/browser/components/resistfingerprinting/test/browser/browser.ini index a9f351029af5..2d5a702a80f3 100644 --- a/browser/components/resistfingerprinting/test/browser/browser.ini +++ b/browser/components/resistfingerprinting/test/browser/browser.ini @@ -20,6 +20,7 @@ https_first_disabled = true skip-if = (os == "mac") #Bug 1570812 os == 'linux' && bits == 64 && !debug # Bug 1570812 +[browser_math.js] [browser_navigator.js] https_first_disabled = true skip-if = diff --git a/browser/components/resistfingerprinting/test/browser/browser_math.js b/browser/components/resistfingerprinting/test/browser/browser_math.js new file mode 100644 index 000000000000..66a71145907f --- /dev/null +++ b/browser/components/resistfingerprinting/test/browser/browser_math.js @@ -0,0 +1,107 @@ +/** + * Bug 531915 - A test for verifying that the JS Math fingerprint is constant + * when using fdlibm for Math.sin, Math.cos, and Math.tan. + */ + +async function test_math(rfp_pref, fdlibm_pref) { + await SpecialPowers.pushPrefEnv({ + set: [ + ["javascript.options.use_fdlibm_for_sin_cos_tan", fdlibm_pref], + ["privacy.resistFingerprinting", rfp_pref], + ], + }); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + TEST_PATH + "file_dummy.html" + ); + + await SpecialPowers.spawn(tab.linkedBrowser, [], async function() { + // override is to check strict equality like we would do in a JS ref test. + function strictly_is(a, b) { + ok(a === b); + } + + // + // Tests adapted from https://github.com/arkenfox/TZP/blob/5e3f5ff2c64b4edc7beecd8308aa4f7a3efb49e3/tests/math.html#L158-L319 + // + strictly_is(Math.cos(1e251), -0.37419577499634155); + strictly_is(Math.cos(1e140), -0.7854805190645291); + strictly_is(Math.cos(1e12), 0.7914463018528902); + strictly_is(Math.cos(1e130), -0.767224894221913); + strictly_is(Math.cos(1e272), -0.7415825695514536); + strictly_is(Math.cos(1), 0.5403023058681398); + strictly_is(Math.cos(1e284), 0.7086865671674247); + strictly_is(Math.cos(1e75), -0.7482651726250321); + strictly_is(Math.cos(Math.PI), -1); + strictly_is(Math.cos(-1e308), -0.8913089376870335); + strictly_is(Math.cos(13 * Math.E), -0.7108118501064332); + strictly_is(Math.cos(57 * Math.E), -0.536911695749024); + strictly_is(Math.cos(21 * Math.LN2), -0.4067775970251724); + strictly_is(Math.cos(51 * Math.LN2), -0.7017203400855446); + strictly_is(Math.cos(21 * Math.LOG2E), 0.4362848063618998); + strictly_is(Math.cos(25 * Math.SQRT2), -0.6982689820462377); + strictly_is(Math.cos(50 * Math.SQRT1_2), -0.6982689820462377); + strictly_is(Math.cos(21 * Math.SQRT1_2), -0.6534063185820198); + strictly_is(Math.cos(17 * Math.LOG10E), 0.4537557425982784); + strictly_is(Math.cos(2 * Math.LOG10E), 0.6459044007438142); + + strictly_is(Math.sin(1e251), -0.9273497301314576); + strictly_is(Math.sin(1e140), -0.6188863822787813); + strictly_is(Math.sin(1e12), -0.6112387023768895); + strictly_is(Math.sin(1e130), 0.6413781736901984); + strictly_is(Math.sin(1e272), 0.6708616046081811); + strictly_is(Math.sin(1), 0.8414709848078965); + strictly_is(Math.sin(1e284), -0.7055234578073583); + strictly_is(Math.sin(1e75), 0.66339975236386); + strictly_is(Math.sin(Math.PI), 1.2246467991473532e-16); + strictly_is(Math.sin(39 * Math.E), -0.7181630308570678); + strictly_is(Math.sin(35 * Math.LN2), -0.765996413898051); + strictly_is(Math.sin(110 * Math.LOG2E), 0.9989410140273757); + strictly_is(Math.sin(7 * Math.LOG10E), 0.10135692924965616); + strictly_is(Math.sin(35 * Math.SQRT1_2), -0.3746357547858202); + strictly_is(Math.sin(21 * Math.SQRT2), -0.9892668187780497); + + strictly_is(Math.tan(1e251), 2.478247463217681); + strictly_is(Math.tan(1e140), 0.7879079967710036); + strictly_is(Math.tan(1e12), -0.7723059681318761); + strictly_is(Math.tan(1e130), -0.8359715365344825); + strictly_is(Math.tan(1e272), -0.904635076595654); + strictly_is(Math.tan(1), 1.5574077246549023); + strictly_is(Math.tan(1e284), -0.9955366596368418); + strictly_is(Math.tan(1e75), -0.8865837628611647); + strictly_is(Math.tan(-1e308), 0.5086861259107568); + strictly_is(Math.tan(Math.PI), -1.2246467991473532e-16); + strictly_is(Math.tan(6 * Math.E), 0.6866761546452431); + strictly_is(Math.tan(6 * Math.LN2), 1.6182817135715877); + strictly_is(Math.tan(10 * Math.LOG2E), -3.3537128705376014); + strictly_is(Math.tan(17 * Math.SQRT2), -1.9222955461799982); + strictly_is(Math.tan(34 * Math.SQRT1_2), -1.9222955461799982); + strictly_is(Math.tan(10 * Math.LOG10E), 2.5824856130712432); + + // + // Tests adapted from https://github.com/fingerprintjs/fingerprintjs/blob/7096a5589af495f1f46067963e13ad27d887d185/src/sources/math.ts#L32-L64 + // + strictly_is(Math.acos(0.123124234234234242), 1.4473588658278522); + strictly_is(Math.acosh(1e308), 709.889355822726); + strictly_is(Math.asin(0.123124234234234242), 0.12343746096704435); + strictly_is(Math.asinh(1), 0.881373587019543); + strictly_is(Math.atanh(0.5), 0.5493061443340548); + strictly_is(Math.atan(0.5), 0.4636476090008061); + strictly_is(Math.sin(-1e300), 0.8178819121159085); + strictly_is(Math.sinh(1), 1.1752011936438014); + strictly_is(Math.cos(10.000000000123), -0.8390715290095377); + strictly_is(Math.cosh(1), 1.5430806348152437); + strictly_is(Math.tan(-1e300), -1.4214488238747245); + strictly_is(Math.tanh(1), 0.7615941559557649); + strictly_is(Math.exp(1), 2.718281828459045); + strictly_is(Math.expm1(1), 1.718281828459045); + strictly_is(Math.log1p(10), 2.3978952727983707); + }); + + BrowserTestUtils.removeTab(tab); +} + +add_task(test_math.bind(null, false, true)); +add_task(test_math.bind(null, true, false)); +add_task(test_math.bind(null, true, true)); diff --git a/js/public/CompileOptions.h b/js/public/CompileOptions.h index e5fa9b9d195d..acd9a8bb5e48 100644 --- a/js/public/CompileOptions.h +++ b/js/public/CompileOptions.h @@ -167,6 +167,8 @@ class JS_PUBLIC_API TransitiveCompileOptions { uint32_t introductionOffset = 0; bool hasIntroductionInfo = false; + bool useFdlibmForSinCosTan = false; + protected: TransitiveCompileOptions() = default; diff --git a/js/src/jit-test/tests/basic/fdlibm-for-sin-cos-tan-argument.js b/js/src/jit-test/tests/basic/fdlibm-for-sin-cos-tan-argument.js new file mode 100644 index 000000000000..e46a8259bcc1 --- /dev/null +++ b/js/src/jit-test/tests/basic/fdlibm-for-sin-cos-tan-argument.js @@ -0,0 +1,7 @@ +// |jit-test| --use-fdlibm-for-sin-cos-tan +// Test that fdlibm is being used for sin, cos, and tan. + +// Tests adapted from https://github.com/arkenfox/TZP/blob/master/tests/math.html#L158-L319 +assertEq(Math.cos(1e284), 0.7086865671674247); +assertEq(Math.sin(7*Math.LOG10E), 0.10135692924965616); +assertEq(Math.tan(6*Math.E), 0.6866761546452431); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 6382fc174fc5..8eb9fef96130 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -2329,6 +2329,7 @@ void JS::TransitiveCompileOptions::copyPODTransitiveOptions( classStaticBlocks = rhs.classStaticBlocks; useStencilXDR = rhs.useStencilXDR; useOffThreadParseGlobal = rhs.useOffThreadParseGlobal; + useFdlibmForSinCosTan = rhs.useFdlibmForSinCosTan; }; void JS::ReadOnlyCompileOptions::copyPODNonTransitiveOptions( @@ -2417,6 +2418,8 @@ JS::CompileOptions::CompileOptions(JSContext* cx) : ReadOnlyCompileOptions() { useStencilXDR = !UseOffThreadParseGlobal(); useOffThreadParseGlobal = UseOffThreadParseGlobal(); + useFdlibmForSinCosTan = math_use_fdlibm_for_sin_cos_tan(); + sourcePragmas_ = cx->options().sourcePragmas(); // Certain modes of operation force strict-mode in general. diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 7821ff575f15..1d2b7e948f14 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -64,6 +64,16 @@ /************************************************************************/ +namespace JS { +/** + * Tell JS engine whether to use fdlibm for Math.sin, Math.cos, and Math.tan. + * Using fdlibm ensures that we don't expose a math fingerprint. + */ +extern JS_PUBLIC_API void SetUseFdlibmForSinCosTan(bool value); +} // namespace JS + +/************************************************************************/ + struct JSFunctionSpec; struct JSPropertySpec; diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index cacd861004a1..e167a5c291a0 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -52,6 +52,12 @@ using mozilla::NumberEqualsInt32; using mozilla::PositiveInfinity; using mozilla::WrappingMultiply; +static mozilla::Atomic sUseFdlibmForSinCosTan; + +JS_PUBLIC_API void JS::SetUseFdlibmForSinCosTan(bool value) { + sUseFdlibmForSinCosTan = value; +} + template static bool math_function(JSContext* cx, HandleValue val, MutableHandleValue res) { @@ -206,13 +212,25 @@ bool js::math_clz32(JSContext* cx, unsigned argc, Value* vp) { return true; } -double js::math_cos_impl(double x) { +bool js::math_use_fdlibm_for_sin_cos_tan() { return sUseFdlibmForSinCosTan; } + +double js::math_cos_fdlibm_impl(double x) { + MOZ_ASSERT(sUseFdlibmForSinCosTan); + AutoUnsafeCallWithABI unsafe; + return fdlibm::cos(x); +} + +double js::math_cos_native_impl(double x) { + MOZ_ASSERT(!sUseFdlibmForSinCosTan); AutoUnsafeCallWithABI unsafe; return cos(x); } bool js::math_cos(JSContext* cx, unsigned argc, Value* vp) { - return math_function(cx, argc, vp); + if (sUseFdlibmForSinCosTan) { + return math_function(cx, argc, vp); + } + return math_function(cx, argc, vp); } double js::math_exp_impl(double x) { @@ -582,18 +600,31 @@ js::math_round(JSContext* cx, unsigned argc, Value* vp) { return math_round_handle(cx, args[0], args.rval()); } -double js::math_sin_impl(double x) { +double js::math_sin_fdlibm_impl(double x) { + MOZ_ASSERT(sUseFdlibmForSinCosTan); + AutoUnsafeCallWithABI unsafe; + return fdlibm::sin(x); +} + +double js::math_sin_native_impl(double x) { + MOZ_ASSERT(!sUseFdlibmForSinCosTan); AutoUnsafeCallWithABI unsafe(UnsafeABIStrictness::AllowPendingExceptions); return sin(x); } bool js::math_sin_handle(JSContext* cx, HandleValue val, MutableHandleValue res) { - return math_function(cx, val, res); + if (sUseFdlibmForSinCosTan) { + return math_function(cx, val, res); + } + return math_function(cx, val, res); } bool js::math_sin(JSContext* cx, unsigned argc, Value* vp) { - return math_function(cx, argc, vp); + if (sUseFdlibmForSinCosTan) { + return math_function(cx, argc, vp); + } + return math_function(cx, argc, vp); } double js::math_sqrt_impl(double x) { @@ -610,13 +641,23 @@ bool js::math_sqrt(JSContext* cx, unsigned argc, Value* vp) { return math_function(cx, argc, vp); } -double js::math_tan_impl(double x) { +double js::math_tan_fdlibm_impl(double x) { + MOZ_ASSERT(sUseFdlibmForSinCosTan); + AutoUnsafeCallWithABI unsafe; + return fdlibm::tan(x); +} + +double js::math_tan_native_impl(double x) { + MOZ_ASSERT(!sUseFdlibmForSinCosTan); AutoUnsafeCallWithABI unsafe; return tan(x); } bool js::math_tan(JSContext* cx, unsigned argc, Value* vp) { - return math_function(cx, argc, vp); + if (sUseFdlibmForSinCosTan) { + return math_function(cx, argc, vp); + } + return math_function(cx, argc, vp); } double js::math_log10_impl(double x) { @@ -885,13 +926,22 @@ UnaryMathFunctionType js::GetUnaryMathFunctionPtr(UnaryMathFunction fun) { case UnaryMathFunction::Log: return math_log_impl; case UnaryMathFunction::Sin: - return math_sin_impl; + if (sUseFdlibmForSinCosTan) { + return math_sin_fdlibm_impl; + } + return math_sin_native_impl; case UnaryMathFunction::Cos: - return math_cos_impl; + if (sUseFdlibmForSinCosTan) { + return math_cos_fdlibm_impl; + } + return math_cos_native_impl; case UnaryMathFunction::Exp: return math_exp_impl; case UnaryMathFunction::Tan: - return math_tan_impl; + if (sUseFdlibmForSinCosTan) { + return math_tan_fdlibm_impl; + } + return math_tan_native_impl; case UnaryMathFunction::ATan: return math_atan_impl; case UnaryMathFunction::ASin: diff --git a/js/src/jsmath.h b/js/src/jsmath.h index 500de464e62a..04e405b4632a 100644 --- a/js/src/jsmath.h +++ b/js/src/jsmath.h @@ -109,16 +109,20 @@ extern double math_log_impl(double x); extern bool math_log_handle(JSContext* cx, HandleValue val, MutableHandleValue res); +extern bool math_use_fdlibm_for_sin_cos_tan(); + extern bool math_sin(JSContext* cx, unsigned argc, js::Value* vp); -extern double math_sin_impl(double x); +extern double math_sin_fdlibm_impl(double x); +extern double math_sin_native_impl(double x); extern bool math_sin_handle(JSContext* cx, HandleValue val, MutableHandleValue res); extern bool math_cos(JSContext* cx, unsigned argc, js::Value* vp); -extern double math_cos_impl(double x); +extern double math_cos_fdlibm_impl(double x); +extern double math_cos_native_impl(double x); extern bool math_exp(JSContext* cx, unsigned argc, js::Value* vp); @@ -126,7 +130,8 @@ extern double math_exp_impl(double x); extern bool math_tan(JSContext* cx, unsigned argc, js::Value* vp); -extern double math_tan_impl(double x); +extern double math_tan_fdlibm_impl(double x); +extern double math_tan_native_impl(double x); extern bool math_log10(JSContext* cx, unsigned argc, js::Value* vp); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 50048483aedb..472d38e0734b 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -638,6 +638,8 @@ bool shell::compileOnly = false; bool shell::disableOOMFunctions = false; bool shell::defaultToSameCompartment = true; +bool shell::useFdlibmForSinCosTan = false; + #ifdef DEBUG bool shell::dumpEntrainedVariables = false; bool shell::OOM_printAllocationCount = false; @@ -11274,6 +11276,7 @@ static bool SetContextOptions(JSContext* cx, const OptionParser& op) { enableTopLevelAwait = op.getBoolOption("enable-top-level-await"); enableClassStaticBlocks = op.getBoolOption("enable-class-static-blocks"); useOffThreadParseGlobal = op.getBoolOption("off-thread-parse-global"); + useFdlibmForSinCosTan = op.getBoolOption("use-fdlibm-for-sin-cos-tan"); JS::ContextOptionsRef(cx) .setAsmJS(enableAsmJS) @@ -11307,6 +11310,7 @@ static bool SetContextOptions(JSContext* cx, const OptionParser& op) { .setClassStaticBlocks(enableClassStaticBlocks); JS::SetUseOffThreadParseGlobal(useOffThreadParseGlobal); + JS::SetUseFdlibmForSinCosTan(useFdlibmForSinCosTan); // Check --fast-warmup first because it sets default warm-up thresholds. These // thresholds can then be overridden below by --ion-eager and other flags. @@ -12490,7 +12494,9 @@ int main(int argc, char** argv) { !op.addBoolOption('\0', "reprl", "Enable REPRL mode for fuzzing") || #endif !op.addStringOption('\0', "telemetry-dir", "[directory]", - "Output telemetry results in a directory")) { + "Output telemetry results in a directory") || + !op.addBoolOption('\0', "use-fdlibm-for-sin-cos-tan", + "Use fdlibm for Math.sin, Math.cos, and Math.tan")) { return EXIT_FAILURE; } diff --git a/js/src/shell/jsshell.h b/js/src/shell/jsshell.h index bea760dba4ff..cff0c5c84242 100644 --- a/js/src/shell/jsshell.h +++ b/js/src/shell/jsshell.h @@ -157,6 +157,8 @@ extern bool dumpEntrainedVariables; extern bool OOM_printAllocationCount; #endif +extern bool useFdlibmForSinCosTan; + extern UniqueChars processWideModuleLoadPath; // Alias the global dstName to namespaceObj.srcName. For example, if dstName is diff --git a/js/src/wasm/WasmBuiltins.cpp b/js/src/wasm/WasmBuiltins.cpp index 50e1faf98da3..bad3db394a09 100644 --- a/js/src/wasm/WasmBuiltins.cpp +++ b/js/src/wasm/WasmBuiltins.cpp @@ -1427,10 +1427,12 @@ bool wasm::NeedsBuiltinThunk(SymbolicAddress sym) { // Each JS builtin can have several overloads. These must all be enumerated in // PopulateTypedNatives() so they can be included in the process-wide thunk set. +#define FOR_EACH_SIN_COS_TAN_NATIVE(_) \ + _(math_sin, MathSin) \ + _(math_tan, MathTan) \ + _(math_cos, MathCos) + #define FOR_EACH_UNARY_NATIVE(_) \ - _(math_sin, MathSin) \ - _(math_tan, MathTan) \ - _(math_cos, MathCos) \ _(math_exp, MathExp) \ _(math_log, MathLog) \ _(math_asin, MathASin) \ @@ -1455,6 +1457,14 @@ bool wasm::NeedsBuiltinThunk(SymbolicAddress sym) { _(ecmaHypot, MathHypot) \ _(ecmaPow, MathPow) +#define DEFINE_SIN_COS_TAN_FLOAT_WRAPPER(func, _) \ + static float func##_impl_f32(float x) { \ + if (math_use_fdlibm_for_sin_cos_tan()) { \ + return float(func##_fdlibm_impl(double(x))); \ + } \ + return float(func##_native_impl(double(x))); \ + } + #define DEFINE_UNARY_FLOAT_WRAPPER(func, _) \ static float func##_impl_f32(float x) { \ return float(func##_impl(double(x))); \ @@ -1465,6 +1475,7 @@ bool wasm::NeedsBuiltinThunk(SymbolicAddress sym) { return float(func(double(x), double(y))); \ } +FOR_EACH_SIN_COS_TAN_NATIVE(DEFINE_SIN_COS_TAN_FLOAT_WRAPPER) FOR_EACH_UNARY_NATIVE(DEFINE_UNARY_FLOAT_WRAPPER) FOR_EACH_BINARY_NATIVE(DEFINE_BINARY_FLOAT_WRAPPER) @@ -1496,6 +1507,14 @@ static bool PopulateTypedNatives(TypedNativeToFuncPtrMap* typedNatives) { FuncCast(funcName, abiType))) \ return false; +#define ADD_SIN_COS_TAN_OVERLOADS(funcName, native) \ + if (math_use_fdlibm_for_sin_cos_tan()) { \ + ADD_OVERLOAD(funcName##_fdlibm_impl, native, Args_Double_Double) \ + } else { \ + ADD_OVERLOAD(funcName##_native_impl, native, Args_Double_Double) \ + } \ + ADD_OVERLOAD(funcName##_impl_f32, native, Args_Float32_Float32) + #define ADD_UNARY_OVERLOADS(funcName, native) \ ADD_OVERLOAD(funcName##_impl, native, Args_Double_Double) \ ADD_OVERLOAD(funcName##_impl_f32, native, Args_Float32_Float32) @@ -1504,6 +1523,7 @@ static bool PopulateTypedNatives(TypedNativeToFuncPtrMap* typedNatives) { ADD_OVERLOAD(funcName, native, Args_Double_DoubleDouble) \ ADD_OVERLOAD(funcName##_f32, native, Args_Float32_Float32Float32) + FOR_EACH_SIN_COS_TAN_NATIVE(ADD_SIN_COS_TAN_OVERLOADS) FOR_EACH_UNARY_NATIVE(ADD_UNARY_OVERLOADS) FOR_EACH_BINARY_NATIVE(ADD_BINARY_OVERLOADS) diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp index 4ece7b9df3a8..3a2ced1ccd3d 100644 --- a/js/xpconnect/src/XPCJSContext.cpp +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -1048,6 +1048,10 @@ static void ReloadPrefsCallback(const char* pref, void* aXpccx) { .setErgnomicBrandChecks(ergnomicBrandChecksEnabled) .setTopLevelAwait(topLevelAwaitEnabled); + JS::SetUseFdlibmForSinCosTan( + Preferences::GetBool(JS_OPTIONS_DOT_STR "use_fdlibm_for_sin_cos_tan") || + Preferences::GetBool("privacy.resistFingerprinting")); + nsCOMPtr xr = do_GetService("@mozilla.org/xre/runtime;1"); if (xr) { bool safeMode = false; diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 1e05a5143e56..408c34e1be87 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -6150,6 +6150,14 @@ mirror: always do_not_use_directly: true +# Whether to use fdlibm for Math.sin, Math.cos, and Math.tan. When +# privacy.resistFingerprinting is true, this pref is ignored and fdlibm is used +# anyway. +- name: javascript.options.use_fdlibm_for_sin_cos_tan + type: bool + value: false + mirror: always + #--------------------------------------------------------------------------- # Prefs starting with "layers." #--------------------------------------------------------------------------- diff --git a/toolkit/components/resistfingerprinting/nsRFPService.cpp b/toolkit/components/resistfingerprinting/nsRFPService.cpp index 057a43510ed6..62b13e78acda 100644 --- a/toolkit/components/resistfingerprinting/nsRFPService.cpp +++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp @@ -33,6 +33,7 @@ #include "mozilla/RefPtr.h" #include "mozilla/Services.h" #include "mozilla/StaticMutex.h" +#include "mozilla/StaticPrefs_javascript.h" #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/StaticPtr.h" #include "mozilla/TextEvents.h" @@ -716,6 +717,12 @@ void nsRFPService::UpdateRFPPref() { bool privacyResistFingerprinting = StaticPrefs::privacy_resistFingerprinting(); + + // set fdlibm pref + JS::SetUseFdlibmForSinCosTan( + StaticPrefs::javascript_options_use_fdlibm_for_sin_cos_tan() || + privacyResistFingerprinting); + if (privacyResistFingerprinting) { PR_SetEnv("TZ=UTC"); } else if (sInitialized) {