Add public C API to send request to CompOS
The API allows a client to connect to a CID (assumed privileged, more
below) and disconnect from it. The client can send an opaque byte
array to the "compile" method, which will internall forward the opaque
byte array to someone who could unmarshal (not guaranteed, since the
client may be untrusted).
This API is designed for ART (which is another APEX) to marshal its own
private compilation arugments to execute in the VM.
In a sense, what matters to the service in the API is the file
descriptor lists. It is what compsvc used to set up the "remote FDs".
Other than preparing the FDs, the rest is to execute the actual program.
In this implementation, ART's code is responsible for providing the
command line arguments from the "dexopt context".
Bug: 193668901
Test: atest ComposHostTestCases
Change-Id: I614b03d3d9916a624237b63925e0168e2146b32a
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 547fd44..3a9d5f5 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -38,12 +38,19 @@
platform_apis: true,
binaries: [
+ // Used in Android
"compos_key_cmd",
"compos_verify_key",
"composd",
"composd_cmd",
+ "pvm_exec", // to be superseded by libcompos_client
+
+ // Used in VM
"compsvc",
- "pvm_exec",
+ ],
+
+ native_shared_libs: [
+ "libcompos_client",
],
apps: [
diff --git a/compos/composd/aidl/Android.bp b/compos/composd/aidl/Android.bp
index 0352001..8116632 100644
--- a/compos/composd/aidl/Android.bp
+++ b/compos/composd/aidl/Android.bp
@@ -18,5 +18,11 @@
"com.android.compos",
],
},
+ ndk: {
+ enabled: true,
+ apex_available: [
+ "com.android.compos",
+ ],
+ },
},
}
diff --git a/compos/libcompos_client/Android.bp b/compos/libcompos_client/Android.bp
new file mode 100644
index 0000000..c9a0e5f
--- /dev/null
+++ b/compos/libcompos_client/Android.bp
@@ -0,0 +1,23 @@
+cc_library {
+ name: "libcompos_client",
+ srcs: ["libcompos_client.cc"],
+ min_sdk_version: "apex_inherit",
+ shared_libs: [
+ "android.system.composd-ndk",
+ "compos_aidl_interface-ndk",
+ "libbase",
+ "libbinder_ndk",
+ "libbinder_rpc_unstable",
+ ],
+ export_include_dirs: ["include"],
+ stubs: {
+ symbol_file: "libcompos_client.map.txt",
+ },
+ apex_available: [
+ "com.android.compos",
+ ],
+ visibility: [
+ "//packages/modules/Virtualization/compos:__subpackages__",
+ "//art/odrefresh:__subpackages__",
+ ],
+}
diff --git a/compos/libcompos_client/include/libcompos_client.h b/compos/libcompos_client/include/libcompos_client.h
new file mode 100644
index 0000000..171854d
--- /dev/null
+++ b/compos/libcompos_client/include/libcompos_client.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Sends request encoded in a marshaled byte buffer to the Compilation OS service, which will
+ * execute the compiler with context encoded in the marshaled byte buffer.
+ *
+ * @param cid the VM's cid to send the request to.
+ * @param marshaled pointer to a marshaled byte buffer.
+ * @param size size of the marshaled byte buffer pointed by `marshaled`.
+ * @param ro_fds pointer to a int array of read-only file descriptor numbers.
+ * @param ro_fds_num size of the array pointed by `ro_fds`.
+ * @param rw_fds pointer to a int array of read-writable file descriptor numbers.
+ * @param rw_fds_num size of the array pointed by `rw_fds`.
+ * @return the exit code of the compiler.
+ *
+ * Available since API level 33.
+ */
+int AComposClient_Request(int cid, const uint8_t* marshaled, size_t size, const int* ro_fds,
+ size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num)
+ __INTRODUCED_IN(33);
+
+__END_DECLS
diff --git a/compos/libcompos_client/libcompos_client.cc b/compos/libcompos_client/libcompos_client.cc
new file mode 100644
index 0000000..147fcd0
--- /dev/null
+++ b/compos/libcompos_client/libcompos_client.cc
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "libcompos_client.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_manager.h>
+#include <binder/IInterface.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <binder_rpc_unstable.hpp>
+#include <memory>
+
+#include "aidl/android/system/composd/IIsolatedCompilationService.h"
+#include "aidl/com/android/compos/FdAnnotation.h"
+#include "aidl/com/android/compos/ICompOsService.h"
+
+using aidl::android::system::composd::IIsolatedCompilationService;
+using aidl::com::android::compos::FdAnnotation;
+using aidl::com::android::compos::ICompOsService;
+using android::base::Join;
+using android::base::Pipe;
+using android::base::unique_fd;
+
+namespace {
+
+constexpr unsigned int kCompsvcRpcPort = 6432;
+constexpr const char* kComposdServiceName = "android.system.composd";
+
+void ExecFdServer(const int* ro_fds, size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num,
+ unique_fd ready_fd) {
+ // Holder of C Strings, with enough memory reserved to avoid reallocation. Otherwise,
+ // `holder.rbegin()->c_str()` may become invalid.
+ std::vector<std::string> holder;
+ holder.reserve(ro_fds_num + rw_fds_num + 1 /* for --ready-fd */);
+
+ std::vector<char const*> args = {"/apex/com.android.virt/bin/fd_server"};
+ for (int i = 0; i < ro_fds_num; ++i) {
+ args.emplace_back("--ro-fds");
+ holder.emplace_back(std::to_string(*(ro_fds + i)));
+ args.emplace_back(holder.rbegin()->c_str());
+ }
+ for (int i = 0; i < rw_fds_num; ++i) {
+ args.emplace_back("--rw-fds");
+ holder.emplace_back(std::to_string(*(rw_fds + i)));
+ args.emplace_back(holder.rbegin()->c_str());
+ }
+ args.emplace_back("--ready-fd");
+ holder.emplace_back(std::to_string(ready_fd.get()));
+ args.emplace_back(holder.rbegin()->c_str());
+
+ LOG(DEBUG) << "Starting fd_server, args: " << Join(args, ' ');
+ args.emplace_back(nullptr);
+ if (execv(args[0], const_cast<char* const*>(args.data())) < 0) {
+ PLOG(ERROR) << "execv failed";
+ }
+}
+
+class FileSharingSession final {
+public:
+ static std::unique_ptr<FileSharingSession> Create(const int* ro_fds, size_t ro_fds_num,
+ const int* rw_fds, size_t rw_fds_num) {
+ // Create pipe for receiving a ready ping from fd_server.
+ unique_fd pipe_read, pipe_write;
+ if (!Pipe(&pipe_read, &pipe_write, /* flags= */ 0)) {
+ PLOG(ERROR) << "Cannot create pipe";
+ return nullptr;
+ }
+
+ pid_t pid = fork();
+ if (pid < 0) {
+ PLOG(ERROR) << "fork error";
+ return nullptr;
+ } else if (pid > 0) {
+ pipe_write.reset();
+
+ // When fd_server is ready it closes its end of the pipe. And if it exits, the pipe is
+ // also closed. Either way this read will return 0 bytes at that point, and there's no
+ // point waiting any longer.
+ char c;
+ read(pipe_read.get(), &c, sizeof(c));
+
+ std::unique_ptr<FileSharingSession> session(new FileSharingSession(pid));
+ return session;
+ } else if (pid == 0) {
+ pipe_read.reset();
+ ExecFdServer(ro_fds, ro_fds_num, rw_fds, rw_fds_num, std::move(pipe_write));
+ exit(EXIT_FAILURE);
+ }
+ return nullptr;
+ }
+
+ ~FileSharingSession() {
+ if (kill(fd_server_pid_, SIGTERM) < 0) {
+ PLOG(ERROR) << "Cannot kill fd_server (pid " << std::to_string(fd_server_pid_)
+ << ") with SIGTERM. Retry with SIGKILL.";
+ if (kill(fd_server_pid_, SIGKILL) < 0) {
+ PLOG(ERROR) << "Still cannot terminate with SIGKILL. Give up.";
+ // TODO: it may be the safest if we turn fd_server into a library to run in a
+ // thread.
+ }
+ }
+ }
+
+private:
+ explicit FileSharingSession(pid_t pid) : fd_server_pid_(pid) {}
+
+ pid_t fd_server_pid_;
+};
+
+int MakeRequestToVM(int cid, const uint8_t* marshaled, size_t size, const int* ro_fds,
+ size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num) {
+ ndk::SpAIBinder binder(RpcClient(cid, kCompsvcRpcPort));
+ std::shared_ptr<ICompOsService> service = ICompOsService::fromBinder(binder);
+ if (!service) {
+ LOG(ERROR) << "Cannot connect to the service";
+ return -1;
+ }
+
+ std::unique_ptr<FileSharingSession> session_raii =
+ FileSharingSession::Create(ro_fds, ro_fds_num, rw_fds, rw_fds_num);
+ if (!session_raii) {
+ LOG(ERROR) << "Cannot start to share FDs";
+ return -1;
+ }
+
+ // Since the input from the C API are raw pointers, we need to duplicate them into vectors in
+ // order to pass to the binder API.
+ std::vector<uint8_t> duplicated_buffer(marshaled, marshaled + size);
+ FdAnnotation fd_annotation = {
+ .input_fds = std::vector<int>(ro_fds, ro_fds + ro_fds_num),
+ .output_fds = std::vector<int>(rw_fds, rw_fds + rw_fds_num),
+ };
+ int8_t exit_code;
+ ndk::ScopedAStatus status = service->compile(duplicated_buffer, fd_annotation, &exit_code);
+ if (!status.isOk()) {
+ LOG(ERROR) << "Compilation failed (exit " << std::to_string(exit_code)
+ << "): " << status.getDescription();
+ return -1;
+ }
+ return 0;
+}
+
+int MakeRequestToComposd(const uint8_t* marshaled, size_t size, const int* ro_fds,
+ size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num) {
+ ndk::SpAIBinder binder(AServiceManager_getService(kComposdServiceName));
+ std::shared_ptr<IIsolatedCompilationService> service =
+ IIsolatedCompilationService::fromBinder(binder);
+ if (!service) {
+ LOG(ERROR) << "Cannot connect to the service";
+ return -1;
+ }
+
+ auto session_raii = std::unique_ptr<FileSharingSession>(
+ FileSharingSession::Create(ro_fds, ro_fds_num, rw_fds, rw_fds_num));
+ if (!session_raii) {
+ LOG(ERROR) << "Cannot start to share FDs";
+ return -1;
+ }
+
+ // Since the input from the C API are raw pointers, we need to duplicate them into vectors in
+ // order to pass to the binder API.
+ std::vector<uint8_t> duplicated_buffer(marshaled, marshaled + size);
+ FdAnnotation fd_annotation = {
+ .input_fds = std::vector<int>(ro_fds, ro_fds + ro_fds_num),
+ .output_fds = std::vector<int>(rw_fds, rw_fds + rw_fds_num),
+ };
+ int8_t exit_code;
+ ndk::ScopedAStatus status = service->compile(duplicated_buffer, fd_annotation, &exit_code);
+ if (!status.isOk()) {
+ LOG(ERROR) << "Compilation failed (exit " << std::to_string(exit_code)
+ << "): " << status.getDescription();
+ return -1;
+ }
+ return 0;
+}
+
+} // namespace
+
+__BEGIN_DECLS
+
+int AComposClient_Request(int cid, const uint8_t* marshaled, size_t size, const int* ro_fds,
+ size_t ro_fds_num, const int* rw_fds, size_t rw_fds_num) {
+ if (cid == -1 /* VMADDR_CID_ANY */) {
+ return MakeRequestToComposd(marshaled, size, ro_fds, ro_fds_num, rw_fds, rw_fds_num);
+ } else {
+ return MakeRequestToVM(cid, marshaled, size, ro_fds, ro_fds_num, rw_fds, rw_fds_num);
+ }
+}
+
+__END_DECLS
diff --git a/compos/libcompos_client/libcompos_client.map.txt b/compos/libcompos_client/libcompos_client.map.txt
new file mode 100644
index 0000000..9d47c53
--- /dev/null
+++ b/compos/libcompos_client/libcompos_client.map.txt
@@ -0,0 +1,6 @@
+LIBCOMPOS_CLIENT {
+ global:
+ AComposClient_Request; # apex
+ local:
+ *;
+};