Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

############################################################################# 

# 

# Copyright (C) 2017 Menlo Security, Inc. 

# 

# Monitor local directory for updates to sv-cr and flash packages. 

# 

# Updates occur by modify update.json. Format for update.json is: 

# {"sv-cr":{"version":"1","file":"sv-cr-1.deb","sha256":"DEAD...BEEF"}, 

# "flash":{...}} 

# Files listed in update.json should exist when update.json is written. 

# 

############################################################################# 

 

import ctypes 

import hashlib 

import json 

import os 

import subprocess 

 

import tornado.ioloop 

 

from safly.config import config 

from safly.logger import SafelyLoggerMixin 

 

 

# Needed for inotify. 

IN_CLOSE_WRITE = 0x8 

libc = ctypes.cdll.LoadLibrary('libc.so.6') 

inotify_fd = None 

inotify_watch_descriptor = None 

 

 

class UpdateManager(SafelyLoggerMixin): 

def __init__(self): 

super(UpdateManager, self).__init__('sm-um', auto_prefix=True) 

self._cr_version = None 

self._flash_version = None 

self._inotify_fd = None 

self._update_dir = os.path.join(config.get('service', 'conf_dir'), 

'sm-updates') 

self._update_metadata = os.path.join(self._update_dir, 'update.json') 

self.producer = None 

 

def handle_inotify_event(self, fd, events): 

assert(fd == self._inotify_fd) 

ioloop = tornado.ioloop.IOLoop.current() 

if events & ioloop.ERROR: 

self.log.error({'error': 'inotify-error'}, event='unexpected-error') 

# Drain the events. We don't care what they are and just check the 

# metadata any time we get an event. 

events = os.read(fd, 4096) 

self._handle_update() 

 

def _check_package_metadata(self, p): 

"""Validate update metadata. 

 

Verify that all required fields are present, that the update file exists 

and hashes to the correct value. 

""" 

if 'file' not in p or 'sha256' not in p: 

self.log.error({'details': 'Missing key', 'metadata': p}, 

event='bad-metadata') 

return False 

path = os.path.join(self._update_dir, p['file']) 

if not os.path.exists(path): 

self.log.error({'details': 'Missing file', 'file': path}, 

event='bad-metadata') 

return False 

try: 

file_hash = hashlib.sha256(open(path).read()).hexdigest().lower() 

except Exception as e: 

self.log.error({'details': 'Digest error', 'file': path, 

'error': e}, event='bad-metadata') 

return False 

if file_hash != p['sha256'].lower(): 

self.log.error({'details': 'Digest mismatch', 'actual': file_hash, 

'expected': p['sha256']}, event='bad-metadata') 

return False 

return True 

 

def _handle_update(self): 

"""Parse metadata file and install update if necessary.""" 

if not os.path.exists(self._update_metadata): 

return 

try: 

update = json.loads(open(self._update_metadata).read()) 

except Exception as e: 

self.log.error({'error': e}, event='bad-metadata') 

return 

packages = {} 

if self._cr_version != update.get('sv-cr', {}).get('version', 

self._cr_version): 

packages['sv-cr'] = update['sv-cr'] 

if self._flash_version != update.get('flash', {}).get('version', 

self._flash_version): 

packages['flash'] = update['flash'] 

if not packages: 

return 

for p in packages.values(): 

if not self._check_package_metadata(p): 

return 

if self.producer: 

if not self.producer.freeze(): 

self.log.error({'details': 'Could not freeze'}, 

event='unexpected-error') 

return 

try: 

# This is intentionally done synchronously on the ioloop for 

# simplicity. Surrogate manager will be unresponsive for a few 

# seconds. 

dpkg_out = subprocess.check_output( 

['dpkg', '-i'] + [os.path.join(self._update_dir, 

p['file']) for p in packages.values()]) 

except subprocess.CalledProcessError as e: 

self.log.error({'details': 'Could not install packages', 'error': e, 

'metadata': packages, 

'output': e.output.replace('\n', ' ')}, 

event='unexpected-error') 

else: 

self._cr_version = packages.get('sv-cr', {}).get('version', 

self._cr_version) 

self._flash_version = packages.get('flash', {}).get('version', 

self._flash_version) 

self.log.info({'sv-cr': self._cr_version, 'flash': self._flash_version, 

'output': dpkg_out.replace('\n', ' ')}, 

event='updated') 

if self.producer: 

if not self.producer.unfreeze(): 

self.log.error({'details': 'Could not unfreeze'}, 

event='unexpected-error') 

 

def start(self): 

"""Make sure initial state is correct. 

 

If an update file is present, handle it. Install inotify watch for 

updates. 

""" 

# Get the currently installed package versions. 

try: 

self._cr_version = subprocess.check_output( 

['dpkg-query', '--showformat', '${Version}', '--show', 

'sv-cr-menlosec'], 

stderr=open(os.devnull, 'w')).strip() 

self._flash_version = subprocess.check_output( 

['dpkg-query', '--showformat', '${Version}', '--show', 

'adobe-flashplugin'], stderr=open(os.devnull, 'w')).strip() 

except Exception: 

# If we cannot get package info, we do not do automatic updates. 

self.log.info({'details': 'Packages not found'}, event='disabled') 

return 

self._inotify_fd = libc.inotify_init() 

if self._inotify_fd < 0: 

self.log.error({'details': 'Inotify FD error', 

'error': os.strerror(self._inotify_fd)}, 

event='unexpected-error') 

return 

self._inotify_watch_descriptor = libc.inotify_add_watch( 

self._inotify_fd, self._update_dir, IN_CLOSE_WRITE) 

if self._inotify_watch_descriptor < 0: 

self.log.error({'details': 'Inotify watch error', 

'error': os.strerror(self._inotify_watch_descriptor)}, 

event='unexpected-error') 

return 

ioloop = tornado.ioloop.IOLoop.current() 

ioloop.add_handler( 

self._inotify_fd, self.handle_inotify_event, ioloop.READ|ioloop.ERROR) 

self.log.info({'sv-cr': self._cr_version, 'flash': self._flash_version}, 

event='started') 

self._handle_update()