|
| 1 | +"Validate that the protoc binary is authentic and not spoofed by a malicious actor." |
| 2 | + |
| 3 | +load("//bazel/common:proto_common.bzl", "proto_common") |
| 4 | +load("//bazel/private:prebuilt_tool_integrity.bzl", "RELEASE_VERSION") |
| 5 | +load("//bazel/private:toolchain_helpers.bzl", "toolchains") |
| 6 | + |
| 7 | +def _protoc_authenticity_impl(ctx): |
| 8 | + # When this flag is disabled, then users have no way to replace the protoc binary with their own toolchain registration. |
| 9 | + # Therefore there's no validation to perform. |
| 10 | + if not proto_common.INCOMPATIBLE_ENABLE_PROTO_TOOLCHAIN_RESOLUTION: |
| 11 | + return [OutputGroupInfo(_validation = depset())] |
| 12 | + toolchain = ctx.toolchains[toolchains.PROTO_TOOLCHAIN] |
| 13 | + if not toolchain: |
| 14 | + fail("Protocol compiler toolchain could not be resolved.") |
| 15 | + proto_lang_toolchain_info = toolchain.proto |
| 16 | + validation_output = ctx.actions.declare_file("validation_output.txt") |
| 17 | + |
| 18 | + ctx.actions.run_shell( |
| 19 | + mnemonic = "ProtocAuthenticityCheck", |
| 20 | + outputs = [validation_output], |
| 21 | + tools = [proto_lang_toolchain_info.proto_compiler], |
| 22 | + command = """\ |
| 23 | + {protoc} --version > {validation_output} |
| 24 | + grep -q -e "-dev$" {validation_output} && {{ |
| 25 | + echo 'WARNING: Detected a development version of protoc. |
| 26 | + Development versions are not validated for authenticity. |
| 27 | + To ensure a secure build, please use a released version of protoc.' |
| 28 | + exit 0 |
| 29 | + }} |
| 30 | + grep -q "^libprotoc {RELEASE_VERSION}" {validation_output} || {{ |
| 31 | + echo '{severity}: protoc version does not match protobuf Bazel module; we do not support this. |
| 32 | + It is considered undefined behavior that is expected to break in the future even if it appears to work today.' |
| 33 | + echo '{suppression_note}' |
| 34 | + echo 'Expected: libprotoc {RELEASE_VERSION}' |
| 35 | + echo -n 'Actual: ' |
| 36 | + cat {validation_output} |
| 37 | + exit {mismatch_exit_code} |
| 38 | + }} >&2 |
| 39 | + """.format( |
| 40 | + protoc = proto_lang_toolchain_info.proto_compiler.executable.path, |
| 41 | + validation_output = validation_output.path, |
| 42 | + RELEASE_VERSION = RELEASE_VERSION.removeprefix("v"), |
| 43 | + suppression_note = ( |
| 44 | + "To suppress this error, run Bazel with --@com_google_protobuf//bazel/toolchains:allow_nonstandard_protoc" if ctx.attr.fail_on_mismatch else "" |
| 45 | + ), |
| 46 | + mismatch_exit_code = 1 if ctx.attr.fail_on_mismatch else 0, |
| 47 | + severity = "ERROR" if ctx.attr.fail_on_mismatch else "INFO", |
| 48 | + ), |
| 49 | + ) |
| 50 | + return [OutputGroupInfo(_validation = depset([validation_output]))] |
| 51 | + |
| 52 | +protoc_authenticity = rule( |
| 53 | + implementation = _protoc_authenticity_impl, |
| 54 | + fragments = ["proto"], |
| 55 | + attrs = { |
| 56 | + "fail_on_mismatch": attr.bool( |
| 57 | + default = True, |
| 58 | + doc = "If true, the build will fail when the protoc binary does not match the expected version.", |
| 59 | + ), |
| 60 | + } | toolchains.if_legacy_toolchain({ |
| 61 | + "_proto_compiler": attr.label( |
| 62 | + cfg = "exec", |
| 63 | + executable = True, |
| 64 | + allow_files = True, |
| 65 | + default = "//src/google/protobuf/compiler:protoc_minimal", |
| 66 | + ), |
| 67 | + }), |
| 68 | + toolchains = toolchains.use_toolchain(toolchains.PROTO_TOOLCHAIN), |
| 69 | +) |
0 commit comments