Skip to contentSkip to conversation

random: introduce getrandom(2) system call]

RFC PATCH In review Theodore Ts'o 2 participants 3 messages Jul 18, 2014 1 message not in the archive

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

RFC PATCH 1/1 random: introduce getrandom(2) system call]

Apologies, screwed up the e-mail address again.

- Ted

The getrandom(2) system call was requested by the LibreSSL Portable developers. It is analoguous to the getentropy(2) system call in OpenBSD.

The rationale of this system call is to provide resiliance against file descriptor exhaustion attacks, where the attacker consumes all available file descriptors, forcing the use of the fallback code where /dev/[u]random is not available. Since the fallback code is often not well-tested, it is better to eliminate this potential failure mode entirely.

The other feature provided by this new system call is the ability to request randomness from the /dev/urandom entropy pool, but to block until at least 128 bits of entropy has been accumulated in the /dev/urandom entropy pool. Historically, the emphasis in the /dev/urandom development has been to ensure that urandom pool is initialized as quickly as possible after system boot, and preferably before the init scripts start execution.

This is because changing /dev/urandom reads to block represents an interface change that could potentially break userspace which is not acceptable. In practice, on most x86 desktop and server systems, in general the entropy pool can be initialized before it is needed (and in modern kernels, we will printk a warning message if not). However, on an embedded system, this may not be the case. And so with this new interface, we can provide the functionality of blocking until the urandom pool has been initialized. Any userspace program which uses this new functionality must take care to assure that if it is used during the boot process, that it will not cause the init scripts or other portions of the system startup to hang indefinitely.

SYNOPSIS

	#include <linux/random.h>

	int getrandom(void *buf, size_t buflen, unsigned int flags);

DESCRIPTION
The system call getrandom() fills the buffer pointed to by buf with up to buflen random bytes which can be used to seed user space random number generators (i.e., DRBG's) or for other cryptographic processes. It should not be used Monte Carlo simulations or for other probabilistic sampling applications.

If the GRND_RANDOM flags bit is set, then draw from the /dev/random pool instead of the /dev/urandom pool. The /dev/random pool is limited based on the entropy that can be obtained from environmental noise, so if there is insufficient entropy, the requested number of bytes may not be returned. If there is no entropy available at all, getrandom(2) will either block, or return an error with errno set to EAGAIN if the GRND_NONBLOCK bit is set in flags.

If the GRND_RANDOM bit is not set, then the /dev/urandom pool will be used. Unlike using read(2) to fetch data from /dev/urandom, if the urandom pool has not been sufficiently initialized, getrandom(2) will block or return -1 with the errno set to EGAIN if the GRND_NONBLOCK bit is set in flags.

The getentropy(2) system call in OpenBSD can be emulated using the following function:

int getentropy(void *buf, size_t buflen) {
int ret;

if (buflen > 256)
goto failure;

ret = getrandom(buf, buflen, 0); if (ret < 0)
return ret;
if (ret != buflen) failure:
errno = EIO; return -1;
}
return 0;
}

RETURN VALUE
On success, the number of bytes that was filled in the buf is returned. This may not be all the bytes requested by the caller via buflen if insufficient entropy was present in the /dev/random pool, or if the system call was interrupted by a signal.

On error, -1 is returned, and errno is set appropriately.

ERRORS
EINVAL An invalid flag was passed to getrandom(2)

EFAULT buf is outside the accessible address space.

EAGAIN The requested entropy was not available, and the
getentropy(2) would have blocked if GRND_BLOCK flag was set.

EINTR While blocked waiting for entropy, the call was
interrupted by a signal handler; see the description of how interrupted read(2) calls on "slow" devices are handled with and without the SA_RESTART flag in the signal(7) man page.

NOTES
For small requests (buflen <= 256) getrandom(2) will not return EINTR when reading from the urandom pool once the entropy pool has been initialized, and it will return all of the bytes that have been requested. This is the recommended way to use getrandom(2), and is designed for compatibility with OpenBSD's getentropy() system call.

However, if you are using GRND_RANDOM, then getrandom(2) may block until the entropy accounting determines that sufficient environmental noise has been gathered such that getrandom(2) will be operating as a NRBG instead of a DRBG for those people who are working in the NIST SP 800-90 regime. Since it may block for a long time, these guarantees do *not* apply. The user may want to interrupt a hanging process using a signal, so blocking until all of the requested bytes are returned would be unfriendly.

For this reason, the user of getrandom(2) MUST always check the return value, in case it returns some error, or if fewer bytes than requested was returned. In the case of !GRND_RANDOM and small request, the latter should never happen, but the careful userspace code (and all crypto code should be careful) should check for this anyway!

---

The change in the v3 version of the commit was to eliminate potential short reads and EINTR returns when reading from urandom (once the urandom pool is initialized). This was based on comments and requests from Theo de Raadt. See the NOTES section in the suggested man page for a more in-depth discussion of the issues involved.

 arch/x86/syscalls/syscall_32.tbl  |  1 +
 arch/x86/syscalls/syscall_64.tbl  |  1 +
 drivers/char/random.c             | 42 ++++++++++++++++++++++++++++++++++++---
 include/linux/syscalls.h          |  3 +++
 include/uapi/asm-generic/unistd.h |  4 +++-
 include/uapi/linux/random.h       |  9 +++++++++
 6 files changed, 56 insertions(+), 4 deletions(-)
arch/x86/syscalls/syscall_32.tbl +1 -0
index d6b8679..f484e39 100644
@@ -360,3 +360,4 @@
 351	i386	sched_setattr		sys_sched_setattr
 352	i386	sched_getattr		sys_sched_getattr
 353	i386	renameat2		sys_renameat2
