Skip to content

Commit db89d4b

Browse files
dvyukovcopybara-github
authored andcommitted
Add cc_sandboxed_library rule
The rule is supposed to be complete transparent drop-in replacement for cc_library rules. Implementation outline: 1. Run clang generator tool in the new replacement mode. In this mode it takes all functions declared in the library public headers, and generates 3 files: - Guest header file with wrappers for the public library functions. These wrappers have similar signature to the original functions, but also have some differences. For example, absl::string_view would be passed as a (const char*, size_t) pair of arguments so that SAPI supports it. - Guest source file with implementation of the wrappers that call original functions. These wrappers will construct the string_view back from the pair of arguments. - Host source file with implementation of the public library functions. These implementations use SAPI sandbox to call the generated guest wrappers in the sandbox, and will deconstruct string_view into the pair of arguments. 2. Build cc_library with the guest header and source files and dependency on the original library. 3. Create a sapi_library for the guest library created at step 2. This library also links in the generated host source file, so that it implements the original library interface verbatim. 4. Create a transparent replacement rule that pretends to be a cc_library by assembling CcInfo from compilation context of the original library and linking context of the sapi_library created at step 3. Using the compilation context of the original library ensures that during compilation the replacement library "looks" exactly as the original library (this includes any use of defines/includes and any other cc_library attributes). PiperOrigin-RevId: 792088247 Change-Id: I1d641d75312a4e24d902347fab3a0ae828eb4159
1 parent 28b102b commit db89d4b

12 files changed

Lines changed: 1041 additions & 31 deletions

File tree

sandboxed_api/bazel/sapi.bzl

Lines changed: 259 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
1818
load("@rules_cc//cc:cc_library.bzl", "cc_library")
19+
load("@rules_cc//cc:cc_test.bzl", "cc_test")
1920
load("//sandboxed_api/bazel:build_defs.bzl", "sapi_platform_copts")
2021
load("//sandboxed_api/bazel:embed_data.bzl", "sapi_cc_embed_data")
2122
load(
@@ -72,6 +73,42 @@ def sort_deps(deps):
7273
other_deps = [x for x in deps if not x.startswith(":")]
7374
return sorted(colon_deps) + sorted(other_deps)
7475

76+
def _clang_generator_flags(cc_ctx, cpp_toolchain):
77+
flags = []
78+
79+
# TODO(cblichmann): Get language standard from the toolchain
80+
flags.append("--extra-arg=-std=c++17")
81+
82+
# Disable warnings in parsed code
83+
flags.append("--extra-arg=-Wno-everything")
84+
flags += ["--extra-arg=-D{}".format(d) for d in cc_ctx.defines.to_list()]
85+
flags += ["--extra-arg=-isystem{}".format(i) for i in cc_ctx.system_includes.to_list()]
86+
flags += ["--extra-arg=-iquote{}".format(i) for i in cc_ctx.quote_includes.to_list()]
87+
flags += ["--extra-arg=-I{}".format(d) for d in cc_ctx.includes.to_list()]
88+
return flags
89+
90+
def _lib_direct_headers(lib, cc_ctx):
91+
headers = []
92+
for h in cc_ctx.direct_headers:
93+
if h.extension != "h" or "/PROTECTED/" in h.path:
94+
continue
95+
96+
# Include only headers coming from the target
97+
# not ones that it depends on by comparing the label packages.
98+
if (h.owner.package == lib.label.package):
99+
headers.append(h.path)
100+
101+
return headers
102+
103+
def _clang_format_file(src, out, **kwargs):
104+
native.genrule(
105+
name = "_format_" + out,
106+
srcs = [":" + src],
107+
outs = [out],
108+
cmd = "cp $< $@",
109+
**kwargs
110+
)
111+
75112
def _sapi_interface_impl(ctx):
76113
cpp_toolchain = find_cpp_toolchain(ctx)
77114
generator = select_generator(ctx)
@@ -113,16 +150,7 @@ def _sapi_interface_impl(ctx):
113150

114151
if use_clang_generator:
115152
input_files += cpp_toolchain.all_files.to_list()
116-
117-
# TODO(cblichmann): Get language standard from the toolchain
118-
extra_flags.append("--extra-arg=-std=c++17")
119-
120-
# Disable warnings in parsed code
121-
extra_flags.append("--extra-arg=-Wno-everything")
122-
extra_flags += ["--extra-arg=-D{}".format(d) for d in cc_ctx.defines.to_list()]
123-
extra_flags += ["--extra-arg=-isystem{}".format(i) for i in cc_ctx.system_includes.to_list()]
124-
extra_flags += ["--extra-arg=-iquote{}".format(i) for i in cc_ctx.quote_includes.to_list()]
125-
extra_flags += ["--extra-arg=-I{}".format(d) for d in cc_ctx.includes.to_list()]
153+
extra_flags += _clang_generator_flags(cc_ctx, cpp_toolchain)
126154
else:
127155
append_all(extra_flags, "-D", cc_ctx.defines.to_list())
128156
append_all(extra_flags, "-isystem", cc_ctx.system_includes.to_list())
@@ -135,14 +163,7 @@ def _sapi_interface_impl(ctx):
135163
input_files_paths.append(f.path)
136164
else:
137165
# Try to find files automatically
138-
for h in cc_ctx.direct_headers:
139-
if h.extension != "h" or "/PROTECTED/" in h.path:
140-
continue
141-
142-
# Include only headers coming from the target
143-
# not ones that it depends on by comparing the label packages.
144-
if (h.owner.package == ctx.attr.lib.label.package):
145-
input_files_paths.append(h.path)
166+
input_files_paths += _lib_direct_headers(ctx.attr.lib, cc_ctx)
146167

147168
if use_clang_generator:
148169
args += extra_flags + input_files_paths
@@ -231,6 +252,16 @@ def symbol_list_gen(name, lib, out, **kwargs):
231252
**kwargs
232253
)
233254

