blob: b3154ca4a7870f9f29ee6b274d7b5626a428a3c4 [file] [log] [blame]
Mike Frysingere58c0e22017-10-04 15:43:30 -04001# -*- coding: utf-8 -*-
Jim Hebert91c052c2011-03-11 11:00:53 -08002# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Don Garrett25f309a2014-03-19 14:02:12 -07006"""Command to extract the dependancy tree for a given package."""
7
Mike Frysinger383367e2014-09-16 15:06:17 -04008from __future__ import print_function
9
Jim Hebert91c052c2011-03-11 11:00:53 -080010import json
Xuewei Zhang656f9932017-09-15 16:15:05 -070011import os
Don Garrettf8bf7842014-03-20 17:03:42 -070012
Jim Hebert91c052c2011-03-11 11:00:53 -080013from parallel_emerge import DepGraphGenerator
Don Garrettf8bf7842014-03-20 17:03:42 -070014
Jim Hebertcf870d72013-06-12 15:33:34 -070015from chromite.lib import commandline
16from chromite.lib import cros_build_lib
Ralph Nathan446aee92015-03-23 14:44:56 -070017from chromite.lib import cros_logging as logging
Xuewei Zhang656f9932017-09-15 16:15:05 -070018from chromite.lib import osutils
19from chromite.lib import portage_util
Mike Frysingercc838832014-05-24 13:10:30 -040020
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070021def FlattenDepTree(deptree, pkgtable=None, parentcpv=None, get_cpe=False):
Don Garrett25f309a2014-03-19 14:02:12 -070022 """Simplify dependency json.
23
24Turn something like this (the parallel_emerge DepsTree format):
Jim Hebert91c052c2011-03-11 11:00:53 -080025{
26 "app-admin/eselect-1.2.9": {
27 "action": "merge",
28 "deps": {
29 "sys-apps/coreutils-7.5-r1": {
30 "action": "merge",
31 "deps": {},
32 "deptype": "runtime"
33 },
34 ...
35 }
36 }
37}
38 ...into something like this (the cros_extract_deps format):
39{
40 "app-admin/eselect-1.2.9": {
41 "deps": ["coreutils-7.5-r1"],
42 "rev_deps": [],
43 "name": "eselect",
44 "category": "app-admin",
45 "version": "1.2.9",
46 "full_name": "app-admin/eselect-1.2.9",
47 "action": "merge"
48 },
49 "sys-apps/coreutils-7.5-r1": {
50 "deps": [],
51 "rev_deps": ["app-admin/eselect-1.2.9"],
52 "name": "coreutils",
53 "category": "sys-apps",
54 "version": "7.5-r1",
55 "full_name": "sys-apps/coreutils-7.5-r1",
56 "action": "merge"
57 }
58}
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070059
60 Args:
61 deptree: The dependency tree.
62 pkgtable: The package table to update. If None, create a new one.
63 parentcpv: The parent CPV.
64 get_cpe: If set True, include CPE in the flattened dependency tree.
65
66 Returns:
67 A flattened dependency tree.
Jim Hebert91c052c2011-03-11 11:00:53 -080068 """
David James1b363582012-12-17 11:53:11 -080069 if pkgtable is None:
70 pkgtable = {}
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070071 for cpv, record in deptree.iteritems():
Jim Hebert91c052c2011-03-11 11:00:53 -080072 if cpv not in pkgtable:
Xuewei Zhang656f9932017-09-15 16:15:05 -070073 split = portage_util.SplitCPV(cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070074 pkgtable[cpv] = {'deps': [],
75 'rev_deps': [],
Xuewei Zhang656f9932017-09-15 16:15:05 -070076 'name': split.package,
77 'category': split.category,
78 'version': '%s' % split.version,
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070079 'full_name': cpv,
80 'cpes': [],
81 'action': record['action']}
82 if get_cpe:
Xuewei Zhang656f9932017-09-15 16:15:05 -070083 pkgtable[cpv]['cpes'].extend(GetCPEFromCPV(
84 split.category, split.package, split.version_no_rev))
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070085
Jim Hebert91c052c2011-03-11 11:00:53 -080086 # If we have a parent, that is a rev_dep for the current package.
87 if parentcpv:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070088 pkgtable[cpv]['rev_deps'].append(parentcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080089 # If current package has any deps, record those.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070090 for childcpv in record['deps']:
91 pkgtable[cpv]['deps'].append(childcpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080092 # Visit the subtree recursively as well.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -070093 FlattenDepTree(record['deps'], pkgtable=pkgtable, parentcpv=cpv,
94 get_cpe=get_cpe)
Jim Hebert91c052c2011-03-11 11:00:53 -080095 return pkgtable
96
97
Jim Hebertcf870d72013-06-12 15:33:34 -070098def GetCPEFromCPV(category, package, version):
99 """Look up the CPE for a specified Portage package.
Jim Hebert91c052c2011-03-11 11:00:53 -0800100
Jim Hebertcf870d72013-06-12 15:33:34 -0700101 Args:
102 category: The Portage package's category, e.g. "net-misc"
103 package: The Portage package's name, e.g. "curl"
104 version: The Portage version, e.g. "7.30.0"
105
Mike Frysinger02e1e072013-11-10 22:11:34 -0500106 Returns:
107 A list of CPE Name strings, e.g.
108 ["cpe:/a:curl:curl:7.30.0", "cpe:/a:curl:libcurl:7.30.0"]
Jim Hebertcf870d72013-06-12 15:33:34 -0700109 """
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700110 equery_cmd = ['equery', 'm', '-U', '%s/%s' % (category, package)]
Jim Hebertcf870d72013-06-12 15:33:34 -0700111 lines = cros_build_lib.RunCommand(equery_cmd, error_code_ok=True,
112 print_cmd=False,
113 redirect_stdout=True).output.splitlines()
114 # Look for lines like "Remote-ID: cpe:/a:kernel:linux-pam ID: cpe"
115 # and extract the cpe URI.
116 cpes = []
117 for line in lines:
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700118 if 'ID: cpe' not in line:
Jim Hebertcf870d72013-06-12 15:33:34 -0700119 continue
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700120 cpes.append('%s:%s' % (line.split()[1], version.replace('_', '')))
Jim Hebertcf870d72013-06-12 15:33:34 -0700121 # Note that we're assuming we can combine the root of the CPE, taken
122 # from metadata.xml, and tack on the version number as used by
123 # Portage, and come up with a legitimate CPE. This works so long as
124 # Portage and CPE agree on the precise formatting of the version
Jim Hebert96aff9c2013-07-16 15:43:17 -0700125 # number, which they almost always do. The major exception we've
126 # identified thus far is that our ebuilds have a pattern of inserting
127 # underscores prior to patchlevels, that neither upstream nor CPE
128 # use. For example, our code will decide we have
Jim Hebertcf870d72013-06-12 15:33:34 -0700129 # cpe:/a:todd_miller:sudo:1.8.6_p7 yet the advisories use a format
130 # like cpe:/a:todd_miller:sudo:1.8.6p7, without the underscore. (CPE
131 # is "right" in this example, in that it matches www.sudo.ws.)
132 #
Jim Hebert96aff9c2013-07-16 15:43:17 -0700133 # Removing underscores seems to improve our chances of correctly
134 # arriving at the CPE used by NVD. However, at the end of the day,
135 # ebuild version numbers are rev'd by people who don't have "try to
136 # match NVD" as one of their goals, and there is always going to be
137 # some risk of minor formatting disagreements at the version number
138 # level, if not from stray underscores then from something else.
139 #
Jim Hebertcf870d72013-06-12 15:33:34 -0700140 # This is livable so long as you do some fuzzy version number
141 # comparison in your vulnerability monitoring, between what-we-have
142 # and what-the-advisory-says-is-affected.
143 return cpes
144
145
Xuewei Zhang656f9932017-09-15 16:15:05 -0700146def GenerateSDKCPVList(board):
147 """Find all SDK packages from package.provided
148
149 Args:
150 board: The board to use when finding SDK packages.
151
152 Returns:
153 A list of CPV Name strings, e.g.
154 ["sys-libs/glibc-2.23-r9", "dev-lang/go-1.8.3-r1"]
155 """
156 # Look at packages in package.provided.
157 board_root = cros_build_lib.GetSysroot(board)
158 sdk_file_path = os.path.join(board_root, 'etc', 'portage',
159 'profile', 'package.provided')
160 for line in osutils.ReadFile(sdk_file_path).splitlines():
161 # Skip comments and empty lines.
162 line = line.split('#', 1)[0].strip()
163 if not line:
164 continue
165 yield line
166
167
168def GenerateCPEList(deps_list, board):
169 """Generate all CPEs for the packages included in deps_list and SDK packages
170
171 Args:
172 deps_list: A flattened dependency tree (cros_extract_deps format).
173 board: The board to use when finding SDK packages.
174
175 Returns:
176 A list of CPE info for packages in deps_list and SDK packages, e.g.
177 [
178 {
179 "ComponentName": "app-admin/sudo",
180 "Repository": "cros",
181 "Targets": [
182 "cpe:/a:todd_miller:sudo:1.8.19p2"
183 ]
184 },
185 {
186 "ComponentName": "sys-libs/glibc",
187 "Repository": "cros",
188 "Targets": [
189 "cpe:/a:gnu:glibc:2.23"
190 ]
191 }
192 ]
193 """
Jim Hebertcf870d72013-06-12 15:33:34 -0700194 cpe_dump = []
Xuewei Zhang656f9932017-09-15 16:15:05 -0700195
196 # Generage CPEs for SDK packages.
197 for sdk_cpv in GenerateSDKCPVList(board):
198 # Only add CPE for SDK CPVs missing in deps_list.
199 if deps_list.get(sdk_cpv) is not None:
200 continue
201
202 split = portage_util.SplitCPV(sdk_cpv)
203 cpes = GetCPEFromCPV(split.category, split.package, split.version_no_rev)
204 if cpes:
Alex Klein9f93b482018-10-01 09:26:51 -0600205 cpe_dump.append({'ComponentName': '%s' % split.cp,
Xuewei Zhang656f9932017-09-15 16:15:05 -0700206 '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)