linux installer script: Do not use the filesystem to pass the downloaded tarball to tar as it is possible for malicuous code running on the users computer to alter the download file between signature verification and calling tar to extract the files.

This commit is contained in:
Kovid Goyal 2014-03-04 07:35:48 +05:30
parent 893799249b
commit 10dfa877d2

View File

@ -7,7 +7,7 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>' __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
import sys, os, shutil, subprocess, re, platform, time, signal, tempfile, hashlib, errno import sys, os, shutil, subprocess, re, platform, signal, tempfile, hashlib, errno
import ssl, socket import ssl, socket
from contextlib import closing from contextlib import closing
@ -241,14 +241,13 @@ def clean_cache(cache, fname):
def check_signature(dest, signature): def check_signature(dest, signature):
if not os.path.exists(dest): if not os.path.exists(dest):
return False return None
m = hashlib.sha512() m = hashlib.sha512()
with open(dest, 'rb') as f: with open(dest, 'rb') as f:
raw = True raw = f.read()
while raw: m.update(raw)
raw = f.read(1024*1024) if m.hexdigest().encode('ascii') == signature:
m.update(raw) return raw
return m.hexdigest().encode('ascii') == signature
class URLOpener(urllib.FancyURLopener): class URLOpener(urllib.FancyURLopener):
@ -299,9 +298,10 @@ def download_tarball():
os.makedirs(cache) os.makedirs(cache)
clean_cache(cache, fname) clean_cache(cache, fname)
dest = os.path.join(cache, fname) dest = os.path.join(cache, fname)
if check_signature(dest, signature): raw = check_signature(dest, signature)
if raw is not None:
print ('Using previously downloaded', fname) print ('Using previously downloaded', fname)
return dest return raw
cached_sigf = dest +'.signature' cached_sigf = dest +'.signature'
cached_sig = None cached_sig = None
if os.path.exists(cached_sigf): if os.path.exists(cached_sigf):
@ -320,12 +320,13 @@ def download_tarball():
raise SystemExit(1) raise SystemExit(1)
do_download(dest) do_download(dest)
prints('Checking downloaded file integrity...') prints('Checking downloaded file integrity...')
if not check_signature(dest, signature): raw = check_signature(dest, signature)
if raw is None:
os.remove(dest) os.remove(dest)
print ('The downloaded files\' signature does not match. ' print ('The downloaded files\' signature does not match. '
'Try the download again later.') 'Try the download again later.')
raise SystemExit(1) raise SystemExit(1)
return dest return raw
# }}} # }}}
# Get tarball signature securely {{{ # Get tarball signature securely {{{
@ -581,14 +582,16 @@ def get_https_resource_securely(url, timeout=60, max_redirects=5, ssl_version=No
return response.read() return response.read()
# }}} # }}}
def extract_tarball(tar, destdir): def extract_tarball(raw, destdir):
prints('Extracting application files...') prints('Extracting application files...')
if hasattr(tar, 'read'):
tar = tar.name
with open('/dev/null', 'w') as null: with open('/dev/null', 'w') as null:
subprocess.check_call(['tar', 'xjof', tar, '-C', destdir], stdout=null, p = subprocess.Popen(['tar', 'xjof', '-', '-C', destdir], stdout=null, stdin=subprocess.PIPE, close_fds=True,
preexec_fn=lambda: preexec_fn=lambda:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)) signal.signal(signal.SIGPIPE, signal.SIG_DFL))
p.stdin.write(raw)
p.stdin.close()
if p.wait() != 0:
prints('Extracting of application files failed with error code: %s' % p.returncode)
def get_tarball_info(): def get_tarball_info():
global signature, calibre_version global signature, calibre_version
@ -603,24 +606,14 @@ def get_tarball_info():
def download_and_extract(destdir): def download_and_extract(destdir):
get_tarball_info() get_tarball_info()
try: raw = download_tarball()
f = download_tarball()
except:
raise
print('Failed to download, retrying in 30 seconds...')
time.sleep(30)
try:
f = download_tarball()
except:
print('Failed to download, aborting')
sys.exit(1)
if os.path.exists(destdir): if os.path.exists(destdir):
shutil.rmtree(destdir) shutil.rmtree(destdir)
os.makedirs(destdir) os.makedirs(destdir)
print('Extracting files to %s ...'%destdir) print('Extracting files to %s ...'%destdir)
extract_tarball(f, destdir) extract_tarball(raw, destdir)
def check_version(): def check_version():
global calibre_version global calibre_version