255+
def _common_kwargs(tags, visibility, compatible_with):
256+
common = {
257+
"tags": tags,
258+
}
259+
if visibility:
260+
common["visibility"] = visibility
261+
if compatible_with != None:
262+
common["compatible_with"] = compatible_with
263+
return common
264+
234265
def sapi_library(
235266
name,
236267
lib,
@@ -298,15 +329,7 @@ def sapi_library(
298329
exec_properties: Dict of executable properties to be passed to the generated binary targets.
299330
"""
300331

301-
common = {
302-
"tags": tags,
303-
}
304-
if visibility:
305-
common["visibility"] = visibility
306-
307-
if compatible_with != None:
308-
common["compatible_with"] = compatible_with
309-
332+
common = _common_kwargs(tags, visibility, compatible_with)
310333
generated_header = name + ".sapi.h"
311334

312335
# Reference (pull into the archive) required functions only. If the functions'
@@ -350,7 +373,7 @@ def sapi_library(
350373
**common
351374
)
352375

353-
native.cc_binary(
376+
cc_binary(
354377
name = name + ".bin",
355378
linkopts = [
356379
"-ldl", # For dlopen(), dlsym()
@@ -393,7 +416,7 @@ def sapi_library(
393416
lib = lib,
394417
functions = functions,
395418
input_files = input_files,
396-
out = generated_header,
419+
out = generated_header + ".unformatted",
397420
embed_name = embed_name,
398421
embed_dir = embed_dir,
399422
namespace = namespace,
@@ -402,3 +425,210 @@ def sapi_library(
402425
limit_scan_depth = limit_scan_depth,
403426
**common
404427
)
428+
429+
_clang_format_file(generated_header + ".unformatted", generated_header, **common)
430+
431+
def cc_sandboxed_library(
432+
name,
433+
lib,
434+
tags = [],
435+
visibility = None,
436+
compatible_with = None):
437+
"""Creates a sandboxed drop-in replacement cc_library.
438+
439+
NOTE: this functionality is experimental and may change in the future.
440+
441+
The resulting library can be used instead of the original cc_library
442+
as dependency for other targets. The behavior is supposed to be identical
443+
to the original library, except that the library is sandboxed with a single
444+
global sandbox instance.
445+
446+
Only limited set of types is supported in signatures of the library public
447+
functions. Any unsupported types will cause build failure.
448+
449+
Any crashes or violations in the sandbox process crash the host process.
450+
451+
Args:
452+
name: Name of the target
453+
lib: Label of the cc_library target to sandbox
454+
tags: Same as cc_library.tags
455+
visibility: Same as cc_library.visibility
456+
compatible_with: Same as cc_library.compatible_with
457+
"""
458+
459+
# Implementation outline:
460+
# 1. Run clang generator tool in sandboxed library mode.
461+
# In this mode it takes all functions declared in the library public headers,
462+
# and generates 3 files:
463+
# - Sandboxee header file with wrappers for the public library functions.
464+
# These wrappers have similar signature to the original functions,
465+
# but also have some differences. For example, absl::string_view would be passed
466+
# as a (const char*, size_t) pair of arguments so that SAPI supports it.
467+
# - Sandboxee source file with implementation of the wrappers that call original functions.
468+
# These wrappers will construct the string_view back from the pair of arguments.
469+
# - Host source file with implementation of the public library functions.
470+
# These implementations use SAPI sandbox to call the generated sandboxee wrappers
471+
# in the sandbox, and will deconstruct string_view into the pair of arguments.
472+
# 2. Build cc_library with the sandboxee header and source files
473+
# and dependency on the original library.
474+
# 3. Create a sapi_library for the sandboxee library created at step 2.
475+
# This library also links in the generated host source file,
476+
# so that it implements the original library interface verbatim.
477+
# 4. Create a transparent replacement rule that pretends to be a cc_library
478+
# by assembling CcInfo from compilation context of the original library
479+
# and linking context of the sapi_library created at step 3.
480+
# Using the compilation context of the original library ensures that during
481+
# compilation the replacement library "looks" exactly as the original library
482+
# (this includes any use of defines/includes and any other cc_library attributes).
483+
484+
# Unique prefix for things generated by sapi_library (e.g. FooSandbox class name).
485+
# TODO(dvyukov): add hash/flattening of the full library /path:name, just the name is not
486+
# necessarily globally unique.
487+
wrapper_name = "Sapi" + name
488+
common = _common_kwargs(tags, visibility, compatible_with)
489+
490+
_sandboxed_library_gen(
491+
name = "_sandboxed_library_gen_" + name,
492+
lib = lib,
493+
lib_name = wrapper_name,
494+
sandboxee_hdr_out = name + ".sapi.sandboxee.h.unformatted",
495+
sandboxee_src_out = name + ".sapi.sandboxee.cc.unformatted",
496+
host_src_out = name + ".sapi.host.cc.unformatted",
497+
sapi_hdr = native.package_name() + "/_sapi_" + name + ".sapi.h",
498+
**common
499+
)
500+
501+
_clang_format_file(name + ".sapi.sandboxee.h.unformatted", name + ".sapi.sandboxee.h", **common)
502+
_clang_format_file(name + ".sapi.sandboxee.cc.unformatted", name + ".sapi.sandboxee.cc", **common)
503+
_clang_format_file(name + ".sapi.host.cc.unformatted", name + ".sapi.host.cc", **common)
504+
505+
cc_library(
506+
name = "_sapi_sandboxee_" + name,
507+
hdrs = [":" + name + ".sapi.sandboxee.h"],
508+
srcs = [":" + name + ".sapi.sandboxee.cc"],
509+
# Work-around broken global sapi_library mode (when functions are empty).
510+
# When functions are not empty, sapi_library adds -Wl,-u linker flags
511+
# that force linking of the sandboxee library. In global mode, it won't be linked.
512+
alwayslink = 1,
513+
deps = [
514+
lib,
515+
"//sandboxed_api:lenval_core",
516+
],
517+
**common
518+
)
519+
520+
sapi_library(
521+
name = "_sapi_" + name,
522+
lib = ":_sapi_sandboxee_" + name,
523+
lib_name = wrapper_name,
524+
srcs = [name + ".sapi.host.cc"],
525+
generator_version = 2,
526+
deps = [
527+
"//sandboxed_api:lenval_core",
528+
"@abseil-cpp//absl/log:check",
529+
],
530+
**common
531+
)
532+
533+
_sandboxed_library(
534+
name = name,
535+
lib = lib,
536+
sapi = ":_sapi_" + name,
537+
**common
538+
)
539+
540+
_sandboxed_library = rule(
541+
provides = [CcInfo],
542+
attrs = {
543+
"lib": attr.label(providers = [CcInfo]),
544+
"sapi": attr.label(providers = [CcInfo]),
545+
},
546+
implementation = lambda ctx: [CcInfo(
547+
compilation_context = ctx.attr.lib[CcInfo].compilation_context,
548+
linking_context = ctx.attr.sapi[CcInfo].linking_context,
549+
)],
550+
)
551+
552+
def cc_sandboxed_library_test(
553+
name,
554+
lib,
555+
sandboxed_lib,
556+
deps = [],
557+
**kwargs):
558+
"""Creates a pair of sandboxed/unsandboxed cc_test's for cc_sandboxed_library.
559+
560+
NOTE: this functionality is experimental and may change in the future.
561+
562+
This rule is supposed to be a replacement for any cc_test's for a library
563+
that is used with cc_sandboxed_library. It creates a pair of cc_test's
564+
that test both sandboxed and unsandboxed versions of the library.
565+
566+
Args:
567+
name: Name of the target
568+
lib: Label of the normal unsandboxed cc_library target
569+
sandboxed_lib: Label of the cc_sandboxed_library target for the lib
570+
deps: Same as cc_library.deps, must not include lib/sandboxed_lib
571+
**kwargs: Passed to resulting cc_test's
572+
"""
573+
574+
cc_test(
575+
name = name + "_unsandboxed",
576+
deps = deps + [lib],
577+
**kwargs
578+
)
579+
580+
cc_test(
581+
name = name + "_sandboxed",
582+
deps = deps + [sandboxed_lib],
583+
**kwargs
584+
)
585+
586+
native.test_suite(
587+
name = name,
588+
tests = [
589+
":" + name + "_unsandboxed",
590+
":" + name + "_sandboxed",
591+
],
592+
)
593+
594+
def _sandboxed_library_gen_impl(ctx):
595+
cpp_toolchain = find_cpp_toolchain(ctx)
596+
cc_ctx = ctx.attr.lib[CcInfo].compilation_context
597+
598+
args = []
599+
args.append("--sandboxed_library_gen")
600+
args.append("--sapi_name={}".format(ctx.attr.lib_name))
601+
args.append("--sandboxee_hdr_out={}".format(ctx.outputs.sandboxee_hdr_out.path))
602+
args.append("--sandboxee_src_out={}".format(ctx.outputs.sandboxee_src_out.path))
603+
args.append("--host_src_out={}".format(ctx.outputs.host_src_out.path))
604+
args.append("--sapi_out={}".format(ctx.attr.sapi_hdr))
605+
args.append("--sapi_limit_scan_depth")
606+
args += _clang_generator_flags(cc_ctx, cpp_toolchain)
607+
args += _lib_direct_headers(ctx.attr.lib, cc_ctx)
608+
609+
progress_msg = "Generating sandboxed library {}.".format(ctx.attr.lib_name)
610+
ctx.actions.run(
611+
inputs = cc_ctx.headers.to_list() + cpp_toolchain.all_files.to_list(),
612+
outputs = [ctx.outputs.sandboxee_hdr_out, ctx.outputs.sandboxee_src_out, ctx.outputs.host_src_out],
613+
arguments = args,
614+
mnemonic = "SandboxedLibraryGen",
615+
progress_message = progress_msg,
616+
executable = ctx.executable._generator,
617+
)
618+
619+
# Build rule that generates SAPI interface.
620+
_sandboxed_library_gen = rule(
621+
implementation = _sandboxed_library_gen_impl,
622+
attrs = {
623+
"lib": attr.label(providers = [CcInfo]),
624+
"lib_name": attr.string(),
625+
"sandboxee_hdr_out": attr.output(),
626+
"sandboxee_src_out": attr.output(),
627+
"host_src_out": attr.output(),
628+
"sapi_hdr": attr.string(),
629+
"_generator": make_exec_label(
630+
"//sandboxed_api/tools/clang_generator:generator_tool",
631+
),
632+
},
633+
toolchains = use_cpp_toolchain(),
634+
)

sandboxed_api/testcases/BUILD

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
load("@rules_cc//cc:cc_library.bzl", "cc_library")
16+
load("//sandboxed_api/bazel:sapi.bzl", "cc_sandboxed_library", "cc_sandboxed_library_test")
17+
18+
package(default_visibility = ["//visibility:private"])
19+
20+
licenses(["notice"])
21+
22+
cc_library(
23+
name = "replaced_library",
24+
srcs = ["replaced_library.cc"],
25+
hdrs = ["replaced_library.h"],
26+
deps = [
27+
"@abseil-cpp//absl/strings:string_view",
28+
],
29+
)
30+
31+
cc_sandboxed_library(
32+
name = "replacement_library",
33+
lib = ":replaced_library",
34+
)
35+
36+
cc_sandboxed_library_test(
37+
name = "replaced_test",
38+
srcs = ["replaced_library_test.cc"],
39+
lib = ":replaced_library",
40+
sandboxed_lib = ":replacement_library",
41+
# TSan/MSan may change ABI and name mangling, but the clang tool is not invoked
42+
# correctly (the same way as the actual build) and produces wrong symbol names.
43+
tags = [
44+
"nomsan",
45+
"notsan",
46+
],
47+
deps = ["@googletest//:gtest_main"],
48+
)

0 commit comments

Comments
 (0)