blob: 200f544c38dc1f63056c8da27f5a07a2e27890a7 [file] [log] [blame]
Jim Hebert91c052c2011-03-11 11:00:53 -08001# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Don Garrett25f309a2014-03-19 14:02:12 -07005"""Command to extract the dependancy tree for a given package."""
6
Mike Frysinger383367e2014-09-16 15:06:17 -04007from __future__ import print_function
8
Jim Hebert91c052c2011-03-11 11:00:53 -08009import json
Xuewei Zhang656f9932017-09-15 16:15:05 -070010import os
Don Garrettf8bf7842014-03-20 17:03:42 -070011
Jim Hebert91c052c2011-03-11 11:00:53 -080012from parallel_emerge import DepGraphGenerator
Don Garrettf8bf7842014-03-20 17:03:42 -070013
Jim Hebertcf870d72013-06-12 15:33:34 -070014from chromite.lib import commandline
15from chromite.lib import cros_build_lib
Ralph Nathan446aee92015-03-23 14:44:56 -070016from chromite.lib import cros_logging as logging
Xuewei Zhang656f9932017-09-15 16:15:05 -070017from chromite.lib import osutils
18from chromite.lib import portage_util
Mike Frysingercc838832014-05-24 13:10:30 -040019
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070020def FlattenDepTree(deptree, pkgtable=None, parentcpv=None, get_cpe=False):
Don Garrett25f309a2014-03-19 14:02:12 -070021 """Simplify dependency json.
22
23Turn something like this (the parallel_emerge DepsTree format):
Jim Hebert91c052c2011-03-11 11:00:53 -080024{
25 "app-admin/eselect-1.2.9": {
26 "action": "merge",
27 "deps": {
28 "sys-apps/coreutils-7.5-r1": {
29 "action": "merge",
30 "deps": {},
31 "deptype": "runtime"
32 },
33 ...
34 }
35 }
36}
37 ...into something like this (the cros_extract_deps format):
38{
39 "app-admin/eselect-1.2.9": {
40 "deps": ["coreutils-7.5-r1"],
41 "rev_deps": [],
42 "name": "eselect",
43 "category": "app-admin",
44 "version": "1.2.9",
45 "full_name": "app-admin/eselect-1.2.9",
46 "action": "merge"
47 },
48 "sys-apps/coreutils-7.5-r1": {
49 "deps": [],
50 "rev_deps": ["app-admin/eselect-1.2.9"],
51 "name": "coreutils",
52 "category": "sys-apps",
53 "version": "7.5-r1",
54 "full_name": "sys-apps/coreutils-7.5-r1",
55 "action": "merge"
56 }
57}
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070058
59 Args:
60 deptree: The dependency tree.
61 pkgtable: The package table to update. If None, create a new one.
62 parentcpv: The parent CPV.
63 get_cpe: If set True, include CPE in the flattened dependency tree.
64
65 Returns:
66 A flattened dependency tree.
Jim Hebert91c052c2011-03-11 11:00:53 -080067 """
David James1b363582012-12-17 11:53:11 -080068 if pkgtable is None:
69 pkgtable = {}
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070070 for cpv, record in deptree.iteritems():
Jim Hebert91c052c2011-03-11 11:00:53 -080071 if cpv not in pkgtable:
Xuewei Zhang656f9932017-09-15 16:15:05 -070072 split = portage_util.SplitCPV(cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070073 pkgtable[cpv] = {'deps': [],
74 'rev_deps': [],
Xuewei Zhang656f9932017-09-15 16:15:05 -070075 'name': split.package,
76 'category': split.category,
77 'version': '%s' % split.version,
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070078 'full_name': cpv,
79 'cpes': [],
80 'action': record['action']}
81 if get_cpe:
Xuewei Zhang656f9932017-09-15 16:15:05 -070082 pkgtable[cpv]['cpes'].extend(GetCPEFromCPV(
83 split.category, split.package, split.version_no_rev))
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070084
Jim Hebert91c052c2011-03-11 11:00:53 -080085 # If we have a parent, that is a rev_dep for the current package.
86 if parentcpv:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070087 pkgtable[cpv]['rev_deps'].append(parentcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080088 # If current package has any deps, record those.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070089 for childcpv in record['deps']:
90 pkgtable[cpv]['deps'].append(childcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080091 # Visit the subtree recursively as well.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070092 FlattenDepTree(record['deps'], pkgtable=pkgtable, parentcpv=cpv,
93 get_cpe=get_cpe)
Jim Hebert91c052c2011-03-11 11:00:53 -080094 return pkgtable
95
96
Jim Hebertcf870d72013-06-12 15:33:34 -070097def GetCPEFromCPV(category, package, version):
98 """Look up the CPE for a specified Portage package.
Jim Hebert91c052c2011-03-11 11:00:53 -080099
Jim Hebertcf870d72013-06-12 15:33:34 -0700100 Args:
101 category: The Portage package's category, e.g. "net-misc"
102 package: The Portage package's name, e.g. "curl"
103 version: The Portage version, e.g. "7.30.0"
104
Mike Frysinger02e1e072013-11-10 22:11:34 -0500105 Returns:
106 A list of CPE Name strings, e.g.
107 ["cpe:/a:curl:curl:7.30.0", "cpe:/a:curl:libcurl:7.30.0"]
Jim Hebertcf870d72013-06-12 15:33:34 -0700108 """
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700109 equery_cmd = ['equery', 'm', '-U', '%s/%s' % (category, package)]
Jim Hebertcf870d72013-06-12 15:33:34 -0700110 lines = cros_build_lib.RunCommand(equery_cmd, error_code_ok=True,
111 print_cmd=False,
112 redirect_stdout=True).output.splitlines()
113 # Look for lines like "Remote-ID: cpe:/a:kernel:linux-pam ID: cpe"
114 # and extract the cpe URI.
115 cpes = []
116 for line in lines:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700117 if 'ID: cpe' not in line:
Jim Hebertcf870d72013-06-12 15:33:34 -0700118 continue
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700119 cpes.append('%s:%s' % (line.split()[1], version.replace('_', '')))
Jim Hebertcf870d72013-06-12 15:33:34 -0700120 # Note that we're assuming we can combine the root of the CPE, taken
121 # from metadata.xml, and tack on the version number as used by
122 # Portage, and come up with a legitimate CPE. This works so long as
123 # Portage and CPE agree on the precise formatting of the version
Jim Hebert96aff9c2013-07-16 15:43:17 -0700124 # number, which they almost always do. The major exception we've
125 # identified thus far is that our ebuilds have a pattern of inserting
126 # underscores prior to patchlevels, that neither upstream nor CPE
127 # use. For example, our code will decide we have
Jim Hebertcf870d72013-06-12 15:33:34 -0700128 # cpe:/a:todd_miller:sudo:1.8.6_p7 yet the advisories use a format
129 # like cpe:/a:todd_miller:sudo:1.8.6p7, without the underscore. (CPE
130 # is "right" in this example, in that it matches www.sudo.ws.)
131 #
Jim Hebert96aff9c2013-07-16 15:43:17 -0700132 # Removing underscores seems to improve our chances of correctly
133 # arriving at the CPE used by NVD. However, at the end of the day,
134 # ebuild version numbers are rev'd by people who don't have "try to
135 # match NVD" as one of their goals, and there is always going to be
136 # some risk of minor formatting disagreements at the version number
137 # level, if not from stray underscores then from something else.
138 #
Jim Hebertcf870d72013-06-12 15:33:34 -0700139 # This is livable so long as you do some fuzzy version number
140 # comparison in your vulnerability monitoring, between what-we-have
141 # and what-the-advisory-says-is-affected.
142 return cpes
143
144
Xuewei Zhang656f9932017-09-15 16:15:05 -0700145def GenerateSDKCPVList(board):
146 """Find all SDK packages from package.provided
147
148 Args:
149 board: The board to use when finding SDK packages.
150
151 Returns:
152 A list of CPV Name strings, e.g.
153 ["sys-libs/glibc-2.23-r9", "dev-lang/go-1.8.3-r1"]
154 """
155 # Look at packages in package.provided.
156 board_root = cros_build_lib.GetSysroot(board)
157 sdk_file_path = os.path.join(board_root, 'etc', 'portage',
158 'profile', 'package.provided')
159 for line in osutils.ReadFile(sdk_file_path).splitlines():
160 # Skip comments and empty lines.
161 line = line.split('#', 1)[0].strip()
162 if not line:
163 continue
164 yield line
165
166
167def GenerateCPEList(deps_list, board):
168 """Generate all CPEs for the packages included in deps_list and SDK packages
169
170 Args:
171 deps_list: A flattened dependency tree (cros_extract_deps format).
172 board: The board to use when finding SDK packages.
173
174 Returns:
175 A list of CPE info for packages in deps_list and SDK packages, e.g.
176 [
177 {
178 "ComponentName": "app-admin/sudo",
179 "Repository": "cros",
180 "Targets": [
181 "cpe:/a:todd_miller:sudo:1.8.19p2"
182 ]
183 },
184 {
185 "ComponentName": "sys-libs/glibc",
186 "Repository": "cros",
187 "Targets": [
188 "cpe:/a:gnu:glibc:2.23"
189 ]
190 }
191 ]
192 """
Jim Hebertcf870d72013-06-12 15:33:34 -0700193 cpe_dump = []
Xuewei Zhang656f9932017-09-15 16:15:05 -0700194
195 # Generage CPEs for SDK packages.
196 for sdk_cpv in GenerateSDKCPVList(board):
197 # Only add CPE for SDK CPVs missing in deps_list.
198 if deps_list.get(sdk_cpv) is not None:
199 continue
200
201 split = portage_util.SplitCPV(sdk_cpv)
202 cpes = GetCPEFromCPV(split.category, split.package, split.version_no_rev)
203 if cpes:
204 cpe_dump.append({'ComponentName': '%s/%s' % (split.category,
205 split.package),
206 'Repository': 'cros',
207 'Targets': sorted(cpes)})
208 else:
209 logging.warning('No CPE entry for %s', sdk_cpv)
210
211 # Generage CPEs for packages in deps_list.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700212 for cpv, record in deps_list.iteritems():
213 if record['cpes']:
214 name = '%s/%s' % (record['category'], record['name'])
215 cpe_dump.append({'ComponentName': name,
216 'Repository': 'cros',
217 'Targets': sorted(record['cpes'])})
Jim Hebertcf870d72013-06-12 15:33:34 -0700218 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700219 logging.warning('No CPE entry for %s', cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700220 return sorted(cpe_dump, key=lambda k: k['ComponentName'])
Jim Hebert91c052c2011-03-11 11:00:53 -0800221
222
Brian Harring30675052012-02-29 12:18:22 -0800223def main(argv):
Jim Hebertcf870d72013-06-12 15:33:34 -0700224 parser = commandline.ArgumentParser(description="""
225This extracts the dependency tree for the specified package, and outputs it
226to stdout, in a serialized JSON format.""")
Yu-Ju Hong997108e2014-10-27 11:21:47 -0700227 parser.add_argument('--board', default=None,
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700228 help='The board to use when computing deps.')
229 parser.add_argument('--format', default='deps',
230 choices=['deps', 'cpe'],
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700231 help='Output either traditional deps or CPE-only JSON.')
232 parser.add_argument('--output-path', default=None,
233 help='Write output to the given path.')
Jim Hebertcf870d72013-06-12 15:33:34 -0700234 known_args, unknown_args = parser.parse_known_args(argv)
Jim Hebert91c052c2011-03-11 11:00:53 -0800235
Yu-Ju Hong997108e2014-10-27 11:21:47 -0700236 lib_argv = []
237 if known_args.board:
238 lib_argv += ['--board=%s' % known_args.board]
David Jamesf147b432014-12-02 13:51:55 -0800239 lib_argv += ['--quiet', '--pretend', '--emptytree']
Jim Hebertcf870d72013-06-12 15:33:34 -0700240 lib_argv.extend(unknown_args)
Jim Hebert91c052c2011-03-11 11:00:53 -0800241
242 deps = DepGraphGenerator()
Jim Hebertcf870d72013-06-12 15:33:34 -0700243 deps.Initialize(lib_argv)
David James1b363582012-12-17 11:53:11 -0800244 deps_tree, _deps_info = deps.GenDependencyTree()
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700245 deps_list = FlattenDepTree(deps_tree, get_cpe=(known_args.format == 'cpe'))
246 if known_args.format == 'cpe':
Xuewei Zhang656f9932017-09-15 16:15:05 -0700247 deps_list = GenerateCPEList(deps_list, board=known_args.board)
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700248
249 deps_output = json.dumps(deps_list, sort_keys=True, indent=2)
250 if known_args.output_path:
251 with open(known_args.output_path, 'w') as f:
252 f.write(deps_output)
253 else:
254 print(deps_output)