-
Notifications
You must be signed in to change notification settings - Fork 7.9k
High resolution monotonic timer #2976
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
Conversation
Just had a quick look API-wise. I'd prefer different functions for different formats, |
@kelunik thanks for checking. That's Thanks. |
@weltling I know, but it's not really consistent. |
@kelunik the signature it is and the way it returns some number with true. The stirng return - well, not really sure it should mimic that, too. In terms of functionality - perhaps array is the only thing that makes sense for portability. The number return is good on 64-bit and thus was tempting. Anyway, please suggest another API to discuss. Otherwise could simply only leave the array return and remove the alternative variant. Something for diff/compare would be then unambiguous, too. |
ext/standard/hrtime.c
Outdated
} while (0) | ||
#endif | ||
|
||
/* {{{ proto mixed hrtime([bool get_as_numu = false]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should probably mention it is monotonic. It's in the patch comment, but this won't be available for people reading the code.
ext/standard/hrtime.c
Outdated
} while (0) | ||
#endif | ||
|
||
/* {{{ proto mixed hrtime([bool get_as_numu = false]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess "numu" should be "num"? Or, even better, "number".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, typo here. Spelling completely as suggested.
ext/standard/hrtime.c
Outdated
PHP_RETURN_HRTIME(t); | ||
} | ||
#else | ||
RETURN_LONG(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it would be better to return false? Otherwise there's no reliable non-probabilistic way of seeing whether the current platform supports hrtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, either NULL or false, so lets take false.
} | ||
/* }}} */ | ||
|
||
PHPAPI php_hrtime_t php_hrtime_current(void) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not used anywhere. Is it supposed to be?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done specifically to export the useful piece of the new API, so i'd say yes. It can be used by non core exts, etc.
#ifndef HRTIME_H | ||
#define HRTIME_H | ||
|
||
#define PHP_HRTIME_PLATFORM_POSIX 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these things be handled by autoconf? FPM has some autoconf checks for clock_gettime already, maybe others can be added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can migrate this, yep. In general, a system where sysconf is available could do a compile time check. I've omited it, because a single compile time failure is less to say against the runtime check. The binary can be compiled with a runtime check at the PHP startup, the submodule will fail otherwise.
ext/standard/hrtime.c
Outdated
#if PHP_HRTIME_PLATFORM_WINDOWS | ||
uint64_t cur; | ||
QueryPerformanceCounter((LARGE_INTEGER*) &cur); | ||
return (php_hrtime_t)((double)cur * _timer_int * (double)NANO_IN_SEC); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
double has only 53 bits of precision for value, so there could be a loss of precision here. Couldn't we use timer frequency directly instead of floats?
ext/standard/hrtime.c
Outdated
{/*{{{*/ | ||
#if PHP_HRTIME_PLATFORM_WINDOWS | ||
uint64_t cur; | ||
QueryPerformanceCounter((LARGE_INTEGER*) &cur); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks kinda dangerous. Couldn't we use LARGE_INTEGER properly instead of relying on uint64_t having the same bits (which it probably would, but it still is unclean)?
#ifdef _WIN32 | ||
# define HRTIME_U64A(i, s, len) _ui64toa_s(i, s, len, 10) | ||
#else | ||
# define HRTIME_U64A(i, s, len) \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we already have ZEND_LTOA? Why duplicate it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ZEND_LTOA depends on the platform and matches zend_long. The timer is saved in uint64_t, thus the macro is not suitable on 32-bit.
ext/standard/hrtime.c
Outdated
Z_PARAM_BOOL(get_as_num) | ||
ZEND_PARSE_PARAMETERS_END(); | ||
|
||
if (!get_as_num) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we reverse the condition? It's cognitively easier to read something as "if this then do A else do B" then "if not this then do A else do B".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, that doesn't matter. Also should wrap with UNEXPECTED. For the processor it doesn't matter anyway, only a single instruction in both cases.
char _a[ZEND_LTOA_BUF_LEN]; \ | ||
double _d; \ | ||
HRTIME_U64A(t, _a, ZEND_LTOA_BUF_LEN); \ | ||
_d = zend_strtod(_a, NULL); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why we're converting to string and back here - can't we just do (double)
? Yes, there's a potential for precision loss, but wouldn't it happen in string case too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The integral part of double can be overflown quite fast. With an up time of a couple of months, as soon as timestamp exceeds (2^52)/1000000000
. The string conversion is the only way i see so far to do the correct conversion. Still it's 32-bit only. On 64-bit, even a signed 64-bit integral is fine for some hundreds of years of up time.
Please also document new function in UPGRADING. |
ext/standard/hrtime.c
Outdated
/* $Id$ */ | ||
|
||
#include "php.h" | ||
#include "zend_exceptions.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, removed.
6857b27
to
3cab0c3
Compare
Stas, i've addressed the comments where appropriate or left an explanation. The 32-bit part regarding floats is of course not pretty, but the string conversion is most reliable. Indeed, one could simply abandon the get_as_number part, but it would be a pity for 64-bit. It is anyway to see whether 32-bit dies or we'll have to implement 64-bit integer support there :) Regarding the m4 migration - yep, can be done, then AC_FPM_CLOCK function should be moved into the core m4. Either header or m4 seems ok, so i'd leave it with header for now and see to do this with FPM refactoring. And of course keeping in mind UPGRADING, will document once merged. Thanks. |
You can add UPGRADING part to this very patch, no need to make two separate ones I think? |
Of course, just that's often a conflicting part for the merge. I'll add it then, nevertheless. |
This adds hrtime() to provide access to the system's high-resolution timer. Fix hrtime() proto Merge used parts of timer.h library into hrtime.c Change hrtime return to double, improve feature checking Add hrtime support for HP-UX Add UNEXPECTED wrapping for hrtime failures Fix bogus elif Fix other bogus elif
… return array [s, ns] Adjust tests Move and rename test Fix proto comment Return array by default Fix 32-bit and some more Still provide symbols even if functionality is unavailable Change typename Add AIX specific pieces, untested Improve 32-bit side once more Rework macros Few renames Add comment Remove unused code, a few other small things Add comment ws Reword
about QueryPerformanceCounter usage as per doc
After some tests and research, i've turned the QPC part back to double arithmetics. It turns out, that QPC is still not 100% reliable when it comes to virtualization. As a reference, here's among others the link like https://www.virtualbox.org/ticket/11951. I was also able to find a machine where QPC was resetting about every 2 hours, that's anything but monotonic. The double arithmetic mitigates it by using the compiler magic with type conversions Thanks. |
ext/standard/hrtime.c
Outdated
if (UNEXPECTED(get_as_num)) { | ||
PHP_RETURN_HRTIME(t); | ||
} else { | ||
array_init(return_value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you may use array_init_size and zend_hash_real_init to make it a packed array
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks for the tip!
Merged as 8349732. Thanks everyone! |
Firefox/Chrome recently removed high-performance timers because they can be abused for Spectre exploits in multitenant environments. It's a very strange time to be introducing a new high-performance timer API. Is there any potential security impact of having a high-performance timer API available? e.g. shared hosting environments? |
@mappu could you give more info on that, please? Thanks. |
@weltling "Spectre" attacks can read information from other processes on the machine, by biasing the CPU branch predictor and then timing how long some operations take. It's a major vulnerability for web applications because the vulnerability can be exploited from Javascript. In that case, a website running malicious javascript could read sensitive data from other tabs, or from other OS processes. Firefox [1], Edge [2], and Chrome [3] all issued emergency security patches this week, to reduce the accuracy of their high-resolution timer API ( It's not clear whether a better mitigation will be available in the future, or if this is going to be permanent on current CPUs. I suspect the same attack, or a similar attack, would also apply to PHP in shared hosting environments. Maybe it's not possible, but in a post-Spectre world, high-resolution timers must be treated as a security-sensitive issue. |
@mappu thanks for more info. Basically, anytime one comes from the holiday, something horrible has happened :) Talking seriously - the real mitigation seems unavoidable in first place by hardware and OSes, that's where the focus is. The mitigation in browsers is of course the necessary immediate step to solve the issue right now in the released software. This patch is master only and has time to see what happens further. Today, 5 day after the disclosure, solutions are being worked on, in kernels and compilers. Some distros already provide kernel patches, see for example SUSE, also gcc and clang already have patches. So it is clear a mitigation stands already. But in general, this kind of vulnerability is likely only to solve at the very low level, not by the applications. In the end, there is no reason for panic, but of course a good reason to stay tuned as there are other time sources based on gettimeofday(), etc. and modules in many other scripting languages, etc. Thanks. |
Shared hosting providers may still disable this function, just like they currently disable functions that they consider dangerous for shared hosting use. That said, the reason why this functionality was originally requested, was because AMP needed a monotonic (but not high-resolution) clock source. Maybe it would be better to separate this into two features, so one will not be affected if the other has to be disabled for security reasons. |
I thought about that, too. At the start it was also about high resolution and no Spectre issue known ofc. Basically it doesn't hurt to revert this patch and reintroduce it in another form. Fun enough - same APIs would be used for just monotonic+milliseconds. Probably should introduce just In worst case - yes, hosters can disable these functions. Note that microtime() is already exposed, if disabled - it would likely break some apps. But it really stands in the dark right now - if a program can deliver high resolution times, it's not automatically suspicious. Also it'd still require proof it's possible with PHP, or say another language like Python, etc. Node's hrtime() function uses similar APIs. Without a global fix to OSes and hardware, not only shared hostings but also containers and cloud are vulnerable. There are tons of software, that could potentially be called suspicious otherwise. Thanks. |
One solution on linux could be to fall back down to |
Probably that doesn't help much. As far i understood, the concern of @mappu is, that PHP could potentially provide an attack possibility. PHP itself, as any other program, would be vulnerable to an attack by a third party software. The poc in Javascript is based firstly on the fact, that the code is compiled to JIT, and secondly - that it relies on a certain browser feature. PHP doesn't compile to JIT yet, for one. From what i also could read today in many sources, a fix to this issue is only possible on the system and HW level, fe https://de.wikipedia.org/wiki/Spectre_(Sicherheitsl%C3%BCcke)#Gegenma%C3%9Fnahmen. Otherwise no process can be sure. Also, while containers are important today, there are still not only them. The functions can be disabled by admins, if needed. To compare, as the mitigations can introduce performance impacts, RedHat tells it'll be possible to enable them selectively. Clear, it's probably the worst issue the industry ever seen, but the head should still be cold. I'm nevertheless working on Thanks. |
We could also introduce INI_SYSTEM setting with maximum accuracy and have the code here that limits the accuracy of the timer. This way people running single-user but needing good accuracy can set it to high accuracy, and shared system admins could limit the potential for abusing timing oracles with restricting the accuracy while still keeping the monotonity. What do you think? |
A separate function could have less overhead, that's true. IMHO the idea with the ini sounds better as it gives more flexibility. As i'd see it, there'd be For the furture, this ini setting could be reused for the others like microtime(), if we ever need to do it. Also not sure about the ini setting name, please suggest. Also need to check for possible cast issues, etc. Thanks. |
I was doing some checks now, and seems the calculation is a bit more complicated. Actually for an INI it needs two settings - one is for precision and the other for unit. Say one could deliver nanosecond accuracy still, but the precision would be 20us. Alternatively, the INI could be just like "hrtime.low_resolution" which we could still set to something like 20us or 1ms, etc. In that case, perhaps a separate function would be more clear. Thanks. |
I think it should be something like |
Ok, this seems the simplest variant. I probably went too complicated way of thought. To think, like in the 20000 example - one could first reduce to an arbitrary unit, and then round to a given interval. Fe what is done at Mozilla is first reducing to ms, then again to nearest 20us. But that's actually a specification JS has to follow. I'm now working straight to this simple solution then - Thanks. |
The recently discovered security flaw Spectre requires a high resolution timer. To the today's knowledge, PHP can't be used to create an attack for this flaw. Still some concerns were raised, that there might be impact in shared hosting environments. This patch adds a possibility to reduce the timer resolution by an ini setting, thus giving administrators full control. Especially, as the flaw was also demonstrated by an abuse of the JS engine in a browser, Firefox reduced several time sources to 20us. Any programming language, that doesn't compile to JIT, won't be able to produce an attack vector for Meltdown and Spectre, at least by todays knowledge. There are also other factors that say that the security concern on the hrtime feature is to the big part not justified, still we aim JIT in the future. Thus, adding a possibility to control the timer resolution is a good and small enough tradeoff for safety and future.
OK, i've pushed c3717d9 and reverted it. We're too short in time after the discovery, there should be a better discussion after some time where the world have seen fixes to Meltdown/Spectre. I think it's sufficient to put this topic on hold and resurrect it anyway in time so a mitigation, if needed, lands in 7.3. Thanks. |
For even higher precision, it would be great to add |
Please add to $_SERVER array:
$_SERVER['REQUEST_HRTIME_FLOAT'] returns hrtime(true) of script start |
what about just the monotonic part for getting time? |
The implementation is based on the work started in #2368, with some reworks. This PR gets more focus on portability and solves issues in the predecessor as seems it's abandoned now. Besides benchmarking, the implmented function is suitable for other situations where monotonic timer is needed.
The delivered time starts at some uncertain point in the past. The time source is monotonic and isn't in any way adjustable or related to time of day. Several platform specific details are implemented. The signature is
mixed hrtime([bool as_num])
. By default, it delivers an array of the form [seconds, nanoseconds]. If the optional argument was passed, the return value is the number of nanoseconds as int on 64-bit or float on 32-bit. Some additional APIs can be implemented later, likehrtime_cmp
andhrtime_diff
.Thanks.