From 61088c907a7a2e7ae8027b201618cc8f000f1ecd Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Sat, 17 Oct 2026 08:42:48 +0200 Subject: [PATCH] Use rules_qemu for cross-architecture generators Use the released rules_qemu module [1] and its public user toolchain for cross-architecture seccomp generation. Register the four existing host and target pairs and run the generator directly through the selected emulator. Native generators and the fastbuild stub run directly. Remove the Debian archive extraction and host-specific emulator aliases. Audit the user toolchain's complete emulator archive family through Bazel's repository mappings. Bind each archive to QEMU's upstream notice [2], since the packaging repository's MIT license does not cover QEMU. [1]: https://github.com/hermeticbuild/rules_qemu/tree/de0cdb392 [2]: https://github.com/qemu/qemu/blob/98b060da3/COPYING Assisted-by: OpenAI Codex --- MODULE.bazel | 45 ++----- governance/licensing.yaml | 160 ++++++++++++++++++++++-- pkg/seccomp/precompiledseccomp/defs.bzl | 131 ++++++++++--------- tools/bazeldefs/BUILD | 12 +- tools/bazeldefs/defs.bzl | 65 ---------- tools/bazeldefs/extensions/deb_data.bzl | 22 ---- tools/bazeldefs/no_emulator.sh | 17 --- tools/defs.bzl | 4 +- tools/licensecheck/dependencies.yaml | 156 +++++++++++++++++++++-- tools/licensecheck/licensecheck.go | 149 +++++++++++++++++++--- tools/licensecheck/licensecheck_test.go | 85 +++++++++++++ 11 files changed, 592 insertions(+), 254 deletions(-) delete mode 100644 tools/bazeldefs/extensions/deb_data.bzl delete mode 100755 tools/bazeldefs/no_emulator.sh diff --git a/MODULE.bazel b/MODULE.bazel index 7b202a8c28f..474a8dc4814 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -15,6 +15,7 @@ bazel_dep(name = "rules_cc", version = "0.2.25") bazel_dep(name = "llvm", version = "0.8.24") bazel_dep(name = "libbpf", version = "1.7.0.bcr.1") bazel_dep(name = "rules_shell", version = "0.8.0") +bazel_dep(name = "rules_qemu", version = "0.3.0") bazel_dep(name = "grpc", version = "1.84.0", repo_name = "com_github_grpc_grpc") # gRPC's older minimum imports a removed Google APIs extension repository. @@ -198,42 +199,18 @@ http_file( ) # Statically-linked QEMU user-mode emulators. -QEMU_USER_EXPORTS = 'exports_files(["usr/bin/qemu-aarch64", "usr/bin/qemu-riscv64", "usr/bin/qemu-x86_64"])' - -deb_data = use_repo_rule("//tools/bazeldefs:extensions/deb_data.bzl", "deb_data") - -http_archive( - name = "qemu_user_amd64", - build_file_content = 'exports_files(["data.tar.xz"])', - sha256 = "ca6ede739327a20ae5a3498230c8a3a9a072c586e131bc6bcc1201eb1725e336", - type = "deb", - urls = [ - "https://snapshot.debian.org/archive/debian/20260905T205002Z/pool/main/q/qemu/qemu-user_10.0.13%2Bds-0%2Bdeb13u1_amd64.deb", - "https://deb.debian.org/debian/pool/main/q/qemu/qemu-user_10.0.13%2Bds-0%2Bdeb13u1_amd64.deb", - ], -) - -deb_data( - name = "qemu_user_amd64_files", - build_file_content = QEMU_USER_EXPORTS, - data = "@qemu_user_amd64//:data.tar.xz", -) - -http_archive( - name = "qemu_user_arm64", - build_file_content = 'exports_files(["data.tar.xz"])', - sha256 = "c73711af02b97cd5e2667e735d9c06890c57165517110ca4e646c95a7083158d", - type = "deb", - urls = [ - "https://snapshot.debian.org/archive/debian/20260905T205002Z/pool/main/q/qemu/qemu-user_10.0.13%2Bds-0%2Bdeb13u1_arm64.deb", - "https://deb.debian.org/debian/pool/main/q/qemu/qemu-user_10.0.13%2Bds-0%2Bdeb13u1_arm64.deb", - ], +qemu = use_extension("@rules_qemu//qemu/extension:qemu.bzl", "qemu") +use_repo( + qemu, + "qemu_system_toolchains", + "qemu_user_toolchains", ) -deb_data( - name = "qemu_user_arm64_files", - build_file_content = QEMU_USER_EXPORTS, - data = "@qemu_user_arm64//:data.tar.xz", +register_toolchains( + "@qemu_user_toolchains//:qemu_linux_aarch64_on_linux_x86_64", + "@qemu_user_toolchains//:qemu_linux_riscv64_on_linux_aarch64", + "@qemu_user_toolchains//:qemu_linux_riscv64_on_linux_x86_64", + "@qemu_user_toolchains//:qemu_linux_x86_64_on_linux_aarch64", ) # Root certificates. diff --git a/governance/licensing.yaml b/governance/licensing.yaml index 73c81bee151..737b0c7867d 100644 --- a/governance/licensing.yaml +++ b/governance/licensing.yaml @@ -60,15 +60,159 @@ exceptions: test containers and is not linked into released gVisor binaries. Its source archive and GPLv2 license are retained with the build inputs, and the test image includes the license text. - - dependency: qemu_user_amd64 + - dependency: qemu_user_prebuilt_linux_amd64_aarch64 license: GPL-2.0-only exception_rationale: >- - QEMU user-mode emulators for x86_64 build machines, run as a build tool - to execute binaries built for the target architecture when - cross-compiling. QEMU is neither linked into nor distributed with gVisor. - - dependency: qemu_user_arm64 + QEMU user-mode emulator for aarch64 target binaries on amd64 build + machines. The executable uses QEMU's GPL terms, not the MIT license of + the prebuilt repository's build scripts. Used only for cross-compilation; + QEMU is neither linked into nor distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_arm license: GPL-2.0-only exception_rationale: >- - QEMU user-mode emulators for arm64 build machines, run as a build tool - to execute binaries built for the target architecture when - cross-compiling. QEMU is neither linked into nor distributed with gVisor. + QEMU user-mode emulator for arm target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_i386 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for i386 target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_mips64 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for mips64 target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_ppc + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for ppc target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_ppc64le + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for ppc64le target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_riscv32 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for riscv32 target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_riscv64 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for riscv64 target binaries on amd64 build + machines. The executable uses QEMU's GPL terms, not the MIT license of + the prebuilt repository's build scripts. Used only for cross-compilation; + QEMU is neither linked into nor distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_s390x + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for s390x target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_amd64_x86_64 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for x86_64 target binaries on amd64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_aarch64 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for aarch64 target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_arm + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for arm target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_i386 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for i386 target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_mips64 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for mips64 target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_ppc + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for ppc target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_ppc64le + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for ppc64le target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_riscv32 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for riscv32 target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_riscv64 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for riscv64 target binaries on arm64 build + machines. The executable uses QEMU's GPL terms, not the MIT license of + the prebuilt repository's build scripts. Used only for cross-compilation; + QEMU is neither linked into nor distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_s390x + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for s390x target binaries on arm64 build + machines, declared by the user toolchain alongside the selected + cross-compilers. The executable uses QEMU's GPL terms, not the MIT + license of its build scripts. QEMU is neither linked into nor + distributed with gVisor. + - dependency: qemu_user_prebuilt_linux_arm64_x86_64 + license: GPL-2.0-only + exception_rationale: >- + QEMU user-mode emulator for x86_64 target binaries on arm64 build + machines. The executable uses QEMU's GPL terms, not the MIT license of + the prebuilt repository's build scripts. Used only for cross-compilation; + QEMU is neither linked into nor distributed with gVisor. diff --git a/pkg/seccomp/precompiledseccomp/defs.bzl b/pkg/seccomp/precompiledseccomp/defs.bzl index 3741d32f690..86acae45263 100644 --- a/pkg/seccomp/precompiledseccomp/defs.bzl +++ b/pkg/seccomp/precompiledseccomp/defs.bzl @@ -1,6 +1,45 @@ """Macro for precompiling seccomp-bpf programs.""" -load("//tools:defs.bzl", "go_binary", "select_arch", "target_emulator") +load("//tools:defs.bzl", "go_binary", "select_arch") + +_QEMU_TOOLCHAIN = "@rules_qemu//qemu:toolchain_type" + +def _run_generator_impl(ctx): + generator = ctx.attr.exec_generator or ctx.attr.generator + executable = generator[DefaultInfo].files_to_run + tools = [] + arguments = [] + qemu = None if ctx.attr.exec_generator else ctx.toolchains[_QEMU_TOOLCHAIN] + if qemu: + arguments.extend(ctx.attr.qemu_args) + tools.append(executable) + arguments.append(executable.executable.path) + executable = qemu.qemu + arguments.extend([ + "--package=" + ctx.attr.out_package_name, + "--out=" + ctx.outputs.out.path, + ]) + ctx.actions.run( + executable = executable, + outputs = [ctx.outputs.out], + tools = tools, + arguments = arguments, + mnemonic = "PrecompileSeccomp", + toolchain = _QEMU_TOOLCHAIN if qemu else None, + ) + return [DefaultInfo(files = depset([ctx.outputs.out]))] + +_run_generator = rule( + implementation = _run_generator_impl, + attrs = { + "generator": attr.label(executable = True, cfg = "target"), + "exec_generator": attr.label(executable = True, cfg = "exec"), + "out": attr.output(mandatory = True), + "out_package_name": attr.string(mandatory = True), + "qemu_args": attr.string_list(), + }, + toolchains = [config_common.toolchain_type(_QEMU_TOOLCHAIN, mandatory = False)], +) def precompiled_seccomp_rules( name, @@ -13,7 +52,7 @@ def precompiled_seccomp_rules( """Generates a Go source file containing precompiled seccomp-bpf programs. Args: - name: Name of the final genrule. + name: Name of the generated target. programs_to_compile_go_library: go_library target which describes the set of seccomp-bpf programs that you wish to precompile. This must define the following package-level function: @@ -28,7 +67,7 @@ def precompiled_seccomp_rules( out_package_name: Go package name that `out` belongs to. exclude_in_fastbuild: Whether to skip precompilation in fastbuild mode. The auto-generated `GetPrecompiled` function will fail all lookups. - tags: List of tags to pass to the generated genrule. + tags: List of tags to pass to the generated action. """ if exclude_in_fastbuild: native.config_setting( @@ -153,69 +192,25 @@ def precompiled_seccomp_rules( ], ) - # This genrule actually runs the go_binary we just declared, and writes - # its output (containing the precompiled rules) to the desired `out` file. - # - # The generator is a source (rather than a tool) of this genrule, so that - # it is built for the target platform rather than for the execution - # platform. If the execution platform's architecture differs from the - # target's (i.e. when cross-compiling), it is run under a user-mode - # emulator. If no emulator is configured for the target architecture, it - # is run directly, which works if the kernel is configured to run foreign - # binaries (binfmt_misc) and fails otherwise. - # - # Execution platform constraints are deliberately not used here: they would - # make cross-architecture configurations fail analysis wherever no - # execution platform for the target architecture is available. - emulator = target_emulator() - out_args = " --package='" + out_package_name + "' --out=$@" - run_gen_cmd = ( - "GEN=$(location :" + name + "_gen_bin); " + - "RUN=; " + - "if [ -n \"$$TARGET_ARCH\" ] && [ \"$$(uname -m)\" != \"$$TARGET_ARCH\" ]; then " + - " RUN=\"$$EMULATOR\"; " + - "fi; " + - "$$RUN \"$$GEN\"" + out_args + " || { " + - " echo \"Failed to run $$GEN (built for $$TARGET_ARCH) on $$(uname -m)" + - " (emulator: $${RUN:-none}).\" >&2; " + - " exit 1; " + - "}" - ) - run_gen_env = ( - "TARGET_ARCH=" + select_arch( - amd64 = "x86_64", - arm64 = "aarch64", - riscv64 = "riscv64", - default = "", - ) + "; " + - "EMULATOR='" + emulator.cmd + "'; " - ) + # Compile the real generator for the target platform and the stub for the + # execution platform. The action resolves QEMU for this target/exec pair. + generator = ":" + name + "_gen_bin" + exec_generator = None if exclude_in_fastbuild: - native.genrule( - name = name, - outs = [out], - srcs = select({ - ":" + name + "_fastbuild_cond": [], - "//conditions:default": [":" + name + "_gen_bin"], - }), - # The stubbed generator's output does not depend on the - # architecture, so it is built for the execution platform. - cmd = run_gen_env + select({ - ":" + name + "_fastbuild_cond": "$(location :" + name + "_gen_stubbed_bin)" + out_args, - "//conditions:default": run_gen_cmd, - }), - tools = select({ - ":" + name + "_fastbuild_cond": [":" + name + "_gen_stubbed_bin"], - "//conditions:default": [], - }) + emulator.tools, - tags = tags + ["requires-mem:16g"], - ) - else: - native.genrule( - name = name, - outs = [out], - srcs = [":" + name + "_gen_bin"], - cmd = run_gen_env + run_gen_cmd, - tools = emulator.tools, - tags = tags + ["requires-mem:16g"], - ) + generator = select({ + ":" + name + "_fastbuild_cond": None, + "//conditions:default": generator, + }) + exec_generator = select({ + ":" + name + "_fastbuild_cond": ":" + name + "_gen_stubbed_bin", + "//conditions:default": None, + }) + _run_generator( + name = name, + generator = generator, + exec_generator = exec_generator, + out = out, + out_package_name = out_package_name, + qemu_args = select_arch(arm64 = ["-cpu", "neoverse-n1"], default = []), + tags = tags + ["requires-mem:16g"], + ) diff --git a/tools/bazeldefs/BUILD b/tools/bazeldefs/BUILD index a5299921398..5818f842694 100644 --- a/tools/bazeldefs/BUILD +++ b/tools/bazeldefs/BUILD @@ -1,4 +1,4 @@ -load("//tools:defs.bzl", "bzl_library", "string_flag", "target_emulators") +load("//tools:defs.bzl", "bzl_library", "string_flag") package( default_applicable_licenses = ["//:license"], @@ -35,12 +35,6 @@ bzl_library( deps = [":defs_bzl"], ) -bzl_library( - name = "deb_data_bzl", - srcs = ["extensions/deb_data.bzl"], - visibility = ["//visibility:private"], -) - # Keep Go's pure/cgo mode in its build settings, not the target platform: # Bazel 9 also uses target constraints to select a test execution platform. platform( @@ -82,10 +76,6 @@ config_setting( ], ) -# User-mode emulators for each target architecture (qemu_*). -# See target_emulator in defs.bzl. -target_emulators(name = "qemu") - genrule( name = "version", outs = ["version.txt"], diff --git a/tools/bazeldefs/defs.bzl b/tools/bazeldefs/defs.bzl index 206c89ba107..05db769575b 100644 --- a/tools/bazeldefs/defs.bzl +++ b/tools/bazeldefs/defs.bzl @@ -96,71 +96,6 @@ def default_net_util(): def coreutil(): return [] # Nothing needed. -def target_emulator(): - """Returns the user-mode emulator that runs target binaries at build time. - - The emulators are pinned, statically-linked QEMU builds for the execution - platform's architecture (see target_emulators). - - Returns: - A struct with: - tools: labels to add to the tools of a genrule. - cmd: the emulator command to run target binaries with when the - execution platform's architecture differs from the target's, or an - empty string if there is none. This may reference `tools` using make - variables. - """ - return struct( - tools = select_arch( - amd64 = ["//tools/bazeldefs:qemu_x86_64"], - arm64 = ["//tools/bazeldefs:qemu_aarch64"], - riscv64 = ["//tools/bazeldefs:qemu_riscv64"], - default = [], - ), - cmd = select_arch( - amd64 = "$(execpath //tools/bazeldefs:qemu_x86_64)", - arm64 = "$(execpath //tools/bazeldefs:qemu_aarch64) -cpu neoverse-n1", - riscv64 = "$(execpath //tools/bazeldefs:qemu_riscv64)", - default = "", - ), - ) - -def target_emulators(name): - """Defines the user-mode emulators used by target_emulator. - - Each emulator is the pinned QEMU build for the execution platform's - architecture (see extensions/deb_data.bzl), or a stub that fails if there - is none. This must only be called from tools/bazeldefs/BUILD. - - Args: - name: prefix of the emulator targets, which are named _. - """ - native.alias( - name = name + "_aarch64", - actual = select({ - ":amd64": "@qemu_user_amd64_files//:usr/bin/qemu-aarch64", - "//conditions:default": ":no_emulator.sh", - }), - tags = ["manual"], - ) - native.alias( - name = name + "_riscv64", - actual = select({ - ":amd64": "@qemu_user_amd64_files//:usr/bin/qemu-riscv64", - ":arm64": "@qemu_user_arm64_files//:usr/bin/qemu-riscv64", - "//conditions:default": ":no_emulator.sh", - }), - tags = ["manual"], - ) - native.alias( - name = name + "_x86_64", - actual = select({ - ":arm64": "@qemu_user_arm64_files//:usr/bin/qemu-x86_64", - "//conditions:default": ":no_emulator.sh", - }), - tags = ["manual"], - ) - def _bpf_program_impl(ctx): resource_dir = ctx.attr._clang_resource_dir[DirectoryInfo] kernel_headers = ctx.attr._kernel_headers[DirectoryInfo] diff --git a/tools/bazeldefs/extensions/deb_data.bzl b/tools/bazeldefs/extensions/deb_data.bzl deleted file mode 100644 index 853e9ebb831..00000000000 --- a/tools/bazeldefs/extensions/deb_data.bzl +++ /dev/null @@ -1,22 +0,0 @@ -"""Repository rule that extracts the data archive of a Debian package.""" - -def _deb_data_impl(rctx): - rctx.extract(archive = rctx.path(rctx.attr.data)) - rctx.file("BUILD.bazel", rctx.attr.build_file_content) - -deb_data = repository_rule( - implementation = _deb_data_impl, - attrs = { - "build_file_content": attr.string( - doc = "Content of the BUILD file of the repository.", - mandatory = True, - ), - "data": attr.label( - doc = "The data archive (e.g. data.tar.xz) of a Debian package, " + - "as unpacked by http_archive(type = \"deb\").", - allow_single_file = True, - mandatory = True, - ), - }, - doc = """Extracts the files installed by a Debian package.""", -) diff --git a/tools/bazeldefs/no_emulator.sh b/tools/bazeldefs/no_emulator.sh deleted file mode 100755 index 3abb22171fb..00000000000 --- a/tools/bazeldefs/no_emulator.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -# Copyright 2026 The gVisor Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -echo "$0: no user-mode emulator is available for $(uname -m) build machines" >&2 -exit 1 diff --git a/tools/defs.bzl b/tools/defs.bzl index c04266e8064..fa91a06d7df 100644 --- a/tools/defs.bzl +++ b/tools/defs.bzl @@ -7,7 +7,7 @@ change for Google-internal and bazel-compatible rules. load("//tools:arch.bzl", "arch_genrule", "arch_transition_impl") load("//tools/bazeldefs:cc.bzl", _cc_binary = "cc_binary", _cc_flags_supplier = "cc_flags_supplier", _cc_grpc_library = "cc_grpc_library", _cc_library = "cc_library", _cc_proto_library = "cc_proto_library", _cc_test = "cc_test", _cc_toolchain = "cc_toolchain", _gbenchmark = "gbenchmark", _gbenchmark_internal = "gbenchmark_internal", _grpcpp = "grpcpp", _gtest = "gtest", _select_gtest = "select_gtest") -load("//tools/bazeldefs:defs.bzl", _BuildSettingInfo = "BuildSettingInfo", _bool_flag = "bool_flag", _bpf_program = "bpf_program", _build_test = "build_test", _bzl_library = "bzl_library", _coreutil = "coreutil", _default_net_util = "default_net_util", _expand_template = "expand_template", _more_shards = "more_shards", _most_shards = "most_shards", _proto_library = "proto_library", _select_arch = "select_arch", _select_system = "select_system", _short_path = "short_path", _string_flag = "string_flag", _target_emulator = "target_emulator", _target_emulators = "target_emulators", _version = "version") +load("//tools/bazeldefs:defs.bzl", _BuildSettingInfo = "BuildSettingInfo", _bool_flag = "bool_flag", _bpf_program = "bpf_program", _build_test = "build_test", _bzl_library = "bzl_library", _coreutil = "coreutil", _default_net_util = "default_net_util", _expand_template = "expand_template", _more_shards = "more_shards", _most_shards = "most_shards", _proto_library = "proto_library", _select_arch = "select_arch", _select_system = "select_system", _short_path = "short_path", _string_flag = "string_flag", _version = "version") load("//tools/bazeldefs:go.bzl", _cov_available = "cov_available", _gazelle = "gazelle", _go_binary = "go_binary", _go_binary_nogo_dep = "go_binary_nogo_dep", _go_cov = "go_cov", _go_grpc_and_proto_libraries = "go_grpc_and_proto_libraries", _go_imports = "go_imports", _go_library = "go_library", _go_path = "go_path", _go_proto_library = "go_proto_library", _go_reset_target = "go_reset_target", _go_test = "go_test", _gotsan_flag_values = "gotsan_flag_values", _gotsan_values = "gotsan_values", _select_goarch = "select_goarch", _select_goos = "select_goos") load("//tools/bazeldefs:pkg.bzl", _pkg_deb = "pkg_deb", _pkg_tar = "pkg_tar") load("//tools/bazeldefs:platforms.bzl", _default_platform = "default_platform", _platform_capabilities = "platform_capabilities", _platforms = "platforms", _save_restore_platforms = "save_restore_platforms") @@ -28,8 +28,6 @@ expand_template = _expand_template select_arch = _select_arch select_system = _select_system short_path = _short_path -target_emulator = _target_emulator -target_emulators = _target_emulators coreutil = _coreutil more_shards = _more_shards most_shards = _most_shards diff --git a/tools/licensecheck/dependencies.yaml b/tools/licensecheck/dependencies.yaml index f7a8291fcba..6726240f0b7 100644 --- a/tools/licensecheck/dependencies.yaml +++ b/tools/licensecheck/dependencies.yaml @@ -4,6 +4,8 @@ # Check completeness with ARGS=--mode=verify. # Entries whose license cannot be fetched automatically are maintained by hand and preserved # by --mode=fetch, so you can hand-edit the file for these. +# license_source selects an immutable upstream notice for a pinned binary artifact; +# changing its artifact URL/hash requires renewing that explicit source audit. - dependency: abseil-cpp version: https://github.com/abseil/abseil-cpp/releases/download/20260817.0/abseil-cpp-20260817.0.tar.gz retrieved: "2026-10-06" @@ -1151,18 +1153,146 @@ commit: 4eb9011f3e6d551d067d87c89f082261164fac31 sha256: 26dc6db523ee943af51fdfc292658a2dbe7046735654f1dc839745988f0fe26b license: Apache-2.0 -- dependency: qemu_user_amd64 - version: https://snapshot.debian.org/archive/debian/20260905T205002Z/pool/main/q/qemu/qemu-user_10.0.13%2Bds-0%2Bdeb13u1_amd64.deb - retrieved: "2026-10-05" - commit: "" - sha256: ca6ede739327a20ae5a3498230c8a3a9a072c586e131bc6bcc1201eb1725e336 +- dependency: qemu_user_prebuilt_linux_amd64_aarch64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-aarch64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: cc3bf42da8f219cd314a7806053c73cca166bf14c288daf5b6105e66590a6dee license: GPL-2.0-only -- dependency: qemu_user_arm64 - version: https://snapshot.debian.org/archive/debian/20260905T205002Z/pool/main/q/qemu/qemu-user_10.0.13%2Bds-0%2Bdeb13u1_arm64.deb - retrieved: "2026-10-05" - commit: "" - sha256: c73711af02b97cd5e2667e735d9c06890c57165517110ca4e646c95a7083158d + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_arm + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-arm-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 7efa7a2dfb1a325dd514e16b67d38a6ead24b0bd5326ac0b1b8e8b6667c01698 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_i386 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-i386-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 60cc85a81c4cea6072230211b0cc1742557a6d57f53638d03d8d3c655c366872 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_mips64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-mips64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 1f7d80af0b340b9ad7dec24e6ea01d7028a60e55ebfa1d7ac61a5418f4127621 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_ppc + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-ppc-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: cd90ee01c78cfe2065195076dc9f866cff91cc7aa39c07961eb4f82bbf9eec2a + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_ppc64le + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-ppc64le-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 21b1474ff3e9f05f2abca931659d70506000dd9413fb8662444f4ad739e3aa63 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_riscv32 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-riscv32-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 1d52e2a9e710a39eb7a7e0a6ee1a4b4a964625512cc75ebcd1e1b18c8678367b + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_riscv64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-riscv64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: b974c689b39e704696eed04500e9a2da4105f0b643e5119d01ef4826d6c7f0a0 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_s390x + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-s390x-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 6b0cc9d4afee60b5d5f960622aa77c926b80366f7454d027cf5c0c2e29712e86 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_amd64_x86_64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-amd64-x86_64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 911d8c4dd7ca3780fe11adfa8db52a397e319b4feb46398b2e7488590c8040b6 license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_aarch64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-aarch64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: fa6a9e126306ab3f06f718473425d6094b7336cac7d21a9328a36af673e5d0a4 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_arm + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-arm-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 56220cf250f745253c05f86d55dcccf2d4bcab61840e744a12bda781f735b035 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_i386 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-i386-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 7400ebdfa9020e86847320d0f412906afb0d634e09938792b36340aa0ecf67be + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_mips64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-mips64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 1d9aeb6c36c2cb23ffd8169393dc7de83122664a2d508d27f56a3369c49e413a + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_ppc + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-ppc-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 0572c14bf243f4c05aaa0170fea140a4763db2495cede7ec41f705ebf5981e3e + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_ppc64le + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-ppc64le-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: ab0cb3154f6fa2d6ad91f300e41af82e35b6f9722d99d28ddb787f31dbe399a0 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_riscv32 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-riscv32-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 1076332ae82dfac4102778c5aedbd45eed5f0386038812328855e7d802d148f4 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_riscv64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-riscv64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 463545d675e5df5be0cc493c6822cd00c8708f8bff0b98e6d4e5ad1dcb169e39 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_s390x + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-s390x-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: d864b9e065e41495b593fd0d5038e000f7e32b1a47eff3ae6d8c54a8c238e76d + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING +- dependency: qemu_user_prebuilt_linux_arm64_x86_64 + version: https://github.com/hermeticbuild/qemu-prebuilt/releases/download/11.0.0.1/qemu-user-linux-arm64-x86_64-11.0.0.1.tar.zst + retrieved: "2026-10-17" + commit: 98b060da3a4f92b2a994ead5b16a87e783baf77c + sha256: 58d31d097b72207702458bcd5927337e3428bad3f97d3075ff6c7b4ca13ecf50 + license: GPL-2.0-only + license_source: https://raw.githubusercontent.com/qemu/qemu/98b060da3a4f92b2a994ead5b16a87e783baf77c/COPYING - dependency: rules_cc version: https://github.com/bazelbuild/rules_cc/releases/download/0.2.25/rules_cc-0.2.25.tar.gz retrieved: "2026-10-02" @@ -1199,6 +1329,12 @@ commit: 6aad8828e82667cda09ff449862a5a8559bee27b sha256: 2119ca04726066c53b8f1ff42dffa090ea7f3e42a75679f1cfda937406cf7753 license: Apache-2.0 +- dependency: rules_qemu + version: https://github.com/hermeticbuild/rules_qemu/releases/download/v0.3.0/rules_qemu-v0.3.0.tar.gz + retrieved: "2026-10-17" + commit: de0cdb392c5a4095528122a0725d48eec9c6befd + sha256: cc9ee54a94849eb2467d7ba71773a28ee53f14a0a1d615f283a770d532df889b + license: Apache-2.0 - dependency: rules_shell version: https://github.com/bazel-contrib/rules_shell/releases/download/v0.8.0/rules_shell-v0.8.0.tar.gz retrieved: "2026-09-24" diff --git a/tools/licensecheck/licensecheck.go b/tools/licensecheck/licensecheck.go index 686d39b3fba..f478a50c83e 100644 --- a/tools/licensecheck/licensecheck.go +++ b/tools/licensecheck/licensecheck.go @@ -18,14 +18,16 @@ // mod` in the dockerized build environment): the root module's direct // bazel_deps, the http_archive/http_file repos declared in MODULE.bazel, and // the Go repos imported from the go_deps extension, unioned with go.mod's -// requirements. Fetch downloads each dependency's license text, from the Go -// module proxy for Go modules and from GitHub for everything else, classifies +// requirements, plus the user-mode archives exposed by the QEMU toolchain. +// Fetch downloads each dependency's license text from the Go module proxy +// for Go modules and from GitHub for everything else, classifies // it, and records it in a YAML file. Verify checks that the YAML file has an // entry for every dependency, without fetching any licenses. // // Entries whose license cannot be fetched automatically (e.g. // @google_root_pem) are maintained by hand: Fetch preserves an existing entry -// whenever fetching fails. +// whenever fetching fails. Explicit license_source notices are re-fetched on +// every audit and fail closed if the notice or its artifact binding is invalid. package licensecheck import ( @@ -70,6 +72,9 @@ type Entry struct { Commit string `yaml:"commit"` SHA256 string `yaml:"sha256"` License Licenses `yaml:"license"` + // LicenseSource pins the notice for an executable distributed by a + // differently licensed builder repository. It is bound to Version/SHA256. + LicenseSource string `yaml:"license_source,omitempty"` } // License is an SPDX license identifier (https://spdx.org/licenses/), one of @@ -201,6 +206,8 @@ const ( # Check completeness with ARGS=--mode=verify. # Entries whose license cannot be fetched automatically are maintained by hand and preserved # by --mode=fetch, so you can hand-edit the file for these. +# license_source selects an immutable upstream notice for a pinned binary artifact; +# changing its artifact URL/hash requires renewing that explicit source audit. ` dateFormat = "2006-01-02" fetchWorkers = 8 @@ -234,7 +241,7 @@ func Fetch(p Paths) error { defer wg.Done() sem <- struct{}{} defer func() { <-sem }() - f, err := fetchLicense(d) + f, err := fetchLicense(d, oldByName[d.name]) results[i] = result{f, err} }(i, d) } @@ -246,7 +253,11 @@ func Fetch(p Paths) error { for i, d := range deps { r := results[i] if r.err != nil { - if e, ok := oldByName[d.name]; ok && len(e.License) > 0 { + if e := oldByName[d.name]; e.LicenseSource != "" { + // Keep the explicit source so a later audit cannot silently + // fall back to the builder repository's license. + entries = append(entries, e) + } else if e, ok := oldByName[d.name]; ok && len(e.License) > 0 { fmt.Fprintf(os.Stderr, "%-60s %s (kept existing entry: %v)\n", d.name, e.License, r.err) entries = append(entries, e) continue @@ -256,12 +267,13 @@ func Fetch(p Paths) error { continue } e := Entry{ - Dependency: d.name, - Version: d.source(), - Retrieved: today, - Commit: r.fetched.commit, - SHA256: r.fetched.sha256, - License: r.fetched.license, + Dependency: d.name, + Version: d.source(), + Retrieved: today, + Commit: r.fetched.commit, + SHA256: r.fetched.sha256, + License: r.fetched.license, + LicenseSource: oldByName[d.name].LicenseSource, } if o, ok := oldByName[d.name]; ok && o.Version == e.Version && o.SHA256 == e.SHA256 { // Commit lookup is best-effort. A missing response does not @@ -335,6 +347,11 @@ func verifyProblems(deps []dep, entries []Entry) []string { case len(e.License) == 0: problems = append(problems, fmt.Sprintf("%s has no license", d.name)) default: + if commit, err := licenseSourceCommit(d, e); err != nil { + problems = append(problems, fmt.Sprintf("%s: %v", d.name, err)) + } else if commit != "" && commit != e.Commit { + problems = append(problems, fmt.Sprintf("%s: license_source commit does not match the audited commit", d.name)) + } if e.Version != d.source() { problems = append(problems, fmt.Sprintf("%s was audited at %q, but is now %q", d.name, e.Version, d.source())) } @@ -365,6 +382,11 @@ func verifyProblems(deps []dep, entries []Entry) []string { return problems } +// repoRef identifies a repository to audit and its stable catalog name. +type repoRef struct { + ref, name string +} + // enumerate returns all external dependencies, sorted by name. func enumerate(p Paths) ([]dep, error) { graphOut, err := makeMod("graph", "--depth", "1", "--extension_info=all", "--output", "json") @@ -376,10 +398,7 @@ func enumerate(p Paths) ([]dep, error) { return nil, err } // refs are the repos to audit: modules as name@version, everything else - // by apparent repo name. - type repoRef struct { - ref, name string - } + // by apparent or canonical repo name. var refs []repoRef for _, m := range graph.Dependencies { refs = append(refs, repoRef{m.Key, m.Name}) @@ -391,6 +410,27 @@ func enumerate(p Paths) ([]dep, error) { // http_archive/http_file declared directly in MODULE.bazel. case strings.HasSuffix(u.Key, "%go_deps"): // Go repos imported from the gazelle go_deps extension. + case strings.HasSuffix(u.Key, "//qemu/extension:qemu.bzl%qemu"): + // The public user toolchain refers to indirect archive repos. Audit + // its complete user-mode family, including unselected architectures. + root, err := readRepoMapping("") + if err != nil { + return nil, err + } + hub := root["qemu_user_toolchains"] + if hub == "" { + return nil, errors.New("QEMU user toolchain repository is not imported") + } + mapping, err := readRepoMapping(hub) + if err != nil { + return nil, err + } + qemuRefs, err := qemuUserRepoRefs(mapping) + if err != nil { + return nil, err + } + refs = append(refs, qemuRefs...) + continue default: // Toolchain extensions (crosstool, llvm_zlib, python, go_sdk, // ...) are not audited. @@ -469,6 +509,45 @@ func enumerate(p Paths) ([]dep, error) { return deps, nil } +// readRepoMapping asks Bazel for apparent-to-canonical repository names. +func readRepoMapping(repo string) (map[string]string, error) { + if repo == "" { + // Preserve the empty root repository argument through make's shell. + repo = "''" + } + out, err := makeMod("dump_repo_mapping", repo) + if err != nil { + return nil, err + } + start := strings.Index(out, "{") + if start < 0 { + return nil, fmt.Errorf("no repository mapping JSON in %q", out) + } + var mapping map[string]string + if err := json.Unmarshal([]byte(out[start:]), &mapping); err != nil { + return nil, fmt.Errorf("cannot parse repository mapping: %w", err) + } + return mapping, nil +} + +func qemuUserRepoRefs(mapping map[string]string) ([]repoRef, error) { + var refs []repoRef + for name, canonical := range mapping { + if !strings.HasPrefix(name, "qemu_user_prebuilt_") { + continue + } + if canonical == "" { + return nil, fmt.Errorf("QEMU archive %s has no canonical repository name", name) + } + refs = append(refs, repoRef{"@@" + canonical, name}) + } + if len(refs) == 0 { + return nil, errors.New("QEMU user toolchain exposes no emulator archives") + } + slices.SortFunc(refs, func(a, b repoRef) int { return strings.Compare(a.name, b.name) }) + return refs, nil +} + // makeMod runs `make mod TARGETS="..."`, which wraps `bazel mod`. func makeMod(args ...string) (string, error) { cmd := exec.Command("make", "-s", "mod", "OPTIONS=", "TARGETS="+strings.Join(args, " ")) @@ -645,7 +724,22 @@ type fetched struct { // fetchLicense returns the upstream commit, artifact hash, and licenses of a // dependency. -func fetchLicense(d dep) (*fetched, error) { +func fetchLicense(d dep, previous Entry) (*fetched, error) { + commit, err := licenseSourceCommit(d, previous) + if err != nil { + return nil, err + } + if commit != "" { + body, err := httpGet(previous.LicenseSource, nil) + if err != nil { + return nil, err + } + licenses, err := classify(string(body)) + if err != nil { + return nil, err + } + return &fetched{commit: commit, sha256: d.sha256, license: licenses}, nil + } if d.kind == kindGoModule { return fetchGoModule(d.name, d.version) } @@ -657,6 +751,29 @@ func fetchLicense(d dep) (*fetched, error) { return f, nil } +var artifactSHA256RE = regexp.MustCompile(`^[0-9a-f]{64}$`) + +// licenseSourceCommit requires immutable upstream notice bytes, not a mutable +// tag or the repository that happens to distribute the executable. +func licenseSourceCommit(d dep, e Entry) (string, error) { + if e.LicenseSource == "" { + return "", nil + } + if d.kind != kindArchive || !artifactSHA256RE.MatchString(d.sha256) || e.Version != d.source() || e.SHA256 != d.sha256 { + return "", errors.New("license_source artifact identity changed or is unpinned; audit the new artifact and refresh its notice source") + } + owner, repo, commit, err := parseGitHubURL(e.LicenseSource) + if err != nil || !commitRE.MatchString(commit) { + return "", errors.New("license_source must identify a GitHub notice pinned to a full commit") + } + prefix := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/", owner, repo, commit) + name, ok := strings.CutPrefix(e.LicenseSource, prefix) + if !ok || !slices.Contains(licenseFileNames, name) { + return "", errors.New("license_source must name a recognized raw GitHub root notice") + } + return commit, nil +} + // licenseFileNames are candidate root paths for GitHub raw-file requests. // Module ZIPs provide a directory listing and discover suffixed notices below. var licenseFileNames = []string{ diff --git a/tools/licensecheck/licensecheck_test.go b/tools/licensecheck/licensecheck_test.go index 71c9d2c1e3d..0e9bd50cb5c 100644 --- a/tools/licensecheck/licensecheck_test.go +++ b/tools/licensecheck/licensecheck_test.go @@ -413,3 +413,88 @@ func TestCheckPolicy(t *testing.T) { t.Errorf("CheckPolicy returned %d problems, want 5: %v", len(problems), problems) } } + +func TestLicenseSourceIdentity(t *testing.T) { + const artifact = "https://github.com/example/builder/releases/download/v1/tool.tar.gz" + const commit = "0123456789abcdef0123456789abcdef01234567" + const source = "https://raw.githubusercontent.com/example/upstream/" + commit + "/COPYING" + const hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + for _, test := range []struct { + name string + url string + hash string + source string + goModule bool + wantErr bool + }{ + {name: "pinned", url: artifact, hash: hash, source: source}, + {name: "automatic", url: artifact, hash: hash}, + {name: "changed artifact", url: artifact + "?changed", hash: hash, source: source, wantErr: true}, + {name: "changed digest", url: artifact, hash: strings.Repeat("a", 64), source: source, wantErr: true}, + {name: "unpinned artifact", url: artifact, source: source, wantErr: true}, + {name: "mutable notice", url: artifact, hash: hash, source: strings.ReplaceAll(source, commit, "main"), wantErr: true}, + {name: "notice traversal", url: artifact, hash: hash, source: strings.ReplaceAll(source, "/COPYING", "/../main/COPYING"), wantErr: true}, + {name: "encoded traversal", url: artifact, hash: hash, source: strings.ReplaceAll(source, "/COPYING", "/%2e%2e/main/COPYING"), wantErr: true}, + {name: "go module", url: artifact, hash: hash, source: source, goModule: true, wantErr: true}, + } { + t.Run(test.name, func(t *testing.T) { + d := dep{name: "tool", kind: kindArchive, url: test.url, sha256: test.hash} + if test.goModule { + d.kind = kindGoModule + } + e := Entry{Version: artifact, SHA256: hash, LicenseSource: test.source} + got, err := licenseSourceCommit(d, e) + if (err != nil) != test.wantErr { + t.Fatalf("licenseSourceCommit = %q, %v; want error %t", got, err, test.wantErr) + } + if !test.wantErr && test.source != "" && got != commit { + t.Fatalf("licenseSourceCommit = %q, want %q", got, commit) + } + }) + } +} + +func TestVerifyLicenseSource(t *testing.T) { + const commit = "0123456789abcdef0123456789abcdef01234567" + d := dep{name: "tool", kind: kindArchive, url: "https://example.com/tool.tar.gz", sha256: strings.Repeat("a", 64)} + e := Entry{Dependency: d.name, Version: d.url, SHA256: d.sha256, Retrieved: "2026-10-17", Commit: commit, License: Licenses{gpl2}, LicenseSource: "https://raw.githubusercontent.com/example/upstream/" + commit + "/COPYING"} + if problems := verifyProblems([]dep{d}, []Entry{e}); len(problems) != 0 { + t.Fatalf("verifyProblems = %v, want a valid explicit notice", problems) + } + e.Commit = strings.Repeat("b", 40) + if problems := verifyProblems([]dep{d}, []Entry{e}); len(problems) != 1 || !strings.Contains(problems[0], "license_source commit") { + t.Fatalf("verifyProblems = %v, want mismatched notice commit", problems) + } +} + +func TestQemuUserRepoRefs(t *testing.T) { + // A public hub maps indirect repos that the root module cannot import by + // apparent name. Firmware and system tools are outside this user-mode audit. + mapping := map[string]string{ + "qemu_user_prebuilt_linux_amd64_aarch64": "rules_qemu++qemu+arm", + "qemu_user_prebuilt_linux_arm64_x86_64": "rules_qemu++qemu+x86", + "qemu_user_toolchains": "rules_qemu++qemu+hub", + "qemu_system_data_prebuilt_linux_amd64": "rules_qemu++qemu+firmware", + "qemu_img_prebuilt_linux_amd64": "rules_qemu++qemu+img", + } + got, err := qemuUserRepoRefs(mapping) + if err != nil { + t.Fatal(err) + } + want := []repoRef{ + {"@@rules_qemu++qemu+arm", "qemu_user_prebuilt_linux_amd64_aarch64"}, + {"@@rules_qemu++qemu+x86", "qemu_user_prebuilt_linux_arm64_x86_64"}, + } + if !slices.Equal(got, want) { + t.Errorf("qemuUserRepoRefs() = %v, want %v", got, want) + } + for _, invalid := range []map[string]string{ + {}, + {"qemu_user_toolchains": "rules_qemu++qemu+hub"}, + {"qemu_user_prebuilt_linux_amd64_aarch64": ""}, + } { + if _, err := qemuUserRepoRefs(invalid); err == nil { + t.Errorf("qemuUserRepoRefs(%v) succeeded, want missing archive identity error", invalid) + } + } +}