New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Division by zero in Curl_now #3572
Comments
Right, the use of statics like that is not a good idea. We need something better... /cc @pps83 |
I propose we just make it a build-time decision: if someone wants to build libcurl for Windows pre-vista, they need to set some define or something and then we go with the old code and for the rest we go with the modern take. |
You could also use thread safe function local static init. Something like:
|
That define already exists: |
Another take could be to store the dynamically detected windows version in |
Not only the Windows version but also the QPC frequency ;) |
Written blindly, but perhaps just something like this: struct curltime Curl_now(void)
{
struct curltime now;
#if !defined(_WIN32_WINNT) || !defined(_WIN32_WINNT_VISTA) || \
(_WIN32_WINNT < _WIN32_WINNT_VISTA)
/* for really old Windows versions */
DWORD milliseconds = GetTickCount();
now.tv_sec = milliseconds / 1000;
now.tv_usec = (milliseconds % 1000) * 1000;
#else
/* Vista or later */
{
LARGE_INTEGER count;
QueryPerformanceCounter(&count);
now.tv_sec = (time_t)(count.QuadPart / freq.QuadPart);
now.tv_usec =
(int)((count.QuadPart % freq.QuadPart) * 1000000 / freq.QuadPart);
}
#endif
return now;
} Sorry, that's incomplete |
I personally wouldn't want to introduce a compile time switch for just that. Any in your |
How about static LARGE_INTEGER qpcFreq;
static int useQpc;
void Curl_init_qpc() {
if(Curl_verify_windows_version(6, 0, PLATFORM_WINNT,
VERSION_GREATER_THAN_EQUAL)) {
useQpc = QueryPerformanceFrequency(&qpcFreq) != 0;
} else {
useQpc = 0;
}
} and calling |
#3573 moves the initialization of the variables to the init function. I have not tested this, so I will certainly appreciate feedback and corrections if any! |
Looks good to me. |
Sorry for coming late to the party ;)
I totally agree with that. In my project we totally have to support WinXP (heavy install base in China), but most of our users use modern Windows versions. Also, that's a minor impl detail to have compilation time change for that. |
No worries @pps83, several of us missed that. Thanks for checking out the fix! |
I did this
Execute unit tests for our application, which uses libcurl. Sometimes we see a crash in
Curl_now
withSTATUS_INTEGER_OVERFLOW
at thecount.QuadPart / freq.QuadPart
division. It's not every time, but with two of our tests we see one crash every 10 ~ 100 runs. So not really that rare.I expected the following
No crash.
libcurl version
Operating system
Windows >= Vista
What I think happens
This is probably a data race in
Curl_now
. When two or more threads callCurl_now
at the same time, and it's never been called before, the "first" thread will enter theif(isVistaOrGreater == -1)
block. That first thread will setisVistaOrGreater = 1
before setting the staticfreq
variable. (Strictly the order is undefined, because no synchronization is used. But becauseQueryPerformanceCounter
is an external function, the compiler will normally generate code that setsisVistaOrGreater = 1
before callingQueryPerformanceCounter
to initializefreq
. Even thoughfreq
is a function local static).So the window of vulnerability is from when the first thread sets
isVistaOrGreater = 1
to whenQueryPerformanceCounter
actually writes the result into thefreq
variable. Not particularly long, but this can easily be a few dozen or hundred cycles (and of course much much longer if there's a context switch).If a second thread executes
Curl_now
during that time window, it will seeisVistaOrGreater = 1
, skip the initialization and go on to divide byfreq
-- which is still zero at that time. The integer division by zero then causes a CPU exception/trap/whatever which is handled by the OS and converted into a structured exception (SEH).Side note: When dividing by zero we would normally expect
STATUS_INTEGER_DIVIDE_BY_ZERO
. The reason we seeSTATUS_INTEGER_OVERFLOW
is probably that very shortly after the division by zero, the memory location forfreq
(which is the divisor) is set to a non-zero value. So when the "integer division exception" handler of the Windows kernel later reads the divisor, it will find it non-zero, and therefor think there was an integer overflow. See https://blogs.msdn.microsoft.com/oldnewthing/20141002-00/?p=43933/ for some more information about the topic.Calling
Curl_now
duringcurl_global_init
would probably fix the issue.The text was updated successfully, but these errors were encountered: