Merge branch 'main' into zip

This commit is contained in:
Christian Blichmann 2022-03-15 12:02:40 +01:00 committed by GitHub
commit 071cb79268
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 691 additions and 302 deletions

View File

@ -12,7 +12,8 @@ jobs:
matrix:
include:
- container: fedora:35
compiler: gcc # GCC 11
compiler: gcc
compiler-version: 11 # Only used in cache action so far
ignore-errors: true # Stack trace test fails on Fedora (issue #118)
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.ignore-errors }}
@ -23,6 +24,13 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v2
with:
key: ${{matrix.container}}-${{matrix.compiler}}${{matrix.compiler-version}}
path: |
${{github.workspace}}/_deps
- name: Prepare container
# Note: For the sandbox tests to work, we need a privileged, unconfined
# container that retains its capabilities.

View File

@ -42,6 +42,13 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Cache dependencies
uses: actions/cache@v2
with:
key: ${{matrix.os}}-${{matrix.compiler}}${{matrix.compiler-version}}
path: |
${{github.workspace}}/_deps
- name: Install ninja-build tool
uses: seanmiddleditch/gha-setup-ninja@v3

View File

@ -10,12 +10,12 @@ Directory | Project
`c-blosc/` | c-blosc - A blocking, shuffling and loss-less compression library | [github.com/Blosc/c-blosc](https://github.com/Blosc/c-blosc) | CMake
`hunspell/` | Hunspell - The most popular spellchecking library | [github.com/hunspell/hunspell](https://github.com/hunspell/hunspell) | CMake
`jsonnet/` | Jsonnet - The Data Templating Language | [github.com/google/jsonnet](https://github.com/google/jsonnet) | CMake
`libidn2/` | libidn2 - GNU IDN library | [www.gnu.org/software/libidn/#libidn2](https://www.gnu.org/software/libidn/#libidn2) | CMake
`libzip/` | libzip - operations on zip archives | [github.com/nih-at/libzip](https://github.com/nih-at/libzip) | CMake
`pffft/` | PFFFT - a pretty fast Fourier Transform | [bitbucket.org/jpommier/pffft.git](https://bitbucket.org/jpommier/pffft.git) | CMake
`turbojpeg/` | High-level JPEG library | [libjpeg-turbo.org/About/TurboJPEG](https://libjpeg-turbo.org/About/TurboJPEG) | CMake
`zopfli` | Zopfli - Compression Algorithm | [github.com/google/zopfli](https://github.com/google/zopfli) | CMake
`zstd/` | Zstandard - Fast real-time compression algorithm | [github.com/facebook/zstd](https://github.com/facebook/zstd) | CMake
`libidn2/` | libidn2 - GNU IDN library | [www.gnu.org/software/libidn/#libidn2](https://www.gnu.org/software/libidn/#libidn2) | CMake
`turbojpeg/` | High-level JPEG library | [libjpeg-turbo.org/About/TurboJPEG](https://libjpeg-turbo.org/About/TurboJPEG) | CMake
## Projects Shipping with Sandboxed API Sandboxes

View File

@ -28,13 +28,12 @@ if(NOT TARGET sapi::sapi)
)
endif()
set(HIDE_SYMBOLS off)
set(HIDE_SYMBOLS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
libblosc
GIT_REPOSITORY https://github.com/Blosc/c-blosc.git
GIT_TAG a0e5c18d37db8e6f1003254a574c8062c5b45e00
PATCH_COMMAND patch < "${CMAKE_SOURCE_DIR}/patches/c-blosc.cmake.patch" && cd blosc && patch < "${CMAKE_SOURCE_DIR}/patches/c-blosc.blosc.cmake.patch"
GIT_TAG 5b68ad8a6c25e7f013e78b44e8b3bf3b5a6b9cc7
)
FetchContent_MakeAvailable(libblosc)

View File

@ -1,19 +0,0 @@
# This is patch has a pending PR: https://github.com/Blosc/c-blosc/pull/329
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -3,10 +3,9 @@ add_definitions(-DUSING_CMAKE)
set(INTERNAL_LIBS ${PROJECT_SOURCE_DIR}/internal-complibs)
-# Hide symbols by default unless they're specifically exported.
-# This makes it easier to keep the set of exported symbols the
-# same across all compilers/platforms.
-set(CMAKE_C_VISIBILITY_PRESET hidden)
+if(HIDE_SYMBOLS)
+ set(CMAKE_C_VISIBILITY_PRESET hidden)
+endif(HIDE_SYMBOLS)
# includes
set(BLOSC_INCLUDE_DIRS ${BLOSC_INCLUDE_DIRS} ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -1,25 +0,0 @@
# This is patch has a pending PR: https://github.com/Blosc/c-blosc/pull/329
--- CMakeLists.txt
+++ CMakeLists.txt
@@ -13,6 +13,8 @@
# build fuzz test programs and generates the "test" target
# BUILD_BENCHMARKS: default ON
# build the benchmark program
+# HIDE_SYMBOLS: default ON
+# hide the symols that aren't specifically exported
# DEACTIVATE_SSE2: default OFF
# do not attempt to build with SSE2 instructions
# DEACTIVATE_AVX2: default OFF
@@ -98,6 +100,11 @@ option(BUILD_TESTS
"Build test programs from the blosc compression library" ON)
option(BUILD_FUZZERS
"Build fuzzer programs from the blosc compression library" ${BUILD_STATIC})
+# Hide symbols by default unless they're specifically exported.
+# This makes it easier to keep the set of exported symbols the
+# same across all compilers/platforms.
+option(HIDE_SYMBOLS
+ "Build a libraries with hidden symbols unless they're specifically exported" ON)
option(BUILD_BENCHMARKS
"Build benchmark programs from the blosc compression library" ON)
option(DEACTIVATE_SSE2

View File

@ -133,7 +133,7 @@ absl::Status PffftMain() {
if (simd_size_iter == 0) simd_size_iter = 1;
if (complex) {
SAPI_RETURN_IF_ERROR(api.cffti(n, work_array.PtrBoth()))
SAPI_RETURN_IF_ERROR(api.cffti(n, work_array.PtrBoth()));
} else {
SAPI_RETURN_IF_ERROR(api.rffti(n, work_array.PtrBoth()));
}

View File

@ -44,7 +44,7 @@ absl::Status CompressInMemory(ZstdApi& api, std::ifstream& in_stream,
size_t outsize,
api.ZSTD_compress(outbuf.PtrAfter(), size, inbuf.PtrBefore(),
inbuf.GetSize(), level));
SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_isError(outsize))
SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_isError(outsize));
if (iserr) {
return absl::UnavailableError("Unable to compress file");
}
@ -115,13 +115,13 @@ absl::Status CompressStream(ZstdApi& api, std::ifstream& in_stream,
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_CCtx_setParameter(
&rcctx, ZSTD_c_compressionLevel, level));
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr))
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr));
if (iserr) {
return absl::UnavailableError("Unable to set parameter");
}
SAPI_ASSIGN_OR_RETURN(
iserr, api.ZSTD_CCtx_setParameter(&rcctx, ZSTD_c_checksumFlag, 1));
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr))
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr));
if (iserr) {
return absl::UnavailableError("Unable to set parameter");
}
@ -155,7 +155,7 @@ absl::Status CompressStream(ZstdApi& api, std::ifstream& in_stream,
SAPI_ASSIGN_OR_RETURN(size_t remaining, api.ZSTD_compressStream2(
&rcctx, struct_out.PtrBoth(),
struct_in.PtrBoth(), mode));
SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_isError(remaining))
SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_isError(remaining));
if (iserr) {
return absl::UnavailableError("Unable to decompress file");
}
@ -220,7 +220,7 @@ absl::Status DecompressStream(ZstdApi& api, std::ifstream& in_stream,
SAPI_ASSIGN_OR_RETURN(
size_t ret, api.ZSTD_decompressStream(&rdctx, struct_out.PtrBoth(),
struct_in.PtrBoth()));
SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_isError(ret))
SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_isError(ret));
if (iserr) {
return absl::UnavailableError("Unable to decompress file");
}
@ -250,7 +250,7 @@ absl::Status CompressInMemoryFD(ZstdApi& api, sapi::v::Fd& infd,
SAPI_ASSIGN_OR_RETURN(
int iserr,
api.ZSTD_compress_fd(infd.GetRemoteFd(), outfd.GetRemoteFd(), 0));
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr))
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr));
if (iserr) {
return absl::UnavailableError("Unable to compress file");
}
@ -268,7 +268,7 @@ absl::Status DecompressInMemoryFD(ZstdApi& api, sapi::v::Fd& infd,
SAPI_ASSIGN_OR_RETURN(int iserr, api.ZSTD_decompress_fd(infd.GetRemoteFd(),
outfd.GetRemoteFd()));
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr))
SAPI_ASSIGN_OR_RETURN(iserr, api.ZSTD_isError(iserr));
if (iserr) {
return absl::UnavailableError("Unable to compress file");
}

View File

@ -55,12 +55,13 @@ def sapi_interface_impl(ctx):
append_arg(args, "--sapi_isystem", isystem.path)
input_files += [isystem]
if ctx.attr.limit_scan_depth:
args.append("--sapi_limit_scan_depth")
# Parse provided files.
# The parser doesn't need the entire set of transitive headers
# here, just the top-level cc_library headers. It would be nice
# if Skylark or Bazel provided this, but it is surprisingly hard
# to get.
# here, just the top-level cc_library headers.
#
# Allow all headers through that contain the dependency's
# package path. Including extra headers is harmless except that
@ -68,43 +69,36 @@ def sapi_interface_impl(ctx):
# pass a lot through that we don't strictly need.
extra_flags = []
if ctx.attr.lib[CcInfo]:
cc_ctx = ctx.attr.lib[CcInfo].compilation_context
cc_ctx = ctx.attr.lib[CcInfo].compilation_context
# Append system headers as dependencies
input_files += cc_ctx.headers.to_list()
append_all(extra_flags, "-D", cc_ctx.defines.to_list())
append_all(extra_flags, "-isystem", cc_ctx.system_includes.to_list())
append_all(extra_flags, "-iquote", cc_ctx.quote_includes.to_list())
# Append all headers as dependencies
input_files += cc_ctx.headers.to_list()
if ctx.attr.input_files:
for h in cc_ctx.headers.to_list():
# Collect all headers as dependency in case libclang needs them.
if h.extension == "h" and "/PROTECTED/" not in h.path:
input_files.append(h)
for target in ctx.attr.input_files:
if target.files:
for f in target.files.to_list():
input_files_paths.append(f.path)
input_files.append(f)
quote_includes = cc_ctx.quote_includes.to_list()
append_all(extra_flags, "-D", cc_ctx.defines.to_list())
append_all(extra_flags, "-isystem", cc_ctx.system_includes.to_list())
append_all(extra_flags, "-iquote", quote_includes)
# Try to find files automatically.
else:
for h in cc_ctx.headers.to_list():
# Collect all headers as dependency in case clang needs them.
if h.extension == "h" and "/PROTECTED/" not in h.path:
input_files.append(h)
# Include only headers coming from the target
# not ones that it depends on by comparing the label packages.
if (h.owner.package == ctx.attr.lib.label.package):
input_files_paths.append(h.path)
append_arg(args, "--sapi_in", ",".join(input_files_paths))
args += ["--"] + extra_flags
if ctx.attr.input_files:
for target in ctx.attr.input_files:
if target.files:
for f in target.files.to_list():
input_files_paths.append(f.path)
input_files.append(f)
else:
# TODO(szwl): Error out if the lib has no cc.
pass
# Try to find files automatically
for h in cc_ctx.direct_headers:
# Collect all headers as dependency.
if h.extension != "h" or "/PROTECTED/" in h.path:
continue
# Include only headers coming from the target
# not ones that it depends on by comparing the label packages.
if (h.owner.package == ctx.attr.lib.label.package):
input_files_paths.append(h.path)
append_arg(args, "--sapi_in", ",".join(input_files_paths))
args += ["--"] + extra_flags
progress_msg = ("Generating {} from {} header files." +
"").format(ctx.outputs.out.short_path, len(input_files_paths))
@ -126,10 +120,11 @@ sapi_interface = rule(
"functions": attr.string_list(allow_empty = True, default = []),
"include_prefix": attr.string(),
"input_files": attr.label_list(allow_files = True),
"lib": attr.label(mandatory = True),
"lib": attr.label(providers = [CcInfo], mandatory = True),
"lib_name": attr.string(mandatory = True),
"namespace": attr.string(),
"isystem": attr.label(),
"limit_scan_depth": attr.bool(default = False),
"_sapi_generator": attr.label(
executable = True,
cfg = "host",

View File

@ -143,7 +143,7 @@ class FunctionCallPreparer {
private:
// Deserializes the protobuf argument.
google::protobuf::Message** GetDeserializedProto(LenValStruct* src) {
google::protobuf::MessageLite** GetDeserializedProto(LenValStruct* src) {
ProtoArg proto_arg;
if (!proto_arg.ParseFromArray(src->data, src->size)) {
LOG(FATAL) << "Unable to parse ProtoArg.";
@ -153,7 +153,7 @@ class FunctionCallPreparer {
proto_arg.full_name());
LOG_IF(FATAL, desc == nullptr) << "Unable to find the descriptor for '"
<< proto_arg.full_name() << "'" << desc;
google::protobuf::Message* deserialized_proto =
google::protobuf::MessageLite* deserialized_proto =
google::protobuf::MessageFactory::generated_factory()->GetPrototype(desc)->New();
LOG_IF(FATAL, deserialized_proto == nullptr)
<< "Unable to create deserialized proto for " << proto_arg.full_name();
@ -168,7 +168,8 @@ class FunctionCallPreparer {
// Use list instead of vector to preserve references even with modifications.
// Contains pairs of lenval message pointer -> deserialized message
// so that we can serialize the argument again after the function call.
std::list<std::pair<LenValStruct*, google::protobuf::Message*>> protos_to_be_destroyed_;
std::list<std::pair<LenValStruct*, google::protobuf::MessageLite*>>
protos_to_be_destroyed_;
ffi_type* ret_type_;
ffi_type* arg_types_[FuncCall::kArgsMax];
const void* arg_values_[FuncCall::kArgsMax];

View File

@ -150,8 +150,26 @@ constexpr bool IsASan() {
#endif
}
constexpr bool IsHwASan() {
#ifdef ABSL_HAVE_HWADDRESS_SANITIZER
return true;
#else
return false;
#endif
}
constexpr bool IsLSan() {
#ifdef ABSL_HAVE_LEAK_SANITIZER
return true;
#else
return false;
#endif
}
// Returns whether any of the sanitizers is enabled.
constexpr bool IsAny() { return IsMSan() || IsTSan() || IsASan(); }
constexpr bool IsAny() {
return IsMSan() || IsTSan() || IsASan() || IsHwASan() || IsLSan();
}
} // namespace sanitizers

View File

@ -21,7 +21,7 @@ namespace sapi {
namespace internal {
absl::Status DeserializeProto(const char* data, size_t len,
google::protobuf::Message& output) {
google::protobuf::MessageLite& output) {
ProtoArg envelope;
if (!envelope.ParseFromArray(data, len)) {
return absl::InternalError("Unable to parse proto from array");
@ -37,13 +37,12 @@ absl::Status DeserializeProto(const char* data, size_t len,
} // namespace internal
absl::StatusOr<std::vector<uint8_t>> SerializeProto(
const google::protobuf::Message& proto) {
const google::protobuf::MessageLite& proto) {
// Wrap protobuf in a envelope so that we know the name of the protobuf
// structure when deserializing in the sandboxee.
ProtoArg proto_arg;
proto_arg.set_protobuf_data(proto.SerializeAsString());
proto_arg.set_full_name(proto.GetDescriptor()->full_name());
proto_arg.set_full_name(proto.GetTypeName());
std::vector<uint8_t> serialized_proto(proto_arg.ByteSizeLong());
if (!proto_arg.SerializeToArray(serialized_proto.data(),
serialized_proto.size())) {

View File

@ -31,16 +31,16 @@ namespace sapi {
namespace internal {
absl::Status DeserializeProto(const char* data, size_t len,
google::protobuf::Message& output);
google::protobuf::MessageLite& output);
} // namespace internal
absl::StatusOr<std::vector<uint8_t>> SerializeProto(
const google::protobuf::Message& proto);
const google::protobuf::MessageLite& proto);
template <typename T>
absl::StatusOr<T> DeserializeProto(const char* data, size_t len) {
static_assert(std::is_base_of<google::protobuf::Message, T>::value,
static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
"Template argument must be a proto message");
T result;
SAPI_RETURN_IF_ERROR(

View File

@ -243,12 +243,16 @@ cc_library(
":forkserver_bin_embed",
":forkserver_cc_proto",
":util",
"//sandboxed_api:config",
"//sandboxed_api:embed_file",
"//sandboxed_api/util:fileops",
"//sandboxed_api/util:flags",
"//sandboxed_api/util:raw_logging",
"//sandboxed_api/util:status",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_glog//:glog",
@ -282,7 +286,9 @@ cc_library(
":limits",
":namespace",
":util",
"//sandboxed_api:config",
"//sandboxed_api/util:fileops",
"//sandboxed_api/util:status",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
@ -466,7 +472,7 @@ cc_library(
hdrs = ["mounts.h"],
copts = sapi_platform_copts(),
deps = [
":mounttree_cc_proto",
":mount_tree_cc_proto",
"//sandboxed_api:config",
"//sandboxed_api/sandbox2/util:minielf",
"//sandboxed_api/util:file_base",
@ -489,6 +495,7 @@ cc_test(
copts = sapi_platform_copts(),
data = ["//sandboxed_api/sandbox2/testcases:minimal_dynamic"],
deps = [
":mount_tree_cc_proto",
":mounts",
"//sandboxed_api:testing",
"//sandboxed_api/util:file_base",
@ -506,8 +513,8 @@ cc_library(
hdrs = ["namespace.h"],
copts = sapi_platform_copts(),
deps = [
":mount_tree_cc_proto",
":mounts",
":mounttree_cc_proto",
":util",
":violation_cc_proto",
"//sandboxed_api/util:file_base",
@ -623,12 +630,12 @@ sapi_proto_library(
name = "forkserver_proto",
srcs = ["forkserver.proto"],
copts = sapi_platform_copts(),
deps = [":mounttree_proto"],
deps = [":mount_tree_proto"],
)
sapi_proto_library(
name = "mounttree_proto",
srcs = ["mounttree.proto"],
name = "mount_tree_proto",
srcs = ["mount_tree.proto"],
)
cc_library(
@ -879,7 +886,7 @@ cc_library(
sapi_proto_library(
name = "violation_proto",
srcs = ["violation.proto"],
deps = [":mounttree_proto"],
deps = [":mount_tree_proto"],
)
cc_test(

View File

@ -229,13 +229,17 @@ add_library(sandbox2::global_forkserver ALIAS sandbox2_global_forkserver)
target_link_libraries(sandbox2_global_forkserver
PRIVATE absl::memory
absl::strings
absl::status
absl::statusor
glog::glog
sandbox2::client
sandbox2::forkserver_bin_embed
sapi::strerror
sandbox2::util
sapi::base
sapi::config
sapi::embed_file
sapi::fileops
sapi::raw_logging
sapi::status
PUBLIC absl::core_headers
@ -280,7 +284,9 @@ target_link_libraries(sandbox2_executor
PUBLIC absl::span
absl::strings
glog::glog
sapi::config
sapi::fileops
sapi::status
sandbox2::fork_client
sandbox2::global_forkserver
)
@ -330,7 +336,7 @@ target_link_libraries(sandbox2_sandbox2
sandbox2::limits
sandbox2::logsink
sandbox2::mounts
sandbox2::mounttree_proto
sandbox2::mount_tree_proto
sandbox2::namespace
sandbox2::network_proxy_client
sandbox2::network_proxy_server
@ -455,7 +461,7 @@ target_link_libraries(sandbox2_mounts
absl::status
absl::statusor
absl::strings
sandbox2::mounttree_proto
sandbox2::mount_tree_proto
)
# sandboxed_api/sandbox2:namespace
@ -473,7 +479,7 @@ target_link_libraries(sandbox2_namespace PRIVATE
sapi::file_base
sapi::fileops
sandbox2::mounts
sandbox2::mounttree_proto
sandbox2::mount_tree_proto
sapi::strerror
sandbox2::util
sandbox2::violation_proto
@ -554,20 +560,20 @@ add_library(sandbox2_forkserver_proto ${SAPI_LIB_TYPE}
add_library(sandbox2::forkserver_proto ALIAS sandbox2_forkserver_proto)
target_link_libraries(sandbox2_forkserver_proto PRIVATE
protobuf::libprotobuf
sandbox2::mounttree_proto
sandbox2::mount_tree_proto
sapi::base
)
# sandboxed_api/sandbox2:mounttree_proto
sapi_protobuf_generate_cpp(_sandbox2_mounttree_pb_h _sandbox2_mounttree_pb_cc
mounttree.proto
# sandboxed_api/sandbox2:mount_tree_proto
sapi_protobuf_generate_cpp(_sandbox2_mount_tree_pb_h _sandbox2_mount_tree_pb_cc
mount_tree.proto
)
add_library(sandbox2_mounttree_proto ${SAPI_LIB_TYPE}
${_sandbox2_mounttree_pb_cc}
${_sandbox2_mounttree_pb_h}
add_library(sandbox2_mount_tree_proto ${SAPI_LIB_TYPE}
${_sandbox2_mount_tree_pb_cc}
${_sandbox2_mount_tree_pb_h}
)
add_library(sandbox2::mounttree_proto ALIAS sandbox2_mounttree_proto)
target_link_libraries(sandbox2_mounttree_proto PRIVATE
add_library(sandbox2::mount_tree_proto ALIAS sandbox2_mount_tree_proto)
target_link_libraries(sandbox2_mount_tree_proto PRIVATE
protobuf::libprotobuf
sapi::base
)
@ -607,7 +613,7 @@ add_library(sandbox2_violation_proto ${SAPI_LIB_TYPE}
add_library(sandbox2::violation_proto ALIAS sandbox2_violation_proto)
target_link_libraries(sandbox2_violation_proto PRIVATE
protobuf::libprotobuf
sandbox2::mounttree_proto
sandbox2::mount_tree_proto
sapi::base
)
@ -662,6 +668,7 @@ if(SAPI_ENABLE_TESTS)
absl::strings
sapi::file_base
sandbox2::mounts
sandbox2::mount_tree_proto
sapi::temp_file
sapi::testing
sapi::status_matchers

View File

@ -437,7 +437,7 @@ bool Comms::SendFD(int fd) {
return true;
}
bool Comms::RecvProtoBuf(google::protobuf::Message* message) {
bool Comms::RecvProtoBuf(google::protobuf::MessageLite* message) {
uint32_t tag;
std::vector<uint8_t> bytes;
if (!RecvTLV(&tag, &bytes)) {
@ -457,7 +457,7 @@ bool Comms::RecvProtoBuf(google::protobuf::Message* message) {
return message->ParseFromArray(bytes.data(), bytes.size());
}
bool Comms::SendProtoBuf(const google::protobuf::Message& message) {
bool Comms::SendProtoBuf(const google::protobuf::MessageLite& message) {
std::string str;
if (!message.SerializeToString(&str)) {
SAPI_RAW_LOG(ERROR, "Couldn't serialize the ProtoBuf");

View File

@ -155,8 +155,8 @@ class Comms {
bool SendFD(int fd);
// Receives/sends protobufs.
bool RecvProtoBuf(google::protobuf::Message* message);
bool SendProtoBuf(const google::protobuf::Message& message);
bool RecvProtoBuf(google::protobuf::MessageLite* message);
bool SendProtoBuf(const google::protobuf::MessageLite& message);
// Receives/sends Status objects.
bool RecvStatus(absl::Status* status);

View File

@ -38,6 +38,7 @@ cc_binary(
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/time",
],
)

View File

@ -21,6 +21,7 @@ add_executable(sandbox2::sandbox2tool ALIAS sandbox2_sandbox2tool)
target_link_libraries(sandbox2_sandbox2tool PRIVATE
absl::memory
absl::strings
absl::time
sandbox2::bpf_helper
# sandbox2::ipc
sandbox2::sandbox2

View File

@ -39,6 +39,7 @@
#include "absl/memory/memory.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/time/time.h"
#include "sandboxed_api/sandbox2/executor.h"
#include "sandboxed_api/sandbox2/ipc.h"
#include "sandboxed_api/sandbox2/limits.h"
@ -202,7 +203,7 @@ int main(int argc, char** argv) {
sleep(3);
kill(s2.pid(), SIGSTOP);
sleep(3);
s2.SetWallTimeLimit(3);
s2.set_walltime_limit(absl::Seconds(3));
kill(s2.pid(), SIGCONT);
} else if (absl::GetFlag(FLAGS_sandbox2tool_pause_kill)) {
sleep(3);

View File

@ -23,37 +23,79 @@
#include <climits>
#include <cstddef>
#include <string_view>
#include "absl/memory/memory.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/sandbox2/fork_client.h"
#include "sandboxed_api/sandbox2/forkserver.pb.h"
#include "sandboxed_api/sandbox2/global_forkclient.h"
#include "sandboxed_api/sandbox2/ipc.h"
#include "sandboxed_api/sandbox2/util.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/os_error.h"
namespace sandbox2 {
namespace file_util = ::sapi::file_util;
namespace {
void DisableCompressStackDepot(ForkRequest& request) {
auto disable_compress_stack_depot = [&request](absl::string_view sanitizer) {
auto prefix = absl::StrCat(sanitizer, "_OPTIONS=");
auto it = std::find_if(request.mutable_envs()->begin(),
request.mutable_envs()->end(),
[&prefix](const std::string& env) {
return absl::StartsWith(env, prefix);
});
constexpr absl::string_view option = "compress_stack_depot=0";
if (it != request.mutable_envs()->end()) {
// If it's already there, the last value will be used.
absl::StrAppend(&*it, ":", option);
return;
}
request.add_envs(absl::StrCat(prefix, option));
};
if constexpr (sapi::sanitizers::IsASan()) {
disable_compress_stack_depot("ASAN");
}
if constexpr (sapi::sanitizers::IsMSan()) {
disable_compress_stack_depot("MSAN");
}
if constexpr (sapi::sanitizers::IsLSan()) {
disable_compress_stack_depot("LSAN");
}
if constexpr (sapi::sanitizers::IsHwASan()) {
disable_compress_stack_depot("HWSAN");
}
if constexpr (sapi::sanitizers::IsTSan()) {
disable_compress_stack_depot("TSAN");
}
}
} // namespace
std::vector<std::string> Executor::CopyEnviron() {
return util::CharPtrArray(environ).ToStringVector();
}
pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns,
const std::vector<int>& caps,
pid_t* init_pid_out) {
absl::StatusOr<Executor::Process> Executor::StartSubProcess(
int32_t clone_flags, const Namespace* ns, const std::vector<int>& caps) {
if (started_) {
LOG(ERROR) << "This executor has already been started";
return -1;
return absl::FailedPreconditionError(
"This executor has already been started");
}
if (!path_.empty()) {
exec_fd_ = file_util::fileops::FDCloser(open(path_.c_str(), O_PATH));
if (exec_fd_.get() < 0) {
PLOG(ERROR) << "Could not open file " << path_;
return -1;
if (errno == ENOENT) {
return absl::NotFoundError(sapi::OsErrorMessage(errno, path_));
}
return absl::InternalError(
sapi::OsErrorMessage(errno, "Could not open file ", path_));
}
}
@ -78,6 +120,11 @@ pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns,
file_util::fileops::StripBasename(path_)));
}
// Disable optimization to avoid related syscalls.
if constexpr (sapi::sanitizers::IsAny()) {
DisableCompressStackDepot(request);
}
// If neither the path, nor exec_fd is specified, just assume that we need to
// send a fork request.
//
@ -97,6 +144,7 @@ pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns,
clone_flags |= ns->GetCloneFlags();
*request.mutable_mount_tree() = ns->mounts().GetMountTree();
request.set_hostname(ns->hostname());
request.set_allow_mount_propagation(ns->allow_mount_propagation());
}
request.set_clone_flags(clone_flags);
@ -110,33 +158,22 @@ pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns,
const std::string ns_path =
absl::StrCat("/proc/", libunwind_sbox_for_pid_, "/ns/user");
ns_fd = file_util::fileops::FDCloser(open(ns_path.c_str(), O_RDONLY));
PCHECK(ns_fd.get() != -1)
<< "Could not open user ns fd (" << ns_path << ")";
if (ns_fd.get() == -1) {
return absl::InternalError(sapi::OsErrorMessage(
errno, "Could not open user ns fd (", ns_path, ")"));
}
}
pid_t init_pid = -1;
Process process;
pid_t sandboxee_pid;
if (fork_client_) {
sandboxee_pid = fork_client_->SendRequest(request, exec_fd_.get(),
client_comms_fd_.get(),
ns_fd.get(), &init_pid);
process.main_pid = fork_client_->SendRequest(
request, exec_fd_.get(), client_comms_fd_.get(), ns_fd.get(),
&process.init_pid);
} else {
sandboxee_pid = GlobalForkClient::SendRequest(request, exec_fd_.get(),
client_comms_fd_.get(),
ns_fd.get(), &init_pid);
}
if (init_pid < 0) {
LOG(ERROR) << "Could not obtain init PID";
} else if (init_pid == 0 && request.clone_flags() & CLONE_NEWPID) {
LOG(FATAL)
<< "No init process was spawned even though a PID NS was created, "
<< "potential logic bug";
}
if (init_pid_out) {
*init_pid_out = init_pid;
process.main_pid = GlobalForkClient::SendRequest(
request, exec_fd_.get(), client_comms_fd_.get(), ns_fd.get(),
&process.init_pid);
}
started_ = true;
@ -144,19 +181,19 @@ pid_t Executor::StartSubProcess(int32_t clone_flags, const Namespace* ns,
client_comms_fd_.Close();
exec_fd_.Close();
VLOG(1) << "StartSubProcess returned with: " << sandboxee_pid;
return sandboxee_pid;
VLOG(1) << "StartSubProcess returned with: " << process.main_pid;
return process;
}
std::unique_ptr<ForkClient> Executor::StartForkServer() {
// This flag is set explicitly to 'true' during object instantiation, and
// custom fork-servers should never be sandboxed.
set_enable_sandbox_before_exec(false);
pid_t pid = StartSubProcess(0);
if (pid == -1) {
absl::StatusOr<Process> process = StartSubProcess(0);
if (!process.ok()) {
return nullptr;
}
return absl::make_unique<ForkClient>(pid, ipc_.comms());
return absl::make_unique<ForkClient>(process->main_pid, ipc_.comms());
}
void Executor::SetUpServerSideCommsFd() {

View File

@ -39,6 +39,11 @@ namespace sandbox2 {
// new processes which will be sandboxed.
class Executor final {
public:
struct Process {
pid_t init_pid;
pid_t main_pid;
};
Executor(const Executor&) = delete;
Executor& operator=(const Executor&) = delete;
@ -120,11 +125,9 @@ class Executor final {
//
// caps is a vector of capabilities that are kept in the permitted set after
// the clone, use with caution.
//
// Returns the same values as fork().
pid_t StartSubProcess(int clone_flags, const Namespace* ns = nullptr,
const std::vector<int>& caps = {},
pid_t* init_pid_out = nullptr);
absl::StatusOr<Process> StartSubProcess(int clone_flags,
const Namespace* ns = nullptr,
const std::vector<int>& caps = {});
// Whether the Executor has been started yet
bool started_ = false;

View File

@ -33,6 +33,8 @@
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
@ -43,6 +45,7 @@
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "libcap/include/sys/capability.h"
#include "sandboxed_api/sandbox2/client.h"
#include "sandboxed_api/sandbox2/comms.h"
@ -205,6 +208,35 @@ absl::StatusOr<pid_t> ReceivePid(int signaling_fd) {
struct ucred* ucredp = reinterpret_cast<struct ucred*>(CMSG_DATA(cmsgp));
return ucredp->pid;
}
absl::StatusOr<std::string> GetRootMountId(const std::string& proc_id) {
std::ifstream mounts(absl::StrCat("/proc/", proc_id, "/mountinfo"));
if (!mounts.good()) {
return absl::InternalError("Failed to open mountinfo");
}
std::string line;
while (std::getline(mounts, line)) {
std::vector<absl::string_view> parts =
absl::StrSplit(line, absl::MaxSplits(' ', 4));
if (parts.size() >= 4 && parts[3] == "/") {
return std::string(parts[0]);
}
}
return absl::NotFoundError("Root entry not found in mountinfo");
}
bool IsLikelyChrooted() {
absl::StatusOr<std::string> self_root_id = GetRootMountId("self");
if (!self_root_id.ok()) {
return absl::IsNotFound(self_root_id.status());
}
absl::StatusOr<std::string> init_root_id = GetRootMountId("1");
if (!init_root_id.ok()) {
return false;
}
return *self_root_id != *init_root_id;
}
} // namespace
namespace sandbox2 {
@ -242,6 +274,9 @@ void ForkServer::LaunchChild(const ForkRequest& request, int execve_fd,
int client_fd, uid_t uid, gid_t gid,
int user_ns_fd, int signaling_fd,
bool avoid_pivot_root) const {
SAPI_RAW_CHECK(request.mode() != FORKSERVER_FORK_UNSPECIFIED,
"Forkserver mode is unspecified");
bool will_execve = (request.mode() == FORKSERVER_FORK_EXECVE ||
request.mode() == FORKSERVER_FORK_EXECVE_SANDBOX);
@ -357,6 +392,9 @@ pid_t ForkServer::ServeRequest() {
int comms_fd;
SAPI_RAW_CHECK(comms_->RecvFD(&comms_fd), "Failed to receive Comms FD");
SAPI_RAW_CHECK(fork_request.mode() != FORKSERVER_FORK_UNSPECIFIED,
"Forkserver mode is unspecified");
int exec_fd = -1;
if (fork_request.mode() == FORKSERVER_FORK_EXECVE ||
fork_request.mode() == FORKSERVER_FORK_EXECVE_SANDBOX) {
@ -522,6 +560,11 @@ void ForkServer::CreateInitialNamespaces() {
SAPI_RAW_PCHECK(socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != -1,
"creating socket");
pid_t pid = util::ForkWithFlags(CLONE_NEWUSER | CLONE_NEWNS | SIGCHLD);
if (pid == -1 && errno == EPERM && IsLikelyChrooted()) {
SAPI_RAW_LOG(FATAL,
"failed to fork initial namespaces process: parent process is "
"likely chrooted");
}
SAPI_RAW_PCHECK(pid != -1, "failed to fork initial namespaces process");
char unused = '\0';
if (pid == 0) {
@ -605,7 +648,7 @@ void ForkServer::InitializeNamespaces(const ForkRequest& request, uid_t uid,
Namespace::InitializeNamespaces(
uid, gid, clone_flags, Mounts(request.mount_tree()),
request.mode() != FORKSERVER_FORK_JOIN_SANDBOX_UNWIND, request.hostname(),
avoid_pivot_root);
avoid_pivot_root, request.allow_mount_propagation());
}
} // namespace sandbox2

View File

@ -14,13 +14,15 @@
// A proto for the sandbox2::Forkserver class
syntax = "proto2";
syntax = "proto3";
package sandbox2;
import "sandboxed_api/sandbox2/mounttree.proto";
import "sandboxed_api/sandbox2/mount_tree.proto";
enum Mode {
// Default value
FORKSERVER_FORK_UNSPECIFIED = 0;
// Fork, execve and sandbox
FORKSERVER_FORK_EXECVE_SANDBOX = 1;
// Fork and execve, but no sandboxing
@ -38,10 +40,10 @@ message ForkRequest {
repeated bytes envs = 2;
// How to interpret the request
required Mode mode = 3;
optional Mode mode = 3;
// Clone flags for the new process
optional int32 clone_flags = 4 [default = 0];
optional int32 clone_flags = 4;
// Capabilities to keep when starting the sandboxee
repeated int32 capabilities = 5;
@ -51,4 +53,7 @@ message ForkRequest {
// Hostname in the network namespace
optional bytes hostname = 7;
// Changes mount propagation from MS_PRIVATE to MS_SLAVE if set
optional bool allow_mount_propagation = 8;
}

View File

@ -32,22 +32,29 @@
#include <glog/logging.h>
#include "sandboxed_api/util/flag.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/embed_file.h"
#include "sandboxed_api/sandbox2/comms.h"
#include "sandboxed_api/sandbox2/fork_client.h"
#include "sandboxed_api/sandbox2/forkserver_bin_embed.h"
#include "sandboxed_api/sandbox2/util.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/os_error.h"
#include "sandboxed_api/util/raw_logging.h"
#include "sandboxed_api/util/status_macros.h"
namespace sandbox2 {
namespace file_util = ::sapi::file_util;
bool AbslParseFlag(absl::string_view text, GlobalForkserverStartModeSet* out,
std::string* error) {
*out = {};
@ -102,6 +109,8 @@ std::string AbslUnparseFlag(GlobalForkserverStartModeSet in) {
} // namespace sandbox2
ABSL_FLAG(string, sandbox2_forkserver_binary_path, "",
"Path to forkserver_bin binary");
ABSL_FLAG(string, sandbox2_forkserver_start_mode, "ondemand",
"When Sandbox2 Forkserver process should be started");
DEFINE_validator(sandbox2_forkserver_start_mode, &sandbox2::ValidateStartMode);
@ -121,12 +130,27 @@ GlobalForkserverStartModeSet GetForkserverStartMode() {
absl::StatusOr<std::unique_ptr<GlobalForkClient>> StartGlobalForkServer() {
SAPI_RAW_LOG(INFO, "Starting global forkserver");
// The fd is owned by EmbedFile
int exec_fd = sapi::EmbedFile::instance()->GetFdForFileToc(
forkserver_bin_embed_create());
// Allow passing of a spearate forkserver_bin via flag
int exec_fd = -1;
if (!absl::GetFlag(FLAGS_sandbox2_forkserver_binary_path).empty()) {
exec_fd = open(absl::GetFlag(FLAGS_sandbox2_forkserver_binary_path).c_str(),
O_RDONLY);
}
if (exec_fd < 0) {
// For Android we expect the forkserver_bin in the flag
if constexpr (sapi::host_os::IsAndroid()) {
return absl::InternalError(sapi::OsErrorMessage(
errno,
"Open init binary passed via --sandbox2_forkserver_binary_path"));
}
// Extract the fd when it's owned by EmbedFile
exec_fd = sapi::EmbedFile::instance()->GetDupFdForFileToc(
forkserver_bin_embed_create());
}
if (exec_fd < 0) {
return absl::InternalError("Getting FD for init binary failed");
}
file_util::fileops::FDCloser exec_fd_closer(exec_fd);
std::string proc_name = "S2-FORK-SERV";

View File

@ -107,5 +107,6 @@ std::string AbslUnparseFlag(GlobalForkserverStartModeSet in);
} // namespace sandbox2
ABSL_DECLARE_FLAG(string, sandbox2_forkserver_start_mode);
ABSL_DECLARE_FLAG(string, sandbox2_forkserver_binary_path);
#endif // SANDBOXED_API_SANDBOX2_GLOBAL_FORKCLIENT_H_

View File

@ -276,13 +276,20 @@ void Monitor::Run() {
}
// Get PID of the sandboxee.
pid_t init_pid = 0;
bool should_have_init = ns && (ns->GetCloneFlags() & CLONE_NEWPID);
pid_ = executor_->StartSubProcess(clone_flags, ns, policy_->capabilities(),
&init_pid);
absl::StatusOr<Executor::Process> process =
executor_->StartSubProcess(clone_flags, ns, policy_->capabilities());
if (init_pid > 0) {
if (ptrace(PTRACE_SEIZE, init_pid, 0, PTRACE_O_EXITKILL) != 0) {
if (!process.ok()) {
LOG(ERROR) << "Starting sandboxed subprocess failed: " << process.status();
SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SUBPROCESS);
return;
}
pid_ = process->main_pid;
if (process->init_pid > 0) {
if (ptrace(PTRACE_SEIZE, process->init_pid, 0, PTRACE_O_EXITKILL) != 0) {
if (errno == ESRCH) {
SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_PTRACE);
return;
@ -291,7 +298,7 @@ void Monitor::Run() {
}
}
if (pid_ <= 0 || (should_have_init && init_pid <= 0)) {
if (pid_ <= 0 || (should_have_init && process->init_pid <= 0)) {
SetExitStatusCode(Result::SETUP_ERROR, Result::FAILED_SUBPROCESS);
return;
}
@ -598,10 +605,17 @@ bool Monitor::InitSendCwd() {
return true;
}
bool Monitor::InitApplyLimit(pid_t pid, __rlimit_resource resource,
bool Monitor::InitApplyLimit(pid_t pid, int resource,
const rlimit64& rlim) const {
#if defined(__ANDROID__)
using RlimitResource = int;
#else
using RlimitResource = __rlimit_resource;
#endif
rlimit64 curr_limit;
if (prlimit64(pid, resource, nullptr, &curr_limit) == -1) {
if (prlimit64(pid, static_cast<RlimitResource>(resource), nullptr,
&curr_limit) == -1) {
PLOG(ERROR) << "prlimit64(" << pid << ", " << util::GetRlimitName(resource)
<< ")";
} else if (rlim.rlim_cur > curr_limit.rlim_max) {
@ -613,7 +627,8 @@ bool Monitor::InitApplyLimit(pid_t pid, __rlimit_resource resource,
return true;
}
if (prlimit64(pid, resource, &rlim, nullptr) == -1) {
if (prlimit64(pid, static_cast<RlimitResource>(resource), &rlim, nullptr) ==
-1) {
PLOG(ERROR) << "prlimit64(" << pid << ", " << util::GetRlimitName(resource)
<< ", " << rlim.rlim_cur << ")";
return false;

View File

@ -94,8 +94,7 @@ class Monitor final {
bool InitApplyLimits();
// Applies individual limit on the sandboxee.
bool InitApplyLimit(pid_t pid, __rlimit_resource resource,
const rlimit64& rlim) const;
bool InitApplyLimit(pid_t pid, int resource, const rlimit64& rlim) const;
// Kills the main traced PID with PTRACE_KILL.
void KillSandboxee();

View File

@ -14,7 +14,7 @@
// A proto for serializing the sandbox2::MountTree class
syntax = "proto2";
syntax = "proto3";
package sandbox2;
@ -26,24 +26,24 @@ message MountTree {
// FileNode represents a bind mount for a regular file using "outside" as the
// source.
message FileNode {
required string outside = 2;
required bool is_ro = 3;
optional string outside = 2;
optional bool writable = 3;
}
// DirNode is like FileNode but for directories.
message DirNode {
required string outside = 2;
required bool is_ro = 3;
optional string outside = 2;
optional bool writable = 3;
}
// TmpfsNode mounts a tmpfs with given options.
message TmpfsNode {
required string tmpfs_options = 1;
optional string tmpfs_options = 1;
}
// RootNode is as special node for root of the MountTree
message RootNode {
required bool is_ro = 3;
optional bool writable = 3;
}
message Node {

View File

@ -37,6 +37,7 @@
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "sandboxed_api/config.h"
#include "sandboxed_api/sandbox2/mount_tree.pb.h"
#include "sandboxed_api/sandbox2/util/minielf.h"
#include "sandboxed_api/util/fileops.h"
#include "sandboxed_api/util/path.h"
@ -55,43 +56,6 @@ bool PathContainsNullByte(absl::string_view path) {
return absl::StrContains(path, '\0');
}
bool IsSameFile(const std::string& path1, const std::string& path2) {
struct stat stat1, stat2;
if (stat(path1.c_str(), &stat1) == -1) {
return false;
}
if (stat(path2.c_str(), &stat2) == -1) {
return false;
}
return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino;
}
bool IsEquivalentNode(const sandbox2::MountTree::Node& n1,
const sandbox2::MountTree::Node& n2) {
// Node equals 1:1
if (google::protobuf::util::MessageDifferencer::Equals(n1, n2)) {
return true;
}
if (n1.node_case() != n2.node_case()) {
return false;
}
// Check whether files/dirs are the same (e.g symlinks / hardlinks)
switch (n1.node_case()) {
case sandbox2::MountTree::Node::kFileNode:
return n1.file_node().is_ro() == n2.file_node().is_ro() &&
IsSameFile(n1.file_node().outside(), n2.file_node().outside());
case sandbox2::MountTree::Node::kDirNode:
return n1.dir_node().is_ro() == n2.dir_node().is_ro() &&
IsSameFile(n1.dir_node().outside(), n2.dir_node().outside());
default:
return false;
}
}
absl::string_view GetOutsidePath(const MountTree::Node& node) {
switch (node.node_case()) {
case MountTree::Node::kFileNode:
@ -162,6 +126,52 @@ std::string GetPlatform(absl::string_view interpreter) {
} // namespace
namespace internal {
bool IsSameFile(const std::string& path1, const std::string& path2) {
if (path1 == path2) {
return true;
}
struct stat stat1, stat2;
if (stat(path1.c_str(), &stat1) == -1) {
return false;
}
if (stat(path2.c_str(), &stat2) == -1) {
return false;
}
return stat1.st_dev == stat2.st_dev && stat1.st_ino == stat2.st_ino;
}
bool IsEquivalentNode(const MountTree::Node& n1, const MountTree::Node& n2) {
// Return early when node types are different
if (n1.node_case() != n2.node_case()) {
return false;
}
// Compare proto fileds
switch (n1.node_case()) {
case MountTree::Node::kFileNode:
// Check whether files are the same (e.g. symlinks / hardlinks)
return n1.file_node().writable() == n2.file_node().writable() &&
IsSameFile(n1.file_node().outside(), n2.file_node().outside());
case MountTree::Node::kDirNode:
// Check whether dirs are the same (e.g. symlinks / hardlinks)
return n1.dir_node().writable() == n2.dir_node().writable() &&
IsSameFile(n1.dir_node().outside(), n2.dir_node().outside());
case MountTree::Node::kTmpfsNode:
return n1.tmpfs_node().tmpfs_options() == n2.tmpfs_node().tmpfs_options();
case MountTree::Node::kRootNode:
return n1.root_node().writable() == n2.root_node().writable();
default:
return false;
}
}
} // namespace internal
absl::Status Mounts::Insert(absl::string_view path,
const MountTree::Node& new_node) {
// Some sandboxes allow the inside/outside paths to be partially
@ -225,7 +235,7 @@ absl::Status Mounts::Insert(absl::string_view path,
.first->second);
if (curtree->has_node()) {
if (IsEquivalentNode(curtree->node(), new_node)) {
if (internal::IsEquivalentNode(curtree->node(), new_node)) {
SAPI_RAW_LOG(INFO, "Inserting %s with the same value twice",
std::string(path).c_str());
return absl::OkStatus();
@ -253,7 +263,7 @@ absl::Status Mounts::AddFileAt(absl::string_view outside,
MountTree::Node node;
auto* file_node = node.mutable_file_node();
file_node->set_outside(std::string(outside));
file_node->set_is_ro(is_ro);
file_node->set_writable(!is_ro);
return Insert(inside, node);
}
@ -262,7 +272,7 @@ absl::Status Mounts::AddDirectoryAt(absl::string_view outside,
MountTree::Node node;
auto dir_node = node.mutable_dir_node();
dir_node->set_outside(std::string(outside));
dir_node->set_is_ro(is_ro);
dir_node->set_writable(!is_ro);
return Insert(inside, node);
}
@ -479,6 +489,7 @@ uint64_t GetMountFlagsFor(const std::string& path) {
uint64_t flags = 0;
using MountPair = std::pair<uint64_t, uint64_t>;
for (const auto& [mount_flag, vfs_flag] : {
MountPair(MS_RDONLY, ST_RDONLY),
MountPair(MS_NOSUID, ST_NOSUID),
MountPair(MS_NODEV, ST_NODEV),
MountPair(MS_NOEXEC, ST_NOEXEC),
@ -562,8 +573,14 @@ void MountWithDefaults(const std::string& source, const std::string& target,
// Flags are ignored for a bind mount, a remount is needed to set the flags.
if (extra_flags & MS_BIND) {
// Get actual mount flags.
flags |= GetMountFlagsFor(target);
res = mount("", target.c_str(), "", flags | MS_REMOUNT, nullptr);
uint64_t target_flags = GetMountFlagsFor(target);
if ((target_flags & MS_RDONLY) != 0 && (flags & MS_RDONLY) == 0) {
SAPI_RAW_LOG(FATAL,
"cannot remount %s as read-write as it's on read-only dev",
target.c_str());
}
res = mount("", target.c_str(), "", flags | target_flags | MS_REMOUNT,
nullptr);
SAPI_RAW_PCHECK(res != -1, "remounting %s with flags=%s failed", target,
MountFlagsToString(flags));
}
@ -611,7 +628,7 @@ void CreateMounts(const MountTree& tree, const std::string& path,
auto node = tree.node().dir_node();
MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr,
node.is_ro());
!node.writable());
break;
}
case MountTree::Node::kTmpfsNode: {
@ -626,7 +643,7 @@ void CreateMounts(const MountTree& tree, const std::string& path,
case MountTree::Node::kFileNode: {
auto node = tree.node().file_node();
MountWithDefaults(node.outside(), path, "", MS_BIND, nullptr,
node.is_ro());
!node.writable());
// A file node has to be a leaf so we can skip traversing here.
return;
@ -659,11 +676,11 @@ void RecursivelyListMountsImpl(const MountTree& tree,
std::vector<std::string>* inside_entries) {
const MountTree::Node& node = tree.node();
if (node.has_dir_node()) {
const char* rw_str = node.dir_node().is_ro() ? "R " : "W ";
const char* rw_str = node.dir_node().writable() ? "W " : "R ";
inside_entries->emplace_back(absl::StrCat(rw_str, tree_path, "/"));
outside_entries->emplace_back(absl::StrCat(node.dir_node().outside(), "/"));
} else if (node.has_file_node()) {
const char* rw_str = node.file_node().is_ro() ? "R " : "W ";
const char* rw_str = node.file_node().writable() ? "W " : "R ";
inside_entries->emplace_back(absl::StrCat(rw_str, tree_path));
outside_entries->emplace_back(absl::StrCat(node.file_node().outside()));
} else if (node.has_tmpfs_node()) {

View File

@ -22,15 +22,21 @@
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "sandboxed_api/sandbox2/mounttree.pb.h"
#include "sandboxed_api/sandbox2/mount_tree.pb.h"
namespace sandbox2 {
namespace internal {
bool IsSameFile(const std::string& path1, const std::string& path2);
bool IsEquivalentNode(const MountTree::Node& n1, const MountTree::Node& n2);
} // namespace internal
class Mounts {
public:
Mounts() {
MountTree::Node root;
root.mutable_root_node()->set_is_ro(true);
root.mutable_root_node()->set_writable(false);
*mount_tree_.mutable_node() = root;
}
@ -59,12 +65,12 @@ class Mounts {
MountTree GetMountTree() const { return mount_tree_; }
void SetRootWritable() {
mount_tree_.mutable_node()->mutable_root_node()->set_is_ro(false);
mount_tree_.mutable_node()->mutable_root_node()->set_writable(true);
}
bool IsRootReadOnly() const {
return mount_tree_.has_node() && mount_tree_.node().has_root_node() &&
mount_tree_.node().root_node().is_ro();
!mount_tree_.node().root_node().writable();
}
// Lists the outside and inside entries of the input tree in the output

View File

@ -178,7 +178,7 @@ TEST(MountTreeTest, TestMinimalDynamicBinary) {
TEST(MountTreeTest, TestList) {
struct TestCase {
const char *path;
const char* path;
const bool is_ro;
};
// clang-format off
@ -196,7 +196,7 @@ TEST(MountTreeTest, TestList) {
Mounts mounts;
// Create actual directories and files on disk and selectively add
for (const auto &test_case : kTestCases) {
for (const auto& test_case : kTestCases) {
const auto inside_path = test_case.path;
const std::string outside_path = absl::StrCat("/some/dir/", inside_path);
if (absl::EndsWith(outside_path, "/")) {
@ -244,6 +244,61 @@ TEST(MountTreeTest, TestList) {
// clang-format on
}
TEST(MountTreeTest, TestNodeEquivalence) {
MountTree::Node nodes[8];
MountTree::FileNode* fn0 = nodes[0].mutable_file_node();
fn0->set_writable(false);
fn0->set_outside("foo");
MountTree::FileNode* fn1 = nodes[1].mutable_file_node();
fn1->set_writable(false);
fn1->set_outside("bar");
MountTree::DirNode* dn0 = nodes[2].mutable_dir_node();
dn0->set_writable(false);
dn0->set_outside("foo");
MountTree::DirNode* dn1 = nodes[3].mutable_dir_node();
dn1->set_writable(false);
dn1->set_outside("bar");
MountTree::TmpfsNode* tn0 = nodes[4].mutable_tmpfs_node();
tn0->set_tmpfs_options("option1");
MountTree::TmpfsNode* tn1 = nodes[5].mutable_tmpfs_node();
tn1->set_tmpfs_options("option2");
MountTree::RootNode* rn0 = nodes[6].mutable_root_node();
rn0->set_writable(false);
MountTree::RootNode* rn1 = nodes[7].mutable_root_node();
rn1->set_writable(true);
for (const MountTree::Node n : nodes) {
ASSERT_TRUE(n.IsInitialized());
}
// Compare same file nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[0], nodes[0]));
// Compare with different file node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[0], nodes[1]));
// compare file node with dir node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[0], nodes[2]));
// Compare same dir nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[2], nodes[2]));
// Compare with different dir node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[2], nodes[3]));
// Compare dir node with tmpfs node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[2], nodes[4]));
// Compare same tmpfs nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[4], nodes[4]));
// Compare with different tmpfs nodes
EXPECT_FALSE(internal::IsEquivalentNode(nodes[4], nodes[5]));
// Compare tmpfs node with root node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[4], nodes[6]));
// Compare same root nodes
EXPECT_TRUE(internal::IsEquivalentNode(nodes[6], nodes[6]));
// Compare different root node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[6], nodes[7]));
// Compare root node with file node
EXPECT_FALSE(internal::IsEquivalentNode(nodes[6], nodes[0]));
}
TEST(MountsResolvePathTest, Files) {
Mounts mounts;
ASSERT_THAT(mounts.AddFileAt("/A", "/a"), IsOk());

View File

@ -197,11 +197,12 @@ void LogFilesystem(const std::string& dir) {
} // namespace
Namespace::Namespace(bool allow_unrestricted_networking, Mounts mounts,
std::string hostname)
std::string hostname, bool allow_mount_propagation)
: clone_flags_(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWPID |
CLONE_NEWIPC),
mounts_(std::move(mounts)),
hostname_(std::move(hostname)) {
hostname_(std::move(hostname)),
allow_mount_propagation_(allow_mount_propagation) {
if (!allow_unrestricted_networking) {
clone_flags_ |= CLONE_NEWNET;
}
@ -214,7 +215,8 @@ int32_t Namespace::GetCloneFlags() const { return clone_flags_; }
void Namespace::InitializeNamespaces(uid_t uid, gid_t gid, int32_t clone_flags,
const Mounts& mounts, bool mount_proc,
const std::string& hostname,
bool avoid_pivot_root) {
bool avoid_pivot_root,
bool allow_mount_propagation) {
if (clone_flags & CLONE_NEWUSER && !avoid_pivot_root) {
SetupIDMaps(uid, gid);
}
@ -325,8 +327,13 @@ void Namespace::InitializeNamespaces(uid_t uid, gid_t gid, int32_t clone_flags,
SAPI_RAW_PCHECK(chdir("/") == 0,
"changing cwd after mntns initialization failed");
SAPI_RAW_PCHECK(mount("/", "/", "", MS_PRIVATE | MS_REC, nullptr) == 0,
"changing mount propagation to private failed");
if (allow_mount_propagation) {
SAPI_RAW_PCHECK(mount("/", "/", "", MS_SLAVE | MS_REC, nullptr) == 0,
"changing mount propagation to slave failed");
} else {
SAPI_RAW_PCHECK(mount("/", "/", "", MS_PRIVATE | MS_REC, nullptr) == 0,
"changing mount propagation to private failed");
}
if (SAPI_VLOG_IS_ON(2)) {
SAPI_RAW_VLOG(2, "Dumping the sandboxee's filesystem:");

View File

@ -36,7 +36,8 @@ class Namespace final {
static void InitializeNamespaces(uid_t uid, gid_t gid, int32_t clone_flags,
const Mounts& mounts, bool mount_proc,
const std::string& hostname,
bool avoid_pivot_root);
bool avoid_pivot_root,
bool allow_mount_propagation);
static void InitializeInitialNamespaces(uid_t uid, gid_t gid);
Namespace() = delete;
@ -44,7 +45,7 @@ class Namespace final {
Namespace& operator=(const Namespace&) = delete;
Namespace(bool allow_unrestricted_networking, Mounts mounts,
std::string hostname);
std::string hostname, bool allow_mount_propagation);
void DisableUserNamespace();
@ -59,12 +60,15 @@ class Namespace final {
const std::string& hostname() const { return hostname_; }
bool allow_mount_propagation() const { return allow_mount_propagation_; }
private:
friend class StackTracePeer;
int32_t clone_flags_;
Mounts mounts_;
std::string hostname_;
bool allow_mount_propagation_;
};
} // namespace sandbox2

View File

@ -22,6 +22,7 @@
#include <linux/random.h> // For GRND_NONBLOCK
#include <sys/mman.h> // For mmap arguments
#include <sys/socket.h>
#include <sys/statvfs.h>
#include <syscall.h>
#include <array>
@ -72,6 +73,15 @@ bool CheckBpfBounds(const sock_filter& filter, size_t max_jmp) {
return true;
}
bool IsOnReadOnlyDev(const std::string& path) {
struct statvfs vfs;
if (TEMP_FAILURE_RETRY(statvfs(path.c_str(), &vfs)) == -1) {
PLOG(ERROR) << "Could not statvfs: " << path.c_str();
return false;
}
return vfs.f_flag & ST_RDONLY;
}
} // namespace
PolicyBuilder& PolicyBuilder::AllowSyscall(uint32_t num) {
@ -106,6 +116,36 @@ PolicyBuilder& PolicyBuilder::BlockSyscallWithErrno(uint32_t num, int error) {
return *this;
}
PolicyBuilder& PolicyBuilder::OverridableBlockSyscallWithErrno(uint32_t num,
int error) {
overridable_policy_.insert(overridable_policy_.end(),
{SYSCALL(num, ERRNO(error))});
return *this;
}
PolicyBuilder& PolicyBuilder::AllowEpoll() {
return AllowSyscalls({
#ifdef __NR_epoll_create
__NR_epoll_create,
#endif
#ifdef __NR_epoll_create1
__NR_epoll_create1,
#endif
#ifdef __NR_epoll_ctl
__NR_epoll_ctl,
#endif
#ifdef __NR_epoll_wait
__NR_epoll_wait,
#endif
#ifdef __NR_epoll_pwait
__NR_epoll_pwait,
#endif
#ifdef __NR_epoll_pwait2
__NR_epoll_pwait2,
#endif
});
}
PolicyBuilder& PolicyBuilder::AllowExit() {
return AllowSyscalls({__NR_exit, __NR_exit_group});
}
@ -207,10 +247,21 @@ PolicyBuilder& PolicyBuilder::AllowSystemMalloc() {
PolicyBuilder& PolicyBuilder::AllowLlvmSanitizers() {
if constexpr (sapi::sanitizers::IsAny()) {
// *san use a custom allocator that runs mmap under the hood. For example:
// *san use a custom allocator that runs mmap/unmap under the hood. For
// example:
// https://github.com/llvm/llvm-project/blob/596d534ac3524052df210be8d3c01a33b2260a42/compiler-rt/lib/asan/asan_allocator.cpp#L980
// https://github.com/llvm/llvm-project/blob/62ec4ac90738a5f2d209ed28c822223e58aaaeb7/compiler-rt/lib/sanitizer_common/sanitizer_allocator_secondary.h#L98
AllowMmap();
AllowSyscall(__NR_munmap);
// https://github.com/llvm/llvm-project/blob/4bbc3290a25c0dc26007912a96e0f77b2092ee56/compiler-rt/lib/sanitizer_common/sanitizer_stack_store.cpp#L293
AddPolicyOnSyscall(__NR_mprotect,
{
ARG_32(2),
BPF_STMT(BPF_AND | BPF_ALU | BPF_K,
~uint32_t{PROT_READ | PROT_WRITE}),
JEQ32(PROT_NONE, ALLOW),
});
AddPolicyOnSyscall(__NR_madvise, {
ARG_32(2),
@ -218,22 +269,18 @@ PolicyBuilder& PolicyBuilder::AllowLlvmSanitizers() {
JEQ32(MADV_NOHUGEPAGE, ALLOW),
});
// Sanitizers read from /proc. For example:
// https://github.com/llvm-mirror/compiler-rt/blob/69445f095c22aac2388f939bedebf224a6efcdaf/lib/sanitizer_common/sanitizer_linux.cpp#L1101
// https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L1155
AddDirectory("/proc");
// Sanitizers need pid for reports. For example:
// https://github.com/llvm/llvm-project/blob/634da7a1c61ee8c173e90a841eb1f4ea03caa20b/compiler-rt/lib/sanitizer_common/sanitizer_linux.cpp#L740
AllowGetPIDs();
// Sanitizers may try color output. For example:
// https://github.com/llvm/llvm-project/blob/87dd3d350c4ce0115b2cdf91d85ddd05ae2661aa/compiler-rt/lib/sanitizer_common/sanitizer_posix_libcdep.cpp#L157
OverridableBlockSyscallWithErrno(__NR_ioctl, EPERM);
}
if constexpr (sapi::sanitizers::IsASan()) {
AllowSyscall(__NR_sigaltstack);
}
if constexpr (sapi::sanitizers::IsTSan()) {
AllowSyscall(__NR_munmap);
AddPolicyOnSyscall(__NR_mprotect,
{
ARG_32(2),
BPF_STMT(BPF_AND | BPF_ALU | BPF_K,
~uint32_t{PROT_READ | PROT_WRITE}),
JEQ32(0, ALLOW),
});
}
return *this;
}
@ -647,10 +694,10 @@ PolicyBuilder& PolicyBuilder::AllowStaticStartup() {
#endif
if constexpr (sapi::host_cpu::IsArm64()) {
BlockSyscallWithErrno(__NR_readlinkat, ENOENT);
OverridableBlockSyscallWithErrno(__NR_readlinkat, ENOENT);
}
#ifdef __NR_readlink
BlockSyscallWithErrno(__NR_readlink, ENOENT);
OverridableBlockSyscallWithErrno(__NR_readlink, ENOENT);
#endif
AddPolicyOnSyscall(__NR_mprotect, {
@ -851,7 +898,8 @@ absl::StatusOr<std::unique_ptr<Policy>> PolicyBuilder::TryBuild() {
"Cannot set hostname without network namespaces.");
}
output->SetNamespace(absl::make_unique<Namespace>(
allow_unrestricted_networking_, std::move(mounts_), hostname_));
allow_unrestricted_networking_, std::move(mounts_), hostname_,
allow_mount_propagation_));
} else {
// Not explicitly disabling them here as this is a technical limitation in
// our stack trace collection functionality.
@ -865,6 +913,9 @@ absl::StatusOr<std::unique_ptr<Policy>> PolicyBuilder::TryBuild() {
output->collect_stacktrace_on_kill_ = collect_stacktrace_on_kill_;
output->collect_stacktrace_on_exit_ = collect_stacktrace_on_exit_;
output->user_policy_ = std::move(user_policy_);
output->user_policy_.insert(output->user_policy_.end(),
overridable_policy_.begin(),
overridable_policy_.end());
output->user_policy_handles_bpf_ = user_policy_handles_bpf_;
auto pb_description = absl::make_unique<PolicyBuilderDescription>();
@ -899,6 +950,13 @@ PolicyBuilder& PolicyBuilder::AddFileAt(absl::string_view outside,
return *this;
}
if (!is_ro && IsOnReadOnlyDev(*valid_outside)) {
SetError(absl::FailedPreconditionError(
absl::StrCat("Cannot add ", outside,
" as read-write as it's on a read-only device")));
return *this;
}
if (auto status = mounts_.AddFileAt(*valid_outside, inside, is_ro);
!status.ok()) {
SetError(
@ -955,6 +1013,13 @@ PolicyBuilder& PolicyBuilder::AddDirectoryAt(absl::string_view outside,
return *this;
}
if (!is_ro && IsOnReadOnlyDev(*valid_outside)) {
SetError(absl::FailedPreconditionError(
absl::StrCat("Cannot add ", outside,
" as read-write as it's on a read-only device")));
return *this;
}
if (absl::Status status =
mounts_.AddDirectoryAt(*valid_outside, inside, is_ro);
!status.ok()) {

View File

@ -120,6 +120,16 @@ class PolicyBuilder final {
// Appends code to block a specific syscall and setting errno.
PolicyBuilder& BlockSyscallWithErrno(uint32_t num, int error);
// Appends code to allow using epoll.
// Allows these syscalls:
// - epoll_create
// - epoll_create1
// - epoll_ctl
// - epoll_wait
// - epoll_pwait
// - epoll_pwait2
PolicyBuilder& AllowEpoll();
// Appends code to allow exiting.
// Allows these syscalls:
// - exit
@ -567,6 +577,12 @@ class PolicyBuilder final {
// Not recommended
PolicyBuilder& SetRootWritable();
// Changes mounts propagation from MS_PRIVATE to MS_SLAVE.
PolicyBuilder& DangerAllowMountPropagation() {
allow_mount_propagation_ = true;
return *this;
}
// Allows connections to this IP.
PolicyBuilder& AllowIPv4(const std::string& ip_and_mask, uint32_t port = 0);
PolicyBuilder& AllowIPv6(const std::string& ip_and_mask, uint32_t port = 0);
@ -582,6 +598,10 @@ class PolicyBuilder final {
// Allows a limited version of madvise
PolicyBuilder& AllowLimitedMadvise();
// Appends code to block a specific syscall and setting errno at the end of
// the policy - decision taken by user policy take precedence.
PolicyBuilder& OverridableBlockSyscallWithErrno(uint32_t num, int error);
PolicyBuilder& SetMounts(Mounts mounts) {
mounts_ = std::move(mounts);
return *this;
@ -599,6 +619,7 @@ class PolicyBuilder final {
bool use_namespaces_ = true;
bool requires_namespaces_ = false;
bool allow_unrestricted_networking_ = false;
bool allow_mount_propagation_ = false;
std::string hostname_ = std::string(kDefaultHostname);
bool collect_stacktrace_on_violation_ = true;
@ -609,6 +630,7 @@ class PolicyBuilder final {
// Seccomp fields
std::vector<sock_filter> user_policy_;
std::vector<sock_filter> overridable_policy_;
bool user_policy_handles_bpf_ = false;
absl::flat_hash_set<uint32_t> handled_syscalls_;

View File

@ -16,7 +16,7 @@ syntax = "proto3";
package sandbox2;
import "sandboxed_api/sandbox2/mounttree.proto";
import "sandboxed_api/sandbox2/mount_tree.proto";
enum PBViolationType {
VIOLATION_TYPE_UNSPECIFIED = 0;

View File

@ -47,9 +47,8 @@ target_link_libraries(sapi_generator PUBLIC
absl::status
absl::statusor
absl::strings
clangFormat
clangFrontendTool
clangTooling
clang
clang-cpp
sapi::fileops
sapi::status
${_sapi_generator_llvm_libs}
@ -77,7 +76,7 @@ if(SAPI_ENABLE_TESTS)
sapi::sapi
sapi::generator
sapi::status
sapi::statusor
absl::statusor
sapi::status_matchers
sapi::test_main
)

View File

@ -27,6 +27,7 @@
#include "absl/strings/strip.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/PrettyPrinter.h"
#include "clang/AST/Type.h"
#include "clang/Format/Format.h"
#include "sandboxed_api/tools/clang_generator/diagnostics.h"
@ -213,23 +214,38 @@ std::string PrintRecordTemplateArguments(const clang::CXXRecordDecl* record) {
}
// Serializes the given Clang AST declaration back into compilable source code.
std::string PrintAstDecl(const clang::Decl* decl) {
// TODO(cblichmann): Make types nicer
// - Rewrite typedef to using
// - Rewrite function pointers using std::add_pointer_t<>;
if (const auto* record = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
// For C++ classes/structs, only emit a forward declaration.
return absl::StrCat(PrintRecordTemplateArguments(record),
record->isClass() ? "class " : "struct ",
std::string(record->getName()));
}
std::string PrintDecl(const clang::Decl* decl) {
std::string pretty;
llvm::raw_string_ostream os(pretty);
decl->print(os);
return os.str();
}
// Returns the spelling for a given declaration will be emitted to the final
// header. This may rewrite declarations (like converting typedefs to using,
// etc.).
std::string GetSpelling(const clang::Decl* decl) {
// TODO(cblichmann): Make types nicer
// - Rewrite typedef to using
// - Rewrite function pointers using std::add_pointer_t<>;
if (const auto* typedef_decl = llvm::dyn_cast<clang::TypedefNameDecl>(decl)) {
// Special case: anonymous enum/struct
if (auto* tag_decl = typedef_decl->getAnonDeclWithTypedefName()) {
return absl::StrCat("typedef ", PrintDecl(tag_decl), " ",
ToStringView(typedef_decl->getName()));
}
}
if (const auto* record_decl = llvm::dyn_cast<clang::CXXRecordDecl>(decl)) {
// For C++ classes/structs, only emit a forward declaration.
return absl::StrCat(PrintRecordTemplateArguments(record_decl),
record_decl->isClass() ? "class " : "struct ",
ToStringView(record_decl->getName()));
}
return PrintDecl(decl);
}
std::string GetParamName(const clang::ParmVarDecl* decl, int index) {
if (std::string name = decl->getName().str(); !name.empty()) {
return absl::StrCat(name, "_"); // Suffix to avoid collisions
@ -305,7 +321,7 @@ absl::StatusOr<std::string> EmitFunction(const clang::FunctionDecl* decl) {
absl::StrAppend(&out, ", ", IsPointerOrReference(qual) ? "" : "&v_", name);
}
absl::StrAppend(&out, "));\nreturn ",
(returns_void ? "absl::OkStatus()" : "v_ret_.GetValue()"),
(returns_void ? "::absl::OkStatus()" : "v_ret_.GetValue()"),
";\n}\n");
return out;
}
@ -392,18 +408,27 @@ void Emitter::CollectType(clang::QualType qual) {
const std::vector<std::string> ns_path = GetNamespacePath(decl);
std::string ns_name;
if (!ns_path.empty()) {
if (const auto& ns_root = ns_path.front();
ns_root == "std" || ns_root == "sapi" || ns_root == "__gnu_cxx") {
// Filter out any and all declarations from the C++ standard library,
// from SAPI itself and from other well-known namespaces. This avoids
// re-declaring things like standard integer types, for example.
const auto& ns_root = ns_path.front();
// Filter out any and all declarations from the C++ standard library,
// from SAPI itself and from other well-known namespaces. This avoids
// re-declaring things like standard integer types, for example.
if (ns_root == "std" || ns_root == "__gnu_cxx" || ns_root == "sapi") {
return;
}
if (ns_root == "absl") {
// Skip types from Abseil that we already include in the generated
// header.
if (auto name = ToStringView(decl->getName());
name == "StatusCode" || name == "StatusToStringMode" ||
name == "CordMemoryAccounting") {
return;
}
}
ns_name = absl::StrCat(ns_path[0].empty() ? "" : " ",
absl::StrJoin(ns_path, "::"));
}
rendered_types_[ns_name].push_back(PrintAstDecl(decl));
rendered_types_[ns_name].push_back(GetSpelling(decl));
}
void Emitter::CollectFunction(clang::FunctionDecl* decl) {

View File

@ -29,6 +29,8 @@
namespace sapi {
namespace {
using ::testing::ElementsAre;
using ::testing::IsEmpty;
using ::testing::MatchesRegex;
using ::testing::SizeIs;
using ::testing::StrEq;
@ -37,6 +39,7 @@ using ::testing::StrNe;
class EmitterForTesting : public Emitter {
public:
using Emitter::functions_;
using Emitter::rendered_types_;
};
class EmitterTest : public FrontendActionTest {};
@ -57,6 +60,43 @@ TEST_F(EmitterTest, BasicFunctionality) {
EXPECT_THAT(header, IsOk());
}
TEST_F(EmitterTest, RelatedTypes) {
EmitterForTesting emitter;
RunFrontendAction(
R"(
namespace std {
using size_t = unsigned long;
} // namespace std
using std::size_t;
typedef enum { kRed, kGreen, kBlue } Color;
struct Channel {
Color color;
size_t width;
size_t height;
};
struct ByValue {
int value;
};
extern "C" void Colorize(Channel* chan, ByValue v) {}
typedef struct { int member; } MyStruct;
extern "C" void Structize(MyStruct* s);
)",
absl::make_unique<GeneratorAction>(emitter, GeneratorOptions()));
// Types from "std" should be skipped
EXPECT_THAT(emitter.rendered_types_["std"], IsEmpty());
std::vector<std::string> ugly_types;
for (const auto& type : emitter.rendered_types_[""]) {
ugly_types.push_back(Uglify(type));
}
EXPECT_THAT(ugly_types,
ElementsAre("typedef enum { kRed, kGreen, kBlue } Color",
"struct Channel", "struct ByValue",
"typedef struct { int member; } MyStruct"));
}
TEST(IncludeGuard, CreatesRandomizedGuardForEmptyFilename) {
// Copybara will transform the string. This is intentional.
constexpr absl::string_view kGeneratedHeaderPrefix =

View File

@ -17,6 +17,8 @@
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/ascii.h"
#include "absl/strings/str_replace.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/FileSystemOptions.h"
#include "clang/Frontend/FrontendAction.h"
@ -67,4 +69,10 @@ std::vector<std::string> FrontendActionTest::GetCommandLineFlagsForTesting(
"-I.", "-Wno-error", std::string(input_file)};
}
std::string Uglify(absl::string_view code) {
std::string result = absl::StrReplaceAll(code, {{"\n", " "}});
absl::RemoveExtraAsciiWhitespace(&result);
return result;
}
} // namespace sapi

View File

@ -78,6 +78,12 @@ class FrontendActionTest : public ::testing::Test {
absl::flat_hash_map<std::string, std::string> file_contents_;
};
// Flattens a piece of C++ code into one line and removes consecutive runs of
// whitespace. This makes it easier to compare code snippets for testing.
// Note: This is not syntax-aware and will replace characters within strings as
// well.
std::string Uglify(absl::string_view code);
} // namespace sapi
#endif // SANDBOXED_API_TOOLS_CLANG_GENERATOR_FRONTEND_ACTION_TEST_UTIL_H_

View File

@ -40,10 +40,6 @@ std::string ReplaceFileExtension(absl::string_view path,
return absl::StrCat(path.substr(0, pos), new_extension);
}
inline absl::string_view ToStringView(llvm::StringRef ref) {
return absl::string_view(ref.data(), ref.size());
}
} // namespace
std::string GetOutputFilename(absl::string_view source_file) {

View File

@ -21,6 +21,7 @@
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
@ -128,6 +129,10 @@ class GeneratorFactory : public clang::tooling::FrontendActionFactory {
std::string GetOutputFilename(absl::string_view source_file);
inline absl::string_view ToStringView(llvm::StringRef ref) {
return absl::string_view(ref.data(), ref.size());
}
} // namespace sapi
#endif // SANDBOXED_API_TOOLS_CLANG_GENERATOR_GENERATOR_H_

View File

@ -15,6 +15,8 @@
#include "sandboxed_api/tools/clang_generator/types.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "clang/AST/Type.h"
namespace sapi {
namespace {
@ -37,7 +39,12 @@ void TypeCollector::CollectRelatedTypes(clang::QualType qual) {
seen_.insert(qual);
if (const auto* typedef_type = qual->getAs<clang::TypedefType>()) {
CollectRelatedTypes(typedef_type->getDecl()->getUnderlyingType());
auto* typedef_decl = typedef_type->getDecl();
if (!typedef_decl->getAnonDeclWithTypedefName()) {
// Do not collect anonymous enums/structs as those are handled when
// emitting them via their parent typedef/using declaration.
CollectRelatedTypes(typedef_decl->getUnderlyingType());
}
collected_.insert(qual);
return;
}
@ -210,10 +217,10 @@ std::string MapQualTypeParameter(const clang::ASTContext& /*context*/,
std::string MapQualTypeReturn(const clang::ASTContext& context,
clang::QualType qual) {
if (qual->isVoidType()) {
return "absl::Status";
return "::absl::Status";
}
// Remove const qualifier like in MapQualType().
return absl::StrCat("absl::StatusOr<",
return absl::StrCat("::absl::StatusOr<",
MaybeRemoveConst(context, qual).getAsString(), ">");
}

View File

@ -35,7 +35,7 @@
if (ABSL_PREDICT_FALSE(!status.ok())) { \
return status; \
} \
} while (0);
} while (0)
#define SAPI_ASSIGN_OR_RETURN(lhs, rexpr) \
SAPI_ASSIGN_OR_RETURN_IMPL( \
@ -46,6 +46,6 @@
if (ABSL_PREDICT_FALSE(!statusor.ok())) { \
return statusor.status(); \
} \
lhs = std::move(statusor).value();
lhs = std::move(statusor).value()
#endif // THIRD_PARTY_SAPI_UTIL_STATUS_MACROS_H_

View File

@ -35,7 +35,7 @@ namespace sapi::v {
template <typename T>
class Proto : public Var {
public:
static_assert(std::is_base_of<google::protobuf::Message, T>::value,
static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value,
"Template argument must be a proto message");
ABSL_DEPRECATED("Use Proto<>::FromMessage() instead")