blob: dab2c7f827f42dc8079e021c897b7e9875c50ad8 [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
Don Garrettf8bf7842014-03-20 17:03:42 -070010import portage # pylint: disable=F0401
11
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
Jim Hebert91c052c2011-03-11 11:00:53 -080016
Mike Frysingercc838832014-05-24 13:10:30 -040017
David James1b363582012-12-17 11:53:11 -080018def FlattenDepTree(deptree, pkgtable=None, parentcpv=None):
Don Garrett25f309a2014-03-19 14:02:12 -070019 """Simplify dependency json.
20
21Turn something like this (the parallel_emerge DepsTree format):
Jim Hebert91c052c2011-03-11 11:00:53 -080022{
23 "app-admin/eselect-1.2.9": {
24 "action": "merge",
25 "deps": {
26 "sys-apps/coreutils-7.5-r1": {
27 "action": "merge",
28 "deps": {},
29 "deptype": "runtime"
30 },
31 ...
32 }
33 }
34}
35 ...into something like this (the cros_extract_deps format):
36{
37 "app-admin/eselect-1.2.9": {
38 "deps": ["coreutils-7.5-r1"],
39 "rev_deps": [],
40 "name": "eselect",
41 "category": "app-admin",
42 "version": "1.2.9",
43 "full_name": "app-admin/eselect-1.2.9",
44 "action": "merge"
45 },
46 "sys-apps/coreutils-7.5-r1": {
47 "deps": [],
48 "rev_deps": ["app-admin/eselect-1.2.9"],
49 "name": "coreutils",
50 "category": "sys-apps",
51 "version": "7.5-r1",
52 "full_name": "sys-apps/coreutils-7.5-r1",
53 "action": "merge"
54 }
55}
56 """
David James1b363582012-12-17 11:53:11 -080057 if pkgtable is None:
58 pkgtable = {}
Jim Hebert91c052c2011-03-11 11:00:53 -080059 for cpv, record in deptree.items():
60 if cpv not in pkgtable:
David James1b363582012-12-17 11:53:11 -080061 cat, nam, ver, rev = portage.versions.catpkgsplit(cpv)
Jim Hebert91c052c2011-03-11 11:00:53 -080062 pkgtable[cpv] = {"deps": [],
63 "rev_deps": [],
64 "name": nam,
65 "category": cat,
66 "version": "%s-%s" % (ver, rev),
67 "full_name": cpv,
Jim Hebertcf870d72013-06-12 15:33:34 -070068 "cpes": GetCPEFromCPV(cat, nam, ver),
Jim Hebert91c052c2011-03-11 11:00:53 -080069 "action": record["action"]}
70 # If we have a parent, that is a rev_dep for the current package.
71 if parentcpv:
72 pkgtable[cpv]["rev_deps"].append(parentcpv)
73 # If current package has any deps, record those.
74 for childcpv in record["deps"]:
75 pkgtable[cpv]["deps"].append(childcpv)
76 # Visit the subtree recursively as well.
77 FlattenDepTree(record["deps"], pkgtable=pkgtable, parentcpv=cpv)
78 return pkgtable
79
80
Jim Hebertcf870d72013-06-12 15:33:34 -070081def GetCPEFromCPV(category, package, version):
82 """Look up the CPE for a specified Portage package.
Jim Hebert91c052c2011-03-11 11:00:53 -080083
Jim Hebertcf870d72013-06-12 15:33:34 -070084 Args:
85 category: The Portage package's category, e.g. "net-misc"
86 package: The Portage package's name, e.g. "curl"
87 version: The Portage version, e.g. "7.30.0"
88
Mike Frysinger02e1e072013-11-10 22:11:34 -050089 Returns:
90 A list of CPE Name strings, e.g.
91 ["cpe:/a:curl:curl:7.30.0", "cpe:/a:curl:libcurl:7.30.0"]
Jim Hebertcf870d72013-06-12 15:33:34 -070092 """
93 equery_cmd = ["equery", "m", "-U", "%s/%s" % (category, package)]
94 lines = cros_build_lib.RunCommand(equery_cmd, error_code_ok=True,
95 print_cmd=False,
96 redirect_stdout=True).output.splitlines()
97 # Look for lines like "Remote-ID: cpe:/a:kernel:linux-pam ID: cpe"
98 # and extract the cpe URI.
99 cpes = []
100 for line in lines:
101 if "ID: cpe" not in line:
102 continue
Jim Hebert96aff9c2013-07-16 15:43:17 -0700103 cpes.append("%s:%s" % (line.split()[1], version.replace("_", "")))
Jim Hebertcf870d72013-06-12 15:33:34 -0700104 # Note that we're assuming we can combine the root of the CPE, taken
105 # from metadata.xml, and tack on the version number as used by
106 # Portage, and come up with a legitimate CPE. This works so long as
107 # Portage and CPE agree on the precise formatting of the version
Jim Hebert96aff9c2013-07-16 15:43:17 -0700108 # number, which they almost always do. The major exception we've
109 # identified thus far is that our ebuilds have a pattern of inserting
110 # underscores prior to patchlevels, that neither upstream nor CPE
111 # use. For example, our code will decide we have
Jim Hebertcf870d72013-06-12 15:33:34 -0700112 # cpe:/a:todd_miller:sudo:1.8.6_p7 yet the advisories use a format
113 # like cpe:/a:todd_miller:sudo:1.8.6p7, without the underscore. (CPE
114 # is "right" in this example, in that it matches www.sudo.ws.)
115 #
Jim Hebert96aff9c2013-07-16 15:43:17 -0700116 # Removing underscores seems to improve our chances of correctly
117 # arriving at the CPE used by NVD. However, at the end of the day,
118 # ebuild version numbers are rev'd by people who don't have "try to
119 # match NVD" as one of their goals, and there is always going to be
120 # some risk of minor formatting disagreements at the version number
121 # level, if not from stray underscores then from something else.
122 #
Jim Hebertcf870d72013-06-12 15:33:34 -0700123 # This is livable so long as you do some fuzzy version number
124 # comparison in your vulnerability monitoring, between what-we-have
125 # and what-the-advisory-says-is-affected.
126 return cpes
127
128
129def ExtractCPEList(deps_list):
130 cpe_dump = []
131 for cpv, record in deps_list.items():
132 if record["cpes"]:
Kees Cook5e3961b2014-06-19 12:03:12 -0700133 name = "%s/%s" % (record["category"], record["name"])
134 cpe_dump.append({"ComponentName": name,
135 "Repository": "cros",
136 "Targets": sorted(record["cpes"])})
Jim Hebertcf870d72013-06-12 15:33:34 -0700137 else:
138 cros_build_lib.Warning("No CPE entry for %s", cpv)
Kees Cook5e3961b2014-06-19 12:03:12 -0700139 return sorted(cpe_dump, key=lambda k: k["ComponentName"])
Jim Hebert91c052c2011-03-11 11:00:53 -0800140
141
Brian Harring30675052012-02-29 12:18:22 -0800142def main(argv):
Jim Hebertcf870d72013-06-12 15:33:34 -0700143 parser = commandline.ArgumentParser(description="""
144This extracts the dependency tree for the specified package, and outputs it
145to stdout, in a serialized JSON format.""")
146 parser.add_argument("--board", required=True,
147 help="The board to use when computing deps.")
148 parser.add_argument("--format", default="deps",
149 choices=["deps", "cpe"],
150 help="Output either traditional deps or CPE-only JSON")
151 # Even though this is really just a pass-through to DepGraphGenerator,
152 # handling it as a known arg here allows us to specify a default more
153 # elegantly than testing for its presence in the unknown_args later.
154 parser.add_argument("--root-deps", default="rdeps",
155 help="Which deps to report (defaults to rdeps)")
156 known_args, unknown_args = parser.parse_known_args(argv)
Jim Hebert91c052c2011-03-11 11:00:53 -0800157
Jim Hebertcf870d72013-06-12 15:33:34 -0700158 lib_argv = ["--board=%s" % known_args.board,
159 "--root-deps=%s" % known_args.root_deps,
160 "--quiet", "--pretend", "--emptytree"]
161 lib_argv.extend(unknown_args)
Jim Hebert91c052c2011-03-11 11:00:53 -0800162
163 deps = DepGraphGenerator()
Jim Hebertcf870d72013-06-12 15:33:34 -0700164 deps.Initialize(lib_argv)
David James1b363582012-12-17 11:53:11 -0800165 deps_tree, _deps_info = deps.GenDependencyTree()
Jim Hebertcf870d72013-06-12 15:33:34 -0700166 deps_list = FlattenDepTree(deps_tree)
167 if known_args.format == "cpe":
168 deps_list = ExtractCPEList(deps_list)
Mike Frysinger383367e2014-09-16 15:06:17 -0400169 print(json.dumps(deps_list, sort_keys=True, indent=2))