Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why "cat /dev/urandom" hung my bash script?

Tags:

linux

bash

I'm using the following line to generate a random script:

export MY_VAR="nxf-$(cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 24 | head -n 1)"

This works fine, but when I include it in a BASH script and I execute it, the script execution hung.

The process tree show the following processes:

 4045 ?        S      0:00      bash .command.run
 4046 ?        R     22:38       \_ cat /dev/urandom
 4047 ?        S      1:03       \_ tr -dc a-zA-Z0-9
 4048 ?        S      0:34       \_ fold -w 24

It seems that urandom never exit. Why this happens ?

like image 288
pditommaso Avatar asked Apr 07 '15 13:04

pditommaso


2 Answers

You should never use cat with /dev/urandom. Nor should you use any utilities which are designed for text files.

/dev/urandom is a continuous stream of random data. It will never produce an end of file. Buffered reads will fill the read buffer, so even if you are piping the output of cat into some other program, the read won't be terminated until the pipe is closed.

None of that would be anything other than inefficient, except that when you read /dev/urandom, you are using up entropy (randomness), which is a precious resource. Once entropy is used up, /dev/urandom's output will be less random, which defeats the purpose. (More entropy will be collected, but it takes a while to build up.)

All of this goes double for /dev/random, because when it runs out of entropy, it usually blocks. (Except on OSs which make /dev/random a synonym for /dev/urandom.)

Consequently, you should always read exactly the amount of random data you need, and no more.

Apparently, you're aiming at 24 alphanumeric characters. There are 62 possible alphanumeric characters; it would simplify things greatly if you were willing to allow two other characters to bring the total to 64. In that case, you could produce 24 characters by extracting 18 bytes of randomness and passing it through a base64 encoder. To extract a precise amount of data, use dd, which is designed for the purpose:

dd bs=18 count=1 if=/dev/urandom | base64 | tr +/ _.

(The tr at the end translates the two non-alphanumeric characters produced by base64 into two different characters which are more filename-friendly. Just a suggestion.)

If you're determined to use precisely alphanumeric characters, you could use a rejection strategy similar to the one you're currently using, but based on the above. Unfortunately, it's not possible to predict exactly how much input you'll need in this case, so the simplest approach is to read a little bit extra, and retry in the rare case that you don't get enough:

# Here we produce 28 characters each time
until s=$(dd bs=21 count=1 if=/dev/urandom |
           LC_ALL=C tr -cd A-Za-z0-9)
      ((${#s} >= 24)); do :; done
# When the loop ends we have at least 24 characters; truncate
s=${s:0:24} 

If you don't have bash, you can replace ((${#s} >= 24)) with [ ${#s} -ge 24 ] and s=${s:0:24} with s=$(printf %.24s $s)

But if you're just trying to generate good random filenames, you should use mktemp, which allows you to specify a skeleton for the names, and also verifies that the generated name is not already present. See man mktemp.

like image 101
rici Avatar answered Oct 21 '22 12:10

rici


Actually cat /dev/urandom never ends on its own. But when head -1 reads first line, it exits, thus closing stdin and closing a pipe. OS raises SIGPIPE to fold is also exits, and so on, so cat /dev/urandom ends eventually.

In your case, something blocking SIGPIPE, i.e. trap can do that:

$ trap '' PIPE 
$ cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 24 | head -n 1
7FazO6mnsIow3ylkvEHB55jE
(hungs)

Try to re-enable it in subshell:

( trap - PIPE ; cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z0-9' | fold -w 24 | head -n 1 )
like image 27
myaut Avatar answered Oct 21 '22 12:10

myaut