Skip to content
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

Use getentropy from std::random_device, avoiding FS usage of /dev/urandom #12240

Merged
merged 7 commits into from
Sep 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ See docs/process.md for how version tagging works.

Current Trunk
-------------
- Add `getentropy` in `sys/random.h`, and use that from libc++'s
`random_device`. This is more efficient, see #12240.

2.0.4: 09/16/2020
-----------------
Expand Down
41 changes: 41 additions & 0 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -2845,6 +2845,47 @@ LibraryManager.library = {
endgrent: function() { throw 'endgrent: TODO' },
setgrent: function() { throw 'setgrent: TODO' },

// random.h

// TODO: consider allowing the API to get a parameter for the number of
// bytes.
$getRandomDevice: function() {
if (typeof crypto === 'object' && typeof crypto['getRandomValues'] === 'function') {
// for modern web browsers
var randomBuffer = new Uint8Array(1);
return function() { crypto.getRandomValues(randomBuffer); return randomBuffer[0]; };
} else
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
// for nodejs with or without crypto support included
try {
var crypto_module = require('crypto');
// nodejs has crypto support
return function() { return crypto_module['randomBytes'](1)[0]; };
} catch (e) {
// nodejs doesn't have crypto support
}
}
#endif // ENVIRONMENT_MAY_BE_NODE
// we couldn't find a proper implementation, as Math.random() is not suitable for /dev/random, see emscripten-core/emscripten/pull/7096
#if ASSERTIONS
return function() { abort("no cryptographic support found for randomDevice. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };"); };
#else
return function() { abort("randomDevice"); };
#endif
},

