blob: b5770b0fee5bc5cdf4e79d760297112ceaedad58 [file] [log] [blame]
Mike Frysingerb9743c62020-02-20 02:53:55 -05001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2020 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Make sure packages don't create random paths outside of existing norms."""
8
9from __future__ import print_function
10
11import argparse
12import fnmatch
13import logging # pylint: disable=cros-logging-import
14import os
15import sys
16
17
18# NB: Do not add any new entries here without wider discussion.
19
20
21# Paths that are allowed in the / dir.
Mike Frysingera77e33d2020-03-22 15:15:58 -040022#
23# NB: We don't allow packages to install into some subdirs because they are
24# always bind mounted with the host distro, and we don't want to pollute them.
25# Those are: /dev
Mike Frysingerb9743c62020-02-20 02:53:55 -050026VALID_ROOT = {
Mike Frysingera77e33d2020-03-22 15:15:58 -040027 'bin', 'boot', 'etc', 'home', 'lib', 'lib32', 'lib64', 'media',
Mike Frysingerb9743c62020-02-20 02:53:55 -050028 'mnt', 'opt', 'proc', 'root', 'run', 'sbin', 'sys', 'tmp', 'usr', 'var',
29}
30
31# Paths that are allowed in the / dir for boards.
32VALID_BOARD_ROOT = {
Mike Frysingera77e33d2020-03-22 15:15:58 -040033 'build', 'dev', 'firmware',
Mike Frysingerb9743c62020-02-20 02:53:55 -050034 # TODO(): We should clean this up.
35 'postinst',
36}
37
38# Paths that are allowed in the / dir for the SDK chroot.
39VALID_HOST_ROOT = set()
40
41# Paths under / that should not have any subdirs.
42NOSUBDIRS_ROOT = {
43 'bin', 'dev', 'proc', 'sbin', 'sys', 'tmp',
44}
45
46# Paths that are allowed in the /usr dir.
47VALID_USR = {
48 'bin', 'include', 'lib', 'lib32', 'lib64', 'libexec', 'sbin', 'share',
49 'src',
50}
51
52# Paths that are allowed in the /usr dir for boards.
53VALID_BOARD_USR = {
54 # Boards install into /usr/local for test images.
55 'local',
56}
57
58# Paths that are allowed in the /usr dir for the SDK chroot.
59VALID_HOST_USR = set()
60
61# Paths under /usr that should not have any subdirs.
62NOSUBDIRS_USR = {
63 'bin', 'sbin',
64}
65
66# Valid toolchain targets. We don't want to add any more non-standard ones.
67# targets that use *-cros-* as the vendor are OK to add more.
68KNOWN_TARGETS = {
69 'arm-none-eabi',
70 'i686-pc-linux-gnu',
71 'x86_64-pc-linux-gnu',
72 'arm*-cros-eabi',
73 '*-cros-linux-gnu*',
74}
75
76# These packages need fixing.
77# NB: Do *not* add more packages here.
78BAD_ROOT_PACKAGES = {
79 # TODO(crbug.com/1003107): Delete this.
80 'dev-go/go-tools',
81}
82
83# These SDK packages need cleanup.
84# NB: Do *not* add more packages here.
85BAD_HOST_USR_LOCAL_PACKAGES = {
86 'app-crypt/nss',
87}
88
89# Ignore some packages installing into /var for now.
90# NB: Do *not* add more packages here.
91BAD_VAR_PACKAGES = {
92 'app-accessibility/brltty',
93 'app-admin/eselect',
94 'app-admin/puppet',
95 'app-admin/rsyslog',
96 'app-admin/sudo',
97 'app-admin/sysstat',
98 'app-admin/webapp-config',
99 'app-crypt/mit-krb5',
100 'app-crypt/trousers',
101 'app-emulation/containerd',
102 'app-emulation/lxc',
Mike Frysingerf3828e92020-03-06 13:17:34 -0500103 # https://crbug.com/1059308
104 'chromeos-base/chromeos-bsp-initramfs-rialto',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500105 'chromeos-base/chromeos-initramfs',
106 # https://crbug.com/1054646
107 'chromeos-base/devserver',
108 # https://crbug.com/1007402
109 'chromeos-base/factory',
110 'chromeos-base/factory-board',
111 'dev-python/django',
112 'media-gfx/sane-backends',
113 'media-sound/alsa-utils',
114 'net-analyzer/netperf',
115 'net-dns/dnsmasq',
116 'net-firewall/iptables',
Mike Frysinger88028fe2020-03-04 18:53:39 -0500117 'net-firewall/nftables',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500118 'net-fs/samba',
Mike Frysinger0669e002020-03-02 16:56:24 -0500119 'net-misc/chrony',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500120 'net-misc/dhcpcd',
121 'net-misc/openssh',
122 'net-print/cups',
123 'sys-apps/dbus',
124 'sys-apps/fwupd',
125 'sys-apps/iproute2',
126 'sys-apps/journald',
127 'sys-apps/portage',
128 'sys-apps/sandbox',
129 'sys-apps/systemd',
130 'sys-apps/usbguard',
131 'sys-kernel/loonix-initramfs',
132 'sys-libs/glibc',
133 'sys-process/audit',
134 'www-servers/nginx',
135 'x11-base/xwayland',
136}
137
138# Ignore some packages installing into /run for now.
139# NB: Do *not* add more packages here.
140BAD_RUN_PACKAGES = {
141 'app-accessibility/brltty',
142 'net-fs/samba',
143}
144
145# Ignore some packages installing into /tmp for now.
146# NB: Do *not* add more packages here.
147BAD_TMP_PACKAGES = {
148 # https://crbug.com/1057059
149 'chromeos-base/chromeos-bsp-caroline-private',
Mike Frysinger833a8222020-03-02 14:04:36 -0500150 'chromeos-base/chromeos-bsp-elm-private',
Mike Frysinger7dd8d682020-03-03 11:18:29 -0500151 'chromeos-base/chromeos-config-bsp',
Mike Frysinger833a8222020-03-02 14:04:36 -0500152 'chromeos-base/chromeos-config-bsp-coral',
153 'chromeos-base/chromeos-config-bsp-coral-private',
154 'chromeos-base/chromeos-config-bsp-nami',
155 'chromeos-base/chromeos-config-bsp-scarlet-private',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500156 'chromeos-base/cros-config-test',
157}
158
159
160def has_subdirs(path):
161 """See if |path| has any subdirs."""
162 # These checks are helpful for manually running the script when debugging.
163 if os.path.ismount(path):
164 logging.warning('Ignoring mounted dir for subdir check: %s', path)
165 return False
166
167 if os.path.join(os.getenv('SYSROOT', '/'), 'tmp') == path:
168 logging.warning('Ignoring live dir: %s', path)
169 return False
170
171 for _, dirs, _ in os.walk(path):
172 if dirs:
173 logging.error('Subdirs found in a dir that should be empty:\n %s\n'
174 ' |-- %s', path, '\n |-- '.join(sorted(dirs)))
175 return True
176 break
177
178 return False
179
180
181def check_usr(usr, host=False):
182 """Check the /usr filesystem at |usr|."""
183 ret = True
184
185 # Not all packages install into /usr.
186 if not os.path.exists(usr):
187 return ret
188
189 atom = get_current_package()
190 paths = set(os.listdir(usr))
191 unknown = paths - VALID_USR
192 for target in KNOWN_TARGETS:
193 unknown = set(x for x in unknown if not fnmatch.fnmatch(x, target))
194 if host:
195 unknown -= VALID_HOST_USR
196
197 if atom in BAD_HOST_USR_LOCAL_PACKAGES:
198 logging.warning('Ignoring known bad /usr/local install for now')
199 unknown -= {'local'}
200 else:
201 unknown -= VALID_BOARD_USR
202
203 if atom in {'chromeos-base/ap-daemons'}:
204 logging.warning('Ignoring known bad /usr install for now')
205 unknown -= {'www'}
206
207 if unknown:
208 logging.error('Paths are not allowed in the /usr dir: %s', sorted(unknown))
209 ret = False
210
211 for path in NOSUBDIRS_USR:
212 if has_subdirs(os.path.join(usr, path)):
213 ret = False
214
215 return ret
216
217
218def check_root(root, host=False):
219 """Check the filesystem |root|."""
220 ret = True
221
222 atom = get_current_package()
223 paths = set(os.listdir(root))
224 unknown = paths - VALID_ROOT
225 if host:
226 unknown -= VALID_HOST_ROOT
227 else:
228 unknown -= VALID_BOARD_ROOT
229
230 if atom in BAD_ROOT_PACKAGES:
231 logging.warning('Ignoring known bad / install for now')
232 elif unknown:
233 logging.error('Paths are not allowed in the root dir:\n %s\n |-- %s',
234 root, '\n |-- '.join(sorted(unknown)))
235 ret = False
236
237 # Some of these may have subdirs at runtime, but not from package installs.
238 for path in NOSUBDIRS_ROOT:
239 if has_subdirs(os.path.join(root, path)):
240 if path == 'tmp' and atom in BAD_TMP_PACKAGES:
241 logging.warning('Ignoring known bad /tmp install for now')
242 else:
243 ret = False
244
245 # Special case /var due to so many misuses currently.
246 if has_subdirs(os.path.join(root, 'var')):
247 if atom in BAD_VAR_PACKAGES:
248 logging.warning('Ignoring known bad /var install for now')
249 elif os.environ.get('PORTAGE_REPO_NAME') == 'portage-stable':
250 logging.warning('Ignoring bad /var install with portage-stable package '
251 'for now')
252 else:
253 ret = False
254 else:
255 if atom in BAD_VAR_PACKAGES:
256 logging.warning('Package has improved; please update BAD_VAR_PACKAGES')
257
258 # Special case /run due to so many misuses currently.
259 if has_subdirs(os.path.join(root, 'run')):
260 if atom in BAD_RUN_PACKAGES:
261 logging.warning('Ignoring known bad /run install for now')
262 elif os.environ.get('PORTAGE_REPO_NAME') == 'portage-stable':
263 logging.warning('Ignoring bad /run install with portage-stable package '
264 'for now')
265 else:
266 ret = False
267 else:
268 if atom in BAD_RUN_PACKAGES:
269 logging.warning('Package has improved; please update BAD_RUN_PACKAGES')
270
271 if not check_usr(os.path.join(root, 'usr'), host):
272 ret = False
273
274 return ret
275
276
277def get_current_package():
278 """Figure out what package is being built currently."""
279 if 'CATEGORY' in os.environ and 'PN' in os.environ:
280 return f'{os.environ.get("CATEGORY")}/{os.environ.get("PN")}'
281 else:
282 return None
283
284
285def get_parser():
286 """Get a CLI parser."""
287 parser = argparse.ArgumentParser(description=__doc__)
288 parser.add_argument('--host', default=None, action='store_true',
289 help='the filesystem is the host SDK, not board sysroot')
290 parser.add_argument('--board', dest='host', action='store_false',
291 help='the filesystem is a board sysroot')
292 parser.add_argument('root', nargs='?',
293 help='the rootfs to scan')
294 return parser
295
296
297def main(argv):
298 """The main func!"""
299 parser = get_parser()
300 opts = parser.parse_args(argv)
301
302 # Default to common portage env vars.
303 if opts.root is None:
304 for var in ('ED', 'D', 'ROOT'):
305 if var in os.environ:
306 logging.debug('Scanning filesystem root via $%s', var)
307 opts.root = os.environ[var]
308 break
309 if not opts.root:
310 parser.error('Need a valid rootfs to scan, but unable to detect one')
311
312 if opts.host is None:
313 if os.getenv('BOARD') == 'amd64-host':
314 opts.host = True
315 else:
316 opts.host = not bool(os.getenv('SYSROOT'))
317
318 if not check_root(opts.root, opts.host):
319 logging.critical(
320 "This package does not conform to CrOS's filesystem conventions. "
321 'Please review the paths flagged above and adjust its layout.')
322 return 1
323 else:
324 return 0
325
326
327if __name__ == '__main__':
328 sys.exit(main(sys.argv[1:]))