Merge pull request #478 from OrdnanceNetworks/fix-low-entrypy-at-boot

ordnance: Initialize Linux Random Number Generator (RNG) early
This commit is contained in:
Jeffrey Townsend
2018-12-26 09:15:38 -08:00
committed by GitHub
2 changed files with 233 additions and 1 deletions

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python
"""
Distributed under MIT License.
See LICENSE file for full license text.
"""
import fcntl
import struct
import sys
import os
import hashlib
import logging
import argparse
RNDADDENTROPY = 0x40085203 # from linux/random.h
def sha512sum(digest, fileName, blockSize = 16 * 1024):
"""
Take SHA512 digest from file given by it's name contents.
File opened in os.O_NONBLOCK mode to support FIFOs and
special files like /dev/kmsg. Read data from files in
blockSize (default 16kB) chunks to keep memory usage at
minimum when used with large files.
Return True on success and False otherwise.
"""
def read():
try:
return fp.read(blockSize)
except IOError:
return b''
#end def
if digest is None:
return False
try:
fp = open(fileName, 'rb')
except IOError:
return False
flags = fcntl.fcntl(fp, fcntl.F_GETFL) | os.O_NONBLOCK
if fcntl.fcntl(fp, fcntl.F_SETFL, flags):
fp.close()
return False
for block in iter(read, b''):
digest.update(block)
fp.close()
return True
def sha512(digest, fileName):
"""
Computes sha512sum() optionally logging status.
For internal use only.
Return 1 on success and 0 on failure.
"""
rc = sha512sum(digest, fileName)
logging.debug("SHA512 (%s) = %s", fileName, 'ok' if rc else 'fail')
return int(rc)
def add_entropy(digest, fileName = "/dev/urandom"):
"""
Adds digest.digest_size * 8 bits of entropy using ioctl(RNDADDENTROPY, ...)
to increase entropy count if fileName is a character special file node and
CAP_SYS_ADMIN is available (otherwise IOError is raised by fnctl.fnctl()).
Writes entropy to fileName if IOError exception is raised by fnctl.fnctl()
or fileName is a regular file. Update entropy pool without incrementing
entropy count if fileName is special character device like "/dev/urandom".
"""
try:
fp = open(fileName, 'ab')
except IOError as e:
return None, str(e)
size = digest.digest_size
digest = digest.digest()
method = None
err = None
do_ioctl = not os.path.isfile(fileName)
try:
if do_ioctl:
# from random(4):
#
# struct rand_pool_info {
# int entropy_count;
# int buf_size;
# __u32 buf[0];
# };
#
fmt = "ii{:d}s".format(size)
rand_pool_info = struct.pack(fmt, size * 8, size, digest)
fcntl.ioctl(fp, RNDADDENTROPY, rand_pool_info)
else:
raise IOError()
except IOError:
try:
fp.write(digest)
except IOError as e:
err = str(e)
else:
method = "write"
else:
method = "ioctl"
return method, err
def init():
prog_name = os.path.splitext(os.path.basename(__file__))[0]
if not prog_name:
prog_name = "initrng"
parser = argparse.ArgumentParser(description = 'Linux RNG early init')
# loglevel
loglevels = {
'crit' : 'CRITICAL',
'err' : 'ERROR',
'warn' : 'WARNING',
'info' : 'INFO',
'debug': 'DEBUG',
}
parser.add_argument('-l', '--loglevel', default = 'info',
choices = list(loglevels.keys()),
help = 'set program loging severity (level)')
# entropy_files
dflt_entropy_files = [
"/proc/timer_list",
"/proc/buddyinfo",
"/proc/interrupts",
"/proc/softirqs",
]
sched_debug = "/proc/sched_debug"
if os.path.isfile(sched_debug):
dflt_entropy_files.append(sched_debug)
else:
dflt_entropy_files.append("/proc/schedstat")
parser.add_argument('-e', '--entropy-file', default = [],
action = 'append', dest = 'entropy_files', type = str,
help = 'files to use as source of entropy ({:s})'.format(', '.join(dflt_entropy_files)))
# repeat
dflt_repeat = 8
parser.add_argument('-r', '--repeat', default = dflt_repeat,
action = 'store', type = int,
help = 'repeat entropy updates # times (default {:d})'.format(dflt_repeat))
# output
dflt_output = "/dev/urandom"
parser.add_argument('-o', '--output', default = dflt_output,
action = 'store', type = str,
help = 'file to output entropy (default "{:s}")'.format(dflt_output))
args = parser.parse_args()
logging.basicConfig(format = "{:s}: %(message)s".format(prog_name),
level = getattr(logging, loglevels[args.loglevel]))
# adjust entropy_files
if not args.entropy_files:
args.entropy_files = dflt_entropy_files
logging.debug("using default list of files as entropy source")
# adjust repeat
repeat = args.repeat
if repeat <= 0:
repeat = 1
elif repeat > 65536:
repeat = 65536
if repeat != args.repeat:
logging.debug("adjust repeat count from %d to %d", args.repeat, repeat)
args.repeat = repeat
# truncate entropy file
output = args.output
try:
with open(output, 'wb'):
pass
except IOError:
logging.debug("entropy file '%s' isn't writable", output)
args = None
else:
logging.debug("entropy file '%s' is writable", output)
return args
if __name__ == '__main__':
args = init()
if args is None:
logging.debug("fail to adjust/validate args")
sys.exit(1)
logging.info("Linux Random Number Generator (RNG) early init")
entropy_files = args.entropy_files
repeat = args.repeat
for x in range(1, repeat + 1):
digest_sha512 = hashlib.sha512()
i = 0
for f in entropy_files:
i += sha512(digest_sha512, f)
if not i:
logging.debug("digest error for all entropy files, step %d", x)
continue
method, err = add_entropy(digest_sha512, args.output)
if err:
logging.debug("error seeding Linux RNG, step %d: %s", x, err)
else:
logging.debug("seeded Linux RNG, method '%s', step %d", method, x)

View File

@@ -47,8 +47,10 @@ case "$(stat -f -c "%T" /tmp)" in
;;
esac
# Grab cmdline settings
# Initialize RNG early
python /bin/initrng.py
# Grab cmdline settings
touch /etc/onl/boot-config
tr -s " " "\n" </proc/cmdline |
while read -r l; do