mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
Merge branch 'main' into zip
This commit is contained in:
commit
071cb79268
10
.github/workflows/fedora-cmake.yml
vendored
10
.github/workflows/fedora-cmake.yml
vendored
|
@ -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.
|
||||
|
|
7
.github/workflows/ubuntu-cmake.yml
vendored
7
.github/workflows/ubuntu-cmake.yml
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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})
|
||||
|
|
@ -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
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {
|
|
@ -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()) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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:");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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_;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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(), ">");
|
||||
}
|
||||
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue
Block a user