- newkey = xmalloc(sizeof(*newkey));
+ newkey = xcalloc(1, sizeof(*newkey));
The only change is that the allocated memory is zeroed, right? (Just wondering if I'm missing something.) - newkey = xmalloc(sizeof(*newkey));
+ newkey = xcalloc(1, sizeof(*newkey));
The only change is that the allocated memory is zeroed, right? (Just wondering if I'm missing something.)The newkey struct in turn contains a bunch of OpenSSL context structures, and one of these includes a cleanup callback pointer for the MAC (message authentication code) in use. For most ciphers this was being initialised later but AES-GCM provides message integrity without the need for an external MAC and in this case the MAC context was being left uninitialised.
When the newkeys struct was later being cleaned up as part of a rekeying operation, the cleanup callback was called. It's address was whatever happened to be on the heap when this allocation occurred.
It certainly appears exploitable using standard techniques, though it may be complicated somewhat by OpenSSH's privilege separation architecture.
Is calloc(1, size) considered idiomatic for zeroing memory? While I haven't used C much lately, back when I did I don't think I realized this, and could see myself at least considering reverting the change during "code cleanup".
And besides, exposing a latent bug is a good thing, no?
* If you malloc something very large, on Linux at least (I don't know about the BSDs), it doesn't necessarily get allocated, and only requests memory from the system when you actually access it. It's a valid pattern to, say, malloc a large amount of memory, pass it to your own allocator as a pool, and use what you need from it. If you go and zero all that memory, then you've accessed it all, resulting in much increased memory usage and maybe OOM kills. (You can argue this is an issue with the Linux overcommit system, not with malloc, but that's the system we have today and we'd need to account for it.)
* Slow code is a deterrent for people deploying crypto. If it takes 5 milliseconds for an HTTP response and 50 for an HTTPS handshake, people are going to be hesitant about deploying HTTPS, or do something silly like use it to protect only the initial login. So you do need to worry about the time that your crypto takes as an operational concern for deployment, and balance that off against hardening and paranoia. Favoring either extreme too much results in less overall security.
* There are very few languages that are truly "appropriate" for crypto algorithms. Even C only kind of counts, and you have to be very careful about how you write it. You ideally need promises from the language that certain operations are constant-time and that the optimizer is going to do what you want and neither more nor less. Most of the languages I can think of that only allocate you zeroed memory also include risks on the order of garbage collection being triggered in the middle of your algorithm depending on what your secret data is, so that makes them definitely unsuitable. And most of the non-toy crypto libraries in higher-level languages end up being bindings of carefully-tuned C libraries, and in that sense _do_ quasi-unintentionally expose a way to allocate uninitialized memory, by going through the C allocation routines.
The caveat I would want everyone to be aware of is that if the object is a struct that is initialized member-wise, malloc can still be dangerous, because if a member is subsequently added to the struct, one would have to manually review all points where the struct type is allocated to add the initialization for the new member. So I would approve of malloc in such a case only if there were only one place in the program that allocated that struct type.
I'm talking about the crypto code itself. If you've written your own allocator, I agree that different rules apply for the allocator code.
You do realize that even a C program running on a modern kernel can be preempted? Unless you are running it as a high-priority real-time process on a real-time hardened kernel, and, OpenSSL is giving you hard-real-time guarantees.
In other words, there may not be a good language, but some languages (or runtimes, really) are certainly worse than others.
if (newkey->callback != NULL) {
newkey->callback()
}
If newkey structure was not zeroed, then the callback would have had some contents from previous use and the NULL check would have passed and some unintended memory position would be called.The point is that a targeted malicious attack would try and ensure that those bytes were not NUL and that it ended up calling malicious injected code on the target server.
"An integer constant expression with the value 0, or such an expression cast to type void * , is called a null pointer constant. If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function."
(Edit: the asterisk.)
I'll just be flamboyant now and claim that on all currently relevant platforms calloc'ing the memory of all supported numerical and pointer types will give you the numerical value 0 and also the NULL pointer.
Can someone name any CPU or microcontroller currently being produced that violates that statement? (e.g. uint64_t x; calloc(1,sizeof(x)) and void *p; calloc(1, sizeof(x)) or maybe memset(x,'0',sizeof(x))... and still x != 0 and p != NULL)? Even for gross missalignment? What about DSP platforms that don't have a useable byte-addressible memory? GPUs?
Note that your memset call is totally bogus. '0' is not 0. You may have meant '\0', but plain 0 is perfectly legible. :)
Just FYI, the word you probably mean is 'moot'.