+354	i386	getrandom		sys_getrandom
arch/x86/syscalls/syscall_64.tbl +1 -0
index ec255a1..6705032 100644
@@ -323,6 +323,7 @@
 314	common	sched_setattr		sys_sched_setattr
 315	common	sched_getattr		sys_sched_getattr
 316	common	renameat2		sys_renameat2
+317	common	getrandom		sys_getrandom
 
 #
 # x32-specific system call numbers start at 512 to avoid cache impact
drivers/char/random.c +39 -3

96 lines not on this page · raw message

include/linux/syscalls.h +3 -0
index b0881a0..cd82f72f 100644
@@ -866,4 +866,7 @@ asmlinkage long sys_process_vm_writev(pid_t pid,
 asmlinkage long sys_kcmp(pid_t pid1, pid_t pid2, int type,
 			 unsigned long idx1, unsigned long idx2);
 asmlinkage long sys_finit_module(int fd, const char __user *uargs, int flags);
+asmlinkage long sys_getrandom(char __user * buf, size_t count,
+			      unsigned int flags);
+
 #endif
include/uapi/asm-generic/unistd.h +3 -1
index 3336406..2926b1d 100644
@@ -699,9 +699,11 @@ __SYSCALL(__NR_sched_setattr, sys_sched_setattr)
 __SYSCALL(__NR_sched_getattr, sys_sched_getattr)
 #define __NR_renameat2 276
 __SYSCALL(__NR_renameat2, sys_renameat2)
+#define __NR_getrandom 277
+__SYSCALL(__NR_getrandom, sys_getrandom)
 
 #undef __NR_syscalls
-#define __NR_syscalls 277
+#define __NR_syscalls 278
 
 /*
  * All syscalls below here should go away really,
include/uapi/linux/random.h +9 -0
index fff3528..3f93d16 100644
@@ -40,4 +40,13 @@ struct rand_pool_info {
 	__u32	buf[0];
 };
 
+/*
+ * Flags for getrandom(2)
+ *
+ * GRND_NONBLOCK	Don't block and return EAGAIN instead
+ * GRND_RANDOM		Use the /dev/random pool instead of /dev/urandom
+ */
+#define GRND_NONBLOCK	0x0001
+#define GRND_RANDOM	0x0002
+
 #endif /* _UAPI_LINUX_RANDOM_H */
Signature · 1 line
2.0.0

A message that is not in the archive (sent off-list or not yet received): [email protected]

#2 lore raw
Quoted 6 lines · On Friday 18 July 2014 08:56:06 Theodore Ts'o wrote:

The change in the v3 version of the commit was to eliminate potential short reads and EINTR returns when reading from urandom (once the urandom pool is initialized). This was based on comments and requests from Theo de Raadt. See the NOTES section in the suggested man page for a more in-depth discussion of the issues involved.

I think there is a problem with the completion:

drivers/char/random.c retry: · line 667

Quoted 8 lines
drivers/char/random.c +1 -0
@@ -657,6 +661,7 @@ retry:
                r->entropy_total = 0;
                if (r == &nonblocking_pool) {
                        prandom_reseed_late();
+                       complete_all(&urandom_initialized);
                        pr_notice("random: %s pool is initialized\n", r->name);
                }
        }

complete_all() sets the 'done' part of the completion to 'UINT_MAX/2', which is enough to ensure that everyone waiting for the completion at a given time will wake up, but a completion should not be reused without calling init_completion again after a call to complete_all().

Quoted 14 lines
drivers/char/random.c +14 -0
+SYSCALL_DEFINE3(getrandom, char __user *, buf, size_t, count,
+               unsigned int, flags)
+{
+       int r;
+
+       if (flags & ~(GRND_NONBLOCK|GRND_RANDOM))
+               return -EINVAL;
+
+       if (count > INT_MAX)
+               count = INT_MAX;
+
+       if (flags & GRND_RANDOM)
+               return _random_read(flags & GRND_NONBLOCK, buf, count);
+       if (flags & GRND_NONBLOCK) {

drivers/char/random.c const struct file_operations urandom_fops = { · line 1571

Quoted 10 lines
drivers/char/random.c +10 -0
+               if (!completion_done(&urandom_initialized))
+                       return -EAGAIN;
+       } else {
+               r = wait_for_completion_interruptible(&urandom_initialized);
+               if (r)
+                       return r;
+       }
+       return urandom_read(NULL, buf, count, NULL);
+}
+

However, here you can get called an arbitrary number of times. It seems entirely possible than an attacker can manage to call this function 2 billion times. Assuming a latency of 1 microsecond per syscall, that would take about half an hour. After that, you never again get any urandom data out of the syscall.

I think you are better off using a plain wait_event() here.

Arnd

#3 lore raw
Quoted 10 lines · Arnd Bergmann
Quoted 6 lines · On Friday 18 July 2014 08:56:06 Theodore Ts'o wrote:

The change in the v3 version of the commit was to eliminate potential short reads and EINTR returns when reading from urandom (once the urandom pool is initialized). This was based on comments and requests from Theo de Raadt. See the NOTES section in the suggested man page for a more in-depth discussion of the issues involved.

I think there is a problem with the completion...

Quoted 7 lines

However, here you can get called an arbitrary number of times. It seems entirely possible than an attacker can manage to call this function 2 billion times. Assuming a latency of 1 microsecond per syscall, that would take about half an hour. After that, you never again get any urandom data out of the syscall.

I think you are better off using a plain wait_event() here.

Nice catch, thanks!!

I'll rework the patch to use wait_event().

- Ted