-
Notifications
You must be signed in to change notification settings - Fork 653
Use OpenBSD implementation for uv_fs_mkdtemp on Windows #1402
Conversation
template_part[4] = letters[v % 62]; | ||
v /= 62; | ||
template_part[5] = letters[v % 62]; | ||
for (i = 1; i <= num_x; ++i) |
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.
Replace this for with the previous "wcsncmp(&req->pathw[len - 6], L"XXXXXX", 6)", and or it in the len < num_x test.
I don't think we need arc4random here, there are nough number of tries to make it not collide, I guess. (famous last words) |
Made some comments, but generally LGTM/ @sam-github, @bnoordhuis, can you PTAL? |
@saghul thanks for review, pushed fixes. |
LGTM. I'll wait for others to chime in before merging. Thanks for handling this so quickly @Hinidu, much appreciated. |
fd = _wmkdir(req->pathw); | ||
tries = TMP_MAX; | ||
do { | ||
v = (((uint64_t)rand()) << 32) | rand(); |
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 I put a space between (uint64_t)
and rand()
?
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.
Oh, yeah, good catch.
I'm very glad. It was such a shame for me, so that I actually couldn't do anything else :-) |
fd = _wmkdir(req->pathw); | ||
tries = TMP_MAX; | ||
do { | ||
v = (((uint64_t) rand()) << 32) | rand(); |
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 has weak entropy. rand() on Windows returns a value between 0 and 32768. That's 15 bits of entropy so that means v
will have at most 15 * 2 = 30 bits of entropy.
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 assume the original code used arc4random()? I think the Windows equivalent is CryptGenRandom() but that API is somewhat cumbersome to use.
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.
How about using uv_hrtime() ^ _getpid();
as we did before?
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 upper bound of entropy in this case is 62^6 (< 2^36). So the best and simplest implementation would be:
v = (((uint64_t) rand()) << 30) | (rand() << 15) | rand();
What do you think?
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.
That looks reasonable to me. Arguably better than time + pid because that's fairly predictable and/or externally observable.
EDIT: Assuming the PRNG has been seeded, of course.
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.
EDIT: Assuming the PRNG has been seeded, of course.
How and when I should do this?
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 don't think that should be the responsibility of libuv. Rather, I would add a note to the documentation for uv_fs_mkdtemp() that it's the responsibility of the libuv user to ensure that srand() has been called.
It's either that or:
- Switch to a CSPRNG like CryptGenRandom(), or
- Make the entropy the responsibility of the user, by means of a user-provided buffer or callback.
Option 1 makes the API easier to use but the user won't have much influence on the quality of the (CS)PRNG.
Option 2 makes the API more cumbersome but is useful in applications like node.js that have access to a cross-platform, high-quality entropy pool (OpenSSL's entropy pool in node's case.)
I don't have a preference, I'm just outlining the possibilities that I can think of.
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 don't think that should be the responsibility of libuv. Rather, I would add a note to the documentation for uv_fs_mkdtemp() that it's the responsibility of the libuv user to ensure that srand() has been called.
I'd go with this.
@bnoordhuis @saghul pushed first solution of the problem with entropy. |
@sam-github thanks for suggestion. Updated documentation of |
What if we use both uint64_t x = uv_hrtime() ^ getpid();
while (!success) {
uint64_t y = (((uint64_t) rand()) << 30) | (rand() << 15) | rand();
success |= try_create_directory(x ^ y);
} I think it would reduce the need to call What do you think @saghul, @bnoordhuis? |
@Hinidu The problem with that approach is if rand() also returns Real-world usage won't be that black and white, of course, but if there's some correlation between rand(), hrtime() and getpid(), then instead of strengthening the entropy, you might end up weakening it. |
@bnoordhuis thanks for interesting observation! |
Sounds good, thanks Pavel!
|
len = strlen(req->path); | ||
wcstombs((char*) req->path + len - 6, template_part, 6); | ||
wcstombs((char*) req->path + len - num_x, ep - num_x, num_x); | ||
SET_REQ_RESULT(req, 0); | ||
return; | ||
} else if (errno != EEXIST) { | ||
SET_REQ_RESULT(req, -1); |
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 request result is supposed to have the actual error code and not just -1
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 think this is the prefered way because SET_REQ_RESULT will handle this
https://github.com/Hinidu/libuv/blob/openbsd_mkdtemp/src/win/fs.c#L54
Pushed commit with CryptGenRandom(). |
LGTM with the caveat that I'm not really a Windows programmer. If I could make two suggestions:
|
It bothers me too.
How is it possible? The handle is the local variable. I thought that another thread should not be able to clobber it. |
I think I found pretty good solution: diff --git a/src/win/fs.c b/src/win/fs.c
index 511952f..c412db5 100644
--- a/src/win/fs.c
+++ b/src/win/fs.c
@@ -748,10 +748,9 @@ void fs__mkdtemp(uv_fs_t* req) {
tries = TMP_MAX;
do {
if (!CryptGenRandom(hCryptProv, sizeof(v), (BYTE*) &v)) {
- CryptReleaseContext(hCryptProv, 0);
SET_REQ_WIN32_ERROR(req, GetLastError());
- return;
+ break;
}
cp = ep - num_x;
@@ -763,18 +762,18 @@ void fs__mkdtemp(uv_fs_t* req) {
if (_wmkdir(req->pathw) == 0) {
len = strlen(req->path);
wcstombs((char*) req->path + len - num_x, ep - num_x, num_x);
- CryptReleaseContext(hCryptProv, 0);
SET_REQ_RESULT(req, 0);
- return;
+ break;
} else if (errno != EEXIST) {
- CryptReleaseContext(hCryptProv, 0);
SET_REQ_RESULT(req, -1);
- return;
+ break;
}
} while (--tries);
CryptReleaseContext(hCryptProv, 0);
- SET_REQ_RESULT(req, -1);
+ if (tries == 0) {
+ SET_REQ_RESULT(req, -1);
+ }
} |
Looks good, though we need to set some error in case all tries are exhausted. |
In this case |
Ah, right. No, that's OK. |
Force pushed cleanup extraction. |
You can add the assert just to be deffensive :-) |
Ok, force-pushed assert. |
Not normally, no, but a HANDLE is just a pointer to an object. If another thread manages to get a pointer to the same object (accidentally or otherwise), then things can go badly wrong. The assert() is a bit of defensive programming against such bugs. |
Thanks for explanation! I've already pushed |
Thanks @Hinidu, I'll have a final look tonight and land it if everything checks out, which looks that way. |
Thank you and all others, your suggestions were very helpful. Will wait landing or other comments. |
Looks OK to me, thanks for adding a doc comment. |
Thanks Pavel! Landed in a669f21. PS: I added an include for wincrypt.h, otherwise it wouldn't work on MinGW. |
Thanks Saúl! |
As stated by @sam-github libuv can't use glibc's modified code because glibc licensed under LGPL, but libuv under MIT. I apologize again, it was my fault because of ignorance.
So in this PR I ported OpenBSD version.
Main differences with original:
tries = TMP_MAX
instead oftries = INT_MAX
. Though I'm not sure what is the best value.rand
instead ofarc4random
. Though I found portable arc4random.c licensed under 3-clause BSD license. So it is possible to use it if appropriate.