getentropy__deps: ['$getRandomDevice'],
getentropy: function(buffer, size) {
if (!_getentropy.randomDevice) {
_getentropy.randomDevice = getRandomDevice();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it makes sense to do this memorization in getRandomDevice itself?

Also if you made _getentropy.randomDevice into $randomDevice then you could maybe save a few bytes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried a few ways, but it's hard to reduce the code size below what it currently is.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we not memoize the getRandomDevice inner function? And why not use global for the memoization?

Copy link
Member Author

@kripken kripken Sep 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see getRandomDevice as getting a random device. It returns a new function each time.

In theory each of those functions could have its own internal DRNG or such (see discussion in #10068).

Callers can memoize when it makes sense. In the FS code we do, and in getentropy it does.

I do agree that if memoizing in getRandomDevice itself were smaller, it would be worth figuring out a proper name for that different API. I can't get it any smaller, though...

for (var i = 0; i < size; i++) {
{{{ makeSetValue('buffer', 'i', '_getentropy.randomDevice()', 'i8') }}}
}
return 0;
},

// ==========================================================================
// emscripten.h
// ==========================================================================
Expand Down
30 changes: 2 additions & 28 deletions src/library_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

mergeInto(LibraryManager.library, {
$FS__deps: ['$setErrNo', '$PATH', '$PATH_FS', '$TTY', '$MEMFS',
$FS__deps: ['$setErrNo', '$getRandomDevice', '$PATH', '$PATH_FS', '$TTY', '$MEMFS',
#if LibraryManager.has('library_idbfs.js')
'$IDBFS',
#endif
Expand Down Expand Up @@ -1344,33 +1344,7 @@ FS.staticInit();` +
FS.mkdev('/dev/tty', FS.makedev(5, 0));
FS.mkdev('/dev/tty1', FS.makedev(6, 0));
// setup /dev/[u]random
var random_device;
if (typeof crypto === 'object' && typeof crypto['getRandomValues'] === 'function') {
// for modern web browsers
var randomBuffer = new Uint8Array(1);
random_device = function() { crypto.getRandomValues(randomBuffer); return randomBuffer[0]; };
} else
#if ENVIRONMENT_MAY_BE_NODE
if (ENVIRONMENT_IS_NODE) {
// for nodejs with or without crypto support included
try {
var crypto_module = require('crypto');
// nodejs has crypto support
random_device = function() { return crypto_module['randomBytes'](1)[0]; };
} catch (e) {
// nodejs doesn't have crypto support
}
} else
#endif // ENVIRONMENT_MAY_BE_NODE
{}
if (!random_device) {
// we couldn't find a proper implementation, as Math.random() is not suitable for /dev/random, see emscripten-core/emscripten/pull/7096
#if ASSERTIONS
random_device = function() { abort("no cryptographic support found for random_device. consider polyfilling it if you want to use something insecure like Math.random(), e.g. put this in a --pre-js: var crypto = { getRandomValues: function(array) { for (var i = 0; i < array.length; i++) array[i] = (Math.random()*256)|0 } };"); };
#else
random_device = function() { abort("random_device"); };
#endif
}
var random_device = getRandomDevice();
FS.createDevice('/dev', 'random', random_device);
FS.createDevice('/dev', 'urandom', random_device);
// we're not going to emulate the actual shm device,
Expand Down
14 changes: 14 additions & 0 deletions system/include/compat/sys/random.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#ifdef __cplusplus
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment about why we are adding this.. how it helps?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. The __cplusplus pattern? Lots of headers have this, like these siblings in the same dir:

system/include/compat/malloc.h
system/include/compat/netdb.h
system/include/compat/stdarg.h
system/include/compat/stdlib.h
system/include/compat/string.h
system/include/compat/time.h
system/include/compat/xlocale.h

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry no I meant a comment about why this header is added by emscripten when its not part of musl? What is the purpose of adding getransom? I guess that answer is something like "to get ransom bytes without depending on the filesytem stuff which is good for code size and complexity in the generated output" .. or something along those lines :)

Copy link
Member Author

@kripken kripken Sep 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, adding now.

The bigger question is maybe why isn't this in musl, given it seems to be a Linux unistd.h thing..? Looks like musl has added it meanwhile actually, ifduyue/musl@82f1768#diff-2fc78454dd4e341aace02870b1e0c8c9 - but using a syscall, which would adds some unnecessary redirection for us, and it's not where libc++ looks for it (in unistd as opposed to sys/random).

extern "C" {
#endif

// This is used by libc++ as an efficient way to get high-quality random data
// (more efficiently than via the filesystem using /dev/urandom).
// Upstream musl added support for this, so we can switch to that, but it isn't
// where libc++ looks for it (which is here and not unistd.h), and it uses a
// syscall which is unnecessary indirection for us.
int getentropy(void *buffer, size_t length);

#ifdef __cplusplus
}
#endif
2 changes: 1 addition & 1 deletion system/include/libcxx/__config
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@
// random data even when using sandboxing mechanisms such as chroots,
// Capsicum, etc.
# define _LIBCPP_USING_ARC4_RANDOM
#elif defined(__Fuchsia__) || defined(__wasi__)
#elif defined(__Fuchsia__) || defined(__wasi__) || defined(__EMSCRIPTEN__)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention in system/lib/libcxx/readme.txt?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, added. Also I added a mention in the changelog.

# define _LIBCPP_USING_GETENTROPY
#elif defined(__native_client__)
// NaCl's sandbox (which PNaCl also runs in) doesn't allow filesystem access,
Expand Down
2 changes: 2 additions & 0 deletions system/lib/libcxx/readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ Local modifications are marked with the comment: 'XXX EMSCRIPTEN'
3. Define _LIBCPP_ELAST in libcxx/include/config_elast.h

4. Set init_priority of __start_std_streams in libcxx/iostream.cpp

5. Use _LIBCPP_USING_GETENTROPY (like wasi)
2 changes: 1 addition & 1 deletion tests/core/test_random_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ auto main()
try
{
std::random_device rd;
std::cout << "random was read" << "\n";
std::cout << rd() << ", a random was read\n";
}
catch( const std::exception& e )
{
Expand Down