1#!/usr/bin/env python3

2"""

3 Test that the kernel is not "tainted" by out-of-tree drivers, etc.

4

5 Copyright (C) 2019 Canonical Ltd.

6

7 Authors:

8 Rod Smith <rod.smith@canonical.com>

9

10 This program is free software: you can redistribute it and/or modify

11 it under the terms of the GNU General Public License version 3,

12 as published by the Free Software Foundation.

13

14 This program is distributed in the hope that it will be useful,

15 but WITHOUT ANY WARRANTY; without even the implied warranty of

16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

17 GNU General Public License for more details.

18

19 You should have received a copy of the GNU General Public License

20 along with this program. If not, see <http://www.gnu.org/licenses/>.

21

22 This script examines the /proc/sys/kernel/tainted file (or a user-specified

23 file, for debugging purposes) and parses the contents to determine if the

24 kernel is tainted. If so, the script reports the nature of the taint and

25 returns a value of 1. The script also returns a value of 1 if

26# /proc/sys/kernel/tainted cannot be found or opened. If the kernel is NOT

27# tainted, the script notes this fact and returns 0.

28"""

29

30

31 import sys

32 import shlex

33 from argparse import ArgumentParser

34 from subprocess import check_output

35

36# Note: If max_taints is increased, add descriptions to taint_meanings in

37# report_failures()

38 max_taints = 17

39

40

41 def find_taints(taint_file):

42 """Read the kernel-taint file."""

43 try:

44 f = open(taint_file, "r")

45 taints = int(f.read())

46 except OSError:

47 raise SystemExit(

48 "Kernel taint file ({}) not found!".format(taint_file)

49 )

50 print("Kernel taint value is {}".format(taints))

51 return taints

52

53

54 def get_modules():

55 lsmod_output = check_output("lsmod", universal_newlines=True).split("\n")

56 # get only the module names

57 modules = []

58 for line in lsmod_output:

59 if line and "Module" not in line:

60 modules.append(line.split()[0])

61 return modules

62

63

64 def process_out_of_tree_modules(modules):

65 mod_list = []

66 modules = remove_ignored_modules(modules)

67 for mod in modules:

68 cmd = "modinfo -F intree %s" % mod

69 if not check_output(shlex.split(cmd), universal_newlines=True):

70 mod_list.append(mod)

71 return mod_list

72

73

74 def process_GPL_incompatible_modules(modules):

75 mod_list = []

76 modules = remove_ignored_modules(modules)

77 for mod in modules:

78 cmd = "modinfo -F license %s" % mod

79 license = check_output(

80 shlex.split(cmd), universal_newlines=True

81 ).strip()

82 if "GPL" not in license and "MIT" not in license:

83 mod_list.append((mod, license))

84 return mod_list

85

86

87 def remove_ignored_modules(modules):

88 # Remove modules we know will fail, but accept

89 ignored_modules = [

90 "icp",

91 "spl",

92 "zavl",

93 "zcommon",

94 "zfs",

95 "zlua",

96 "znvpair",

97 "zunicode",

98 "zzstd",

99 "nvidia_uvm",

100 "nvidia_drm",

101 "nvidia_modeset",

102 "nvidia",

103 ]

104 for ignore_mod in ignored_modules:

105 try:

106 modules.remove(ignore_mod)

107 except ValueError:

108 pass

109 return modules

110

111

112 def main():

113 """Print out the tainted state code and its meaning(s)."""

114 parser = ArgumentParser()

115 parser.add_argument(

116 "--taint-file",

117 default="/proc/sys/kernel/tainted",

118 help="The file that holds the taint information",

119 )

120 args = parser.parse_args()

121 taints = find_taints(args.taint_file)

122

123 # Below meaning strings are taken from

124 # https://www.kernel.org/doc/html/latest/admin-guide/tainted-kernels.html

125 taint_meanings = [

126 "proprietary module was loaded",

127 "module was force loaded",

128 "SMP kernel oops on an officially SMP incapable CPU",

129 "module was force unloaded",

130 "processor reported a Machine Check Exception (MCE)",

131 "bad page referenced or some unexpected page flags",

132 "taint requested by userspace application",

133 "kernel died recently, i.e. there was an OOPS or BUG",

134 "ACPI table overridden by user",

135 "kernel issued warning",

136 "staging driver was loaded",

137 "workaround for bug in platform firmware applied",

138 "externally-built ('out-of-tree') module was loaded",

139 "unsigned module was loaded",

140 "soft lockup occurred",

141 "kernel has been live patched",

142 "auxiliary taint, defined for and used by distros",

143 "kernel was built with the struct randomization plugin",

144 ]

145 count = 0

146 for i in range(max_taints + 1):

147 if taints & (2**i):

148 modules = get_modules()

149 print("Taint bit value: {} ({})".format(i, taint_meanings[i]))

150 if i == 0: # List GPL incompatible modules and licenses

151 proprietary_modules = process_GPL_incompatible_modules(modules)

152 if proprietary_modules:

153 print("* Modules with GPL Incompatible Licenses:")

154 for mod in proprietary_modules:

155 print(" %s: %s" % (mod[0], mod[1]))

156 count += 1

157 else:

158 print(

159 "* Proprietary modules found, "

160 "but they are expected and OK"

161 )

162 elif i == 11:

163 print("* Firmware workarounds are expected and OK")

164 elif i == 12: # List out-of-tree modules

165 out_of_tree_modules = process_out_of_tree_modules(modules)

166 if len(out_of_tree_modules) > 0:

167 print("* Modules not in-tree:")

168 for mod in out_of_tree_modules:

169 print(" %s" % mod)

170 count += 1

171 else:

172 print(

173 "* Out of Tree modules found, "

174 "but they are expected and OK"

175 )

176 else:

177 count += 1

178

179 if count == 0:

180 # else case below contains expected issue in case 0 / 11 / 12

181 if not taints:

182 print("No kernel taints detected.")

183 return 0

184 else:

185 return 1

186

187

188 if __name__ == "__main__":

189 sys.exit(main())