Skip to content

Commit 1fd7854

Browse files
committed
feat: verify protoc as a validation action
Gives some protection against a repo accidentally registering a protoc binary on the toolchain that doesnt behave as expected. Based on #23539
1 parent bef042a commit 1fd7854

File tree

4 files changed

+55
-0
lines changed

4 files changed

+55
-0
lines changed

bazel/private/prebuilt_tool_integrity.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The checked in content is only here to allow load() statements in the sources to
88

99
# An arbitrary version of protobuf that includes pre-built binaries.
1010
# See /examples/example_without_cc_toolchain which uses this for testing.
11+
# TODO(alexeagle): add some automation to update this version occasionally.
1112
_TEST_VERSION = "v33.0"
1213
_TEST_SHAS = dict()
1314
# Add a couple platforms which are commonly used for testing.

bazel/private/proto_library_rule.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def _proto_library_impl(ctx):
113113
default_runfiles = ctx.runfiles(), # empty
114114
data_runfiles = data_runfiles,
115115
),
116+
OutputGroupInfo(_validation = ctx.attr._authenticity_validation[OutputGroupInfo]._validation),
116117
]
117118

118119
def _process_srcs(ctx, srcs, import_prefix, strip_import_prefix):
@@ -375,6 +376,10 @@ List of files containing extension declarations. This attribute is only allowed
375376
for use with MessageSet.
376377
""",
377378
),
379+
"_authenticity_validation": attr.label(
380+
default = "//bazel/private/toolchains/prebuilt:authenticity_validation",
381+
doc = "Validate that the binary registered on the toolchain is produced by protobuf team",
382+
),
378383
# buildifier: disable=attr-license (calling attr.license())
379384
"licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
380385
"_experimental_proto_descriptor_sets_include_source_info": attr.label(

bazel/private/toolchains/prebuilt/BUILD.bazel

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Ensures that Bazel only downloads required binaries for selected toolchains.
44
In particular, see comment below on the toolchain#toolchain attribute.
55
"""
66

7+
load(":protoc_authenticity.bzl", "protoc_authenticity")
78
load("//toolchain:platforms.bzl", "PROTOBUF_PLATFORMS")
89
[
910
toolchain(
@@ -18,3 +19,9 @@ load("//toolchain:platforms.bzl", "PROTOBUF_PLATFORMS")
1819
)
1920
for platform, meta in PROTOBUF_PLATFORMS.items()
2021
]
22+
23+
# Support verification of user-registered toolchains
24+
protoc_authenticity(
25+
name = "authenticity_validation",
26+
visibility = ["//visibility:public"],
27+
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"Validate that the protoc binary is authentic and not spoofed by a malicious actor."
2+
load("//bazel/common:proto_common.bzl", "proto_common")
3+
load("//bazel/private:toolchain_helpers.bzl", "toolchains")
4+
load("//bazel/private:prebuilt_tool_integrity.bzl", "RELEASE_VERSION")
5+
6+
def _protoc_authenticity_impl(ctx):
7+
# When this flag is disabled, then users have no way to replace the protoc binary with their own toolchain registration.
8+
# Therefore there's no validation to perform.
9+
if not proto_common.INCOMPATIBLE_ENABLE_PROTO_TOOLCHAIN_RESOLUTION:
10+
return []
11+
toolchain = ctx.toolchains[toolchains.PROTO_TOOLCHAIN]
12+
if not toolchain:
13+
fail("Protocol compiler toolchain could not be resolved.")
14+
proto_lang_toolchain_info = toolchain.proto
15+
validation_output = ctx.actions.declare_file("validation_output.txt")
16+
17+
ctx.actions.run_shell(
18+
outputs = [validation_output],
19+
tools = [proto_lang_toolchain_info.proto_compiler],
20+
command = """\
21+
{protoc} --version > {validation_output}
22+
grep -q "^libprotoc {RELEASE_VERSION}" {validation_output} || {{
23+
echo >&2 'ERROR: protoc version does not match protobuf Bazel module; we do not support this. To suppress this error, run Bazel with --norun_validations'
24+
exit 1
25+
}}
26+
""".format(protoc = proto_lang_toolchain_info.proto_compiler.executable.path, validation_output = validation_output.path, RELEASE_VERSION = RELEASE_VERSION.removeprefix("v")),
27+
)
28+
return [OutputGroupInfo(_validation = depset([validation_output]))]
29+
30+
protoc_authenticity = rule(
31+
implementation = _protoc_authenticity_impl,
32+
fragments = ["proto"],
33+
attrs = toolchains.if_legacy_toolchain({
34+
"_proto_compiler": attr.label(
35+
cfg = "exec",
36+
executable = True,
37+
allow_files = True,
38+
default = "//src/google/protobuf/compiler:protoc_minimal",
39+
),
40+
}),
41+
toolchains = toolchains.use_toolchain(toolchains.PROTO_TOOLCHAIN),
42+
)

0 commit comments

Comments
 (0)