blob: 82eb2b04cb1c62f9a31082495d00ce6e81b71eaf [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
Chris McDonalde69db662018-11-15 12:50:18 -0700146def GenerateSDKCPVList(sysroot):
Xuewei Zhang656f9932017-09-15 16:15:05 -0700147 """Find all SDK packages from package.provided
148
149 Args:
Chris McDonalde69db662018-11-15 12:50:18 -0700150 sysroot: The board directory to use when finding SDK packages.
Xuewei Zhang656f9932017-09-15 16:15:05 -0700151
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.
Chris McDonalde69db662018-11-15 12:50:18 -0700157 sdk_file_path = os.path.join(sysroot, 'etc', 'portage',
Xuewei Zhang656f9932017-09-15 16:15:05 -0700158 '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
Chris McDonalde69db662018-11-15 12:50:18 -0700167def GenerateCPEList(deps_list, sysroot):
Xuewei Zhang656f9932017-09-15 16:15:05 -0700168 """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).
Chris McDonalde69db662018-11-15 12:50:18 -0700172 sysroot: The board directory to use when finding SDK packages.
Xuewei Zhang656f9932017-09-15 16:15:05 -0700173
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.
Chris McDonalde69db662018-11-15 12:50:18 -0700196 for sdk_cpv in GenerateSDKCPVList(sysroot):
Xuewei Zhang656f9932017-09-15 16:15:05 -0700197 # 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:
Alex Klein9f93b482018-10-01 09:26:51 -0600204 cpe_dump.append({'ComponentName': '%s' % split.cp,
Xuewei Zhang656f9932017-09-15 16:15:05 -0700205 'Repository': 'cros',
206 'Targets': sorted(cpes)})
207 else:
208 logging.warning('No CPE entry for %s', sdk_cpv)
209
210 # Generage CPEs for packages in deps_list.
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700211 for cpv, record in deps_list.iteritems():
212 if record['cpes']:
213 name = '%s/%s' % (record['category'], record['name'])
214 cpe_dump.append({'ComponentName': name,
215 'Repository': 'cros',
216 'Targets': sorted(record['cpes'])})
Jim Hebertcf870d72013-06-12 15:33:34 -0700217 else:
Ralph Nathan446aee92015-03-23 14:44:56 -0700218 logging.warning('No CPE entry for %s', cpv)
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700219 return sorted(cpe_dump, key=lambda k: k['ComponentName'])
Jim Hebert91c052c2011-03-11 11:00:53 -0800220
221
Chris McDonalde69db662018-11-15 12:50:18 -0700222def ParseArgs(argv):
223 """Parse command line arguments."""
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.""")
Chris McDonalde69db662018-11-15 12:50:18 -0700227 target = parser.add_mutually_exclusive_group()
228 target.add_argument('--sysroot', type='path', help='Path to the sysroot.')
229 target.add_argument('--board', help='Board name.')
230
Yu-Ju Hong7f01e9a2014-10-23 11:01:57 -0700231 parser.add_argument('--format', default='deps',
232 choices=['deps', 'cpe'],
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700233 help='Output either traditional deps or CPE-only JSON.')
234 parser.add_argument('--output-path', default=None,
235 help='Write output to the given path.')
Jim Hebertcf870d72013-06-12 15:33:34 -0700236 known_args, unknown_args = parser.parse_known_args(argv)
Chris McDonalde69db662018-11-15 12:50:18 -0700237 return (known_args, unknown_args)
Jim Hebert91c052c2011-03-11 11:00:53 -0800238
Chris McDonalde69db662018-11-15 12:50:18 -0700239
240def ExtractDeps(sysroot, package_list, formatting='deps'):
241 """Returns the set of dependencies for the packages in package_list"""
242 lib_argv = ['--quiet', '--pretend', '--emptytree']
243 lib_argv += ['--sysroot=%s' % sysroot]
244 lib_argv.extend(package_list)
Jim Hebert91c052c2011-03-11 11:00:53 -0800245
246 deps = DepGraphGenerator()
Jim Hebertcf870d72013-06-12 15:33:34 -0700247 deps.Initialize(lib_argv)
David James1b363582012-12-17 11:53:11 -0800248 deps_tree, _deps_info = deps.GenDependencyTree()
Chris McDonalde69db662018-11-15 12:50:18 -0700249 flattened_deps = FlattenDepTree(deps_tree, get_cpe=(formatting == 'cpe'))
250 if formatting == 'cpe':
251 flattened_deps = GenerateCPEList(flattened_deps, sysroot)
252 return flattened_deps
253
254
255def main(argv):
256 known_args, unknown_args = ParseArgs(argv)
257
258 sysroot = known_args.sysroot or cros_build_lib.GetSysroot(known_args.board)
259 pkgs = list(unknown_args)
260 deps_list = ExtractDeps(sysroot, pkgs, known_args.format)
Yu-Ju Hongf48d3982014-10-30 16:12:16 -0700261
262 deps_output = json.dumps(deps_list, sort_keys=True, indent=2)
263 if known_args.output_path:
264 with open(known_args.output_path, 'w') as f:
265 f.write(deps_output)
266 else:
267 print(deps_output)